first commit
This commit is contained in:
@@ -0,0 +1,570 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Admin;
|
||||
|
||||
use Elementor\Api;
|
||||
use Elementor\Core\Base\Module;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Tracker;
|
||||
use Elementor\User;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Admin_Notices extends Module {
|
||||
|
||||
private $notices = [
|
||||
'api_notice',
|
||||
'api_upgrade_plugin',
|
||||
'tracker',
|
||||
'rate_us_feedback',
|
||||
'woocommerce_promote',
|
||||
'cf7_promote',
|
||||
'mc4wp_promote',
|
||||
'popup_maker_promote',
|
||||
'role_manager_promote',
|
||||
];
|
||||
|
||||
private $elementor_pages_count = null;
|
||||
|
||||
private $install_time = null;
|
||||
|
||||
private $current_screen_id = null;
|
||||
|
||||
private function get_install_time() {
|
||||
if ( null === $this->install_time ) {
|
||||
$this->install_time = Plugin::$instance->get_install_time();
|
||||
}
|
||||
|
||||
return $this->install_time;
|
||||
}
|
||||
|
||||
private function get_elementor_pages_count() {
|
||||
if ( null === $this->elementor_pages_count ) {
|
||||
$elementor_pages = new \WP_Query( [
|
||||
'post_type' => 'any',
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
'update_post_meta_cache' => false,
|
||||
'update_post_term_cache' => false,
|
||||
'meta_key' => '_elementor_edit_mode',
|
||||
'meta_value' => 'builder',
|
||||
] );
|
||||
|
||||
$this->elementor_pages_count = $elementor_pages->post_count;
|
||||
}
|
||||
|
||||
return $this->elementor_pages_count;
|
||||
}
|
||||
|
||||
private function notice_api_upgrade_plugin() {
|
||||
$upgrade_notice = Api::get_upgrade_notice();
|
||||
if ( empty( $upgrade_notice ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'update_plugins' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_elementor', 'edit-elementor_library', 'elementor_page_elementor-system-info', 'dashboard' ], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if have any upgrades.
|
||||
$update_plugins = get_site_transient( 'update_plugins' );
|
||||
|
||||
$has_remote_update_package = ! ( empty( $update_plugins ) || empty( $update_plugins->response[ ELEMENTOR_PLUGIN_BASE ] ) || empty( $update_plugins->response[ ELEMENTOR_PLUGIN_BASE ]->package ) );
|
||||
|
||||
if ( ! $has_remote_update_package && empty( $upgrade_notice['update_link'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $has_remote_update_package ) {
|
||||
$product = $update_plugins->response[ ELEMENTOR_PLUGIN_BASE ];
|
||||
|
||||
$details_url = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $product->slug . '§ion=changelog&TB_iframe=true&width=600&height=800' );
|
||||
$upgrade_url = wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . ELEMENTOR_PLUGIN_BASE ), 'upgrade-plugin_' . ELEMENTOR_PLUGIN_BASE );
|
||||
$new_version = $product->new_version;
|
||||
} else {
|
||||
$upgrade_url = $upgrade_notice['update_link'];
|
||||
$details_url = $upgrade_url;
|
||||
|
||||
$new_version = $upgrade_notice['version'];
|
||||
}
|
||||
|
||||
// Check if have upgrade notices to show.
|
||||
if ( version_compare( ELEMENTOR_VERSION, $upgrade_notice['version'], '>=' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$notice_id = 'upgrade_notice_' . $upgrade_notice['version'];
|
||||
if ( User::is_user_notice_viewed( $notice_id ) ) {
|
||||
return false;
|
||||
}
|
||||
?>
|
||||
<div class="notice updated is-dismissible elementor-message elementor-message-dismissed" data-notice_id="<?php echo esc_attr( $notice_id ); ?>">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<strong><?php echo __( 'Update Notification', 'elementor' ); ?></strong>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: Details URL, 2: Accessibility text, 3: Version number, 4: Update URL, 5: Accessibility text */
|
||||
__( 'There is a new version of Elementor Page Builder available. <a href="%1$s" class="thickbox open-plugin-details-modal" aria-label="%2$s">View version %3$s details</a> or <a href="%4$s" class="update-link" aria-label="%5$s">update now</a>.', 'elementor' ),
|
||||
esc_url( $details_url ),
|
||||
esc_attr( sprintf(
|
||||
/* translators: %s: Elementor version */
|
||||
__( 'View Elementor version %s details', 'elementor' ),
|
||||
$new_version
|
||||
) ),
|
||||
$new_version,
|
||||
esc_url( $upgrade_url ),
|
||||
esc_attr( __( 'Update Elementor Now', 'elementor' ) )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="elementor-message-action">
|
||||
<a class="button elementor-button" href="<?php echo $upgrade_url; ?>">
|
||||
<i class="dashicons dashicons-update" aria-hidden="true"></i>
|
||||
<?php echo __( 'Update Now', 'elementor' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notice_api_notice() {
|
||||
$admin_notice = Api::get_admin_notice();
|
||||
if ( empty( $admin_notice ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_elementor', 'edit-elementor_library', 'elementor_page_elementor-system-info', 'dashboard' ], true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$notice_id = 'admin_notice_api_' . $admin_notice['notice_id'];
|
||||
if ( User::is_user_notice_viewed( $notice_id ) ) {
|
||||
return false;
|
||||
}
|
||||
?>
|
||||
<div class="notice is-dismissible updated elementor-message-dismissed elementor-message-announcement" data-notice_id="<?php echo esc_attr( $notice_id ); ?>">
|
||||
<p><?php echo $admin_notice['notice_text']; ?></p>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notice_tracker() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show tracker notice after 24 hours from installed time.
|
||||
if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( '1' === get_option( 'elementor_tracker_notice' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( Tracker::is_allow_track() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 2 > $this->get_elementor_pages_count() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Skip for development env.
|
||||
$optin_url = wp_nonce_url( add_query_arg( 'elementor_tracker', 'opt_into' ), 'opt_into' );
|
||||
$optout_url = wp_nonce_url( add_query_arg( 'elementor_tracker', 'opt_out' ), 'opt_out' );
|
||||
|
||||
$tracker_description_text = __( 'Love using Elementor? Become a super contributor by opting in to share non-sensitive plugin data and to receive periodic email updates from us.', 'elementor' );
|
||||
|
||||
/**
|
||||
* Tracker admin description text.
|
||||
*
|
||||
* Filters the admin notice text for non-sensitive data collection.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $tracker_description_text Description text displayed in admin notice.
|
||||
*/
|
||||
$tracker_description_text = apply_filters( 'elementor/tracker/admin_description_text', $tracker_description_text );
|
||||
?>
|
||||
<div class="notice updated elementor-message">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<p><?php echo esc_html( $tracker_description_text ); ?> <a href="https://go.elementor.com/usage-data-tracking/" target="_blank"><?php echo __( 'Learn more.', 'elementor' ); ?></a></p>
|
||||
<p class="elementor-message-actions">
|
||||
<a href="<?php echo $optin_url; ?>" class="button button-primary"><?php echo __( 'Sure! I\'d love to help', 'elementor' ); ?></a> <a href="<?php echo $optout_url; ?>" class="button-secondary"><?php echo __( 'No thanks', 'elementor' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notice_rate_us_feedback() {
|
||||
$notice_id = 'rate_us_feedback';
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'dashboard' !== $this->current_screen_id || User::is_user_notice_viewed( $notice_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 10 >= $this->get_elementor_pages_count() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dismiss_url = add_query_arg( [
|
||||
'action' => 'elementor_set_admin_notice_viewed',
|
||||
'notice_id' => esc_attr( $notice_id ),
|
||||
], admin_url( 'admin-post.php' ) );
|
||||
|
||||
?>
|
||||
<div class="notice updated is-dismissible elementor-message elementor-message-dismissed" data-notice_id="<?php echo esc_attr( $notice_id ); ?>">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<p><strong><?php echo __( 'Congrats!', 'elementor' ); ?></strong> <?php _e( 'You created over 10 pages with Elementor. Great job! If you can spare a minute, please help us by leaving a five star review on WordPress.org.', 'elementor' ); ?></p>
|
||||
<p class="elementor-message-actions">
|
||||
<a href="https://go.elementor.com/admin-review/" target="_blank" class="button button-primary"><?php _e( 'Happy To Help', 'elementor' ); ?></a>
|
||||
<a href="<?php echo esc_url_raw( $dismiss_url ); ?>" class="button elementor-button-notice-dismiss"><?php _e( 'Hide Notification', 'elementor' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notice_woocommerce_promote() {
|
||||
$notice_id = 'woocommerce_promote';
|
||||
|
||||
if ( Utils::has_pro() || ! function_exists( 'WC' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $this->current_screen_id, [ 'edit-product', 'woocommerce_page_wc-settings' ], true ) || User::is_user_notice_viewed( $notice_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strtotime( '2019-08-01' ) > $this->get_install_time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="notice updated is-dismissible elementor-message elementor-message-dismissed" data-notice_id="<?php echo esc_attr( $notice_id ); ?>">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<p><?php _e( 'Using WooCommerce? With Elementor Pro’s WooCommerce Builder, you’ll be able to design your store without coding!', 'elementor' ); ?></p>
|
||||
<p class="elementor-message-actions">
|
||||
<a href="https://go.elementor.com/plugin-promotion-woocommerce/" target="_blank" class="button button-secondary"><?php _e( 'Learn More', 'elementor' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notice_cf7_promote() {
|
||||
$notice_id = 'cf7_promote';
|
||||
|
||||
if ( Utils::has_pro() || ! defined( 'WPCF7_VERSION' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_wpcf7', 'contact_page_wpcf7-integration' ], true ) || User::is_user_notice_viewed( $notice_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strtotime( '2019-08-01' ) > $this->get_install_time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="notice updated is-dismissible elementor-message elementor-message-dismissed" data-notice_id="<?php echo esc_attr( $notice_id ); ?>">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<p><?php _e( 'Using Elementor & Contact Form 7? Try out Elementor Pro and design your forms visually with one powerful tool.', 'elementor' ); ?></p>
|
||||
<p class="elementor-message-actions">
|
||||
<a href="https://go.elementor.com/plugin-promotion-contactform7/" target="_blank" class="button button-secondary"><?php _e( 'Learn More', 'elementor' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notice_mc4wp_promote() {
|
||||
$notice_id = 'mc4wp_promote';
|
||||
|
||||
if ( Utils::has_pro() || ! defined( 'MC4WP_VERSION' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $this->current_screen_id, [ 'toplevel_page_mailchimp-for-wp', 'mc4wp_page_mailchimp-for-wp-forms', 'mc4wp_page_mailchimp-for-wp-integrations', 'mc4wp_page_mailchimp-for-wp-other', 'mc4wp_page_mailchimp-for-wp-extensions' ], true ) || User::is_user_notice_viewed( $notice_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strtotime( '2019-08-01' ) > $this->get_install_time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="notice updated is-dismissible elementor-message elementor-message-dismissed" data-notice_id="<?php echo esc_attr( $notice_id ); ?>">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<p><?php _e( 'Want to design better MailChimp forms? Use Elementor Pro and enjoy unlimited integrations, visual design, templates and more.', 'elementor' ); ?></p>
|
||||
<p class="elementor-message-actions">
|
||||
<a href="https://go.elementor.com/plugin-promotion-mc4wp/" target="_blank" class="button button-secondary"><?php _e( 'Learn More', 'elementor' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notice_popup_maker_promote() {
|
||||
$notice_id = 'popup_maker_promote';
|
||||
|
||||
if ( Utils::has_pro() || ! class_exists( 'Popup_Maker' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $this->current_screen_id, [ 'edit-popup', 'popup_page_pum-settings' ], true ) || User::is_user_notice_viewed( $notice_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strtotime( '2019-08-01' ) > $this->get_install_time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( strtotime( '+24 hours', $this->get_install_time() ) > time() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="notice updated is-dismissible elementor-message elementor-message-dismissed" data-notice_id="<?php echo esc_attr( $notice_id ); ?>">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<p><?php _e( 'Using popups on your site? Build outstanding popups using Elementor Pro and get more leads, sales and subscribers.', 'elementor' ); ?></p>
|
||||
<p class="elementor-message-actions">
|
||||
<a href="https://go.elementor.com/plugin-promotion-popupmaker/" target="_blank" class="button button-secondary"><?php _e( 'Learn More', 'elementor' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function notice_role_manager_promote() {
|
||||
$notice_id = 'role_manager_promote';
|
||||
|
||||
if ( Utils::has_pro() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'elementor_page_elementor-role-manager' !== $this->current_screen_id || User::is_user_notice_viewed( $notice_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$users = new \WP_User_Query( [
|
||||
'fields' => 'ID',
|
||||
'number' => 10,
|
||||
] );
|
||||
|
||||
if ( 5 > $users->get_total() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="notice updated is-dismissible elementor-message elementor-message-dismissed" data-notice_id="<?php echo esc_attr( $notice_id ); ?>">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<p><?php _e( 'Managing a multi-user site? With Elementor Pro, you can control user access and make sure no one messes up your design.', 'elementor' ); ?></p>
|
||||
<p class="elementor-message-actions">
|
||||
<a href="https://go.elementor.com/promotion-role-manager/" target="_blank" class="button button-secondary"><?php _e( 'Learn More', 'elementor' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* @TODO: Rewrite this method markup and use it for every admin notice
|
||||
*/
|
||||
public function print_admin_notice( array $options ) {
|
||||
$default_options = [
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
'button' => [
|
||||
'text' => '',
|
||||
'url' => '',
|
||||
'class' => 'elementor-button',
|
||||
],
|
||||
];
|
||||
|
||||
$options = array_replace_recursive( $default_options, $options );
|
||||
?>
|
||||
<div class="notice elementor-message">
|
||||
<div class="elementor-message-inner">
|
||||
<div class="elementor-message-icon">
|
||||
<div class="e-logo-wrapper">
|
||||
<i class="eicon-elementor" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="elementor-message-content">
|
||||
<?php if ( $options['title'] ) { ?>
|
||||
<strong><?php echo $options['title']; ?></strong>
|
||||
<?php } ?>
|
||||
<?php if ( $options['description'] ) { ?>
|
||||
<p><?php echo $options['description']; ?></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php if ( $options['button']['text'] ) { ?>
|
||||
<div class="elementor-message-action">
|
||||
<a class="<?php echo $options['button']['class']; ?>" href="<?php echo esc_url( $options['button']['url'] ); ?>"><?php echo $options['button']['text']; ?></a>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function admin_notices() {
|
||||
$this->install_time = Plugin::$instance->get_install_time();
|
||||
$this->current_screen_id = get_current_screen()->id;
|
||||
|
||||
foreach ( $this->notices as $notice ) {
|
||||
$method_callback = "notice_{$notice}";
|
||||
if ( $this->$method_callback() ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.9.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'admin_notices', [ $this, 'admin_notices' ], 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 2.9.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'admin-notices';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,734 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Admin;
|
||||
|
||||
use Elementor\Api;
|
||||
use Elementor\Beta_Testers;
|
||||
use Elementor\Core\Base\App;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Settings;
|
||||
use Elementor\User;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Admin extends App {
|
||||
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.2.0
|
||||
* @access public
|
||||
*/
|
||||
public function maybe_redirect_to_getting_started() {
|
||||
if ( ! get_transient( 'elementor_activation_redirect' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( wp_doing_ajax() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete_transient( 'elementor_activation_redirect' );
|
||||
|
||||
if ( is_network_admin() || isset( $_GET['activate-multi'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$has_elementor_page = ! ! $wpdb->get_var( "SELECT `post_id` FROM `{$wpdb->postmeta}` WHERE `meta_key` = '_elementor_edit_mode' LIMIT 1;" );
|
||||
|
||||
if ( $has_elementor_page ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=elementor-getting-started' ) );
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin scripts.
|
||||
*
|
||||
* Registers all the admin scripts and enqueues them.
|
||||
*
|
||||
* Fired by `admin_enqueue_scripts` action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
wp_register_script(
|
||||
'elementor-admin',
|
||||
$this->get_js_assets_url( 'admin' ),
|
||||
[
|
||||
'elementor-common',
|
||||
],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script( 'elementor-admin' );
|
||||
|
||||
$this->print_config();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue admin styles.
|
||||
*
|
||||
* Registers all the admin styles and enqueues them.
|
||||
*
|
||||
* Fired by `admin_enqueue_scripts` action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function enqueue_styles() {
|
||||
$direction_suffix = is_rtl() ? '-rtl' : '';
|
||||
|
||||
wp_register_style(
|
||||
'elementor-admin',
|
||||
$this->get_css_assets_url( 'admin' . $direction_suffix ),
|
||||
[
|
||||
'elementor-common',
|
||||
],
|
||||
ELEMENTOR_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_style( 'elementor-admin' );
|
||||
|
||||
// It's for upgrade notice.
|
||||
// TODO: enqueue this just if needed.
|
||||
add_thickbox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Print switch mode button.
|
||||
*
|
||||
* Adds a switch button in post edit screen (which has cpt support). To allow
|
||||
* the user to switch from the native WordPress editor to Elementor builder.
|
||||
*
|
||||
* Fired by `edit_form_after_title` action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param \WP_Post $post The current post object.
|
||||
*/
|
||||
public function print_switch_mode_button( $post ) {
|
||||
// Exit if Gutenberg are active.
|
||||
if ( did_action( 'enqueue_block_editor_assets' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$document = Plugin::$instance->documents->get( $post->ID );
|
||||
|
||||
if ( ! $document || ! $document->is_editable_by_current_user() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_nonce_field( basename( __FILE__ ), '_elementor_edit_mode_nonce' );
|
||||
?>
|
||||
<div id="elementor-switch-mode">
|
||||
<input id="elementor-switch-mode-input" type="hidden" name="_elementor_post_mode" value="<?php echo $document->is_built_with_elementor(); ?>" />
|
||||
<button id="elementor-switch-mode-button" type="button" class="button button-primary button-hero">
|
||||
<span class="elementor-switch-mode-on">
|
||||
<i class="eicon-arrow-<?php echo ( is_rtl() ) ? 'right' : 'left'; ?>" aria-hidden="true"></i>
|
||||
<?php echo __( 'Back to WordPress Editor', 'elementor' ); ?>
|
||||
</span>
|
||||
<span class="elementor-switch-mode-off">
|
||||
<i class="eicon-elementor-square" aria-hidden="true"></i>
|
||||
<?php echo __( 'Edit with Elementor', 'elementor' ); ?>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="elementor-editor">
|
||||
<a id="elementor-go-to-edit-page-link" href="<?php echo $document->get_edit_url(); ?>">
|
||||
<div id="elementor-editor-button" class="button button-primary button-hero">
|
||||
<i class="eicon-elementor-square" aria-hidden="true"></i>
|
||||
<?php echo __( 'Edit with Elementor', 'elementor' ); ?>
|
||||
</div>
|
||||
<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 __( 'Loading', 'elementor' ); ?></div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save post.
|
||||
*
|
||||
* Flag the post mode when the post is saved.
|
||||
*
|
||||
* Fired by `save_post` action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*/
|
||||
public function save_post( $post_id ) {
|
||||
if ( ! isset( $_POST['_elementor_edit_mode_nonce'] ) || ! wp_verify_nonce( $_POST['_elementor_edit_mode_nonce'], basename( __FILE__ ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Plugin::$instance->db->set_is_elementor_page( $post_id, ! empty( $_POST['_elementor_post_mode'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Elementor post state.
|
||||
*
|
||||
* Adds a new "Elementor" post state to the post table.
|
||||
*
|
||||
* Fired by `display_post_states` filter.
|
||||
*
|
||||
* @since 1.8.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $post_states An array of post display states.
|
||||
* @param \WP_Post $post The current post object.
|
||||
*
|
||||
* @return array A filtered array of post display states.
|
||||
*/
|
||||
public function add_elementor_post_state( $post_states, $post ) {
|
||||
if ( User::is_current_user_can_edit( $post->ID ) && Plugin::$instance->db->is_built_with_elementor( $post->ID ) ) {
|
||||
$post_states['elementor'] = __( 'Elementor', 'elementor' );
|
||||
}
|
||||
|
||||
return $post_states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Body status classes.
|
||||
*
|
||||
* Adds CSS classes to the admin body tag.
|
||||
*
|
||||
* Fired by `admin_body_class` filter.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $classes Space-separated list of CSS classes.
|
||||
*
|
||||
* @return string Space-separated list of CSS classes.
|
||||
*/
|
||||
public function body_status_classes( $classes ) {
|
||||
global $pagenow;
|
||||
|
||||
if ( in_array( $pagenow, [ 'post.php', 'post-new.php' ], true ) && Utils::is_post_support() ) {
|
||||
$post = get_post();
|
||||
|
||||
$mode_class = Plugin::$instance->db->is_built_with_elementor( $post->ID ) ? 'elementor-editor-active' : 'elementor-editor-inactive';
|
||||
|
||||
$classes .= ' ' . $mode_class;
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin action links.
|
||||
*
|
||||
* Adds action links to the plugin list table
|
||||
*
|
||||
* Fired by `plugin_action_links` filter.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $links An array of plugin action links.
|
||||
*
|
||||
* @return array An array of plugin action links.
|
||||
*/
|
||||
public function plugin_action_links( $links ) {
|
||||
$settings_link = sprintf( '<a href="%1$s">%2$s</a>', admin_url( 'admin.php?page=' . Settings::PAGE_ID ), __( 'Settings', 'elementor' ) );
|
||||
|
||||
array_unshift( $links, $settings_link );
|
||||
|
||||
$links['go_pro'] = sprintf( '<a href="%1$s" target="_blank" class="elementor-plugins-gopro">%2$s</a>', Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=wp-plugins&utm_campaign=gopro&utm_medium=wp-dash' ), __( 'Go Pro', 'elementor' ) );
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin row meta.
|
||||
*
|
||||
* Adds row meta links to the plugin list table
|
||||
*
|
||||
* Fired by `plugin_row_meta` filter.
|
||||
*
|
||||
* @since 1.1.4
|
||||
* @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 ) {
|
||||
if ( ELEMENTOR_PLUGIN_BASE === $plugin_file ) {
|
||||
$row_meta = [
|
||||
'docs' => '<a href="https://go.elementor.com/docs-admin-plugins/" aria-label="' . esc_attr( __( 'View Elementor Documentation', 'elementor' ) ) . '" target="_blank">' . __( 'Docs & FAQs', 'elementor' ) . '</a>',
|
||||
'ideo' => '<a href="https://go.elementor.com/yt-admin-plugins/" aria-label="' . esc_attr( __( 'View Elementor Video Tutorials', 'elementor' ) ) . '" target="_blank">' . __( 'Video Tutorials', 'elementor' ) . '</a>',
|
||||
];
|
||||
|
||||
$plugin_meta = array_merge( $plugin_meta, $row_meta );
|
||||
}
|
||||
|
||||
return $plugin_meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin footer text.
|
||||
*
|
||||
* Modifies the "Thank you" text displayed in the admin footer.
|
||||
*
|
||||
* Fired by `admin_footer_text` filter.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $footer_text The content that will be printed.
|
||||
*
|
||||
* @return string The content that will be printed.
|
||||
*/
|
||||
public function admin_footer_text( $footer_text ) {
|
||||
$current_screen = get_current_screen();
|
||||
$is_elementor_screen = ( $current_screen && false !== strpos( $current_screen->id, 'elementor' ) );
|
||||
|
||||
if ( $is_elementor_screen ) {
|
||||
$footer_text = sprintf(
|
||||
/* translators: 1: Elementor, 2: Link to plugin review */
|
||||
__( 'Enjoyed %1$s? Please leave us a %2$s rating. We really appreciate your support!', 'elementor' ),
|
||||
'<strong>' . __( 'Elementor', 'elementor' ) . '</strong>',
|
||||
'<a href="https://go.elementor.com/admin-review/" target="_blank">★★★★★</a>'
|
||||
);
|
||||
}
|
||||
|
||||
return $footer_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register dashboard widgets.
|
||||
*
|
||||
* Adds a new Elementor widgets to WordPress dashboard.
|
||||
*
|
||||
* Fired by `wp_dashboard_setup` action.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access public
|
||||
*/
|
||||
public function register_dashboard_widgets() {
|
||||
wp_add_dashboard_widget( 'e-dashboard-overview', __( 'Elementor Overview', 'elementor' ), [ $this, 'elementor_dashboard_overview_widget' ] );
|
||||
|
||||
// Move our widget to top.
|
||||
global $wp_meta_boxes;
|
||||
|
||||
$dashboard = $wp_meta_boxes['dashboard']['normal']['core'];
|
||||
$ours = [
|
||||
'e-dashboard-overview' => $dashboard['e-dashboard-overview'],
|
||||
];
|
||||
|
||||
$wp_meta_boxes['dashboard']['normal']['core'] = array_merge( $ours, $dashboard ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor dashboard widget.
|
||||
*
|
||||
* Displays the Elementor dashboard widget.
|
||||
*
|
||||
* Fired by `wp_add_dashboard_widget` function.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access public
|
||||
*/
|
||||
public function elementor_dashboard_overview_widget() {
|
||||
$elementor_feed = Api::get_feed_data();
|
||||
|
||||
$recently_edited_query_args = [
|
||||
'post_type' => 'any',
|
||||
'post_status' => [ 'publish', 'draft' ],
|
||||
'posts_per_page' => '3',
|
||||
'meta_key' => '_elementor_edit_mode',
|
||||
'meta_value' => 'builder',
|
||||
'orderby' => 'modified',
|
||||
];
|
||||
|
||||
$recently_edited_query = new \WP_Query( $recently_edited_query_args );
|
||||
|
||||
if ( User::is_current_user_can_edit_post_type( 'page' ) ) {
|
||||
$create_new_label = __( 'Create New Page', 'elementor' );
|
||||
$create_new_post_type = 'page';
|
||||
} elseif ( User::is_current_user_can_edit_post_type( 'post' ) ) {
|
||||
$create_new_label = __( 'Create New Post', 'elementor' );
|
||||
$create_new_post_type = 'post';
|
||||
}
|
||||
?>
|
||||
<div class="e-dashboard-widget">
|
||||
<div class="e-overview__header">
|
||||
<div class="e-overview__logo"><div class="e-logo-wrapper"><i class="eicon-elementor"></i></div></div>
|
||||
<div class="e-overview__versions">
|
||||
<span class="e-overview__version"><?php echo __( 'Elementor', 'elementor' ); ?> v<?php echo ELEMENTOR_VERSION; ?></span>
|
||||
<?php
|
||||
/**
|
||||
* Elementor dashboard widget after the version.
|
||||
*
|
||||
* Fires after Elementor version display in the dashboard widget.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*/
|
||||
do_action( 'elementor/admin/dashboard_overview_widget/after_version' );
|
||||
?>
|
||||
</div>
|
||||
<?php if ( ! empty( $create_new_post_type ) ) : ?>
|
||||
<div class="e-overview__create">
|
||||
<a href="<?php echo esc_url( Utils::get_create_new_post_url( $create_new_post_type ) ); ?>" class="button"><span aria-hidden="true" class="dashicons dashicons-plus"></span> <?php echo esc_html( $create_new_label ); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ( $recently_edited_query->have_posts() ) : ?>
|
||||
<div class="e-overview__recently-edited">
|
||||
<h3 class="e-overview__heading"><?php echo __( 'Recently Edited', 'elementor' ); ?></h3>
|
||||
<ul class="e-overview__posts">
|
||||
<?php
|
||||
while ( $recently_edited_query->have_posts() ) :
|
||||
$recently_edited_query->the_post();
|
||||
$document = Plugin::$instance->documents->get( get_the_ID() );
|
||||
|
||||
$date = date_i18n( _x( 'M jS', 'Dashboard Overview Widget Recently Date', 'elementor' ), get_the_modified_time( 'U' ) );
|
||||
?>
|
||||
<li class="e-overview__post">
|
||||
<a href="<?php echo esc_attr( $document->get_edit_url() ); ?>" class="e-overview__post-link"><?php echo esc_html( get_the_title() ); ?> <span class="dashicons dashicons-edit"></span></a> <span><?php echo $date; ?>, <?php the_time(); ?></span>
|
||||
</li>
|
||||
<?php endwhile; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $elementor_feed ) ) : ?>
|
||||
<div class="e-overview__feed">
|
||||
<h3 class="e-overview__heading"><?php echo __( 'News & Updates', 'elementor' ); ?></h3>
|
||||
<ul class="e-overview__posts">
|
||||
<?php foreach ( $elementor_feed as $feed_item ) : ?>
|
||||
<li class="e-overview__post">
|
||||
<a href="<?php echo esc_url( $feed_item['url'] ); ?>" class="e-overview__post-link" target="_blank">
|
||||
<?php if ( ! empty( $feed_item['badge'] ) ) : ?>
|
||||
<span class="e-overview__badge"><?php echo esc_html( $feed_item['badge'] ); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php echo esc_html( $feed_item['title'] ); ?>
|
||||
</a>
|
||||
<p class="e-overview__post-description"><?php echo esc_html( $feed_item['excerpt'] ); ?></p>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="e-overview__footer">
|
||||
<ul>
|
||||
<?php foreach ( $this->get_dashboard_overview_widget_footer_actions() as $action_id => $action ) : ?>
|
||||
<li class="e-overview__<?php echo esc_attr( $action_id ); ?>"><a href="<?php echo esc_attr( $action['link'] ); ?>" target="_blank"><?php echo esc_html( $action['title'] ); ?> <span class="screen-reader-text"><?php echo __( '(opens in a new window)', 'elementor' ); ?></span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get elementor dashboard overview widget footer actions.
|
||||
*
|
||||
* Retrieves the footer action links displayed in elementor dashboard widget.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access private
|
||||
*/
|
||||
private function get_dashboard_overview_widget_footer_actions() {
|
||||
$base_actions = [
|
||||
'blog' => [
|
||||
'title' => __( 'Blog', 'elementor' ),
|
||||
'link' => 'https://go.elementor.com/overview-widget-blog/',
|
||||
],
|
||||
'help' => [
|
||||
'title' => __( 'Help', 'elementor' ),
|
||||
'link' => 'https://go.elementor.com/overview-widget-docs/',
|
||||
],
|
||||
];
|
||||
|
||||
$additions_actions = [
|
||||
'go-pro' => [
|
||||
'title' => __( 'Go Pro', 'elementor' ),
|
||||
'link' => Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=wp-overview-widget&utm_campaign=gopro&utm_medium=wp-dash' ),
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Dashboard widget footer actions.
|
||||
*
|
||||
* Filters the additions actions displayed in Elementor dashboard widget.
|
||||
*
|
||||
* Developers can add new action links to Elementor dashboard widget
|
||||
* footer using this filter.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*
|
||||
* @param array $additions_actions Elementor dashboard widget footer actions.
|
||||
*/
|
||||
$additions_actions = apply_filters( 'elementor/admin/dashboard_overview_widget/footer_actions', $additions_actions );
|
||||
|
||||
$actions = $base_actions + $additions_actions;
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin action new post.
|
||||
*
|
||||
* When a new post action is fired the title is set to 'Elementor' and the post ID.
|
||||
*
|
||||
* Fired by `admin_action_elementor_new_post` action.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access public
|
||||
*/
|
||||
public function admin_action_new_post() {
|
||||
check_admin_referer( 'elementor_action_new_post' );
|
||||
|
||||
if ( empty( $_GET['post_type'] ) ) {
|
||||
$post_type = 'post';
|
||||
} else {
|
||||
$post_type = $_GET['post_type'];
|
||||
}
|
||||
|
||||
if ( ! User::is_current_user_can_edit_post_type( $post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $_GET['template_type'] ) ) {
|
||||
$type = 'post';
|
||||
} else {
|
||||
$type = sanitize_text_field( $_GET['template_type'] );
|
||||
}
|
||||
|
||||
$post_data = isset( $_GET['post_data'] ) ? $_GET['post_data'] : [];
|
||||
|
||||
$meta = [];
|
||||
|
||||
/**
|
||||
* Create new post meta data.
|
||||
*
|
||||
* Filters the meta data of any new post created.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param array $meta Post meta data.
|
||||
*/
|
||||
$meta = apply_filters( 'elementor/admin/create_new_post/meta', $meta );
|
||||
|
||||
$post_data['post_type'] = $post_type;
|
||||
|
||||
$document = Plugin::$instance->documents->create( $type, $post_data, $meta );
|
||||
|
||||
if ( is_wp_error( $document ) ) {
|
||||
wp_die( $document );
|
||||
}
|
||||
|
||||
wp_redirect( $document->get_edit_url() );
|
||||
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function add_new_template_template() {
|
||||
Plugin::$instance->common->add_template( ELEMENTOR_PATH . 'includes/admin-templates/new-template.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function enqueue_new_template_scripts() {
|
||||
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
|
||||
wp_enqueue_script(
|
||||
'elementor-new-template',
|
||||
ELEMENTOR_ASSETS_URL . 'js/new-template' . $suffix . '.js',
|
||||
[],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.6.0
|
||||
* @access public
|
||||
*/
|
||||
public function add_beta_tester_template() {
|
||||
Plugin::$instance->common->add_template( ELEMENTOR_PATH . 'includes/admin-templates/beta-tester.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function enqueue_beta_tester_scripts() {
|
||||
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
|
||||
wp_enqueue_script(
|
||||
'elementor-beta-tester',
|
||||
ELEMENTOR_ASSETS_URL . 'js/beta-tester' . $suffix . '.js',
|
||||
[],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function init_new_template() {
|
||||
if ( 'edit-elementor_library' !== get_current_screen()->id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow plugins to add their templates on admin_head.
|
||||
add_action( 'admin_head', [ $this, 'add_new_template_template' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_new_template_scripts' ] );
|
||||
}
|
||||
|
||||
public function version_update_warning( $current_version, $new_version ) {
|
||||
$current_version_minor_part = explode( '.', $current_version )[1];
|
||||
$new_version_minor_part = explode( '.', $new_version )[1];
|
||||
|
||||
if ( $current_version_minor_part === $new_version_minor_part ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="e-major-update-warning">
|
||||
<div class="e-major-update-warning__title"><?php echo __( 'Heads up, Please backup before upgrade!', 'elementor' ); ?></div>
|
||||
<div class="e-major-update-warning__message"><?php echo __( 'The latest update includes some substantial changes across different areas of the plugin. We highly recommend you backup your site before upgrading, and make sure you first update in a staging environment', 'elementor' ); ?></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function init_beta_tester( $current_screen ) {
|
||||
if ( ( 'toplevel_page_elementor' === $current_screen->base ) || 'elementor_page_elementor-tools' === $current_screen->id ) {
|
||||
add_action( 'admin_head', [ $this, 'add_beta_tester_template' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_beta_tester_scripts' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin constructor.
|
||||
*
|
||||
* Initializing Elementor in WordPress admin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
Plugin::$instance->init_common();
|
||||
|
||||
$this->add_component( 'feedback', new Feedback() );
|
||||
$this->add_component( 'canary-deployment', new Canary_Deployment() );
|
||||
$this->add_component( 'admin-notices', new Admin_Notices() );
|
||||
|
||||
add_action( 'admin_init', [ $this, 'maybe_redirect_to_getting_started' ] );
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] );
|
||||
|
||||
add_action( 'edit_form_after_title', [ $this, 'print_switch_mode_button' ] );
|
||||
add_action( 'save_post', [ $this, 'save_post' ] );
|
||||
|
||||
add_filter( 'display_post_states', [ $this, 'add_elementor_post_state' ], 10, 2 );
|
||||
|
||||
add_filter( 'plugin_action_links_' . ELEMENTOR_PLUGIN_BASE, [ $this, 'plugin_action_links' ] );
|
||||
add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 2 );
|
||||
|
||||
add_filter( 'admin_body_class', [ $this, 'body_status_classes' ] );
|
||||
add_filter( 'admin_footer_text', [ $this, 'admin_footer_text' ] );
|
||||
|
||||
// Register Dashboard Widgets.
|
||||
add_action( 'wp_dashboard_setup', [ $this, 'register_dashboard_widgets' ] );
|
||||
|
||||
// Admin Actions
|
||||
add_action( 'admin_action_elementor_new_post', [ $this, 'admin_action_new_post' ] );
|
||||
|
||||
add_action( 'current_screen', [ $this, 'init_new_template' ] );
|
||||
add_action( 'current_screen', [ $this, 'init_beta_tester' ] );
|
||||
|
||||
add_action( 'in_plugin_update_message-' . ELEMENTOR_PLUGIN_BASE, function( $plugin_data ) {
|
||||
$this->version_update_warning( ELEMENTOR_VERSION, $plugin_data['new_version'] );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_init_settings() {
|
||||
$beta_tester_email = get_user_meta( get_current_user_id(), User::BETA_TESTER_META_KEY, true );
|
||||
$elementor_beta = get_option( 'elementor_beta', 'no' );
|
||||
$all_introductions = User::get_introduction_meta();
|
||||
$beta_tester_signup_dismissed = array_key_exists( Beta_Testers::BETA_TESTER_SIGNUP, $all_introductions );
|
||||
|
||||
$settings = [
|
||||
'home_url' => home_url(),
|
||||
'settings_url' => Settings::get_url(),
|
||||
'i18n' => [
|
||||
'rollback_confirm' => __( 'Are you sure you want to reinstall previous version?', 'elementor' ),
|
||||
'rollback_to_previous_version' => __( 'Rollback to Previous Version', 'elementor' ),
|
||||
'yes' => __( 'Continue', 'elementor' ),
|
||||
'cancel' => __( 'Cancel', 'elementor' ),
|
||||
'new_template' => __( 'New Template', 'elementor' ),
|
||||
'back_to_wordpress_editor_message' => __( 'Please note that you are switching to WordPress default editor. Your current layout, design and content might break.', 'elementor' ),
|
||||
'back_to_wordpress_editor_header' => __( 'Back to WordPress Editor', 'elementor' ),
|
||||
'beta_tester_sign_up' => __( 'Sign Up', 'elementor' ),
|
||||
'do_not_show_again' => __( 'Don\'t Show Again', 'elementor' ),
|
||||
],
|
||||
'user' => [
|
||||
'introduction' => User::get_introduction_meta(),
|
||||
],
|
||||
'beta_tester' => [
|
||||
'beta_tester_signup' => Beta_Testers::BETA_TESTER_SIGNUP,
|
||||
'has_email' => $beta_tester_email,
|
||||
'option_enabled' => 'no' !== $elementor_beta,
|
||||
'signup_dismissed' => $beta_tester_signup_dismissed,
|
||||
],
|
||||
];
|
||||
|
||||
return apply_filters( 'elementor/admin/localize_settings', $settings );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Admin;
|
||||
|
||||
use Elementor\Api;
|
||||
use Elementor\Core\Base\Module;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Canary_Deployment extends Module {
|
||||
|
||||
const CURRENT_VERSION = ELEMENTOR_VERSION;
|
||||
const PLUGIN_BASE = ELEMENTOR_PLUGIN_BASE;
|
||||
|
||||
private $canary_deployment_info = null;
|
||||
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'canary-deployment';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check version.
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @access public
|
||||
*
|
||||
* @param object $transient Plugin updates data.
|
||||
*
|
||||
* @return object Plugin updates data.
|
||||
*/
|
||||
public function check_version( $transient ) {
|
||||
// First transient before the real check.
|
||||
if ( ! isset( $transient->response ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
// Placeholder
|
||||
$stable_version = '0.0.0';
|
||||
|
||||
if ( ! empty( $transient->response[ static::PLUGIN_BASE ]->new_version ) ) {
|
||||
$stable_version = $transient->response[ static::PLUGIN_BASE ]->new_version;
|
||||
}
|
||||
|
||||
if ( null === $this->canary_deployment_info ) {
|
||||
$this->canary_deployment_info = $this->get_canary_deployment_info();
|
||||
}
|
||||
|
||||
// Can be false - if canary version is not available.
|
||||
if ( empty( $this->canary_deployment_info ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( ! version_compare( $this->canary_deployment_info['new_version'], $stable_version, '>' ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
$canary_deployment_info = $this->canary_deployment_info;
|
||||
|
||||
// Most of plugin info comes from the $transient but on first check - the response is empty.
|
||||
if ( ! empty( $transient->response[ static::PLUGIN_BASE ] ) ) {
|
||||
$canary_deployment_info = array_merge( (array) $transient->response[ static::PLUGIN_BASE ], $canary_deployment_info );
|
||||
}
|
||||
|
||||
$transient->response[ static::PLUGIN_BASE ] = (object) $canary_deployment_info;
|
||||
|
||||
return $transient;
|
||||
}
|
||||
|
||||
protected function get_canary_deployment_remote_info( $force ) {
|
||||
return Api::get_canary_deployment_info( $force );
|
||||
}
|
||||
|
||||
private function get_canary_deployment_info() {
|
||||
global $pagenow;
|
||||
|
||||
$force = 'update-core.php' === $pagenow && isset( $_GET['force-check'] );
|
||||
|
||||
$canary_deployment = $this->get_canary_deployment_remote_info( $force );
|
||||
|
||||
if ( empty( $canary_deployment['plugin_info']['new_version'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$canary_version = $canary_deployment['plugin_info']['new_version'];
|
||||
|
||||
if ( version_compare( $canary_version, static::CURRENT_VERSION, '<=' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! empty( $canary_deployment['conditions'] ) && ! $this->check_conditions( $canary_deployment['conditions'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $canary_deployment['plugin_info'];
|
||||
}
|
||||
|
||||
private function check_conditions( $groups ) {
|
||||
foreach ( $groups as $group ) {
|
||||
if ( $this->check_group( $group ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private 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 ( ! empty( $condition['plugin_file'] ) ) {
|
||||
$plugin_file = $condition['plugin_file']; // For PHP Unit tests.
|
||||
} else {
|
||||
$plugin_file = WP_PLUGIN_DIR . '/' . $condition['plugin']; // Default.
|
||||
}
|
||||
|
||||
$version = '';
|
||||
|
||||
if ( is_plugin_active( $condition['plugin'] ) && file_exists( $plugin_file ) ) {
|
||||
$plugin_data = get_plugin_data( $plugin_file );
|
||||
if ( isset( $plugin_data['Version'] ) ) {
|
||||
$version = $plugin_data['Version'];
|
||||
}
|
||||
}
|
||||
|
||||
$result = version_compare( $version, $condition['version'], $condition['operator'] );
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
if ( ( $is_or_relation && $result ) || ( ! $is_or_relation && ! $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.6.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_version' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Admin;
|
||||
|
||||
use Elementor\Api;
|
||||
use Elementor\Core\Base\Module;
|
||||
use Elementor\Tracker;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Feedback extends Module {
|
||||
|
||||
/**
|
||||
* @since 2.2.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'current_screen', function () {
|
||||
if ( ! $this->is_plugins_screen() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_feedback_dialog_scripts' ] );
|
||||
|
||||
add_filter( 'elementor/admin/localize_settings', [ $this, 'localize_feedback_dialog_settings' ] );
|
||||
} );
|
||||
|
||||
// Ajax.
|
||||
add_action( 'wp_ajax_elementor_deactivate_feedback', [ $this, 'ajax_elementor_deactivate_feedback' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'feedback';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue feedback dialog scripts.
|
||||
*
|
||||
* Registers the feedback dialog scripts and enqueues them.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function enqueue_feedback_dialog_scripts() {
|
||||
add_action( 'admin_footer', [ $this, 'print_deactivate_feedback_dialog' ] );
|
||||
|
||||
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
|
||||
wp_register_script(
|
||||
'elementor-admin-feedback',
|
||||
ELEMENTOR_ASSETS_URL . 'js/admin-feedback' . $suffix . '.js',
|
||||
[
|
||||
'elementor-common',
|
||||
],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script( 'elementor-admin-feedback' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function localize_feedback_dialog_settings( $localized_settings ) {
|
||||
$localized_settings['i18n']['submit_n_deactivate'] = __( 'Submit & Deactivate', 'elementor' );
|
||||
$localized_settings['i18n']['skip_n_deactivate'] = __( 'Skip & Deactivate', 'elementor' );
|
||||
|
||||
return $localized_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print deactivate feedback dialog.
|
||||
*
|
||||
* Display a dialog box to ask the user why he deactivated Elementor.
|
||||
*
|
||||
* Fired by `admin_footer` filter.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function print_deactivate_feedback_dialog() {
|
||||
$deactivate_reasons = [
|
||||
'no_longer_needed' => [
|
||||
'title' => __( 'I no longer need the plugin', 'elementor' ),
|
||||
'input_placeholder' => '',
|
||||
],
|
||||
'found_a_better_plugin' => [
|
||||
'title' => __( 'I found a better plugin', 'elementor' ),
|
||||
'input_placeholder' => __( 'Please share which plugin', 'elementor' ),
|
||||
],
|
||||
'couldnt_get_the_plugin_to_work' => [
|
||||
'title' => __( 'I couldn\'t get the plugin to work', 'elementor' ),
|
||||
'input_placeholder' => '',
|
||||
],
|
||||
'temporary_deactivation' => [
|
||||
'title' => __( 'It\'s a temporary deactivation', 'elementor' ),
|
||||
'input_placeholder' => '',
|
||||
],
|
||||
'elementor_pro' => [
|
||||
'title' => __( 'I have Elementor Pro', 'elementor' ),
|
||||
'input_placeholder' => '',
|
||||
'alert' => __( 'Wait! Don\'t deactivate Elementor. You have to activate both Elementor and Elementor Pro in order for the plugin to work.', 'elementor' ),
|
||||
],
|
||||
'other' => [
|
||||
'title' => __( 'Other', 'elementor' ),
|
||||
'input_placeholder' => __( 'Please share the reason', 'elementor' ),
|
||||
],
|
||||
];
|
||||
|
||||
?>
|
||||
<div id="elementor-deactivate-feedback-dialog-wrapper">
|
||||
<div id="elementor-deactivate-feedback-dialog-header">
|
||||
<i class="eicon-elementor-square" aria-hidden="true"></i>
|
||||
<span id="elementor-deactivate-feedback-dialog-header-title"><?php echo __( 'Quick Feedback', 'elementor' ); ?></span>
|
||||
</div>
|
||||
<form id="elementor-deactivate-feedback-dialog-form" method="post">
|
||||
<?php
|
||||
wp_nonce_field( '_elementor_deactivate_feedback_nonce' );
|
||||
?>
|
||||
<input type="hidden" name="action" value="elementor_deactivate_feedback" />
|
||||
|
||||
<div id="elementor-deactivate-feedback-dialog-form-caption"><?php echo __( 'If you have a moment, please share why you are deactivating Elementor:', 'elementor' ); ?></div>
|
||||
<div id="elementor-deactivate-feedback-dialog-form-body">
|
||||
<?php foreach ( $deactivate_reasons as $reason_key => $reason ) : ?>
|
||||
<div class="elementor-deactivate-feedback-dialog-input-wrapper">
|
||||
<input id="elementor-deactivate-feedback-<?php echo esc_attr( $reason_key ); ?>" class="elementor-deactivate-feedback-dialog-input" type="radio" name="reason_key" value="<?php echo esc_attr( $reason_key ); ?>" />
|
||||
<label for="elementor-deactivate-feedback-<?php echo esc_attr( $reason_key ); ?>" class="elementor-deactivate-feedback-dialog-label"><?php echo esc_html( $reason['title'] ); ?></label>
|
||||
<?php if ( ! empty( $reason['input_placeholder'] ) ) : ?>
|
||||
<input class="elementor-feedback-text" type="text" name="reason_<?php echo esc_attr( $reason_key ); ?>" placeholder="<?php echo esc_attr( $reason['input_placeholder'] ); ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ( ! empty( $reason['alert'] ) ) : ?>
|
||||
<div class="elementor-feedback-text"><?php echo esc_html( $reason['alert'] ); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax elementor deactivate feedback.
|
||||
*
|
||||
* Send the user feedback when Elementor is deactivated.
|
||||
*
|
||||
* Fired by `wp_ajax_elementor_deactivate_feedback` action.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function ajax_elementor_deactivate_feedback() {
|
||||
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], '_elementor_deactivate_feedback_nonce' ) ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
$reason_text = '';
|
||||
$reason_key = '';
|
||||
|
||||
if ( ! empty( $_POST['reason_key'] ) ) {
|
||||
$reason_key = $_POST['reason_key'];
|
||||
}
|
||||
|
||||
if ( ! empty( $_POST[ "reason_{$reason_key}" ] ) ) {
|
||||
$reason_text = $_POST[ "reason_{$reason_key}" ];
|
||||
}
|
||||
|
||||
Api::send_feedback( $reason_key, $reason_text );
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_init_settings() {
|
||||
if ( ! $this->is_plugins_screen() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [ 'is_tracker_opted_in' => Tracker::is_allow_track() ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access private
|
||||
*/
|
||||
private function is_plugins_screen() {
|
||||
return in_array( get_current_screen()->id, [ 'plugins', 'plugins-network' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
namespace Elementor\Core\App;
|
||||
|
||||
use Elementor\Core\Base\App as BaseApp;
|
||||
use Elementor\Core\Settings\Manager as SettingsManager;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class App extends BaseApp {
|
||||
|
||||
const PAGE_ID = 'elementor-app';
|
||||
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'app';
|
||||
}
|
||||
|
||||
public function get_base_url() {
|
||||
return admin_url( 'admin.php?page=' . self::PAGE_ID . '&ver=' . ELEMENTOR_VERSION );
|
||||
}
|
||||
|
||||
public function register_admin_menu() {
|
||||
add_submenu_page(
|
||||
Source_Local::ADMIN_MENU_SLUG,
|
||||
__( 'Theme Builder', 'elementor' ),
|
||||
__( 'Theme Builder', 'elementor' ),
|
||||
'manage_options',
|
||||
self::PAGE_ID
|
||||
);
|
||||
}
|
||||
|
||||
public function fix_submenu( $menu ) {
|
||||
global $submenu;
|
||||
|
||||
if ( is_multisite() && is_network_admin() ) {
|
||||
return $menu;
|
||||
}
|
||||
|
||||
// Non admin role / custom wp menu.
|
||||
if ( empty( $submenu[ Source_Local::ADMIN_MENU_SLUG ] ) ) {
|
||||
return $menu;
|
||||
}
|
||||
|
||||
// Hack to add a link to sub menu.
|
||||
foreach ( $submenu[ Source_Local::ADMIN_MENU_SLUG ] as &$item ) {
|
||||
if ( self::PAGE_ID === $item[2] ) {
|
||||
$item[2] = $this->get_settings( 'menu_url' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$item[4] = 'elementor-app-link'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
}
|
||||
}
|
||||
|
||||
return $menu;
|
||||
}
|
||||
|
||||
public function is_current() {
|
||||
return ( ! empty( $_GET['page'] ) && self::PAGE_ID === $_GET['page'] );
|
||||
}
|
||||
|
||||
public function admin_init() {
|
||||
do_action( 'elementor/app/init', $this );
|
||||
|
||||
$this->enqueue_assets();
|
||||
|
||||
// Setup default heartbeat options
|
||||
// TODO: Enable heartbeat.
|
||||
add_filter( 'heartbeat_settings', function( $settings ) {
|
||||
$settings['interval'] = 15;
|
||||
return $settings;
|
||||
} );
|
||||
|
||||
$this->render();
|
||||
die;
|
||||
}
|
||||
|
||||
protected function get_init_settings() {
|
||||
return [
|
||||
'menu_url' => $this->get_base_url() . '#site-editor/promotion',
|
||||
'assets_url' => ELEMENTOR_ASSETS_URL,
|
||||
'return_url' => isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : admin_url(),
|
||||
];
|
||||
}
|
||||
|
||||
private function render() {
|
||||
require __DIR__ . '/view.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Elementor UI theme preference.
|
||||
*
|
||||
* Retrieve the user UI theme preference as defined by editor preferences manager.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @access private
|
||||
*
|
||||
* @return string Preferred UI theme.
|
||||
*/
|
||||
private function get_elementor_ui_theme_preference() {
|
||||
$editor_preferences = SettingsManager::get_settings_managers( 'editorPreferences' );
|
||||
|
||||
return $editor_preferences->get_model()->get_settings( 'ui_theme' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue dark theme detection script.
|
||||
*
|
||||
* Enqueues an inline script that detects user-agent settings for dark mode and adds a complimentary class to the body tag.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @access private
|
||||
*/
|
||||
private function enqueue_dark_theme_detection_script() {
|
||||
if ( 'auto' === $this->get_elementor_ui_theme_preference() ) {
|
||||
wp_add_inline_script( 'elementor-app',
|
||||
'if ( window.matchMedia && window.matchMedia( `(prefers-color-scheme: dark)` ).matches )
|
||||
{ document.body.classList.add( `eps-theme-dark` ); }' );
|
||||
}
|
||||
}
|
||||
|
||||
private function enqueue_assets() {
|
||||
Plugin::$instance->init_common();
|
||||
Plugin::$instance->common->register_scripts();
|
||||
|
||||
wp_register_style(
|
||||
'select2',
|
||||
$this->get_css_assets_url( 'e-select2', 'assets/lib/e-select2/css/' ),
|
||||
[],
|
||||
'4.0.6-rc.1'
|
||||
);
|
||||
|
||||
wp_register_style(
|
||||
'elementor-icons',
|
||||
$this->get_css_assets_url( 'elementor-icons', 'assets/lib/eicons/css/' ),
|
||||
[],
|
||||
'5.6.2'
|
||||
);
|
||||
|
||||
wp_register_style(
|
||||
'elementor-common',
|
||||
$this->get_css_assets_url( 'common', null, 'default', true ),
|
||||
[],
|
||||
ELEMENTOR_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
'elementor-app',
|
||||
$this->get_css_assets_url( 'app', null, 'default', true ),
|
||||
[
|
||||
'elementor-icons',
|
||||
'elementor-common',
|
||||
'select2',
|
||||
],
|
||||
ELEMENTOR_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'elementor-app-packages',
|
||||
$this->get_js_assets_url( 'app-packages' ),
|
||||
[
|
||||
'wp-i18n',
|
||||
'react',
|
||||
],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'select2',
|
||||
$this->get_js_assets_url( 'e-select2.full', 'assets/lib/e-select2/js/' ),
|
||||
[
|
||||
'jquery',
|
||||
],
|
||||
'4.0.6-rc.1',
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'elementor-app',
|
||||
$this->get_js_assets_url( 'app' ),
|
||||
[
|
||||
'wp-i18n',
|
||||
'react',
|
||||
'react-dom',
|
||||
'select2',
|
||||
],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
$this->enqueue_dark_theme_detection_script();
|
||||
|
||||
wp_set_script_translations( 'elementor-app-packages', 'elementor' );
|
||||
wp_set_script_translations( 'elementor-app', 'elementor' );
|
||||
|
||||
$this->print_config();
|
||||
}
|
||||
|
||||
public function enqueue_app_loader() {
|
||||
wp_enqueue_script(
|
||||
'elementor-app-loader',
|
||||
$this->get_js_assets_url( 'app-loader' ),
|
||||
[
|
||||
'elementor-common',
|
||||
],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
$this->print_config( 'elementor-app-loader' );
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
$this->add_component( 'site-editor', new Modules\SiteEditor\Module() );
|
||||
|
||||
add_action( 'admin_menu', [ $this, 'register_admin_menu' ], 21 /* after Elementor page */ );
|
||||
|
||||
// Happens after WP plugin page validation.
|
||||
add_filter( 'add_menu_classes', [ $this, 'fix_submenu' ] );
|
||||
|
||||
if ( $this->is_current() ) {
|
||||
add_action( 'admin_init', [ $this, 'admin_init' ], 0 );
|
||||
} else {
|
||||
add_action( 'elementor/common/after_register_scripts', [ $this, 'enqueue_app_loader' ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace Elementor\Core\App\Modules\SiteEditor;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Site Editor Module
|
||||
*
|
||||
* Responsible for initializing Elementor App functionality
|
||||
*/
|
||||
class Module extends BaseModule {
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'site-editor';
|
||||
}
|
||||
|
||||
public function add_menu_in_admin_bar( $admin_bar_config ) {
|
||||
$admin_bar_config['elementor_edit_page']['children'][] = [
|
||||
'id' => 'elementor_app_site_editor',
|
||||
'title' => __( 'Open Theme Builder', 'elementor' ),
|
||||
'href' => Plugin::$instance->app->get_settings( 'menu_url' ),
|
||||
'class' => 'elementor-app-link',
|
||||
];
|
||||
|
||||
return $admin_bar_config;
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
add_filter( 'elementor/frontend/admin_bar/settings', [ $this, 'add_menu_in_admin_bar' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace Elementor\Core\App;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* @var App $this
|
||||
*/
|
||||
|
||||
|
||||
$theme_class = 'dark' === $this->get_elementor_ui_theme_preference() ? 'eps-theme-dark' : '';
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><?php echo __( 'Elementor', 'elementor' ) . ' ... '; ?></title>
|
||||
<base target="_parent">
|
||||
<?php wp_print_styles(); ?>
|
||||
</head>
|
||||
<body class="<?php echo $theme_class; ?>">
|
||||
<div id="e-app"></div>
|
||||
<?php wp_print_footer_scripts(); ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Base;
|
||||
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Base App
|
||||
*
|
||||
* Base app utility class that provides shared functionality of apps.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*/
|
||||
abstract class App extends Module {
|
||||
|
||||
/**
|
||||
* Print config.
|
||||
*
|
||||
* Used to print the app and its components settings as a JavaScript object.
|
||||
*
|
||||
* @param string $handle Optional
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @since 2.6.0 added the `$handle` parameter
|
||||
* @access protected
|
||||
*/
|
||||
final protected function print_config( $handle = null ) {
|
||||
$name = $this->get_name();
|
||||
|
||||
$js_var = 'elementor' . str_replace( ' ', '', ucwords( str_replace( '-', ' ', $name ) ) ) . 'Config';
|
||||
|
||||
$config = $this->get_settings() + $this->get_components_config();
|
||||
|
||||
if ( ! $handle ) {
|
||||
$handle = 'elementor-' . $name;
|
||||
}
|
||||
|
||||
Utils::print_js_config( $handle, $js_var, $config );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get components config.
|
||||
*
|
||||
* Retrieves the app components settings.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access private
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_components_config() {
|
||||
$settings = [];
|
||||
|
||||
foreach ( $this->get_components() as $id => $instance ) {
|
||||
$settings[ $id ] = $instance->get_settings();
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Base\BackgroundProcess;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/A5hleyRich/wp-background-processing GPL v2.0
|
||||
*
|
||||
* WP Async Request
|
||||
*
|
||||
* @package WP-Background-Processing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract WP_Async_Request class.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
abstract class WP_Async_Request {
|
||||
|
||||
/**
|
||||
* Prefix
|
||||
*
|
||||
* (default value: 'wp')
|
||||
*
|
||||
* @var string
|
||||
* @access protected
|
||||
*/
|
||||
protected $prefix = 'wp';
|
||||
|
||||
/**
|
||||
* Action
|
||||
*
|
||||
* (default value: 'async_request')
|
||||
*
|
||||
* @var string
|
||||
* @access protected
|
||||
*/
|
||||
protected $action = 'async_request';
|
||||
|
||||
/**
|
||||
* Identifier
|
||||
*
|
||||
* @var mixed
|
||||
* @access protected
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* Data
|
||||
*
|
||||
* (default value: array())
|
||||
*
|
||||
* @var array
|
||||
* @access protected
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* Initiate new async request
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->identifier = $this->prefix . '_' . $this->action;
|
||||
|
||||
add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
|
||||
add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data used during the request
|
||||
*
|
||||
* @param array $data Data.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function data( $data ) {
|
||||
$this->data = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the async request
|
||||
*
|
||||
* @return array|\WP_Error
|
||||
*/
|
||||
public function dispatch() {
|
||||
$url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
|
||||
$args = $this->get_post_args();
|
||||
|
||||
return wp_remote_post( esc_url_raw( $url ), $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_query_args() {
|
||||
if ( property_exists( $this, 'query_args' ) ) {
|
||||
return $this->query_args;
|
||||
}
|
||||
|
||||
return array(
|
||||
'action' => $this->identifier,
|
||||
'nonce' => wp_create_nonce( $this->identifier ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_query_url() {
|
||||
if ( property_exists( $this, 'query_url' ) ) {
|
||||
return $this->query_url;
|
||||
}
|
||||
|
||||
return admin_url( 'admin-ajax.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_post_args() {
|
||||
if ( property_exists( $this, 'post_args' ) ) {
|
||||
return $this->post_args;
|
||||
}
|
||||
|
||||
return array(
|
||||
'timeout' => 0.01,
|
||||
'blocking' => false,
|
||||
'body' => $this->data,
|
||||
'cookies' => $_COOKIE,
|
||||
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe handle
|
||||
*
|
||||
* Check for correct nonce and pass to handler.
|
||||
*/
|
||||
public function maybe_handle() {
|
||||
// Don't lock up other requests while processing
|
||||
session_write_close();
|
||||
|
||||
check_ajax_referer( $this->identifier, 'nonce' );
|
||||
|
||||
$this->handle();
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle
|
||||
*
|
||||
* Override this method to perform any actions required
|
||||
* during the async request.
|
||||
*/
|
||||
abstract protected function handle();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Base\BackgroundProcess;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://github.com/A5hleyRich/wp-background-processing GPL v2.0
|
||||
*
|
||||
* WP Background Process
|
||||
*
|
||||
* @package WP-Background-Processing
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract WP_Background_Process class.
|
||||
*
|
||||
* @abstract
|
||||
* @extends WP_Async_Request
|
||||
*/
|
||||
abstract class WP_Background_Process extends WP_Async_Request {
|
||||
|
||||
/**
|
||||
* Action
|
||||
*
|
||||
* (default value: 'background_process')
|
||||
*
|
||||
* @var string
|
||||
* @access protected
|
||||
*/
|
||||
protected $action = 'background_process';
|
||||
|
||||
/**
|
||||
* Start time of current process.
|
||||
*
|
||||
* (default value: 0)
|
||||
*
|
||||
* @var int
|
||||
* @access protected
|
||||
*/
|
||||
protected $start_time = 0;
|
||||
|
||||
/**
|
||||
* Cron_hook_identifier
|
||||
*
|
||||
* @var mixed
|
||||
* @access protected
|
||||
*/
|
||||
protected $cron_hook_identifier;
|
||||
|
||||
/**
|
||||
* Cron_interval_identifier
|
||||
*
|
||||
* @var mixed
|
||||
* @access protected
|
||||
*/
|
||||
protected $cron_interval_identifier;
|
||||
|
||||
/**
|
||||
* Initiate new background process
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->cron_hook_identifier = $this->identifier . '_cron';
|
||||
$this->cron_interval_identifier = $this->identifier . '_cron_interval';
|
||||
|
||||
add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
|
||||
add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch
|
||||
*
|
||||
* @access public
|
||||
* @return array|\WP_Error
|
||||
*/
|
||||
public function dispatch() {
|
||||
// Schedule the cron healthcheck.
|
||||
$this->schedule_event();
|
||||
|
||||
// Perform remote post.
|
||||
return parent::dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push to queue
|
||||
*
|
||||
* @param mixed $data Data.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function push_to_queue( $data ) {
|
||||
$this->data[] = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save queue
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function save() {
|
||||
$key = $this->generate_key();
|
||||
|
||||
if ( ! empty( $this->data ) ) {
|
||||
update_site_option( $key, $this->data );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update queue
|
||||
*
|
||||
* @param string $key Key.
|
||||
* @param array $data Data.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function update( $key, $data ) {
|
||||
if ( ! empty( $data ) ) {
|
||||
update_site_option( $key, $data );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete queue
|
||||
*
|
||||
* @param string $key Key.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function delete( $key ) {
|
||||
delete_site_option( $key );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate key
|
||||
*
|
||||
* Generates a unique key based on microtime. Queue items are
|
||||
* given a unique key so that they can be merged upon save.
|
||||
*
|
||||
* @param int $length Length.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_key( $length = 64 ) {
|
||||
$unique = md5( microtime() . rand() );
|
||||
$prepend = $this->identifier . '_batch_';
|
||||
|
||||
return substr( $prepend . $unique, 0, $length );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe process queue
|
||||
*
|
||||
* Checks whether data exists within the queue and that
|
||||
* the process is not already running.
|
||||
*/
|
||||
public function maybe_handle() {
|
||||
// Don't lock up other requests while processing
|
||||
session_write_close();
|
||||
|
||||
if ( $this->is_process_running() ) {
|
||||
// Background process already running.
|
||||
wp_die();
|
||||
}
|
||||
|
||||
if ( $this->is_queue_empty() ) {
|
||||
// No data to process.
|
||||
wp_die();
|
||||
}
|
||||
|
||||
check_ajax_referer( $this->identifier, 'nonce' );
|
||||
|
||||
$this->handle();
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is queue empty
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_queue_empty() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
}
|
||||
|
||||
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
// Can't use placeholders for table/column names, it will be wrapped by a single quote (') instead of a backquote (`).
|
||||
$count = $wpdb->get_var( $wpdb->prepare( "
|
||||
SELECT COUNT(*)
|
||||
FROM {$table}
|
||||
WHERE {$column} LIKE %s
|
||||
", $key ) );
|
||||
// phpcs:enable
|
||||
|
||||
return ( $count > 0 ) ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is process running
|
||||
*
|
||||
* Check whether the current process is already running
|
||||
* in a background process.
|
||||
*/
|
||||
protected function is_process_running() {
|
||||
if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
|
||||
// Process already running.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock process
|
||||
*
|
||||
* Lock the process so that multiple instances can't run simultaneously.
|
||||
* Override if applicable, but the duration should be greater than that
|
||||
* defined in the time_exceeded() method.
|
||||
*/
|
||||
protected function lock_process() {
|
||||
$this->start_time = time(); // Set start time of current process.
|
||||
|
||||
$lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
|
||||
$lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
|
||||
|
||||
set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock process
|
||||
*
|
||||
* Unlock the process so that other instances can spawn.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function unlock_process() {
|
||||
delete_site_transient( $this->identifier . '_process_lock' );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch
|
||||
*
|
||||
* @return \stdClass Return the first batch from the queue
|
||||
*/
|
||||
protected function get_batch() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
$key_column = 'option_id';
|
||||
$value_column = 'option_value';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
$key_column = 'meta_id';
|
||||
$value_column = 'meta_value';
|
||||
}
|
||||
|
||||
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
// Can't use placeholders for table/column names, it will be wrapped by a single quote (') instead of a backquote (`).
|
||||
$query = $wpdb->get_row( $wpdb->prepare( "
|
||||
SELECT *
|
||||
FROM {$table}
|
||||
WHERE {$column} LIKE %s
|
||||
ORDER BY {$key_column} ASC
|
||||
LIMIT 1
|
||||
", $key ) );
|
||||
// phpcs:enable
|
||||
|
||||
$batch = new \stdClass();
|
||||
$batch->key = $query->$column;
|
||||
$batch->data = maybe_unserialize( $query->$value_column );
|
||||
|
||||
return $batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle
|
||||
*
|
||||
* Pass each queue item to the task handler, while remaining
|
||||
* within server memory and time limit constraints.
|
||||
*/
|
||||
protected function handle() {
|
||||
$this->lock_process();
|
||||
|
||||
do {
|
||||
$batch = $this->get_batch();
|
||||
|
||||
foreach ( $batch->data as $key => $value ) {
|
||||
$task = $this->task( $value );
|
||||
|
||||
if ( false !== $task ) {
|
||||
$batch->data[ $key ] = $task;
|
||||
} else {
|
||||
unset( $batch->data[ $key ] );
|
||||
}
|
||||
|
||||
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
|
||||
// Batch limits reached.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update or delete current batch.
|
||||
if ( ! empty( $batch->data ) ) {
|
||||
$this->update( $batch->key, $batch->data );
|
||||
} else {
|
||||
$this->delete( $batch->key );
|
||||
}
|
||||
} while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
|
||||
|
||||
$this->unlock_process();
|
||||
|
||||
// Start next batch or complete process.
|
||||
if ( ! $this->is_queue_empty() ) {
|
||||
$this->dispatch();
|
||||
} else {
|
||||
$this->complete();
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory exceeded
|
||||
*
|
||||
* Ensures the batch process never exceeds 90%
|
||||
* of the maximum WordPress memory.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function memory_exceeded() {
|
||||
$memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
|
||||
$current_memory = memory_get_usage( true );
|
||||
$return = false;
|
||||
|
||||
if ( $current_memory >= $memory_limit ) {
|
||||
$return = true;
|
||||
}
|
||||
|
||||
return apply_filters( $this->identifier . '_memory_exceeded', $return );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory limit
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_memory_limit() {
|
||||
if ( function_exists( 'ini_get' ) ) {
|
||||
$memory_limit = ini_get( 'memory_limit' );
|
||||
} else {
|
||||
// Sensible default.
|
||||
$memory_limit = '128M';
|
||||
}
|
||||
|
||||
if ( ! $memory_limit || -1 === intval( $memory_limit ) ) {
|
||||
// Unlimited, set to 32GB.
|
||||
$memory_limit = '32000M';
|
||||
}
|
||||
|
||||
return intval( $memory_limit ) * 1024 * 1024;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time exceeded.
|
||||
*
|
||||
* Ensures the batch never exceeds a sensible time limit.
|
||||
* A timeout limit of 30s is common on shared hosting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function time_exceeded() {
|
||||
$finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
|
||||
$return = false;
|
||||
|
||||
if ( time() >= $finish ) {
|
||||
$return = true;
|
||||
}
|
||||
|
||||
return apply_filters( $this->identifier . '_time_exceeded', $return );
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete.
|
||||
*
|
||||
* Override if applicable, but ensure that the below actions are
|
||||
* performed, or, call parent::complete().
|
||||
*/
|
||||
protected function complete() {
|
||||
// Unschedule the cron healthcheck.
|
||||
$this->clear_scheduled_event();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule cron healthcheck
|
||||
*
|
||||
* @access public
|
||||
* @param mixed $schedules Schedules.
|
||||
* @return mixed
|
||||
*/
|
||||
public function schedule_cron_healthcheck( $schedules ) {
|
||||
$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
|
||||
|
||||
if ( property_exists( $this, 'cron_interval' ) ) {
|
||||
$interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
|
||||
}
|
||||
|
||||
// Adds every 5 minutes to the existing schedules.
|
||||
$schedules[ $this->identifier . '_cron_interval' ] = array(
|
||||
'interval' => MINUTE_IN_SECONDS * $interval,
|
||||
'display' => sprintf( __( 'Every %d Minutes', 'elementor' ), $interval ),
|
||||
);
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle cron healthcheck
|
||||
*
|
||||
* Restart the background process if not already running
|
||||
* and data exists in the queue.
|
||||
*/
|
||||
public function handle_cron_healthcheck() {
|
||||
if ( $this->is_process_running() ) {
|
||||
// Background process already running.
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $this->is_queue_empty() ) {
|
||||
// No data to process.
|
||||
$this->clear_scheduled_event();
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->handle();
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule event
|
||||
*/
|
||||
protected function schedule_event() {
|
||||
if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
|
||||
wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear scheduled event
|
||||
*/
|
||||
protected function clear_scheduled_event() {
|
||||
$timestamp = wp_next_scheduled( $this->cron_hook_identifier );
|
||||
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel Process
|
||||
*
|
||||
* Stop processing queue items, clear cronjob and delete batch.
|
||||
*
|
||||
*/
|
||||
public function cancel_process() {
|
||||
if ( ! $this->is_queue_empty() ) {
|
||||
$batch = $this->get_batch();
|
||||
|
||||
$this->delete( $batch->key );
|
||||
|
||||
wp_clear_scheduled_hook( $this->cron_hook_identifier );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Task
|
||||
*
|
||||
* Override this method to perform any actions required on each
|
||||
* queue item. Return the modified item for further processing
|
||||
* in the next pass through. Or, return false to remove the
|
||||
* item from the queue.
|
||||
*
|
||||
* @param mixed $item Queue item to iterate over.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract protected function task( $item );
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Base;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Background_Task_Manager extends BaseModule {
|
||||
/**
|
||||
* @var Background_Task
|
||||
*/
|
||||
protected $task_runner;
|
||||
|
||||
abstract public function get_action();
|
||||
abstract public function get_plugin_name();
|
||||
abstract public function get_plugin_label();
|
||||
abstract public function get_task_runner_class();
|
||||
abstract public function get_query_limit();
|
||||
|
||||
abstract protected function start_run();
|
||||
|
||||
public function on_runner_start() {
|
||||
$logger = Plugin::$instance->logger->get_logger();
|
||||
$logger->info( $this->get_plugin_name() . '::' . $this->get_action() . ' Started' );
|
||||
}
|
||||
|
||||
public function on_runner_complete( $did_tasks = false ) {
|
||||
$logger = Plugin::$instance->logger->get_logger();
|
||||
$logger->info( $this->get_plugin_name() . '::' . $this->get_action() . ' Completed' );
|
||||
}
|
||||
|
||||
public function get_task_runner() {
|
||||
if ( empty( $this->task_runner ) ) {
|
||||
$class_name = $this->get_task_runner_class();
|
||||
$this->task_runner = new $class_name( $this );
|
||||
}
|
||||
|
||||
return $this->task_runner;
|
||||
}
|
||||
|
||||
// TODO: Replace with a db settings system.
|
||||
protected function add_flag( $flag ) {
|
||||
add_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag, 1 );
|
||||
}
|
||||
|
||||
protected function get_flag( $flag ) {
|
||||
return get_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag );
|
||||
}
|
||||
|
||||
protected function delete_flag( $flag ) {
|
||||
delete_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag );
|
||||
}
|
||||
|
||||
protected function get_start_action_url() {
|
||||
return wp_nonce_url( add_query_arg( $this->get_action(), 'run' ), $this->get_action() . 'run' );
|
||||
}
|
||||
|
||||
protected function get_continue_action_url() {
|
||||
return wp_nonce_url( add_query_arg( $this->get_action(), 'continue' ), $this->get_action() . 'continue' );
|
||||
}
|
||||
|
||||
private function continue_run() {
|
||||
$runner = $this->get_task_runner();
|
||||
$runner->continue_run();
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
if ( empty( $_GET[ $this->get_action() ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Plugin::$instance->init_common();
|
||||
|
||||
if ( 'run' === $_GET[ $this->get_action() ] && check_admin_referer( $this->get_action() . 'run' ) ) {
|
||||
$this->start_run();
|
||||
}
|
||||
|
||||
if ( 'continue' === $_GET[ $this->get_action() ] && check_admin_referer( $this->get_action() . 'continue' ) ) {
|
||||
$this->continue_run();
|
||||
}
|
||||
|
||||
wp_safe_redirect( remove_query_arg( [ $this->get_action(), '_wpnonce' ] ) );
|
||||
die;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Base;
|
||||
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Core\Base\BackgroundProcess\WP_Background_Process;
|
||||
|
||||
/**
|
||||
* Based on https://github.com/woocommerce/woocommerce/blob/master/includes/abstracts/class-wc-background-process.php
|
||||
* & https://github.com/woocommerce/woocommerce/blob/master/includes/class-wc-background-updater.php
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
* WC_Background_Process class.
|
||||
*/
|
||||
abstract class Background_Task extends WP_Background_Process {
|
||||
protected $current_item;
|
||||
|
||||
/**
|
||||
* Dispatch updater.
|
||||
*
|
||||
* Updater will still run via cron job if this fails for any reason.
|
||||
*/
|
||||
public function dispatch() {
|
||||
$dispatched = parent::dispatch();
|
||||
|
||||
if ( is_wp_error( $dispatched ) ) {
|
||||
wp_die( $dispatched );
|
||||
}
|
||||
}
|
||||
|
||||
public function query_col( $sql ) {
|
||||
global $wpdb;
|
||||
|
||||
// Add Calc.
|
||||
$item = $this->get_current_item();
|
||||
if ( empty( $item['total'] ) ) {
|
||||
$sql = preg_replace( '/^SELECT/', 'SELECT SQL_CALC_FOUND_ROWS', $sql );
|
||||
}
|
||||
|
||||
// Add offset & limit.
|
||||
$sql = preg_replace( '/;$/', '', $sql );
|
||||
$sql .= ' LIMIT %d, %d;';
|
||||
|
||||
$results = $wpdb->get_col( $wpdb->prepare( $sql, $this->get_current_offset(), $this->get_limit() ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
if ( ! empty( $results ) ) {
|
||||
$this->set_total();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function should_run_again( $updated_rows ) {
|
||||
return count( $updated_rows ) === $this->get_limit();
|
||||
}
|
||||
|
||||
public function get_current_offset() {
|
||||
$limit = $this->get_limit();
|
||||
return ( $this->current_item['iterate_num'] - 1 ) * $limit;
|
||||
}
|
||||
|
||||
public function get_limit() {
|
||||
return $this->manager->get_query_limit();
|
||||
}
|
||||
|
||||
public function set_total() {
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $this->current_item['total'] ) ) {
|
||||
$total_rows = $wpdb->get_var( 'SELECT FOUND_ROWS();' );
|
||||
$total_iterates = ceil( $total_rows / $this->get_limit() );
|
||||
$this->current_item['total'] = $total_iterates;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete
|
||||
*
|
||||
* Override if applicable, but ensure that the below actions are
|
||||
* performed, or, call parent::complete().
|
||||
*/
|
||||
protected function complete() {
|
||||
$this->manager->on_runner_complete( true );
|
||||
|
||||
parent::complete();
|
||||
}
|
||||
|
||||
public function continue_run() {
|
||||
// Used to fire an action added in WP_Background_Process::_construct() that calls WP_Background_Process::handle_cron_healthcheck().
|
||||
// This method will make sure the database updates are executed even if cron is disabled. Nothing will happen if the updates are already running.
|
||||
do_action( $this->cron_hook_identifier );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_current_item() {
|
||||
return $this->current_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch.
|
||||
*
|
||||
* @return \stdClass Return the first batch from the queue.
|
||||
*/
|
||||
protected function get_batch() {
|
||||
$batch = parent::get_batch();
|
||||
$batch->data = array_filter( (array) $batch->data );
|
||||
|
||||
return $batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle cron healthcheck
|
||||
*
|
||||
* Restart the background process if not already running
|
||||
* and data exists in the queue.
|
||||
*/
|
||||
public function handle_cron_healthcheck() {
|
||||
if ( $this->is_process_running() ) {
|
||||
// Background process already running.
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_queue_empty() ) {
|
||||
// No data to process.
|
||||
$this->clear_scheduled_event();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule fallback event.
|
||||
*/
|
||||
protected function schedule_event() {
|
||||
if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
|
||||
wp_schedule_event( time() + 10, $this->cron_interval_identifier, $this->cron_hook_identifier );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the updater running?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_running() {
|
||||
return false === $this->is_queue_empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the batch limit has been exceeded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function batch_limit_exceeded() {
|
||||
return $this->time_exceeded() || $this->memory_exceeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle.
|
||||
*
|
||||
* Pass each queue item to the task handler, while remaining
|
||||
* within server memory and time limit constraints.
|
||||
*/
|
||||
protected function handle() {
|
||||
$this->manager->on_runner_start();
|
||||
|
||||
$this->lock_process();
|
||||
|
||||
do {
|
||||
$batch = $this->get_batch();
|
||||
|
||||
foreach ( $batch->data as $key => $value ) {
|
||||
$task = $this->task( $value );
|
||||
|
||||
if ( false !== $task ) {
|
||||
$batch->data[ $key ] = $task;
|
||||
} else {
|
||||
unset( $batch->data[ $key ] );
|
||||
}
|
||||
|
||||
if ( $this->batch_limit_exceeded() ) {
|
||||
// Batch limits reached.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update or delete current batch.
|
||||
if ( ! empty( $batch->data ) ) {
|
||||
$this->update( $batch->key, $batch->data );
|
||||
} else {
|
||||
$this->delete( $batch->key );
|
||||
}
|
||||
} while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() );
|
||||
|
||||
$this->unlock_process();
|
||||
|
||||
// Start next batch or complete process.
|
||||
if ( ! $this->is_queue_empty() ) {
|
||||
$this->dispatch();
|
||||
} else {
|
||||
$this->complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the protected `is_process_running` method as a public method.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_process_locked() {
|
||||
return $this->is_process_running();
|
||||
}
|
||||
|
||||
public function handle_immediately( $callbacks ) {
|
||||
$this->manager->on_runner_start();
|
||||
|
||||
$this->lock_process();
|
||||
|
||||
foreach ( $callbacks as $callback ) {
|
||||
$item = [
|
||||
'callback' => $callback,
|
||||
];
|
||||
|
||||
do {
|
||||
$item = $this->task( $item );
|
||||
} while ( $item );
|
||||
}
|
||||
|
||||
$this->unlock_process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Task
|
||||
*
|
||||
* Override this method to perform any actions required on each
|
||||
* queue item. Return the modified item for further processing
|
||||
* in the next pass through. Or, return false to remove the
|
||||
* item from the queue.
|
||||
*
|
||||
* @param array $item
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function task( $item ) {
|
||||
$result = false;
|
||||
|
||||
if ( ! isset( $item['iterate_num'] ) ) {
|
||||
$item['iterate_num'] = 1;
|
||||
}
|
||||
|
||||
$logger = Plugin::$instance->logger->get_logger();
|
||||
$callback = $this->format_callback_log( $item );
|
||||
|
||||
if ( is_callable( $item['callback'] ) ) {
|
||||
$progress = '';
|
||||
|
||||
if ( 1 < $item['iterate_num'] ) {
|
||||
if ( empty( $item['total'] ) ) {
|
||||
$progress = sprintf( '(x%s)', $item['iterate_num'] );
|
||||
} else {
|
||||
$percent = ceil( $item['iterate_num'] / ( $item['total'] / 100 ) );
|
||||
$progress = sprintf( '(%s of %s, %s%%)', $item['iterate_num'], $item['total'], $percent );
|
||||
}
|
||||
}
|
||||
|
||||
$logger->info( sprintf( '%s Start %s', $callback, $progress ) );
|
||||
|
||||
$this->current_item = $item;
|
||||
|
||||
$result = (bool) call_user_func( $item['callback'], $this );
|
||||
|
||||
// get back the updated item.
|
||||
$item = $this->current_item;
|
||||
$this->current_item = null;
|
||||
|
||||
if ( $result ) {
|
||||
if ( empty( $item['total'] ) ) {
|
||||
$logger->info( sprintf( '%s callback needs to run again', $callback ) );
|
||||
} elseif ( 1 === $item['iterate_num'] ) {
|
||||
$logger->info( sprintf( '%s callback needs to run more %d times', $callback, $item['total'] - $item['iterate_num'] ) );
|
||||
}
|
||||
|
||||
$item['iterate_num']++;
|
||||
} else {
|
||||
$logger->info( sprintf( '%s Finished', $callback ) );
|
||||
}
|
||||
} else {
|
||||
$logger->notice( sprintf( 'Could not find %s callback', $callback ) );
|
||||
}
|
||||
|
||||
return $result ? $item : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule cron healthcheck.
|
||||
*
|
||||
* @param array $schedules Schedules.
|
||||
* @return array
|
||||
*/
|
||||
public function schedule_cron_healthcheck( $schedules ) {
|
||||
$interval = apply_filters( $this->identifier . '_cron_interval', 5 );
|
||||
|
||||
// Adds every 5 minutes to the existing schedules.
|
||||
$schedules[ $this->identifier . '_cron_interval' ] = array(
|
||||
'interval' => MINUTE_IN_SECONDS * $interval,
|
||||
/* translators: %d: interval */
|
||||
'display' => sprintf( __( 'Every %d minutes', 'elementor' ), $interval ),
|
||||
);
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
/**
|
||||
* See if the batch limit has been exceeded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_memory_exceeded() {
|
||||
return $this->memory_exceeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all batches.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function delete_all_batches() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
}
|
||||
|
||||
$key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
|
||||
|
||||
$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill process.
|
||||
*
|
||||
* Stop processing queue items, clear cronjob and delete all batches.
|
||||
*/
|
||||
public function kill_process() {
|
||||
if ( ! $this->is_queue_empty() ) {
|
||||
$this->delete_all_batches();
|
||||
wp_clear_scheduled_hook( $this->cron_hook_identifier );
|
||||
}
|
||||
}
|
||||
|
||||
public function set_current_item( $item ) {
|
||||
$this->current_item = $item;
|
||||
}
|
||||
|
||||
protected function format_callback_log( $item ) {
|
||||
return implode( '::', (array) $item['callback'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \Elementor\Core\Base\Background_Task_Manager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
public function __construct( $manager ) {
|
||||
$this->manager = $manager;
|
||||
// Uses unique prefix per blog so each blog has separate queue.
|
||||
$this->prefix = 'elementor_' . get_current_blog_id();
|
||||
$this->action = $this->manager->get_action();
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Base Object
|
||||
*
|
||||
* Base class that provides basic settings handling functionality.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*/
|
||||
class Base_Object {
|
||||
|
||||
/**
|
||||
* Settings.
|
||||
*
|
||||
* Holds the object settings.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* Get Settings.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $setting Optional. The key of the requested setting. Default is null.
|
||||
*
|
||||
* @return mixed An array of all settings, or a single value if `$setting` was specified.
|
||||
*/
|
||||
final public function get_settings( $setting = null ) {
|
||||
$this->ensure_settings();
|
||||
|
||||
return self::get_items( $this->settings, $setting );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set settings.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array|string $key If key is an array, the settings are overwritten by that array. Otherwise, the
|
||||
* settings of the key will be set to the given `$value` param.
|
||||
*
|
||||
* @param mixed $value Optional. Default is null.
|
||||
*/
|
||||
final public function set_settings( $key, $value = null ) {
|
||||
$this->ensure_settings();
|
||||
|
||||
if ( is_array( $key ) ) {
|
||||
$this->settings = $key;
|
||||
} else {
|
||||
$this->settings[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete setting.
|
||||
*
|
||||
* Deletes the settings array or a specific key of the settings array if `$key` is specified.
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $key Optional. Default is null.
|
||||
*/
|
||||
public function delete_setting( $key = null ) {
|
||||
if ( $key ) {
|
||||
unset( $this->settings[ $key ] );
|
||||
} else {
|
||||
$this->settings = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items.
|
||||
*
|
||||
* Utility method that receives an array with a needle and returns all the
|
||||
* items that match the needle. If needle is not defined the entire haystack
|
||||
* will be returned.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
* @static
|
||||
*
|
||||
* @param array $haystack An array of items.
|
||||
* @param string $needle Optional. Needle. Default is null.
|
||||
*
|
||||
* @return mixed The whole haystack or the needle from the haystack when requested.
|
||||
*/
|
||||
final protected static function get_items( array $haystack, $needle = null ) {
|
||||
if ( $needle ) {
|
||||
return isset( $haystack[ $needle ] ) ? $haystack[ $needle ] : null;
|
||||
}
|
||||
|
||||
return $haystack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get init settings.
|
||||
*
|
||||
* Used to define the default/initial settings of the object. Inheriting classes may implement this method to define
|
||||
* their own default/initial settings.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_init_settings() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure settings.
|
||||
*
|
||||
* Ensures that the `$settings` member is initialized
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access private
|
||||
*/
|
||||
private function ensure_settings() {
|
||||
if ( null === $this->settings ) {
|
||||
$this->settings = $this->get_init_settings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Base;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class DB_Upgrades_Manager extends Background_Task_Manager {
|
||||
protected $current_version = null;
|
||||
protected $query_limit = 100;
|
||||
|
||||
abstract public function get_new_version();
|
||||
abstract public function get_version_option_name();
|
||||
abstract public function get_upgrades_class();
|
||||
abstract public function get_updater_label();
|
||||
|
||||
public function get_task_runner_class() {
|
||||
return 'Elementor\Core\Upgrade\Updater';
|
||||
}
|
||||
|
||||
public function get_query_limit() {
|
||||
return $this->query_limit;
|
||||
}
|
||||
|
||||
public function set_query_limit( $limit ) {
|
||||
$this->query_limit = $limit;
|
||||
}
|
||||
|
||||
public function get_current_version() {
|
||||
if ( null === $this->current_version ) {
|
||||
$this->current_version = get_option( $this->get_version_option_name() );
|
||||
}
|
||||
|
||||
return $this->current_version;
|
||||
}
|
||||
|
||||
public function should_upgrade() {
|
||||
$current_version = $this->get_current_version();
|
||||
|
||||
// It's a new install.
|
||||
if ( ! $current_version ) {
|
||||
$this->update_db_version();
|
||||
return false;
|
||||
}
|
||||
|
||||
return version_compare( $this->get_new_version(), $current_version, '>' );
|
||||
}
|
||||
|
||||
public function on_runner_start() {
|
||||
parent::on_runner_start();
|
||||
|
||||
define( 'IS_ELEMENTOR_UPGRADE', true );
|
||||
}
|
||||
|
||||
public function on_runner_complete( $did_tasks = false ) {
|
||||
$logger = Plugin::$instance->logger->get_logger();
|
||||
|
||||
$logger->info( 'Elementor data updater process has been completed.', [
|
||||
'meta' => [
|
||||
'plugin' => $this->get_plugin_label(),
|
||||
'from' => $this->current_version,
|
||||
'to' => $this->get_new_version(),
|
||||
],
|
||||
] );
|
||||
|
||||
Plugin::$instance->files_manager->clear_cache();
|
||||
|
||||
$this->update_db_version();
|
||||
|
||||
if ( $did_tasks ) {
|
||||
$this->add_flag( 'completed' );
|
||||
}
|
||||
}
|
||||
|
||||
public function admin_notice_start_upgrade() {
|
||||
$upgrade_link = $this->get_start_action_url();
|
||||
$message = '<p>' . sprintf( __( '%s Your site database needs to be updated to the latest version.', 'elementor' ), $this->get_updater_label() ) . '</p>';
|
||||
$message .= '<p>' . sprintf( '<a href="%s" class="button-primary">%s</a>', $upgrade_link, __( 'Update Now', 'elementor' ) ) . '</p>';
|
||||
|
||||
echo '<div class="notice notice-error is-dismissible">' . $message . '</div>';
|
||||
}
|
||||
|
||||
public function admin_notice_upgrade_is_running() {
|
||||
$upgrade_link = $this->get_continue_action_url();
|
||||
$message = '<p>' . sprintf( __( '%s Database update process is running in the background.', 'elementor' ), $this->get_updater_label() ) . '</p>';
|
||||
$message .= '<p>' . __( 'Taking a while?', 'elementor' ) . ' <a href="' . $upgrade_link . '" class="button-primary">' . __( 'Click here to run it now', 'elementor' ) . '</a></p>';
|
||||
|
||||
echo '<div class="notice notice-warning is-dismissible">' . $message . '</div>';
|
||||
}
|
||||
|
||||
public function admin_notice_upgrade_is_completed() {
|
||||
$this->delete_flag( 'completed' );
|
||||
|
||||
$message = '<p>' . sprintf( __( '%s The database update process is now complete. Thank you for updating to the latest version!', 'elementor' ), $this->get_updater_label() ) . '</p>';
|
||||
|
||||
echo '<div class="notice notice-success is-dismissible">' . $message . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @access protected
|
||||
*/
|
||||
protected function start_run() {
|
||||
$updater = $this->get_task_runner();
|
||||
|
||||
if ( $updater->is_running() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$upgrade_callbacks = $this->get_upgrade_callbacks();
|
||||
|
||||
if ( empty( $upgrade_callbacks ) ) {
|
||||
$this->on_runner_complete();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $upgrade_callbacks as $callback ) {
|
||||
$updater->push_to_queue( [
|
||||
'callback' => $callback,
|
||||
] );
|
||||
}
|
||||
|
||||
$updater->save()->dispatch();
|
||||
|
||||
Plugin::$instance->logger->get_logger()->info( 'Elementor data updater process has been queued.', [
|
||||
'meta' => [
|
||||
'plugin' => $this->get_plugin_label(),
|
||||
'from' => $this->current_version,
|
||||
'to' => $this->get_new_version(),
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
protected function update_db_version() {
|
||||
update_option( $this->get_version_option_name(), $this->get_new_version() );
|
||||
}
|
||||
|
||||
public function get_upgrade_callbacks() {
|
||||
$prefix = '_v_';
|
||||
$upgrades_class = $this->get_upgrades_class();
|
||||
$upgrades_reflection = new \ReflectionClass( $upgrades_class );
|
||||
|
||||
$callbacks = [];
|
||||
|
||||
foreach ( $upgrades_reflection->getMethods() as $method ) {
|
||||
$method_name = $method->getName();
|
||||
if ( false === strpos( $method_name, $prefix ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! preg_match_all( "/$prefix(\d+_\d+_\d+)/", $method_name, $matches ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$method_version = str_replace( '_', '.', $matches[1][0] );
|
||||
|
||||
if ( ! version_compare( $method_version, $this->current_version, '>' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$callbacks[] = [ $upgrades_class, $method_name ];
|
||||
}
|
||||
|
||||
return $callbacks;
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
// If upgrade is completed - show the notice only for admins.
|
||||
// Note: in this case `should_upgrade` returns false, because it's already upgraded.
|
||||
if ( is_admin() && current_user_can( 'update_plugins' ) && $this->get_flag( 'completed' ) ) {
|
||||
add_action( 'admin_notices', [ $this, 'admin_notice_upgrade_is_completed' ] );
|
||||
}
|
||||
|
||||
if ( ! $this->should_upgrade() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$updater = $this->get_task_runner();
|
||||
|
||||
$this->start_run();
|
||||
|
||||
if ( $updater->is_running() && current_user_can( 'update_plugins' ) ) {
|
||||
add_action( 'admin_notices', [ $this, 'admin_notice_upgrade_is_running' ] );
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor module.
|
||||
*
|
||||
* An abstract class that provides the needed properties and methods to
|
||||
* manage and handle modules in inheriting classes.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @abstract
|
||||
*/
|
||||
abstract class Module extends Base_Object {
|
||||
|
||||
/**
|
||||
* Module class reflection.
|
||||
*
|
||||
* Holds the information about a class.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access private
|
||||
*
|
||||
* @var \ReflectionClass
|
||||
*/
|
||||
private $reflection;
|
||||
|
||||
/**
|
||||
* Module components.
|
||||
*
|
||||
* Holds the module components.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $components = [];
|
||||
|
||||
/**
|
||||
* Module instance.
|
||||
*
|
||||
* Holds the module instance.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access protected
|
||||
*
|
||||
* @var Module
|
||||
*/
|
||||
protected static $_instances = [];
|
||||
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
* @abstract
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
abstract public function get_name();
|
||||
|
||||
/**
|
||||
* Instance.
|
||||
*
|
||||
* Ensures only one instance of the module class is loaded or can be loaded.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return Module An instance of the class.
|
||||
*/
|
||||
public static function instance() {
|
||||
$class_name = static::class_name();
|
||||
|
||||
if ( empty( static::$_instances[ $class_name ] ) ) {
|
||||
static::$_instances[ $class_name ] = new static();
|
||||
}
|
||||
|
||||
return static::$_instances[ $class_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function is_active() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class name.
|
||||
*
|
||||
* Retrieve the name of the class.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function class_name() {
|
||||
return get_called_class();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone.
|
||||
*
|
||||
* Disable class cloning and throw an error on object clone.
|
||||
*
|
||||
* The whole idea of the singleton design pattern is that there is a single
|
||||
* object. Therefore, we don't want the object to be cloned.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
*/
|
||||
public function __clone() {
|
||||
// Cloning instances of the class is forbidden
|
||||
_doing_it_wrong( __FUNCTION__, esc_html__( 'Something went wrong.', 'elementor' ), '1.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wakeup.
|
||||
*
|
||||
* Disable unserializing of the class.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
*/
|
||||
public function __wakeup() {
|
||||
// Unserializing instances of the class is forbidden
|
||||
_doing_it_wrong( __FUNCTION__, esc_html__( 'Something went wrong.', 'elementor' ), '1.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_reflection() {
|
||||
if ( null === $this->reflection ) {
|
||||
$this->reflection = new \ReflectionClass( $this );
|
||||
}
|
||||
|
||||
return $this->reflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add module component.
|
||||
*
|
||||
* Add new component to the current module.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $id Component ID.
|
||||
* @param mixed $instance An instance of the component.
|
||||
*/
|
||||
public function add_component( $id, $instance ) {
|
||||
$this->components[ $id ] = $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
* @return Module[]
|
||||
*/
|
||||
public function get_components() {
|
||||
return $this->components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module component.
|
||||
*
|
||||
* Retrieve the module component.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $id Component ID.
|
||||
*
|
||||
* @return mixed An instance of the component, or `false` if the component
|
||||
* doesn't exist.
|
||||
*/
|
||||
public function get_component( $id ) {
|
||||
if ( isset( $this->components[ $id ] ) ) {
|
||||
return $this->components[ $id ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assets url.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @param string $file_name
|
||||
* @param string $file_extension
|
||||
* @param string $relative_url Optional. Default is null.
|
||||
* @param string $add_min_suffix Optional. Default is 'default'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final protected function get_assets_url( $file_name, $file_extension, $relative_url = null, $add_min_suffix = 'default' ) {
|
||||
static $is_test_mode = null;
|
||||
|
||||
if ( null === $is_test_mode ) {
|
||||
$is_test_mode = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || defined( 'ELEMENTOR_TESTS' ) && ELEMENTOR_TESTS;
|
||||
}
|
||||
|
||||
if ( ! $relative_url ) {
|
||||
$relative_url = $this->get_assets_relative_url() . $file_extension . '/';
|
||||
}
|
||||
|
||||
$url = $this->get_assets_base_url() . $relative_url . $file_name;
|
||||
|
||||
if ( 'default' === $add_min_suffix ) {
|
||||
$add_min_suffix = ! $is_test_mode;
|
||||
}
|
||||
|
||||
if ( $add_min_suffix ) {
|
||||
$url .= '.min';
|
||||
}
|
||||
|
||||
return $url . '.' . $file_extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get js assets url
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @param string $file_name
|
||||
* @param string $relative_url Optional. Default is null.
|
||||
* @param string $add_min_suffix Optional. Default is 'default'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final protected function get_js_assets_url( $file_name, $relative_url = null, $add_min_suffix = 'default' ) {
|
||||
return $this->get_assets_url( $file_name, 'js', $relative_url, $add_min_suffix );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get css assets url
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @param string $file_name
|
||||
* @param string $relative_url Optional. Default is null.
|
||||
* @param string $add_min_suffix Optional. Default is 'default'.
|
||||
* @param bool $add_direction_suffix Optional. Default is `false`
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
final protected function get_css_assets_url( $file_name, $relative_url = null, $add_min_suffix = 'default', $add_direction_suffix = false ) {
|
||||
static $direction_suffix = null;
|
||||
|
||||
if ( ! $direction_suffix ) {
|
||||
$direction_suffix = is_rtl() ? '-rtl' : '';
|
||||
}
|
||||
|
||||
if ( $add_direction_suffix ) {
|
||||
$file_name .= $direction_suffix;
|
||||
}
|
||||
|
||||
return $this->get_assets_url( $file_name, 'css', $relative_url, $add_min_suffix );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assets base url
|
||||
*
|
||||
* @since 2.6.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_assets_base_url() {
|
||||
return ELEMENTOR_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assets relative url
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_assets_relative_url() {
|
||||
return 'assets/';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common;
|
||||
|
||||
use Elementor\Core\Base\App as BaseApp;
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Core\Common\Modules\Finder\Module as Finder;
|
||||
use Elementor\Core\Common\Modules\Connect\Module as Connect;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* App
|
||||
*
|
||||
* Elementor's common app that groups shared functionality, components and configuration
|
||||
*
|
||||
* @since 2.3.0
|
||||
*/
|
||||
class App extends BaseApp {
|
||||
|
||||
private $templates = [];
|
||||
|
||||
/**
|
||||
* App constructor.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->add_default_templates();
|
||||
|
||||
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/editor/before_enqueue_styles', [ $this, 'register_styles' ] );
|
||||
add_action( 'admin_enqueue_scripts', [ $this, 'register_styles' ] );
|
||||
add_action( 'wp_enqueue_scripts', [ $this, 'register_styles' ], 9 );
|
||||
|
||||
add_action( 'elementor/editor/footer', [ $this, 'print_templates' ] );
|
||||
add_action( 'admin_footer', [ $this, 'print_templates' ] );
|
||||
add_action( 'wp_footer', [ $this, 'print_templates' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Init components
|
||||
*
|
||||
* Initializing common components.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function init_components() {
|
||||
$this->add_component( 'ajax', new Ajax() );
|
||||
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
if ( ! is_customize_preview() ) {
|
||||
$this->add_component( 'finder', new Finder() );
|
||||
}
|
||||
}
|
||||
|
||||
$this->add_component( 'connect', new Connect() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* Retrieve the app name.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Common app name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'common';
|
||||
}
|
||||
|
||||
/**
|
||||
* Register scripts.
|
||||
*
|
||||
* Register common scripts.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function register_scripts() {
|
||||
wp_register_script(
|
||||
'elementor-common-modules',
|
||||
$this->get_js_assets_url( 'common-modules' ),
|
||||
[],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'backbone-marionette',
|
||||
$this->get_js_assets_url( 'backbone.marionette', 'assets/lib/backbone/' ),
|
||||
[
|
||||
'backbone',
|
||||
],
|
||||
'2.4.5',
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'backbone-radio',
|
||||
$this->get_js_assets_url( 'backbone.radio', 'assets/lib/backbone/' ),
|
||||
[
|
||||
'backbone',
|
||||
],
|
||||
'1.0.4',
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'elementor-dialog',
|
||||
$this->get_js_assets_url( 'dialog', 'assets/lib/dialog/' ),
|
||||
[
|
||||
'jquery-ui-position',
|
||||
],
|
||||
'4.8.1',
|
||||
true
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'elementor-common',
|
||||
$this->get_js_assets_url( 'common' ),
|
||||
[
|
||||
'jquery',
|
||||
'jquery-ui-draggable',
|
||||
'backbone-marionette',
|
||||
'backbone-radio',
|
||||
'elementor-common-modules',
|
||||
'elementor-dialog',
|
||||
'wp-api-request',
|
||||
],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
$this->print_config();
|
||||
|
||||
// Used for external plugins.
|
||||
do_action( 'elementor/common/after_register_scripts', $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register styles.
|
||||
*
|
||||
* Register common styles.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function register_styles() {
|
||||
wp_register_style(
|
||||
'elementor-icons',
|
||||
$this->get_css_assets_url( 'elementor-icons', 'assets/lib/eicons/css/' ),
|
||||
[],
|
||||
'5.9.1'
|
||||
);
|
||||
|
||||
wp_enqueue_style(
|
||||
'elementor-common',
|
||||
$this->get_css_assets_url( 'common', null, 'default', true ),
|
||||
[
|
||||
'elementor-icons',
|
||||
],
|
||||
ELEMENTOR_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add template.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $template Can be either a link to template file or template
|
||||
* HTML content.
|
||||
* @param string $type Optional. Whether to handle the template as path
|
||||
* or text. Default is `path`.
|
||||
*/
|
||||
public function add_template( $template, $type = 'path' ) {
|
||||
if ( 'path' === $type ) {
|
||||
ob_start();
|
||||
|
||||
include $template;
|
||||
|
||||
$template = ob_get_clean();
|
||||
}
|
||||
|
||||
$this->templates[] = $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print Templates
|
||||
*
|
||||
* Prints all registered templates.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function print_templates() {
|
||||
foreach ( $this->templates as $template ) {
|
||||
echo $template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get init settings.
|
||||
*
|
||||
* Define the default/initial settings of the common app.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_init_settings() {
|
||||
return [
|
||||
'version' => ELEMENTOR_VERSION,
|
||||
'isRTL' => is_rtl(),
|
||||
'isDebug' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ),
|
||||
'isElementorDebug' => ( defined( 'ELEMENTOR_DEBUG' ) && ELEMENTOR_DEBUG ),
|
||||
'activeModules' => array_keys( $this->get_components() ),
|
||||
'urls' => [
|
||||
'assets' => ELEMENTOR_ASSETS_URL,
|
||||
'rest' => get_rest_url(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default templates.
|
||||
*
|
||||
* Register common app default templates.
|
||||
* @since 2.3.0
|
||||
* @access private
|
||||
*/
|
||||
private function add_default_templates() {
|
||||
$default_templates = [
|
||||
'includes/editor-templates/library-layout.php',
|
||||
];
|
||||
|
||||
foreach ( $default_templates as $template ) {
|
||||
$this->add_template( ELEMENTOR_PATH . $template );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Ajax;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Core\Utils\Exceptions;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor ajax manager.
|
||||
*
|
||||
* Elementor ajax manager handler class is responsible for handling Elementor
|
||||
* ajax requests, ajax responses and registering actions applied on them.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Module extends BaseModule {
|
||||
|
||||
const NONCE_KEY = 'elementor_ajax';
|
||||
|
||||
/**
|
||||
* Ajax actions.
|
||||
*
|
||||
* Holds all the register ajax action.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $ajax_actions = [];
|
||||
|
||||
/**
|
||||
* Ajax requests.
|
||||
*
|
||||
* Holds all the register ajax requests.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $requests = [];
|
||||
|
||||
/**
|
||||
* Ajax response data.
|
||||
*
|
||||
* Holds all the response data for all the ajax requests.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $response_data = [];
|
||||
|
||||
/**
|
||||
* Current ajax action ID.
|
||||
*
|
||||
* Holds all the ID for the current ajax action.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $current_action_id = null;
|
||||
|
||||
/**
|
||||
* Ajax manager constructor.
|
||||
*
|
||||
* Initializing Elementor ajax manager.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'wp_ajax_elementor_ajax', [ $this, 'handle_ajax_request' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module name.
|
||||
*
|
||||
* Retrieve the module name.
|
||||
*
|
||||
* @since 1.7.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Module name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'ajax';
|
||||
}
|
||||
|
||||
/**
|
||||
* Register ajax action.
|
||||
*
|
||||
* Add new actions for a specific ajax request and the callback function to
|
||||
* be handle the response.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $tag Ajax request name/tag.
|
||||
* @param callable $callback The callback function.
|
||||
*/
|
||||
public function register_ajax_action( $tag, $callback ) {
|
||||
if ( ! did_action( 'elementor/ajax/register_actions' ) ) {
|
||||
_doing_it_wrong( __METHOD__, esc_html( sprintf( 'Use `%s` hook to register ajax action.', 'elementor/ajax/register_actions' ) ), '2.0.0' );
|
||||
}
|
||||
|
||||
$this->ajax_actions[ $tag ] = compact( 'tag', 'callback' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ajax request.
|
||||
*
|
||||
* Verify ajax nonce, and run all the registered actions for this request.
|
||||
*
|
||||
* Fired by `wp_ajax_elementor_ajax` action.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function handle_ajax_request() {
|
||||
if ( ! $this->verify_request_nonce() ) {
|
||||
$this->add_response_data( false, __( 'Token Expired.', 'elementor' ) )
|
||||
->send_error( Exceptions::UNAUTHORIZED );
|
||||
}
|
||||
|
||||
$editor_post_id = 0;
|
||||
|
||||
if ( ! empty( $_REQUEST['editor_post_id'] ) ) {
|
||||
$editor_post_id = absint( $_REQUEST['editor_post_id'] );
|
||||
|
||||
Plugin::$instance->db->switch_to_post( $editor_post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register ajax actions.
|
||||
*
|
||||
* Fires when an ajax request is received and verified.
|
||||
*
|
||||
* Used to register new ajax action handles.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param self $this An instance of ajax manager.
|
||||
*/
|
||||
do_action( 'elementor/ajax/register_actions', $this );
|
||||
|
||||
$this->requests = json_decode( stripslashes( $_REQUEST['actions'] ), true );
|
||||
|
||||
foreach ( $this->requests as $id => $action_data ) {
|
||||
$this->current_action_id = $id;
|
||||
|
||||
if ( ! isset( $this->ajax_actions[ $action_data['action'] ] ) ) {
|
||||
$this->add_response_data( false, __( 'Action not found.', 'elementor' ), Exceptions::BAD_REQUEST );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $editor_post_id ) {
|
||||
$action_data['data']['editor_post_id'] = $editor_post_id;
|
||||
}
|
||||
|
||||
try {
|
||||
$results = call_user_func( $this->ajax_actions[ $action_data['action'] ]['callback'], $action_data['data'], $this );
|
||||
|
||||
if ( false === $results ) {
|
||||
$this->add_response_data( false );
|
||||
} else {
|
||||
$this->add_response_data( true, $results );
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
$this->add_response_data( false, $e->getMessage(), $e->getCode() );
|
||||
}
|
||||
}
|
||||
|
||||
$this->current_action_id = null;
|
||||
|
||||
$this->send_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current action data.
|
||||
*
|
||||
* Retrieve the data for the current ajax request.
|
||||
*
|
||||
* @since 2.0.1
|
||||
* @access public
|
||||
*
|
||||
* @return bool|mixed Ajax request data if action exist, False otherwise.
|
||||
*/
|
||||
public function get_current_action_data() {
|
||||
if ( ! $this->current_action_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->requests[ $this->current_action_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create nonce.
|
||||
*
|
||||
* Creates a cryptographic token to
|
||||
* give the user an access to Elementor ajax actions.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string The nonce token.
|
||||
*/
|
||||
public function create_nonce() {
|
||||
return wp_create_nonce( self::NONCE_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify request nonce.
|
||||
*
|
||||
* Whether the request nonce verified or not.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return bool True if request nonce verified, False otherwise.
|
||||
*/
|
||||
public function verify_request_nonce() {
|
||||
return ! empty( $_REQUEST['_nonce'] ) && wp_verify_nonce( $_REQUEST['_nonce'], self::NONCE_KEY );
|
||||
}
|
||||
|
||||
protected function get_init_settings() {
|
||||
return [
|
||||
'url' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => $this->create_nonce(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax success response.
|
||||
*
|
||||
* Send a JSON response data back to the ajax request, indicating success.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*/
|
||||
private function send_success() {
|
||||
$response = [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'responses' => $this->response_data,
|
||||
],
|
||||
];
|
||||
|
||||
$json = wp_json_encode( $response );
|
||||
|
||||
while ( ob_get_status() ) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
if ( function_exists( 'gzencode' ) ) {
|
||||
$response = gzencode( $json );
|
||||
|
||||
header( 'Content-Type: application/json; charset=utf-8' );
|
||||
header( 'Content-Encoding: gzip' );
|
||||
header( 'Content-Length: ' . strlen( $response ) );
|
||||
|
||||
echo $response;
|
||||
} else {
|
||||
echo $json;
|
||||
}
|
||||
|
||||
wp_die( '', '', [ 'response' => null ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax failure response.
|
||||
*
|
||||
* Send a JSON response data back to the ajax request, indicating failure.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*
|
||||
* @param null $code
|
||||
*/
|
||||
private function send_error( $code = null ) {
|
||||
wp_send_json_error( [
|
||||
'responses' => $this->response_data,
|
||||
], $code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add response data.
|
||||
*
|
||||
* Add new response data to the array of all the ajax requests.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*
|
||||
* @param bool $success True if the requests returned successfully, False
|
||||
* otherwise.
|
||||
* @param mixed $data Optional. Response data. Default is null.
|
||||
*
|
||||
* @param int $code Optional. Response code. Default is 200.
|
||||
*
|
||||
* @return Module An instance of ajax manager.
|
||||
*/
|
||||
private function add_response_data( $success, $data = null, $code = 200 ) {
|
||||
$this->response_data[ $this->current_action_id ] = [
|
||||
'success' => $success,
|
||||
'code' => $code,
|
||||
'data' => $data,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Connect;
|
||||
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Settings;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Admin {
|
||||
|
||||
const PAGE_ID = 'elementor-connect';
|
||||
|
||||
public static $url = '';
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function register_admin_menu() {
|
||||
$submenu_page = add_submenu_page(
|
||||
Settings::PAGE_ID,
|
||||
__( 'Connect', 'elementor' ),
|
||||
__( 'Connect', 'elementor' ),
|
||||
'edit_posts',
|
||||
self::PAGE_ID,
|
||||
[ $this, 'render_page' ]
|
||||
);
|
||||
|
||||
add_action( 'load-' . $submenu_page, [ $this, 'on_load_page' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function hide_menu_item() {
|
||||
remove_submenu_page( Settings::PAGE_ID, self::PAGE_ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function on_load_page() {
|
||||
if ( isset( $_GET['action'], $_GET['app'] ) ) {
|
||||
$manager = Plugin::$instance->common->get_component( 'connect' );
|
||||
$app_slug = $_GET['app'];
|
||||
$app = $manager->get_app( $app_slug );
|
||||
$nonce_action = $_GET['app'] . $_GET['action'];
|
||||
|
||||
if ( ! $app ) {
|
||||
wp_die( 'Unknown app: ' . esc_attr( $app_slug ) );
|
||||
}
|
||||
|
||||
if ( empty( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], $nonce_action ) ) {
|
||||
wp_die( 'Invalid Nonce', 'Invalid Nonce', [
|
||||
'back_link' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
$method = 'action_' . $_GET['action'];
|
||||
|
||||
if ( method_exists( $app, $method ) ) {
|
||||
call_user_func( [ $app, $method ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function render_page() {
|
||||
$apps = Plugin::$instance->common->get_component( 'connect' )->get_apps();
|
||||
?>
|
||||
<style>
|
||||
.elementor-connect-app-wrapper{
|
||||
margin-bottom: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<div class="wrap">
|
||||
<?php
|
||||
|
||||
/** @var \Elementor\Core\Common\Modules\Connect\Apps\Base_App $app */
|
||||
foreach ( $apps as $app ) {
|
||||
echo '<div class="elementor-connect-app-wrapper">';
|
||||
$app->render_admin_widget();
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
?>
|
||||
</div><!-- /.wrap -->
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
self::$url = admin_url( 'admin.php?page=' . self::PAGE_ID );
|
||||
|
||||
add_action( 'admin_menu', [ $this, 'register_admin_menu' ], 206 );
|
||||
add_action( 'admin_head', [ $this, 'hide_menu_item' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Connect\Apps;
|
||||
|
||||
use Elementor\Core\Common\Modules\Connect\Admin;
|
||||
use Elementor\Tracker;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Base_App {
|
||||
|
||||
const OPTION_NAME_PREFIX = 'elementor_connect_';
|
||||
|
||||
const SITE_URL = 'https://my.elementor.com/connect/v1';
|
||||
|
||||
const API_URL = 'https://my.elementor.com/api/connect/v1';
|
||||
|
||||
protected $data = [];
|
||||
|
||||
protected $auth_mode = '';
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
* @abstract
|
||||
* TODO: make it public.
|
||||
*/
|
||||
abstract protected function get_slug();
|
||||
|
||||
/**
|
||||
* @since 2.8.0
|
||||
* @access public
|
||||
* TODO: make it abstract.
|
||||
*/
|
||||
public function get_title() {
|
||||
return $this->get_slug();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
* @abstract
|
||||
*/
|
||||
abstract protected function update_settings();
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function get_class_name() {
|
||||
return get_called_class();
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
* @abstract
|
||||
*/
|
||||
public function render_admin_widget() {
|
||||
echo '<h2>' . $this->get_title() . '</h2>';
|
||||
|
||||
if ( $this->is_connected() ) {
|
||||
$remote_user = $this->get( 'user' );
|
||||
$title = sprintf( __( 'Connected as %s', 'elementor' ), '<strong>' . $remote_user->email . '</strong>' );
|
||||
$label = __( 'Disconnect', 'elementor' );
|
||||
$url = $this->get_admin_url( 'disconnect' );
|
||||
$attr = '';
|
||||
|
||||
echo sprintf( '%s <a %s href="%s">%s</a>', $title, $attr, esc_attr( $url ), esc_html( $label ) );
|
||||
} else {
|
||||
echo 'Not Connected';
|
||||
}
|
||||
|
||||
echo '<hr>';
|
||||
|
||||
$this->print_app_info();
|
||||
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
printf( '<div><a href="%s">%s</a></div>', $this->get_admin_url( 'reset' ), __( 'Reset Data', 'elementor' ) );
|
||||
}
|
||||
|
||||
echo '<hr>';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_option_name() {
|
||||
return static::OPTION_NAME_PREFIX . $this->get_slug();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function admin_notice() {
|
||||
$notices = $this->get( 'notices' );
|
||||
|
||||
if ( ! $notices ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->print_notices( $notices );
|
||||
|
||||
$this->delete( 'notices' );
|
||||
}
|
||||
|
||||
|
||||
public function get_app_token_from_cli_token( $cli_token ) {
|
||||
$response = $this->request( 'get_app_token_from_cli_token', [
|
||||
'cli_token' => $cli_token,
|
||||
] );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
wp_die( $response, $response->get_error_message() );
|
||||
}
|
||||
|
||||
// Use state as usual.
|
||||
$_REQUEST['state'] = $this->get( 'state' );
|
||||
$_REQUEST['code'] = $response->code;
|
||||
}
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function action_authorize() {
|
||||
if ( $this->is_connected() ) {
|
||||
$this->add_notice( __( 'Already connected.', 'elementor' ), 'info' );
|
||||
$this->redirect_to_admin_page();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_client_id();
|
||||
$this->set_request_state();
|
||||
|
||||
$this->redirect_to_remote_authorize_url();
|
||||
}
|
||||
|
||||
public function action_reset() {
|
||||
delete_user_option( get_current_user_id(), 'elementor_connect_common_data' );
|
||||
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
delete_option( 'elementor_connect_site_key' );
|
||||
delete_option( 'elementor_remote_info_library' );
|
||||
}
|
||||
|
||||
$this->redirect_to_admin_page();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function action_get_token() {
|
||||
if ( $this->is_connected() ) {
|
||||
$this->redirect_to_admin_page();
|
||||
}
|
||||
|
||||
if ( empty( $_REQUEST['state'] ) || $_REQUEST['state'] !== $this->get( 'state' ) ) {
|
||||
$this->add_notice( 'Get Token: Invalid Request.', 'error' );
|
||||
$this->redirect_to_admin_page();
|
||||
}
|
||||
|
||||
$response = $this->request( 'get_token', [
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $_REQUEST['code'],
|
||||
'redirect_uri' => rawurlencode( $this->get_admin_url( 'get_token' ) ),
|
||||
'client_id' => $this->get( 'client_id' ),
|
||||
] );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$notice = 'Cannot Get Token:' . $response->get_error_message();
|
||||
$this->add_notice( $notice, 'error' );
|
||||
$this->redirect_to_admin_page();
|
||||
}
|
||||
|
||||
if ( ! empty( $response->data_share_opted_in ) && current_user_can( 'manage_options' ) ) {
|
||||
Tracker::set_opt_in( true );
|
||||
}
|
||||
|
||||
$this->delete( 'state' );
|
||||
$this->set( (array) $response );
|
||||
|
||||
$this->after_connect();
|
||||
|
||||
// Add the notice *after* the method `after_connect`, so an app can redirect without the notice.
|
||||
$this->add_notice( __( 'Connected Successfully.', 'elementor' ) );
|
||||
|
||||
$this->redirect_to_admin_page();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function action_disconnect() {
|
||||
if ( $this->is_connected() ) {
|
||||
$this->disconnect();
|
||||
$this->add_notice( __( 'Disconnected Successfully.', 'elementor' ) );
|
||||
}
|
||||
|
||||
$this->redirect_to_admin_page();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.8.0
|
||||
* @access public
|
||||
*/
|
||||
public function action_reconnect() {
|
||||
$this->disconnect();
|
||||
|
||||
$this->action_authorize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_admin_url( $action, $params = [] ) {
|
||||
$params = [
|
||||
'app' => $this->get_slug(),
|
||||
'action' => $action,
|
||||
'nonce' => wp_create_nonce( $this->get_slug() . $action ),
|
||||
] + $params;
|
||||
|
||||
// Encode base url, the encode is limited to 64 chars.
|
||||
$admin_url = \Requests_IDNAEncoder::encode( get_admin_url() );
|
||||
|
||||
$admin_url .= 'admin.php?page=' . Admin::PAGE_ID;
|
||||
|
||||
return add_query_arg( $params, $admin_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function is_connected() {
|
||||
return (bool) $this->get( 'access_token' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function init() {}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function init_data() {}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function after_connect() {}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function get( $key, $default = null ) {
|
||||
$this->init_data();
|
||||
|
||||
return isset( $this->data[ $key ] ) ? $this->data[ $key ] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function set( $key, $value = null ) {
|
||||
$this->init_data();
|
||||
|
||||
if ( is_array( $key ) ) {
|
||||
$this->data = array_replace_recursive( $this->data, $key );
|
||||
} else {
|
||||
$this->data[ $key ] = $value;
|
||||
}
|
||||
|
||||
$this->update_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function delete( $key = null ) {
|
||||
$this->init_data();
|
||||
|
||||
if ( $key ) {
|
||||
unset( $this->data[ $key ] );
|
||||
} else {
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
$this->update_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function add( $key, $value, $default = '' ) {
|
||||
$new_value = $this->get( $key, $default );
|
||||
|
||||
if ( is_array( $new_value ) ) {
|
||||
$new_value[] = $value;
|
||||
} elseif ( is_string( $new_value ) ) {
|
||||
$new_value .= $value;
|
||||
} elseif ( is_numeric( $new_value ) ) {
|
||||
$new_value += $value;
|
||||
}
|
||||
|
||||
$this->set( $key, $new_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function add_notice( $content, $type = 'success' ) {
|
||||
$this->add( 'notices', compact( 'content', 'type' ), [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function request( $action, $request_body = [], $as_array = false ) {
|
||||
$request_body = [
|
||||
'app' => $this->get_slug(),
|
||||
'access_token' => $this->get( 'access_token' ),
|
||||
'client_id' => $this->get( 'client_id' ),
|
||||
'local_id' => get_current_user_id(),
|
||||
'site_key' => $this->get_site_key(),
|
||||
'home_url' => trailingslashit( home_url() ),
|
||||
] + $request_body;
|
||||
|
||||
$headers = [];
|
||||
|
||||
if ( $this->is_connected() ) {
|
||||
$headers['X-Elementor-Signature'] = hash_hmac( 'sha256', wp_json_encode( $request_body, JSON_NUMERIC_CHECK ), $this->get( 'access_token_secret' ) );
|
||||
}
|
||||
|
||||
$response = wp_remote_post( $this->get_api_url() . '/' . $action, [
|
||||
'body' => $request_body,
|
||||
'headers' => $headers,
|
||||
'timeout' => 25,
|
||||
] );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
wp_die( $response, [
|
||||
'back_link' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$response_code = (int) wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( ! $response_code ) {
|
||||
return new \WP_Error( 500, 'No Response' );
|
||||
|
||||
}
|
||||
|
||||
// Server sent a success message without content.
|
||||
if ( 'null' === $body ) {
|
||||
$body = true;
|
||||
}
|
||||
|
||||
$body = json_decode( $body, $as_array );
|
||||
|
||||
if ( false === $body ) {
|
||||
return new \WP_Error( 422, 'Wrong Server Response' );
|
||||
}
|
||||
|
||||
if ( 200 !== $response_code ) {
|
||||
// In case $as_array = true.
|
||||
$body = (object) $body;
|
||||
|
||||
$message = isset( $body->message ) ? $body->message : wp_remote_retrieve_response_message( $response );
|
||||
$code = isset( $body->code ) ? $body->code : $response_code;
|
||||
|
||||
if ( 401 === $code ) {
|
||||
$this->delete();
|
||||
$this->action_authorize();
|
||||
}
|
||||
|
||||
return new \WP_Error( $code, $message );
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_api_url() {
|
||||
return static::API_URL . '/' . $this->get_slug();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_remote_site_url() {
|
||||
return static::SITE_URL . '/' . $this->get_slug();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_remote_authorize_url() {
|
||||
$redirect_uri = $this->get_auth_redirect_uri();
|
||||
|
||||
$url = add_query_arg( [
|
||||
'action' => 'authorize',
|
||||
'response_type' => 'code',
|
||||
'client_id' => $this->get( 'client_id' ),
|
||||
'auth_secret' => $this->get( 'auth_secret' ),
|
||||
'state' => $this->get( 'state' ),
|
||||
'redirect_uri' => rawurlencode( $redirect_uri ),
|
||||
'may_share_data' => current_user_can( 'manage_options' ) && ! Tracker::is_allow_track(),
|
||||
'reconnect_nonce' => wp_create_nonce( $this->get_slug() . 'reconnect' ),
|
||||
], $this->get_remote_site_url() );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function redirect_to_admin_page( $url = '' ) {
|
||||
if ( ! $url ) {
|
||||
$url = Admin::$url;
|
||||
}
|
||||
|
||||
switch ( $this->auth_mode ) {
|
||||
case 'popup':
|
||||
$this->print_popup_close_script( $url );
|
||||
break;
|
||||
|
||||
case 'cli':
|
||||
$this->admin_notice();
|
||||
die;
|
||||
|
||||
default:
|
||||
wp_safe_redirect( $url );
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function set_client_id() {
|
||||
if ( $this->get( 'client_id' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $this->request( 'get_client_id' );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
wp_die( $response, $response->get_error_message() );
|
||||
}
|
||||
|
||||
$this->set( 'client_id', $response->client_id );
|
||||
$this->set( 'auth_secret', $response->auth_secret );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function set_request_state() {
|
||||
$this->set( 'state', wp_generate_password( 12, false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function print_popup_close_script( $url ) {
|
||||
?>
|
||||
<script>
|
||||
if ( opener && opener !== window ) {
|
||||
opener.jQuery( 'body' ).trigger( 'elementor/connect/success/<?php echo esc_attr( $_REQUEST['callback_id'] ); ?>' );
|
||||
window.close();
|
||||
opener.focus();
|
||||
} else {
|
||||
location = '<?php echo $url; ?>';
|
||||
}
|
||||
</script>
|
||||
<?php
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function disconnect() {
|
||||
if ( $this->is_connected() ) {
|
||||
// Try update the server, but not needed to handle errors.
|
||||
$this->request( 'disconnect' );
|
||||
}
|
||||
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_site_key() {
|
||||
$site_key = get_option( 'elementor_connect_site_key' );
|
||||
|
||||
if ( ! $site_key ) {
|
||||
$site_key = md5( uniqid( wp_generate_password() ) );
|
||||
update_option( 'elementor_connect_site_key', $site_key );
|
||||
}
|
||||
|
||||
return $site_key;
|
||||
}
|
||||
|
||||
protected function redirect_to_remote_authorize_url() {
|
||||
switch ( $this->auth_mode ) {
|
||||
case 'cli':
|
||||
$this->get_app_token_from_cli_token( $_REQUEST['token'] );
|
||||
return;
|
||||
default:
|
||||
wp_redirect( $this->get_remote_authorize_url() );
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
protected function get_auth_redirect_uri() {
|
||||
$redirect_uri = $this->get_admin_url( 'get_token' );
|
||||
|
||||
switch ( $this->auth_mode ) {
|
||||
case 'popup':
|
||||
$redirect_uri = add_query_arg( [
|
||||
'mode' => 'popup',
|
||||
'callback_id' => esc_attr( $_REQUEST['callback_id'] ),
|
||||
], $redirect_uri );
|
||||
break;
|
||||
}
|
||||
|
||||
return $redirect_uri;
|
||||
}
|
||||
|
||||
|
||||
protected function print_notices( $notices ) {
|
||||
switch ( $this->auth_mode ) {
|
||||
case 'cli':
|
||||
foreach ( $notices as $notice ) {
|
||||
printf( '[%s] %s', $notice['type'], $notice['content'] );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
echo '<div id="message" class="updated notice is-dismissible"><p>';
|
||||
|
||||
foreach ( $notices as $notice ) {
|
||||
echo wp_kses_post( sprintf( '<div class="%s"><p>%s</p></div>', $notice['type'], wpautop( $notice['content'] ) ) );
|
||||
}
|
||||
|
||||
echo '</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss', 'elementor' ) . '</span></button></div>';
|
||||
}
|
||||
}
|
||||
|
||||
protected function get_app_info() {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function print_app_info() {
|
||||
$app_info = $this->get_app_info();
|
||||
|
||||
foreach ( $app_info as $key => $item ) {
|
||||
if ( $item['value'] ) {
|
||||
$status = 'Exist';
|
||||
$color = 'green';
|
||||
} else {
|
||||
$status = 'Empty';
|
||||
$color = 'red';
|
||||
}
|
||||
|
||||
printf( '%s: <strong style="color:%s">%s</strong><br>', $item['label'], $color, $status );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'admin_notices', [ $this, 'admin_notice' ] );
|
||||
|
||||
if ( isset( $_REQUEST['mode'] ) ) { // phpcs:ignore -- nonce validation is not require here.
|
||||
$allowed_auth_modes = [
|
||||
'popup',
|
||||
];
|
||||
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
$allowed_auth_modes[] = 'cli';
|
||||
}
|
||||
|
||||
$mode = $_REQUEST['mode']; // phpcs:ignore -- nonce validation is not require here.
|
||||
|
||||
if ( in_array( $mode, $allowed_auth_modes, true ) ) {
|
||||
$this->auth_mode = $mode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow extended apps to customize the __construct without call parent::__construct.
|
||||
*/
|
||||
$this->init();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Connect\Apps;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Base_User_App extends Base_App {
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function update_settings() {
|
||||
update_user_option( get_current_user_id(), $this->get_option_name(), $this->data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function init_data() {
|
||||
$this->data = get_user_option( $this->get_option_name() );
|
||||
|
||||
if ( ! $this->data ) {
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Connect\Apps;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Common_App extends Base_User_App {
|
||||
|
||||
protected static $common_data = null;
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_option_name() {
|
||||
return static::OPTION_NAME_PREFIX . 'common_data';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function init_data() {
|
||||
if ( is_null( self::$common_data ) ) {
|
||||
self::$common_data = get_user_option( static::get_option_name() );
|
||||
|
||||
if ( ! self::$common_data ) {
|
||||
self::$common_data = [];
|
||||
};
|
||||
}
|
||||
|
||||
$this->data = & self::$common_data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Connect\Apps;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Connect extends Common_App {
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Connect', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
protected function get_slug() {
|
||||
return 'connect';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function render_admin_widget() {}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Connect\Apps;
|
||||
|
||||
use Elementor\User;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Library extends Common_App {
|
||||
public function get_title() {
|
||||
return __( 'Library', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_slug() {
|
||||
return 'library';
|
||||
}
|
||||
|
||||
public function get_template_content( $id ) {
|
||||
if ( ! $this->is_connected() ) {
|
||||
return new \WP_Error( '401', __( 'Connecting to the Library failed. Please try reloading the page and try again', 'elementor' ) );
|
||||
}
|
||||
|
||||
$body_args = [
|
||||
'id' => $id,
|
||||
|
||||
// Which API version is used.
|
||||
'api_version' => ELEMENTOR_VERSION,
|
||||
// Which language to return.
|
||||
'site_lang' => get_bloginfo( 'language' ),
|
||||
];
|
||||
|
||||
/**
|
||||
* API: Template body args.
|
||||
*
|
||||
* Filters the body arguments send with the GET request when fetching the content.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array $body_args Body arguments.
|
||||
*/
|
||||
$body_args = apply_filters( 'elementor/api/get_templates/body_args', $body_args );
|
||||
|
||||
$template_content = $this->request( 'get_template_content', $body_args, true );
|
||||
|
||||
return $template_content;
|
||||
}
|
||||
|
||||
public function localize_settings( $settings ) {
|
||||
$is_connected = $this->is_connected();
|
||||
|
||||
return array_replace_recursive( $settings, [
|
||||
'i18n' => [
|
||||
// Route: library/connect
|
||||
'library/connect:title' => __( 'Connect to Template Library', 'elementor' ),
|
||||
'library/connect:message' => __( 'Access this template and our entire library by creating a free personal account', 'elementor' ),
|
||||
'library/connect:button' => __( 'Get Started', 'elementor' ),
|
||||
],
|
||||
'library_connect' => [
|
||||
'is_connected' => $is_connected,
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
public function library_connect_popup_seen() {
|
||||
User::set_introduction_viewed( [
|
||||
'introductionKey' => 'library_connect',
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Elementor\Core\Common\Modules\Ajax\Module $ajax_manager
|
||||
*/
|
||||
public function register_ajax_actions( $ajax_manager ) {
|
||||
$ajax_manager->register_ajax_action( 'library_connect_popup_seen', [ $this, 'library_connect_popup_seen' ] );
|
||||
}
|
||||
|
||||
protected function get_app_info() {
|
||||
return [
|
||||
'user_common_data' => [
|
||||
'label' => 'User Common Data',
|
||||
'value' => get_user_option( $this->get_option_name(), get_current_user_id() ),
|
||||
],
|
||||
'connect_site_key' => [
|
||||
'label' => 'Site Key',
|
||||
'value' => get_option( 'elementor_connect_site_key' ),
|
||||
],
|
||||
'remote_info_library' => [
|
||||
'label' => 'Remote Library Info',
|
||||
'value' => get_option( 'elementor_remote_info_library' ),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function init() {
|
||||
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
|
||||
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Connect;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Core\Common\Modules\Connect\Apps\Base_App;
|
||||
use Elementor\Core\Common\Modules\Connect\Apps\Connect;
|
||||
use Elementor\Core\Common\Modules\Connect\Apps\Library;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Module extends BaseModule {
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'connect';
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $registered_apps = [];
|
||||
|
||||
/**
|
||||
* Apps Instances.
|
||||
*
|
||||
* Holds the list of all the apps instances.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @var Base_App[]
|
||||
*/
|
||||
protected $apps = [];
|
||||
|
||||
/**
|
||||
* Registered apps categories.
|
||||
*
|
||||
* Holds the list of all the registered apps categories.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $categories = [];
|
||||
|
||||
protected $admin_page;
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->registered_apps = [
|
||||
'connect' => Connect::get_class_name(),
|
||||
'library' => Library::get_class_name(),
|
||||
];
|
||||
|
||||
// Note: The priority 11 is for allowing plugins to add their register callback on elementor init.
|
||||
add_action( 'elementor/init', [ $this, 'init' ], 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register default apps.
|
||||
*
|
||||
* Registers the default apps.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function init() {
|
||||
if ( is_admin() ) {
|
||||
$this->admin_page = new Admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Elementor apps.
|
||||
*
|
||||
* Fires after Elementor registers the default apps.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*
|
||||
* @param self $this The apps manager instance.
|
||||
*/
|
||||
do_action( 'elementor/connect/apps/register', $this );
|
||||
|
||||
foreach ( $this->registered_apps as $slug => $class ) {
|
||||
$this->apps[ $slug ] = new $class();
|
||||
}
|
||||
|
||||
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
|
||||
}
|
||||
|
||||
public function localize_settings( $settings ) {
|
||||
return array_replace_recursive( $settings, [
|
||||
'i18n' => [
|
||||
'connect_error' => __( 'Unable to connect', 'elementor' ),
|
||||
'connected_successfully' => __( 'Connected successfully', 'elementor' ),
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register app.
|
||||
*
|
||||
* Registers an app.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $slug App slug.
|
||||
* @param string $class App full class name.
|
||||
*
|
||||
* @return self The updated apps manager instance.
|
||||
*/
|
||||
public function register_app( $slug, $class ) {
|
||||
$this->registered_apps[ $slug ] = $class;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app instance.
|
||||
*
|
||||
* Retrieve the app instance.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param $slug
|
||||
*
|
||||
* @return Base_App|null
|
||||
*/
|
||||
public function get_app( $slug ) {
|
||||
if ( isset( $this->apps[ $slug ] ) ) {
|
||||
return $this->apps[ $slug ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
* @return Base_App[]
|
||||
*/
|
||||
public function get_apps() {
|
||||
return $this->apps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function register_category( $slug, $args ) {
|
||||
$this->categories[ $slug ] = $args;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_categories() {
|
||||
return $this->categories;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Common\Modules\Finder;
|
||||
|
||||
use Elementor\Core\Base\Base_Object;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Base Category
|
||||
*
|
||||
* Base class for Elementor Finder categories.
|
||||
*/
|
||||
abstract class Base_Category extends Base_Object {
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @abstract
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_title();
|
||||
|
||||
/**
|
||||
* Get category items.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @abstract
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function get_category_items( array $options = [] );
|
||||
|
||||
/**
|
||||
* Is dynamic.
|
||||
*
|
||||
* Determine if the category is dynamic.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dynamic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get init settings.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_init_settings() {
|
||||
$settings = [
|
||||
'title' => $this->get_title(),
|
||||
'dynamic' => $this->is_dynamic(),
|
||||
];
|
||||
|
||||
if ( ! $settings['dynamic'] ) {
|
||||
$settings['items'] = $this->get_category_items();
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Common\Modules\Finder;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Categories_Manager {
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*
|
||||
* @var Base_Category[]
|
||||
*/
|
||||
private $categories;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $categories_list = [
|
||||
'edit',
|
||||
'general',
|
||||
'create',
|
||||
'site',
|
||||
'settings',
|
||||
'tools',
|
||||
];
|
||||
|
||||
/**
|
||||
* Add category.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
* @param string $category_name
|
||||
* @param Base_Category $category
|
||||
*/
|
||||
public function add_category( $category_name, Base_Category $category ) {
|
||||
$this->categories[ $category_name ] = $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
* @param string $category
|
||||
*
|
||||
* @return Base_Category|Base_Category[]|null
|
||||
*/
|
||||
public function get_categories( $category = '' ) {
|
||||
if ( ! $this->categories ) {
|
||||
$this->init_categories();
|
||||
}
|
||||
|
||||
if ( $category ) {
|
||||
if ( isset( $this->categories[ $category ] ) ) {
|
||||
return $this->categories[ $category ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init categories.
|
||||
*
|
||||
* Used to initialize finder default categories.
|
||||
* @since 2.3.0
|
||||
* @access private
|
||||
*/
|
||||
private function init_categories() {
|
||||
foreach ( $this->categories_list as $category_name ) {
|
||||
$class_name = __NAMESPACE__ . '\Categories\\' . $category_name;
|
||||
|
||||
$this->add_category( $category_name, new $class_name() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor Finder categories init.
|
||||
*
|
||||
* Fires after Elementor Finder initialize it's native categories.
|
||||
*
|
||||
* This hook should be used to add your own Finder categories.
|
||||
*
|
||||
* @since 2.3.0
|
||||
*
|
||||
* @param Categories_Manager $this.
|
||||
*/
|
||||
do_action( 'elementor/finder/categories/init', $this );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Finder\Categories;
|
||||
|
||||
use Elementor\Core\Common\Modules\Finder\Base_Category;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Category
|
||||
*
|
||||
* Provides items related to creation of new posts/pages/templates etc.
|
||||
*/
|
||||
class Create extends Base_Category {
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Create', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category items.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_category_items( array $options = [] ) {
|
||||
$elementor_supported_post_types = get_post_types_by_support( 'elementor' );
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ( $elementor_supported_post_types as $post_type ) {
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
|
||||
// If there is an old post type from inactive plugins
|
||||
if ( ! $post_type_object ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( Source_Local::CPT === $post_type ) {
|
||||
$url = admin_url( Source_Local::ADMIN_MENU_SLUG . '#add_new' );
|
||||
} else {
|
||||
$url = Utils::get_create_new_post_url( $post_type );
|
||||
}
|
||||
|
||||
$items[ $post_type ] = [
|
||||
/* translators: %s the title of the post type */
|
||||
'title' => sprintf( __( 'Add New %s', 'elementor' ), $post_type_object->labels->singular_name ),
|
||||
'icon' => 'plus-circle-o',
|
||||
'url' => $url,
|
||||
'keywords' => [ 'post', 'page', 'template', 'new', 'create' ],
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Common\Modules\Finder\Categories;
|
||||
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Core\Common\Modules\Finder\Base_Category;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Category
|
||||
*
|
||||
* Provides items related to editing of posts/pages/templates etc.
|
||||
*/
|
||||
class Edit extends Base_Category {
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Edit', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is dynamic.
|
||||
*
|
||||
* Determine if the category is dynamic.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_dynamic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category items.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_category_items( array $options = [] ) {
|
||||
$post_types = get_post_types( [
|
||||
'exclude_from_search' => false,
|
||||
] );
|
||||
|
||||
$post_types[] = Source_Local::CPT;
|
||||
|
||||
$document_types = Plugin::$instance->documents->get_document_types( [
|
||||
'is_editable' => true,
|
||||
'show_in_finder' => true,
|
||||
] );
|
||||
|
||||
// TODO: Remove on 2.4.0.
|
||||
unset( $document_types['widget'] );
|
||||
|
||||
$recently_edited_query_args = [
|
||||
'post_type' => $post_types,
|
||||
'post_status' => [ 'publish', 'draft', 'private', 'pending', 'future' ],
|
||||
'posts_per_page' => '10',
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => '_elementor_edit_mode',
|
||||
'value' => 'builder',
|
||||
],
|
||||
[
|
||||
'relation' => 'or',
|
||||
[
|
||||
'key' => Document::TYPE_META_KEY,
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
[
|
||||
'key' => Document::TYPE_META_KEY,
|
||||
'value' => array_keys( $document_types ),
|
||||
],
|
||||
],
|
||||
],
|
||||
'orderby' => 'modified',
|
||||
's' => $options['filter'],
|
||||
];
|
||||
|
||||
$recently_edited_query = new \WP_Query( $recently_edited_query_args );
|
||||
|
||||
$items = [];
|
||||
|
||||
/** @var \WP_Post $post */
|
||||
foreach ( $recently_edited_query->posts as $post ) {
|
||||
$document = Plugin::$instance->documents->get( $post->ID );
|
||||
|
||||
if ( ! $document ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_template = Source_Local::CPT === $post->post_type;
|
||||
|
||||
$description = $document->get_title();
|
||||
|
||||
$icon = 'document-file';
|
||||
|
||||
if ( $is_template ) {
|
||||
$description = __( 'Template', 'elementor' ) . ' / ' . $description;
|
||||
|
||||
$icon = 'post-title';
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'icon' => $icon,
|
||||
'title' => esc_html( $post->post_title ),
|
||||
'description' => $description,
|
||||
'url' => $document->get_edit_url(),
|
||||
'actions' => [
|
||||
[
|
||||
'name' => 'view',
|
||||
'url' => $document->get_permalink(),
|
||||
'icon' => 'preview-medium',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Finder\Categories;
|
||||
|
||||
use Elementor\Core\Common\Modules\Finder\Base_Category;
|
||||
use Elementor\Core\RoleManager\Role_Manager;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* General Category
|
||||
*
|
||||
* Provides general items related to Elementor Admin.
|
||||
*/
|
||||
class General extends Base_Category {
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'General', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category items.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_category_items( array $options = [] ) {
|
||||
return [
|
||||
'saved-templates' => [
|
||||
'title' => _x( 'Saved Templates', 'Template Library', 'elementor' ),
|
||||
'icon' => 'library-save',
|
||||
'url' => Source_Local::get_admin_url(),
|
||||
'keywords' => [ 'template', 'section', 'page', 'library' ],
|
||||
],
|
||||
'system-info' => [
|
||||
'title' => __( 'System Info', 'elementor' ),
|
||||
'icon' => 'info-circle-o',
|
||||
'url' => admin_url( 'admin.php?page=elementor-system-info' ),
|
||||
'keywords' => [ 'system', 'info', 'environment', 'elementor' ],
|
||||
],
|
||||
'role-manager' => [
|
||||
'title' => __( 'Role Manager', 'elementor' ),
|
||||
'icon' => 'person',
|
||||
'url' => Role_Manager::get_url(),
|
||||
'keywords' => [ 'role', 'manager', 'user', 'elementor' ],
|
||||
],
|
||||
'knowledge-base' => [
|
||||
'title' => __( 'Knowledge Base', 'elementor' ),
|
||||
'url' => admin_url( 'admin.php?page=go_knowledge_base_site' ),
|
||||
'keywords' => [ 'help', 'knowledge', 'docs', 'elementor' ],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Common\Modules\Finder\Categories;
|
||||
|
||||
use Elementor\Core\Common\Modules\Finder\Base_Category;
|
||||
use Elementor\Settings as ElementorSettings;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings Category
|
||||
*
|
||||
* Provides items related to Elementor's settings.
|
||||
*/
|
||||
class Settings extends Base_Category {
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Settings', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category items.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_category_items( array $options = [] ) {
|
||||
$settings_url = ElementorSettings::get_url();
|
||||
|
||||
return [
|
||||
'general-settings' => [
|
||||
'title' => __( 'General Settings', 'elementor' ),
|
||||
'url' => $settings_url,
|
||||
'keywords' => [ 'general', 'settings', 'elementor' ],
|
||||
],
|
||||
'advanced' => [
|
||||
'title' => __( 'Advanced', 'elementor' ),
|
||||
'url' => $settings_url . '#tab-advanced',
|
||||
'keywords' => [ 'advanced', 'settings', 'elementor' ],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Common\Modules\Finder\Categories;
|
||||
|
||||
use Elementor\Core\Common\Modules\Finder\Base_Category;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Site Category
|
||||
*
|
||||
* Provides general site items.
|
||||
*/
|
||||
class Site extends Base_Category {
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Site', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category items.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_category_items( array $options = [] ) {
|
||||
return [
|
||||
'homepage' => [
|
||||
'title' => __( 'Homepage', 'elementor' ),
|
||||
'url' => home_url(),
|
||||
'icon' => 'home-heart',
|
||||
'keywords' => [ 'home', 'page' ],
|
||||
],
|
||||
'wordpress-dashboard' => [
|
||||
'title' => __( 'Dashboard', 'elementor' ),
|
||||
'icon' => 'dashboard',
|
||||
'url' => admin_url(),
|
||||
'keywords' => [ 'dashboard', 'wordpress' ],
|
||||
],
|
||||
'wordpress-menus' => [
|
||||
'title' => __( 'Menus', 'elementor' ),
|
||||
'icon' => 'wordpress',
|
||||
'url' => admin_url( 'nav-menus.php' ),
|
||||
'keywords' => [ 'menu', 'wordpress' ],
|
||||
],
|
||||
'wordpress-themes' => [
|
||||
'title' => __( 'Themes', 'elementor' ),
|
||||
'icon' => 'wordpress',
|
||||
'url' => admin_url( 'themes.php' ),
|
||||
'keywords' => [ 'themes', 'wordpress' ],
|
||||
],
|
||||
'wordpress-customizer' => [
|
||||
'title' => __( 'Customizer', 'elementor' ),
|
||||
'icon' => 'wordpress',
|
||||
'url' => admin_url( 'customize.php' ),
|
||||
'keywords' => [ 'customizer', 'wordpress' ],
|
||||
],
|
||||
'wordpress-plugins' => [
|
||||
'title' => __( 'Plugins', 'elementor' ),
|
||||
'icon' => 'wordpress',
|
||||
'url' => admin_url( 'plugins.php' ),
|
||||
'keywords' => [ 'plugins', 'wordpress' ],
|
||||
],
|
||||
'wordpress-users' => [
|
||||
'title' => __( 'Users', 'elementor' ),
|
||||
'icon' => 'wordpress',
|
||||
'url' => admin_url( 'users.php' ),
|
||||
'keywords' => [ 'users', 'profile', 'wordpress' ],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Common\Modules\Finder\Categories;
|
||||
|
||||
use Elementor\Core\Common\Modules\Finder\Base_Category;
|
||||
use Elementor\Tools as ElementorTools;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Tools Category
|
||||
*
|
||||
* Provides items related to Elementor's tools.
|
||||
*/
|
||||
class Tools extends Base_Category {
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Tools', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category items.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_category_items( array $options = [] ) {
|
||||
$tools_url = ElementorTools::get_url();
|
||||
|
||||
return [
|
||||
'tools' => [
|
||||
'title' => __( 'Tools', 'elementor' ),
|
||||
'icon' => 'tools',
|
||||
'url' => $tools_url,
|
||||
'keywords' => [ 'tools', 'regenerate css', 'safe mode', 'debug bar', 'sync library', 'elementor' ],
|
||||
],
|
||||
'replace-url' => [
|
||||
'title' => __( 'Replace URL', 'elementor' ),
|
||||
'icon' => 'tools',
|
||||
'url' => $tools_url . '#tab-replace_url',
|
||||
'keywords' => [ 'tools', 'replace url', 'domain', 'elementor' ],
|
||||
],
|
||||
'version-control' => [
|
||||
'title' => __( 'Version Control', 'elementor' ),
|
||||
'icon' => 'time-line',
|
||||
'url' => $tools_url . '#tab-versions',
|
||||
'keywords' => [ 'tools', 'version', 'control', 'rollback', 'beta', 'elementor' ],
|
||||
],
|
||||
'maintenance-mode' => [
|
||||
'title' => __( 'Maintenance Mode', 'elementor' ),
|
||||
'icon' => 'tools',
|
||||
'url' => $tools_url . '#tab-maintenance_mode',
|
||||
'keywords' => [ 'tools', 'maintenance', 'coming soon', 'elementor' ],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Common\Modules\Finder;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Finder Module
|
||||
*
|
||||
* Responsible for initializing Elementor Finder functionality
|
||||
*/
|
||||
class Module extends BaseModule {
|
||||
|
||||
/**
|
||||
* Categories manager.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var Categories_Manager
|
||||
*/
|
||||
private $categories_manager;
|
||||
|
||||
/**
|
||||
* Module constructor.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->categories_manager = new Categories_Manager();
|
||||
|
||||
$this->add_template();
|
||||
|
||||
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'finder';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add template.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function add_template() {
|
||||
Plugin::$instance->common->add_template( __DIR__ . '/template.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register ajax actions.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param Ajax $ajax
|
||||
*/
|
||||
public function register_ajax_actions( Ajax $ajax ) {
|
||||
$ajax->register_ajax_action( 'finder_get_category_items', [ $this, 'ajax_get_category_items' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax get category items.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function ajax_get_category_items( array $data ) {
|
||||
$category = $this->categories_manager->get_categories( $data['category'] );
|
||||
|
||||
return $category->get_category_items( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get init settings.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_init_settings() {
|
||||
$categories = $this->categories_manager->get_categories();
|
||||
|
||||
$categories_data = [];
|
||||
|
||||
foreach ( $categories as $category_name => $category ) {
|
||||
$categories_data[ $category_name ] = array_merge( $category->get_settings(), [ 'name' => $category_name ] );
|
||||
}
|
||||
|
||||
$categories_data = apply_filters( 'elementor/finder/categories', $categories_data );
|
||||
|
||||
return [
|
||||
'data' => $categories_data,
|
||||
'i18n' => [
|
||||
'finder' => __( 'Finder', 'elementor' ),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\Finder;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
?>
|
||||
<script type="text/template" id="tmpl-elementor-finder">
|
||||
<div id="elementor-finder__search">
|
||||
<i class="eicon-search"></i>
|
||||
<input id="elementor-finder__search__input" placeholder="<?php echo __( 'Type to find anything in Elementor', 'elementor' ); ?>">
|
||||
</div>
|
||||
<div id="elementor-finder__content"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="tmpl-elementor-finder-results-container">
|
||||
<div id="elementor-finder__no-results"><?php echo __( 'No Results Found', 'elementor' ); ?></div>
|
||||
<div id="elementor-finder__results"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="tmpl-elementor-finder__results__category">
|
||||
<div class="elementor-finder__results__category__title">{{{ title }}}</div>
|
||||
<div class="elementor-finder__results__category__items"></div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="tmpl-elementor-finder__results__item">
|
||||
<a href="{{ url }}" class="elementor-finder__results__item__link">
|
||||
<div class="elementor-finder__results__item__icon">
|
||||
<i class="eicon-{{{ icon }}}"></i>
|
||||
</div>
|
||||
<div class="elementor-finder__results__item__title">{{{ title }}}</div>
|
||||
<# if ( description ) { #>
|
||||
<div class="elementor-finder__results__item__description">- {{{ description }}}</div>
|
||||
<# } #>
|
||||
</a>
|
||||
<# if ( actions.length ) { #>
|
||||
<div class="elementor-finder__results__item__actions">
|
||||
<# jQuery.each( actions, function() { #>
|
||||
<a class="elementor-finder__results__item__action elementor-finder__results__item__action--{{ this.name }}" href="{{ this.url }}" target="_blank">
|
||||
<i class="eicon-{{{ this.icon }}}"></i>
|
||||
</a>
|
||||
<# } ); #>
|
||||
</div>
|
||||
<# } #>
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Debug\Classes;
|
||||
|
||||
use Elementor\Modules\SafeMode\Module as Safe_Mode;
|
||||
|
||||
class Htaccess extends Inspection_Base {
|
||||
|
||||
private $message = '';
|
||||
|
||||
public function __construct() {
|
||||
$this->message = __( 'Your site\'s .htaccess file appears to be missing.', 'elementor' );
|
||||
}
|
||||
|
||||
public function run() {
|
||||
$safe_mode_enabled = get_option( Safe_Mode::OPTION_ENABLED, '' );
|
||||
if ( empty( $safe_mode_enabled ) || is_multisite() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$permalink_structure = get_option( 'permalink_structure' );
|
||||
if ( empty( $permalink_structure ) || empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$server = strtoupper( $_SERVER['SERVER_SOFTWARE'] );
|
||||
|
||||
if ( strstr( $server, 'APACHE' ) ) {
|
||||
$htaccess_file = get_home_path() . '.htaccess';
|
||||
$this->message .= ' ' . sprintf( __( 'File Path: %s', 'elementor' ), $htaccess_file ) . ' ';
|
||||
return file_exists( $htaccess_file );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return 'apache-htaccess';
|
||||
}
|
||||
|
||||
public function get_message() {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function get_help_doc_url() {
|
||||
return 'https://go.elementor.com/preview-not-loaded/#htaccess';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Debug\Classes;
|
||||
|
||||
abstract class Inspection_Base {
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function run();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_name();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_message();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_header_message() {
|
||||
return __( 'The preview could not be loaded', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_help_doc_url();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Debug\Classes;
|
||||
|
||||
use Elementor\Modules\SafeMode\Module as Safe_Mode;
|
||||
|
||||
class Theme_Missing extends Inspection_Base {
|
||||
|
||||
public function run() {
|
||||
$safe_mode_enabled = get_option( Safe_Mode::OPTION_ENABLED, '' );
|
||||
if ( ! empty( $safe_mode_enabled ) ) {
|
||||
return true;
|
||||
}
|
||||
$theme = wp_get_theme();
|
||||
return $theme->exists();
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return 'theme-missing';
|
||||
}
|
||||
|
||||
public function get_message() {
|
||||
return __( 'Some of your theme files are missing.', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_help_doc_url() {
|
||||
return 'https://go.elementor.com/preview-not-loaded/#theme-files';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Debug;
|
||||
|
||||
use Elementor\Settings;
|
||||
use Elementor\Tools;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Inspector {
|
||||
|
||||
protected $is_enabled = false;
|
||||
|
||||
protected $log = [];
|
||||
|
||||
/**
|
||||
* @since 2.1.2
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$is_debug = ( defined( 'WP_DEBUG' ) && WP_DEBUG );
|
||||
$option = get_option( 'elementor_enable_inspector', null );
|
||||
|
||||
$this->is_enabled = is_null( $option ) ? $is_debug : 'enable' === $option;
|
||||
|
||||
if ( $this->is_enabled ) {
|
||||
add_action( 'admin_bar_menu', [ $this, 'add_menu_in_admin_bar' ], 201 );
|
||||
}
|
||||
|
||||
add_action( 'elementor/admin/after_create_settings/' . Tools::PAGE_ID, [ $this, 'register_admin_tools_fields' ], 50 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.3
|
||||
* @access public
|
||||
*/
|
||||
public function is_enabled() {
|
||||
return $this->is_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.3
|
||||
* @access public
|
||||
*/
|
||||
public function register_admin_tools_fields( Tools $tools ) {
|
||||
$tools->add_fields( Settings::TAB_GENERAL, 'tools', [
|
||||
'enable_inspector' => [
|
||||
'label' => __( 'Debug Bar', 'elementor' ),
|
||||
'field_args' => [
|
||||
'type' => 'select',
|
||||
'std' => $this->is_enabled ? 'enable' : '',
|
||||
'options' => [
|
||||
'' => __( 'Disable', 'elementor' ),
|
||||
'enable' => __( 'Enable', 'elementor' ),
|
||||
],
|
||||
'desc' => __( 'Debug Bar adds an admin bar menu that lists all the templates that are used on a page that is being displayed.', 'elementor' ),
|
||||
],
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.2
|
||||
* @access public
|
||||
*/
|
||||
public function parse_template_path( $template ) {
|
||||
// `untrailingslashit` for windows path style.
|
||||
if ( 0 === strpos( $template, untrailingslashit( ELEMENTOR_PATH ) ) ) {
|
||||
return 'Elementor - ' . basename( $template );
|
||||
}
|
||||
|
||||
if ( 0 === strpos( $template, get_stylesheet_directory() ) ) {
|
||||
return wp_get_theme()->get( 'Name' ) . ' - ' . basename( $template );
|
||||
}
|
||||
|
||||
$plugins_dir = dirname( ELEMENTOR_PATH );
|
||||
if ( 0 === strpos( $template, $plugins_dir ) ) {
|
||||
return ltrim( str_replace( $plugins_dir, '', $template ), '/\\' );
|
||||
}
|
||||
|
||||
return str_replace( WP_CONTENT_DIR, '', $template );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.2
|
||||
* @access public
|
||||
*/
|
||||
public function add_log( $module, $title, $url = '' ) {
|
||||
if ( ! $this->is_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $this->log[ $module ] ) ) {
|
||||
$this->log[ $module ] = [];
|
||||
}
|
||||
|
||||
$this->log[ $module ][] = [
|
||||
'title' => $title,
|
||||
'url' => $url,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.2
|
||||
* @access public
|
||||
*/
|
||||
public function add_menu_in_admin_bar( \WP_Admin_Bar $wp_admin_bar ) {
|
||||
if ( empty( $this->log ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wp_admin_bar->add_node( [
|
||||
'id' => 'elementor_inspector',
|
||||
'title' => __( 'Elementor Debugger', 'elementor' ),
|
||||
] );
|
||||
|
||||
foreach ( $this->log as $module => $log ) {
|
||||
$module_id = sanitize_key( $module );
|
||||
|
||||
$wp_admin_bar->add_menu( [
|
||||
'id' => 'elementor_inspector_' . $module_id,
|
||||
'parent' => 'elementor_inspector',
|
||||
'title' => $module,
|
||||
] );
|
||||
|
||||
foreach ( $log as $index => $row ) {
|
||||
$url = $row['url'];
|
||||
|
||||
unset( $row['url'] );
|
||||
|
||||
$wp_admin_bar->add_menu( [
|
||||
'id' => 'elementor_inspector_log_' . $module_id . '_' . $index,
|
||||
'parent' => 'elementor_inspector_' . $module_id,
|
||||
'href' => $url,
|
||||
'title' => implode( ' > ', $row ),
|
||||
'meta' => [
|
||||
'target' => '_blank',
|
||||
],
|
||||
] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Debug;
|
||||
|
||||
use Elementor\Core\Debug\Classes\Inspection_Base;
|
||||
use Elementor\Core\Debug\Classes\Theme_Missing;
|
||||
use Elementor\Core\Debug\Classes\Htaccess;
|
||||
|
||||
class Loading_Inspection_Manager {
|
||||
|
||||
public static $_instance = null;
|
||||
|
||||
public static function instance() {
|
||||
if ( null === self::$_instance ) {
|
||||
self::$_instance = new Loading_Inspection_Manager();
|
||||
}
|
||||
return self::$_instance;
|
||||
}
|
||||
|
||||
/** @var Inspection_Base[] */
|
||||
private $inspections = [];
|
||||
|
||||
public function register_inspections() {
|
||||
$this->inspections['theme-missing'] = new Theme_Missing();
|
||||
$this->inspections['htaccess'] = new Htaccess();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Inspection_Base $inspection
|
||||
*/
|
||||
public function register_inspection( $inspection ) {
|
||||
$this->inspections[ $inspection->get_name() ] = $inspection;
|
||||
}
|
||||
|
||||
public function run_inspections() {
|
||||
$debug_data = [
|
||||
'message' => __( 'We\'re sorry, but something went wrong. Click on \'Learn more\' and follow each of the steps to quickly solve it.', 'elementor' ),
|
||||
'header' => __( 'The preview could not be loaded', 'elementor' ),
|
||||
'doc_url' => 'https://go.elementor.com/preview-not-loaded/',
|
||||
];
|
||||
foreach ( $this->inspections as $inspection ) {
|
||||
if ( ! $inspection->run() ) {
|
||||
$debug_data = [
|
||||
'message' => $inspection->get_message(),
|
||||
'header' => $inspection->get_header_message(),
|
||||
'doc_url' => $inspection->get_help_doc_url(),
|
||||
'error' => true,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $debug_data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
namespace Elementor\Core\DocumentTypes;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Group_Control_Background;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class PageBase extends Document {
|
||||
|
||||
/**
|
||||
* @since 2.0.8
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function get_properties() {
|
||||
$properties = parent::get_properties();
|
||||
|
||||
$properties['admin_tab_group'] = '';
|
||||
$properties['support_wp_page_templates'] = true;
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.2
|
||||
* @access protected
|
||||
* @static
|
||||
*/
|
||||
protected static function get_editor_panel_categories() {
|
||||
return Utils::array_inject(
|
||||
parent::get_editor_panel_categories(),
|
||||
'theme-elements',
|
||||
[
|
||||
'theme-elements-single' => [
|
||||
'title' => __( 'Single', 'elementor' ),
|
||||
'active' => false,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_css_wrapper_selector() {
|
||||
return 'body.elementor-page-' . $this->get_main_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function _register_controls() {
|
||||
parent::_register_controls();
|
||||
|
||||
self::register_hide_title_control( $this );
|
||||
|
||||
self::register_post_fields_control( $this );
|
||||
|
||||
self::register_style_controls( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @static
|
||||
* @param Document $document
|
||||
*/
|
||||
public static function register_hide_title_control( $document ) {
|
||||
$document->start_injection( [
|
||||
'of' => 'post_status',
|
||||
'fallback' => [
|
||||
'of' => 'post_title',
|
||||
],
|
||||
] );
|
||||
|
||||
$document->add_control(
|
||||
'hide_title',
|
||||
[
|
||||
'label' => __( 'Hide Title', 'elementor' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'description' => __( 'Not working? You can set a different selector for the title in Site Settings > Layout', 'elementor' ),
|
||||
'selectors' => [
|
||||
':root' => '--page-title-display: none',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$document->end_injection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @static
|
||||
* @param Document $document
|
||||
*/
|
||||
public static function register_style_controls( $document ) {
|
||||
$document->start_controls_section(
|
||||
'section_page_style',
|
||||
[
|
||||
'label' => __( 'Body Style', 'elementor' ),
|
||||
'tab' => Controls_Manager::TAB_STYLE,
|
||||
]
|
||||
);
|
||||
|
||||
$document->add_group_control(
|
||||
Group_Control_Background::get_type(),
|
||||
[
|
||||
'name' => 'background',
|
||||
'fields_options' => [
|
||||
'image' => [
|
||||
// Currently isn't supported.
|
||||
'dynamic' => [
|
||||
'active' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$document->add_responsive_control(
|
||||
'padding',
|
||||
[
|
||||
'label' => __( 'Padding', 'elementor' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', 'em', '%' ],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}}',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$document->end_controls_section();
|
||||
|
||||
Plugin::$instance->controls_manager->add_custom_css_controls( $document );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @static
|
||||
* @param Document $document
|
||||
*/
|
||||
public static function register_post_fields_control( $document ) {
|
||||
$document->start_injection( [
|
||||
'of' => 'post_status',
|
||||
'fallback' => [
|
||||
'of' => 'post_title',
|
||||
],
|
||||
] );
|
||||
|
||||
if ( post_type_supports( $document->post->post_type, 'excerpt' ) ) {
|
||||
$document->add_control(
|
||||
'post_excerpt',
|
||||
[
|
||||
'label' => __( 'Excerpt', 'elementor' ),
|
||||
'type' => Controls_Manager::TEXTAREA,
|
||||
'default' => $document->post->post_excerpt,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ( current_theme_supports( 'post-thumbnails' ) && post_type_supports( $document->post->post_type, 'thumbnail' ) ) {
|
||||
$document->add_control(
|
||||
'post_featured_image',
|
||||
[
|
||||
'label' => __( 'Featured Image', 'elementor' ),
|
||||
'type' => Controls_Manager::MEDIA,
|
||||
'default' => [
|
||||
'id' => get_post_thumbnail_id(),
|
||||
'url' => (string) get_the_post_thumbnail_url( $document->post->ID ),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$document->end_injection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct( array $data = [] ) {
|
||||
if ( $data ) {
|
||||
$template = get_post_meta( $data['post_id'], '_wp_page_template', true );
|
||||
|
||||
if ( empty( $template ) ) {
|
||||
$template = 'default';
|
||||
}
|
||||
|
||||
$data['settings']['template'] = $template;
|
||||
}
|
||||
|
||||
parent::__construct( $data );
|
||||
}
|
||||
|
||||
protected function get_remote_library_config() {
|
||||
$config = parent::get_remote_library_config();
|
||||
|
||||
$config['category'] = '';
|
||||
$config['type'] = 'page';
|
||||
$config['default_route'] = 'templates/pages';
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Elementor\Core\DocumentTypes;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Page extends PageBase {
|
||||
public static function get_properties() {
|
||||
$properties = parent::get_properties();
|
||||
|
||||
$properties['cpt'] = [ 'page' ];
|
||||
$properties['support_kit'] = true;
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'wp-page';
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function get_title() {
|
||||
return __( 'Page', 'elementor' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Elementor\Core\DocumentTypes;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Post extends PageBase {
|
||||
|
||||
public static function get_properties() {
|
||||
$properties = parent::get_properties();
|
||||
|
||||
$properties['support_kit'] = true;
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'wp-post';
|
||||
}
|
||||
|
||||
/**
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function get_title() {
|
||||
return __( 'Post', 'elementor' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
<?php
|
||||
namespace Elementor\Core;
|
||||
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Core\DocumentTypes\Page;
|
||||
use Elementor\Core\DocumentTypes\Post;
|
||||
use Elementor\DB;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor documents manager.
|
||||
*
|
||||
* Elementor documents manager handler class is responsible for registering and
|
||||
* managing Elementor documents.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
class Documents_Manager {
|
||||
|
||||
/**
|
||||
* Registered types.
|
||||
*
|
||||
* Holds the list of all the registered types.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*
|
||||
* @var Document[]
|
||||
*/
|
||||
protected $types = [];
|
||||
|
||||
/**
|
||||
* Registered documents.
|
||||
*
|
||||
* Holds the list of all the registered documents.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*
|
||||
* @var Document[]
|
||||
*/
|
||||
protected $documents = [];
|
||||
|
||||
/**
|
||||
* Current document.
|
||||
*
|
||||
* Holds the current document.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*
|
||||
* @var Document
|
||||
*/
|
||||
protected $current_doc;
|
||||
|
||||
/**
|
||||
* Switched data.
|
||||
*
|
||||
* Holds the current document when changing to the requested post.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $switched_data = [];
|
||||
|
||||
protected $cpt = [];
|
||||
|
||||
/**
|
||||
* Documents manager constructor.
|
||||
*
|
||||
* Initializing the Elementor documents manager.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
add_action( 'elementor/documents/register', [ $this, 'register_default_types' ], 0 );
|
||||
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
|
||||
add_filter( 'post_row_actions', [ $this, 'filter_post_row_actions' ], 11, 2 );
|
||||
add_filter( 'page_row_actions', [ $this, 'filter_post_row_actions' ], 11, 2 );
|
||||
add_filter( 'user_has_cap', [ $this, 'remove_user_edit_cap' ], 10, 3 );
|
||||
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register ajax actions.
|
||||
*
|
||||
* Process ajax action handles when saving data and discarding changes.
|
||||
*
|
||||
* Fired by `elementor/ajax/register_actions` action.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param Ajax $ajax_manager An instance of the ajax manager.
|
||||
*/
|
||||
public function register_ajax_actions( $ajax_manager ) {
|
||||
$ajax_manager->register_ajax_action( 'save_builder', [ $this, 'ajax_save' ] );
|
||||
$ajax_manager->register_ajax_action( 'discard_changes', [ $this, 'ajax_discard_changes' ] );
|
||||
$ajax_manager->register_ajax_action( 'get_document_config', [ $this, 'ajax_get_document_config' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register default types.
|
||||
*
|
||||
* Registers the default document types.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function register_default_types() {
|
||||
$default_types = [
|
||||
'post' => Post::get_class_full_name(), // BC.
|
||||
'wp-post' => Post::get_class_full_name(),
|
||||
'wp-page' => Page::get_class_full_name(),
|
||||
];
|
||||
|
||||
foreach ( $default_types as $type => $class ) {
|
||||
$this->register_document_type( $type, $class );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register document type.
|
||||
*
|
||||
* Registers a single document.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Document type name.
|
||||
* @param string $class The name of the class that registers the document type.
|
||||
* Full name with the namespace.
|
||||
*
|
||||
* @return Documents_Manager The updated document manager instance.
|
||||
*/
|
||||
public function register_document_type( $type, $class ) {
|
||||
$this->types[ $type ] = $class;
|
||||
|
||||
$cpt = $class::get_property( 'cpt' );
|
||||
|
||||
if ( $cpt ) {
|
||||
foreach ( $cpt as $post_type ) {
|
||||
$this->cpt[ $post_type ] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $class::get_property( 'register_type' ) ) {
|
||||
Source_Local::add_template_type( $type );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document.
|
||||
*
|
||||
* Retrieve the document data based on a post ID.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param bool $from_cache Optional. Whether to retrieve cached data. Default is true.
|
||||
*
|
||||
* @return false|Document Document data or false if post ID was not entered.
|
||||
*/
|
||||
public function get( $post_id, $from_cache = true ) {
|
||||
$this->register_types();
|
||||
|
||||
$post_id = absint( $post_id );
|
||||
|
||||
if ( ! $post_id || ! get_post( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$post_id = apply_filters( 'elementor/documents/get/post_id', $post_id );
|
||||
|
||||
if ( ! $from_cache || ! isset( $this->documents[ $post_id ] ) ) {
|
||||
|
||||
if ( wp_is_post_autosave( $post_id ) ) {
|
||||
$post_type = get_post_type( wp_get_post_parent_id( $post_id ) );
|
||||
} else {
|
||||
$post_type = get_post_type( $post_id );
|
||||
}
|
||||
|
||||
$doc_type = 'post';
|
||||
|
||||
if ( isset( $this->cpt[ $post_type ] ) ) {
|
||||
$doc_type = $this->cpt[ $post_type ];
|
||||
}
|
||||
|
||||
$meta_type = get_post_meta( $post_id, Document::TYPE_META_KEY, true );
|
||||
|
||||
if ( $meta_type && isset( $this->types[ $meta_type ] ) ) {
|
||||
$doc_type = $meta_type;
|
||||
}
|
||||
|
||||
$doc_type_class = $this->get_document_type( $doc_type );
|
||||
$this->documents[ $post_id ] = new $doc_type_class( [
|
||||
'post_id' => $post_id,
|
||||
] );
|
||||
}
|
||||
|
||||
return $this->documents[ $post_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document or autosave.
|
||||
*
|
||||
* Retrieve either the document or the autosave.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param int $id Optional. Post ID. Default is `0`.
|
||||
* @param int $user_id Optional. User ID. Default is `0`.
|
||||
*
|
||||
* @return false|Document The document if it exist, False otherwise.
|
||||
*/
|
||||
public function get_doc_or_auto_save( $id, $user_id = 0 ) {
|
||||
$document = $this->get( $id );
|
||||
if ( $document && $document->get_autosave_id( $user_id ) ) {
|
||||
$document = $document->get_autosave( $user_id );
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document for frontend.
|
||||
*
|
||||
* Retrieve the document for frontend use.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Optional. Post ID. Default is `0`.
|
||||
*
|
||||
* @return false|Document The document if it exist, False otherwise.
|
||||
*/
|
||||
public function get_doc_for_frontend( $post_id ) {
|
||||
if ( is_preview() || Plugin::$instance->preview->is_preview_mode() ) {
|
||||
$document = $this->get_doc_or_auto_save( $post_id, get_current_user_id() );
|
||||
} else {
|
||||
$document = $this->get( $post_id );
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document type.
|
||||
*
|
||||
* Retrieve the type of any given document.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @param string $fallback
|
||||
*
|
||||
* @return Document|bool The type of the document.
|
||||
*/
|
||||
public function get_document_type( $type, $fallback = 'post' ) {
|
||||
$types = $this->get_document_types();
|
||||
|
||||
if ( isset( $types[ $type ] ) ) {
|
||||
return $types[ $type ];
|
||||
}
|
||||
|
||||
if ( isset( $types[ $fallback ] ) ) {
|
||||
return $types[ $fallback ];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document types.
|
||||
*
|
||||
* Retrieve the all the registered document types.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $args Optional. An array of key => value arguments to match against
|
||||
* the properties. Default is empty array.
|
||||
* @param string $operator Optional. The logical operation to perform. 'or' means only one
|
||||
* element from the array needs to match; 'and' means all elements
|
||||
* must match; 'not' means no elements may match. Default 'and'.
|
||||
*
|
||||
* @return Document[] All the registered document types.
|
||||
*/
|
||||
public function get_document_types( $args = [], $operator = 'and' ) {
|
||||
$this->register_types();
|
||||
|
||||
if ( ! empty( $args ) ) {
|
||||
$types_properties = $this->get_types_properties();
|
||||
|
||||
$filtered = wp_filter_object_list( $types_properties, $args, $operator );
|
||||
|
||||
return array_intersect_key( $this->types, $filtered );
|
||||
}
|
||||
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document types with their properties.
|
||||
*
|
||||
* @return array A list of properties arrays indexed by the type.
|
||||
*/
|
||||
public function get_types_properties() {
|
||||
$types_properties = [];
|
||||
|
||||
foreach ( $this->get_document_types() as $type => $class ) {
|
||||
$types_properties[ $type ] = $class::get_properties();
|
||||
}
|
||||
return $types_properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a document.
|
||||
*
|
||||
* Create a new document using any given parameters.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $type Document type.
|
||||
* @param array $post_data An array containing the post data.
|
||||
* @param array $meta_data An array containing the post meta data.
|
||||
*
|
||||
* @return Document The type of the document.
|
||||
*/
|
||||
public function create( $type, $post_data = [], $meta_data = [] ) {
|
||||
$class = $this->get_document_type( $type, false );
|
||||
|
||||
if ( ! $class ) {
|
||||
return new \WP_Error( 500, sprintf( 'Type %s does not exist.', $type ) );
|
||||
}
|
||||
|
||||
if ( empty( $post_data['post_title'] ) ) {
|
||||
$post_data['post_title'] = __( 'Elementor', 'elementor' );
|
||||
if ( 'post' !== $type ) {
|
||||
$post_data['post_title'] = sprintf(
|
||||
/* translators: %s: Document title */
|
||||
__( 'Elementor %s', 'elementor' ),
|
||||
call_user_func( [ $class, 'get_title' ] )
|
||||
);
|
||||
}
|
||||
$update_title = true;
|
||||
}
|
||||
|
||||
$meta_data['_elementor_edit_mode'] = 'builder';
|
||||
|
||||
// Save the type as-is for plugins that hooked at `wp_insert_post`.
|
||||
$meta_data[ Document::TYPE_META_KEY ] = $type;
|
||||
|
||||
$post_data['meta_input'] = $meta_data;
|
||||
|
||||
$post_id = wp_insert_post( $post_data );
|
||||
|
||||
if ( ! empty( $update_title ) ) {
|
||||
$post_data['ID'] = $post_id;
|
||||
$post_data['post_title'] .= ' #' . $post_id;
|
||||
|
||||
// The meta doesn't need update.
|
||||
unset( $post_data['meta_input'] );
|
||||
|
||||
wp_update_post( $post_data );
|
||||
}
|
||||
|
||||
/** @var Document $document */
|
||||
$document = new $class( [
|
||||
'post_id' => $post_id,
|
||||
] );
|
||||
|
||||
// Let the $document to re-save the template type by his way + version.
|
||||
$document->save( [] );
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove user edit capabilities if document is not editable.
|
||||
*
|
||||
* Filters the user capabilities to disable editing in admin.
|
||||
*
|
||||
* @param array $allcaps An array of all the user's capabilities.
|
||||
* @param array $caps Actual capabilities for meta capability.
|
||||
* @param array $args Optional parameters passed to has_cap(), typically object ID.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function remove_user_edit_cap( $allcaps, $caps, $args ) {
|
||||
global $pagenow;
|
||||
|
||||
if ( ! in_array( $pagenow, [ 'post.php', 'edit.php' ], true ) ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
// Don't touch not existing or not allowed caps.
|
||||
if ( empty( $caps[0] ) || empty( $allcaps[ $caps[0] ] ) ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
$capability = $args[0];
|
||||
|
||||
if ( 'edit_post' !== $capability ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
if ( empty( $args[2] ) ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
$post_id = $args[2];
|
||||
|
||||
$document = Plugin::$instance->documents->get( $post_id );
|
||||
|
||||
if ( ! $document ) {
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
$allcaps[ $caps[0] ] = $document::get_property( 'is_editable' );
|
||||
|
||||
return $allcaps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Post Row Actions.
|
||||
*
|
||||
* Let the Document to filter the array of row action links on the Posts list table.
|
||||
*
|
||||
* @param array $actions
|
||||
* @param \WP_Post $post
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_post_row_actions( $actions, $post ) {
|
||||
$document = $this->get( $post->ID );
|
||||
|
||||
if ( $document ) {
|
||||
$actions = $document->filter_admin_row_actions( $actions );
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save document data using ajax.
|
||||
*
|
||||
* Save the document on the builder using ajax, when saving the changes, and refresh the editor.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param $request Post ID.
|
||||
*
|
||||
* @throws \Exception If current user don't have permissions to edit the post or the post is not using Elementor.
|
||||
*
|
||||
* @return array The document data after saving.
|
||||
*/
|
||||
public function ajax_save( $request ) {
|
||||
$document = $this->get( $request['editor_post_id'] );
|
||||
|
||||
if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) {
|
||||
throw new \Exception( 'Access denied.' );
|
||||
}
|
||||
|
||||
$this->switch_to_document( $document );
|
||||
|
||||
// Set the post as global post.
|
||||
Plugin::$instance->db->switch_to_post( $document->get_post()->ID );
|
||||
|
||||
$status = DB::STATUS_DRAFT;
|
||||
|
||||
if ( isset( $request['status'] ) && in_array( $request['status'], [ DB::STATUS_PUBLISH, DB::STATUS_PRIVATE, DB::STATUS_PENDING, DB::STATUS_AUTOSAVE ], true ) ) {
|
||||
$status = $request['status'];
|
||||
}
|
||||
|
||||
if ( DB::STATUS_AUTOSAVE === $status ) {
|
||||
// If the post is a draft - save the `autosave` to the original draft.
|
||||
// Allow a revision only if the original post is already published.
|
||||
if ( in_array( $document->get_post()->post_status, [ DB::STATUS_PUBLISH, DB::STATUS_PRIVATE ], true ) ) {
|
||||
$document = $document->get_autosave( 0, true );
|
||||
}
|
||||
}
|
||||
|
||||
// Set default page template because the footer-saver doesn't send default values,
|
||||
// But if the template was changed from canvas to default - it needed to save.
|
||||
if ( Utils::is_cpt_custom_templates_supported() && ! isset( $request['settings']['template'] ) ) {
|
||||
$request['settings']['template'] = 'default';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'elements' => $request['elements'],
|
||||
'settings' => $request['settings'],
|
||||
];
|
||||
|
||||
$document->save( $data );
|
||||
|
||||
// Refresh after save.
|
||||
$document = $this->get( $document->get_post()->ID, false );
|
||||
|
||||
$return_data = [
|
||||
'status' => $document->get_post()->post_status,
|
||||
'config' => [
|
||||
'document' => [
|
||||
'last_edited' => $document->get_last_edited(),
|
||||
'urls' => [
|
||||
'wp_preview' => $document->get_wp_preview_url(),
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Returned documents ajax saved data.
|
||||
*
|
||||
* Filters the ajax data returned when saving the post on the builder.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param array $return_data The returned data.
|
||||
* @param Document $document The document instance.
|
||||
*/
|
||||
$return_data = apply_filters( 'elementor/documents/ajax_save/return_data', $return_data, $document );
|
||||
|
||||
return $return_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax discard changes.
|
||||
*
|
||||
* Load the document data from an autosave, deleting unsaved changes.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param $request
|
||||
*
|
||||
* @return bool True if changes discarded, False otherwise.
|
||||
*/
|
||||
public function ajax_discard_changes( $request ) {
|
||||
$document = $this->get( $request['editor_post_id'] );
|
||||
|
||||
$autosave = $document->get_autosave();
|
||||
|
||||
if ( $autosave ) {
|
||||
$success = $autosave->delete();
|
||||
} else {
|
||||
$success = true;
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function ajax_get_document_config( $request ) {
|
||||
$post_id = absint( $request['id'] );
|
||||
|
||||
Plugin::$instance->editor->set_post_id( $post_id );
|
||||
|
||||
$document = $this->get_doc_or_auto_save( $post_id );
|
||||
|
||||
if ( ! $document ) {
|
||||
throw new \Exception( 'Not Found.' );
|
||||
}
|
||||
|
||||
if ( ! $document->is_editable_by_current_user() ) {
|
||||
throw new \Exception( 'Access denied.' );
|
||||
}
|
||||
|
||||
// Set the global data like $post, $authordata and etc
|
||||
Plugin::$instance->db->switch_to_post( $post_id );
|
||||
|
||||
$this->switch_to_document( $document );
|
||||
|
||||
// Change mode to Builder
|
||||
Plugin::$instance->db->set_is_elementor_page( $post_id );
|
||||
|
||||
$doc_config = $document->get_config();
|
||||
|
||||
return $doc_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to document.
|
||||
*
|
||||
* Change the document to any new given document type.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param Document $document The document to switch to.
|
||||
*/
|
||||
public function switch_to_document( $document ) {
|
||||
// If is already switched, or is the same post, return.
|
||||
if ( $this->current_doc === $document ) {
|
||||
$this->switched_data[] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->switched_data[] = [
|
||||
'switched_doc' => $document,
|
||||
'original_doc' => $this->current_doc, // Note, it can be null if the global isn't set
|
||||
];
|
||||
|
||||
$this->current_doc = $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore document.
|
||||
*
|
||||
* Rollback to the original document.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function restore_document() {
|
||||
$data = array_pop( $this->switched_data );
|
||||
|
||||
// If not switched, return.
|
||||
if ( ! $data ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->current_doc = $data['original_doc'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current document.
|
||||
*
|
||||
* Retrieve the current document.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @return Document The current document.
|
||||
*/
|
||||
public function get_current() {
|
||||
return $this->current_doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @deprecated 2.4.0
|
||||
* @access public
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_groups() {
|
||||
_deprecated_function( __METHOD__, '2.4.0' );
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function localize_settings( $settings ) {
|
||||
$translations = [];
|
||||
|
||||
foreach ( $this->get_document_types() as $type => $class ) {
|
||||
$translations[ $type ] = $class::get_title();
|
||||
}
|
||||
|
||||
return array_replace_recursive( $settings, [
|
||||
'i18n' => $translations,
|
||||
] );
|
||||
}
|
||||
|
||||
private function register_types() {
|
||||
if ( ! did_action( 'elementor/documents/register' ) ) {
|
||||
/**
|
||||
* Register Elementor documents.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param Documents_Manager $this The document manager instance.
|
||||
*/
|
||||
do_action( 'elementor/documents/register', $this );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
namespace Elementor\Core\DynamicTags;
|
||||
|
||||
use Elementor\Controls_Stack;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor base tag.
|
||||
*
|
||||
* An abstract class to register new Elementor tags.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @abstract
|
||||
*/
|
||||
abstract class Base_Tag extends Controls_Stack {
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
final public static function get_type() {
|
||||
return 'tag';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @abstract
|
||||
*/
|
||||
abstract public function get_categories();
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @abstract
|
||||
*/
|
||||
abstract public function get_group();
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @abstract
|
||||
*/
|
||||
abstract public function get_title();
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @abstract
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
abstract public function get_content( array $options = [] );
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @abstract
|
||||
*/
|
||||
abstract public function get_content_type();
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_panel_template_setting_key() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function is_settings_required() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.9
|
||||
* @access public
|
||||
*/
|
||||
public function get_editor_config() {
|
||||
ob_start();
|
||||
|
||||
$this->print_panel_template();
|
||||
|
||||
$panel_template = ob_get_clean();
|
||||
|
||||
return [
|
||||
'name' => $this->get_name(),
|
||||
'title' => $this->get_title(),
|
||||
'panel_template' => $panel_template,
|
||||
'categories' => $this->get_categories(),
|
||||
'group' => $this->get_group(),
|
||||
'controls' => $this->get_controls(),
|
||||
'content_type' => $this->get_content_type(),
|
||||
'settings_required' => $this->is_settings_required(),
|
||||
'editable' => $this->is_editable(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function print_panel_template() {
|
||||
$panel_template_setting_key = $this->get_panel_template_setting_key();
|
||||
|
||||
if ( ! $panel_template_setting_key ) {
|
||||
return;
|
||||
}
|
||||
?><#
|
||||
var key = <?php echo esc_html( $panel_template_setting_key ); ?>;
|
||||
|
||||
if ( key ) {
|
||||
var settingsKey = "<?php echo esc_html( $panel_template_setting_key ); ?>";
|
||||
|
||||
/*
|
||||
* If the tag has controls,
|
||||
* and key is an existing control (and not an old one),
|
||||
* and the control has options (select/select2),
|
||||
* and the key is an existing option (and not in a group or an old one).
|
||||
*/
|
||||
if ( controls && controls[settingsKey] ) {
|
||||
var controlSettings = controls[settingsKey];
|
||||
|
||||
if ( controlSettings.options && controlSettings.options[ key ] ) {
|
||||
key = controlSettings.options[ key ];
|
||||
} else if ( controlSettings.groups ) {
|
||||
var label = _.filter( _.pluck( _.pluck( controls.key.groups, 'options' ), key ) );
|
||||
|
||||
if ( label[0] ) {
|
||||
key = label[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print( '(' + key + ')' );
|
||||
}
|
||||
#>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
final public function get_unique_name() {
|
||||
return 'tag-' . $this->get_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function register_advanced_section() {}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*/
|
||||
final protected function init_controls() {
|
||||
Plugin::$instance->controls_manager->open_stack( $this );
|
||||
|
||||
$this->start_controls_section( 'settings', [
|
||||
'label' => __( 'Settings', 'elementor' ),
|
||||
] );
|
||||
|
||||
$this->_register_controls();
|
||||
|
||||
$this->end_controls_section();
|
||||
|
||||
// If in fact no controls were registered, empty the stack
|
||||
if ( 1 === count( Plugin::$instance->controls_manager->get_stacks( $this->get_unique_name() )['controls'] ) ) {
|
||||
Plugin::$instance->controls_manager->open_stack( $this );
|
||||
}
|
||||
|
||||
$this->register_advanced_section();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Elementor\Core\DynamicTags;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor base data tag.
|
||||
*
|
||||
* An abstract class to register new Elementor data tags.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @abstract
|
||||
*/
|
||||
abstract class Data_Tag extends Base_Tag {
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
* @abstract
|
||||
*
|
||||
* @param array $options
|
||||
*/
|
||||
abstract protected function get_value( array $options = [] );
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
final public function get_content_type() {
|
||||
return 'plain';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_content( array $options = [] ) {
|
||||
return $this->get_value( $options );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
namespace Elementor\Core\DynamicTags;
|
||||
|
||||
use Elementor\Controls_Stack;
|
||||
use Elementor\Core\Files\CSS\Post as Post_CSS;
|
||||
use Elementor\Core\Files\CSS\Post_Local_Cache;
|
||||
use Elementor\Core\Files\CSS\Post_Preview;
|
||||
use Elementor\Element_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_CSS extends Post_Local_Cache {
|
||||
|
||||
private $post_dynamic_elements_ids;
|
||||
|
||||
private $post_id_for_data;
|
||||
|
||||
protected function get_post_id_for_data() {
|
||||
return $this->post_id_for_data;
|
||||
}
|
||||
|
||||
protected function is_global_parsing_supported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function render_styles( Element_Base $element ) {
|
||||
$id = $element->get_id();
|
||||
|
||||
if ( in_array( $id, $this->post_dynamic_elements_ids ) ) {
|
||||
parent::render_styles( $element );
|
||||
}
|
||||
|
||||
foreach ( $element->get_children() as $child_element ) {
|
||||
$this->render_styles( $child_element );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic_CSS constructor.
|
||||
*
|
||||
* @since 2.0.13
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Post ID
|
||||
* @param Post_CSS $post_css_file
|
||||
*/
|
||||
public function __construct( $post_id, Post_CSS $post_css_file ) {
|
||||
if ( $post_css_file instanceof Post_Preview ) {
|
||||
$this->post_id_for_data = $post_css_file->get_post_id_for_data();
|
||||
} else {
|
||||
$this->post_id_for_data = $post_id;
|
||||
}
|
||||
|
||||
$this->post_dynamic_elements_ids = $post_css_file->get_meta( 'dynamic_elements_ids' );
|
||||
|
||||
parent::__construct( $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.13
|
||||
* @access public
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'dynamic';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.13
|
||||
* @access protected
|
||||
*/
|
||||
protected function use_external_file() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.13
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_file_handle_id() {
|
||||
return 'elementor-post-dynamic-' . $this->get_post_id_for_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.13
|
||||
* @access public
|
||||
*/
|
||||
public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) {
|
||||
$dynamic_settings = $controls_stack->get_settings( '__dynamic__' );
|
||||
|
||||
if ( ! empty( $dynamic_settings ) ) {
|
||||
$controls = array_intersect_key( $controls, $dynamic_settings );
|
||||
|
||||
$all_controls = $controls_stack->get_controls();
|
||||
|
||||
$parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls );
|
||||
|
||||
foreach ( $controls as $control ) {
|
||||
if ( ! empty( $control['style_fields'] ) ) {
|
||||
$this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements );
|
||||
}
|
||||
|
||||
if ( empty( $control['selectors'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
<?php
|
||||
namespace Elementor\Core\DynamicTags;
|
||||
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Core\Files\CSS\Post;
|
||||
use Elementor\Core\Files\CSS\Post_Preview;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\User;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Manager {
|
||||
|
||||
const TAG_LABEL = 'elementor-tag';
|
||||
|
||||
const MODE_RENDER = 'render';
|
||||
|
||||
const MODE_REMOVE = 'remove';
|
||||
|
||||
const DYNAMIC_SETTING_KEY = '__dynamic__';
|
||||
|
||||
private $tags_groups = [];
|
||||
|
||||
private $tags_info = [];
|
||||
|
||||
private $parsing_mode = self::MODE_RENDER;
|
||||
|
||||
/**
|
||||
* Dynamic tags manager constructor.
|
||||
*
|
||||
* Initializing Elementor dynamic tags manager.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->add_actions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize settings.
|
||||
*
|
||||
* Add new localized settings for the dynamic module.
|
||||
*
|
||||
* Fired by `elementor/editor/localize_settings` filter.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param array $settings Localized settings.
|
||||
*
|
||||
* @return array Localized settings.
|
||||
*/
|
||||
public function localize_settings( $settings ) {
|
||||
$settings = array_replace_recursive( $settings, [
|
||||
'i18n' => [
|
||||
'dynamic' => __( 'Dynamic', 'elementor' ),
|
||||
],
|
||||
] );
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse dynamic tags text.
|
||||
*
|
||||
* Receives the dynamic tag text, and returns a single value or multiple values
|
||||
* from the tag callback function.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $text Dynamic tag text.
|
||||
* @param array $settings The dynamic tag settings.
|
||||
* @param callable $parse_callback The functions that renders the dynamic tag.
|
||||
*
|
||||
* @return string|string[]|mixed A single string or an array of strings with
|
||||
* the return values from each tag callback
|
||||
* function.
|
||||
*/
|
||||
public function parse_tags_text( $text, array $settings, callable $parse_callback ) {
|
||||
if ( ! empty( $settings['returnType'] ) && 'object' === $settings['returnType'] ) {
|
||||
$value = $this->parse_tag_text( $text, $settings, $parse_callback );
|
||||
} else {
|
||||
|
||||
$value = preg_replace_callback( '/\[' . self::TAG_LABEL . '.+?(?=\])\]/', function( $tag_text_match ) use ( $settings, $parse_callback ) {
|
||||
return $this->parse_tag_text( $tag_text_match[0], $settings, $parse_callback );
|
||||
}, $text );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse dynamic tag text.
|
||||
*
|
||||
* Receives the dynamic tag text, and returns the value from the callback
|
||||
* function.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $tag_text Dynamic tag text.
|
||||
* @param array $settings The dynamic tag settings.
|
||||
* @param callable $parse_callback The functions that renders the dynamic tag.
|
||||
*
|
||||
* @return string|array|mixed If the tag was not found an empty string or an
|
||||
* empty array will be returned, otherwise the
|
||||
* return value from the tag callback function.
|
||||
*/
|
||||
public function parse_tag_text( $tag_text, array $settings, callable $parse_callback ) {
|
||||
$tag_data = $this->tag_text_to_tag_data( $tag_text );
|
||||
|
||||
if ( ! $tag_data ) {
|
||||
if ( ! empty( $settings['returnType'] ) && 'object' === $settings['returnType'] ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
return call_user_func_array( $parse_callback, $tag_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $tag_text
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function tag_text_to_tag_data( $tag_text ) {
|
||||
preg_match( '/id="(.*?(?="))"/', $tag_text, $tag_id_match );
|
||||
preg_match( '/name="(.*?(?="))"/', $tag_text, $tag_name_match );
|
||||
preg_match( '/settings="(.*?(?="]))/', $tag_text, $tag_settings_match );
|
||||
|
||||
if ( ! $tag_id_match || ! $tag_name_match || ! $tag_settings_match ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $tag_id_match[1],
|
||||
'name' => $tag_name_match[1],
|
||||
'settings' => json_decode( urldecode( $tag_settings_match[1] ), true ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic tag to text.
|
||||
*
|
||||
* Retrieve the shortcode that represents the dynamic tag.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param Base_Tag $tag An instance of the dynamic tag.
|
||||
*
|
||||
* @return string The shortcode that represents the dynamic tag.
|
||||
*/
|
||||
public function tag_to_text( Base_Tag $tag ) {
|
||||
return sprintf( '[%1$s id="%2$s" name="%3$s" settings="%4$s"]', self::TAG_LABEL, $tag->get_id(), $tag->get_name(), urlencode( wp_json_encode( $tag->get_settings(), JSON_FORCE_OBJECT ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @param string $tag_id
|
||||
* @param string $tag_name
|
||||
* @param array $settings
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function tag_data_to_tag_text( $tag_id, $tag_name, array $settings = [] ) {
|
||||
$tag = $this->create_tag( $tag_id, $tag_name, $settings );
|
||||
|
||||
if ( ! $tag ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->tag_to_text( $tag );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @param string $tag_id
|
||||
* @param string $tag_name
|
||||
* @param array $settings
|
||||
*
|
||||
* @return Tag|null
|
||||
*/
|
||||
public function create_tag( $tag_id, $tag_name, array $settings = [] ) {
|
||||
$tag_info = $this->get_tag_info( $tag_name );
|
||||
|
||||
if ( ! $tag_info ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tag_class = $tag_info['class'];
|
||||
|
||||
return new $tag_class( [
|
||||
'settings' => $settings,
|
||||
'id' => $tag_id,
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param $tag_id
|
||||
* @param $tag_name
|
||||
* @param array $settings
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function get_tag_data_content( $tag_id, $tag_name, array $settings = [] ) {
|
||||
if ( self::MODE_REMOVE === $this->parsing_mode ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tag = $this->create_tag( $tag_id, $tag_name, $settings );
|
||||
|
||||
if ( ! $tag ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $tag->get_content();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param $tag_name
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get_tag_info( $tag_name ) {
|
||||
$tags = $this->get_tags();
|
||||
|
||||
if ( empty( $tags[ $tag_name ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $tags[ $tag_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.9
|
||||
* @access public
|
||||
*/
|
||||
public function get_tags() {
|
||||
if ( ! did_action( 'elementor/dynamic_tags/register_tags' ) ) {
|
||||
/**
|
||||
* Register dynamic tags.
|
||||
*
|
||||
* Fires when Elementor registers dynamic tags.
|
||||
*
|
||||
* @since 2.0.9
|
||||
*
|
||||
* @param Manager $this Dynamic tags manager.
|
||||
*/
|
||||
do_action( 'elementor/dynamic_tags/register_tags', $this );
|
||||
}
|
||||
|
||||
return $this->tags_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $class
|
||||
*/
|
||||
public function register_tag( $class ) {
|
||||
/** @var Tag $tag */
|
||||
$tag = new $class();
|
||||
|
||||
$this->tags_info[ $tag->get_name() ] = [
|
||||
'class' => $class,
|
||||
'instance' => $tag,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.9
|
||||
* @access public
|
||||
*
|
||||
* @param string $tag_name
|
||||
*/
|
||||
public function unregister_tag( $tag_name ) {
|
||||
unset( $this->tags_info[ $tag_name ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param $group_name
|
||||
* @param array $group_settings
|
||||
*/
|
||||
public function register_group( $group_name, array $group_settings ) {
|
||||
$default_group_settings = [
|
||||
'title' => '',
|
||||
];
|
||||
|
||||
$group_settings = array_merge( $default_group_settings, $group_settings );
|
||||
|
||||
$this->tags_groups[ $group_name ] = $group_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function print_templates() {
|
||||
foreach ( $this->get_tags() as $tag_name => $tag_info ) {
|
||||
$tag = $tag_info['instance'];
|
||||
|
||||
if ( ! $tag instanceof Tag ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tag->print_template();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_tags_config() {
|
||||
$config = [];
|
||||
|
||||
foreach ( $this->get_tags() as $tag_name => $tag_info ) {
|
||||
/** @var Tag $tag */
|
||||
$tag = $tag_info['instance'];
|
||||
|
||||
$config[ $tag_name ] = $tag->get_editor_config();
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_config() {
|
||||
return [
|
||||
'tags' => $this->get_tags_config(),
|
||||
'groups' => $this->tags_groups,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @throws \Exception If post ID is missing.
|
||||
* @throws \Exception If current user don't have permissions to edit the post.
|
||||
*/
|
||||
public function ajax_render_tags( $data ) {
|
||||
if ( empty( $data['post_id'] ) ) {
|
||||
throw new \Exception( 'Missing post id.' );
|
||||
}
|
||||
|
||||
if ( ! User::is_current_user_can_edit( $data['post_id'] ) ) {
|
||||
throw new \Exception( 'Access denied.' );
|
||||
}
|
||||
|
||||
Plugin::$instance->db->switch_to_post( $data['post_id'] );
|
||||
|
||||
/**
|
||||
* Before dynamic tags rendered.
|
||||
*
|
||||
* Fires before Elementor renders the dynamic tags.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
do_action( 'elementor/dynamic_tags/before_render' );
|
||||
|
||||
$tags_data = [];
|
||||
|
||||
foreach ( $data['tags'] as $tag_key ) {
|
||||
$tag_key_parts = explode( '-', $tag_key );
|
||||
|
||||
$tag_name = base64_decode( $tag_key_parts[0] );
|
||||
|
||||
$tag_settings = json_decode( urldecode( base64_decode( $tag_key_parts[1] ) ), true );
|
||||
|
||||
$tag = $this->create_tag( null, $tag_name, $tag_settings );
|
||||
|
||||
$tags_data[ $tag_key ] = $tag->get_content();
|
||||
}
|
||||
|
||||
/**
|
||||
* After dynamic tags rendered.
|
||||
*
|
||||
* Fires after Elementor renders the dynamic tags.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
do_action( 'elementor/dynamic_tags/after_render' );
|
||||
|
||||
return $tags_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param $mode
|
||||
*/
|
||||
public function set_parsing_mode( $mode ) {
|
||||
$this->parsing_mode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_parsing_mode() {
|
||||
return $this->parsing_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
* @param Post $css_file
|
||||
*/
|
||||
public function after_enqueue_post_css( $css_file ) {
|
||||
$css_file = Dynamic_CSS::create( $css_file->get_post_id(), $css_file );
|
||||
|
||||
$css_file->enqueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.3.0
|
||||
* @access public
|
||||
*/
|
||||
public function register_ajax_actions( Ajax $ajax ) {
|
||||
$ajax->register_ajax_action( 'render_tags', [ $this, 'ajax_render_tags' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*/
|
||||
private function add_actions() {
|
||||
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
|
||||
add_action( 'elementor/css-file/post/enqueue', [ $this, 'after_enqueue_post_css' ] );
|
||||
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
namespace Elementor\Core\DynamicTags;
|
||||
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor tag.
|
||||
*
|
||||
* An abstract class to register new Elementor tag.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @abstract
|
||||
*/
|
||||
abstract class Tag extends Base_Tag {
|
||||
|
||||
const WRAPPED_TAG = false;
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content( array $options = [] ) {
|
||||
$settings = $this->get_settings();
|
||||
|
||||
ob_start();
|
||||
|
||||
$this->render();
|
||||
|
||||
$value = ob_get_clean();
|
||||
|
||||
if ( ! Utils::is_empty( $value ) ) {
|
||||
// TODO: fix spaces in `before`/`after` if WRAPPED_TAG ( conflicted with .elementor-tag { display: inline-flex; } );
|
||||
if ( ! Utils::is_empty( $settings, 'before' ) ) {
|
||||
$value = wp_kses_post( $settings['before'] ) . $value;
|
||||
}
|
||||
|
||||
if ( ! Utils::is_empty( $settings, 'after' ) ) {
|
||||
$value .= wp_kses_post( $settings['after'] );
|
||||
}
|
||||
|
||||
if ( static::WRAPPED_TAG ) :
|
||||
$value = '<span id="elementor-tag-' . esc_attr( $this->get_id() ) . '" class="elementor-tag">' . $value . '</span>';
|
||||
endif;
|
||||
|
||||
} elseif ( ! Utils::is_empty( $settings, 'fallback' ) ) {
|
||||
$value = $settings['fallback'];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
final public function get_content_type() {
|
||||
return 'ui';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.9
|
||||
* @access public
|
||||
*/
|
||||
public function get_editor_config() {
|
||||
$config = parent::get_editor_config();
|
||||
|
||||
$config['wrapped_tag'] = $this::WRAPPED_TAG;
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function register_advanced_section() {
|
||||
$this->start_controls_section(
|
||||
'advanced',
|
||||
[
|
||||
'label' => __( 'Advanced', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'before',
|
||||
[
|
||||
'label' => __( 'Before', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'after',
|
||||
[
|
||||
'label' => __( 'After', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'fallback',
|
||||
[
|
||||
'label' => __( 'Fallback', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Editor\Data\Globals;
|
||||
|
||||
use Elementor\Data\Base\Controller as Controller_Base;
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Controller extends Controller_Base {
|
||||
public function get_name() {
|
||||
return 'globals';
|
||||
}
|
||||
|
||||
public function register_endpoints() {
|
||||
$this->register_endpoint( Endpoints\Colors::class );
|
||||
$this->register_endpoint( Endpoints\Typography::class );
|
||||
}
|
||||
|
||||
protected function register_internal_endpoints() {
|
||||
$this->register_endpoint( Endpoints\Index::class );
|
||||
}
|
||||
|
||||
public function get_permission_callback( $request ) {
|
||||
// Allow internal get global values. (e.g render global.css for a visitor)
|
||||
if ( 'GET' === $request->get_method() && Plugin::$instance->data_manager->is_internal() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return current_user_can( 'edit_posts' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Editor\Data\Globals\Endpoints;
|
||||
|
||||
use Elementor\Data\Base\Endpoint;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Core\Utils\Exceptions;
|
||||
|
||||
abstract class Base extends Endpoint {
|
||||
public static function get_format() {
|
||||
return '{id}';
|
||||
}
|
||||
|
||||
protected function register() {
|
||||
parent::register();
|
||||
|
||||
$this->register_item_route();
|
||||
$this->register_item_route( \WP_REST_Server::CREATABLE );
|
||||
$this->register_item_route( \WP_REST_Server::DELETABLE );
|
||||
}
|
||||
|
||||
public function get_items( $request ) {
|
||||
return $this->get_kit_items();
|
||||
}
|
||||
|
||||
public function get_item( $id, $request ) {
|
||||
$items = $this->get_kit_items();
|
||||
|
||||
if ( ! isset( $items[ $id ] ) ) {
|
||||
return new \WP_Error(
|
||||
'global_not_found',
|
||||
__( 'Global value was not found.', 'elementor' ),
|
||||
[ 'status' => Exceptions::NOT_FOUND ]
|
||||
);
|
||||
}
|
||||
|
||||
return $items[ $id ];
|
||||
}
|
||||
|
||||
public function create_item( $id, $request ) {
|
||||
$item = $request->get_body_params();
|
||||
|
||||
if ( ! isset( $item['title'] ) ) {
|
||||
return new \WP_Error( 'invalid_title', 'Invalid title' );
|
||||
}
|
||||
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit();
|
||||
|
||||
$item['id'] = $id;
|
||||
|
||||
$db_item = $this->convert_db_format( $item );
|
||||
|
||||
$kit->add_repeater_row( 'custom_' . $this->get_name(), $db_item );
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
abstract protected function get_kit_items();
|
||||
|
||||
/**
|
||||
* @param array $item frontend format.
|
||||
* @return array backend format.
|
||||
*/
|
||||
abstract protected function convert_db_format( $item );
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Editor\Data\Globals\Endpoints;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Colors extends Base {
|
||||
public function get_name() {
|
||||
return 'colors';
|
||||
}
|
||||
|
||||
protected function get_kit_items() {
|
||||
$result = [];
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
|
||||
|
||||
$system_items = $kit->get_settings_for_display( 'system_colors' );
|
||||
$custom_items = $kit->get_settings_for_display( 'custom_colors' );
|
||||
|
||||
if ( ! $system_items ) {
|
||||
$system_items = [];
|
||||
}
|
||||
|
||||
if ( ! $custom_items ) {
|
||||
$custom_items = [];
|
||||
}
|
||||
|
||||
$items = array_merge( $system_items, $custom_items );
|
||||
|
||||
foreach ( $items as $index => $item ) {
|
||||
$id = $item['_id'];
|
||||
$result[ $id ] = [
|
||||
'id' => $id,
|
||||
'title' => $item['title'],
|
||||
'value' => $item['color'],
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function convert_db_format( $item ) {
|
||||
return [
|
||||
'_id' => $item['id'],
|
||||
'title' => $item['title'],
|
||||
'color' => $item['value'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Editor\Data\Globals\Endpoints;
|
||||
|
||||
use Elementor\Data\Base\Endpoint;
|
||||
|
||||
// TODO: Create base class for index endpoints, and move this function to there.
|
||||
class Index extends Endpoint {
|
||||
public function get_name() {
|
||||
return 'index'; // TODO: Replace with BaseIndex class.
|
||||
}
|
||||
|
||||
public function get_items( $request ) {
|
||||
return $this->controller->get_items_recursive( [ $this ] ); // TODO: Replace with BaseIndex class.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Editor\Data\Globals\Endpoints;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Typography extends Base {
|
||||
public function get_name() {
|
||||
return 'typography';
|
||||
}
|
||||
|
||||
protected function get_kit_items() {
|
||||
$result = [];
|
||||
|
||||
$kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
|
||||
|
||||
// Use raw settings that doesn't have default values.
|
||||
$kit_raw_settings = $kit->get_data( 'settings' );
|
||||
|
||||
if ( isset( $kit_raw_settings['system_typography'] ) ) {
|
||||
$system_items = $kit_raw_settings['system_typography'];
|
||||
} else {
|
||||
// Get default items, but without empty defaults.
|
||||
$control = $kit->get_controls( 'system_typography' );
|
||||
$system_items = $control['default'];
|
||||
}
|
||||
|
||||
$custom_items = $kit->get_settings( 'custom_typography' );
|
||||
|
||||
if ( ! $custom_items ) {
|
||||
$custom_items = [];
|
||||
}
|
||||
|
||||
$items = array_merge( $system_items, $custom_items );
|
||||
|
||||
foreach ( $items as $index => &$item ) {
|
||||
foreach ( $item as $setting => $value ) {
|
||||
$new_setting = str_replace( 'styles_', '', $setting, $count );
|
||||
if ( $count ) {
|
||||
$item[ $new_setting ] = $value;
|
||||
unset( $item[ $setting ] );
|
||||
}
|
||||
}
|
||||
|
||||
$id = $item['_id'];
|
||||
|
||||
$result[ $id ] = [
|
||||
'title' => $item['title'],
|
||||
'id' => $id,
|
||||
];
|
||||
|
||||
unset( $item['_id'], $item['title'] );
|
||||
|
||||
$result[ $id ]['value'] = $item;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function convert_db_format( $item ) {
|
||||
$db_format = [
|
||||
'_id' => $item['id'],
|
||||
'title' => $item['title'],
|
||||
];
|
||||
|
||||
$db_format = array_merge( $item['value'], $db_format );
|
||||
|
||||
return $db_format;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Editor;
|
||||
|
||||
use Elementor\Core\Base\Base_Object;
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Notice_Bar extends Base_Object {
|
||||
|
||||
protected function get_init_settings() {
|
||||
if ( Plugin::$instance->get_install_time() > strtotime( '-30 days' ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'muted_period' => 90,
|
||||
'option_key' => '_elementor_editor_upgrade_notice_dismissed',
|
||||
'message' => __( 'Love using Elementor? <a href="%s">Learn how you can build better sites with Elementor Pro.</a>', 'elementor' ),
|
||||
'action_title' => __( 'Go Pro', 'elementor' ),
|
||||
'action_url' => Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=editor-notice-bar&utm_campaign=gopro&utm_medium=wp-dash' ),
|
||||
];
|
||||
}
|
||||
|
||||
final public function get_notice() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$settings = $this->get_settings();
|
||||
|
||||
if ( empty( $settings['option_key'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dismissed_time = get_option( $settings['option_key'] );
|
||||
|
||||
if ( $dismissed_time ) {
|
||||
if ( $dismissed_time > strtotime( '-' . $settings['muted_period'] . ' days' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->set_notice_dismissed();
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
|
||||
}
|
||||
|
||||
public function set_notice_dismissed() {
|
||||
update_option( $this->get_settings( 'option_key' ), time() );
|
||||
}
|
||||
|
||||
public function register_ajax_actions( Ajax $ajax ) {
|
||||
$ajax->register_ajax_action( 'notice_bar_dismiss', [ $this, 'set_notice_dismissed' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Files\Assets;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Files_Upload_Handler {
|
||||
const OPTION_KEY = 'elementor_unfiltered_files_upload';
|
||||
|
||||
public function __construct() {
|
||||
add_filter( 'upload_mimes', [ $this, 'support_unfiltered_files_upload' ] );
|
||||
add_filter( 'wp_handle_upload_prefilter', [ $this, 'handle_upload_prefilter' ] );
|
||||
add_filter( 'wp_check_filetype_and_ext', [ $this, 'check_filetype_and_ext' ], 10, 4 );
|
||||
}
|
||||
|
||||
abstract public function get_mime_type();
|
||||
|
||||
abstract public function get_file_type();
|
||||
|
||||
/**
|
||||
* is_elementor_media_upload
|
||||
* @return bool
|
||||
*/
|
||||
private function is_elementor_media_upload() {
|
||||
return isset( $_POST['uploadTypeCaller'] ) && 'elementor-editor-upload' === $_POST['uploadTypeCaller']; // phpcs:ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
final public static function is_enabled() {
|
||||
$enabled = ! ! get_option( self::OPTION_KEY ) && self::file_sanitizer_can_run();
|
||||
|
||||
/**
|
||||
* @deprecated 3.0.0 Use `elementor/document/urls/edit` filter instead.
|
||||
*/
|
||||
$enabled = apply_filters( 'elementor/files/svg/enabled', $enabled );
|
||||
|
||||
/**
|
||||
* Allow Unfiltered Files Upload.
|
||||
*
|
||||
* Determines whether to enable unfiltered file uploads.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param bool $enabled Weather upload is enabled or not.
|
||||
*/
|
||||
$enabled = apply_filters( 'elementor/files/allow_unfiltered_upload', $enabled );
|
||||
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
final public function support_unfiltered_files_upload( $existing_mimes ) {
|
||||
$existing_mimes[ $this->get_file_type() ] = $this->get_mime_type();
|
||||
|
||||
return $existing_mimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle_upload_prefilter
|
||||
* @param $file
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle_upload_prefilter( $file ) {
|
||||
if ( ! $this->is_file_should_handled( $file ) ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$ext = pathinfo( $file['name'], PATHINFO_EXTENSION );
|
||||
$file_type = $this->get_file_type();
|
||||
$display_type = strtoupper( $file_type );
|
||||
|
||||
if ( $file_type !== $ext ) {
|
||||
$file['error'] = sprintf( __( 'The uploaded %1$s file is not supported. Please upload a valid %2$s file', 'elementor' ), $ext, $display_type );
|
||||
return $file;
|
||||
}
|
||||
|
||||
if ( ! self::is_enabled() ) {
|
||||
$file['error'] = sprintf( __( '%1$s file is not allowed for security reasons', 'elementor' ), $display_type );
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
protected function is_file_should_handled( $file ) {
|
||||
return $this->is_elementor_media_upload() && $this->get_mime_type() === $file['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* file_sanitizer_can_run
|
||||
* @return bool
|
||||
*/
|
||||
public static function file_sanitizer_can_run() {
|
||||
return class_exists( 'DOMDocument' ) && class_exists( 'SimpleXMLElement' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check filetype and ext
|
||||
*
|
||||
* A workaround for upload validation which relies on a PHP extension (fileinfo)
|
||||
* with inconsistent reporting behaviour.
|
||||
* ref: https://core.trac.wordpress.org/ticket/39550
|
||||
* ref: https://core.trac.wordpress.org/ticket/40175
|
||||
*
|
||||
* @param $data
|
||||
* @param $file
|
||||
* @param $filename
|
||||
* @param $mimes
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function check_filetype_and_ext( $data, $file, $filename, $mimes ) {
|
||||
if ( ! empty( $data['ext'] ) && ! empty( $data['type'] ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$wp_file_type = wp_check_filetype( $filename, $mimes );
|
||||
$file_type = strtolower( $this->get_file_type() );
|
||||
|
||||
if ( $file_type === $wp_file_type['ext'] ) {
|
||||
$data['ext'] = $file_type;
|
||||
$data['type'] = $this->get_mime_type();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files\Assets\Json;
|
||||
|
||||
use Elementor\Core\Files\Assets\Files_Upload_Handler;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Json_Handler extends Files_Upload_Handler {
|
||||
public static function get_name() {
|
||||
return 'json-handler';
|
||||
}
|
||||
|
||||
public function get_mime_type() {
|
||||
return 'application/json';
|
||||
}
|
||||
|
||||
public function get_file_type() {
|
||||
return 'json';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files\Assets;
|
||||
|
||||
use Elementor\Core\Files\Assets\Svg\Svg_Handler;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor files manager.
|
||||
*
|
||||
* Elementor files manager handler class is responsible for creating files.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
class Manager {
|
||||
|
||||
/**
|
||||
* Holds registered asset types
|
||||
* @var array
|
||||
*/
|
||||
protected $asset_types = [];
|
||||
|
||||
/**
|
||||
* Assets manager constructor.
|
||||
*
|
||||
* Initializing the Elementor assets manager.
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->register_asset_types();
|
||||
/**
|
||||
* Elementor files assets registered.
|
||||
*
|
||||
* Fires after Elementor registers assets types
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
do_action( 'elementor/core/files/assets/assets_registered', $this );
|
||||
}
|
||||
|
||||
public function get_asset( $name ) {
|
||||
return isset( $this->asset_types[ $name ] ) ? $this->asset_types[ $name ] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Asset
|
||||
* @param $instance
|
||||
*/
|
||||
public function add_asset( $instance ) {
|
||||
$this->asset_types[ $instance::get_name() ] = $instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register Asset Types
|
||||
*
|
||||
* Registers Elementor Asset Types
|
||||
*/
|
||||
private function register_asset_types() {
|
||||
$this->add_asset( new Svg_Handler() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,704 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files\Assets\Svg;
|
||||
|
||||
use Elementor\Core\Files\Assets\Files_Upload_Handler;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Svg_Handler extends Files_Upload_Handler {
|
||||
/**
|
||||
* Inline svg attachment meta key
|
||||
*/
|
||||
const META_KEY = '_elementor_inline_svg';
|
||||
|
||||
const SCRIPT_REGEX = '/(?:\w+script|data):/xi';
|
||||
|
||||
/**
|
||||
* @var \DOMDocument
|
||||
*/
|
||||
private $svg_dom = null;
|
||||
|
||||
/**
|
||||
* Attachment ID.
|
||||
*
|
||||
* Holds the current attachment ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $attachment_id;
|
||||
|
||||
public static function get_name() {
|
||||
return 'svg-handler';
|
||||
}
|
||||
|
||||
/**
|
||||
* get_meta
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_meta() {
|
||||
return get_post_meta( $this->attachment_id, self::META_KEY, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* update_meta
|
||||
* @param $meta
|
||||
*/
|
||||
protected function update_meta( $meta ) {
|
||||
update_post_meta( $this->attachment_id, self::META_KEY, $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* delete_meta
|
||||
*/
|
||||
protected function delete_meta() {
|
||||
delete_post_meta( $this->attachment_id, self::META_KEY );
|
||||
}
|
||||
|
||||
public function get_mime_type() {
|
||||
return 'image/svg+xml';
|
||||
}
|
||||
|
||||
public function get_file_type() {
|
||||
return 'svg';
|
||||
}
|
||||
|
||||
/**
|
||||
* delete_meta_cache
|
||||
*/
|
||||
public function delete_meta_cache() {
|
||||
delete_post_meta_by_key( self::META_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* read_from_file
|
||||
* @return bool|string
|
||||
*/
|
||||
public function read_from_file() {
|
||||
return file_get_contents( get_attached_file( $this->attachment_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* get_inline_svg
|
||||
* @param $attachment_id
|
||||
*
|
||||
* @return bool|mixed|string
|
||||
*/
|
||||
public static function get_inline_svg( $attachment_id ) {
|
||||
$svg = get_post_meta( $attachment_id, self::META_KEY, true );
|
||||
|
||||
if ( ! empty( $svg ) ) {
|
||||
return $svg;
|
||||
}
|
||||
|
||||
$attachment_file = get_attached_file( $attachment_id );
|
||||
|
||||
if ( ! $attachment_file ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$svg = file_get_contents( $attachment_file );
|
||||
|
||||
if ( ! empty( $svg ) ) {
|
||||
update_post_meta( $attachment_id, self::META_KEY, $svg );
|
||||
}
|
||||
|
||||
return $svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* decode_svg
|
||||
* @param $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function decode_svg( $content ) {
|
||||
return gzdecode( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* encode_svg
|
||||
* @param $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function encode_svg( $content ) {
|
||||
return gzencode( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* sanitize_svg
|
||||
* @param $filename
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sanitize_svg( $filename ) {
|
||||
$original_content = file_get_contents( $filename );
|
||||
$is_encoded = $this->is_encoded( $original_content );
|
||||
|
||||
if ( $is_encoded ) {
|
||||
$decoded = $this->decode_svg( $original_content );
|
||||
if ( false === $decoded ) {
|
||||
return false;
|
||||
}
|
||||
$original_content = $decoded;
|
||||
}
|
||||
|
||||
$valid_svg = $this->sanitizer( $original_content );
|
||||
|
||||
if ( false === $valid_svg ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we were gzipped, we need to re-zip
|
||||
if ( $is_encoded ) {
|
||||
$valid_svg = $this->encode_svg( $valid_svg );
|
||||
}
|
||||
file_put_contents( $filename, $valid_svg );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the contents are gzipped
|
||||
* @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
|
||||
*
|
||||
* @param $contents
|
||||
* @return bool
|
||||
*/
|
||||
private function is_encoded( $contents ) {
|
||||
$needle = "\x1f\x8b\x08";
|
||||
if ( function_exists( 'mb_strpos' ) ) {
|
||||
return 0 === mb_strpos( $contents, $needle );
|
||||
} else {
|
||||
return 0 === strpos( $contents, $needle );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* is_allowed_tag
|
||||
* @param $element
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_allowed_tag( $element ) {
|
||||
static $allowed_tags = false;
|
||||
if ( false === $allowed_tags ) {
|
||||
$allowed_tags = $this->get_allowed_elements();
|
||||
}
|
||||
|
||||
$tag_name = $element->tagName; // phpcs:ignore -- php DomDocument
|
||||
|
||||
if ( ! in_array( strtolower( $tag_name ), $allowed_tags ) ) {
|
||||
$this->remove_element( $element );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function remove_element( $element ) {
|
||||
$element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomDocument
|
||||
}
|
||||
|
||||
/**
|
||||
* is_a_attribute
|
||||
* @param $name
|
||||
* @param $check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_a_attribute( $name, $check ) {
|
||||
return 0 === strpos( $name, $check . '-' );
|
||||
}
|
||||
|
||||
/**
|
||||
* is_remote_value
|
||||
* @param $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function is_remote_value( $value ) {
|
||||
$value = trim( preg_replace( '/[^ -~]/xu', '', $value ) );
|
||||
$wrapped_in_url = preg_match( '~^url\(\s*[\'"]\s*(.*)\s*[\'"]\s*\)$~xi', $value, $match );
|
||||
if ( ! $wrapped_in_url ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = trim( $match[1], '\'"' );
|
||||
return preg_match( '~^((https?|ftp|file):)?//~xi', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* has_js_value
|
||||
* @param $value
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
private function has_js_value( $value ) {
|
||||
return preg_match( '/base64|data|(?:java)?script|alert\(|window\.|document/i', $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* get_allowed_attributes
|
||||
* @return array
|
||||
*/
|
||||
private function get_allowed_attributes() {
|
||||
$allowed_attributes = [
|
||||
'class',
|
||||
'clip-path',
|
||||
'clip-rule',
|
||||
'fill',
|
||||
'fill-opacity',
|
||||
'fill-rule',
|
||||
'filter',
|
||||
'id',
|
||||
'mask',
|
||||
'opacity',
|
||||
'stroke',
|
||||
'stroke-dasharray',
|
||||
'stroke-dashoffset',
|
||||
'stroke-linecap',
|
||||
'stroke-linejoin',
|
||||
'stroke-miterlimit',
|
||||
'stroke-opacity',
|
||||
'stroke-width',
|
||||
'style',
|
||||
'systemlanguage',
|
||||
'transform',
|
||||
'href',
|
||||
'xlink:href',
|
||||
'xlink:title',
|
||||
'cx',
|
||||
'cy',
|
||||
'r',
|
||||
'requiredfeatures',
|
||||
'clippathunits',
|
||||
'type',
|
||||
'rx',
|
||||
'ry',
|
||||
'color-interpolation-filters',
|
||||
'stddeviation',
|
||||
'filterres',
|
||||
'filterunits',
|
||||
'height',
|
||||
'primitiveunits',
|
||||
'width',
|
||||
'x',
|
||||
'y',
|
||||
'font-size',
|
||||
'display',
|
||||
'font-family',
|
||||
'font-style',
|
||||
'font-weight',
|
||||
'text-anchor',
|
||||
'marker-end',
|
||||
'marker-mid',
|
||||
'marker-start',
|
||||
'x1',
|
||||
'x2',
|
||||
'y1',
|
||||
'y2',
|
||||
'gradienttransform',
|
||||
'gradientunits',
|
||||
'spreadmethod',
|
||||
'markerheight',
|
||||
'markerunits',
|
||||
'markerwidth',
|
||||
'orient',
|
||||
'preserveaspectratio',
|
||||
'refx',
|
||||
'refy',
|
||||
'viewbox',
|
||||
'maskcontentunits',
|
||||
'maskunits',
|
||||
'd',
|
||||
'patterncontentunits',
|
||||
'patterntransform',
|
||||
'patternunits',
|
||||
'points',
|
||||
'fx',
|
||||
'fy',
|
||||
'offset',
|
||||
'stop-color',
|
||||
'stop-opacity',
|
||||
'xmlns',
|
||||
'xmlns:se',
|
||||
'xmlns:xlink',
|
||||
'xml:space',
|
||||
'method',
|
||||
'spacing',
|
||||
'startoffset',
|
||||
'dx',
|
||||
'dy',
|
||||
'rotate',
|
||||
'textlength',
|
||||
];
|
||||
|
||||
return apply_filters( 'elementor/files/svg/allowed_attributes', $allowed_attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* get_allowed_elements
|
||||
* @return array
|
||||
*/
|
||||
private function get_allowed_elements() {
|
||||
$allowed_elements = [
|
||||
'a',
|
||||
'circle',
|
||||
'clippath',
|
||||
'defs',
|
||||
'style',
|
||||
'desc',
|
||||
'ellipse',
|
||||
'fegaussianblur',
|
||||
'filter',
|
||||
'foreignobject',
|
||||
'g',
|
||||
'image',
|
||||
'line',
|
||||
'lineargradient',
|
||||
'marker',
|
||||
'mask',
|
||||
'metadata',
|
||||
'path',
|
||||
'pattern',
|
||||
'polygon',
|
||||
'polyline',
|
||||
'radialgradient',
|
||||
'rect',
|
||||
'stop',
|
||||
'svg',
|
||||
'switch',
|
||||
'symbol',
|
||||
'text',
|
||||
'textpath',
|
||||
'title',
|
||||
'tspan',
|
||||
'use',
|
||||
];
|
||||
return apply_filters( 'elementor/files/svg/allowed_elements', $allowed_elements );
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_allowed_attributes
|
||||
* @param \DOMElement $element
|
||||
*/
|
||||
private function validate_allowed_attributes( $element ) {
|
||||
static $allowed_attributes = false;
|
||||
if ( false === $allowed_attributes ) {
|
||||
$allowed_attributes = $this->get_allowed_attributes();
|
||||
}
|
||||
|
||||
for ( $index = $element->attributes->length - 1; $index >= 0; $index-- ) {
|
||||
// get attribute name
|
||||
$attr_name = $element->attributes->item( $index )->name;
|
||||
$attr_name_lowercase = strtolower( $attr_name );
|
||||
// Remove attribute if not in whitelist
|
||||
if ( ! in_array( $attr_name_lowercase, $allowed_attributes ) && ! $this->is_a_attribute( $attr_name_lowercase, 'aria' ) && ! $this->is_a_attribute( $attr_name_lowercase, 'data' ) ) {
|
||||
$element->removeAttribute( $attr_name );
|
||||
continue;
|
||||
}
|
||||
|
||||
$attr_value = $element->attributes->item( $index )->value;
|
||||
|
||||
// Remove attribute if it has a remote reference or js or data-URI/base64
|
||||
if ( ! empty( $attr_value ) && ( $this->is_remote_value( $attr_value ) || $this->has_js_value( $attr_value ) ) ) {
|
||||
$element->removeAttribute( $attr_name );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* strip_xlinks
|
||||
* @param \DOMElement $element
|
||||
*/
|
||||
private function strip_xlinks( $element ) {
|
||||
$xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
|
||||
|
||||
if ( ! $xlinks ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$allowed_links = [
|
||||
'data:image/png', // PNG
|
||||
'data:image/gif', // GIF
|
||||
'data:image/jpg', // JPG
|
||||
'data:image/jpe', // JPEG
|
||||
'data:image/pjp', // PJPEG
|
||||
];
|
||||
if ( 1 === preg_match( self::SCRIPT_REGEX, $xlinks ) ) {
|
||||
if ( ! in_array( substr( $xlinks, 0, 14 ), $allowed_links ) ) {
|
||||
$element->removeAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* validate_use_tag
|
||||
* @param $element
|
||||
*/
|
||||
private function validate_use_tag( $element ) {
|
||||
$xlinks = $element->getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' );
|
||||
if ( $xlinks && '#' !== substr( $xlinks, 0, 1 ) ) {
|
||||
$element->parentNode->removeChild( $element ); // phpcs:ignore -- php DomNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* strip_docktype
|
||||
*/
|
||||
private function strip_doctype() {
|
||||
foreach ( $this->svg_dom->childNodes as $child ) {
|
||||
if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType ) { // phpcs:ignore -- php DomDocument
|
||||
$child->parentNode->removeChild( $child ); // phpcs:ignore -- php DomDocument
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sanitize_elements
|
||||
*/
|
||||
private function sanitize_elements() {
|
||||
$elements = $this->svg_dom->getElementsByTagName( '*' );
|
||||
// loop through all elements
|
||||
// we do this backwards so we don't skip anything if we delete a node
|
||||
// see comments at: http://php.net/manual/en/class.domnamednodemap.php
|
||||
for ( $index = $elements->length - 1; $index >= 0; $index-- ) {
|
||||
/**
|
||||
* @var \DOMElement $current_element
|
||||
*/
|
||||
$current_element = $elements->item( $index );
|
||||
// If the tag isn't in the whitelist, remove it and continue with next iteration
|
||||
if ( ! $this->is_allowed_tag( $current_element ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//validate element attributes
|
||||
$this->validate_allowed_attributes( $current_element );
|
||||
|
||||
$this->strip_xlinks( $current_element );
|
||||
|
||||
if ( 'use' === strtolower( $current_element->tagName ) ) { // phpcs:ignore -- php DomDocument
|
||||
$this->validate_use_tag( $current_element );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sanitizer
|
||||
* @param $content
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function sanitizer( $content ) {
|
||||
// Strip php tags
|
||||
$content = $this->strip_comments( $content );
|
||||
$content = $this->strip_php_tags( $content );
|
||||
|
||||
// Find the start and end tags so we can cut out miscellaneous garbage.
|
||||
$start = strpos( $content, '<svg' );
|
||||
$end = strrpos( $content, '</svg>' );
|
||||
if ( false === $start || false === $end ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = substr( $content, $start, ( $end - $start + 6 ) );
|
||||
|
||||
// Make sure to Disable the ability to load external entities
|
||||
$libxml_disable_entity_loader = libxml_disable_entity_loader( true );
|
||||
// Suppress the errors
|
||||
$libxml_use_internal_errors = libxml_use_internal_errors( true );
|
||||
|
||||
// Create DomDocument instance
|
||||
$this->svg_dom = new \DOMDocument();
|
||||
$this->svg_dom->formatOutput = false;
|
||||
$this->svg_dom->preserveWhiteSpace = false;
|
||||
$this->svg_dom->strictErrorChecking = false;
|
||||
|
||||
$open_svg = $this->svg_dom->loadXML( $content );
|
||||
if ( ! $open_svg ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->strip_doctype();
|
||||
$this->sanitize_elements();
|
||||
|
||||
// Export sanitized svg to string
|
||||
// Using documentElement to strip out <?xml version="1.0" encoding="UTF-8"...
|
||||
$sanitized = $this->svg_dom->saveXML( $this->svg_dom->documentElement, LIBXML_NOEMPTYTAG );
|
||||
|
||||
// Restore defaults
|
||||
libxml_disable_entity_loader( $libxml_disable_entity_loader );
|
||||
libxml_use_internal_errors( $libxml_use_internal_errors );
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* strip_php_tags
|
||||
* @param $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function strip_php_tags( $string ) {
|
||||
$string = preg_replace( '/<\?(=|php)(.+?)\?>/i', '', $string );
|
||||
// Remove XML, ASP, etc.
|
||||
$string = preg_replace( '/<\?(.*)\?>/Us', '', $string );
|
||||
$string = preg_replace( '/<\%(.*)\%>/Us', '', $string );
|
||||
|
||||
if ( ( false !== strpos( $string, '<?' ) ) || ( false !== strpos( $string, '<%' ) ) ) {
|
||||
return '';
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* strip_comments
|
||||
* @param $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function strip_comments( $string ) {
|
||||
// Remove comments.
|
||||
$string = preg_replace( '/<!--(.*)-->/Us', '', $string );
|
||||
$string = preg_replace( '/\/\*(.*)\*\//Us', '', $string );
|
||||
if ( ( false !== strpos( $string, '<!--' ) ) || ( false !== strpos( $string, '/*' ) ) ) {
|
||||
return '';
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_prepare_attachment_for_js
|
||||
* @param $attachment_data
|
||||
* @param $attachment
|
||||
* @param $meta
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $attachment_data, $attachment, $meta ) {
|
||||
if ( 'image' !== $attachment_data['type'] || 'svg+xml' !== $attachment_data['subtype'] || ! class_exists( 'SimpleXMLElement' ) ) {
|
||||
return $attachment_data;
|
||||
}
|
||||
|
||||
$svg = self::get_inline_svg( $attachment->ID );
|
||||
|
||||
if ( ! $svg ) {
|
||||
return $attachment_data;
|
||||
}
|
||||
|
||||
try {
|
||||
$svg = new \SimpleXMLElement( $svg );
|
||||
} catch ( \Exception $e ) {
|
||||
return $attachment_data;
|
||||
}
|
||||
|
||||
$src = $attachment_data['url'];
|
||||
$width = (int) $svg['width'];
|
||||
$height = (int) $svg['height'];
|
||||
|
||||
// Media Gallery
|
||||
$attachment_data['image'] = compact( 'src', 'width', 'height' );
|
||||
$attachment_data['thumb'] = compact( 'src', 'width', 'height' );
|
||||
|
||||
// Single Details of Image
|
||||
$attachment_data['sizes']['full'] = [
|
||||
'height' => $height,
|
||||
'width' => $width,
|
||||
'url' => $src,
|
||||
'orientation' => $height > $width ? 'portrait' : 'landscape',
|
||||
];
|
||||
return $attachment_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* set_attachment_id
|
||||
* @param $attachment_id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function set_attachment_id( $attachment_id ) {
|
||||
$this->attachment_id = $attachment_id;
|
||||
return $this->attachment_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_attachment_id
|
||||
* @return int
|
||||
*/
|
||||
public function get_attachment_id() {
|
||||
return $this->attachment_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle_upload_prefilter
|
||||
* @param $file
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle_upload_prefilter( $file ) {
|
||||
if ( ! $this->is_file_should_handled( $file ) ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$file = parent::handle_upload_prefilter( $file );
|
||||
|
||||
if ( ! $file['error'] && self::file_sanitizer_can_run() && ! $this->sanitize_svg( $file['tmp_name'] ) ) {
|
||||
$display_type = strtoupper( $this->get_file_type() );
|
||||
|
||||
$file['error'] = sprintf( __( 'Invalid %1$s Format, file not uploaded for security reasons', 'elementor' ), $display_type );
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
* @deprecated 3.0.0 Use Files_Upload_Handler::file_sanitizer_can_run() instead.
|
||||
*/
|
||||
public function svg_sanitizer_can_run() {
|
||||
_deprecated_function( __METHOD__, '3.0.0', 'Files_Upload_Handler::file_sanitizer_can_run()' );
|
||||
|
||||
return Files_Upload_Handler::file_sanitizer_can_run();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
* @deprecated 3.0.0
|
||||
*/
|
||||
public function upload_mimes() {
|
||||
_deprecated_function( __METHOD__, '3.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
* @deprecated 3.0.0
|
||||
*/
|
||||
public function wp_handle_upload_prefilter() {
|
||||
_deprecated_function( __METHOD__, '3.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
* @deprecated 3.0.0 Use Files_Upload_Handler::is_enabled() instead.
|
||||
* @see is_enabled()
|
||||
*/
|
||||
public function is_svg_uploads_enabled() {
|
||||
_deprecated_function( __METHOD__, '3.0.0', 'Files_Upload_Handler::is_enabled()' );
|
||||
|
||||
return Files_Upload_Handler::is_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Svg_Handler constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ], 10, 3 );
|
||||
add_action( 'elementor/core/files/clear_cache', [ $this, 'delete_meta_cache' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Base {
|
||||
|
||||
const UPLOADS_DIR = 'elementor/';
|
||||
|
||||
const DEFAULT_FILES_DIR = 'css/';
|
||||
|
||||
const META_KEY = '';
|
||||
|
||||
private static $wp_uploads_dir = [];
|
||||
|
||||
private $files_dir;
|
||||
|
||||
private $file_name;
|
||||
|
||||
/**
|
||||
* File path.
|
||||
*
|
||||
* Holds the file path.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* Holds the file content.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function get_base_uploads_dir() {
|
||||
$wp_upload_dir = self::get_wp_uploads_dir();
|
||||
|
||||
return $wp_upload_dir['basedir'] . '/' . self::UPLOADS_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function get_base_uploads_url() {
|
||||
$wp_upload_dir = self::get_wp_uploads_dir();
|
||||
|
||||
return $wp_upload_dir['baseurl'] . '/' . self::UPLOADS_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a create function for PhpDoc (@return static).
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function create() {
|
||||
return Plugin::$instance->files_manager->get( get_called_class(), func_get_args() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct( $file_name ) {
|
||||
/**
|
||||
* Elementor File Name
|
||||
*
|
||||
* Filters the File name
|
||||
*
|
||||
* @since 2.3.0
|
||||
*
|
||||
* @param string $file_name
|
||||
* @param object $this The file instance, which inherits Elementor\Core\Files
|
||||
*/
|
||||
$file_name = apply_filters( 'elementor/files/file_name', $file_name, $this );
|
||||
|
||||
$this->set_file_name( $file_name );
|
||||
|
||||
$this->set_files_dir( static::DEFAULT_FILES_DIR );
|
||||
|
||||
$this->set_path();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function set_files_dir( $files_dir ) {
|
||||
$this->files_dir = $files_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function set_file_name( $file_name ) {
|
||||
$this->file_name = $file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_file_name() {
|
||||
return $this->file_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_url() {
|
||||
$url = set_url_scheme( self::get_base_uploads_url() . $this->files_dir . $this->file_name );
|
||||
|
||||
return add_query_arg( [ 'ver' => $this->get_meta( 'time' ) ], $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_content() {
|
||||
if ( ! $this->content ) {
|
||||
$this->content = $this->parse_content();
|
||||
}
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function update() {
|
||||
$this->update_file();
|
||||
|
||||
$meta = $this->get_meta();
|
||||
|
||||
$meta['time'] = time();
|
||||
|
||||
$this->update_meta( $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function update_file() {
|
||||
$this->content = $this->parse_content();
|
||||
|
||||
if ( $this->content ) {
|
||||
$this->write();
|
||||
} else {
|
||||
$this->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function write() {
|
||||
return file_put_contents( $this->path, $this->content );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function delete() {
|
||||
if ( file_exists( $this->path ) ) {
|
||||
unlink( $this->path );
|
||||
}
|
||||
|
||||
$this->delete_meta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get meta data.
|
||||
*
|
||||
* Retrieve the CSS file meta data. Returns an array of all the data, or if
|
||||
* custom property is given it will return the property value, or `null` if
|
||||
* the property does not exist.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $property Optional. Custom meta data property. Default is
|
||||
* null.
|
||||
*
|
||||
* @return array|null An array of all the data, or if custom property is
|
||||
* given it will return the property value, or `null` if
|
||||
* the property does not exist.
|
||||
*/
|
||||
public function get_meta( $property = null ) {
|
||||
$meta = array_merge( $this->get_default_meta(), (array) $this->load_meta() );
|
||||
|
||||
if ( $property ) {
|
||||
return isset( $meta[ $property ] ) ? $meta[ $property ] : null;
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
* @abstract
|
||||
*/
|
||||
abstract protected function parse_content();
|
||||
|
||||
/**
|
||||
* Load meta.
|
||||
*
|
||||
* Retrieve the file meta data.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function load_meta() {
|
||||
return get_option( static::META_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update meta.
|
||||
*
|
||||
* Update the file meta data.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
*
|
||||
* @param array $meta New meta data.
|
||||
*/
|
||||
protected function update_meta( $meta ) {
|
||||
update_option( static::META_KEY, $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete meta.
|
||||
*
|
||||
* Delete the file meta data.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function delete_meta() {
|
||||
delete_option( static::META_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_default_meta() {
|
||||
return [
|
||||
'time' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access private
|
||||
* @static
|
||||
*/
|
||||
private static function get_wp_uploads_dir() {
|
||||
global $blog_id;
|
||||
if ( empty( self::$wp_uploads_dir[ $blog_id ] ) ) {
|
||||
self::$wp_uploads_dir[ $blog_id ] = wp_upload_dir( null, false );
|
||||
}
|
||||
|
||||
return self::$wp_uploads_dir[ $blog_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access private
|
||||
*/
|
||||
private function set_path() {
|
||||
$dir_path = self::get_base_uploads_dir() . $this->files_dir;
|
||||
|
||||
if ( ! is_dir( $dir_path ) ) {
|
||||
wp_mkdir_p( $dir_path );
|
||||
}
|
||||
|
||||
$this->path = $dir_path . $this->file_name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,902 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files\CSS;
|
||||
|
||||
use Elementor\Base_Data_Control;
|
||||
use Elementor\Control_Repeater;
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Controls_Stack;
|
||||
use Elementor\Core\Files\Base as Base_File;
|
||||
use Elementor\Core\DynamicTags\Manager;
|
||||
use Elementor\Core\DynamicTags\Tag;
|
||||
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
|
||||
use Elementor\Element_Base;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Core\Responsive\Responsive;
|
||||
use Elementor\Stylesheet;
|
||||
use Elementor\Icons_Manager;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor CSS file.
|
||||
*
|
||||
* Elementor CSS file handler class is responsible for generating CSS files.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @abstract
|
||||
*/
|
||||
abstract class Base extends Base_File {
|
||||
|
||||
/**
|
||||
* Elementor CSS file generated status.
|
||||
*
|
||||
* The parsing result after generating CSS file.
|
||||
*/
|
||||
const CSS_STATUS_FILE = 'file';
|
||||
|
||||
/**
|
||||
* Elementor inline CSS status.
|
||||
*
|
||||
* The parsing result after generating inline CSS.
|
||||
*/
|
||||
const CSS_STATUS_INLINE = 'inline';
|
||||
|
||||
/**
|
||||
* Elementor CSS empty status.
|
||||
*
|
||||
* The parsing result when an empty CSS returned.
|
||||
*/
|
||||
const CSS_STATUS_EMPTY = 'empty';
|
||||
|
||||
/**
|
||||
* Fonts.
|
||||
*
|
||||
* Holds the list of fonts.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $fonts = [];
|
||||
|
||||
private $icons_fonts = [];
|
||||
|
||||
private $dynamic_elements_ids = [];
|
||||
|
||||
/**
|
||||
* Stylesheet object.
|
||||
*
|
||||
* Holds the CSS file stylesheet instance.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @var Stylesheet
|
||||
*/
|
||||
protected $stylesheet_obj;
|
||||
|
||||
/**
|
||||
* Printed.
|
||||
*
|
||||
* Holds the list of printed files.
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $printed = [];
|
||||
|
||||
/**
|
||||
* Get CSS file name.
|
||||
*
|
||||
* Retrieve the CSS file name.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access public
|
||||
* @abstract
|
||||
*/
|
||||
abstract public function get_name();
|
||||
|
||||
protected function is_global_parsing_supported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use external file.
|
||||
*
|
||||
* Whether to use external CSS file of not. When there are new schemes or settings
|
||||
* updates.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access protected
|
||||
*
|
||||
* @return bool True if the CSS requires an update, False otherwise.
|
||||
*/
|
||||
protected function use_external_file() {
|
||||
return 'internal' !== get_option( 'elementor_css_print_method' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the CSS file.
|
||||
*
|
||||
* Delete old CSS, parse the CSS, save the new file and update the database.
|
||||
*
|
||||
* This method also sets the CSS status to be used later on in the render posses.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*/
|
||||
public function update() {
|
||||
$this->update_file();
|
||||
|
||||
$meta = $this->get_meta();
|
||||
|
||||
$meta['time'] = time();
|
||||
|
||||
$content = $this->get_content();
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
$meta['status'] = self::CSS_STATUS_EMPTY;
|
||||
$meta['css'] = '';
|
||||
} else {
|
||||
$use_external_file = $this->use_external_file();
|
||||
|
||||
if ( $use_external_file ) {
|
||||
$meta['status'] = self::CSS_STATUS_FILE;
|
||||
} else {
|
||||
$meta['status'] = self::CSS_STATUS_INLINE;
|
||||
$meta['css'] = $content;
|
||||
}
|
||||
}
|
||||
|
||||
$meta['dynamic_elements_ids'] = $this->dynamic_elements_ids;
|
||||
|
||||
$this->update_meta( $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function write() {
|
||||
if ( $this->use_external_file() ) {
|
||||
parent::write();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function delete() {
|
||||
if ( $this->use_external_file() ) {
|
||||
parent::delete();
|
||||
} else {
|
||||
$this->delete_meta();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue CSS.
|
||||
*
|
||||
* Either enqueue the CSS file in Elementor or add inline style.
|
||||
*
|
||||
* This method is also responsible for loading the fonts.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*/
|
||||
public function enqueue() {
|
||||
$handle_id = $this->get_file_handle_id();
|
||||
|
||||
if ( isset( self::$printed[ $handle_id ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$printed[ $handle_id ] = true;
|
||||
|
||||
$meta = $this->get_meta();
|
||||
|
||||
if ( self::CSS_STATUS_EMPTY === $meta['status'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First time after clear cache and etc.
|
||||
if ( '' === $meta['status'] || $this->is_update_required() ) {
|
||||
$this->update();
|
||||
|
||||
$meta = $this->get_meta();
|
||||
}
|
||||
|
||||
if ( self::CSS_STATUS_INLINE === $meta['status'] ) {
|
||||
$dep = $this->get_inline_dependency();
|
||||
// If the dependency has already been printed ( like a template in footer )
|
||||
if ( wp_styles()->query( $dep, 'done' ) ) {
|
||||
printf( '<style id="%1$s">%2$s</style>', $this->get_file_handle_id(), $meta['css'] ); // XSS ok.
|
||||
} else {
|
||||
wp_add_inline_style( $dep, $meta['css'] );
|
||||
}
|
||||
} elseif ( self::CSS_STATUS_FILE === $meta['status'] ) { // Re-check if it's not empty after CSS update.
|
||||
wp_enqueue_style( $this->get_file_handle_id(), $this->get_url(), $this->get_enqueue_dependencies(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
}
|
||||
|
||||
// Handle fonts.
|
||||
if ( ! empty( $meta['fonts'] ) ) {
|
||||
foreach ( $meta['fonts'] as $font ) {
|
||||
Plugin::$instance->frontend->enqueue_font( $font );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $meta['icons'] ) ) {
|
||||
$icons_types = Icons_Manager::get_icon_manager_tabs();
|
||||
foreach ( $meta['icons'] as $icon_font ) {
|
||||
if ( ! isset( $icons_types[ $icon_font ] ) ) {
|
||||
continue;
|
||||
}
|
||||
Plugin::$instance->frontend->enqueue_font( $icon_font );
|
||||
}
|
||||
}
|
||||
|
||||
$name = $this->get_name();
|
||||
|
||||
/**
|
||||
* Enqueue CSS file.
|
||||
*
|
||||
* Fires when CSS file is enqueued on Elementor.
|
||||
*
|
||||
* The dynamic portion of the hook name, `$name`, refers to the CSS file name.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param Base $this The current CSS file.
|
||||
*/
|
||||
do_action( "elementor/css-file/{$name}/enqueue", $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print CSS.
|
||||
*
|
||||
* Output the final CSS inside the `<style>` tags and all the frontend fonts in
|
||||
* use.
|
||||
*
|
||||
* @since 1.9.4
|
||||
* @access public
|
||||
*/
|
||||
public function print_css() {
|
||||
echo '<style>' . $this->get_content() . '</style>'; // XSS ok.
|
||||
Plugin::$instance->frontend->print_fonts_links();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add control rules.
|
||||
*
|
||||
* Parse the CSS for all the elements inside any given control.
|
||||
*
|
||||
* This method recursively renders the CSS for all the selectors in the control.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $control The controls.
|
||||
* @param array $controls_stack The controls stack.
|
||||
* @param callable $value_callback Callback function for the value.
|
||||
* @param array $placeholders Placeholders.
|
||||
* @param array $replacements Replacements.
|
||||
* @param array $values Global Values.
|
||||
*/
|
||||
public function add_control_rules( array $control, array $controls_stack, callable $value_callback, array $placeholders, array $replacements, array $values = [] ) {
|
||||
if ( empty( $control['selectors'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$control_global_key = $control['name'];
|
||||
|
||||
if ( ! empty( $control['groupType'] ) ) {
|
||||
$control_global_key = $control['groupPrefix'] . $control['groupType'];
|
||||
}
|
||||
|
||||
$global_values = [];
|
||||
$global_key = '';
|
||||
|
||||
if ( ! empty( $values['__globals__'] ) ) {
|
||||
$global_values = $values['__globals__'];
|
||||
}
|
||||
|
||||
if ( ! empty( $global_values[ $control_global_key ] ) ) {
|
||||
$global_key = $global_values[ $control_global_key ];
|
||||
}
|
||||
|
||||
if ( ! $global_key ) {
|
||||
$value = call_user_func( $value_callback, $control );
|
||||
|
||||
if ( null === $value ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$stylesheet = $this->get_stylesheet();
|
||||
|
||||
foreach ( $control['selectors'] as $selector => $css_property ) {
|
||||
$output_css_property = '';
|
||||
|
||||
if ( $global_key ) {
|
||||
$selector_global_value = $this->get_selector_global_value( $control, $global_key );
|
||||
|
||||
if ( $selector_global_value ) {
|
||||
$output_css_property = preg_replace( '/(:)[^;]+(;?)/', '$1' . $selector_global_value . '$2', $css_property );
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$output_css_property = preg_replace_callback( '/{{(?:([^.}]+)\.)?([^}| ]*)(?: *\|\| *(?:([^.}]+)\.)?([^}| ]*) *)*}}/', function( $matches ) use ( $control, $value_callback, $controls_stack, $value, $css_property ) {
|
||||
$external_control_missing = $matches[1] && ! isset( $controls_stack[ $matches[1] ] );
|
||||
|
||||
$parsed_value = '';
|
||||
|
||||
if ( ! $external_control_missing ) {
|
||||
$parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[2], $matches[1] );
|
||||
}
|
||||
|
||||
if ( '' === $parsed_value ) {
|
||||
if ( isset( $matches[4] ) ) {
|
||||
$parsed_value = $matches[4];
|
||||
|
||||
$is_string_value = preg_match( '/^([\'"])(.*)\1$/', $parsed_value, $string_matches );
|
||||
|
||||
if ( $is_string_value ) {
|
||||
$parsed_value = $string_matches[2];
|
||||
} elseif ( ! is_numeric( $parsed_value ) ) {
|
||||
if ( $matches[3] && ! isset( $controls_stack[ $matches[3] ] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parsed_value = $this->parse_property_placeholder( $control, $value, $controls_stack, $value_callback, $matches[4], $matches[3] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( '' === $parsed_value ) {
|
||||
if ( $external_control_missing ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
throw new \Exception();
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed_value;
|
||||
}, $css_property );
|
||||
} catch ( \Exception $e ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $output_css_property ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$device_pattern = '/^(?:\([^\)]+\)){1,2}/';
|
||||
|
||||
preg_match( $device_pattern, $selector, $device_rules );
|
||||
|
||||
$query = [];
|
||||
|
||||
if ( $device_rules ) {
|
||||
$selector = preg_replace( $device_pattern, '', $selector );
|
||||
|
||||
preg_match_all( '/\(([^)]+)\)/', $device_rules[0], $pure_device_rules );
|
||||
|
||||
$pure_device_rules = $pure_device_rules[1];
|
||||
|
||||
foreach ( $pure_device_rules as $device_rule ) {
|
||||
if ( Element_Base::RESPONSIVE_DESKTOP === $device_rule ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$device = preg_replace( '/\+$/', '', $device_rule );
|
||||
|
||||
$endpoint = $device === $device_rule ? 'max' : 'min';
|
||||
|
||||
$query[ $endpoint ] = $device;
|
||||
}
|
||||
}
|
||||
|
||||
$parsed_selector = str_replace( $placeholders, $replacements, $selector );
|
||||
|
||||
if ( ! $query && ! empty( $control['responsive'] ) ) {
|
||||
$query = array_intersect_key( $control['responsive'], array_flip( [ 'min', 'max' ] ) );
|
||||
|
||||
if ( ! empty( $query['max'] ) && Element_Base::RESPONSIVE_DESKTOP === $query['max'] ) {
|
||||
unset( $query['max'] );
|
||||
}
|
||||
}
|
||||
|
||||
$stylesheet->add_rules( $parsed_selector, $output_css_property, $query );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $control
|
||||
* @param mixed $value
|
||||
* @param array $controls_stack
|
||||
* @param callable $value_callback
|
||||
* @param string $placeholder
|
||||
* @param string $parser_control_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parse_property_placeholder( array $control, $value, array $controls_stack, $value_callback, $placeholder, $parser_control_name = null ) {
|
||||
if ( $parser_control_name ) {
|
||||
$control = $controls_stack[ $parser_control_name ];
|
||||
|
||||
$value = call_user_func( $value_callback, $control );
|
||||
}
|
||||
|
||||
if ( Controls_Manager::FONT === $control['type'] ) {
|
||||
$this->fonts[] = $value;
|
||||
}
|
||||
|
||||
/** @var Base_Data_Control $control_obj */
|
||||
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
|
||||
|
||||
return (string) $control_obj->get_style_value( $placeholder, $value, $control );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fonts.
|
||||
*
|
||||
* Retrieve the list of fonts.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access public
|
||||
*
|
||||
* @return array Fonts.
|
||||
*/
|
||||
public function get_fonts() {
|
||||
return $this->fonts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stylesheet.
|
||||
*
|
||||
* Retrieve the CSS file stylesheet instance.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*
|
||||
* @return Stylesheet The stylesheet object.
|
||||
*/
|
||||
public function get_stylesheet() {
|
||||
if ( ! $this->stylesheet_obj ) {
|
||||
$this->init_stylesheet();
|
||||
}
|
||||
|
||||
return $this->stylesheet_obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add controls stack style rules.
|
||||
*
|
||||
* Parse the CSS for all the elements inside any given controls stack.
|
||||
*
|
||||
* This method recursively renders the CSS for all the child elements in the stack.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access public
|
||||
*
|
||||
* @param Controls_Stack $controls_stack The controls stack.
|
||||
* @param array $controls Controls array.
|
||||
* @param array $values Values array.
|
||||
* @param array $placeholders Placeholders.
|
||||
* @param array $replacements Replacements.
|
||||
* @param array $all_controls All controls.
|
||||
*/
|
||||
public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) {
|
||||
if ( ! $all_controls ) {
|
||||
$all_controls = $controls_stack->get_controls();
|
||||
}
|
||||
|
||||
$parsed_dynamic_settings = $controls_stack->parse_dynamic_settings( $values, $controls );
|
||||
|
||||
foreach ( $controls as $control ) {
|
||||
if ( ! empty( $control['style_fields'] ) ) {
|
||||
$this->add_repeater_control_style_rules( $controls_stack, $control, $values[ $control['name'] ], $placeholders, $replacements );
|
||||
}
|
||||
|
||||
if ( ! empty( $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
|
||||
$this->add_dynamic_control_style_rules( $control, $control[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] );
|
||||
}
|
||||
|
||||
if ( Controls_Manager::ICONS === $control['type'] ) {
|
||||
$this->icons_fonts[] = $values[ $control['name'] ]['library'];
|
||||
}
|
||||
|
||||
if ( ! empty( $parsed_dynamic_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ) ) {
|
||||
// Dynamic CSS should not be added to the CSS files.
|
||||
// Instead it's handled by \Elementor\Core\DynamicTags\Dynamic_CSS
|
||||
// and printed in a style tag.
|
||||
unset( $parsed_dynamic_settings[ $control['name'] ] );
|
||||
|
||||
$this->dynamic_elements_ids[] = $controls_stack->get_id();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $control['selectors'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->add_control_style_rules( $control, $parsed_dynamic_settings, $all_controls, $placeholders, $replacements );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file handle ID.
|
||||
*
|
||||
* Retrieve the file handle ID.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
* @abstract
|
||||
*
|
||||
* @return string CSS file handle ID.
|
||||
*/
|
||||
abstract protected function get_file_handle_id();
|
||||
|
||||
/**
|
||||
* Render CSS.
|
||||
*
|
||||
* Parse the CSS.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
* @abstract
|
||||
*/
|
||||
abstract protected function render_css();
|
||||
|
||||
protected function get_default_meta() {
|
||||
return array_merge( parent::get_default_meta(), [
|
||||
'fonts' => array_unique( $this->fonts ),
|
||||
'icons' => array_unique( $this->icons_fonts ),
|
||||
'dynamic_elements_ids' => [],
|
||||
'status' => '',
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enqueue dependencies.
|
||||
*
|
||||
* Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array Name of the stylesheet.
|
||||
*/
|
||||
protected function get_enqueue_dependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inline dependency.
|
||||
*
|
||||
* Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string Name of the stylesheet.
|
||||
*/
|
||||
protected function get_inline_dependency() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is update required.
|
||||
*
|
||||
* Whether the CSS requires an update. When there are new schemes or settings
|
||||
* updates.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return bool True if the CSS requires an update, False otherwise.
|
||||
*/
|
||||
protected function is_update_required() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse CSS.
|
||||
*
|
||||
* Parsing the CSS file.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function parse_content() {
|
||||
$this->render_css();
|
||||
|
||||
$name = $this->get_name();
|
||||
|
||||
/**
|
||||
* Parse CSS file.
|
||||
*
|
||||
* Fires when CSS file is parsed on Elementor.
|
||||
*
|
||||
* The dynamic portion of the hook name, `$name`, refers to the CSS file name.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param Base $this The current CSS file.
|
||||
*/
|
||||
do_action( "elementor/css-file/{$name}/parse", $this );
|
||||
|
||||
return $this->get_stylesheet()->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add control style rules.
|
||||
*
|
||||
* Register new style rules for the control.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access private
|
||||
*
|
||||
* @param array $control The control.
|
||||
* @param array $values Values array.
|
||||
* @param array $controls The controls stack.
|
||||
* @param array $placeholders Placeholders.
|
||||
* @param array $replacements Replacements.
|
||||
*/
|
||||
protected function add_control_style_rules( array $control, array $values, array $controls, array $placeholders, array $replacements ) {
|
||||
$this->add_control_rules(
|
||||
$control, $controls, function( $control ) use ( $values ) {
|
||||
return $this->get_style_control_value( $control, $values );
|
||||
}, $placeholders, $replacements, $values
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get style control value.
|
||||
*
|
||||
* Retrieve the value of the style control for any give control and values.
|
||||
*
|
||||
* It will retrieve the control name and return the style value.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access private
|
||||
*
|
||||
* @param array $control The control.
|
||||
* @param array $values Values array.
|
||||
*
|
||||
* @return mixed Style control value.
|
||||
*/
|
||||
private function get_style_control_value( array $control, array $values ) {
|
||||
if ( ! empty( $values['__globals__'][ $control['name'] ] ) ) {
|
||||
// When the control itself has no global value, but it refers to another control global value
|
||||
return $this->get_selector_global_value( $control, $values['__globals__'][ $control['name'] ] );
|
||||
}
|
||||
|
||||
$value = $values[ $control['name'] ];
|
||||
|
||||
if ( isset( $control['selectors_dictionary'][ $value ] ) ) {
|
||||
$value = $control['selectors_dictionary'][ $value ];
|
||||
}
|
||||
|
||||
if ( ! is_numeric( $value ) && ! is_float( $value ) && empty( $value ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init stylesheet.
|
||||
*
|
||||
* Initialize CSS file stylesheet by creating a new `Stylesheet` object and register new
|
||||
* breakpoints for the stylesheet.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access private
|
||||
*/
|
||||
private function init_stylesheet() {
|
||||
$this->stylesheet_obj = new Stylesheet();
|
||||
|
||||
$breakpoints = Responsive::get_breakpoints();
|
||||
|
||||
$this->stylesheet_obj
|
||||
->add_device( 'mobile', 0 )
|
||||
->add_device( 'tablet', $breakpoints['md'] )
|
||||
->add_device( 'desktop', $breakpoints['lg'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add repeater control style rules.
|
||||
*
|
||||
* Register new style rules for the repeater control.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @param Controls_Stack $controls_stack The control stack.
|
||||
* @param array $repeater_control The repeater control.
|
||||
* @param array $repeater_values Repeater values array.
|
||||
* @param array $placeholders Placeholders.
|
||||
* @param array $replacements Replacements.
|
||||
*/
|
||||
protected function add_repeater_control_style_rules( Controls_Stack $controls_stack, array $repeater_control, array $repeater_values, array $placeholders, array $replacements ) {
|
||||
$placeholders = array_merge( $placeholders, [ '{{CURRENT_ITEM}}' ] );
|
||||
|
||||
foreach ( $repeater_control['style_fields'] as $index => $item ) {
|
||||
$this->add_controls_stack_style_rules(
|
||||
$controls_stack,
|
||||
$item,
|
||||
$repeater_values[ $index ],
|
||||
$placeholders,
|
||||
array_merge( $replacements, [ '.elementor-repeater-item-' . $repeater_values[ $index ]['_id'] ] ),
|
||||
$repeater_control['fields']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dynamic control style rules.
|
||||
*
|
||||
* Register new style rules for the dynamic control.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @param array $control The control.
|
||||
* @param string $value The value.
|
||||
*/
|
||||
protected function add_dynamic_control_style_rules( array $control, $value ) {
|
||||
Plugin::$instance->dynamic_tags->parse_tags_text( $value, $control, function( $id, $name, $settings ) {
|
||||
$tag = Plugin::$instance->dynamic_tags->create_tag( $id, $name, $settings );
|
||||
|
||||
if ( ! $tag instanceof Tag ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->add_controls_stack_style_rules( $tag, $this->get_style_controls( $tag ), $tag->get_active_settings(), [ '{{WRAPPER}}' ], [ '#elementor-tag-' . $id ] );
|
||||
} );
|
||||
}
|
||||
|
||||
private function get_selector_global_value( $control, $global_key ) {
|
||||
$data = Plugin::$instance->data_manager->run( $global_key );
|
||||
|
||||
if ( empty( $data['value'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$global_args = explode( '?id=', $global_key );
|
||||
|
||||
$id = $global_args[1];
|
||||
|
||||
if ( ! empty( $control['groupType'] ) ) {
|
||||
$property_name = str_replace( [ $control['groupPrefix'], '_tablet', '_mobile' ], '', $control['name'] );
|
||||
|
||||
// TODO: This check won't retrieve the proper answer for array values (multiple controls).
|
||||
if ( empty( $data['value'][ Global_Typography::TYPOGRAPHY_GROUP_PREFIX . $property_name ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$property_name = str_replace( '_', '-', $property_name );
|
||||
|
||||
$value = "var( --e-global-$control[groupType]-$id-$property_name )";
|
||||
} else {
|
||||
$value = "var( --e-global-$control[type]-$id )";
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
final protected function get_active_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) {
|
||||
if ( ! $controls ) {
|
||||
$controls = $controls_stack->get_controls();
|
||||
}
|
||||
|
||||
if ( ! $settings ) {
|
||||
$settings = $controls_stack->get_controls_settings();
|
||||
}
|
||||
|
||||
if ( $this->is_global_parsing_supported() ) {
|
||||
$settings = $this->parse_global_settings( $settings, $controls );
|
||||
}
|
||||
|
||||
$active_controls = array_reduce(
|
||||
array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls_stack, $controls, $settings ) {
|
||||
$control = $controls[ $control_key ];
|
||||
|
||||
if ( $controls_stack->is_control_visible( $control, $settings ) ) {
|
||||
$active_controls[ $control_key ] = $control;
|
||||
}
|
||||
|
||||
return $active_controls;
|
||||
}, []
|
||||
);
|
||||
|
||||
return $active_controls;
|
||||
}
|
||||
|
||||
final public function get_style_controls( Controls_Stack $controls_stack, array $controls = null, array $settings = null ) {
|
||||
$controls = $this->get_active_controls( $controls_stack, $controls, $settings );
|
||||
|
||||
$style_controls = [];
|
||||
|
||||
foreach ( $controls as $control_name => $control ) {
|
||||
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
|
||||
|
||||
if ( ! $control_obj instanceof Base_Data_Control ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$control = array_merge( $control_obj->get_settings(), $control );
|
||||
|
||||
if ( $control_obj instanceof Control_Repeater ) {
|
||||
$style_fields = [];
|
||||
|
||||
foreach ( $controls_stack->get_settings( $control_name ) as $item ) {
|
||||
$style_fields[] = $this->get_style_controls( $controls_stack, $control['fields'], $item );
|
||||
}
|
||||
|
||||
$control['style_fields'] = $style_fields;
|
||||
}
|
||||
|
||||
if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || $this->is_global_control( $controls_stack, $control_name, $controls ) || ! empty( $control['style_fields'] ) ) {
|
||||
$style_controls[ $control_name ] = $control;
|
||||
}
|
||||
}
|
||||
|
||||
return $style_controls;
|
||||
}
|
||||
|
||||
private function parse_global_settings( array $settings, array $controls ) {
|
||||
foreach ( $controls as $control ) {
|
||||
$control_name = $control['name'];
|
||||
$control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] );
|
||||
|
||||
if ( ! $control_obj instanceof Base_Data_Control ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $control_obj instanceof Control_Repeater ) {
|
||||
foreach ( $settings[ $control_name ] as & $field ) {
|
||||
$field = $this->parse_global_settings( $field, $control['fields'] );
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $control['global']['active'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $settings['__globals__'][ $control_name ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$settings[ $control_name ] = 'global';
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function is_global_control( Controls_Stack $controls_stack, $control_name, $controls ) {
|
||||
$control = $controls[ $control_name ];
|
||||
|
||||
$control_global_key = $control_name;
|
||||
|
||||
if ( ! empty( $control['groupType'] ) ) {
|
||||
$control_global_key = $control['groupPrefix'] . $control['groupType'];
|
||||
}
|
||||
|
||||
if ( empty( $controls[ $control_global_key ]['global']['active'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$globals = $controls_stack->get_settings( '__globals__' );
|
||||
|
||||
return ! empty( $globals[ $control_global_key ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files\CSS;
|
||||
|
||||
use Elementor\Core\Kits\Manager;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Settings;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor global CSS file.
|
||||
*
|
||||
* Elementor CSS file handler class is responsible for generating the global CSS
|
||||
* file.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class Global_CSS extends Base {
|
||||
|
||||
/**
|
||||
* Elementor global CSS file handler ID.
|
||||
*/
|
||||
const FILE_HANDLER_ID = 'elementor-global';
|
||||
|
||||
const META_KEY = '_elementor_global_css';
|
||||
|
||||
/**
|
||||
* Get CSS file name.
|
||||
*
|
||||
* Retrieve the CSS file name.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access public
|
||||
*
|
||||
* @return string CSS file name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'global';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file handle ID.
|
||||
*
|
||||
* Retrieve the handle ID for the global post CSS file.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string CSS file handle ID.
|
||||
*/
|
||||
protected function get_file_handle_id() {
|
||||
return self::FILE_HANDLER_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render CSS.
|
||||
*
|
||||
* Parse the CSS for all the widgets and all the scheme controls.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function render_css() {
|
||||
$this->render_schemes_and_globals_css();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inline dependency.
|
||||
*
|
||||
* Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string Name of the stylesheet.
|
||||
*/
|
||||
protected function get_inline_dependency() {
|
||||
return 'elementor-frontend';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is update required.
|
||||
*
|
||||
* Whether the CSS requires an update. When there are new schemes or settings
|
||||
* updates.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return bool True if the CSS requires an update, False otherwise.
|
||||
*/
|
||||
protected function is_update_required() {
|
||||
return $this->get_meta( 'time' ) < get_option( Settings::UPDATE_TIME_FIELD );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render schemes CSS.
|
||||
*
|
||||
* Parse the CSS for all the widgets and all the scheme controls.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access private
|
||||
*/
|
||||
private function render_schemes_and_globals_css() {
|
||||
$elementor = Plugin::$instance;
|
||||
|
||||
/** @var Manager $module */
|
||||
$kits_manager = Plugin::$instance->kits_manager;
|
||||
$custom_colors_enabled = $kits_manager->is_custom_colors_enabled();
|
||||
$custom_typography_enabled = $kits_manager->is_custom_typography_enabled();
|
||||
|
||||
// If both default colors and typography are disabled, there is no need to render schemes and default global css.
|
||||
if ( ! $custom_colors_enabled && ! $custom_typography_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $elementor->widgets_manager->get_widget_types() as $widget ) {
|
||||
$controls = $widget->get_controls();
|
||||
|
||||
$global_controls = [];
|
||||
|
||||
$global_values['__globals__'] = [];
|
||||
|
||||
foreach ( $controls as $control ) {
|
||||
$is_color_control = 'color' === $control['type'];
|
||||
$is_typography_control = isset( $control['groupType'] ) && 'typography' === $control['groupType'];
|
||||
|
||||
// If it is a color/typography control and default colors/typography are disabled,
|
||||
// don't add the default CSS.
|
||||
if ( ( $is_color_control && ! $custom_colors_enabled ) || ( $is_typography_control && ! $custom_typography_enabled ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$global_control = $control;
|
||||
|
||||
// Handle group controls that don't have a default global property.
|
||||
if ( ! empty( $control['groupType'] ) ) {
|
||||
$global_control = $controls[ $control['groupPrefix'] . $control['groupType'] ];
|
||||
}
|
||||
|
||||
// If the control has a default global defined, add it to the globals array
|
||||
// that is used in add_control_rules.
|
||||
if ( ! empty( $control['global']['default'] ) ) {
|
||||
$global_values['__globals__'][ $control['name'] ] = $global_control['global']['default'];
|
||||
}
|
||||
|
||||
if ( ! empty( $global_control['global']['default'] ) ) {
|
||||
$global_controls[] = $control;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $global_controls as $control ) {
|
||||
$this->add_control_rules( $control, $controls, function( $control ) {}, [ '{{WRAPPER}}' ], [ '.elementor-widget-' . $widget->get_name() ], $global_values );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Files\CSS;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Post_Local_Cache extends Post {
|
||||
|
||||
/**
|
||||
* Meta cache
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $meta_cache = [];
|
||||
|
||||
abstract protected function get_post_id_for_data();
|
||||
|
||||
public function is_update_required() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function load_meta() {
|
||||
return $this->meta_cache;
|
||||
}
|
||||
|
||||
protected function delete_meta() {
|
||||
$this->meta_cache = [];
|
||||
}
|
||||
|
||||
protected function update_meta( $meta ) {
|
||||
$this->meta_cache = $meta;
|
||||
}
|
||||
|
||||
protected function get_data() {
|
||||
$document = Plugin::$instance->documents->get( $this->get_post_id_for_data() );
|
||||
|
||||
return $document ? $document->get_elements_data() : [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files\CSS;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor post preview CSS file.
|
||||
*
|
||||
* Elementor CSS file handler class is responsible for generating the post
|
||||
* preview CSS file.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*/
|
||||
class Post_Preview extends Post_Local_Cache {
|
||||
|
||||
/**
|
||||
* Preview ID.
|
||||
*
|
||||
* Holds the ID of the current post being previewed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $post_id_for_data;
|
||||
|
||||
/**
|
||||
* Post preview CSS file constructor.
|
||||
*
|
||||
* Initializing the CSS file of the post preview. Set the post ID and the
|
||||
* parent ID and initiate the stylesheet.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*/
|
||||
public function __construct( $post_id ) {
|
||||
$this->post_id_for_data = $post_id;
|
||||
|
||||
$parent_id = wp_get_post_parent_id( $post_id );
|
||||
|
||||
parent::__construct( $parent_id );
|
||||
}
|
||||
|
||||
protected function get_post_id_for_data() {
|
||||
return $this->post_id_for_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
* @deprecated 3.0.0 Use `Post_Preview::get_post_id_for_data()` instead
|
||||
*/
|
||||
protected function get_preview_id() {
|
||||
_deprecated_function( __METHOD__, '3.0.0', __CLASS__ . '::get_post_id_for_data()' );
|
||||
|
||||
return $this->get_post_id_for_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file handle ID.
|
||||
*
|
||||
* Retrieve the handle ID for the previewed post CSS file.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string CSS file handle ID.
|
||||
*/
|
||||
protected function get_file_handle_id() {
|
||||
return 'elementor-preview-' . $this->get_post_id_for_data();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files\CSS;
|
||||
|
||||
use Elementor\Base_Data_Control;
|
||||
use Elementor\Control_Repeater;
|
||||
use Elementor\Controls_Stack;
|
||||
use Elementor\Element_Base;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor post CSS file.
|
||||
*
|
||||
* Elementor CSS file handler class is responsible for generating the single
|
||||
* post CSS file.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class Post extends Base {
|
||||
|
||||
/**
|
||||
* Elementor post CSS file prefix.
|
||||
*/
|
||||
const FILE_PREFIX = 'post-';
|
||||
|
||||
const META_KEY = '_elementor_css';
|
||||
|
||||
/**
|
||||
* Post ID.
|
||||
*
|
||||
* Holds the current post ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $post_id;
|
||||
|
||||
protected function is_global_parsing_supported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post CSS file constructor.
|
||||
*
|
||||
* Initializing the CSS file of the post. Set the post ID and initiate the stylesheet.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
*/
|
||||
public function __construct( $post_id ) {
|
||||
$this->post_id = $post_id;
|
||||
|
||||
parent::__construct( static::FILE_PREFIX . $post_id . '.css' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS file name.
|
||||
*
|
||||
* Retrieve the CSS file name.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access public
|
||||
*
|
||||
* @return string CSS file name.
|
||||
*/
|
||||
public function get_name() {
|
||||
return 'post';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post ID.
|
||||
*
|
||||
* Retrieve the ID of current post.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*
|
||||
* @return int Post ID.
|
||||
*/
|
||||
public function get_post_id() {
|
||||
return $this->post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique element selector.
|
||||
*
|
||||
* Retrieve the unique selector for any given element.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*
|
||||
* @param Element_Base $element The element.
|
||||
*
|
||||
* @return string Unique element selector.
|
||||
*/
|
||||
public function get_element_unique_selector( Element_Base $element ) {
|
||||
return '.elementor-' . $this->post_id . ' .elementor-element' . $element->get_unique_selector();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load meta data.
|
||||
*
|
||||
* Retrieve the post CSS file meta data.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array Post CSS file meta data.
|
||||
*/
|
||||
protected function load_meta() {
|
||||
return get_post_meta( $this->post_id, static::META_KEY, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update meta data.
|
||||
*
|
||||
* Update the global CSS file meta data.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @param array $meta New meta data.
|
||||
*/
|
||||
protected function update_meta( $meta ) {
|
||||
update_post_meta( $this->post_id, static::META_KEY, $meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete meta.
|
||||
*
|
||||
* Delete the file meta data.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function delete_meta() {
|
||||
delete_post_meta( $this->post_id, static::META_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post data.
|
||||
*
|
||||
* Retrieve raw post data from the database.
|
||||
*
|
||||
* @since 1.9.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array Post data.
|
||||
*/
|
||||
protected function get_data() {
|
||||
$document = Plugin::$instance->documents->get( $this->post_id );
|
||||
return $document ? $document->get_elements_data() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render CSS.
|
||||
*
|
||||
* Parse the CSS for all the elements.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function render_css() {
|
||||
$data = $this->get_data();
|
||||
|
||||
if ( ! empty( $data ) ) {
|
||||
foreach ( $data as $element_data ) {
|
||||
$element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
|
||||
|
||||
if ( ! $element ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->render_styles( $element );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue CSS.
|
||||
*
|
||||
* Enqueue the post CSS file in Elementor.
|
||||
*
|
||||
* This method ensures that the post was actually built with elementor before
|
||||
* enqueueing the post CSS file.
|
||||
*
|
||||
* @since 1.2.2
|
||||
* @access public
|
||||
*/
|
||||
public function enqueue() {
|
||||
if ( ! Plugin::$instance->db->is_built_with_elementor( $this->post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::enqueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add controls-stack style rules.
|
||||
*
|
||||
* Parse the CSS for all the elements inside any given controls stack.
|
||||
*
|
||||
* This method recursively renders the CSS for all the child elements in the stack.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access public
|
||||
*
|
||||
* @param Controls_Stack $controls_stack The controls stack.
|
||||
* @param array $controls Controls array.
|
||||
* @param array $values Values array.
|
||||
* @param array $placeholders Placeholders.
|
||||
* @param array $replacements Replacements.
|
||||
* @param array $all_controls All controls.
|
||||
*/
|
||||
public function add_controls_stack_style_rules( Controls_Stack $controls_stack, array $controls, array $values, array $placeholders, array $replacements, array $all_controls = null ) {
|
||||
parent::add_controls_stack_style_rules( $controls_stack, $controls, $values, $placeholders, $replacements, $all_controls );
|
||||
|
||||
if ( $controls_stack instanceof Element_Base ) {
|
||||
foreach ( $controls_stack->get_children() as $child_element ) {
|
||||
$this->render_styles( $child_element );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enqueue dependencies.
|
||||
*
|
||||
* Retrieve the name of the stylesheet used by `wp_enqueue_style()`.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array Name of the stylesheet.
|
||||
*/
|
||||
protected function get_enqueue_dependencies() {
|
||||
return [ 'elementor-frontend' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inline dependency.
|
||||
*
|
||||
* Retrieve the name of the stylesheet used by `wp_add_inline_style()`.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string Name of the stylesheet.
|
||||
*/
|
||||
protected function get_inline_dependency() {
|
||||
return 'elementor-frontend';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file handle ID.
|
||||
*
|
||||
* Retrieve the handle ID for the post CSS file.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string CSS file handle ID.
|
||||
*/
|
||||
protected function get_file_handle_id() {
|
||||
return 'elementor-post-' . $this->post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render styles.
|
||||
*
|
||||
* Parse the CSS for any given element.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access protected
|
||||
*
|
||||
* @param Element_Base $element The element.
|
||||
*/
|
||||
protected function render_styles( Element_Base $element ) {
|
||||
/**
|
||||
* Before element parse CSS.
|
||||
*
|
||||
* Fires before the CSS of the element is parsed.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param Post $this The post CSS file.
|
||||
* @param Element_Base $element The element.
|
||||
*/
|
||||
do_action( 'elementor/element/before_parse_css', $this, $element );
|
||||
|
||||
$element_settings = $element->get_settings();
|
||||
|
||||
$this->add_controls_stack_style_rules( $element, $this->get_style_controls( $element, null, $element->get_parsed_dynamic_settings() ), $element_settings, [ '{{ID}}', '{{WRAPPER}}' ], [ $element->get_id(), $this->get_element_unique_selector( $element ) ] );
|
||||
|
||||
/**
|
||||
* After element parse CSS.
|
||||
*
|
||||
* Fires after the CSS of the element is parsed.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*
|
||||
* @param Post $this The post CSS file.
|
||||
* @param Element_Base $element The element.
|
||||
*/
|
||||
do_action( 'elementor/element/parse_css', $this, $element );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Files;
|
||||
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Core\Files\Assets\Files_Upload_Handler;
|
||||
use Elementor\Core\Files\Assets\Json\Json_Handler;
|
||||
use Elementor\Core\Files\Assets\Svg\Svg_Handler;
|
||||
use Elementor\Core\Files\CSS\Global_CSS;
|
||||
use Elementor\Core\Files\CSS\Post as Post_CSS;
|
||||
use Elementor\Core\Responsive\Files\Frontend;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor files manager.
|
||||
*
|
||||
* Elementor files manager handler class is responsible for creating files.
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
class Manager {
|
||||
|
||||
private $files = [];
|
||||
|
||||
/**
|
||||
* Files manager constructor.
|
||||
*
|
||||
* Initializing the Elementor files manager.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->register_actions();
|
||||
|
||||
new Svg_Handler();
|
||||
new Json_Handler();
|
||||
}
|
||||
|
||||
public function get( $class, $args ) {
|
||||
$id = $class . '-' . wp_json_encode( $args );
|
||||
|
||||
if ( ! isset( $this->files[ $id ] ) ) {
|
||||
// Create an instance from dynamic args length.
|
||||
$reflection_class = new \ReflectionClass( $class );
|
||||
$this->files[ $id ] = $reflection_class->newInstanceArgs( $args );
|
||||
}
|
||||
|
||||
return $this->files[ $id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* On post delete.
|
||||
*
|
||||
* Delete post CSS immediately after a post is deleted from the database.
|
||||
*
|
||||
* Fired by `deleted_post` action.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $post_id Post ID.
|
||||
*/
|
||||
public function on_delete_post( $post_id ) {
|
||||
if ( ! Utils::is_post_support( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$css_file = Post_CSS::create( $post_id );
|
||||
|
||||
$css_file->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* On export post meta.
|
||||
*
|
||||
* When exporting data using WXR, skip post CSS file meta key. This way the
|
||||
* export won't contain the post CSS file data used by Elementor.
|
||||
*
|
||||
* Fired by `wxr_export_skip_postmeta` filter.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*
|
||||
* @param bool $skip Whether to skip the current post meta.
|
||||
* @param string $meta_key Current meta key.
|
||||
*
|
||||
* @return bool Whether to skip the post CSS meta.
|
||||
*/
|
||||
public function on_export_post_meta( $skip, $meta_key ) {
|
||||
if ( Post_CSS::META_KEY === $meta_key ) {
|
||||
$skip = true;
|
||||
}
|
||||
|
||||
return $skip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cache.
|
||||
*
|
||||
* Delete all meta containing files data. And delete the actual
|
||||
* files from the upload directory.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access public
|
||||
*/
|
||||
public function clear_cache() {
|
||||
delete_post_meta_by_key( Post_CSS::META_KEY );
|
||||
|
||||
delete_option( Global_CSS::META_KEY );
|
||||
|
||||
delete_option( Frontend::META_KEY );
|
||||
|
||||
// Delete files.
|
||||
$path = Base::get_base_uploads_dir() . Base::DEFAULT_FILES_DIR . '*';
|
||||
|
||||
foreach ( glob( $path ) as $file_path ) {
|
||||
unlink( $file_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor clear files.
|
||||
*
|
||||
* Fires after Elementor clears files
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
do_action( 'elementor/core/files/clear_cache' );
|
||||
}
|
||||
|
||||
public function register_ajax_actions( Ajax $ajax ) {
|
||||
$ajax->register_ajax_action( 'enable_unfiltered_files_upload', [ $this, 'ajax_unfiltered_files_upload' ] );
|
||||
}
|
||||
|
||||
public function ajax_unfiltered_files_upload() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( Files_Upload_Handler::OPTION_KEY, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register actions.
|
||||
*
|
||||
* Register filters and actions for the files manager.
|
||||
*
|
||||
* @since 1.2.0
|
||||
* @access private
|
||||
*/
|
||||
private function register_actions() {
|
||||
add_action( 'deleted_post', [ $this, 'on_delete_post' ] );
|
||||
|
||||
// Ajax.
|
||||
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
|
||||
|
||||
add_filter( 'wxr_export_skip_postmeta', [ $this, 'on_export_post_meta' ], 10, 2 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Frontend;
|
||||
|
||||
use Elementor\Core\Frontend\RenderModes\Render_Mode_Base;
|
||||
use Elementor\Core\Frontend\RenderModes\Render_Mode_Normal;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Render_Mode_Manager {
|
||||
const QUERY_STRING_PARAM_NAME = 'render_mode';
|
||||
const QUERY_STRING_POST_ID = 'post_id';
|
||||
const QUERY_STRING_NONCE_PARAM_NAME = 'render_mode_nonce';
|
||||
const NONCE_ACTION_PATTERN = 'render_mode_{post_id}';
|
||||
|
||||
/**
|
||||
* @var Render_Mode_Base
|
||||
*/
|
||||
private $current;
|
||||
|
||||
/**
|
||||
* @var Render_Mode_Base[]
|
||||
*/
|
||||
private $render_modes = [];
|
||||
|
||||
/**
|
||||
* @param $post_id
|
||||
* @param $render_mode_name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_base_url( $post_id, $render_mode_name ) {
|
||||
return add_query_arg( [
|
||||
self::QUERY_STRING_POST_ID => $post_id,
|
||||
self::QUERY_STRING_PARAM_NAME => $render_mode_name,
|
||||
self::QUERY_STRING_NONCE_PARAM_NAME => wp_create_nonce( self::get_nonce_action( $post_id ) ),
|
||||
'ver' => time(),
|
||||
], get_permalink( $post_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $post_id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_nonce_action( $post_id ) {
|
||||
return str_replace( '{post_id}', $post_id, self::NONCE_ACTION_PATTERN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new render mode into the render mode manager.
|
||||
*
|
||||
* @param $class_name
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function register_render_mode( $class_name ) {
|
||||
if ( ! is_subclass_of( $class_name, Render_Mode_Base::class ) ) {
|
||||
throw new \Exception( "'{$class_name}' must extends 'Render_Mode_Base'" );
|
||||
}
|
||||
|
||||
$this->render_modes[ $class_name::get_name() ] = $class_name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current render mode.
|
||||
*
|
||||
* @return Render_Mode_Base
|
||||
*/
|
||||
public function get_current() {
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set render mode.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
private function set_current_render_mode() {
|
||||
$post_id = null;
|
||||
$key = null;
|
||||
$nonce = null;
|
||||
|
||||
if ( isset( $_GET[ self::QUERY_STRING_POST_ID ] ) ) {
|
||||
$post_id = $_GET[ self::QUERY_STRING_POST_ID ]; // phpcs:ignore -- Nonce will be checked next line.
|
||||
}
|
||||
|
||||
if ( isset( $_GET[ self::QUERY_STRING_NONCE_PARAM_NAME ] ) ) {
|
||||
$nonce = $_GET[ self::QUERY_STRING_NONCE_PARAM_NAME ]; // phpcs:ignore -- Nonce will be checked next line.
|
||||
}
|
||||
|
||||
if ( isset( $_GET[ self::QUERY_STRING_PARAM_NAME ] ) ) {
|
||||
$key = $_GET[ self::QUERY_STRING_PARAM_NAME ]; // phpcs:ignore -- Nonce will be checked next line.
|
||||
}
|
||||
|
||||
if (
|
||||
$post_id &&
|
||||
$nonce &&
|
||||
wp_verify_nonce( $nonce, self::get_nonce_action( $post_id ) ) &&
|
||||
$key &&
|
||||
array_key_exists( $key, $this->render_modes )
|
||||
) {
|
||||
$this->current = new $this->render_modes[ $key ]( $post_id );
|
||||
} else {
|
||||
$this->current = new Render_Mode_Normal( $post_id );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add actions base on the current render.
|
||||
*
|
||||
* @throws \Requests_Exception_HTTP_403
|
||||
*/
|
||||
private function add_current_actions() {
|
||||
if ( ! $this->current->get_permissions_callback() ) {
|
||||
throw new \Requests_Exception_HTTP_403();
|
||||
}
|
||||
|
||||
// Run when 'template-redirect' actually because the the class is instantiate when 'template-redirect' run.
|
||||
$this->current->prepare_render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render_Mode_Manager constructor.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->register_render_mode( Render_Mode_Normal::class );
|
||||
|
||||
do_action( 'elementor/frontend/render_mode/register', $this );
|
||||
|
||||
$this->set_current_render_mode();
|
||||
$this->add_current_actions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Frontend\RenderModes;
|
||||
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Core\Base\Document;
|
||||
use Elementor\Core\Frontend\Render_Mode_Manager;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Render_Mode_Base {
|
||||
const ENQUEUE_SCRIPTS_PRIORITY = 10;
|
||||
const ENQUEUE_STYLES_PRIORITY = 10;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $post_id;
|
||||
|
||||
/**
|
||||
* @var Document
|
||||
*/
|
||||
protected $document;
|
||||
|
||||
/**
|
||||
* Render_Mode_Base constructor.
|
||||
*
|
||||
* @param $post_id
|
||||
*/
|
||||
public function __construct( $post_id ) {
|
||||
$this->post_id = intval( $post_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key name of the class.
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function get_name() {
|
||||
throw new \Exception( 'You must implements `get_name` static method in ' . static::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $post_id
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function get_url( $post_id ) {
|
||||
return Render_Mode_Manager::get_base_url( $post_id, static::get_name() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs before the render, by default load scripts and styles.
|
||||
*/
|
||||
public function prepare_render() {
|
||||
add_action( 'wp_enqueue_scripts', function () {
|
||||
$this->enqueue_scripts();
|
||||
}, static::ENQUEUE_SCRIPTS_PRIORITY );
|
||||
|
||||
add_action( 'wp_enqueue_scripts', function () {
|
||||
$this->enqueue_styles();
|
||||
}, static::ENQUEUE_STYLES_PRIORITY );
|
||||
}
|
||||
|
||||
/**
|
||||
* By default do not do anything.
|
||||
*/
|
||||
protected function enqueue_scripts() {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* By default do not do anything.
|
||||
*/
|
||||
protected function enqueue_styles() {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user has permissions for the current render mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_permissions_callback() {
|
||||
return $this->get_document()->is_editable_by_current_user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current render mode is static render, By default returns false.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_static() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Document
|
||||
*/
|
||||
public function get_document() {
|
||||
if ( ! $this->document ) {
|
||||
$this->document = Plugin::$instance->documents->get( $this->post_id );
|
||||
}
|
||||
|
||||
return $this->document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Frontend\RenderModes;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Render_Mode_Normal extends Render_Mode_Base {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return 'normal';
|
||||
}
|
||||
|
||||
/**
|
||||
* Anyone can access the normal render mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_permissions_callback() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Kits\Controls;
|
||||
|
||||
use Elementor\Control_Repeater;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Repeater extends Control_Repeater {
|
||||
|
||||
const CONTROL_TYPE = 'global-style-repeater';
|
||||
|
||||
/**
|
||||
* Get control type.
|
||||
*
|
||||
* Retrieve the control type, in this case `global-style-repeater`.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @access public
|
||||
*
|
||||
* @return string Control type.
|
||||
*/
|
||||
public function get_type() {
|
||||
return self::CONTROL_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get repeater control default settings.
|
||||
*
|
||||
* Retrieve the default settings of the repeater control. Used to return the
|
||||
* default settings while initializing the repeater control.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @access protected
|
||||
*
|
||||
* @return array Control default settings.
|
||||
*/
|
||||
protected function get_default_settings() {
|
||||
$settings = parent::get_default_settings();
|
||||
|
||||
$settings['item_actions']['duplicate'] = false;
|
||||
$settings['item_actions']['sort'] = false;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render repeater control output in the editor.
|
||||
*
|
||||
* Used to generate the control HTML in the editor using Underscore JS
|
||||
* template. The variables for the class are available using `data` JS
|
||||
* object.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function content_template() {
|
||||
?>
|
||||
<div class="elementor-repeater-fields-wrapper"></div>
|
||||
<# if ( itemActions.add ) { #>
|
||||
<div class="elementor-button-wrapper">
|
||||
<button class="elementor-button elementor-button-default elementor-repeater-add" type="button">
|
||||
<i class="eicon-plus" aria-hidden="true"></i><span class="elementor-repeater__add-button__text">{{{ addButtonText }}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<# } #>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Kits\Documents;
|
||||
|
||||
use Elementor\Core\DocumentTypes\PageBase;
|
||||
use Elementor\Core\Files\CSS\Post as Post_CSS;
|
||||
use Elementor\Core\Kits\Documents\Tabs;
|
||||
use Elementor\Core\Settings\Manager as SettingsManager;
|
||||
use Elementor\Core\Settings\Page\Manager as PageManager;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Kit extends PageBase {
|
||||
/**
|
||||
* @var Tabs\Tab_Base[]
|
||||
*/
|
||||
private $tabs;
|
||||
|
||||
public function __construct( array $data = [] ) {
|
||||
parent::__construct( $data );
|
||||
|
||||
$this->tabs = [
|
||||
'global-colors' => new Tabs\Global_Colors( $this ),
|
||||
'global-typography' => new Tabs\Global_Typography( $this ),
|
||||
'theme-style-typography' => new Tabs\Theme_Style_Typography( $this ),
|
||||
'theme-style-buttons' => new Tabs\Theme_Style_Buttons( $this ),
|
||||
'theme-style-images' => new Tabs\Theme_Style_Images( $this ),
|
||||
'theme-style-form-fields' => new Tabs\Theme_Style_Form_Fields( $this ),
|
||||
'settings-site-identity' => new Tabs\Settings_Site_Identity( $this ),
|
||||
'settings-background' => new Tabs\Settings_Background( $this ),
|
||||
'settings-layout' => new Tabs\Settings_Layout( $this ),
|
||||
'settings-lightbox' => new Tabs\Settings_Lightbox( $this ),
|
||||
'settings-custom-css' => new Tabs\Settings_Custom_CSS( $this ),
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_properties() {
|
||||
$properties = parent::get_properties();
|
||||
|
||||
$properties['has_elements'] = false;
|
||||
$properties['show_in_finder'] = false;
|
||||
$properties['show_on_admin_bar'] = false;
|
||||
$properties['edit_capability'] = 'edit_theme_options';
|
||||
$properties['support_kit'] = true;
|
||||
|
||||
return $properties;
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return 'kit';
|
||||
}
|
||||
|
||||
public static function get_title() {
|
||||
return __( 'Kit', 'elementor' );
|
||||
}
|
||||
|
||||
protected function get_have_a_look_url() {
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function get_editor_panel_config() {
|
||||
$config = parent::get_editor_panel_config();
|
||||
$config['default_route'] = 'panel/global/menu';
|
||||
|
||||
$config['needHelpUrl'] = 'https://go.elementor.com/global-settings';
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function get_css_wrapper_selector() {
|
||||
return '.elementor-kit-' . $this->get_main_id();
|
||||
}
|
||||
|
||||
public function save( $data ) {
|
||||
$saved = parent::save( $data );
|
||||
|
||||
if ( ! $saved ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should set is_saving to true, to avoid infinite loop when updating
|
||||
// settings like: 'site_name" or "site_description".
|
||||
$this->set_is_saving( true );
|
||||
|
||||
foreach ( $this->tabs as $tab ) {
|
||||
$tab->on_save( $data );
|
||||
}
|
||||
|
||||
$this->set_is_saving( false );
|
||||
|
||||
// When deleting a global color or typo, the css variable still exists in the frontend
|
||||
// but without any value and it makes the element to be un styled even if there is a default style for the base element,
|
||||
// for that reason this method removes css files of the entire site.
|
||||
Plugin::instance()->files_manager->clear_cache();
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function _register_controls() {
|
||||
$this->register_document_controls();
|
||||
|
||||
foreach ( $this->tabs as $tab ) {
|
||||
$tab->register_controls();
|
||||
}
|
||||
}
|
||||
|
||||
protected function get_post_statuses() {
|
||||
return [
|
||||
'draft' => sprintf( '%s (%s)', __( 'Disabled', 'elementor' ), __( 'Draft', 'elementor' ) ),
|
||||
'publish' => __( 'Published', 'elementor' ),
|
||||
];
|
||||
}
|
||||
|
||||
public function add_repeater_row( $control_id, $item ) {
|
||||
$meta_key = PageManager::META_KEY;
|
||||
$document_settings = $this->get_meta( $meta_key );
|
||||
|
||||
if ( ! $document_settings ) {
|
||||
$document_settings = [];
|
||||
}
|
||||
|
||||
if ( ! isset( $document_settings[ $control_id ] ) ) {
|
||||
$document_settings[ $control_id ] = [];
|
||||
}
|
||||
|
||||
$document_settings[ $control_id ][] = $item;
|
||||
|
||||
$page_settings_manager = SettingsManager::get_settings_managers( 'page' );
|
||||
$page_settings_manager->save_settings( $document_settings, $this->get_id() );
|
||||
|
||||
/** @var Kit $autosave **/
|
||||
$autosave = $this->get_autosave();
|
||||
|
||||
if ( $autosave ) {
|
||||
$autosave->add_repeater_row( $control_id, $item );
|
||||
}
|
||||
|
||||
// Remove Post CSS.
|
||||
$post_css = Post_CSS::create( $this->post->ID );
|
||||
|
||||
$post_css->delete();
|
||||
|
||||
// Refresh Cache.
|
||||
Plugin::$instance->documents->get( $this->post->ID, false );
|
||||
|
||||
$post_css = Post_CSS::create( $this->post->ID );
|
||||
|
||||
$post_css->enqueue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Core\Kits\Controls\Repeater as Global_Style_Repeater;
|
||||
use Elementor\Repeater;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Global_Colors extends Tab_Base {
|
||||
|
||||
const COLOR_PRIMARY = 'globals/colors?id=primary';
|
||||
const COLOR_SECONDARY = 'globals/colors?id=secondary';
|
||||
const COLOR_TEXT = 'globals/colors?id=text';
|
||||
const COLOR_ACCENT = 'globals/colors?id=accent';
|
||||
|
||||
public function get_id() {
|
||||
return 'global-colors';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Global Colors', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$this->start_controls_section(
|
||||
'section_global_colors',
|
||||
[
|
||||
'label' => __( 'Global Colors', 'elementor' ),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$repeater = new Repeater();
|
||||
|
||||
$repeater->add_control(
|
||||
'title',
|
||||
[
|
||||
'type' => Controls_Manager::TEXT,
|
||||
'label_block' => true,
|
||||
'required' => true,
|
||||
]
|
||||
);
|
||||
|
||||
// Color Value
|
||||
$repeater->add_control(
|
||||
'color',
|
||||
[
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'label_block' => true,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => '--e-global-color-{{_id.VALUE}}: {{VALUE}}',
|
||||
],
|
||||
'global' => [
|
||||
'active' => false,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$default_colors = [
|
||||
[
|
||||
'_id' => 'primary',
|
||||
'title' => __( 'Primary', 'elementor' ),
|
||||
'color' => '#6EC1E4',
|
||||
],
|
||||
[
|
||||
'_id' => 'secondary',
|
||||
'title' => __( 'Secondary', 'elementor' ),
|
||||
'color' => '#54595F',
|
||||
],
|
||||
[
|
||||
'_id' => 'text',
|
||||
'title' => __( 'Text', 'elementor' ),
|
||||
'color' => '#7A7A7A',
|
||||
],
|
||||
[
|
||||
'_id' => 'accent',
|
||||
'title' => __( 'Accent', 'elementor' ),
|
||||
'color' => '#61CE70',
|
||||
],
|
||||
];
|
||||
|
||||
$this->add_control(
|
||||
'system_colors',
|
||||
[
|
||||
'type' => Global_Style_Repeater::CONTROL_TYPE,
|
||||
'fields' => $repeater->get_controls(),
|
||||
'default' => $default_colors,
|
||||
'item_actions' => [
|
||||
'add' => false,
|
||||
'remove' => false,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'custom_colors',
|
||||
[
|
||||
'type' => Global_Style_Repeater::CONTROL_TYPE,
|
||||
'fields' => $repeater->get_controls(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Core\Kits\Controls\Repeater as Global_Style_Repeater;
|
||||
use Elementor\Group_Control_Typography;
|
||||
use Elementor\Repeater;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Global_Typography extends Tab_Base {
|
||||
|
||||
const TYPOGRAPHY_PRIMARY = 'globals/typography?id=primary';
|
||||
const TYPOGRAPHY_SECONDARY = 'globals/typography?id=secondary';
|
||||
const TYPOGRAPHY_TEXT = 'globals/typography?id=text';
|
||||
const TYPOGRAPHY_ACCENT = 'globals/typography?id=accent';
|
||||
|
||||
const TYPOGRAPHY_NAME = 'typography';
|
||||
const TYPOGRAPHY_GROUP_PREFIX = self::TYPOGRAPHY_NAME . '_';
|
||||
|
||||
public function get_id() {
|
||||
return 'global-typography';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Global Fonts', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$this->start_controls_section(
|
||||
'section_text_style',
|
||||
[
|
||||
'label' => __( 'Global Fonts', 'elementor' ),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$repeater = new Repeater();
|
||||
|
||||
$repeater->add_control(
|
||||
'title',
|
||||
[
|
||||
'type' => Controls_Manager::TEXT,
|
||||
'label_block' => true,
|
||||
'required' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$repeater->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'name' => self::TYPOGRAPHY_NAME,
|
||||
'label' => '',
|
||||
'global' => [
|
||||
'active' => false,
|
||||
],
|
||||
'fields_options' => [
|
||||
'font_family' => [
|
||||
'selectors' => [
|
||||
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-family: "{{VALUE}}"',
|
||||
],
|
||||
],
|
||||
'font_size' => [
|
||||
'selectors' => [
|
||||
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-size: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
],
|
||||
'font_weight' => [
|
||||
'selectors' => [
|
||||
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-weight: {{VALUE}}',
|
||||
],
|
||||
],
|
||||
'text_transform' => [
|
||||
'selectors' => [
|
||||
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-text-transform: {{VALUE}}',
|
||||
],
|
||||
],
|
||||
'font_style' => [
|
||||
'selectors' => [
|
||||
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-style: {{VALUE}}',
|
||||
],
|
||||
],
|
||||
'text_decoration' => [
|
||||
'selectors' => [
|
||||
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-text-decoration: {{VALUE}}',
|
||||
],
|
||||
],
|
||||
'line_height' => [
|
||||
'selectors' => [
|
||||
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-line-height: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
],
|
||||
'letter_spacing' => [
|
||||
'selectors' => [
|
||||
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-letter-spacing: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$typography_key = self::TYPOGRAPHY_GROUP_PREFIX . 'typography';
|
||||
$font_family_key = self::TYPOGRAPHY_GROUP_PREFIX . 'font_family';
|
||||
$font_weight_key = self::TYPOGRAPHY_GROUP_PREFIX . 'font_weight';
|
||||
|
||||
$default_typography = [
|
||||
[
|
||||
'_id' => 'primary',
|
||||
'title' => __( 'Primary', 'elementor' ),
|
||||
$typography_key => 'custom',
|
||||
$font_family_key => 'Roboto',
|
||||
$font_weight_key => '600',
|
||||
],
|
||||
[
|
||||
'_id' => 'secondary',
|
||||
'title' => __( 'Secondary', 'elementor' ),
|
||||
$typography_key => 'custom',
|
||||
$font_family_key => 'Roboto Slab',
|
||||
$font_weight_key => '400',
|
||||
],
|
||||
[
|
||||
'_id' => 'text',
|
||||
'title' => __( 'Text', 'elementor' ),
|
||||
$typography_key => 'custom',
|
||||
$font_family_key => 'Roboto',
|
||||
$font_weight_key => '400',
|
||||
],
|
||||
[
|
||||
'_id' => 'accent',
|
||||
'title' => __( 'Accent', 'elementor' ),
|
||||
$typography_key => 'custom',
|
||||
$font_family_key => 'Roboto',
|
||||
$font_weight_key => '500',
|
||||
],
|
||||
];
|
||||
|
||||
$this->add_control(
|
||||
'system_typography',
|
||||
[
|
||||
'type' => Global_Style_Repeater::CONTROL_TYPE,
|
||||
'fields' => $repeater->get_controls(),
|
||||
'default' => $default_typography,
|
||||
'item_actions' => [
|
||||
'add' => false,
|
||||
'remove' => false,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'custom_typography',
|
||||
[
|
||||
'type' => Global_Style_Repeater::CONTROL_TYPE,
|
||||
'fields' => $repeater->get_controls(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'default_generic_fonts',
|
||||
[
|
||||
'label' => __( 'Fallback Font Family', 'elementor' ),
|
||||
'type' => Controls_Manager::TEXT,
|
||||
'default' => 'Sans-serif',
|
||||
'description' => __( 'The list of fonts used if the chosen font is not available.', 'elementor' ),
|
||||
'label_block' => true,
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Group_Control_Background;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Settings_Background extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'settings-background';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Background', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$this->start_controls_section(
|
||||
'section_background',
|
||||
[
|
||||
'label' => $this->get_title(),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Background::get_type(),
|
||||
[
|
||||
'name' => 'body_background',
|
||||
'types' => [ 'classic', 'gradient' ],
|
||||
'selector' => '{{WRAPPER}}',
|
||||
'fields_options' => [
|
||||
'background' => [
|
||||
'frontend_available' => true,
|
||||
],
|
||||
'color' => [
|
||||
'dynamic' => [],
|
||||
],
|
||||
'color_b' => [
|
||||
'dynamic' => [],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'mobile_browser_background',
|
||||
[
|
||||
'label' => __( 'Mobile Browser Background', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'description' => __( 'The `theme-color` meta tag will only be available in supported browsers and devices.', 'elementor' ),
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Settings_Custom_CSS extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'settings-custom-css';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Custom CSS', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
Plugin::$instance->controls_manager->add_custom_css_controls( $this->parent, $this->get_id() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\DB;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Core\Responsive\Responsive;
|
||||
use Elementor\Modules\PageTemplates\Module as PageTemplatesModule;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Settings_Layout extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'settings-layout';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Layout', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$default_breakpoints = Responsive::get_default_breakpoints();
|
||||
|
||||
$this->start_controls_section(
|
||||
'section_' . $this->get_id(),
|
||||
[
|
||||
'label' => __( 'Layout Settings', 'elementor' ),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'container_width',
|
||||
[
|
||||
'label' => __( 'Content Width', 'elementor' ) . ' (px)',
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'default' => [
|
||||
'size' => '1140',
|
||||
],
|
||||
'tablet_default' => [
|
||||
'size' => $default_breakpoints['lg'],
|
||||
],
|
||||
'mobile_default' => [
|
||||
'size' => $default_breakpoints['md'],
|
||||
],
|
||||
'range' => [
|
||||
'px' => [
|
||||
'min' => 300,
|
||||
'max' => 1500,
|
||||
'step' => 10,
|
||||
],
|
||||
],
|
||||
'description' => __( 'Sets the default width of the content area (Default: 1140)', 'elementor' ),
|
||||
'selectors' => [
|
||||
'.elementor-section.elementor-section-boxed > .elementor-container' => 'max-width: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'space_between_widgets',
|
||||
[
|
||||
'label' => __( 'Widgets Space', 'elementor' ) . ' (px)',
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'default' => [
|
||||
'size' => 20,
|
||||
],
|
||||
'range' => [
|
||||
'px' => [
|
||||
'min' => 0,
|
||||
'max' => 40,
|
||||
],
|
||||
],
|
||||
'placeholder' => '20',
|
||||
'description' => __( 'Sets the default space between widgets (Default: 20)', 'elementor' ),
|
||||
'selectors' => [
|
||||
'.elementor-widget:not(:last-child)' => 'margin-bottom: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'page_title_selector',
|
||||
[
|
||||
'label' => __( 'Page Title Selector', 'elementor' ),
|
||||
'type' => Controls_Manager::TEXT,
|
||||
'default' => 'h1.entry-title',
|
||||
'placeholder' => 'h1.entry-title',
|
||||
'description' => __( 'Elementor lets you hide the page title. This works for themes that have "h1.entry-title" selector. If your theme\'s selector is different, please enter it above.', 'elementor' ),
|
||||
'label_block' => true,
|
||||
'selectors' => [
|
||||
// Hack to convert the value into a CSS selector.
|
||||
'' => '}{{VALUE}}{display: var(--page-title-display)',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'stretched_section_container',
|
||||
[
|
||||
'label' => __( 'Stretched Section Fit To', 'elementor' ),
|
||||
'type' => Controls_Manager::TEXT,
|
||||
'placeholder' => 'body',
|
||||
'description' => __( 'Enter parent element selector to which stretched sections will fit to (e.g. #primary / .wrapper / main etc). Leave blank to fit to page width.', 'elementor' ),
|
||||
'label_block' => true,
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* @var PageTemplatesModule $page_templates_module
|
||||
*/
|
||||
$page_templates_module = Plugin::$instance->modules_manager->get_modules( 'page-templates' );
|
||||
$page_templates = $page_templates_module->add_page_templates( [], null, null );
|
||||
|
||||
$page_template_control_options = [
|
||||
'label' => __( 'Default Page Layout', 'elementor' ),
|
||||
'options' => [
|
||||
// This is here because the "Theme" string is different than the default option's string.
|
||||
'default' => __( 'Theme', 'elementor' ),
|
||||
] + $page_templates,
|
||||
];
|
||||
|
||||
$page_templates_module->add_template_controls( $this->parent, 'default_page_template', $page_template_control_options );
|
||||
|
||||
$this->end_controls_section();
|
||||
|
||||
$this->start_controls_section(
|
||||
'section_breakpoints',
|
||||
[
|
||||
'label' => __( 'Breakpoints', 'elementor' ),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'breakpoint_md_heading',
|
||||
[
|
||||
'label' => __( 'Mobile', 'elementor' ),
|
||||
'type' => Controls_Manager::HEADING,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
Responsive::BREAKPOINT_OPTION_PREFIX . 'md',
|
||||
[
|
||||
'label' => __( 'Breakpoint', 'elementor' ) . ' (px)',
|
||||
'type' => Controls_Manager::NUMBER,
|
||||
'min' => $default_breakpoints['sm'] + 1,
|
||||
'max' => $default_breakpoints['lg'] - 1,
|
||||
'default' => $default_breakpoints['md'],
|
||||
'placeholder' => $default_breakpoints['md'],
|
||||
/* translators: %d: Breakpoint value */
|
||||
'desc' => sprintf( __( 'Sets the breakpoint between tablet and mobile devices. Below this breakpoint mobile layout will appear (Default: %dpx).', 'elementor' ), $default_breakpoints['md'] ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'breakpoint_lg_heading',
|
||||
[
|
||||
'label' => __( 'Tablet', 'elementor' ),
|
||||
'type' => Controls_Manager::HEADING,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
Responsive::BREAKPOINT_OPTION_PREFIX . 'lg',
|
||||
[
|
||||
'label' => __( 'Breakpoint', 'elementor' ) . ' (px)',
|
||||
'type' => Controls_Manager::NUMBER,
|
||||
'min' => $default_breakpoints['md'] + 1,
|
||||
'max' => $default_breakpoints['xl'] - 1,
|
||||
'default' => $default_breakpoints['lg'],
|
||||
'placeholder' => $default_breakpoints['lg'],
|
||||
/* translators: %d: Breakpoint value */
|
||||
'desc' => sprintf( __( 'Sets the breakpoint between desktop and tablet devices. Below this breakpoint tablet layout will appear (Default: %dpx).', 'elementor' ), $default_breakpoints['lg'] ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
|
||||
public function on_save( $data ) {
|
||||
if ( ! isset( $data['settings'] ) || DB::STATUS_PUBLISH !== $data['settings']['post_status'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$should_compile_css = false;
|
||||
|
||||
foreach ( Responsive::get_editable_breakpoints() as $breakpoint_key => $breakpoint ) {
|
||||
$setting_key = "viewport_{$breakpoint_key}";
|
||||
if ( isset( $data['settings'][ $setting_key ] ) ) {
|
||||
$should_compile_css = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $should_compile_css ) {
|
||||
Responsive::compile_stylesheet_templates();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Settings_Lightbox extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'settings-lightbox';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Lightbox', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$this->start_controls_section(
|
||||
'section_' . $this->get_id(),
|
||||
[
|
||||
'label' => $this->get_title(),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'global_image_lightbox',
|
||||
[
|
||||
'label' => __( 'Image Lightbox', 'elementor' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'default' => 'yes',
|
||||
'description' => __( 'Open all image links in a lightbox popup window. The lightbox will automatically work on any link that leads to an image file.', 'elementor' ),
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_enable_counter',
|
||||
[
|
||||
'label' => __( 'Counter', 'elementor' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'default' => 'yes',
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_enable_fullscreen',
|
||||
[
|
||||
'label' => __( 'Fullscreen', 'elementor' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'default' => 'yes',
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_enable_zoom',
|
||||
[
|
||||
'label' => __( 'Zoom', 'elementor' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'default' => 'yes',
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_enable_share',
|
||||
[
|
||||
'label' => __( 'Share', 'elementor' ),
|
||||
'type' => Controls_Manager::SWITCHER,
|
||||
'default' => 'yes',
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_title_src',
|
||||
[
|
||||
'label' => __( 'Title', 'elementor' ),
|
||||
'type' => Controls_Manager::SELECT,
|
||||
'options' => [
|
||||
'' => __( 'None', 'elementor' ),
|
||||
'title' => __( 'Title', 'elementor' ),
|
||||
'caption' => __( 'Caption', 'elementor' ),
|
||||
'alt' => __( 'Alt', 'elementor' ),
|
||||
'description' => __( 'Description', 'elementor' ),
|
||||
],
|
||||
'default' => 'title',
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_description_src',
|
||||
[
|
||||
'label' => __( 'Description', 'elementor' ),
|
||||
'type' => Controls_Manager::SELECT,
|
||||
'options' => [
|
||||
'' => __( 'None', 'elementor' ),
|
||||
'title' => __( 'Title', 'elementor' ),
|
||||
'caption' => __( 'Caption', 'elementor' ),
|
||||
'alt' => __( 'Alt', 'elementor' ),
|
||||
'description' => __( 'Description', 'elementor' ),
|
||||
],
|
||||
'default' => 'description',
|
||||
'frontend_available' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_color',
|
||||
[
|
||||
'label' => __( 'Background Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'selectors' => [
|
||||
'.elementor-lightbox' => 'background-color: {{VALUE}}',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_ui_color',
|
||||
[
|
||||
'label' => __( 'UI Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'selectors' => [
|
||||
'.elementor-lightbox' => '--lightbox-ui-color: {{VALUE}}',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_ui_color_hover',
|
||||
[
|
||||
'label' => __( 'UI Hover Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'selectors' => [
|
||||
'.elementor-lightbox' => '--lightbox-ui-color-hover: {{VALUE}}',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_text_color',
|
||||
[
|
||||
'label' => __( 'Text Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'selectors' => [
|
||||
'.elementor-lightbox' => '--lightbox-text-color: {{VALUE}}',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_icons_size',
|
||||
[
|
||||
'label' => __( 'Toolbar Icons Size', 'elementor' ),
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'selectors' => [
|
||||
'.elementor-lightbox' => '--lightbox-header-icons-size: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'lightbox_slider_icons_size',
|
||||
[
|
||||
'label' => __( 'Navigation Icons Size', 'elementor' ),
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'selectors' => [
|
||||
'.elementor-lightbox' => '--lightbox-navigation-icons-size: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\DB;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Settings_Site_Identity extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'settings-site-identity';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Site Identity', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$custom_logo_id = get_theme_mod( 'custom_logo' );
|
||||
$custom_logo_src = wp_get_attachment_image_src( $custom_logo_id, 'full' );
|
||||
|
||||
$site_icon_id = get_option( 'site_icon' );
|
||||
$site_icon_src = wp_get_attachment_image_src( $site_icon_id, 'full' );
|
||||
|
||||
$this->start_controls_section(
|
||||
'section_' . $this->get_id(),
|
||||
[
|
||||
'label' => $this->get_title(),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
$this->get_id() . '_refresh_notice',
|
||||
[
|
||||
'type' => Controls_Manager::RAW_HTML,
|
||||
'raw' => __( 'Changes will be reflected in the preview only after the page reloads.', 'elementor' ),
|
||||
'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'site_name',
|
||||
[
|
||||
'label' => __( 'Site Name', 'elementor' ),
|
||||
'default' => get_option( 'blogname' ),
|
||||
'placeholder' => __( 'Choose name', 'elementor' ),
|
||||
'label_block' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'site_description',
|
||||
[
|
||||
'label' => __( 'Site Description', 'elementor' ),
|
||||
'default' => get_option( 'blogdescription' ),
|
||||
'placeholder' => __( 'Choose description', 'elementor' ),
|
||||
'label_block' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'site_logo',
|
||||
[
|
||||
'label' => __( 'Site Logo', 'elementor' ),
|
||||
'type' => Controls_Manager::MEDIA,
|
||||
'default' => [
|
||||
'id' => $custom_logo_id,
|
||||
'url' => $custom_logo_src ? $custom_logo_src[0] : '',
|
||||
],
|
||||
'description' => __( 'Suggested image dimensions: 350 × 100 pixels.', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'site_favicon',
|
||||
[
|
||||
'label' => __( 'Site Favicon', 'elementor' ),
|
||||
'type' => Controls_Manager::MEDIA,
|
||||
'default' => [
|
||||
'id' => $site_icon_id,
|
||||
'url' => $site_icon_src ? $site_icon_src[0] : '',
|
||||
],
|
||||
'description' => __( 'Suggested favicon dimensions: 512 × 512 pixels.', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
|
||||
public function on_save( $data ) {
|
||||
if (
|
||||
! isset( $data['settings'] ) ||
|
||||
DB::STATUS_PUBLISH !== $data['settings']['post_status'] ||
|
||||
// Should check for the current action to avoid infinite loop
|
||||
// when updating options like: "blogname" and "blogdescription".
|
||||
strpos( current_action(), 'update_option_' ) === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $data['settings']['site_name'] ) ) {
|
||||
update_option( 'blogname', $data['settings']['site_name'] );
|
||||
}
|
||||
|
||||
if ( isset( $data['settings']['site_description'] ) ) {
|
||||
update_option( 'blogdescription', $data['settings']['site_description'] );
|
||||
}
|
||||
|
||||
if ( isset( $data['settings']['site_logo'] ) ) {
|
||||
set_theme_mod( 'custom_logo', $data['settings']['site_logo']['id'] );
|
||||
}
|
||||
|
||||
if ( isset( $data['settings']['site_favicon'] ) ) {
|
||||
update_option( 'site_icon', $data['settings']['site_favicon']['id'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Core\Kits\Documents\Kit;
|
||||
use Elementor\Core\Kits\Manager;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Settings;
|
||||
use Elementor\Sub_Controls_Stack;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Tab_Base extends Sub_Controls_Stack {
|
||||
/**
|
||||
* @var Kit
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
abstract protected function register_tab_controls();
|
||||
|
||||
public function register_controls() {
|
||||
$this->register_tab();
|
||||
|
||||
$this->register_tab_controls();
|
||||
}
|
||||
|
||||
public function on_save( $data ) {}
|
||||
|
||||
protected function register_tab() {
|
||||
Controls_Manager::add_tab( $this->get_id(), $this->get_title() );
|
||||
}
|
||||
|
||||
protected function add_default_globals_notice() {
|
||||
// Get the current section config (array - section id and tab) to use for creating a unique control ID and name
|
||||
$current_section = $this->parent->get_current_section();
|
||||
|
||||
/** @var Manager $module */
|
||||
$kits_manager = Plugin::$instance->kits_manager;
|
||||
|
||||
if ( $kits_manager->is_custom_colors_enabled() || $kits_manager->is_custom_typography_enabled() ) {
|
||||
$this->add_control(
|
||||
$current_section['section'] . '_schemes_notice',
|
||||
[
|
||||
'name' => $current_section['section'] . '_schemes_notice',
|
||||
'type' => Controls_Manager::RAW_HTML,
|
||||
'raw' => sprintf( __( 'In order for Theme Style to affect all relevant Elementor elements, please disable Default Colors and Fonts from the <a href="%s" target="_blank">Settings Page</a>.', 'elementor' ), Settings::get_url() ),
|
||||
'content_classes' => 'elementor-panel-alert elementor-panel-alert-warning',
|
||||
'render_type' => 'ui',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Group_Control_Border;
|
||||
use Elementor\Group_Control_Box_Shadow;
|
||||
use Elementor\Group_Control_Text_Shadow;
|
||||
use Elementor\Group_Control_Typography;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Theme_Style_Buttons extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'theme-style-buttons';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Buttons', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$button_selectors = [
|
||||
'{{WRAPPER}} button',
|
||||
'{{WRAPPER}} input[type="button"]',
|
||||
'{{WRAPPER}} input[type="submit"]',
|
||||
'{{WRAPPER}} .elementor-button',
|
||||
];
|
||||
|
||||
$button_hover_selectors = [
|
||||
'{{WRAPPER}} button:hover',
|
||||
'{{WRAPPER}} button:focus',
|
||||
'{{WRAPPER}} input[type="button"]:hover',
|
||||
'{{WRAPPER}} input[type="button"]:focus',
|
||||
'{{WRAPPER}} input[type="submit"]:hover',
|
||||
'{{WRAPPER}} input[type="submit"]:focus',
|
||||
'{{WRAPPER}} .elementor-button:hover',
|
||||
'{{WRAPPER}} .elementor-button:focus',
|
||||
];
|
||||
|
||||
$button_selector = implode( ',', $button_selectors );
|
||||
$button_hover_selector = implode( ',', $button_hover_selectors );
|
||||
|
||||
$this->start_controls_section(
|
||||
'section_buttons',
|
||||
[
|
||||
'label' => __( 'Buttons', 'elementor' ),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_default_globals_notice();
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'label' => __( 'Typography', 'elementor' ),
|
||||
'name' => 'button_typography',
|
||||
'selector' => $button_selector,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Text_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'button_text_shadow',
|
||||
'selector' => $button_selector,
|
||||
]
|
||||
);
|
||||
|
||||
$this->start_controls_tabs( 'tabs_button_style' );
|
||||
|
||||
$this->start_controls_tab(
|
||||
'tab_button_normal',
|
||||
[
|
||||
'label' => __( 'Normal', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'button_text_color',
|
||||
[
|
||||
'label' => __( 'Text Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$button_selector => 'color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'button_background_color',
|
||||
[
|
||||
'label' => __( 'Background Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$button_selector => 'background-color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Box_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'button_box_shadow',
|
||||
'selector' => $button_selector,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Border::get_type(),
|
||||
[
|
||||
'name' => 'button_border',
|
||||
'selector' => $button_selector,
|
||||
'fields_options' => [
|
||||
'color' => [
|
||||
'dynamic' => [],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'button_border_radius',
|
||||
[
|
||||
'label' => __( 'Border Radius', 'elementor' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', '%' ],
|
||||
'selectors' => [
|
||||
$button_selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->start_controls_tab(
|
||||
'tab_button_hover',
|
||||
[
|
||||
'label' => __( 'Hover', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'button_hover_text_color',
|
||||
[
|
||||
'label' => __( 'Text Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$button_hover_selector => 'color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'button_hover_background_color',
|
||||
[
|
||||
'label' => __( 'Background Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$button_hover_selector => 'background-color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Box_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'button_hover_box_shadow',
|
||||
'selector' => $button_hover_selector,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Border::get_type(),
|
||||
[
|
||||
'name' => 'button_hover_border',
|
||||
'selector' => $button_hover_selector,
|
||||
'fields_options' => [
|
||||
'color' => [
|
||||
'dynamic' => [],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'button_hover_border_radius',
|
||||
[
|
||||
'label' => __( 'Border Radius', 'elementor' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', '%' ],
|
||||
'selectors' => [
|
||||
$button_hover_selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->end_controls_tabs();
|
||||
|
||||
$this->add_responsive_control(
|
||||
'button_padding',
|
||||
[
|
||||
'label' => __( 'Padding', 'elementor' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', 'em', '%' ],
|
||||
'selectors' => [
|
||||
$button_selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
||||
],
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Group_Control_Border;
|
||||
use Elementor\Group_Control_Box_Shadow;
|
||||
use Elementor\Group_Control_Typography;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Theme_Style_Form_Fields extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'theme-style-form-fields';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Form Fields', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$label_selectors = [
|
||||
'{{WRAPPER}} label',
|
||||
];
|
||||
|
||||
$input_selectors = [
|
||||
'{{WRAPPER}} input:not([type="button"]):not([type="submit"])',
|
||||
'{{WRAPPER}} textarea',
|
||||
'{{WRAPPER}} .elementor-field-textual',
|
||||
];
|
||||
|
||||
$input_focus_selectors = [
|
||||
'{{WRAPPER}} input:focus:not([type="button"]):not([type="submit"])',
|
||||
'{{WRAPPER}} textarea:focus',
|
||||
'{{WRAPPER}} .elementor-field-textual:focus',
|
||||
];
|
||||
|
||||
$label_selector = implode( ',', $label_selectors );
|
||||
$input_selector = implode( ',', $input_selectors );
|
||||
$input_focus_selector = implode( ',', $input_focus_selectors );
|
||||
|
||||
$this->start_controls_section(
|
||||
'section_form_fields',
|
||||
[
|
||||
'label' => __( 'Form Fields', 'elementor' ),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_default_globals_notice();
|
||||
|
||||
$this->add_control(
|
||||
'form_label_heading',
|
||||
[
|
||||
'type' => Controls_Manager::HEADING,
|
||||
'label' => __( 'Label', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'form_label_color',
|
||||
[
|
||||
'label' => __( 'Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$label_selector => 'color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'label' => __( 'Typography', 'elementor' ),
|
||||
'name' => 'form_label_typography',
|
||||
'selector' => $label_selector,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'form_field_heading',
|
||||
[
|
||||
'type' => Controls_Manager::HEADING,
|
||||
'label' => __( 'Field', 'elementor' ),
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'label' => __( 'Typography', 'elementor' ),
|
||||
'name' => 'form_field_typography',
|
||||
'selector' => $input_selector,
|
||||
]
|
||||
);
|
||||
|
||||
$this->start_controls_tabs( 'tabs_form_field_style' );
|
||||
|
||||
$this->start_controls_tab(
|
||||
'tab_form_field_normal',
|
||||
[
|
||||
'label' => __( 'Normal', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_form_field_state_tab_controls( 'form_field', $input_selector );
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->start_controls_tab(
|
||||
'tab_form_field_focus',
|
||||
[
|
||||
'label' => __( 'Focus', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_form_field_state_tab_controls( 'form_field_focus', $input_focus_selector );
|
||||
|
||||
$this->add_control(
|
||||
'form_field_focus_transition_duration',
|
||||
[
|
||||
'label' => __( 'Transition Duration', 'elementor' ) . ' (ms)',
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'selectors' => [
|
||||
$input_selector => 'transition: {{SIZE}}ms',
|
||||
],
|
||||
'range' => [
|
||||
'px' => [
|
||||
'min' => 0,
|
||||
'max' => 3000,
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->end_controls_tabs();
|
||||
|
||||
$this->add_responsive_control(
|
||||
'form_field_padding',
|
||||
[
|
||||
'label' => __( 'Padding', 'elementor' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', 'em', '%' ],
|
||||
'selectors' => [
|
||||
$input_selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
||||
],
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
|
||||
private function add_form_field_state_tab_controls( $prefix, $selector ) {
|
||||
$this->add_control(
|
||||
$prefix . '_text_color',
|
||||
[
|
||||
'label' => __( 'Text Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$selector => 'color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
$prefix . '_background_color',
|
||||
[
|
||||
'label' => __( 'Background Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$selector => 'background-color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Box_Shadow::get_type(),
|
||||
[
|
||||
'name' => $prefix . '_box_shadow',
|
||||
'selector' => $selector,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Border::get_type(),
|
||||
[
|
||||
'name' => $prefix . '_border',
|
||||
'selector' => $selector,
|
||||
'fields_options' => [
|
||||
'color' => [
|
||||
'dynamic' => [],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
$prefix . '_border_radius',
|
||||
[
|
||||
'label' => __( 'Border Radius', 'elementor' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', '%' ],
|
||||
'selectors' => [
|
||||
$selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Group_Control_Border;
|
||||
use Elementor\Group_Control_Box_Shadow;
|
||||
use Elementor\Group_Control_Css_Filter;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Theme_Style_Images extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'theme-style-images';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Images', 'elementor' );
|
||||
}
|
||||
|
||||
protected function register_tab_controls() {
|
||||
$image_selectors = [
|
||||
'{{WRAPPER}} img',
|
||||
];
|
||||
|
||||
$image_hover_selectors = [
|
||||
'{{WRAPPER}} img:hover',
|
||||
];
|
||||
|
||||
$image_selectors = implode( ',', $image_selectors );
|
||||
$image_hover_selectors = implode( ',', $image_hover_selectors );
|
||||
|
||||
$this->start_controls_section(
|
||||
'section_images',
|
||||
[
|
||||
'label' => __( 'Images', 'elementor' ),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_default_globals_notice();
|
||||
|
||||
$this->start_controls_tabs( 'tabs_image_style' );
|
||||
|
||||
$this->start_controls_tab(
|
||||
'tab_image_normal',
|
||||
[
|
||||
'label' => __( 'Normal', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Border::get_type(),
|
||||
[
|
||||
'name' => 'image_border',
|
||||
'selector' => $image_selectors,
|
||||
'fields_options' => [
|
||||
'color' => [
|
||||
'dynamic' => [],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'image_border_radius',
|
||||
[
|
||||
'label' => __( 'Border Radius', 'elementor' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', '%' ],
|
||||
'selectors' => [
|
||||
$image_selectors => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'image_opacity',
|
||||
[
|
||||
'label' => __( 'Opacity', 'elementor' ),
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'range' => [
|
||||
'px' => [
|
||||
'max' => 1,
|
||||
'min' => 0.10,
|
||||
'step' => 0.01,
|
||||
],
|
||||
],
|
||||
'selectors' => [
|
||||
$image_selectors => 'opacity: {{SIZE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Box_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'image_box_shadow',
|
||||
'exclude' => [
|
||||
'box_shadow_position',
|
||||
],
|
||||
'selector' => $image_selectors,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Css_Filter::get_type(),
|
||||
[
|
||||
'name' => 'image_css_filters',
|
||||
'selector' => '{{WRAPPER}} img',
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->start_controls_tab(
|
||||
'tab_image_hover',
|
||||
[
|
||||
'label' => __( 'Hover', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Border::get_type(),
|
||||
[
|
||||
'name' => 'image_hover_border',
|
||||
'selector' => '{{WRAPPER}} img:hover',
|
||||
'fields_options' => [
|
||||
'color' => [
|
||||
'dynamic' => [],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'image_hover_border_radius',
|
||||
[
|
||||
'label' => __( 'Border Radius', 'elementor' ),
|
||||
'type' => Controls_Manager::DIMENSIONS,
|
||||
'size_units' => [ 'px', '%' ],
|
||||
'selectors' => [
|
||||
$image_hover_selectors => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'image_hover_opacity',
|
||||
[
|
||||
'label' => __( 'Opacity', 'elementor' ),
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'range' => [
|
||||
'px' => [
|
||||
'max' => 1,
|
||||
'min' => 0.10,
|
||||
'step' => 0.01,
|
||||
],
|
||||
],
|
||||
'selectors' => [
|
||||
$image_hover_selectors => 'opacity: {{SIZE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Box_Shadow::get_type(),
|
||||
[
|
||||
'name' => 'image_hover_box_shadow',
|
||||
'exclude' => [
|
||||
'box_shadow_position',
|
||||
],
|
||||
'selector' => $image_hover_selectors,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Css_Filter::get_type(),
|
||||
[
|
||||
'name' => 'image_hover_css_filters',
|
||||
'selector' => $image_hover_selectors,
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'image_hover_transition',
|
||||
[
|
||||
'label' => __( 'Transition Duration', 'elementor' ) . ' (s)',
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'range' => [
|
||||
'px' => [
|
||||
'max' => 3,
|
||||
'step' => 0.1,
|
||||
],
|
||||
],
|
||||
'selectors' => [
|
||||
$image_selectors => 'transition-duration: {{SIZE}}s',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->end_controls_tabs();
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Kits\Documents\Tabs;
|
||||
|
||||
use Elementor\Controls_Manager;
|
||||
use Elementor\Group_Control_Typography;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Theme_Style_Typography extends Tab_Base {
|
||||
|
||||
public function get_id() {
|
||||
return 'theme-style-typography';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return __( 'Typography', 'elementor' );
|
||||
}
|
||||
|
||||
public function register_tab_controls() {
|
||||
$this->start_controls_section(
|
||||
'section_typography',
|
||||
[
|
||||
'label' => __( 'Typography', 'elementor' ),
|
||||
'tab' => $this->get_id(),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_default_globals_notice();
|
||||
|
||||
$this->add_control(
|
||||
'body_heading',
|
||||
[
|
||||
'type' => Controls_Manager::HEADING,
|
||||
'label' => __( 'Body', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'body_color',
|
||||
[
|
||||
'label' => __( 'Text Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
'{{WRAPPER}}' => 'color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'label' => __( 'Typography', 'elementor' ),
|
||||
'name' => 'body_typography',
|
||||
'selector' => '{{WRAPPER}}',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_responsive_control(
|
||||
'paragraph_spacing',
|
||||
[
|
||||
'label' => __( 'Paragraph Spacing', 'elementor' ),
|
||||
'type' => Controls_Manager::SLIDER,
|
||||
'selectors' => [
|
||||
'{{WRAPPER}} p' => 'margin-bottom: {{SIZE}}{{UNIT}}',
|
||||
],
|
||||
'range' => [
|
||||
'px' => [
|
||||
'min' => 0,
|
||||
'max' => 100,
|
||||
],
|
||||
'em' => [
|
||||
'min' => 0.1,
|
||||
'max' => 20,
|
||||
],
|
||||
'vh' => [
|
||||
'min' => 0,
|
||||
'max' => 100,
|
||||
],
|
||||
],
|
||||
'size_units' => [ 'px', 'em', 'vh' ],
|
||||
]
|
||||
);
|
||||
|
||||
//Link Selectors
|
||||
$link_selectors = [
|
||||
'{{WRAPPER}} a',
|
||||
];
|
||||
|
||||
$link_hover_selectors = [
|
||||
'{{WRAPPER}} a:hover',
|
||||
];
|
||||
|
||||
$link_selectors = implode( ',', $link_selectors );
|
||||
$link_hover_selectors = implode( ',', $link_hover_selectors );
|
||||
|
||||
$this->add_control(
|
||||
'link_heading',
|
||||
[
|
||||
'type' => Controls_Manager::HEADING,
|
||||
'label' => __( 'Link', 'elementor' ),
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->start_controls_tabs( 'tabs_link_style' );
|
||||
|
||||
$this->start_controls_tab(
|
||||
'tab_link_normal',
|
||||
[
|
||||
'label' => __( 'Normal', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'link_normal_color',
|
||||
[
|
||||
'label' => __( 'Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$link_selectors => 'color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'label' => __( 'Typography', 'elementor' ),
|
||||
'name' => 'link_normal_typography',
|
||||
'selector' => $link_selectors,
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->start_controls_tab(
|
||||
'tab_link_hover',
|
||||
[
|
||||
'label' => __( 'Hover', 'elementor' ),
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
'link_hover_color',
|
||||
[
|
||||
'label' => __( 'Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$link_hover_selectors => 'color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'label' => __( 'Typography', 'elementor' ),
|
||||
'name' => 'link_hover_typography',
|
||||
'selector' => $link_hover_selectors,
|
||||
]
|
||||
);
|
||||
|
||||
$this->end_controls_tab();
|
||||
|
||||
$this->end_controls_tabs();
|
||||
|
||||
// Headings.
|
||||
$this->add_element_controls( __( 'H1', 'elementor' ), 'h1', '{{WRAPPER}} h1' );
|
||||
$this->add_element_controls( __( 'H2', 'elementor' ), 'h2', '{{WRAPPER}} h2' );
|
||||
$this->add_element_controls( __( 'H3', 'elementor' ), 'h3', '{{WRAPPER}} h3' );
|
||||
$this->add_element_controls( __( 'H4', 'elementor' ), 'h4', '{{WRAPPER}} h4' );
|
||||
$this->add_element_controls( __( 'H5', 'elementor' ), 'h5', '{{WRAPPER}} h5' );
|
||||
$this->add_element_controls( __( 'H6', 'elementor' ), 'h6', '{{WRAPPER}} h6' );
|
||||
|
||||
$this->end_controls_section();
|
||||
}
|
||||
|
||||
private function add_element_controls( $label, $prefix, $selector ) {
|
||||
$this->add_control(
|
||||
$prefix . '_heading',
|
||||
[
|
||||
'type' => Controls_Manager::HEADING,
|
||||
'label' => $label,
|
||||
'separator' => 'before',
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_control(
|
||||
$prefix . '_color',
|
||||
[
|
||||
'label' => __( 'Color', 'elementor' ),
|
||||
'type' => Controls_Manager::COLOR,
|
||||
'dynamic' => [],
|
||||
'selectors' => [
|
||||
$selector => 'color: {{VALUE}};',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->add_group_control(
|
||||
Group_Control_Typography::get_type(),
|
||||
[
|
||||
'label' => __( 'Typography', 'elementor' ),
|
||||
'name' => $prefix . '_typography',
|
||||
'selector' => $selector,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Kits;
|
||||
|
||||
use Elementor\Core\Kits\Controls\Repeater;
|
||||
use Elementor\Core\Kits\Documents\Tabs\Global_Colors;
|
||||
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Core\Files\CSS\Post as Post_CSS;
|
||||
use Elementor\Core\Files\CSS\Post_Preview as Post_Preview;
|
||||
use Elementor\Core\Documents_Manager;
|
||||
use Elementor\Core\Kits\Documents\Kit;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Manager {
|
||||
|
||||
const OPTION_ACTIVE = 'elementor_active_kit';
|
||||
|
||||
public function get_active_id() {
|
||||
$id = get_option( self::OPTION_ACTIVE );
|
||||
|
||||
$kit_document = Plugin::$instance->documents->get( $id );
|
||||
|
||||
if ( ! $kit_document || ! $kit_document instanceof Kit || 'trash' === $kit_document->get_main_post()->post_status ) {
|
||||
$id = $this->create_default();
|
||||
update_option( self::OPTION_ACTIVE, $id );
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function get_active_kit() {
|
||||
$id = $this->get_active_id();
|
||||
|
||||
return Plugin::$instance->documents->get( $id );
|
||||
}
|
||||
|
||||
public function get_active_kit_for_frontend() {
|
||||
$id = $this->get_active_id();
|
||||
|
||||
return Plugin::$instance->documents->get_doc_for_frontend( $id );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Init kit controls.
|
||||
*
|
||||
* A temp solution in order to avoid init kit group control from within another group control.
|
||||
*
|
||||
* After moving the `default_font` to the kit, the Typography group control cause initialize the kit controls at: https://github.com/elementor/elementor/blob/e6e1db9eddef7e3c1a5b2ba0c2338e2af2a3bfe3/includes/controls/groups/typography.php#L91
|
||||
* and because the group control is a singleton, its args are changed to the last kit group control.
|
||||
*/
|
||||
public function init_kit_controls() {
|
||||
$this->get_active_kit_for_frontend()->get_settings();
|
||||
}
|
||||
|
||||
public function get_current_settings( $setting = null ) {
|
||||
$kit = $this->get_active_kit_for_frontend();
|
||||
|
||||
if ( ! $kit ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $kit->get_settings( $setting );
|
||||
}
|
||||
|
||||
private function create_default() {
|
||||
$kit = Plugin::$instance->documents->create( 'kit', [
|
||||
'post_type' => Source_Local::CPT,
|
||||
'post_title' => __( 'Default Kit', 'elementor' ),
|
||||
'post_status' => 'publish',
|
||||
] );
|
||||
|
||||
return $kit->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Documents_Manager $documents_manager
|
||||
*/
|
||||
public function register_document( $documents_manager ) {
|
||||
$documents_manager->register_document_type( 'kit', Kit::get_class_full_name() );
|
||||
}
|
||||
|
||||
public function localize_settings( $settings ) {
|
||||
$kit = $this->get_active_kit();
|
||||
$kit_controls = $kit->get_controls();
|
||||
$design_system_controls = [
|
||||
'colors' => $kit_controls['system_colors']['fields'],
|
||||
'typography' => $kit_controls['system_typography']['fields'],
|
||||
];
|
||||
|
||||
$settings = array_replace_recursive( $settings, [
|
||||
'kit_id' => $kit->get_main_id(),
|
||||
'kit_config' => [
|
||||
'typography_prefix' => Global_Typography::TYPOGRAPHY_GROUP_PREFIX,
|
||||
'design_system_controls' => $design_system_controls,
|
||||
],
|
||||
'user' => [
|
||||
'can_edit_kit' => $kit->is_editable_by_current_user(),
|
||||
],
|
||||
'i18n' => [
|
||||
'close' => __( 'Close', 'elementor' ),
|
||||
'back' => __( 'Back', 'elementor' ),
|
||||
'site_identity' => __( 'Site Identity', 'elementor' ),
|
||||
'lightbox' => __( 'Lightbox', 'elementor' ),
|
||||
'layout' => __( 'Layout', 'elementor' ),
|
||||
'theme_style' => __( 'Theme Style', 'elementor' ),
|
||||
'add_color' => __( 'Add Color', 'elementor' ),
|
||||
'add_style' => __( 'Add Style', 'elementor' ),
|
||||
'new_item' => __( 'New Item', 'elementor' ),
|
||||
'global_color' => __( 'Global Color', 'elementor' ),
|
||||
'global_fonts' => __( 'Global Fonts', 'elementor' ),
|
||||
'global_colors' => __( 'Global Colors', 'elementor' ),
|
||||
'invalid' => __( 'Invalid', 'elementor' ),
|
||||
'color_cannot_be_deleted' => __( 'System Color can\'t be deleted', 'elementor' ),
|
||||
'font_cannot_be_deleted' => __( 'System Font can\'t be deleted', 'elementor' ),
|
||||
'design_system' => __( 'Design System', 'elementor' ),
|
||||
'buttons' => __( 'Buttons', 'elementor' ),
|
||||
'images' => __( 'Images', 'elementor' ),
|
||||
'form_fields' => __( 'Form Fields', 'elementor' ),
|
||||
'background' => __( 'Background', 'elementor' ),
|
||||
'custom_css' => __( 'Custom CSS', 'elementor' ),
|
||||
'additional_settings' => __( 'Additional Settings', 'elementor' ),
|
||||
'kit_changes_updated' => __( 'Your changes have been updated.', 'elementor' ),
|
||||
'back_to_editor' => __( 'Back to Editor', 'elementor' ),
|
||||
],
|
||||
] );
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function preview_enqueue_styles() {
|
||||
$kit = $this->get_kit_for_frontend();
|
||||
|
||||
if ( $kit ) {
|
||||
// On preview, the global style is not enqueued.
|
||||
$this->frontend_before_enqueue_styles();
|
||||
|
||||
Plugin::$instance->frontend->print_fonts_links();
|
||||
}
|
||||
}
|
||||
|
||||
public function frontend_before_enqueue_styles() {
|
||||
$kit = $this->get_kit_for_frontend();
|
||||
|
||||
if ( $kit ) {
|
||||
if ( $kit->is_autosave() ) {
|
||||
$css_file = Post_Preview::create( $kit->get_id() );
|
||||
} else {
|
||||
$css_file = Post_CSS::create( $kit->get_id() );
|
||||
}
|
||||
|
||||
$css_file->enqueue();
|
||||
}
|
||||
}
|
||||
|
||||
public function render_panel_html() {
|
||||
require __DIR__ . '/views/panel.php';
|
||||
}
|
||||
|
||||
public function get_kit_for_frontend() {
|
||||
$kit = false;
|
||||
$active_kit = $this->get_active_kit();
|
||||
$is_kit_preview = is_preview() && isset( $_GET['preview_id'] ) && $active_kit->get_main_id() === (int) $_GET['preview_id'];
|
||||
|
||||
if ( $is_kit_preview ) {
|
||||
$kit = Plugin::$instance->documents->get_doc_or_auto_save( $active_kit->get_main_id(), get_current_user_id() );
|
||||
} elseif ( 'publish' === $active_kit->get_main_post()->post_status ) {
|
||||
$kit = $active_kit;
|
||||
}
|
||||
|
||||
return $kit;
|
||||
}
|
||||
|
||||
public function update_kit_settings_based_on_option( $key, $value ) {
|
||||
/** @var Kit $active_kit */
|
||||
$active_kit = $this->get_active_kit();
|
||||
|
||||
if ( $active_kit->is_saving() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$active_kit->update_settings( [ $key => $value ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Scheme To Global
|
||||
*
|
||||
* Convert a given scheme value to its corresponding default global value
|
||||
*
|
||||
* @param string $type 'color'/'typography'
|
||||
* @param $value
|
||||
* @return mixed
|
||||
*/
|
||||
private function map_scheme_to_global( $type, $value ) {
|
||||
$schemes_to_globals_map = [
|
||||
'color' => [
|
||||
'1' => Global_Colors::COLOR_PRIMARY,
|
||||
'2' => Global_Colors::COLOR_SECONDARY,
|
||||
'3' => Global_Colors::COLOR_TEXT,
|
||||
'4' => Global_Colors::COLOR_ACCENT,
|
||||
],
|
||||
'typography' => [
|
||||
'1' => Global_Typography::TYPOGRAPHY_PRIMARY,
|
||||
'2' => Global_Typography::TYPOGRAPHY_SECONDARY,
|
||||
'3' => Global_Typography::TYPOGRAPHY_TEXT,
|
||||
'4' => Global_Typography::TYPOGRAPHY_ACCENT,
|
||||
],
|
||||
];
|
||||
|
||||
return $schemes_to_globals_map[ $type ][ $value ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Scheme to Default Global
|
||||
*
|
||||
* If a control has a scheme property, convert it to a default Global.
|
||||
*
|
||||
* @param $scheme - Control scheme property
|
||||
* @return array - Control/group control args
|
||||
* @since 3.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function convert_scheme_to_global( $scheme ) {
|
||||
if ( isset( $scheme['type'] ) && isset( $scheme['value'] ) ) {
|
||||
//_deprecated_argument( $args['scheme'], '3.0.0', 'Schemes are now deprecated - use $args[\'global\'] instead.' );
|
||||
return $this->map_scheme_to_global( $scheme['type'], $scheme['value'] );
|
||||
}
|
||||
|
||||
// Typography control 'scheme' properties usually only include the string with the typography value ('1'-'4').
|
||||
return $this->map_scheme_to_global( 'typography', $scheme );
|
||||
}
|
||||
|
||||
public function register_controls() {
|
||||
$controls_manager = Plugin::$instance->controls_manager;
|
||||
|
||||
$controls_manager->register_control( Repeater::CONTROL_TYPE, new Repeater() );
|
||||
}
|
||||
|
||||
public function is_custom_colors_enabled() {
|
||||
return ! get_option( 'elementor_disable_color_schemes' );
|
||||
}
|
||||
|
||||
public function is_custom_typography_enabled() {
|
||||
return ! get_option( 'elementor_disable_typography_schemes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add kit wrapper body class.
|
||||
*
|
||||
* It should be added even for non Elementor pages,
|
||||
* in order to support embedded templates.
|
||||
*/
|
||||
private function add_body_class() {
|
||||
$kit = $this->get_kit_for_frontend();
|
||||
|
||||
if ( $kit ) {
|
||||
Plugin::$instance->frontend->add_body_class( 'elementor-kit-' . $kit->get_main_id() );
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'elementor/documents/register', [ $this, 'register_document' ] );
|
||||
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
|
||||
add_filter( 'elementor/editor/footer', [ $this, 'render_panel_html' ] );
|
||||
add_action( 'elementor/frontend/after_enqueue_styles', [ $this, 'frontend_before_enqueue_styles' ], 0 );
|
||||
add_action( 'elementor/preview/enqueue_styles', [ $this, 'preview_enqueue_styles' ], 0 );
|
||||
add_action( 'elementor/controls/controls_registered', [ $this, 'register_controls' ] );
|
||||
|
||||
add_action( 'update_option_blogname', function ( $old_value, $value ) {
|
||||
$this->update_kit_settings_based_on_option( 'site_name', $value );
|
||||
}, 10, 2 );
|
||||
|
||||
add_action( 'update_option_blogdescription', function ( $old_value, $value ) {
|
||||
$this->update_kit_settings_based_on_option( 'site_description', $value );
|
||||
}, 10, 2 );
|
||||
|
||||
add_action( 'wp_head', function() {
|
||||
$this->add_body_class();
|
||||
} );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<script type="text/template" id="tmpl-elementor-kit-panel">
|
||||
<main id="elementor-kit__panel-content__wrapper" class="elementor-panel-content-wrapper"></main>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="tmpl-elementor-kit-panel-content">
|
||||
<div id="elementor-kit-panel-content-controls"></div>
|
||||
<#
|
||||
const tabConfig = $e.components.get( 'panel/global' ).getActiveTabConfig();
|
||||
if ( tabConfig.helpUrl ) { #>
|
||||
<div id="elementor-panel__editor__help">
|
||||
<a id="elementor-panel__editor__help__link" href="{{ tabConfig.helpUrl }}" target="_blank">
|
||||
<?php echo __( 'Need Help', 'elementor' ); ?>
|
||||
<i class="eicon-help-o"></i>
|
||||
</a>
|
||||
</div>
|
||||
<# } #>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="tmpl-elementor-global-style-repeater-row">
|
||||
<# let removeClass = 'remove',
|
||||
removeIcon = 'eicon-trash-o';
|
||||
|
||||
if ( ! itemActions.remove ) {
|
||||
removeClass += '--disabled';
|
||||
|
||||
removeIcon = 'eicon-disable-trash-o'
|
||||
}
|
||||
#>
|
||||
<div class="elementor-repeater-row-tool elementor-repeater-tool-{{{ removeClass }}}">
|
||||
<i class="{{{ removeIcon }}}" aria-hidden="true"></i>
|
||||
<# if ( itemActions.remove ) { #>
|
||||
<span class="elementor-screen-only"><?php echo __( 'Remove', 'elementor' ); ?></span>
|
||||
<# } #>
|
||||
</div>
|
||||
<div class="elementor-repeater-row-controls"></div>
|
||||
</script>
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger\Items;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Base implements Log_Item_Interface {
|
||||
|
||||
const FORMAT = 'date [type] message [meta]';
|
||||
const TRACE_FORMAT = '#key: file(line): class type function()';
|
||||
const TRACE_LIMIT = 5;
|
||||
|
||||
protected $date;
|
||||
protected $type;
|
||||
protected $message;
|
||||
protected $meta = [];
|
||||
|
||||
protected $times = 0;
|
||||
protected $times_dates = [];
|
||||
protected $args = [];
|
||||
|
||||
public function __construct( $args ) {
|
||||
$this->date = current_time( 'mysql' );
|
||||
$this->message = ! empty( $args['message'] ) ? esc_html( $args['message'] ) : '';
|
||||
$this->type = ! empty( $args['type'] ) ? $args['type'] : 'info';
|
||||
$this->meta = ! empty( $args['meta'] ) ? $args['meta'] : [];
|
||||
$this->args = $args;
|
||||
|
||||
$this->set_trace();
|
||||
}
|
||||
|
||||
public function __get( $name ) {
|
||||
if ( property_exists( $this, $name ) ) {
|
||||
return $this->{$name};
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
$vars = get_object_vars( $this );
|
||||
return strtr( static::FORMAT, $vars );
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'class' => get_class( $this ),
|
||||
'item' => [
|
||||
'date' => $this->date,
|
||||
'message' => $this->message,
|
||||
'type' => $this->type,
|
||||
'meta' => $this->meta,
|
||||
'times' => $this->times,
|
||||
'times_dates' => $this->times_dates,
|
||||
'args' => $this->args,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function deserialize( $properties ) {
|
||||
$this->date = ! empty( $properties['date'] ) && is_string( $properties['date'] ) ? $properties['date'] : '';
|
||||
$this->message = ! empty( $properties['message'] ) && is_string( $properties['message'] ) ? $properties['message'] : '';
|
||||
$this->type = ! empty( $properties['type'] ) && is_string( $properties['type'] ) ? $properties['type'] : '';
|
||||
$this->meta = ! empty( $properties['meta'] ) && is_array( $properties['meta'] ) ? $properties['meta'] : [];
|
||||
$this->times = ! empty( $properties['times'] ) && is_string( $properties['times'] ) ? $properties['times'] : '';
|
||||
$this->times_dates = ! empty( $properties['times_dates'] ) && is_array( $properties['times_dates'] ) ? $properties['times_dates'] : [];
|
||||
$this->args = ! empty( $properties['args'] ) && is_array( $properties['args'] ) ? $properties['args'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Log_Item_Interface | null
|
||||
*/
|
||||
public static function from_json( $str ) {
|
||||
$obj = json_decode( $str, true );
|
||||
if ( ! array_key_exists( 'class', $obj ) ) {
|
||||
return null;
|
||||
}
|
||||
$class = $obj['class'];
|
||||
if ( class_exists( $class ) ) {
|
||||
/** @var Base $item */
|
||||
$item = new $class( $obj['item']['message'] );
|
||||
$item->deserialize( $obj['item'] );
|
||||
return $item;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function to_formatted_string( $output_format = 'html' ) {
|
||||
$vars = get_object_vars( $this );
|
||||
$format = static::FORMAT;
|
||||
if ( 'html' === $output_format ) {
|
||||
$format = str_replace( 'message', '<strong>message</strong>', static::FORMAT );
|
||||
}
|
||||
if ( empty( $vars['meta'] ) ) {
|
||||
$format = str_replace( '[meta]', '', $format );
|
||||
} else {
|
||||
$vars['meta'] = stripslashes( var_export( $vars['meta'], true ) ); // @codingStandardsIgnoreLine
|
||||
}
|
||||
return strtr( $format, $vars );
|
||||
}
|
||||
|
||||
public function get_fingerprint() {
|
||||
$unique_key = $this->type . $this->message . var_export( $this->meta, true ); // @codingStandardsIgnoreLine
|
||||
//info messages are not be aggregated:
|
||||
if ( 'info' === $this->type ) {
|
||||
$unique_key .= $this->date;
|
||||
}
|
||||
return md5( $unique_key );
|
||||
}
|
||||
|
||||
public function increase_times( $item, $truncate = true ) {
|
||||
$this->times++;
|
||||
$this->times_dates[] = $item->date;
|
||||
|
||||
if ( $truncate && ( self::MAX_LOG_ENTRIES < count( $this->times_dates ) ) ) {
|
||||
$this->times_dates = array_slice( $this->times_dates, -self::MAX_LOG_ENTRIES );
|
||||
}
|
||||
}
|
||||
|
||||
public function format( $format = 'html' ) {
|
||||
$trace = $this->format_trace();
|
||||
if ( empty( $trace ) ) {
|
||||
return $this->to_formatted_string( $format );
|
||||
}
|
||||
$copy = clone $this;
|
||||
$copy->meta['trace'] = $trace;
|
||||
return $copy->to_formatted_string( $format );
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return 'Log';
|
||||
}
|
||||
|
||||
private function format_trace() {
|
||||
$trace = empty( $this->meta['trace'] ) ? '' : $this->meta['trace'];
|
||||
|
||||
if ( is_string( $trace ) ) {
|
||||
return $trace;
|
||||
}
|
||||
|
||||
$trace_str = '';
|
||||
foreach ( $trace as $key => $trace_line ) {
|
||||
$format = static::TRACE_FORMAT;
|
||||
$trace_line['key'] = $key;
|
||||
if ( empty( $trace_line['file'] ) ) {
|
||||
$format = str_replace( 'file(line): ', '', $format );
|
||||
}
|
||||
|
||||
$trace_str .= PHP_EOL . strtr( $format, $trace_line );
|
||||
$trace_str .= empty( $trace_line['args'] ) ? '' : var_export( $trace_line['args'], true ); // @codingStandardsIgnoreLine
|
||||
}
|
||||
|
||||
return $trace_str . PHP_EOL;
|
||||
}
|
||||
|
||||
private function set_trace() {
|
||||
if ( ! empty( $this->args['trace'] ) && true === $this->args['trace'] ) {
|
||||
$limit = empty( $this->args['trace_limit'] ) ? static::TRACE_LIMIT : $this->args['trace_limit'];
|
||||
|
||||
$stack = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // @codingStandardsIgnoreLine
|
||||
|
||||
while ( ! empty( $stack ) && ! empty( $stack[0]['file'] ) && ( false !== strpos( $stack[0]['file'], 'core' . DIRECTORY_SEPARATOR . 'logger' ) ) ) {
|
||||
array_shift( $stack );
|
||||
}
|
||||
|
||||
$this->meta['trace'] = array_slice( $stack, 0, $limit );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( is_array( $this->args ) ) {
|
||||
unset( $this->args['trace'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger\Items;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class File extends Base {
|
||||
|
||||
const FORMAT = 'date [type X times][file:line] message [meta]';
|
||||
|
||||
protected $file;
|
||||
protected $line;
|
||||
|
||||
public function __construct( $args ) {
|
||||
parent::__construct( $args );
|
||||
|
||||
$this->file = empty( $args['file'] ) ? '' : $args['file'];
|
||||
$this->line = empty( $args['line'] ) ? '' : $args['line'];
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
$json_arr = parent::jsonSerialize();
|
||||
$json_arr['file'] = $this->file;
|
||||
$json_arr['line'] = $this->line;
|
||||
return $json_arr;
|
||||
}
|
||||
|
||||
public function deserialize( $properties ) {
|
||||
parent::deserialize( $properties );
|
||||
$this->file = ! empty( $properties['file'] ) && is_string( $properties['file'] ) ? $properties['file'] : '';
|
||||
$this->line = ! empty( $properties['line'] ) && is_string( $properties['line'] ) ? $properties['line'] : '';
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return 'File';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger\Items;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class JS extends File {
|
||||
|
||||
const FORMAT = 'JS: date [type X times][file:line:column] message [meta]';
|
||||
|
||||
protected $column;
|
||||
|
||||
public function __construct( $args ) {
|
||||
parent::__construct( $args );
|
||||
$this->column = $args['column'];
|
||||
$this->file = $args['url'];
|
||||
$this->date = gmdate( 'Y-m-d H:i:s', $args['timestamp'] );
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
$json_arr = parent::jsonSerialize();
|
||||
$json_arr['column'] = $this->column;
|
||||
return $json_arr;
|
||||
}
|
||||
|
||||
public function deserialize( $properties ) {
|
||||
parent::deserialize( $properties );
|
||||
$this->column = ! empty( $properties['column'] ) && is_string( $properties['column'] ) ? $properties['column'] : '';
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return 'JS';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger\Items;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface Log_Item_Interface
|
||||
*
|
||||
* @package Elementor\Core\Logger
|
||||
*
|
||||
* @property string $date
|
||||
* @property string $type
|
||||
* @property string $message
|
||||
* @property int $times
|
||||
* @property array $meta
|
||||
* @property array $times_dates
|
||||
* @property array $args
|
||||
*
|
||||
*/
|
||||
|
||||
interface Log_Item_Interface extends \JsonSerializable {
|
||||
|
||||
const MAX_LOG_ENTRIES = 42;
|
||||
|
||||
/**
|
||||
* Log_Item_Interface constructor.
|
||||
*
|
||||
* @param array $args
|
||||
*/
|
||||
public function __construct( $args );
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __get( $name );
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString();
|
||||
|
||||
/**
|
||||
* @param $str
|
||||
* @return Log_Item_Interface | null
|
||||
*/
|
||||
public static function from_json( $str );
|
||||
|
||||
/**
|
||||
* @param string $format
|
||||
* @return string
|
||||
*/
|
||||
public function format( $format = 'html' );
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_fingerprint();
|
||||
|
||||
/**
|
||||
* @param Log_Item_Interface $item
|
||||
* @param bool $truncate
|
||||
*/
|
||||
public function increase_times( $item, $truncate = true );
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger\Items;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class PHP extends File {
|
||||
|
||||
const FORMAT = 'PHP: date [type X times][file::line] message [meta]';
|
||||
|
||||
public function get_name() {
|
||||
return 'PHP';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger;
|
||||
|
||||
use Elementor\Modules\System_Info\Reporters\Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor Log reporter.
|
||||
*
|
||||
* Elementor log reporter handler class is responsible for generating the
|
||||
* debug reports.
|
||||
*
|
||||
* @since 2.4.0
|
||||
*/
|
||||
class Log_Reporter extends Base {
|
||||
|
||||
const MAX_ENTRIES = 20;
|
||||
const CLEAR_LOG_ACTION = 'elementor-clear-log';
|
||||
|
||||
public function get_title() {
|
||||
$title = 'Log';
|
||||
|
||||
if ( 'html' === $this->_properties['format'] && empty( $_GET[ self::CLEAR_LOG_ACTION ] ) ) { // phpcs:ignore -- nonce validation is not require here.
|
||||
$nonce = wp_create_nonce( self::CLEAR_LOG_ACTION );
|
||||
$url = add_query_arg( [
|
||||
self::CLEAR_LOG_ACTION => 1,
|
||||
'_wpnonce' => $nonce,
|
||||
] );
|
||||
|
||||
$title .= '<a href="' . esc_url( $url ) . '#elementor-clear-log" class="box-title-tool">' . __( 'Clear Log', 'elementor' ) . '</a>';
|
||||
$title .= '<span id="elementor-clear-log"></span>';
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
public function get_fields() {
|
||||
return [
|
||||
'log_entries' => '',
|
||||
];
|
||||
}
|
||||
|
||||
public function get_log_entries() {
|
||||
/** @var \Elementor\Core\Logger\Manager $manager */
|
||||
$manager = Manager::instance();
|
||||
|
||||
/** @var \Elementor\Core\Logger\Loggers\Db $logger */
|
||||
$logger = $manager->get_logger( 'db' );
|
||||
|
||||
if ( ! empty( $_GET[ self::CLEAR_LOG_ACTION ] ) ) {
|
||||
if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], self::CLEAR_LOG_ACTION ) ) {
|
||||
wp_die( 'Invalid Nonce', 'Invalid Nonce', [
|
||||
'back_link' => true,
|
||||
] );
|
||||
}
|
||||
|
||||
$logger->clear();
|
||||
}
|
||||
|
||||
$log_string = 'No entries to display';
|
||||
$log_entries = $logger->get_formatted_log_entries( self::MAX_ENTRIES, false );
|
||||
|
||||
if ( ! empty( $log_entries ) ) {
|
||||
$entries_string = '';
|
||||
foreach ( $log_entries as $key => $log_entry ) {
|
||||
if ( $log_entry['count'] ) {
|
||||
$entries_string .= '<h3>' . sprintf( '%s: showing %s of %s', $key, $log_entry['count'], $log_entry['total_count'] ) . '</h3>';
|
||||
$entries_string .= '<div class="elementor-log-entries">' . $log_entry['entries'] . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $entries_string ) ) {
|
||||
$log_string = $entries_string;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'value' => $log_string,
|
||||
];
|
||||
}
|
||||
|
||||
public function get_raw_log_entries() {
|
||||
$log_string = 'No entries to display';
|
||||
|
||||
/** @var \Elementor\Core\Logger\Manager $manager */
|
||||
$manager = Manager::instance();
|
||||
$logger = $manager->get_logger();
|
||||
$log_entries = $logger->get_formatted_log_entries( self::MAX_ENTRIES, false );
|
||||
|
||||
if ( ! empty( $log_entries ) ) {
|
||||
$entries_string = PHP_EOL;
|
||||
foreach ( $log_entries as $key => $log_entry ) {
|
||||
if ( $log_entry['count'] ) {
|
||||
$entries_string .= sprintf( '%s: showing %s of %s', $key, $log_entry['count'], $log_entry['total_count'] ) . $log_entry['entries'] . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $entries_string ) ) {
|
||||
$log_string = $entries_string;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'value' => $log_string,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger\Loggers;
|
||||
|
||||
use Elementor\Core\Logger\Items\Base as Log_Item;
|
||||
use Elementor\Core\Logger\Items\Log_Item_Interface as Log_Item_Interface;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Base implements Logger_Interface {
|
||||
|
||||
abstract protected function save_log( Log_Item_Interface $item );
|
||||
|
||||
/**
|
||||
* @return Log_Item_Interface[]
|
||||
*/
|
||||
abstract public function get_log();
|
||||
|
||||
public function log( $item, $type = self::LEVEL_INFO, $args = [] ) {
|
||||
if ( ! $item instanceof Log_Item ) {
|
||||
$item = $this->create_item( $item, $type, $args );
|
||||
}
|
||||
$this->save_log( $item );
|
||||
}
|
||||
|
||||
public function info( $message, $args = [] ) {
|
||||
$this->log( $message, self::LEVEL_INFO, $args );
|
||||
}
|
||||
|
||||
public function notice( $message, $args = [] ) {
|
||||
$this->log( $message, self::LEVEL_NOTICE, $args );
|
||||
}
|
||||
|
||||
public function warning( $message, $args = [] ) {
|
||||
$this->log( $message, self::LEVEL_WARNING, $args );
|
||||
}
|
||||
|
||||
public function error( $message, $args = [] ) {
|
||||
$this->log( $message, self::LEVEL_ERROR, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @param array $args
|
||||
*
|
||||
* @return Log_Item_Interface
|
||||
*/
|
||||
private function create_item( $message, $type, $args = [] ) {
|
||||
$args['message'] = $message;
|
||||
$args['type'] = $type;
|
||||
|
||||
$item = new Log_Item( $args );
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function get_formatted_log_entries( $max_entries, $table = true ) {
|
||||
$entries = $this->get_log();
|
||||
|
||||
if ( empty( $entries ) ) {
|
||||
return [
|
||||
'All' => [
|
||||
'total_count' => 0,
|
||||
'count' => 0,
|
||||
'entries' => '',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$sorted_entries = [];
|
||||
$open_tag = $table ? '<tr><td>' : '';
|
||||
$close_tab = $table ? '</td></tr>' : PHP_EOL;
|
||||
|
||||
$format = $table ? 'html' : 'raw';
|
||||
|
||||
foreach ( $entries as $entry ) {
|
||||
/** @var Log_Item $entry */
|
||||
$sorted_entries[ $entry->get_name() ][] = $open_tag . $entry->format( $format ) . $close_tab;
|
||||
}
|
||||
|
||||
$formatted_entries = [];
|
||||
foreach ( $sorted_entries as $key => $sorted_entry ) {
|
||||
$formatted_entries[ $key ]['total_count'] = count( $sorted_entry );
|
||||
$formatted_entries[ $key ]['count'] = count( $sorted_entry );
|
||||
$sorted_entry = array_slice( $sorted_entry, -$max_entries );
|
||||
$formatted_entries[ $key ]['count'] = count( $sorted_entry );
|
||||
$formatted_entries[ $key ]['entries'] = implode( $sorted_entry );
|
||||
}
|
||||
return $formatted_entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger\Loggers;
|
||||
|
||||
use Elementor\Core\Logger\Items\Log_Item_Interface as Log_Item;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Db extends Base {
|
||||
|
||||
public function save_log( Log_Item $item ) {
|
||||
$log = $this->maybe_truncate_log();
|
||||
|
||||
$id = $item->get_fingerprint();
|
||||
|
||||
if ( empty( $log[ $id ] ) ) {
|
||||
$log[ $id ] = $item;
|
||||
}
|
||||
|
||||
$log[ $id ]->increase_times( $item );
|
||||
|
||||
update_option( self::LOG_NAME, $log, 'no' );
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
delete_option( self::LOG_NAME );
|
||||
}
|
||||
|
||||
private function maybe_truncate_log() {
|
||||
/** @var Log_Item[] $log */
|
||||
$log = $this->get_log();
|
||||
|
||||
if ( Log_Item::MAX_LOG_ENTRIES < count( $log ) ) {
|
||||
$log = array_slice( $log, -Log_Item::MAX_LOG_ENTRIES );
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
public function get_log() {
|
||||
// Clear cache.
|
||||
wp_cache_delete( self::LOG_NAME, 'options' );
|
||||
|
||||
$log = get_option( self::LOG_NAME, [] );
|
||||
|
||||
// In case the DB log is corrupted.
|
||||
if ( ! is_array( $log ) ) {
|
||||
$log = [];
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger\Loggers;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
interface Logger_Interface {
|
||||
const LEVEL_INFO = 'info';
|
||||
const LEVEL_NOTICE = 'notice';
|
||||
const LEVEL_WARNING = 'warning';
|
||||
const LEVEL_ERROR = 'error';
|
||||
const LOG_NAME = 'elementor_log';
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @param array $meta
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function log( $message, $type = self::LEVEL_INFO, $meta = [] );
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $meta
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function info( $message, $meta = [] );
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $meta
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notice( $message, $meta = [] );
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $meta
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function warning( $message, $meta = [] );
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $meta
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function error( $message, $meta = [] );
|
||||
|
||||
/**
|
||||
* @param int $max_entries
|
||||
* @param bool $table use <td> in format
|
||||
*
|
||||
* @return array [ 'key' => [ 'total_count' => int, 'count' => int, 'entries' => Log_Item[] ] ]
|
||||
*/
|
||||
public function get_formatted_log_entries( $max_entries, $table = true );
|
||||
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Logger;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Core\Common\Modules\Ajax\Module;
|
||||
use Elementor\Core\Logger\Loggers\Logger_Interface;
|
||||
use Elementor\Core\Logger\Items\PHP;
|
||||
use Elementor\Core\Logger\Items\JS;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Modules\System_Info\Module as System_Info;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Manager extends BaseModule {
|
||||
|
||||
protected $loggers = [];
|
||||
|
||||
protected $default_logger = '';
|
||||
|
||||
public function get_name() {
|
||||
return 'log';
|
||||
}
|
||||
|
||||
public function shutdown( $last_error = null ) {
|
||||
if ( ! $last_error ) {
|
||||
$last_error = error_get_last();
|
||||
}
|
||||
|
||||
if ( ! $last_error ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $last_error['file'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$error_path = ( wp_normalize_path( $last_error['file'] ) );
|
||||
// `untrailingslashit` in order to include other plugins prefixed with elementor.
|
||||
$elementor_path = untrailingslashit( wp_normalize_path( ELEMENTOR_PATH ) );
|
||||
|
||||
if ( false === strpos( $error_path, $elementor_path ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$last_error['type'] = $this->get_log_type_from_php_error( $last_error['type'] );
|
||||
$last_error['trace'] = true;
|
||||
|
||||
$item = new PHP( $last_error );
|
||||
|
||||
$this->get_logger()->log( $item );
|
||||
}
|
||||
|
||||
public function rest_error_handler( $error_number, $error_message, $error_file, $error_line ) {
|
||||
$error = new \WP_Error( $error_number, $error_message, [
|
||||
'type' => $error_number,
|
||||
'message' => $error_message,
|
||||
'file' => $error_file,
|
||||
'line' => $error_line,
|
||||
] );
|
||||
|
||||
// Notify $e.data.
|
||||
if ( ! headers_sent() ) {
|
||||
header( 'Content-Type: application/json; charset=UTF-8' );
|
||||
|
||||
http_response_code( 500 );
|
||||
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
echo wp_json_encode( $error->get_error_data() );
|
||||
} else {
|
||||
echo wp_json_encode( [
|
||||
'message' => 'Server error, see Elementor => System Info',
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
$this->shutdown( $error->get_error_data() );
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
public function add_system_info_report() {
|
||||
System_Info::add_report(
|
||||
'log', [
|
||||
'file_name' => __DIR__ . '/log-reporter.php',
|
||||
'class_name' => __NAMESPACE__ . '\Log_Reporter',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Javascript log.
|
||||
*
|
||||
* Log Elementor errors and save them in the database.
|
||||
*
|
||||
* Fired by `wp_ajax_elementor_js_log` action.
|
||||
*
|
||||
*/
|
||||
public function js_log() {
|
||||
/** @var Module $ajax */
|
||||
$ajax = Plugin::$instance->common->get_component( 'ajax' );
|
||||
|
||||
if ( ! $ajax->verify_request_nonce() || empty( $_POST['data'] ) ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
|
||||
array_walk_recursive( $_POST['data'], function( &$value ) {
|
||||
$value = sanitize_text_field( $value );
|
||||
} );
|
||||
|
||||
foreach ( $_POST['data'] as $error ) {
|
||||
$error['type'] = Logger_Interface::LEVEL_ERROR;
|
||||
|
||||
if ( ! empty( $error['customFields'] ) ) {
|
||||
$error['meta'] = $error['customFields'];
|
||||
}
|
||||
|
||||
$item = new JS( $error );
|
||||
$this->get_logger()->log( $item );
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
public function register_logger( $name, $class ) {
|
||||
$this->loggers[ $name ] = $class;
|
||||
}
|
||||
|
||||
public function set_default_logger( $name ) {
|
||||
if ( ! empty( $this->loggers[ $name ] ) ) {
|
||||
$this->default_logger = $name;
|
||||
}
|
||||
}
|
||||
|
||||
public function register_default_loggers() {
|
||||
$this->register_logger( 'db', 'Elementor\Core\Logger\Loggers\Db' );
|
||||
$this->set_default_logger( 'db' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Logger_Interface
|
||||
*/
|
||||
public function get_logger( $name = '' ) {
|
||||
$this->register_loggers();
|
||||
|
||||
if ( empty( $name ) || ! isset( $this->loggers[ $name ] ) ) {
|
||||
$name = $this->default_logger;
|
||||
}
|
||||
|
||||
if ( ! $this->get_component( $name ) ) {
|
||||
$this->add_component( $name, new $this->loggers[ $name ]() );
|
||||
}
|
||||
|
||||
return $this->get_component( $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $args
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function log( $message, $args = [] ) {
|
||||
$this->get_logger()->log( $message, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $args
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function info( $message, $args = [] ) {
|
||||
$this->get_logger()->info( $message, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $args
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notice( $message, $args = [] ) {
|
||||
$this->get_logger()->notice( $message, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $args
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function warning( $message, $args = [] ) {
|
||||
$this->get_logger()->warning( $message, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $args
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function error( $message, $args = [] ) {
|
||||
$this->get_logger()->error( $message, $args );
|
||||
}
|
||||
|
||||
private function get_log_type_from_php_error( $type ) {
|
||||
$error_map = [
|
||||
E_CORE_ERROR => Logger_Interface::LEVEL_ERROR,
|
||||
E_ERROR => Logger_Interface::LEVEL_ERROR,
|
||||
E_USER_ERROR => Logger_Interface::LEVEL_ERROR,
|
||||
E_COMPILE_ERROR => Logger_Interface::LEVEL_ERROR,
|
||||
E_RECOVERABLE_ERROR => Logger_Interface::LEVEL_ERROR,
|
||||
E_PARSE => Logger_Interface::LEVEL_ERROR,
|
||||
E_STRICT => Logger_Interface::LEVEL_ERROR,
|
||||
|
||||
E_WARNING => Logger_Interface::LEVEL_WARNING,
|
||||
E_USER_WARNING => Logger_Interface::LEVEL_WARNING,
|
||||
E_CORE_WARNING => Logger_Interface::LEVEL_WARNING,
|
||||
E_COMPILE_WARNING => Logger_Interface::LEVEL_WARNING,
|
||||
|
||||
E_NOTICE => Logger_Interface::LEVEL_NOTICE,
|
||||
E_USER_NOTICE => Logger_Interface::LEVEL_NOTICE,
|
||||
E_DEPRECATED => Logger_Interface::LEVEL_NOTICE,
|
||||
E_USER_DEPRECATED => Logger_Interface::LEVEL_NOTICE,
|
||||
];
|
||||
|
||||
return isset( $error_map[ $type ] ) ? $error_map[ $type ] : Logger_Interface::LEVEL_ERROR;
|
||||
}
|
||||
|
||||
private function register_loggers() {
|
||||
if ( ! did_action( 'elementor/loggers/register' ) ) {
|
||||
do_action( 'elementor/loggers/register', $this );
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
register_shutdown_function( [ $this, 'shutdown' ] );
|
||||
|
||||
add_action( 'admin_init', [ $this, 'add_system_info_report' ], 80 );
|
||||
|
||||
add_action( 'wp_ajax_elementor_js_log', [ $this, 'js_log' ] );
|
||||
|
||||
add_action( 'elementor/loggers/register', [ $this, 'register_default_loggers' ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
namespace Elementor\Core;
|
||||
|
||||
use Elementor\Core\Base\Module;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor modules manager.
|
||||
*
|
||||
* Elementor modules manager handler class is responsible for registering and
|
||||
* managing Elementor modules.
|
||||
*
|
||||
* @since 1.6.0
|
||||
*/
|
||||
class Modules_Manager {
|
||||
|
||||
/**
|
||||
* Registered modules.
|
||||
*
|
||||
* Holds the list of all the registered modules.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access public
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $modules = [];
|
||||
|
||||
/**
|
||||
* Modules manager constructor.
|
||||
*
|
||||
* Initializing the Elementor modules manager.
|
||||
*
|
||||
* @since 1.6.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
$modules_namespace_prefix = $this->get_modules_namespace_prefix();
|
||||
|
||||
foreach ( $this->get_modules_names() as $module_name ) {
|
||||
$class_name = str_replace( '-', ' ', $module_name );
|
||||
|
||||
$class_name = str_replace( ' ', '', ucwords( $class_name ) );
|
||||
|
||||
$class_name = $modules_namespace_prefix . '\\Modules\\' . $class_name . '\Module';
|
||||
|
||||
/** @var Module $class_name */
|
||||
if ( $class_name::is_active() ) {
|
||||
$this->modules[ $module_name ] = $class_name::instance();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get modules names.
|
||||
*
|
||||
* Retrieve the modules names.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @return string[] Modules names.
|
||||
*/
|
||||
public function get_modules_names() {
|
||||
return [
|
||||
'admin-bar',
|
||||
'history',
|
||||
'library',
|
||||
'dynamic-tags',
|
||||
'page-templates',
|
||||
'gutenberg',
|
||||
'wp-cli',
|
||||
'safe-mode',
|
||||
'usage',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get modules.
|
||||
*
|
||||
* Retrieve all the registered modules or a specific module.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param string $module_name Module name.
|
||||
*
|
||||
* @return null|Module|Module[] All the registered modules or a specific module.
|
||||
*/
|
||||
public function get_modules( $module_name ) {
|
||||
if ( $module_name ) {
|
||||
if ( isset( $this->modules[ $module_name ] ) ) {
|
||||
return $this->modules[ $module_name ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get modules namespace prefix.
|
||||
*
|
||||
* Retrieve the modules namespace prefix.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*
|
||||
* @return string Modules namespace prefix.
|
||||
*/
|
||||
protected function get_modules_namespace_prefix() {
|
||||
return 'Elementor';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Core\Responsive\Files;
|
||||
|
||||
use Elementor\Core\Files\Base;
|
||||
use Elementor\Core\Responsive\Responsive;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Frontend extends Base {
|
||||
|
||||
const META_KEY = 'elementor-custom-breakpoints-files';
|
||||
|
||||
private $template_file;
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct( $file_name, $template_file = null ) {
|
||||
$this->template_file = $template_file;
|
||||
|
||||
parent::__construct( $file_name );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
*/
|
||||
public function parse_content() {
|
||||
$breakpoints = Responsive::get_breakpoints();
|
||||
|
||||
$breakpoints_keys = array_keys( $breakpoints );
|
||||
|
||||
$file_content = file_get_contents( $this->template_file );
|
||||
|
||||
$file_content = preg_replace_callback( '/ELEMENTOR_SCREEN_([A-Z]+)_([A-Z]+)/', function ( $placeholder_data ) use ( $breakpoints_keys, $breakpoints ) {
|
||||
$breakpoint_index = array_search( strtolower( $placeholder_data[1] ), $breakpoints_keys );
|
||||
|
||||
$is_max_point = 'MAX' === $placeholder_data[2];
|
||||
|
||||
if ( $is_max_point ) {
|
||||
$breakpoint_index++;
|
||||
}
|
||||
|
||||
$value = $breakpoints[ $breakpoints_keys[ $breakpoint_index ] ];
|
||||
|
||||
if ( $is_max_point ) {
|
||||
$value--;
|
||||
}
|
||||
|
||||
return $value . 'px';
|
||||
}, $file_content );
|
||||
|
||||
return $file_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load meta.
|
||||
*
|
||||
* Retrieve the file meta data.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function load_meta() {
|
||||
$option = $this->load_meta_option();
|
||||
|
||||
$file_meta_key = $this->get_file_meta_key();
|
||||
|
||||
if ( empty( $option[ $file_meta_key ] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $option[ $file_meta_key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update meta.
|
||||
*
|
||||
* Update the file meta data.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
*
|
||||
* @param array $meta New meta data.
|
||||
*/
|
||||
protected function update_meta( $meta ) {
|
||||
$option = $this->load_meta_option();
|
||||
|
||||
$option[ $this->get_file_meta_key() ] = $meta;
|
||||
|
||||
update_option( static::META_KEY, $option );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete meta.
|
||||
*
|
||||
* Delete the file meta data.
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function delete_meta() {
|
||||
$option = $this->load_meta_option();
|
||||
|
||||
$file_meta_key = $this->get_file_meta_key();
|
||||
|
||||
if ( isset( $option[ $file_meta_key ] ) ) {
|
||||
unset( $option[ $file_meta_key ] );
|
||||
}
|
||||
|
||||
if ( $option ) {
|
||||
update_option( static::META_KEY, $option );
|
||||
} else {
|
||||
delete_option( static::META_KEY );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access private
|
||||
*/
|
||||
private function get_file_meta_key() {
|
||||
return pathinfo( $this->get_file_name(), PATHINFO_FILENAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access private
|
||||
*/
|
||||
private function load_meta_option() {
|
||||
$option = get_option( static::META_KEY );
|
||||
|
||||
if ( ! $option ) {
|
||||
$option = [];
|
||||
}
|
||||
|
||||
return $option;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
namespace Elementor\Core\Responsive;
|
||||
|
||||
use Elementor\Core\Responsive\Files\Frontend;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor responsive.
|
||||
*
|
||||
* Elementor responsive handler class is responsible for setting up Elementor
|
||||
* responsive breakpoints.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Responsive {
|
||||
|
||||
/**
|
||||
* The Elementor breakpoint prefix.
|
||||
*/
|
||||
const BREAKPOINT_OPTION_PREFIX = 'viewport_';
|
||||
|
||||
/**
|
||||
* Default breakpoints.
|
||||
*
|
||||
* Holds the default responsive breakpoints.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @var array Default breakpoints.
|
||||
*/
|
||||
private static $default_breakpoints = [
|
||||
'xs' => 0,
|
||||
'sm' => 480,
|
||||
'md' => 768,
|
||||
'lg' => 1025,
|
||||
'xl' => 1440,
|
||||
'xxl' => 1600,
|
||||
];
|
||||
|
||||
/**
|
||||
* Editable breakpoint keys.
|
||||
*
|
||||
* Holds the editable breakpoint keys.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @var array Editable breakpoint keys.
|
||||
*/
|
||||
private static $editable_breakpoints_keys = [
|
||||
'md',
|
||||
'lg',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get default breakpoints.
|
||||
*
|
||||
* Retrieve the default responsive breakpoints.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array Default breakpoints.
|
||||
*/
|
||||
public static function get_default_breakpoints() {
|
||||
return self::$default_breakpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get editable breakpoints.
|
||||
*
|
||||
* Retrieve the editable breakpoints.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array Editable breakpoints.
|
||||
*/
|
||||
public static function get_editable_breakpoints() {
|
||||
return array_intersect_key( self::get_breakpoints(), array_flip( self::$editable_breakpoints_keys ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get breakpoints.
|
||||
*
|
||||
* Retrieve the responsive breakpoints.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return array Responsive breakpoints.
|
||||
*/
|
||||
public static function get_breakpoints() {
|
||||
return array_reduce(
|
||||
array_keys( self::$default_breakpoints ), function( $new_array, $breakpoint_key ) {
|
||||
if ( ! in_array( $breakpoint_key, self::$editable_breakpoints_keys ) ) {
|
||||
$new_array[ $breakpoint_key ] = self::$default_breakpoints[ $breakpoint_key ];
|
||||
} else {
|
||||
$saved_option = Plugin::$instance->kits_manager->get_current_settings( self::BREAKPOINT_OPTION_PREFIX . $breakpoint_key );
|
||||
|
||||
$new_array[ $breakpoint_key ] = $saved_option ? (int) $saved_option : self::$default_breakpoints[ $breakpoint_key ];
|
||||
}
|
||||
|
||||
return $new_array;
|
||||
}, []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function has_custom_breakpoints() {
|
||||
return ! ! array_diff( self::$default_breakpoints, self::get_breakpoints() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function get_stylesheet_templates_path() {
|
||||
return ELEMENTOR_ASSETS_PATH . 'css/templates/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function compile_stylesheet_templates() {
|
||||
foreach ( self::get_stylesheet_templates() as $file_name => $template_path ) {
|
||||
$file = new Frontend( $file_name, $template_path );
|
||||
|
||||
$file->update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.1.0
|
||||
* @access private
|
||||
* @static
|
||||
*/
|
||||
private static function get_stylesheet_templates() {
|
||||
$templates_paths = glob( self::get_stylesheet_templates_path() . '*.css' );
|
||||
|
||||
$templates = [];
|
||||
|
||||
foreach ( $templates_paths as $template_path ) {
|
||||
$file_name = 'custom-' . basename( $template_path );
|
||||
|
||||
$templates[ $file_name ] = $template_path;
|
||||
}
|
||||
|
||||
return apply_filters( 'elementor/core/responsive/get_stylesheet_templates', $templates );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
namespace Elementor\Core\RoleManager;
|
||||
|
||||
use Elementor\Settings_Page;
|
||||
use Elementor\Settings;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Role_Manager extends Settings_Page {
|
||||
|
||||
const PAGE_ID = 'elementor-role-manager';
|
||||
|
||||
const ROLE_MANAGER_OPTION_NAME = 'exclude_user_roles';
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_role_manager_options() {
|
||||
return get_option( 'elementor_' . self::ROLE_MANAGER_OPTION_NAME, [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function get_page_title() {
|
||||
return __( 'Role Manager', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function register_admin_menu() {
|
||||
add_submenu_page(
|
||||
Settings::PAGE_ID,
|
||||
$this->get_page_title(),
|
||||
$this->get_page_title(),
|
||||
'manage_options',
|
||||
self::PAGE_ID,
|
||||
[ $this, 'display_settings_page' ]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access protected
|
||||
*/
|
||||
protected function create_tabs() {
|
||||
$validation_class = 'Elementor\Settings_Validations';
|
||||
return [
|
||||
'general' => [
|
||||
'label' => __( 'General', 'elementor' ),
|
||||
'sections' => [
|
||||
'tools' => [
|
||||
'fields' => [
|
||||
'exclude_user_roles' => [
|
||||
'label' => __( 'Exclude Roles', 'elementor' ),
|
||||
'field_args' => [
|
||||
'type' => 'checkbox_list_roles',
|
||||
'exclude' => [ 'super_admin', 'administrator' ],
|
||||
],
|
||||
'setting_args' => [
|
||||
'sanitize_callback' => [ $validation_class, 'checkbox_list' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function display_settings_page() {
|
||||
$this->get_tabs();
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php echo esc_html( $this->get_page_title() ); ?></h1>
|
||||
|
||||
<div id="elementor-role-manager">
|
||||
<h3><?php echo __( 'Manage What Your Users Can Edit In Elementor', 'elementor' ); ?></h3>
|
||||
<form id="elementor-settings-form" method="post" action="options.php">
|
||||
<?php
|
||||
settings_fields( static::PAGE_ID );
|
||||
echo '<div class="elementor-settings-form-page elementor-active">';
|
||||
foreach ( get_editable_roles() as $role_slug => $role_data ) {
|
||||
if ( 'administrator' === $role_slug ) {
|
||||
continue;
|
||||
}
|
||||
$this->display_role_controls( $role_slug, $role_data );
|
||||
}
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
</div><!-- /.wrap -->
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*
|
||||
* @param string $role_slug The role slug.
|
||||
* @param array $role_data An array with role data.
|
||||
*/
|
||||
private function display_role_controls( $role_slug, $role_data ) {
|
||||
static $excluded_options = false;
|
||||
if ( false === $excluded_options ) {
|
||||
$excluded_options = $this->get_role_manager_options();
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="elementor-role-row <?php echo esc_attr( $role_slug ); ?>">
|
||||
<div class="elementor-role-label">
|
||||
<span class="elementor-role-name"><?php echo esc_html( $role_data['name'] ); ?></span>
|
||||
<span data-excluded-label="<?php esc_attr_e( 'Role Excluded', 'elementor' ); ?>" class="elementor-role-excluded-indicator"></span>
|
||||
<span class="elementor-role-toggle"><span class="dashicons dashicons-arrow-down"></span></span>
|
||||
</div>
|
||||
<div class="elementor-role-controls hidden">
|
||||
<div class="elementor-role-control">
|
||||
<label>
|
||||
<input type="checkbox" name="elementor_exclude_user_roles[]" value="<?php echo esc_attr( $role_slug ); ?>"<?php checked( in_array( $role_slug, $excluded_options, true ), true ); ?>>
|
||||
<?php echo __( 'No access to editor', 'elementor' ); ?>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<?php
|
||||
/**
|
||||
* Role restrictions controls.
|
||||
*
|
||||
* Fires after the role manager checkbox that allows the user to
|
||||
* exclude the role.
|
||||
*
|
||||
* This filter allows developers to add custom controls to the role
|
||||
* manager.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param string $role_slug The role slug.
|
||||
* @param array $role_data An array with role data.
|
||||
*/
|
||||
do_action( 'elementor/role/restrictions/controls', $role_slug, $role_data );
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_go_pro_link_html() {
|
||||
$pro_link = Utils::get_pro_link( 'https://elementor.com/pro/?utm_source=wp-role-manager&utm_campaign=gopro&utm_medium=wp-dash' );
|
||||
?>
|
||||
<div class="elementor-role-go-pro">
|
||||
<div class="elementor-role-go-pro__desc"><?php echo __( 'Want to give access only to content?', 'elementor' ); ?></div>
|
||||
<div class="elementor-role-go-pro__link"><a class="elementor-button elementor-button-default elementor-button-go-pro" target="_blank" href="<?php echo esc_url( $pro_link ); ?>"><?php echo __( 'Go Pro', 'elementor' ); ?></a></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function get_user_restrictions_array() {
|
||||
$user = wp_get_current_user();
|
||||
$user_roles = $user->roles;
|
||||
$options = $this->get_user_restrictions();
|
||||
$restrictions = [];
|
||||
if ( empty( $options ) ) {
|
||||
return $restrictions;
|
||||
}
|
||||
|
||||
foreach ( $user_roles as $role ) {
|
||||
if ( ! isset( $options[ $role ] ) ) {
|
||||
continue;
|
||||
}
|
||||
$restrictions = array_merge( $restrictions, $options[ $role ] );
|
||||
}
|
||||
return array_unique( $restrictions );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access private
|
||||
*/
|
||||
private function get_user_restrictions() {
|
||||
static $restrictions = false;
|
||||
if ( ! $restrictions ) {
|
||||
$restrictions = [];
|
||||
|
||||
/**
|
||||
* Editor user restrictions.
|
||||
*
|
||||
* Filters the user restrictions in the editor.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*
|
||||
* @param array $restrictions User restrictions.
|
||||
*/
|
||||
$restrictions = apply_filters( 'elementor/editor/user/restrictions', $restrictions );
|
||||
}
|
||||
return $restrictions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*
|
||||
* @param $capability
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function user_can( $capability ) {
|
||||
$options = $this->get_user_restrictions_array();
|
||||
|
||||
if ( in_array( $capability, $options, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
add_action( 'admin_menu', [ $this, 'register_admin_menu' ], 100 );
|
||||
add_action( 'elementor/role/restrictions/controls', [ $this, 'get_go_pro_link_html' ] );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user