first commit

This commit is contained in:
DESKTOP-GBA0BK8\Admin
2023-04-08 12:19:53 -04:00
commit 7c8c8b1c76
4586 changed files with 2050693 additions and 0 deletions
@@ -0,0 +1,127 @@
<?php
namespace Elementor\Modules\AdminBar;
use Elementor\Core\Base\Document;
use Elementor\Core\Base\App as BaseApp;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseApp {
/**
* @var Document[]
*/
private $documents = [];
/**
* @return bool
*/
public static function is_active() {
return is_admin_bar_showing();
}
/**
* @return string
*/
public function get_name() {
return 'admin-bar';
}
/**
* Collect the documents that was rendered in the current page.
*
* @param Document $document
* @param $is_excerpt
*/
public function add_document_to_admin_bar( Document $document, $is_excerpt ) {
if (
$is_excerpt ||
! $document::get_property( 'show_on_admin_bar' ) ||
! $document->is_editable_by_current_user()
) {
return;
}
$this->documents[ $document->get_main_id() ] = $document;
}
/**
* Scripts for module.
*/
public function enqueue_scripts() {
if ( empty( $this->documents ) ) {
return;
}
wp_enqueue_script(
'elementor-admin-bar',
$this->get_js_assets_url( 'admin-bar' ),
[ 'elementor-common' ],
ELEMENTOR_VERSION,
true
);
$this->print_config( 'elementor-admin-bar' );
}
/**
* Creates admin bar menu items config.
*
* @return array
*/
public function get_init_settings() {
$settings = [];
if ( ! empty( $this->documents ) ) {
$settings['elementor_edit_page'] = $this->get_edit_button_config();
}
/**
* Register admin_bar config to parse later in the frontend and add to the admin bar with JS
*
* @param array $settings the admin_bar config
*
* @since 3.0.0
*/
return apply_filters( 'elementor/frontend/admin_bar/settings', $settings );
}
/**
* Creates the config for 'Edit with elementor' menu item.
*
* @return array
*/
private function get_edit_button_config() {
$queried_object_id = get_queried_object_id();
$href = null;
if ( is_singular() && isset( $this->documents[ $queried_object_id ] ) ) {
$href = $this->documents[ $queried_object_id ]->get_edit_url();
unset( $this->documents[ $queried_object_id ] );
}
return [
'id' => 'elementor_edit_page',
'title' => __( 'Edit with Elementor', 'elementor' ),
'href' => $href,
'children' => array_map( function ( $document ) {
return [
'id' => "elementor_edit_doc_{$document->get_main_id()}",
'title' => $document->get_post()->post_title,
'sub_title' => $document::get_title(),
'href' => $document->get_edit_url(),
];
}, $this->documents ),
];
}
/**
* Module constructor.
*/
public function __construct() {
add_action( 'elementor/frontend/get_builder_content', [ $this, 'add_document_to_admin_bar' ], 10, 2 );
add_action( 'wp_footer', [ $this, 'enqueue_scripts' ] );
}
}
@@ -0,0 +1,160 @@
<?php
namespace Elementor\Modules\DynamicTags;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\DynamicTags\Manager;
use Elementor\Core\DynamicTags\Tag;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor dynamic tags module.
*
* Elementor dynamic tags module handler class is responsible for registering
* and managing Elementor dynamic tags modules.
*
* @since 2.0.0
*/
class Module extends BaseModule {
/**
* Base dynamic tag group.
*/
const BASE_GROUP = 'base';
/**
* Dynamic tags text category.
*/
const TEXT_CATEGORY = 'text';
/**
* Dynamic tags URL category.
*/
const URL_CATEGORY = 'url';
/**
* Dynamic tags image category.
*/
const IMAGE_CATEGORY = 'image';
/**
* Dynamic tags media category.
*/
const MEDIA_CATEGORY = 'media';
/**
* Dynamic tags post meta category.
*/
const POST_META_CATEGORY = 'post_meta';
/**
* Dynamic tags gallery category.
*/
const GALLERY_CATEGORY = 'gallery';
/**
* Dynamic tags number category.
*/
const NUMBER_CATEGORY = 'number';
/**
* Dynamic tags number category.
*/
const COLOR_CATEGORY = 'color';
/**
* Dynamic tags module constructor.
*
* Initializing Elementor dynamic tags module.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
$this->register_groups();
add_action( 'elementor/dynamic_tags/register_tags', [ $this, 'register_tags' ] );
}
/**
* Get module name.
*
* Retrieve the dynamic tags module name.
*
* @since 2.0.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'dynamic_tags';
}
/**
* Get classes names.
*
* Retrieve the dynamic tag classes names.
*
* @since 2.0.0
* @access public
*
* @return array Tag dynamic tag classes names.
*/
public function get_tag_classes_names() {
return [];
}
/**
* Get groups.
*
* Retrieve the dynamic tag groups.
*
* @since 2.0.0
* @access public
*
* @return array Tag dynamic tag groups.
*/
public function get_groups() {
return [
self::BASE_GROUP => [
'title' => 'Base Tags',
],
];
}
/**
* Register groups.
*
* Add all the available tag groups.
*
* @since 2.0.0
* @access private
*/
private function register_groups() {
foreach ( $this->get_groups() as $group_name => $group_settings ) {
Plugin::$instance->dynamic_tags->register_group( $group_name, $group_settings );
}
}
/**
* Register tags.
*
* Add all the available dynamic tags.
*
* @since 2.0.0
* @access public
*
* @param Manager $dynamic_tags
*/
public function register_tags( $dynamic_tags ) {
foreach ( $this->get_tag_classes_names() as $tag_class ) {
/** @var Tag $class_name */
$class_name = $this->get_reflection()->getNamespaceName() . '\Tags\\' . $tag_class;
$dynamic_tags->register_tag( $class_name );
}
}
}
@@ -0,0 +1,131 @@
<?php
namespace Elementor\Modules\Gutenberg;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Plugin;
use Elementor\User;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
protected $is_gutenberg_editor_active = false;
/**
* @since 2.1.0
* @access public
*/
public function get_name() {
return 'gutenberg';
}
/**
* @since 2.1.0
* @access public
* @static
*/
public static function is_active() {
return function_exists( 'register_block_type' );
}
/**
* @since 2.1.0
* @access public
*/
public function register_elementor_rest_field() {
register_rest_field( get_post_types( '', 'names' ),
'gutenberg_elementor_mode', [
'update_callback' => function( $request_value, $object ) {
if ( ! User::is_current_user_can_edit( $object->ID ) ) {
return false;
}
Plugin::$instance->db->set_is_elementor_page( $object->ID, false );
return true;
},
]
);
}
/**
* @since 2.1.0
* @access public
*/
public function enqueue_assets() {
$document = Plugin::$instance->documents->get( get_the_ID() );
if ( ! $document || ! $document->is_editable_by_current_user() ) {
return;
}
$this->is_gutenberg_editor_active = true;
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
wp_enqueue_script( 'elementor-gutenberg', ELEMENTOR_ASSETS_URL . 'js/gutenberg' . $suffix . '.js', [ 'jquery' ], ELEMENTOR_VERSION, true );
$elementor_settings = [
'isElementorMode' => $document->is_built_with_elementor(),
'editLink' => $document->get_edit_url(),
];
Utils::print_js_config( 'elementor-gutenberg', 'ElementorGutenbergSettings', $elementor_settings );
}
/**
* @since 2.1.0
* @access public
*/
public function print_admin_js_template() {
if ( ! $this->is_gutenberg_editor_active ) {
return;
}
?>
<script id="elementor-gutenberg-button-switch-mode" type="text/html">
<div id="elementor-switch-mode">
<button id="elementor-switch-mode-button" type="button" class="button button-primary button-large">
<span class="elementor-switch-mode-on"><?php echo __( '&#8592; 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>
</script>
<script id="elementor-gutenberg-panel" type="text/html">
<div id="elementor-editor"><a id="elementor-go-to-edit-page-link" href="#">
<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>
</script>
<?php
}
/**
* @since 2.1.0
* @access public
*/
public function __construct() {
add_action( 'rest_api_init', [ $this, 'register_elementor_rest_field' ] );
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_assets' ] );
add_action( 'admin_footer', [ $this, 'print_admin_js_template' ] );
}
}
@@ -0,0 +1,95 @@
<?php
namespace Elementor\Modules\History;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor history module.
*
* Elementor history module handler class is responsible for registering and
* managing Elementor history modules.
*
* @since 1.7.0
*/
class Module extends BaseModule {
/**
* Get module name.
*
* Retrieve the history module name.
*
* @since 1.7.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'history';
}
/**
* Localize settings.
*
* Add new localized settings for the history module.
*
* Fired by `elementor/editor/localize_settings` filter.
*
* @since 1.7.0
* @access public
*
* @param array $settings Localized settings.
*
* @return array Localized settings.
*/
public function localize_settings( $settings ) {
$settings = array_replace_recursive( $settings, [
'i18n' => [
'history' => __( 'History', 'elementor' ),
'template' => __( 'Template', 'elementor' ),
'added' => __( 'Added', 'elementor' ),
'removed' => __( 'Removed', 'elementor' ),
'edited' => __( 'Edited', 'elementor' ),
'moved' => __( 'Moved', 'elementor' ),
'pasted' => __( 'Pasted', 'elementor' ),
'editing_started' => __( 'Editing Started', 'elementor' ),
'style_pasted' => __( 'Style Pasted', 'elementor' ),
'style_reset' => __( 'Style Reset', 'elementor' ),
'settings_reset' => __( 'Settings Reset', 'elementor' ),
'enabled' => __( 'Enabled', 'elementor' ),
'disabled' => __( 'Disabled', 'elementor' ),
'all_content' => __( 'All Content', 'elementor' ),
'elements' => __( 'Elements', 'elementor' ),
],
] );
return $settings;
}
/**
* @since 2.3.0
* @access public
*/
public function add_templates() {
Plugin::$instance->common->add_template( __DIR__ . '/views/history-panel-template.php' );
Plugin::$instance->common->add_template( __DIR__ . '/views/revisions-panel-template.php' );
}
/**
* History module constructor.
*
* Initializing Elementor history module.
*
* @since 1.7.0
* @access public
*/
public function __construct() {
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
add_action( 'elementor/editor/init', [ $this, 'add_templates' ] );
}
}
@@ -0,0 +1,423 @@
<?php
namespace Elementor\Modules\History;
use Elementor\Core\Base\Document;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
use Elementor\Core\Files\CSS\Post as Post_CSS;
use Elementor\Plugin;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor history revisions manager.
*
* Elementor history revisions manager handler class is responsible for
* registering and managing Elementor revisions manager.
*
* @since 1.7.0
*/
class Revisions_Manager {
/**
* Maximum number of revisions to display.
*/
const MAX_REVISIONS_TO_DISPLAY = 100;
/**
* Authors list.
*
* Holds all the authors.
*
* @access private
*
* @var array
*/
private static $authors = [];
/**
* History revisions manager constructor.
*
* Initializing Elementor history revisions manager.
*
* @since 1.7.0
* @access public
*/
public function __construct() {
self::register_actions();
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function handle_revision() {
add_filter( 'wp_save_post_revision_check_for_changes', '__return_false' );
}
/**
* @since 2.0.0
* @access public
* @static
*
* @param $post_content
* @param $post_id
*
* @return string
*/
public static function avoid_delete_auto_save( $post_content, $post_id ) {
// Add a temporary string in order the $post will not be equal to the $autosave
// in edit-form-advanced.php:210
if ( $post_id && Plugin::$instance->db->is_built_with_elementor( $post_id ) ) {
$post_content .= '<!-- Created with Elementor -->';
}
return $post_content;
}
/**
* @since 2.0.0
* @access public
* @static
*/
public static function remove_temp_post_content() {
global $post;
if ( Plugin::$instance->db->is_built_with_elementor( $post->ID ) ) {
$post->post_content = str_replace( '<!-- Created with Elementor -->', '', $post->post_content );
}
}
/**
* @since 1.7.0
* @access public
* @static
*
* @param int $post_id
* @param array $query_args
* @param bool $parse_result
*
* @return array
*/
public static function get_revisions( $post_id = 0, $query_args = [], $parse_result = true ) {
$post = get_post( $post_id );
if ( ! $post || empty( $post->ID ) ) {
return [];
}
$revisions = [];
$default_query_args = [
'posts_per_page' => self::MAX_REVISIONS_TO_DISPLAY,
'meta_key' => '_elementor_data',
];
$query_args = array_merge( $default_query_args, $query_args );
$posts = wp_get_post_revisions( $post->ID, $query_args );
if ( ! wp_revisions_enabled( $post ) ) {
$autosave = Utils::get_post_autosave( $post->ID );
if ( $autosave ) {
if ( $parse_result ) {
array_unshift( $posts, $autosave );
} else {
array_unshift( $posts, $autosave->ID );
}
}
}
if ( $parse_result ) {
array_unshift( $posts, $post );
} else {
array_unshift( $posts, $post->ID );
return $posts;
}
$current_time = current_time( 'timestamp' );
/** @var \WP_Post $revision */
foreach ( $posts as $revision ) {
$date = date_i18n( _x( 'M j @ H:i', 'revision date format', 'elementor' ), strtotime( $revision->post_modified ) );
$human_time = human_time_diff( strtotime( $revision->post_modified ), $current_time );
if ( $revision->ID === $post->ID ) {
$type = 'current';
} elseif ( false !== strpos( $revision->post_name, 'autosave' ) ) {
$type = 'autosave';
} else {
$type = 'revision';
}
if ( ! isset( self::$authors[ $revision->post_author ] ) ) {
self::$authors[ $revision->post_author ] = [
'avatar' => get_avatar( $revision->post_author, 22 ),
'display_name' => get_the_author_meta( 'display_name', $revision->post_author ),
];
}
$revisions[] = [
'id' => $revision->ID,
'author' => self::$authors[ $revision->post_author ]['display_name'],
'timestamp' => strtotime( $revision->post_modified ),
'date' => sprintf(
/* translators: 1: Human readable time difference, 2: Date */
__( '%1$s ago (%2$s)', 'elementor' ),
$human_time,
$date
),
'type' => $type,
'gravatar' => self::$authors[ $revision->post_author ]['avatar'],
];
}
return $revisions;
}
/**
* @since 1.9.2
* @access public
* @static
*/
public static function update_autosave( $autosave_data ) {
self::save_revision( $autosave_data['ID'] );
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function save_revision( $revision_id ) {
$parent_id = wp_is_post_revision( $revision_id );
if ( $parent_id ) {
Plugin::$instance->db->safe_copy_elementor_meta( $parent_id, $revision_id );
}
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function restore_revision( $parent_id, $revision_id ) {
$is_built_with_elementor = Plugin::$instance->db->is_built_with_elementor( $revision_id );
Plugin::$instance->db->set_is_elementor_page( $parent_id, $is_built_with_elementor );
if ( ! $is_built_with_elementor ) {
return;
}
Plugin::$instance->db->copy_elementor_meta( $revision_id, $parent_id );
$post_css = Post_CSS::create( $parent_id );
$post_css->update();
}
/**
* @since 2.3.0
* @access public
* @static
*
* @param $data
*
* @return array
* @throws \Exception
*/
public static function ajax_get_revision_data( array $data ) {
if ( ! isset( $data['id'] ) ) {
throw new \Exception( 'You must set the revision ID.' );
}
$revision = Plugin::$instance->documents->get( $data['id'] );
if ( ! $revision ) {
throw new \Exception( 'Invalid revision.' );
}
if ( ! current_user_can( 'edit_post', $revision->get_id() ) ) {
throw new \Exception( __( 'Access denied.', 'elementor' ) );
}
$revision_data = [
'settings' => $revision->get_settings(),
'elements' => $revision->get_elements_data(),
];
return $revision_data;
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function add_revision_support_for_all_post_types() {
$post_types = get_post_types_by_support( 'elementor' );
foreach ( $post_types as $post_type ) {
add_post_type_support( $post_type, 'revisions' );
}
}
/**
* @since 2.0.0
* @access public
* @static
* @param array $return_data
* @param Document $document
*
* @return array
*/
public static function on_ajax_save_builder_data( $return_data, $document ) {
$post_id = $document->get_main_id();
$latest_revisions = self::get_revisions(
$post_id, [
'posts_per_page' => 1,
]
);
$all_revision_ids = self::get_revisions(
$post_id, [
'fields' => 'ids',
], false
);
// Send revisions data only if has revisions.
if ( ! empty( $latest_revisions ) ) {
$current_revision_id = self::current_revision_id( $post_id );
$return_data = array_replace_recursive( $return_data, [
'config' => [
'document' => [
'revisions' => [
'current_id' => $current_revision_id,
],
],
],
'latest_revisions' => $latest_revisions,
'revisions_ids' => $all_revision_ids,
] );
}
return $return_data;
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function db_before_save( $status, $has_changes ) {
if ( $has_changes ) {
self::handle_revision();
}
}
public static function document_config( $settings, $post_id ) {
$settings['revisions'] = [
'enabled' => ( $post_id && wp_revisions_enabled( get_post( $post_id ) ) ),
'current_id' => self::current_revision_id( $post_id ),
];
return $settings;
}
/**
* Localize settings.
*
* Add new localized settings for the revisions manager.
*
* Fired by `elementor/editor/editor_settings` filter.
*
* @since 1.7.0
* @access public
* @static
*/
public static function editor_settings( $settings ) {
$settings = array_replace_recursive( $settings, [
'i18n' => [
'edit_draft' => __( 'Edit Draft', 'elementor' ),
'edit_published' => __( 'Edit Published', 'elementor' ),
'no_revisions_1' => __( 'Revision history lets you save your previous versions of your work, and restore them any time.', 'elementor' ),
'no_revisions_2' => __( 'Start designing your page and you\'ll be able to see the entire revision history here.', 'elementor' ),
'current' => __( 'Current Version', 'elementor' ),
'restore' => __( 'Restore', 'elementor' ),
'restore_auto_saved_data' => __( 'Restore Auto Saved Data', 'elementor' ),
'restore_auto_saved_data_message' => __( 'There is an autosave of this post that is more recent than the version below. You can restore the saved data fron the Revisions panel', 'elementor' ),
'revision' => __( 'Revision', 'elementor' ),
'revision_history' => __( 'Revision History', 'elementor' ),
'revisions_disabled_1' => __( 'It looks like the post revision feature is unavailable in your website.', 'elementor' ),
'revisions_disabled_2' => sprintf(
/* translators: %s: Codex URL */
__( 'Learn more about <a target="_blank" href="%s">WordPress revisions</a>', 'elementor' ),
'https://go.elementor.com/wordpress-revisions/'
),
],
] );
return $settings;
}
public static function ajax_get_revisions() {
return self::get_revisions();
}
/**
* @since 2.3.0
* @access public
* @static
*/
public static function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'get_revisions', [ __CLASS__, 'ajax_get_revisions' ] );
$ajax->register_ajax_action( 'get_revision_data', [ __CLASS__, 'ajax_get_revision_data' ] );
}
/**
* @since 1.7.0
* @access private
* @static
*/
private static function register_actions() {
add_action( 'wp_restore_post_revision', [ __CLASS__, 'restore_revision' ], 10, 2 );
add_action( 'init', [ __CLASS__, 'add_revision_support_for_all_post_types' ], 9999 );
add_filter( 'elementor/editor/localize_settings', [ __CLASS__, 'editor_settings' ] );
add_filter( 'elementor/document/config', [ __CLASS__, 'document_config' ], 10, 2 );
add_action( 'elementor/db/before_save', [ __CLASS__, 'db_before_save' ], 10, 2 );
add_action( '_wp_put_post_revision', [ __CLASS__, 'save_revision' ] );
add_action( 'wp_creating_autosave', [ __CLASS__, 'update_autosave' ] );
add_action( 'elementor/ajax/register_actions', [ __CLASS__, 'register_ajax_actions' ] );
// Hack to avoid delete the auto-save revision in WP editor.
add_filter( 'edit_post_content', [ __CLASS__, 'avoid_delete_auto_save' ], 10, 2 );
add_action( 'edit_form_after_title', [ __CLASS__, 'remove_temp_post_content' ] );
if ( wp_doing_ajax() ) {
add_filter( 'elementor/documents/ajax_save/return_data', [ __CLASS__, 'on_ajax_save_builder_data' ], 10, 2 );
}
}
/**
* @since 1.9.0
* @access private
* @static
*/
private static function current_revision_id( $post_id ) {
$current_revision_id = $post_id;
$autosave = Utils::get_post_autosave( $post_id );
if ( is_object( $autosave ) ) {
$current_revision_id = $autosave->ID;
}
return $current_revision_id;
}
}
@@ -0,0 +1,35 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?>
<script type="text/template" id="tmpl-elementor-panel-history-page">
<div id="elementor-panel-elements-navigation" class="elementor-panel-navigation">
<div class="elementor-component-tab elementor-panel-navigation-tab" data-tab="actions"><?php echo __( 'Actions', 'elementor' ); ?></div>
<div class="elementor-component-tab elementor-panel-navigation-tab" data-tab="revisions"><?php echo __( 'Revisions', 'elementor' ); ?></div>
</div>
<div id="elementor-panel-history-content"></div>
</script>
<script type="text/template" id="tmpl-elementor-panel-history-tab">
<div id="elementor-history-list"></div>
<div class="elementor-history-revisions-message"><?php echo __( 'Switch to Revisions tab for older versions', 'elementor' ); ?></div>
</script>
<script type="text/template" id="tmpl-elementor-panel-history-no-items">
<img class="elementor-nerd-box-icon" src="<?php echo ELEMENTOR_ASSETS_URL . 'images/information.svg'; ?>" />
<div class="elementor-nerd-box-title"><?php echo __( 'No History Yet', 'elementor' ); ?></div>
<div class="elementor-nerd-box-message"><?php echo __( 'Once you start working, you\'ll be able to redo / undo any action you make in the editor.', 'elementor' ); ?></div>
</script>
<script type="text/template" id="tmpl-elementor-panel-history-item">
<div class="elementor-history-item__details">
<span class="elementor-history-item__title">{{{ title }}}</span>
<span class="elementor-history-item__subtitle">{{{ subTitle }}}</span>
<span class="elementor-history-item__action">{{{ action }}}</span>
</div>
<div class="elementor-history-item__icon">
<span class="eicon" aria-hidden="true"></span>
</div>
</script>
@@ -0,0 +1,63 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?>
<script type="text/template" id="tmpl-elementor-panel-revisions">
<div class="elementor-panel-box">
<div class="elementor-panel-scheme-buttons">
<div class="elementor-panel-scheme-button-wrapper elementor-panel-scheme-discard">
<button class="elementor-button" disabled>
<i class="eicon-close" aria-hidden="true"></i>
<?php echo __( 'Discard', 'elementor' ); ?>
</button>
</div>
<div class="elementor-panel-scheme-button-wrapper elementor-panel-scheme-save">
<button class="elementor-button elementor-button-success" disabled>
<?php echo __( 'Apply', 'elementor' ); ?>
</button>
</div>
</div>
</div>
<div class="elementor-panel-box">
<div class="elementor-panel-heading">
<div class="elementor-panel-heading-title"><?php echo __( 'Revisions', 'elementor' ); ?></div>
</div>
<div id="elementor-revisions-list" class="elementor-panel-box-content"></div>
</div>
</script>
<script type="text/template" id="tmpl-elementor-panel-revisions-no-revisions">
<img class="elementor-nerd-box-icon" src="<?php echo ELEMENTOR_ASSETS_URL . 'images/information.svg'; ?>" />
<div class="elementor-nerd-box-title"><?php echo __( 'No Revisions Saved Yet', 'elementor' ); ?></div>
<div class="elementor-nerd-box-message">{{{ elementor.translate( elementor.config.document.revisions.enabled ? 'no_revisions_1' : 'revisions_disabled_1' ) }}}</div>
<div class="elementor-nerd-box-message">{{{ elementor.translate( elementor.config.document.revisions.enabled ? 'no_revisions_2' : 'revisions_disabled_2' ) }}}</div>
</script>
<script type="text/template" id="tmpl-elementor-panel-revisions-loading">
<i class="eicon-loading eicon-animation-spin" aria-hidden="true"></i>
</script>
<script type="text/template" id="tmpl-elementor-panel-revisions-revision-item">
<div class="elementor-revision-item__wrapper {{ type }}">
<div class="elementor-revision-item__gravatar">{{{ gravatar }}}</div>
<div class="elementor-revision-item__details">
<div class="elementor-revision-date" title="{{{ new Date( timestamp * 1000 ) }}}">{{{ date }}}</div>
<div class="elementor-revision-meta">
<span>{{{ elementor.translate( type ) }}}</span>
<?php echo __( 'By', 'elementor' ); ?> {{{ author }}}
<span>(#{{{ id }}})</span>&nbsp;
</div>
</div>
<div class="elementor-revision-item__tools">
<# if ( 'current' === type ) { #>
<i class="elementor-revision-item__tools-current eicon-star" aria-hidden="true"></i>
<span class="elementor-screen-only"><?php echo __( 'Current', 'elementor' ); ?></span>
<# } #>
<i class="elementor-revision-item__tools-spinner eicon-loading eicon-animation-spin" aria-hidden="true"></i>
</div>
</div>
</script>
@@ -0,0 +1,89 @@
<?php
namespace Elementor\Modules\Library\Documents;
use Elementor\Core\Base\Document;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor library document.
*
* Elementor library document handler class is responsible for handling
* a document of the library type.
*
* @since 2.0.0
*/
abstract class Library_Document extends Document {
/**
* The taxonomy type slug for the library document.
*/
const TAXONOMY_TYPE_SLUG = 'elementor_library_type';
/**
* Get document properties.
*
* Retrieve the document properties.
*
* @since 2.0.0
* @access public
* @static
*
* @return array Document properties.
*/
public static function get_properties() {
$properties = parent::get_properties();
$properties['admin_tab_group'] = 'library';
$properties['show_in_library'] = true;
$properties['register_type'] = true;
return $properties;
}
/**
* Get initial config.
*
* Retrieve the current element initial configuration.
*
* Adds more configuration on top of the controls list and the tabs assigned
* to the control. This method also adds element name, type, icon and more.
*
* @since 2.9.0
* @access protected
*
* @return array The initial config.
*/
public function get_initial_config() {
$config = parent::get_initial_config();
$config['library'] = [
'save_as_same_type' => true,
];
return $config;
}
public function print_admin_column_type() {
$admin_filter_url = admin_url( Source_Local::ADMIN_MENU_SLUG . '&elementor_library_type=' . $this->get_name() );
printf( '<a href="%s">%s</a>', $admin_filter_url, $this->get_title() );
}
/**
* Save document type.
*
* Set new/updated document type.
*
* @since 2.0.0
* @access public
*/
public function save_template_type() {
parent::save_template_type();
wp_set_object_terms( $this->post->ID, $this->get_name(), self::TAXONOMY_TYPE_SLUG );
}
}
@@ -0,0 +1,84 @@
<?php
namespace Elementor\Modules\Library\Documents;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor section library document.
*
* Elementor section library document handler class is responsible for
* handling a document of a section type.
*
*/
class Not_Supported extends Library_Document {
/**
* Get document properties.
*
* Retrieve the document properties.
*
* @access public
* @static
*
* @return array Document properties.
*/
public static function get_properties() {
$properties = parent::get_properties();
$properties['admin_tab_group'] = '';
$properties['register_type'] = false;
$properties['is_editable'] = false;
$properties['show_in_library'] = false;
$properties['cpt'] = [
Source_Local::CPT,
];
return $properties;
}
/**
* Get document name.
*
* Retrieve the document name.
*
* @access public
*
* @return string Document name.
*/
public function get_name() {
return 'not-supported';
}
/**
* Get document title.
*
* Retrieve the document title.
*
* @access public
* @static
*
* @return string Document title.
*/
public static function get_title() {
return __( 'Not Supported', 'elementor' );
}
public function save_template_type() {
// Do nothing.
}
public function print_admin_column_type() {
echo self::get_title();
}
public function filter_admin_row_actions( $actions ) {
unset( $actions['view'] );
return $actions;
}
}
@@ -0,0 +1,97 @@
<?php
namespace Elementor\Modules\Library\Documents;
use Elementor\Core\DocumentTypes\Post;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor page library document.
*
* Elementor page library document handler class is responsible for
* handling a document of a page type.
*
* @since 2.0.0
*/
class Page extends Library_Document {
/**
* Get document properties.
*
* Retrieve the document properties.
*
* @since 2.0.0
* @access public
* @static
*
* @return array Document properties.
*/
public static function get_properties() {
$properties = parent::get_properties();
$properties['support_wp_page_templates'] = true;
$properties['support_kit'] = true;
return $properties;
}
/**
* Get document name.
*
* Retrieve the document name.
*
* @since 2.0.0
* @access public
*
* @return string Document name.
*/
public function get_name() {
return 'page';
}
/**
* Get document title.
*
* Retrieve the document title.
*
* @since 2.0.0
* @access public
* @static
*
* @return string Document title.
*/
public static function get_title() {
return __( 'Page', 'elementor' );
}
/**
* @since 2.1.3
* @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();
Post::register_hide_title_control( $this );
Post::register_style_controls( $this );
}
protected function get_remote_library_config() {
$config = parent::get_remote_library_config();
$config['type'] = 'page';
$config['default_route'] = 'templates/pages';
return $config;
}
}
@@ -0,0 +1,54 @@
<?php
namespace Elementor\Modules\Library\Documents;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor section library document.
*
* Elementor section library document handler class is responsible for
* handling a document of a section type.
*
* @since 2.0.0
*/
class Section extends Library_Document {
public static function get_properties() {
$properties = parent::get_properties();
$properties['support_kit'] = true;
return $properties;
}
/**
* Get document name.
*
* Retrieve the document name.
*
* @since 2.0.0
* @access public
*
* @return string Document name.
*/
public function get_name() {
return 'section';
}
/**
* Get document title.
*
* Retrieve the document title.
*
* @since 2.0.0
* @access public
* @static
*
* @return string Document title.
*/
public static function get_title() {
return __( 'Section', 'elementor' );
}
}
@@ -0,0 +1,50 @@
<?php
namespace Elementor\Modules\Library;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Modules\Library\Documents;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor library module.
*
* Elementor library module handler class is responsible for registering and
* managing Elementor library modules.
*
* @since 2.0.0
*/
class Module extends BaseModule {
/**
* Get module name.
*
* Retrieve the library module name.
*
* @since 2.0.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'library';
}
/**
* Library module constructor.
*
* Initializing Elementor library module.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
Plugin::$instance->documents
->register_document_type( 'not-supported', Documents\Not_Supported::get_class_full_name() )
->register_document_type( 'page', Documents\Page::get_class_full_name() )
->register_document_type( 'section', Documents\Section::get_class_full_name() );
}
}
@@ -0,0 +1,417 @@
<?php
namespace Elementor\Modules\PageTemplates;
use Elementor\Controls_Manager;
use Elementor\Core\Base\Document;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Kits\Documents\Kit;
use Elementor\DB;
use Elementor\Plugin;
use Elementor\Utils;
use Elementor\Core\DocumentTypes\PageBase as PageBase;
use Elementor\Modules\Library\Documents\Page as LibraryPageDocument;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor page templates module.
*
* Elementor page templates module handler class is responsible for registering
* and managing Elementor page templates modules.
*
* @since 2.0.0
*/
class Module extends BaseModule {
/**
* Elementor Canvas template name.
*/
const TEMPLATE_CANVAS = 'elementor_canvas';
/**
* Elementor Header & Footer template name.
*/
const TEMPLATE_HEADER_FOOTER = 'elementor_header_footer';
/**
* Print callback.
*
* Holds the page template callback content.
*
* @since 2.0.0
* @access protected
*
* @var callable
*/
protected $print_callback;
/**
* Get module name.
*
* Retrieve the page templates module name.
*
* @since 2.0.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'page-templates';
}
/**
* Template include.
*
* Update the path for the Elementor Canvas template.
*
* Fired by `template_include` filter.
*
* @since 2.0.0
* @access public
*
* @param string $template The path of the template to include.
*
* @return string The path of the template to include.
*/
public function template_include( $template ) {
if ( is_singular() ) {
$document = Plugin::$instance->documents->get_doc_for_frontend( get_the_ID() );
if ( $document && $document::get_property( 'support_wp_page_templates' ) ) {
$template_path = $this->get_template_path( $document->get_meta( '_wp_page_template' ) );
if ( ! $template_path && $document->is_built_with_elementor() ) {
$kit_default_template = Plugin::$instance->kits_manager->get_current_settings( 'default_page_template' );
$template_path = $this->get_template_path( $kit_default_template );
}
if ( $template_path ) {
$template = $template_path;
Plugin::$instance->inspector->add_log( 'Page Template', Plugin::$instance->inspector->parse_template_path( $template ), $document->get_edit_url() );
}
}
}
return $template;
}
/**
* Add WordPress templates.
*
* Adds Elementor templates to all the post types that support
* Elementor.
*
* Fired by `init` action.
*
* @since 2.0.0
* @access public
*/
public function add_wp_templates_support() {
$post_types = get_post_types_by_support( 'elementor' );
foreach ( $post_types as $post_type ) {
add_filter( "theme_{$post_type}_templates", [ $this, 'add_page_templates' ], 10, 4 );
}
}
/**
* Add page templates.
*
* Add the Elementor page templates to the theme templates.
*
* Fired by `theme_{$post_type}_templates` filter.
*
* @since 2.0.0
* @access public
* @static
*
* @param array $page_templates Array of page templates. Keys are filenames,
* checks are translated names.
*
* @param \WP_Theme $wp_theme
* @param \WP_Post $post
*
* @return array Page templates.
*/
public function add_page_templates( $page_templates, $wp_theme, $post ) {
if ( $post ) {
// FIX ME: Gutenberg not send $post as WP_Post object, just the post ID.
$post_id = ! empty( $post->ID ) ? $post->ID : $post;
$document = Plugin::$instance->documents->get( $post_id );
if ( $document && ! $document::get_property( 'support_wp_page_templates' ) ) {
return $page_templates;
}
}
$page_templates = [
self::TEMPLATE_CANVAS => _x( 'Elementor Canvas', 'Page Template', 'elementor' ),
self::TEMPLATE_HEADER_FOOTER => _x( 'Elementor Full Width', 'Page Template', 'elementor' ),
] + $page_templates;
return $page_templates;
}
/**
* Set print callback.
*
* Set the page template callback.
*
* @since 2.0.0
* @access public
*
* @param callable $callback
*/
public function set_print_callback( $callback ) {
$this->print_callback = $callback;
}
/**
* Print callback.
*
* Prints the page template content using WordPress loop.
*
* @since 2.0.0
* @access public
*/
public function print_callback() {
while ( have_posts() ) :
the_post();
the_content();
endwhile;
}
/**
* Print content.
*
* Prints the page template content.
*
* @since 2.0.0
* @access public
*/
public function print_content() {
if ( ! $this->print_callback ) {
$this->print_callback = [ $this, 'print_callback' ];
}
call_user_func( $this->print_callback );
}
/**
* Get page template path.
*
* Retrieve the path for any given page template.
*
* @since 2.0.0
* @access public
*
* @param string $page_template The page template name.
*
* @return string Page template path.
*/
public function get_template_path( $page_template ) {
$template_path = '';
switch ( $page_template ) {
case self::TEMPLATE_CANVAS:
$template_path = __DIR__ . '/templates/canvas.php';
break;
case self::TEMPLATE_HEADER_FOOTER:
$template_path = __DIR__ . '/templates/header-footer.php';
break;
}
return $template_path;
}
/**
* Register template control.
*
* Adds custom controls to any given document.
*
* Fired by `update_post_metadata` action.
*
* @since 2.0.0
* @access public
*
* @param Document $document The document instance.
*/
public function action_register_template_control( $document ) {
if ( $document instanceof PageBase || $document instanceof LibraryPageDocument ) {
$this->register_template_control( $document );
}
}
/**
* Register template control.
*
* Adds custom controls to any given document.
*
* @since 2.0.0
* @access public
*
* @param Document $document The document instance.
* @param string $control_id Optional. The control ID. Default is `template`.
*/
public function register_template_control( $document, $control_id = 'template' ) {
if ( ! Utils::is_cpt_custom_templates_supported() ) {
return;
}
require_once ABSPATH . '/wp-admin/includes/template.php';
$document->start_injection( [
'of' => 'post_status',
'fallback' => [
'of' => 'post_title',
],
] );
$control_options = [
'options' => array_flip( get_page_templates( null, $document->get_main_post()->post_type ) ),
];
$this->add_template_controls( $document, $control_id, $control_options );
$document->end_injection();
}
// The $options variable is an array of $control_options to overwrite the default
public function add_template_controls( Document $document, $control_id, $control_options ) {
// Default Control Options
$default_control_options = [
'label' => __( 'Page Layout', 'elementor' ),
'type' => Controls_Manager::SELECT,
'default' => 'default',
'options' => [
'default' => __( 'Default', 'elementor' ),
],
];
$control_options = array_replace_recursive( $default_control_options, $control_options );
$document->add_control(
$control_id,
$control_options
);
$document->add_control(
$control_id . '_default_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => '<b>' . __( 'Default Page Template from your theme', 'elementor' ) . '</b>',
'content_classes' => 'elementor-descriptor',
'condition' => [
$control_id => 'default',
],
]
);
$document->add_control(
$control_id . '_canvas_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => '<b>' . __( 'No header, no footer, just Elementor', 'elementor' ) . '</b>',
'content_classes' => 'elementor-descriptor',
'condition' => [
$control_id => self::TEMPLATE_CANVAS,
],
]
);
$document->add_control(
$control_id . '_header_footer_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => '<b>' . __( 'This template includes the header, full-width content and footer', 'elementor' ) . '</b>',
'content_classes' => 'elementor-descriptor',
'condition' => [
$control_id => self::TEMPLATE_HEADER_FOOTER,
],
]
);
if ( $document instanceof Kit ) {
$document->add_control(
'reload_preview_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => __( 'Changes will be reflected in the preview only after the page reloads.', 'elementor' ),
'content_classes' => 'elementor-descriptor',
]
);
}
}
/**
* Filter metadata update.
*
* Filters whether to update metadata of a specific type.
*
* Elementor don't allow WordPress to update the parent page template
* during `wp_update_post`.
*
* Fired by `update_{$meta_type}_metadata` filter.
*
* @since 2.0.0
* @access public
*
* @param bool $check Whether to allow updating metadata for the given type.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
*
* @return bool Whether to allow updating metadata of a specific type.
*/
public function filter_update_meta( $check, $object_id, $meta_key ) {
if ( '_wp_page_template' === $meta_key && Plugin::$instance->common ) {
/** @var \Elementor\Core\Common\Modules\Ajax\Module $ajax */
$ajax = Plugin::$instance->common->get_component( 'ajax' );
$ajax_data = $ajax->get_current_action_data();
$is_autosave_action = $ajax_data && 'save_builder' === $ajax_data['action'] && DB::STATUS_AUTOSAVE === $ajax_data['data']['status'];
// Don't allow WP to update the parent page template.
// (during `wp_update_post` from page-settings or save_plain_text).
if ( $is_autosave_action && ! wp_is_post_autosave( $object_id ) && DB::STATUS_DRAFT !== get_post_status( $object_id ) ) {
$check = false;
}
}
return $check;
}
/**
* Support `wp_body_open` action, available since WordPress 5.2.
*
* @since 2.7.0
* @access public
*/
public static function body_open() {
if ( function_exists( 'wp_body_open' ) ) {
wp_body_open();
} else {
do_action( 'wp_body_open' );
}
}
/**
* Page templates module constructor.
*
* Initializing Elementor page templates module.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
add_action( 'init', [ $this, 'add_wp_templates_support' ] );
add_filter( 'template_include', [ $this, 'template_include' ], 11 /* After Plugins/WooCommerce */ );
add_action( 'elementor/documents/register_controls', [ $this, 'action_register_template_control' ] );
add_filter( 'update_post_metadata', [ $this, 'filter_update_meta' ], 10, 3 );
}
}
@@ -0,0 +1,50 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
\Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-canvas' );
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<?php if ( ! current_theme_supports( 'title-tag' ) ) : ?>
<title><?php echo wp_get_document_title(); ?></title>
<?php endif; ?>
<?php wp_head(); ?>
<?php
// Keep the following line after `wp_head()` call, to ensure it's not overridden by another templates.
echo \Elementor\Utils::get_meta_viewport( 'canvas' );
?>
</head>
<body <?php body_class(); ?>>
<?php
Elementor\Modules\PageTemplates\Module::body_open();
/**
* Before canvas page template content.
*
* Fires before the content of Elementor canvas page template.
*
* @since 1.0.0
*/
do_action( 'elementor/page_templates/canvas/before_content' );
\Elementor\Plugin::$instance->modules_manager->get_modules( 'page-templates' )->print_content();
/**
* After canvas page template content.
*
* Fires after the content of Elementor canvas page template.
*
* @since 1.0.0
*/
do_action( 'elementor/page_templates/canvas/after_content' );
wp_footer();
?>
</body>
</html>
@@ -0,0 +1,30 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
\Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-full-width' );
get_header();
/**
* Before Header-Footer page template content.
*
* Fires before the content of Elementor Header-Footer page template.
*
* @since 2.0.0
*/
do_action( 'elementor/page_templates/header-footer/before_content' );
\Elementor\Plugin::$instance->modules_manager->get_modules( 'page-templates' )->print_content();
/**
* After Header-Footer page template content.
*
* Fires after the content of Elementor Header-Footer page template.
*
* @since 2.0.0
*/
do_action( 'elementor/page_templates/header-footer/after_content' );
get_footer();
@@ -0,0 +1,545 @@
<?php
namespace Elementor\Modules\SafeMode;
use Elementor\Plugin;
use Elementor\Settings;
use Elementor\Tools;
use Elementor\TemplateLibrary\Source_Local;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends \Elementor\Core\Base\Module {
const OPTION_ENABLED = 'elementor_safe_mode';
const OPTION_TOKEN = self::OPTION_ENABLED . '_token';
const MU_PLUGIN_FILE_NAME = 'elementor-safe-mode.php';
const DOCS_HELPED_URL = 'https://go.elementor.com/safe-mode-helped/';
const DOCS_DIDNT_HELP_URL = 'https://go.elementor.com/safe-mode-didnt-helped/';
const DOCS_MU_PLUGINS_URL = 'https://go.elementor.com/safe-mode-mu-plugins/';
const DOCS_TRY_SAFE_MODE_URL = 'https://go.elementor.com/safe-mode/';
const EDITOR_NOTICE_TIMEOUT = 30000; /* ms */
public function get_name() {
return 'safe-mode';
}
public function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'enable_safe_mode', [ $this, 'ajax_enable_safe_mode' ] );
$ajax->register_ajax_action( 'disable_safe_mode', [ $this, 'disable_safe_mode' ] );
}
/**
* @param Tools $tools_page
*/
public function add_admin_button( $tools_page ) {
$tools_page->add_fields( Settings::TAB_GENERAL, 'tools', [
'safe_mode' => [
'label' => __( 'Safe Mode', 'elementor' ),
'field_args' => [
'type' => 'select',
'std' => $this->is_enabled(),
'options' => [
'' => __( 'Disable', 'elementor' ),
'global' => __( 'Enable', 'elementor' ),
],
'desc' => __( 'Safe Mode allows you to troubleshoot issues by only loading the editor, without loading the theme or any other plugin.', 'elementor' ),
],
],
] );
}
public function on_update_safe_mode( $value ) {
if ( 'yes' === $value || 'global' === $value ) {
$this->enable_safe_mode();
} else {
$this->disable_safe_mode();
}
return $value;
}
public function ajax_enable_safe_mode( $data ) {
// It will run `$this->>update_safe_mode`.
update_option( 'elementor_safe_mode', 'yes' );
$document = Plugin::$instance->documents->get( $data['editor_post_id'] );
if ( $document ) {
return add_query_arg( 'elementor-mode', 'safe', $document->get_edit_url() );
}
return false;
}
public function enable_safe_mode() {
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
WP_Filesystem();
$this->update_allowed_plugins();
if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
wp_mkdir_p( WPMU_PLUGIN_DIR );
add_option( 'elementor_safe_mode_created_mu_dir', true );
}
if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
wp_die( __( 'Cannot enable Safe Mode', 'elementor' ) );
}
$results = copy_dir( __DIR__ . '/mu-plugin/', WPMU_PLUGIN_DIR );
if ( is_wp_error( $results ) ) {
return;
}
$token = md5( wp_rand() );
// Only who own this key can use 'elementor-safe-mode'.
update_option( self::OPTION_TOKEN, $token );
// Save for later use.
setcookie( self::OPTION_TOKEN, $token, time() + HOUR_IN_SECONDS, COOKIEPATH );
}
public function disable_safe_mode() {
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
$file_path = WP_CONTENT_DIR . '/mu-plugins/elementor-safe-mode.php';
if ( file_exists( $file_path ) ) {
unlink( $file_path );
}
if ( get_option( 'elementor_safe_mode_created_mu_dir' ) ) {
// It will be removed only if it's empty and don't have other mu-plugins.
@rmdir( WPMU_PLUGIN_DIR );
}
delete_option( 'elementor_safe_mode' );
delete_option( 'elementor_safe_mode_allowed_plugins' );
delete_option( 'theme_mods_elementor-safe' );
delete_option( 'elementor_safe_mode_created_mu_dir' );
delete_option( self::OPTION_TOKEN );
setcookie( self::OPTION_TOKEN, '', 1 );
}
public function filter_preview_url( $url ) {
return add_query_arg( 'elementor-mode', 'safe', $url );
}
public function filter_template() {
return ELEMENTOR_PATH . 'modules/page-templates/templates/canvas.php';
}
public function print_safe_mode_css() {
?>
<style>
.elementor-safe-mode-toast {
position: absolute;
z-index: 10000; /* Over the loading layer */
bottom: 10px;
width: 400px;
line-height: 30px;
background: white;
padding: 20px 25px 25px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
border-radius: 5px;
font-family: Roboto, Arial, Helvetica, Verdana, sans-serif;
}
body.rtl .elementor-safe-mode-toast {
left: 10px;
}
body:not(.rtl) .elementor-safe-mode-toast {
right: 10px;
}
#elementor-try-safe-mode {
display: none;
}
.elementor-safe-mode-toast .elementor-toast-content {
font-size: 13px;
line-height: 22px;
color: #6D7882;
}
.elementor-safe-mode-toast .elementor-toast-content a {
color: #138FFF;
}
.elementor-safe-mode-toast .elementor-toast-content hr {
margin: 15px auto;
border: 0 none;
border-top: 1px solid #F1F3F5;
}
.elementor-safe-mode-toast header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
margin-bottom: 20px;
}
.elementor-safe-mode-toast header > * {
margin-top: 10px;
}
.elementor-safe-mode-toast .elementor-safe-mode-button {
display: inline-block;
font-weight: 500;
font-size: 11px;
text-transform: uppercase;
color: white;
padding: 10px 15px;
line-height: 1;
background: #A4AFB7;
border-radius: 3px;
}
#elementor-try-safe-mode .elementor-safe-mode-button {
background: #39B54A;
}
.elementor-safe-mode-toast header i {
font-size: 25px;
color: #fcb92c;
}
body:not(.rtl) .elementor-safe-mode-toast header i {
margin-right: 10px;
}
body.rtl .elementor-safe-mode-toast header i {
margin-left: 10px;
}
.elementor-safe-mode-toast header h2 {
flex-grow: 1;
font-size: 18px;
color: #6D7882;
}
.elementor-safe-mode-list-item {
margin-top: 10px;
list-style: outside;
}
body:not(.rtl) .elementor-safe-mode-list-item {
margin-left: 15px;
}
body.rtl .elementor-safe-mode-list-item {
margin-right: 15px;
}
.elementor-safe-mode-list-item b {
font-size: 14px;
}
.elementor-safe-mode-list-item-content {
font-style: italic;
color: #a4afb7;
}
.elementor-safe-mode-list-item-title {
font-weight: 500;
}
.elementor-safe-mode-mu-plugins {
background-color: #f1f3f5;
margin-top: 20px;
padding: 10px 15px;
}
</style>
<?php
}
public function print_safe_mode_notice() {
echo $this->print_safe_mode_css();
?>
<div class="elementor-safe-mode-toast" id="elementor-safe-mode-message">
<header>
<i class="eicon-warning"></i>
<h2><?php echo __( 'Safe Mode ON', 'elementor' ); ?></h2>
<a class="elementor-safe-mode-button elementor-disable-safe-mode" target="_blank" href="<?php echo $this->get_admin_page_url(); ?>">
<?php echo __( 'Disable Safe Mode', 'elementor' ); ?>
</a>
</header>
<div class="elementor-toast-content">
<ul class="elementor-safe-mode-list">
<li class="elementor-safe-mode-list-item">
<div class="elementor-safe-mode-list-item-title"><?php echo __( 'Editor successfully loaded?', 'elementor' ); ?></div>
<div class="elementor-safe-mode-list-item-content"><?php echo __( 'The issue was probably caused by one of your plugins or theme.', 'elementor' ); ?> <?php printf( __( '<a href="%s" target="_blank">Click here</a> to troubleshoot', 'elementor' ), self::DOCS_HELPED_URL ); ?></div>
</li>
<li class="elementor-safe-mode-list-item">
<div class="elementor-safe-mode-list-item-title"><?php echo __( 'Still experiencing issues?', 'elementor' ); ?></div>
<div class="elementor-safe-mode-list-item-content"><?php printf( __( '<a href="%s" target="_blank">Click here</a> to troubleshoot', 'elementor' ), self::DOCS_DIDNT_HELP_URL ); ?></div>
</li>
</ul>
<?php
$mu_plugins = wp_get_mu_plugins();
if ( 1 < count( $mu_plugins ) ) : ?>
<div class="elementor-safe-mode-mu-plugins"><?php printf( __( 'Please note! We couldn\'t deactivate all of your plugins on Safe Mode. Please <a href="%s" target="_blank">read more</a> about this issue.', 'elementor' ), self::DOCS_MU_PLUGINS_URL ); ?></div>
<?php endif; ?>
</div>
</div>
<script>
var ElementorSafeMode = function() {
var attachEvents = function() {
jQuery( '.elementor-disable-safe-mode' ).on( 'click', function( e ) {
if ( ! elementorCommon || ! elementorCommon.ajax ) {
return;
}
e.preventDefault();
elementorCommon.ajax.addRequest(
'disable_safe_mode', {
success: function() {
if ( -1 === location.href.indexOf( 'elementor-mode=safe' ) ) {
location.reload();
} else {
// Need to remove the URL from browser history.
location.replace( location.href.replace( '&elementor-mode=safe', '' ) );
}
},
error: function() {
alert( 'An error occurred' );
},
},
true
);
} );
};
var init = function() {
attachEvents();
};
init();
};
new ElementorSafeMode();
</script>
<?php
}
public function print_try_safe_mode() {
if ( ! $this->is_allowed_post_type() ) {
return;
}
echo $this->print_safe_mode_css();
?>
<div class="elementor-safe-mode-toast" id="elementor-try-safe-mode">
<?php if ( current_user_can( 'install_plugins' ) ) : ?>
<header>
<i class="eicon-warning"></i>
<h2><?php echo __( 'Can\'t Edit?', 'elementor' ); ?></h2>
<a class="elementor-safe-mode-button elementor-enable-safe-mode" target="_blank" href="<?php echo $this->get_admin_page_url(); ?>">
<?php echo __( 'Enable Safe Mode', 'elementor' ); ?>
</a>
</header>
<div class="elementor-toast-content">
<?php echo __( 'Having problems loading Elementor? Please enable Safe Mode to troubleshoot.', 'elementor' ); ?>
<a href="<?php echo self::DOCS_TRY_SAFE_MODE_URL; ?>" target="_blank"><?php echo __( 'Learn More', 'elementor' ); ?></a>
</div>
<?php else : ?>
<header>
<i class="eicon-warning"></i>
<h2><?php echo __( 'Can\'t Edit?', 'elementor' ); ?></h2>
</header>
<div class="elementor-toast-content">
<?php echo __( 'If you are experiencing a loading issue, contact your site administrator to troubleshoot the problem using Safe Mode.', 'elementor' ); ?>
<a href="<?php echo self::DOCS_TRY_SAFE_MODE_URL; ?>" target="_blank"><?php echo __( 'Learn More', 'elementor' ); ?></a>
</div>
<?php endif; ?>
</div>
<script>
var ElementorTrySafeMode = function() {
var attachEvents = function() {
jQuery( '.elementor-enable-safe-mode' ).on( 'click', function( e ) {
if ( ! elementorCommon || ! elementorCommon.ajax ) {
return;
}
e.preventDefault();
elementorCommon.ajax.addRequest(
'enable_safe_mode', {
data: {
editor_post_id: '<?php echo Plugin::$instance->editor->get_post_id(); ?>',
},
success: function( url ) {
location.assign( url );
},
error: function() {
alert( 'An error occurred' );
},
},
true
);
} );
};
var isElementorLoaded = function() {
if ( 'undefined' === typeof elementor ) {
return false;
}
if ( ! elementor.loaded ) {
return false;
}
if ( jQuery( '#elementor-loading' ).is( ':visible' ) ) {
return false;
}
return true;
};
var handleTrySafeModeNotice = function() {
var $notice = jQuery( '#elementor-try-safe-mode' );
if ( isElementorLoaded() ) {
$notice.remove();
return;
}
if ( ! $notice.data( 'visible' ) ) {
$notice.show().data( 'visible', true );
}
// Re-check after 500ms.
setTimeout( handleTrySafeModeNotice, 500 );
};
var init = function() {
setTimeout( handleTrySafeModeNotice, <?php echo self::EDITOR_NOTICE_TIMEOUT; ?> );
attachEvents();
};
init();
};
new ElementorTrySafeMode();
</script>
<?php
}
public function run_safe_mode() {
remove_action( 'elementor/editor/footer', [ $this, 'print_try_safe_mode' ] );
// Avoid notices like for comment.php.
add_filter( 'deprecated_file_trigger_error', '__return_false' );
add_filter( 'template_include', [ $this, 'filter_template' ], 999 );
add_filter( 'elementor/document/urls/preview', [ $this, 'filter_preview_url' ] );
add_action( 'elementor/editor/footer', [ $this, 'print_safe_mode_notice' ] );
add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'register_scripts' ], 11 /* After Common Scripts */ );
}
public function register_scripts() {
wp_add_inline_script( 'elementor-common', 'elementorCommon.ajax.addRequestConstant( "elementor-mode", "safe" );' );
}
private function is_enabled() {
return get_option( self::OPTION_ENABLED, '' );
}
private function get_admin_page_url() {
// A fallback URL if the Js doesn't work.
return Tools::get_url();
}
public function plugin_action_links( $actions ) {
$actions['disable'] = '<a href="' . self::get_admin_page_url() . '">' . __( 'Disable Safe Mode', 'elementor' ) . '</a>';
return $actions;
}
public function on_deactivated_plugin( $plugin ) {
if ( ELEMENTOR_PLUGIN_BASE === $plugin ) {
$this->disable_safe_mode();
return;
}
$allowed_plugins = get_option( 'elementor_safe_mode_allowed_plugins', [] );
$plugin_key = array_search( $plugin, $allowed_plugins, true );
if ( $plugin_key ) {
unset( $allowed_plugins[ $plugin_key ] );
update_option( 'elementor_safe_mode_allowed_plugins', $allowed_plugins );
}
}
public function update_allowed_plugins() {
$allowed_plugins = [
'elementor' => ELEMENTOR_PLUGIN_BASE,
];
if ( defined( 'ELEMENTOR_PRO_PLUGIN_BASE' ) ) {
$allowed_plugins['elementor_pro'] = ELEMENTOR_PRO_PLUGIN_BASE;
}
if ( defined( 'WC_PLUGIN_BASENAME' ) ) {
$allowed_plugins['woocommerce'] = WC_PLUGIN_BASENAME;
}
update_option( 'elementor_safe_mode_allowed_plugins', $allowed_plugins );
}
public function __construct() {
if ( current_user_can( 'install_plugins' ) ) {
add_action( 'elementor/admin/after_create_settings/elementor-tools', [ $this, 'add_admin_button' ] );
}
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
$plugin_file = self::MU_PLUGIN_FILE_NAME;
add_filter( "plugin_action_links_{$plugin_file}", [ $this, 'plugin_action_links' ] );
// Use pre_update, in order to catch cases that $value === $old_value and it not updated.
add_filter( 'pre_update_option_elementor_safe_mode', [ $this, 'on_update_safe_mode' ], 10, 2 );
add_action( 'elementor/safe_mode/init', [ $this, 'run_safe_mode' ] );
add_action( 'elementor/editor/footer', [ $this, 'print_try_safe_mode' ] );
if ( $this->is_enabled() ) {
add_action( 'activated_plugin', [ $this, 'update_allowed_plugins' ] );
add_action( 'deactivated_plugin', [ $this, 'on_deactivated_plugin' ] );
}
}
private function is_allowed_post_type() {
$allowed_post_types = [
'post',
'page',
'product',
Source_Local::CPT,
];
$current_post_type = get_post_type( Plugin::$instance->editor->get_post_id() );
return in_array( $current_post_type, $allowed_post_types );
}
}
@@ -0,0 +1,133 @@
<?php
/**
* Plugin Name: Elementor Safe Mode
* Description: Safe Mode allows you to troubleshoot issues by only loading the editor, without loading the theme or any other plugin.
* Plugin URI: https://elementor.com/?utm_source=safe-mode&utm_campaign=plugin-uri&utm_medium=wp-dash
* Author: Elementor.com
* Version: 1.0.0
* Author URI: https://elementor.com/?utm_source=safe-mode&utm_campaign=author-uri&utm_medium=wp-dash
*
* Text Domain: elementor
*
* @package Elementor
* @category Safe Mode
*
* Elementor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Elementor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Safe_Mode {
const OPTION_ENABLED = 'elementor_safe_mode';
const OPTION_TOKEN = self::OPTION_ENABLED . '_token';
public function is_enabled() {
return get_option( self::OPTION_ENABLED );
}
public function is_valid_token() {
$token = isset( $_COOKIE[ self::OPTION_TOKEN ] ) ? $_COOKIE[ self::OPTION_TOKEN ] : null;
if ( $token && get_option( self::OPTION_TOKEN ) === $token ) {
return true;
}
return false;
}
public function is_requested() {
return ! empty( $_REQUEST['elementor-mode'] ) && 'safe' === $_REQUEST['elementor-mode'];
}
public function is_editor() {
return is_admin() && isset( $_GET['action'] ) && 'elementor' === $_GET['action'];
}
public function is_editor_preview() {
return isset( $_GET['elementor-preview'] );
}
public function is_editor_ajax() {
return is_admin() && isset( $_POST['action'] ) && 'elementor_ajax' === $_POST['action'];
}
public function add_hooks() {
add_filter( 'pre_option_active_plugins', function () {
return get_option( 'elementor_safe_mode_allowed_plugins' );
} );
add_filter( 'pre_option_stylesheet', function () {
return 'elementor-safe';
} );
add_filter( 'pre_option_template', function () {
return 'elementor-safe';
} );
add_action( 'elementor/init', function () {
do_action( 'elementor/safe_mode/init' );
} );
}
/**
* Plugin row meta.
*
* Adds row meta links to the plugin list table
*
* Fired by `plugin_row_meta` filter.
*
* @access public
*
* @param array $plugin_meta An array of the plugin's metadata, including
* the version, author, author URI, and plugin URI.
* @param string $plugin_file Path to the plugin file, relative to the plugins
* directory.
*
* @return array An array of plugin row meta links.
*/
public function plugin_row_meta( $plugin_meta, $plugin_file, $plugin_data, $status ) {
if ( basename( __FILE__ ) === $plugin_file ) {
$row_meta = [
'docs' => '<a href="https://go.elementor.com/safe-mode/" aria-label="' . esc_attr( __( 'Learn More', 'elementor' ) ) . '" target="_blank">' . __( 'Learn More', 'elementor' ) . '</a>',
];
$plugin_meta = array_merge( $plugin_meta, $row_meta );
}
return $plugin_meta;
}
public function __construct() {
add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 4 );
$enabled_type = $this->is_enabled();
if ( ! $enabled_type || ! $this->is_valid_token() ) {
return;
}
if ( ! $this->is_requested() && 'global' !== $enabled_type ) {
return;
}
if ( ! $this->is_editor() && ! $this->is_editor_preview() && ! $this->is_editor_ajax() ) {
return;
}
$this->add_hooks();
}
}
new Safe_Mode();
@@ -0,0 +1,68 @@
<?php
namespace Elementor\Modules\System_Info\Helpers;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor model helper.
*
* Elementor model helper handler class is responsible for filtering properties.
*
* @since 1.0.0
*/
final class Model_Helper {
/**
* Model helper constructor.
*
* Initializing the model helper class.
*
* @since 1.0.0
* @access private
*/
private function __construct() {}
/**
* Filter possible properties.
*
* Retrieve possible properties filtered by property intersect key.
*
* @since 1.0.0
* @access public
* @static
*
* @param array $possible_properties All the possible properties.
* @param array $properties Properties to filter.
*
* @return array Possible properties filtered by property intersect key.
*/
public static function filter_possible_properties( $possible_properties, $properties ) {
$properties_keys = array_flip( $possible_properties );
return array_intersect_key( $properties, $properties_keys );
}
/**
* Prepare properties.
*
* Combine the possible properties with the user properties and filter them.
*
* @since 1.0.0
* @access public
* @static
*
* @param array $possible_properties All the possible properties.
* @param array $user_properties User properties.
*
* @return array Possible properties and user properties filtered by property intersect key.
*/
public static function prepare_properties( $possible_properties, $user_properties ) {
$properties = array_fill_keys( $possible_properties, null );
$properties = array_merge( $properties, $user_properties );
return self::filter_possible_properties( $possible_properties, $properties );
}
}
@@ -0,0 +1,362 @@
<?php
namespace Elementor\Modules\System_Info;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Modules\System_Info\Reporters\Base;
use Elementor\Modules\System_Info\Helpers\Model_Helper;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor system info module.
*
* Elementor system info module handler class is responsible for registering and
* managing Elementor system info reports.
*
* @since 2.9.0
*/
class Module extends BaseModule {
/**
* Get module name.
*
* Retrieve the system info module name.
*
* @since 2.9.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'system-info';
}
/**
* Required user capabilities.
*
* Holds the user capabilities required to manage Elementor menus.
*
* @since 2.9.0
* @access private
*
* @var string
*/
private $capability = 'manage_options';
/**
* Elementor system info reports.
*
* Holds an array of available reports in Elementor system info page.
*
* @since 2.9.0
* @access private
*
* @var array
*/
private static $reports = [
'server' => [],
'wordpress' => [],
'theme' => [],
'user' => [],
'plugins' => [],
'network_plugins' => [],
'mu_plugins' => [],
];
/**
* Main system info page constructor.
*
* Initializing Elementor system info page.
*
* @since 2.9.0
* @access public
*/
public function __construct() {
$this->add_actions();
}
/**
* Get default settings.
*
* Retrieve the default settings. Used to reset the report settings on
* initialization.
*
* @since 2.9.0
* @access protected
*
* @return array Default settings.
*/
protected function get_init_settings() {
$settings = [];
$reporter_properties = Base::get_properties_keys();
array_push( $reporter_properties, 'category', 'name', 'class_name' );
$settings['reporter_properties'] = $reporter_properties;
$settings['reportFilePrefix'] = '';
return $settings;
}
/**
* Add actions.
*
* Register filters and actions for the main system info page.
*
* @since 2.9.0
* @access private
*/
private function add_actions() {
add_action( 'admin_menu', [ $this, 'register_menu' ], 500 );
add_action( 'wp_ajax_elementor_system_info_download_file', [ $this, 'download_file' ] );
}
/**
* Register admin menu.
*
* Add new Elementor system info admin menu.
*
* Fired by `admin_menu` action.
*
* @since 2.9.0
* @access public
*/
public function register_menu() {
$system_info_text = __( 'System Info', 'elementor' );
add_submenu_page(
'elementor',
$system_info_text,
$system_info_text,
$this->capability,
'elementor-system-info',
[ $this, 'display_page' ]
);
}
/**
* Display page.
*
* Output the content for the main system info page.
*
* @since 2.9.0
* @access public
*/
public function display_page() {
$reports_info = self::get_allowed_reports();
$reports = $this->load_reports( $reports_info, 'html' );
$raw_reports = $this->load_reports( $reports_info, 'raw' );
?>
<div id="elementor-system-info">
<h3><?php echo __( 'System Info', 'elementor' ); ?></h3>
<div><?php $this->print_report( $reports, 'html' ); ?></div>
<h3><?php echo __( 'Copy & Paste Info', 'elementor' ); ?></h3>
<div id="elementor-system-info-raw">
<label id="elementor-system-info-raw-code-label" for="elementor-system-info-raw-code"><?php echo __( 'You can copy the below info as simple text with Ctrl+C / Ctrl+V:', 'elementor' ); ?></label>
<textarea id="elementor-system-info-raw-code" readonly>
<?php
unset( $raw_reports['wordpress']['report']['admin_email'] );
$this->print_report( $raw_reports, 'raw' );
?>
</textarea>
<script>
var textarea = document.getElementById( 'elementor-system-info-raw-code' );
var selectRange = function() {
textarea.setSelectionRange( 0, textarea.value.length );
};
textarea.onfocus = textarea.onblur = textarea.onclick = selectRange;
textarea.onfocus();
</script>
</div>
<hr>
<form action="<?php echo admin_url( 'admin-ajax.php' ); ?>" method="post">
<input type="hidden" name="action" value="elementor_system_info_download_file">
<input type="submit" class="button button-primary" value="<?php echo __( 'Download System Info', 'elementor' ); ?>">
</form>
</div>
<?php
}
/**
* Download file.
*
* Download the reports files.
*
* Fired by `wp_ajax_elementor_system_info_download_file` action.
*
* @since 2.9.0
* @access public
*/
public function download_file() {
if ( ! current_user_can( $this->capability ) ) {
wp_die( __( 'You don\'t have permissions to download this file', 'elementor' ) );
}
$reports_info = self::get_allowed_reports();
$reports = $this->load_reports( $reports_info, 'raw' );
$domain = parse_url( site_url(), PHP_URL_HOST );
header( 'Content-Type: text/plain' );
header( 'Content-Disposition:attachment; filename=system-info-' . $domain . '-' . gmdate( 'd-m-Y' ) . '.txt' );
$this->print_report( $reports );
die;
}
/**
* Get report class.
*
* Retrieve the class of the report for any given report type.
*
* @since 2.9.0
* @access public
*
* @param string $reporter_type The type of the report.
*
* @return string The class of the report.
*/
public function get_reporter_class( $reporter_type ) {
return __NAMESPACE__ . '\Reporters\\' . ucfirst( $reporter_type );
}
/**
* Load reports.
*
* Retrieve the system info reports.
*
* @since 2.9.0
* @access public
*
* @param array $reports An array of system info reports.
* @param string $format - possible values: 'raw' or empty string, meaning 'html'
*
* @return array An array of system info reports.
*/
public function load_reports( $reports, $format = '' ) {
$result = [];
foreach ( $reports as $report_name => $report_info ) {
$reporter_params = [
'name' => $report_name,
'format' => $format,
];
$reporter_params = array_merge( $reporter_params, $report_info );
$reporter = $this->create_reporter( $reporter_params );
if ( ! $reporter instanceof Base ) {
continue;
}
$result[ $report_name ] = [
'report' => $reporter->get_report( $format ),
'label' => $reporter->get_title(),
];
if ( ! empty( $report_info['sub'] ) ) {
$result[ $report_name ]['sub'] = $this->load_reports( $report_info['sub'] );
}
}
return $result;
}
/**
* Create a report.
*
* Register a new report that will be displayed in Elementor system info page.
*
* @param array $properties Report properties.
*
* @return \WP_Error|false|Base Base instance if the report was created,
* False or WP_Error otherwise.
*@since 2.9.0
* @access public
*
*/
public function create_reporter( array $properties ) {
$properties = Model_Helper::prepare_properties( $this->get_settings( 'reporter_properties' ), $properties );
$reporter_class = $properties['class_name'] ? $properties['class_name'] : $this->get_reporter_class( $properties['name'] );
$reporter = new $reporter_class( $properties );
if ( ! ( $reporter instanceof Base ) ) {
return new \WP_Error( 'Each reporter must to be an instance or sub-instance of `Base` class.' );
}
if ( ! $reporter->is_enabled() ) {
return false;
}
return $reporter;
}
/**
* Print report.
*
* Output the system info page reports using an output template.
*
* @since 2.9.0
* @access public
*
* @param array $reports An array of system info reports.
* @param string $template Output type from the templates folder. Available
* templates are `raw` and `html`. Default is `raw`.
*/
public function print_report( $reports, $template = 'raw' ) {
static $tabs_count = 0;
static $required_plugins_properties = [
'Name',
'Version',
'URL',
'Author',
];
$template_path = __DIR__ . '/templates/' . $template . '.php';
require $template_path;
}
/**
* Get allowed reports.
*
* Retrieve the available reports in Elementor system info page.
*
* @since 2.9.0
* @access public
* @static
*
* @return array Available reports in Elementor system info page.
*/
public static function get_allowed_reports() {
return self::$reports;
}
/**
* Add report.
*
* Register a new report to Elementor system info page.
*
* @since 2.9.0
* @access public
* @static
*
* @param string $report_name The name of the report.
* @param array $report_info Report info.
*/
public static function add_report( $report_name, $report_info ) {
self::$reports[ $report_name ] = $report_info;
}
}
@@ -0,0 +1,199 @@
<?php
namespace Elementor\Modules\System_Info\Reporters;
use Elementor\Modules\System_Info\Helpers\Model_Helper;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor base reporter.
*
* A base abstract class that provides the needed properties and methods to
* manage and handle reporter in inheriting classes.
*
* @since 2.9.0
* @abstract
*/
abstract class Base {
/**
* Reporter properties.
*
* Holds the list of all the properties of the report.
*
* @access protected
* @static
*
* @var array
*/
protected $_properties;
/**
* Get report title.
*
* Retrieve the title of the report.
*
* @since 2.9.0
* @access public
* @abstract
*/
abstract public function get_title();
/**
* Get report fields.
*
* Retrieve the required fields for the report.
*
* @since 2.9.0
* @access public
* @abstract
*/
abstract public function get_fields();
/**
* Is report enabled.
*
* Whether the report is enabled.
*
* @since 2.9.0
* @access public
*
* @return bool Whether the report is enabled.
*/
public function is_enabled() {
return true;
}
/**
* Get report.
*
* Retrieve the report with all it's containing fields.
*
* @since 2.9.0
* @access public
*
* @return \WP_Error | array {
* Report fields.
*
* @type string $name Field name.
* @type string $label Field label.
* }
*/
final public function get_report( $format = '' ) {
$result = [];
$format = ( empty( $format ) ) ? '' : $format . '_';
foreach ( $this->get_fields() as $field_name => $field_label ) {
$method = 'get_' . $format . $field_name;
if ( ! method_exists( $this, $method ) ) {
$method = 'get_' . $field_name;
//fallback:
if ( ! method_exists( $this, $method ) ) {
return new \WP_Error( sprintf( "Getter method for the field '%s' wasn't found in %s.", $field_name, get_called_class() ) );
}
}
$reporter_field = [
'name' => $field_name,
'label' => $field_label,
];
$reporter_field = array_merge( $reporter_field, $this->$method() );
$result[ $field_name ] = $reporter_field;
}
return $result;
}
/**
* Get properties keys.
*
* Retrieve the keys of the properties.
*
* @since 2.9.0
* @access public
* @static
*
* @return array {
* Property keys.
*
* @type string $name Property name.
* @type string $fields Property fields.
* }
*/
public static function get_properties_keys() {
return [
'name',
'format',
'fields',
];
}
/**
* Filter possible properties.
*
* Retrieve possible properties filtered by property keys.
*
* @since 2.9.0
* @access public
* @static
*
* @param array $properties Properties to filter.
*
* @return array Possible properties filtered by property keys.
*/
final public static function filter_possible_properties( $properties ) {
return Model_Helper::filter_possible_properties( self::get_properties_keys(), $properties );
}
/**
* Set properties.
*
* Add/update properties to the report.
*
* @since 2.9.0
* @access public
*
* @param array $key Property key.
* @param array $value Optional. Property value. Default is `null`.
*/
final public function set_properties( $key, $value = null ) {
if ( is_array( $key ) ) {
$key = self::filter_possible_properties( $key );
foreach ( $key as $sub_key => $sub_value ) {
$this->set_properties( $sub_key, $sub_value );
}
return;
}
if ( ! in_array( $key, self::get_properties_keys(), true ) ) {
return;
}
$this->_properties[ $key ] = $value;
}
/**
* Reporter base constructor.
*
* Initializing the reporter base class.
*
* @since 2.9.0
* @access public
*
* @param array $properties Optional. Properties to filter. Default is `null`.
*/
public function __construct( $properties = null ) {
$this->_properties = array_fill_keys( self::get_properties_keys(), null );
if ( $properties ) {
$this->set_properties( $properties, null );
}
}
}
@@ -0,0 +1,111 @@
<?php
namespace Elementor\Modules\System_Info\Reporters;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor must-use plugins report.
*
* Elementor system report handler class responsible for generating a report for
* must-use plugins.
*
* @since 1.0.0
*/
class MU_Plugins extends Base {
/**
* Must-Use plugins.
*
* Holds the sites must-use plugins list.
*
* @since 1.0.0
* @access private
*
* @var array
*/
private $plugins;
/**
* Get must-use plugins.
*
* Retrieve the must-use plugins.
*
* @since 2.0.0
* @access private
*
* @return array Must-Use plugins.
*/
private function get_mu_plugins() {
if ( ! $this->plugins ) {
$this->plugins = get_mu_plugins();
}
return $this->plugins;
}
/**
* Is enabled.
*
* Whether there are must-use plugins or not.
*
* @since 1.0.0
* @access public
*
* @return bool True if the site has must-use plugins, False otherwise.
*/
public function is_enabled() {
return ! ! $this->get_mu_plugins();
}
/**
* Get must-use plugins reporter title.
*
* Retrieve must-use plugins reporter title.
*
* @since 1.0.0
* @access public
*
* @return string Reporter title.
*/
public function get_title() {
return 'Must-Use Plugins';
}
/**
* Get must-use plugins report fields.
*
* Retrieve the required fields for the must-use plugins report.
*
* @since 1.0.0
* @access public
*
* @return array Required report fields with field ID and field label.
*/
public function get_fields() {
return [
'must_use_plugins' => 'Must-Use Plugins',
];
}
/**
* Get must-use plugins.
*
* Retrieve the sites must-use plugins.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The must-use plugins list.
* }
*/
public function get_must_use_plugins() {
return [
'value' => $this->get_mu_plugins(),
];
}
}
@@ -0,0 +1,116 @@
<?php
namespace Elementor\Modules\System_Info\Reporters;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor network plugins report.
*
* Elementor system report handler class responsible for generating a report for
* network plugins.
*
* @since 1.0.0
*/
class Network_Plugins extends Base {
/**
* Network plugins.
*
* Holds the sites network plugins list.
*
* @since 1.0.0
* @access private
*
* @var array
*/
private $plugins;
/**
* Get network plugins reporter title.
*
* Retrieve network plugins reporter title.
*
* @since 1.0.0
* @access public
*
* @return string Reporter title.
*/
public function get_title() {
return 'Network Plugins';
}
/**
* Get active network plugins.
*
* Retrieve the active network plugins from the list of active site-wide plugins.
*
* @since 2.0.0
* @access private
*
* @return array Active network plugins.
*/
private function get_network_plugins() {
if ( ! $this->plugins ) {
$active_plugins = get_site_option( 'active_sitewide_plugins' );
$this->plugins = array_intersect_key( get_plugins(), $active_plugins );
}
return $this->plugins;
}
/**
* Is enabled.
*
* Whether there are active network plugins or not.
*
* @since 1.0.0
* @access public
*
* @return bool True if the site has active network plugins, False otherwise.
*/
public function is_enabled() {
if ( ! is_multisite() ) {
return false;
};
return ! ! $this->get_network_plugins();
}
/**
* Get network plugins report fields.
*
* Retrieve the required fields for the network plugins report.
*
* @since 1.0.0
* @access public
*
* @return array Required report fields with field ID and field label.
*/
public function get_fields() {
return [
'network_active_plugins' => 'Network Plugins',
];
}
/**
* Get active network plugins.
*
* Retrieve the sites active network plugins.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The active network plugins list.
* }
*/
public function get_network_active_plugins() {
return [
'value' => $this->get_network_plugins(),
];
}
}
@@ -0,0 +1,117 @@
<?php
namespace Elementor\Modules\System_Info\Reporters;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor active plugins report.
*
* Elementor system report handler class responsible for generating a report for
* active plugins.
*
* @since 1.0.0
*/
class Plugins extends Base {
/**
* Active plugins.
*
* Holds the sites active plugins list.
*
* @since 1.0.0
* @access private
*
* @var array
*/
private $plugins;
/**
* Get active plugins.
*
* Retrieve the active plugins from the list of all the installed plugins.
*
* @since 2.0.0
* @access private
*
* @return array Active plugins.
*/
private function get_plugins() {
if ( ! $this->plugins ) {
// Ensure get_plugins function is loaded
if ( ! function_exists( 'get_plugins' ) ) {
include ABSPATH . '/wp-admin/includes/plugin.php';
}
$active_plugins = get_option( 'active_plugins' );
$this->plugins = array_intersect_key( get_plugins(), array_flip( $active_plugins ) );
}
return $this->plugins;
}
/**
* Get active plugins reporter title.
*
* Retrieve active plugins reporter title.
*
* @since 1.0.0
* @access public
*
* @return string Reporter title.
*/
public function get_title() {
return 'Active Plugins';
}
/**
* Is enabled.
*
* Whether there are active plugins or not.
*
* @since 1.0.0
* @access public
*
* @return bool True if the site has active plugins, False otherwise.
*/
public function is_enabled() {
return ! ! $this->get_plugins();
}
/**
* Get active plugins report fields.
*
* Retrieve the required fields for the active plugins report.
*
* @since 1.0.0
* @access public
*
* @return array Required report fields with field ID and field label.
*/
public function get_fields() {
return [
'active_plugins' => 'Active Plugins',
];
}
/**
* Get active plugins.
*
* Retrieve the sites active plugins.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The active plugins list.
* }
*/
public function get_active_plugins() {
return [
'value' => $this->get_plugins(),
];
}
}
@@ -0,0 +1,362 @@
<?php
namespace Elementor\Modules\System_Info\Reporters;
use Elementor\Api;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor server environment report.
*
* Elementor system report handler class responsible for generating a report for
* the server environment.
*
* @since 1.0.0
*/
class Server extends Base {
/**
* Get server environment reporter title.
*
* Retrieve server environment reporter title.
*
* @since 1.0.0
* @access public
*
* @return string Reporter title.
*/
public function get_title() {
return 'Server Environment';
}
/**
* Get server environment report fields.
*
* Retrieve the required fields for the server environment report.
*
* @since 1.0.0
* @access public
*
* @return array Required report fields with field ID and field label.
*/
public function get_fields() {
return [
'os' => 'Operating System',
'software' => 'Software',
'mysql_version' => 'MySQL version',
'php_version' => 'PHP Version',
'php_max_input_vars' => 'PHP Max Input Vars',
'php_max_post_size' => 'PHP Max Post Size',
'gd_installed' => 'GD Installed',
'zip_installed' => 'ZIP Installed',
'write_permissions' => 'Write Permissions',
'elementor_library' => 'Elementor Library',
];
}
/**
* Get server operating system.
*
* Retrieve the server operating system.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Server operating system.
* }
*/
public function get_os() {
return [
'value' => PHP_OS,
];
}
/**
* Get server software.
*
* Retrieve the server software.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Server software.
* }
*/
public function get_software() {
return [
'value' => $_SERVER['SERVER_SOFTWARE'],
];
}
/**
* Get PHP version.
*
* Retrieve the PHP version.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value PHP version.
* @type string $recommendation Minimum PHP version recommendation.
* @type bool $warning Whether to display a warning.
* }
*/
public function get_php_version() {
$result = [
'value' => PHP_VERSION,
];
if ( version_compare( $result['value'], '5.4', '<' ) ) {
$result['recommendation'] = _x( 'We recommend to use php 5.4 or higher', 'System Info', 'elementor' );
$result['warning'] = true;
}
return $result;
}
/**
* Get PHP `max_input_vars`.
*
* Retrieve the value of `max_input_vars` from `php.ini` configuration file.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value PHP `max_input_vars`.
* }
*/
public function get_php_max_input_vars() {
return [
'value' => ini_get( 'max_input_vars' ),
];
}
/**
* Get PHP `post_max_size`.
*
* Retrieve the value of `post_max_size` from `php.ini` configuration file.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value PHP `post_max_size`.
* }
*/
public function get_php_max_post_size() {
return [
'value' => ini_get( 'post_max_size' ),
];
}
/**
* Get GD installed.
*
* Whether the GD extension is installed.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Yes if the GD extension is installed, No otherwise.
* @type bool $warning Whether to display a warning. True if the GD extension is installed, False otherwise.
* }
*/
public function get_gd_installed() {
$gd_installed = extension_loaded( 'gd' );
return [
'value' => $gd_installed ? 'Yes' : 'No',
'warning' => ! $gd_installed,
];
}
/**
* Get ZIP installed.
*
* Whether the ZIP extension is installed.
*
* @since 2.1.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Yes if the ZIP extension is installed, No otherwise.
* @type bool $warning Whether to display a warning. True if the ZIP extension is installed, False otherwise.
* }
*/
public function get_zip_installed() {
$zip_installed = extension_loaded( 'zip' );
return [
'value' => $zip_installed ? 'Yes' : 'No',
'warning' => ! $zip_installed,
];
}
/**
* Get MySQL version.
*
* Retrieve the MySQL version.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value MySQL version.
* }
*/
public function get_mysql_version() {
global $wpdb;
$db_server_version = $wpdb->get_results( "SHOW VARIABLES WHERE `Variable_name` IN ( 'version_comment', 'innodb_version' )", OBJECT_K );
return [
'value' => $db_server_version['version_comment']->Value . ' v' . $db_server_version['innodb_version']->Value,
];
}
/**
* Get write permissions.
*
* Check whether the required folders has writing permissions.
*
* @since 1.9.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Writing permissions status.
* @type bool $warning Whether to display a warning. True if some required
* folders don't have writing permissions, False otherwise.
* }
*/
public function get_write_permissions() {
$paths_to_check = [
ABSPATH => 'WordPress root directory',
];
$write_problems = [];
$wp_upload_dir = wp_upload_dir();
if ( $wp_upload_dir['error'] ) {
$write_problems[] = 'WordPress root uploads directory';
}
$elementor_uploads_path = $wp_upload_dir['basedir'] . '/elementor';
if ( is_dir( $elementor_uploads_path ) ) {
$paths_to_check[ $elementor_uploads_path ] = 'Elementor uploads directory';
}
$htaccess_file = ABSPATH . '/.htaccess';
if ( file_exists( $htaccess_file ) ) {
$paths_to_check[ $htaccess_file ] = '.htaccess file';
}
foreach ( $paths_to_check as $dir => $description ) {
if ( ! is_writable( $dir ) ) {
$write_problems[] = $description;
}
}
if ( $write_problems ) {
$value = 'There are some writing permissions issues with the following directories/files:' . "\n\t\t - ";
$value .= implode( "\n\t\t - ", $write_problems );
} else {
$value = 'All right';
}
return [
'value' => $value,
'warning' => ! ! $write_problems,
];
}
/**
* Check for elementor library connectivity.
*
* Check whether the remote elementor library is reachable.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The status of elementor library connectivity.
* @type bool $warning Whether to display a warning. True if elementor
* * library is not reachable, False otherwise.
* }
*/
public function get_elementor_library() {
$response = wp_remote_get(
Api::$api_info_url, [
'timeout' => 5,
'body' => [
// Which API version is used
'api_version' => ELEMENTOR_VERSION,
// Which language to return
'site_lang' => get_bloginfo( 'language' ),
],
]
);
if ( is_wp_error( $response ) ) {
return [
'value' => 'Not connected (' . $response->get_error_message() . ')',
'warning' => true,
];
}
$http_response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== (int) $http_response_code ) {
$error_msg = 'HTTP Error (' . $http_response_code . ')';
return [
'value' => 'Not connected (' . $error_msg . ')',
'warning' => true,
];
}
$info_data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $info_data ) ) {
return [
'value' => 'Not connected (Returns invalid JSON)',
'warning' => true,
];
}
return [
'value' => 'Connected',
];
}
}
@@ -0,0 +1,257 @@
<?php
namespace Elementor\Modules\System_Info\Reporters;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor theme report.
*
* Elementor system report handler class responsible for generating a report for
* the theme.
*
* @since 1.0.0
*/
class Theme extends Base {
/**
* Theme.
*
* Holds the sites theme object.
*
* @since 1.0.0
* @access private
*
* @var \WP_Theme WordPress theme object.
*/
private $theme = null;
/**
* Get theme reporter title.
*
* Retrieve theme reporter title.
*
* @since 1.0.0
* @access public
*
* @return string Reporter title.
*/
public function get_title() {
return 'Theme';
}
/**
* Get theme report fields.
*
* Retrieve the required fields for the theme report.
*
* @since 1.0.0
* @access public
*
* @return array Required report fields with field ID and field label.
*/
public function get_fields() {
$fields = [
'name' => 'Name',
'version' => 'Version',
'author' => 'Author',
'is_child_theme' => 'Child Theme',
];
if ( $this->get_parent_theme() ) {
$parent_fields = [
'parent_name' => 'Parent Theme Name',
'parent_version' => 'Parent Theme Version',
'parent_author' => 'Parent Theme Author',
];
$fields = array_merge( $fields, $parent_fields );
}
return $fields;
}
/**
* Get theme.
*
* Retrieve the theme.
*
* @since 1.0.0
* @access protected
*
* @return \WP_Theme WordPress theme object.
*/
protected function _get_theme() {
if ( is_null( $this->theme ) ) {
$this->theme = wp_get_theme();
}
return $this->theme;
}
/**
* Get parent theme.
*
* Retrieve the parent theme.
*
* @since 1.0.0
* @access protected
*
* @return \WP_Theme|false WordPress theme object, or false if the current theme is not a child theme.
*/
protected function get_parent_theme() {
return $this->_get_theme()->parent();
}
/**
* Get theme name.
*
* Retrieve the theme name.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The theme name.
* }
*/
public function get_name() {
return [
'value' => $this->_get_theme()->get( 'Name' ),
];
}
/**
* Get theme author.
*
* Retrieve the theme author.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The theme author.
* }
*/
public function get_author() {
return [
'value' => $this->_get_theme()->get( 'Author' ),
];
}
/**
* Get theme version.
*
* Retrieve the theme version.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The theme version.
* }
*/
public function get_version() {
return [
'value' => $this->_get_theme()->get( 'Version' ),
];
}
/**
* Is the theme is a child theme.
*
* Whether the theme is a child theme.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Yes if the theme is a child theme, No otherwise.
* @type string $recommendation Theme source code modification recommendation.
* }
*/
public function get_is_child_theme() {
$is_child_theme = is_child_theme();
$result = [
'value' => $is_child_theme ? 'Yes' : 'No',
];
if ( ! $is_child_theme ) {
$result['recommendation'] = sprintf(
/* translators: %s: Codex URL */
_x( 'If you want to modify the source code of your theme, we recommend using a <a href="%s">child theme</a>.', 'System Info', 'elementor' ),
'https://go.elementor.com/wordpress-child-themes/'
);
}
return $result;
}
/**
* Get parent theme version.
*
* Retrieve the parent theme version.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The parent theme version.
* }
*/
public function get_parent_version() {
return [
'value' => $this->get_parent_theme()->get( 'Version' ),
];
}
/**
* Get parent theme author.
*
* Retrieve the parent theme author.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The parent theme author.
* }
*/
public function get_parent_author() {
return [
'value' => $this->get_parent_theme()->get( 'Author' ),
];
}
/**
* Get parent theme name.
*
* Retrieve the parent theme name.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The parent theme name.
* }
*/
public function get_parent_name() {
return [
'value' => $this->get_parent_theme()->get( 'Name' ),
];
}
}
@@ -0,0 +1,116 @@
<?php
namespace Elementor\Modules\System_Info\Reporters;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor user report.
*
* Elementor system report handler class responsible for generating a report for
* the user.
*
* @since 1.0.0
*/
class User extends Base {
/**
* Get user reporter title.
*
* Retrieve user reporter title.
*
* @since 1.0.0
* @access public
*
* @return string Reporter title.
*/
public function get_title() {
return 'User';
}
/**
* Get user report fields.
*
* Retrieve the required fields for the user report.
*
* @since 1.0.0
* @access public
*
* @return array Required report fields with field ID and field label.
*/
public function get_fields() {
return [
'role' => 'Role',
'locale' => 'WP Profile lang',
'agent' => 'User Agent',
];
}
/**
* Get user role.
*
* Retrieve the user role.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value The user role.
* }
*/
public function get_role() {
$role = null;
$current_user = wp_get_current_user();
if ( ! empty( $current_user->roles ) ) {
$role = $current_user->roles[0];
}
return [
'value' => $role,
];
}
/**
* Get user profile language.
*
* Retrieve the user profile language.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value User profile language.
* }
*/
public function get_locale() {
return [
'value' => get_locale(),
];
}
/**
* Get user agent.
*
* Retrieve user agent.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value HTTP user agent.
* }
*/
public function get_agent() {
return [
'value' => esc_html( $_SERVER['HTTP_USER_AGENT'] ),
];
}
}
@@ -0,0 +1,312 @@
<?php
namespace Elementor\Modules\System_Info\Reporters;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor WordPress environment report.
*
* Elementor system report handler class responsible for generating a report for
* the WordPress environment.
*
* @since 1.0.0
*/
class WordPress extends Base {
/**
* Get WordPress environment reporter title.
*
* Retrieve WordPress environment reporter title.
*
* @since 1.0.0
* @access public
*
* @return string Reporter title.
*/
public function get_title() {
return 'WordPress Environment';
}
/**
* Get WordPress environment report fields.
*
* Retrieve the required fields for the WordPress environment report.
*
* @since 1.0.0
* @access public
*
* @return array Required report fields with field ID and field label.
*/
public function get_fields() {
return [
'version' => 'Version',
'site_url' => 'Site URL',
'home_url' => 'Home URL',
'is_multisite' => 'WP Multisite',
'max_upload_size' => 'Max Upload Size',
'memory_limit' => 'Memory limit',
'permalink_structure' => 'Permalink Structure',
'language' => 'Language',
'timezone' => 'Timezone',
'admin_email' => 'Admin Email',
'debug_mode' => 'Debug Mode',
];
}
/**
* Get WordPress memory limit.
*
* Retrieve the WordPress memory limit.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value WordPress memory limit.
* @type string $recommendation Recommendation memory limit.
* @type bool $warning Whether to display a warning. True if the limit
* is below the recommended 64M, False otherwise.
* }
*/
public function get_memory_limit() {
$result = [
'value' => ini_get( 'memory_limit' ),
];
$min_recommended_memory = '64M';
$memory_limit_bytes = wp_convert_hr_to_bytes( $result['value'] );
$min_recommended_bytes = wp_convert_hr_to_bytes( $min_recommended_memory );
if ( $memory_limit_bytes < $min_recommended_bytes ) {
$result['recommendation'] = sprintf(
/* translators: 1: Minimum recommended_memory, 2: Codex URL */
_x( 'We recommend setting memory to at least %1$s. For more information, read about <a href="%2$s">how to Increase memory allocated to PHP</a>.', 'System Info', 'elementor' ),
$min_recommended_memory,
'https://go.elementor.com/wordpress-wp-config-memory/'
);
$result['warning'] = true;
}
return $result;
}
/**
* Get WordPress version.
*
* Retrieve the WordPress version.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value WordPress version.
* }
*/
public function get_version() {
return [
'value' => get_bloginfo( 'version' ),
];
}
/**
* Is multisite.
*
* Whether multisite is enabled or not.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Yes if multisite is enabled, No otherwise.
* }
*/
public function get_is_multisite() {
return [
'value' => is_multisite() ? 'Yes' : 'No',
];
}
/**
* Get site URL.
*
* Retrieve WordPress site URL.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value WordPress site URL.
* }
*/
public function get_site_url() {
return [
'value' => get_site_url(),
];
}
/**
* Get home URL.
*
* Retrieve WordPress home URL.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value WordPress home URL.
* }
*/
public function get_home_url() {
return [
'value' => get_home_url(),
];
}
/**
* Get permalink structure.
*
* Retrieve the permalink structure
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value WordPress permalink structure.
* }
*/
public function get_permalink_structure() {
global $wp_rewrite;
$structure = $wp_rewrite->permalink_structure;
if ( ! $structure ) {
$structure = 'Plain';
}
return [
'value' => $structure,
];
}
/**
* Get site language.
*
* Retrieve the site language.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value WordPress site language.
* }
*/
public function get_language() {
return [
'value' => get_bloginfo( 'language' ),
];
}
/**
* Get PHP `max_upload_size`.
*
* Retrieve the value of maximum upload file size defined in `php.ini` configuration file.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Maximum upload file size allowed.
* }
*/
public function get_max_upload_size() {
return [
'value' => size_format( wp_max_upload_size() ),
];
}
/**
* Get WordPress timezone.
*
* Retrieve WordPress timezone.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value WordPress timezone.
* }
*/
public function get_timezone() {
$timezone = get_option( 'timezone_string' );
if ( ! $timezone ) {
$timezone = get_option( 'gmt_offset' );
}
return [
'value' => $timezone,
];
}
/**
* Get WordPress administrator email.
*
* Retrieve WordPress administrator email.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value WordPress administrator email.
* }
*/
public function get_admin_email() {
return [
'value' => get_option( 'admin_email' ),
];
}
/**
* Get debug mode.
*
* Whether WordPress debug mode is enabled or not.
*
* @since 1.0.0
* @access public
*
* @return array {
* Report data.
*
* @type string $value Active if debug mode is enabled, Inactive otherwise.
* }
*/
public function get_debug_mode() {
return [
'value' => WP_DEBUG ? 'Active' : 'Inactive',
];
}
}
@@ -0,0 +1,75 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* @var array $reports
*/
foreach ( $reports as $report_name => $report ) : ?>
<div class="elementor-system-info-section elementor-system-info-<?php echo esc_attr( $report_name ); ?>">
<table class="widefat">
<thead>
<tr>
<th><?php echo $report['label']; ?></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<?php
foreach ( $report['report'] as $field_name => $field ) :
if ( in_array( $report_name, [ 'plugins', 'network_plugins', 'mu_plugins' ], true ) ) {
foreach ( $field['value'] as $plugin_info ) :
?>
<tr>
<td><?php
if ( $plugin_info['PluginURI'] ) :
$plugin_name = "<a href='{$plugin_info['PluginURI']}'>{$plugin_info['Name']}</a>";
else :
$plugin_name = $plugin_info['Name'];
endif;
if ( $plugin_info['Version'] ) :
$plugin_name .= ' - ' . $plugin_info['Version'];
endif;
echo $plugin_name;
?></td>
<td><?php
if ( $plugin_info['Author'] ) :
if ( $plugin_info['AuthorURI'] ) :
$author = "<a href='{$plugin_info['AuthorURI']}'>{$plugin_info['Author']}</a>";
else :
$author = $plugin_info['Author'];
endif;
echo "By $author";
endif;
?></td>
<td></td>
</tr>
<?php
endforeach;
} else {
$warning_class = ! empty( $field['warning'] ) ? ' class="elementor-warning"' : '';
$log_label = ! empty( $field['label'] ) ? $field['label'] . ':' : '';
?>
<tr<?php echo $warning_class; ?>>
<td><?php echo $log_label; ?></td>
<td><?php echo $field['value']; ?></td>
<td><?php
if ( ! empty( $field['recommendation'] ) ) :
echo $field['recommendation'];
endif;
?></td>
</tr>
<?php
}
endforeach;
?>
</tbody>
</table>
</div>
<?php
endforeach;
@@ -0,0 +1,65 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* @var array $reports
* @var array $required_plugins_properties
* @var int $tabs_count
*/
$tabs_count++;
$required_plugins_properties = array_flip( $required_plugins_properties );
unset( $required_plugins_properties['Name'] );
foreach ( $reports as $report_name => $report ) :
$indent = str_repeat( "\t", $tabs_count - 1 );
$is_plugins = in_array( $report_name, [
'plugins',
'network_plugins',
'mu_plugins',
] );
if ( ! $is_plugins ) :
echo PHP_EOL . $indent . '== ' . $report['label'] . ' ==';
endif;
echo PHP_EOL;
foreach ( $report['report'] as $field_name => $field ) :
$sub_indent = str_repeat( "\t", $tabs_count );
if ( $is_plugins ) {
echo "== {$field['label']} ==" . PHP_EOL;
foreach ( $field['value'] as $plugin_info ) :
$plugin_properties = array_intersect_key( $plugin_info, $required_plugins_properties );
echo $sub_indent . $plugin_info['Name'];
foreach ( $plugin_properties as $property_name => $property ) :
echo PHP_EOL . "{$sub_indent}\t{$property_name}: {$property}";
endforeach;
echo PHP_EOL . PHP_EOL;
endforeach;
} else {
$label = $field['label'];
if ( ! empty( $label ) ) {
$label .= ': ';
}
echo "{$sub_indent}{$label}{$field['value']}" . PHP_EOL;
}
endforeach;
if ( ! empty( $report['sub'] ) ) :
$this->print_report( $report['sub'], $template, true );
endif;
endforeach;
$tabs_count--;
@@ -0,0 +1,606 @@
<?php
namespace Elementor\Modules\Usage;
use Elementor\Core\Base\Document;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\DynamicTags\Manager;
use Elementor\Modules\System_Info\Module as System_Info;
use Elementor\DB;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor usage module.
*
* Elementor usage module handler class is responsible for registering and
* managing Elementor usage data.
*
*/
class Module extends BaseModule {
const GENERAL_TAB = 'general';
const META_KEY = '_elementor_controls_usage';
const OPTION_NAME = 'elementor_controls_usage';
/**
* @var bool
*/
private $is_document_saving = false;
/**
* Get module name.
*
* Retrieve the usage module name.
*
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'usage';
}
/**
* Get doc type count.
*
* Get count of documents based on doc type
*
* Remove 'wp-' from $doc_type for BC, support doc type change since 2.7.0.
*
* @param \Elementor\Core\Documents_Manager $doc_class
* @param String $doc_type
*
* @return int
*/
public function get_doc_type_count( $doc_class, $doc_type ) {
static $posts = null;
static $library = null;
if ( null === $posts ) {
$posts = \Elementor\Tracker::get_posts_usage();
}
if ( null === $library ) {
$library = \Elementor\Tracker::get_library_usage();
}
$posts_usage = $posts;
if ( $doc_class::get_property( 'show_in_library' ) ) {
$posts_usage = $library;
}
$doc_type_common = str_replace( 'wp-', '', $doc_type );
$doc_usage = isset( $posts_usage[ $doc_type_common ] ) ? $posts_usage[ $doc_type_common ] : 0;
return is_array( $doc_usage ) ? $doc_usage['publish'] : $doc_usage;
}
/**
* Get formatted usage.
*
* Retrieve formatted usage, for frontend.
*
* @param String format
*
* @return array
*/
public function get_formatted_usage( $format = 'html' ) {
$usage = [];
foreach ( get_option( self::OPTION_NAME, [] ) as $doc_type => $elements ) {
$doc_class = Plugin::$instance->documents->get_document_type( $doc_type );
if ( 'html' === $format && $doc_class ) {
$doc_title = $doc_class::get_title();
} else {
$doc_title = $doc_type;
}
$doc_count = $this->get_doc_type_count( $doc_class, $doc_type );
$tab_group = $doc_class::get_property( 'admin_tab_group' );
if ( 'html' === $format && $tab_group ) {
$doc_title = ucwords( $tab_group ) . ' - ' . $doc_title;
}
// Replace element type with element title.
foreach ( $elements as $element_type => $data ) {
unset( $elements[ $element_type ] );
if ( in_array( $element_type, [ 'section', 'column' ], true ) ) {
continue;
}
$widget_instance = Plugin::$instance->widgets_manager->get_widget_types( $element_type );
if ( 'html' === $format && $widget_instance ) {
$widget_title = $widget_instance->get_title();
} else {
$widget_title = $element_type;
}
$elements[ $widget_title ] = $data['count'];
}
// Sort elements by key.
ksort( $elements );
$usage[ $doc_type ] = [
'title' => $doc_title,
'elements' => $elements,
'count' => $doc_count,
];
// Sort usage by title.
uasort( $usage, function( $a, $b ) {
return ( $a['title'] > $b['title'] );
} );
// If title includes '-' will have lower priority.
uasort( $usage, function( $a ) {
return strpos( $a['title'], '-' );
} );
}
return $usage;
}
/**
* Before document Save.
*
* Called on elementor/document/before_save, remove document from global & set saving flag.
*
* @param Document $document
* @param array $data new settings to save.
*/
public function before_document_save( $document, $data ) {
$current_status = get_post_status( $document->get_post() );
$new_status = isset( $data['settings']['post_status'] ) ? $data['settings']['post_status'] : '';
if ( $current_status === $new_status ) {
$this->remove_from_global( $document );
}
$this->is_document_saving = true;
}
/**
* After document save.
*
* Called on elementor/document/after_save, adds document to global & clear saving flag.
*
* @param Document $document
*/
public function after_document_save( $document ) {
if ( DB::STATUS_PUBLISH === $document->get_post()->post_status || DB::STATUS_PRIVATE === $document->get_post()->post_status ) {
$this->save_document_usage( $document );
}
$this->is_document_saving = false;
}
/**
* On status change.
*
* Called on transition_post_status.
*
* @param string $new_status
* @param string $old_status
* @param \WP_Post $post
*/
public function on_status_change( $new_status, $old_status, $post ) {
if ( wp_is_post_autosave( $post ) ) {
return;
}
// If it's from elementor editor, the usage should be saved via `before_document_save`/`after_document_save`.
if ( $this->is_document_saving ) {
return;
}
$document = Plugin::$instance->documents->get( $post->ID );
if ( ! $document ) {
return;
}
$is_public_unpublish = 'publish' === $old_status && 'publish' !== $new_status;
$is_private_unpublish = 'private' === $old_status && 'private' !== $new_status;
if ( $is_public_unpublish || $is_private_unpublish ) {
$this->remove_from_global( $document );
}
$is_public_publish = 'publish' !== $old_status && 'publish' === $new_status;
$is_private_publish = 'private' !== $old_status && 'private' === $new_status;
if ( $is_public_publish || $is_private_publish ) {
$this->save_document_usage( $document );
}
}
/**
* On before delete post.
*
* Called on on_before_delete_post.
*
* @param int $post_id
*/
public function on_before_delete_post( $post_id ) {
$document = Plugin::$instance->documents->get( $post_id );
if ( $document->get_id() !== $document->get_main_id() ) {
return;
}
$this->remove_from_global( $document );
}
/**
* Add's tracking data.
*
* Called on elementor/tracker/send_tracking_data_params.
*
* @param array $params
*
* @return array
*/
public function add_tracking_data( $params ) {
$params['usages']['elements'] = get_option( self::OPTION_NAME );
return $params;
}
/**
* Recalculate usage.
*
* Recalculate usage for all elementor posts.
*
* @param int $limit
* @param int $offset
*
* @return int
*/
public function recalc_usage( $limit = -1, $offset = 0 ) {
// While requesting recalc_usage, data should be deleted.
// if its in a batch the data should be deleted only on the first batch.
if ( 0 === $offset ) {
delete_option( self::OPTION_NAME );
}
$post_types = get_post_types( array( 'public' => true ) );
$query = new \WP_Query( [
'meta_key' => '_elementor_data',
'post_type' => $post_types,
'post_status' => [ 'publish', 'private' ],
'posts_per_page' => $limit,
'offset' => $offset,
] );
foreach ( $query->posts as $post ) {
$document = Plugin::$instance->documents->get( $post->ID );
if ( ! $document ) {
continue;
}
$this->after_document_save( $document );
}
// Clear query memory before leave.
wp_cache_flush();
return count( $query->posts );
}
/**
* Increase controls count.
*
* Increase controls count, for each element.
*
* @param array &$element_ref
* @param string $tab
* @param string $section
* @param string $control
* @param int $count
*/
private function increase_controls_count( &$element_ref, $tab, $section, $control, $count ) {
if ( ! isset( $element_ref['controls'][ $tab ] ) ) {
$element_ref['controls'][ $tab ] = [];
}
if ( ! isset( $element_ref['controls'][ $tab ][ $section ] ) ) {
$element_ref['controls'][ $tab ][ $section ] = [];
}
if ( ! isset( $element_ref['controls'][ $tab ][ $section ][ $control ] ) ) {
$element_ref['controls'][ $tab ][ $section ][ $control ] = 0;
}
$element_ref['controls'][ $tab ][ $section ][ $control ] += $count;
}
/**
* Add Controls
*
* Add's controls to this element_ref, returns changed controls count.
*
* @param array $settings_controls
* @param array $element_controls
* @param array &$element_ref
*
* @return int ($changed_controls_count).
*/
private function add_controls( $settings_controls, $element_controls, &$element_ref ) {
$changed_controls_count = 0;
// Loop over all element settings.
foreach ( $settings_controls as $control => $value ) {
if ( empty( $element_controls[ $control ] ) ) {
continue;
}
$control_config = $element_controls[ $control ];
if ( ! isset( $control_config['section'], $control_config['default'] ) ) {
continue;
}
$tab = $control_config['tab'];
$section = $control_config['section'];
// If setting value is not the control default.
if ( $value !== $control_config['default'] ) {
$this->increase_controls_count( $element_ref, $tab, $section, $control, 1 );
$changed_controls_count++;
}
}
return $changed_controls_count;
}
/**
* Add general controls.
*
* Extract general controls to element ref, return clean `$settings_control`.
*
* @param array $settings_controls
* @param array &$element_ref
*
* @return array ($settings_controls).
*/
private function add_general_controls( $settings_controls, &$element_ref ) {
if ( ! empty( $settings_controls[ Manager::DYNAMIC_SETTING_KEY ] ) ) {
$settings_controls = array_merge( $settings_controls, $settings_controls[ Manager::DYNAMIC_SETTING_KEY ] );
// Add dynamic count to controls under `general` tab.
$this->increase_controls_count(
$element_ref,
self::GENERAL_TAB,
Manager::DYNAMIC_SETTING_KEY,
'count',
count( $settings_controls[ Manager::DYNAMIC_SETTING_KEY ] )
);
}
return $settings_controls;
}
/**
* Add to global.
*
* Add's usage to global (update database).
*
* @param string $doc_name
* @param array $doc_usage
*/
private function add_to_global( $doc_name, $doc_usage ) {
$global_usage = get_option( self::OPTION_NAME, [] );
foreach ( $doc_usage as $element_type => $element_data ) {
if ( ! isset( $global_usage[ $doc_name ] ) ) {
$global_usage[ $doc_name ] = [];
}
if ( ! isset( $global_usage[ $doc_name ][ $element_type ] ) ) {
$global_usage[ $doc_name ][ $element_type ] = [
'count' => 0,
'controls' => [],
];
}
$global_element_ref = &$global_usage[ $doc_name ][ $element_type ];
$global_element_ref['count'] += $element_data['count'];
if ( empty( $element_data['controls'] ) ) {
continue;
}
foreach ( $element_data['controls'] as $tab => $sections ) {
foreach ( $sections as $section => $controls ) {
foreach ( $controls as $control => $count ) {
$this->increase_controls_count( $global_element_ref, $tab, $section, $control, $count );
}
}
}
}
update_option( self::OPTION_NAME, $global_usage, false );
}
/**
* Remove from global.
*
* Remove's usage from global (update database).
*
* @param Document $document
*/
private function remove_from_global( $document ) {
$prev_usage = $document->get_meta( self::META_KEY );
if ( empty( $prev_usage ) ) {
return;
}
$doc_name = $document->get_name();
$global_usage = get_option( self::OPTION_NAME, [] );
foreach ( $prev_usage as $element_type => $doc_value ) {
if ( isset( $global_usage[ $doc_name ][ $element_type ]['count'] ) ) {
$global_usage[ $doc_name ][ $element_type ]['count'] -= $prev_usage[ $element_type ]['count'];
if ( 0 === $global_usage[ $doc_name ][ $element_type ]['count'] ) {
unset( $global_usage[ $doc_name ][ $element_type ] );
if ( 0 === count( $global_usage[ $doc_name ] ) ) {
unset( $global_usage[ $doc_name ] );
}
continue;
}
foreach ( $prev_usage[ $element_type ]['controls'] as $tab => $sections ) {
foreach ( $sections as $section => $controls ) {
foreach ( $controls as $control => $count ) {
if ( isset( $global_usage[ $doc_name ][ $element_type ]['controls'][ $tab ][ $section ][ $control ] ) ) {
$section_ref = &$global_usage[ $doc_name ][ $element_type ]['controls'][ $tab ][ $section ];
$section_ref[ $control ] -= $count;
if ( 0 === $section_ref[ $control ] ) {
unset( $section_ref[ $control ] );
}
}
}
}
}
}
}
update_option( self::OPTION_NAME, $global_usage, false );
$document->delete_meta( self::META_KEY );
}
/**
* Get elements usage.
*
* Get's the current elements usage by passed elements array parameter.
*
* @param array $elements
*
* @return array
*/
private function get_elements_usage( $elements ) {
$usage = [];
Plugin::$instance->db->iterate_data( $elements, function ( $element ) use ( &$usage ) {
if ( empty( $element['widgetType'] ) ) {
$type = $element['elType'];
$element_instance = Plugin::$instance->elements_manager->get_element_types( $type );
} else {
$type = $element['widgetType'];
$element_instance = Plugin::$instance->widgets_manager->get_widget_types( $type );
}
if ( ! isset( $usage[ $type ] ) ) {
$usage[ $type ] = [
'count' => 0,
'control_percent' => 0,
'controls' => [],
];
}
$usage[ $type ]['count']++;
if ( ! $element_instance ) {
return $element;
}
$element_controls = $element_instance->get_controls();
if ( isset( $element['settings'] ) ) {
$settings_controls = $element['settings'];
$element_ref = &$usage[ $type ];
// Add dynamic values.
$settings_controls = $this->add_general_controls( $settings_controls, $element_ref );
$changed_controls_count = $this->add_controls( $settings_controls, $element_controls, $element_ref );
$percent = $changed_controls_count / ( count( $element_controls ) / 100 );
$usage[ $type ] ['control_percent'] = (int) round( $percent );
}
return $element;
} );
return $usage;
}
/**
* Save document usage.
*
* Save requested document usage, and update global.
*
* @param Document $document
*/
private function save_document_usage( Document $document ) {
if ( ! $document::get_property( 'is_editable' ) && ! $document->is_built_with_elementor() ) {
return;
}
// Get data manually to avoid conflict with `\Elementor\Core\Base\Document::get_elements_data... convert_to_elementor`.
$data = $document->get_json_meta( '_elementor_data' );
if ( ! empty( $data ) ) {
try {
$usage = $this->get_elements_usage( $document->get_elements_raw_data( $data ) );
$document->update_meta( self::META_KEY, $usage );
$this->add_to_global( $document->get_name(), $usage );
} catch ( \Exception $exception ) {
return; // Do nothing.
};
}
}
/**
* Add system info report.
*/
public function add_system_info_report() {
System_Info::add_report( 'usage', [
'file_name' => __DIR__ . '/usage-reporter.php',
'class_name' => __NAMESPACE__ . '\Usage_Reporter',
] );
}
/**
* Usage module constructor.
*
* Initializing Elementor usage module.
*
* @access public
*/
public function __construct() {
add_action( 'transition_post_status', [ $this, 'on_status_change' ], 10, 3 );
add_action( 'before_delete_post', [ $this, 'on_before_delete_post' ] );
add_action( 'elementor/document/before_save', [ $this, 'before_document_save' ], 10, 2 );
add_action( 'elementor/document/after_save', [ $this, 'after_document_save' ] );
add_filter( 'elementor/tracker/send_tracking_data_params', [ $this, 'add_tracking_data' ] );
add_action( 'admin_init', [ $this, 'add_system_info_report' ], 50 );
}
}
@@ -0,0 +1,94 @@
<?php
namespace Elementor\Modules\Usage;
use Elementor\Modules\System_Info\Reporters\Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor usage report.
*
* Elementor system report handler class responsible for generating a report for
* the user.
*/
class Usage_Reporter extends Base {
const RECALC_ACTION = 'elementor_usage_recalc';
public function get_title() {
$title = 'Elements Usage';
if ( 'html' === $this->_properties['format'] && empty( $_GET[ self::RECALC_ACTION ] ) ) { // phpcs:ignore -- nonce validation is not require here.
$nonce = wp_create_nonce( self::RECALC_ACTION );
$url = add_query_arg( [
self::RECALC_ACTION => 1,
'_wpnonce' => $nonce,
] );
$title .= '<a id="elementor-usage-recalc" href="' . esc_url( $url ) . '#elementor-usage-recalc" class="box-title-tool">Recalculate</a>';
}
return $title;
}
public function get_fields() {
return [
'usage' => '',
];
}
public function get_usage() {
/** @var Module $module */
$module = Module::instance();
if ( ! empty( $_GET[ self::RECALC_ACTION ] ) ) {
if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], self::RECALC_ACTION ) ) {
wp_die( 'Invalid Nonce', 'Invalid Nonce', [
'back_link' => true,
] );
}
$module->recalc_usage();
wp_safe_redirect( remove_query_arg( self::RECALC_ACTION ) );
die;
}
$usage = '';
foreach ( $module->get_formatted_usage() as $doc_type => $data ) {
$usage .= '<tr><td>' . $data['title'] . ' ( ' . $data['count'] . ' )</td><td>';
foreach ( $data['elements'] as $element => $count ) {
$usage .= $element . ': ' . $count . PHP_EOL;
}
$usage .= '</td></tr>';
}
return [
'value' => $usage,
];
}
public function get_raw_usage() {
/** @var Module $module */
$module = Module::instance();
$usage = PHP_EOL;
foreach ( $module->get_formatted_usage( 'raw' ) as $doc_type => $data ) {
$usage .= "\t{$data['title']} : " . $data['count'] . PHP_EOL;
foreach ( $data['elements'] as $element => $count ) {
$usage .= "\t\t{$element} : {$count}" . PHP_EOL;
}
}
return [
'value' => $usage,
];
}
}
@@ -0,0 +1,29 @@
<?php
namespace Elementor\Modules\WpCli;
use Elementor\Core\Logger\Loggers\Db;
use Elementor\Core\Logger\Items\Log_Item_Interface as Log_Item_Interface;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Cli_Logger extends Db {
public function save_log( Log_Item_Interface $item ) {
$message = $item->format( 'raw' );
switch ( $item->type ) {
case self::LEVEL_WARNING:
\WP_CLI::warning( $message );
break;
case self::LEVEL_ERROR:
\WP_CLI::error( $message, false );
break;
default:
\WP_CLI::log( $message );
break;
}
parent::save_log( $item );
}
}
@@ -0,0 +1,162 @@
<?php
namespace Elementor\Modules\WpCli;
use Elementor\Api;
use Elementor\Plugin;
use Elementor\TemplateLibrary\Source_Local;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor Page Builder cli tools.
*/
class Command extends \WP_CLI_Command {
/**
* Flush the Elementor Page Builder CSS Cache.
*
* [--network]
* Flush CSS Cache for all the sites in the network.
*
* ## EXAMPLES
*
* 1. wp elementor flush-css
* - This will flush the CSS files for elementor page builder.
*
* 2. wp elementor flush-css --network
* - This will flush the CSS files for elementor page builder for all the sites in the network.
*
* @since 2.1.0
* @access public
* @alias flush-css
*/
public function flush_css( $args, $assoc_args ) {
$network = ! empty( $assoc_args['network'] ) && is_multisite();
if ( $network ) {
/** @var \WP_Site[] $blogs */
$blogs = get_sites();
foreach ( $blogs as $keys => $blog ) {
// Cast $blog as an array instead of object
$blog_id = $blog->blog_id;
switch_to_blog( $blog_id );
Plugin::$instance->files_manager->clear_cache();
\WP_CLI::success( 'Flushed the Elementor CSS Cache for site - ' . get_option( 'home' ) );
restore_current_blog();
}
} else {
Plugin::$instance->files_manager->clear_cache();
\WP_CLI::success( 'Flushed the Elementor CSS Cache' );
}
}
/**
* Print system info powered by Elementor
*
* ## EXAMPLES
*
* 1. wp elementor system-info
* - This will print the System Info in JSON format
*
* @since 3.0.11
* @access public
* @alias system-info
*/
public function system_info() {
echo wp_json_encode( \Elementor\Tracker::get_tracking_data() );
}
/**
* Replace old URLs with new URLs in all Elementor pages.
*
* ## EXAMPLES
*
* 1. wp elementor replace-urls <old> <new>
* - This will replace all <old> URLs with the <new> URL.
*
* @access public
* @alias replace-urls
*/
public function replace_urls( $args, $assoc_args ) {
if ( empty( $args[0] ) ) {
\WP_CLI::error( 'Please set the `old` URL' );
}
if ( empty( $args[1] ) ) {
\WP_CLI::error( 'Please set the `new` URL' );
}
try {
$results = Utils::replace_urls( $args[0], $args[1] );
\WP_CLI::success( $results );
} catch ( \Exception $e ) {
\WP_CLI::error( $e->getMessage() );
}
}
/**
* Sync Elementor Library.
*
* ## EXAMPLES
*
* 1. wp elementor sync-library
* - This will sync the library with Elementor cloud library.
*
* @since 2.1.0
* @access public
* @alias sync-library
*/
public function sync_library( $args, $assoc_args ) {
// TODO:
// \WP_CLI::warning( 'command is deprecated since 2.8.0 Please use: wp elementor library sync' );
$data = Api::get_library_data( true );
if ( empty( $data ) ) {
\WP_CLI::error( 'Cannot sync library.' );
}
\WP_CLI::success( 'Library has been synced.' );
}
/**
* Import template files to the Library.
*
* ## EXAMPLES
*
* 1. wp elementor import-library <file-path>
* - This will import a file or a zip of multiple files to the library.
*
* @since 2.1.0
* @access public
* @alias import-library
*/
public function import_library( $args, $assoc_args ) {
// TODO:
// \WP_CLI::warning( 'command is deprecated since 2.8.0 Please use: wp elementor library import' );
if ( empty( $args[0] ) ) {
\WP_CLI::error( 'Please set file path.' );
}
/** @var Source_Local $source */
$source = Plugin::$instance->templates_manager->get_source( 'local' );
$imported_items = $source->import_template( basename( $args[0] ), $args[0] );
if ( is_wp_error( $imported_items ) ) {
\WP_CLI::error( $imported_items->get_error_message() );
}
\WP_CLI::success( count( $imported_items ) . ' item(s) has been imported.' );
}
}
@@ -0,0 +1,189 @@
<?php
namespace Elementor\Modules\WpCli;
use Elementor\Api;
use Elementor\Plugin;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor Page Builder cli tools.
*/
class Library extends \WP_CLI_Command {
/**
* Sync Elementor Library.
*
* [--network]
* Sync library in all the sites in the network.
*
* [--force]
* Force sync even if it's looks like that the library is already up to date.
*
* ## EXAMPLES
*
* 1. wp elementor library sync
* - This will sync the library with Elementor cloud library.
*
* 2. wp elementor library sync --force
* - This will sync the library with Elementor cloud even if it's looks like that the library is already up to date.
*
* 3. wp elementor library sync --network
* - This will sync the library with Elementor cloud library for each site in the network if needed.
*
* @since 2.8.0
* @access public
*/
public function sync( $args, $assoc_args ) {
$network = isset( $assoc_args['network'] ) && is_multisite();
if ( $network ) {
/** @var \WP_Site[] $sites */
$sites = get_sites();
foreach ( $sites as $keys => $blog ) {
// Cast $blog as an array instead of object
$blog_id = $blog->blog_id;
switch_to_blog( $blog_id );
\WP_CLI::line( 'Site #' . $blog_id . ' - ' . get_option( 'blogname' ) );
$this->do_sync( isset( $assoc_args['force'] ) );
\WP_CLI::success( 'Done! - ' . get_option( 'home' ) );
restore_current_blog();
}
} else {
$this->do_sync( isset( $assoc_args['force'] ) );
\WP_CLI::success( 'Done!' );
}
}
/**
* Import template files to the Library.
*
* ## EXAMPLES
*
* 1. wp elementor library import <file-path>
* - This will import a file or a zip of multiple files to the library.
*
* @param $args
* @param $assoc_args
*
* @since 2.8.0
* @access public
*/
public function import( $args ) {
if ( empty( $args[0] ) ) {
\WP_CLI::error( 'Please set file path.' );
}
$file = $args[0];
/** @var Source_Local $source */
$source = Plugin::$instance->templates_manager->get_source( 'local' );
$imported_items = $source->import_template( basename( $file ), $file );
if ( is_wp_error( $imported_items ) ) {
\WP_CLI::error( $imported_items->get_error_message() );
}
\WP_CLI::success( count( $imported_items ) . ' item(s) has been imported.' );
}
/**
* Connect site to Elementor Library.
* (Network is not supported)
*
* --user
* The user to connect <id|login|email>
*
* --token
* A connect token from Elementor Account Dashboard.
*
* ## EXAMPLES
*
* 1. wp elementor library connect --user=admin --token=<connect-cli-token>
* - This will connect the admin to Elementor library.
*
* @param $args
* @param $assoc_args
*
* @since 2.8.0
* @access public
*/
public function connect( $args, $assoc_args ) {
if ( ! get_current_user_id() ) {
\WP_CLI::error( 'Please set user to connect (--user=<id|login|email>).' );
}
if ( empty( $assoc_args['token'] ) ) {
\WP_CLI::error( 'Please set connect token.' );
}
$_REQUEST['mode'] = 'cli';
$_REQUEST['token'] = $assoc_args['token'];
$app = $this->get_library_app();
$app->action_authorize();
$app->action_get_token();
}
/**
* Disconnect site from Elementor Library.
*
* --user
* The user to disconnect <id|login|email>
*
* ## EXAMPLES
*
* 1. wp elementor library disconnect --user=admin
* - This will disconnect the admin from Elementor library.
*
* @param $args
* @param $assoc_args
*
* @since 2.8.0
* @access public
*/
public function disconnect() {
if ( ! get_current_user_id() ) {
\WP_CLI::error( 'Please set user to connect (--user=<id|login|email>).' );
}
$_REQUEST['mode'] = 'cli';
$this->get_library_app()->action_disconnect();
}
private function do_sync() {
$data = Api::get_library_data( true );
if ( empty( $data ) ) {
\WP_CLI::error( 'Cannot sync library.' );
}
}
/**
* @return \Elementor\Core\Common\Modules\Connect\Apps\Library
*/
private function get_library_app() {
$connect = Plugin::$instance->common->get_component( 'connect' );
$app = $connect->get_app( 'library' );
// Before init.
if ( ! $app ) {
$connect->init();
$app = $connect->get_app( 'library' );
}
return $app;
}
}
@@ -0,0 +1,61 @@
<?php
namespace Elementor\Modules\WpCli;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Logger\Manager as Logger;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends BaseModule {
/**
* Get module name.
*
* @since 2.0.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'wp-cli';
}
/**
* @since 2.1.0
* @access public
* @static
*/
public static function is_active() {
return defined( 'WP_CLI' ) && WP_CLI;
}
/**
* @param Logger $logger
* @access public
*/
public function register_cli_logger( $logger ) {
$logger->register_logger( 'cli', 'Elementor\Modules\WpCli\Cli_Logger' );
$logger->set_default_logger( 'cli' );
}
public function init_common() {
Plugin::$instance->init_common();
}
/**
*
* @since 2.1.0
* @access public
*/
public function __construct() {
add_action( 'cli_init', [ $this, 'init_common' ] );
add_action( 'elementor/loggers/register', [ $this, 'register_cli_logger' ] );
\WP_CLI::add_command( 'elementor', '\Elementor\Modules\WpCli\Command' );
\WP_CLI::add_command( 'elementor update', '\Elementor\Modules\WpCli\Update' );
\WP_CLI::add_command( 'elementor library', '\Elementor\Modules\WpCli\Library' );
}
}
@@ -0,0 +1,110 @@
<?php
namespace Elementor\Modules\WpCli;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor Page Builder cli tools.
*/
class Update extends \WP_CLI_Command {
/**
* Update the DB after plugin upgrade.
*
* [--network]
* Update DB in all the sites in the network.
*
* [--force]
* Force update even if it's looks like that update is in progress.
*
*
* ## EXAMPLES
*
* 1. wp elementor update db
* - This will Upgrade the DB if needed.
*
* 2. wp elementor update db --force
* - This will Upgrade the DB even if another process is running.
*
* 3. wp elementor update db --network
* - This will Upgrade the DB for each site in the network if needed.
*
* @since 2.4.0
* @access public
*
* @param $args
* @param $assoc_args
*/
public function db( $args, $assoc_args ) {
$network = ! empty( $assoc_args['network'] ) && is_multisite();
if ( $network ) {
/** @var \WP_Site[] $sites */
$sites = get_sites();
foreach ( $sites as $keys => $blog ) {
// Cast $blog as an array instead of object
$blog_id = $blog->blog_id;
switch_to_blog( $blog_id );
\WP_CLI::line( 'Site #' . $blog_id . ' - ' . get_option( 'blogname' ) );
$this->do_db_upgrade( $assoc_args );
\WP_CLI::success( 'Done! - ' . get_option( 'home' ) );
restore_current_blog();
}
} else {
$this->do_db_upgrade( $assoc_args );
}
}
protected function get_update_db_manager_class() {
return '\Elementor\Core\Upgrade\Manager';
}
protected function do_db_upgrade( $assoc_args ) {
$manager_class = $this->get_update_db_manager_class();
/** @var \Elementor\Core\Upgrade\Manager $manager */
$manager = new $manager_class();
$updater = $manager->get_task_runner();
if ( $updater->is_process_locked() && empty( $assoc_args['force'] ) ) {
\WP_CLI::warning( 'Oops! Process is already running. Use --force to force run.' );
return;
}
if ( ! $manager->should_upgrade() ) {
\WP_CLI::success( 'The DB is already updated!' );
return;
}
$callbacks = $manager->get_upgrade_callbacks();
$did_tasks = false;
if ( ! empty( $callbacks ) ) {
Plugin::$instance->logger->get_logger()->info( 'Update DB has been started', [
'meta' => [
'plugin' => $manager->get_plugin_label(),
'from' => $manager->get_current_version(),
'to' => $manager->get_new_version(),
],
] );
$updater->handle_immediately( $callbacks );
$did_tasks = true;
}
$manager->on_runner_complete( $did_tasks );
\WP_CLI::success( count( $callbacks ) . ' updates(s) has been applied.' );
}
}