first commit

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

View File

@@ -0,0 +1,42 @@
<?php
namespace ElementorPro\Modules\GlobalWidget\Data;
use Elementor\Data\Base\Controller as Controller_Base;
use ElementorPro\Plugin;
class Controller extends Controller_Base {
public function get_name() {
return 'global-widget/templates';
}
public function register_endpoints() {} // No endpoints.
// TODO: After merging with 'REST API V2' add `get_collection_params`.
public function get_items( $request ) {
$result = [];
$ids = $request->get_param( 'ids' );
if ( ! empty( $ids ) ) {
// TODO: This logic should be handled at REST API.
$ids = explode( ',', $ids );
foreach ( $ids as $template_id ) {
$template_data = Plugin::elementor()->templates_manager->get_template_data( [
'source' => 'local',
'template_id' => $template_id,
] );
if ( ! empty( $template_data ) ) {
$result[ $template_id ] = $template_data['content'][0];
}
}
}
return $result;
}
public function get_permission_callback( $request ) {
return current_user_can( 'edit_posts' );
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace ElementorPro\Modules\GlobalWidget\Documents;
use Elementor\Core\Base\Document;
use Elementor\Modules\Library\Documents\Library_Document;
use Elementor\User;
use ElementorPro\Core\Behaviors\Feature_Lock;
use ElementorPro\Modules\GlobalWidget\Module;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Widget extends Library_Document {
public static function get_properties() {
$properties = parent::get_properties();
$properties['admin_tab_group'] = 'library';
$properties['show_in_library'] = false;
$properties['is_editable'] = false;
// Since, created in the editor environment.
$properties['show_in_finder'] = false;
return $properties;
}
public function get_name() {
return 'widget';
}
public static function get_title() {
return esc_html__( 'Global Widget', 'elementor-pro' );
}
public static function get_plural_title() {
return esc_html__( 'Global Widgets', 'elementor-pro' );
}
public static function get_lock_behavior_v2() {
return new Feature_Lock( [
'type' => static::get_type(),
] );
}
public function is_editable_by_current_user() {
return User::is_current_user_can_edit( $this->get_main_id() );
}
public function import( array $data ) {
parent::import( $data );
$this->update_main_meta( Module::WIDGET_TYPE_META_KEY, $data['content'][0]['widgetType'] );
}
public function save( $data ) {
// Since the method of 'modules/usage::before_document_save' will remove from global if new_status is the same as old.
$data['settings'] = [ 'post_status' => Document::STATUS_PUBLISH ];
return parent::save( $data );
}
}

View File

@@ -0,0 +1,266 @@
<?php
namespace ElementorPro\Modules\GlobalWidget;
use Elementor\Core\Documents_Manager;
use Elementor\Element_Base;
use Elementor\TemplateLibrary\Source_Local;
use ElementorPro\Base\Module_Base;
use ElementorPro\Modules\GlobalWidget\Documents\Widget;
use ElementorPro\Plugin;
use ElementorPro\License\API;
use ElementorPro\Modules\Tiers\Module as Tiers;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Module_Base {
const TEMPLATE_TYPE = 'widget';
const WIDGET_TYPE_META_KEY = '_elementor_template_widget_type';
const INCLUDED_POSTS_LIST_META_KEY = '_elementor_global_widget_included_posts';
const WIDGET_NAME_CLASS_NAME_MAP = [
'global-widget' => 'Global_Widget',
];
const LICENSE_FEATURE_NAME = 'global-widget';
public function __construct() {
parent::__construct();
$this->add_hooks();
Plugin::elementor()->data_manager->register_controller_instance( new Data\Controller() );
}
public function get_widgets() {
return API::filter_active_features( static::WIDGET_NAME_CLASS_NAME_MAP );
}
public function get_name() {
return 'global-widget';
}
public function add_templates_localize_data( $settings ) {
$elementor = Plugin::elementor();
$templates_manager = $elementor->templates_manager;
$widgets_types = $elementor->widgets_manager->get_widget_types();
$widget_templates = array_filter( $templates_manager->get_source( 'local' )->get_items( [ 'type' => self::TEMPLATE_TYPE ] ), function( $template ) use ( $widgets_types ) {
if ( empty( $template['widgetType'] ) || empty( $widgets_types[ $template['widgetType'] ] ) ) {
return false;
}
// Open the stack in order to include the widget controls in initial editor config
$widgets_types[ $template['widgetType'] ]->get_stack( false );
return true;
} );
$widget_templates_content = [];
foreach ( $widget_templates as $widget_template ) {
$widget_templates_content[ $widget_template['template_id'] ] = [
'elType' => 'widget',
'title' => $widget_template['title'],
'widgetType' => $widget_template['widgetType'],
];
}
$settings = array_replace_recursive( $settings, [
'widget_templates' => $widget_templates_content,
'should_show_promotion' => ! API::is_licence_has_feature( static::LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ),
] );
return $settings;
}
public function set_template_widget_type_meta( $post_id, $template_data ) {
if ( self::TEMPLATE_TYPE === $template_data['type'] ) {
update_post_meta( $post_id, self::WIDGET_TYPE_META_KEY, $template_data['content'][0]['widgetType'] );
}
}
public function on_template_update( $template_id, $template_data ) {
if ( self::TEMPLATE_TYPE !== $template_data['type'] ) {
return;
}
$this->delete_included_posts_css( $template_id );
}
public function filter_template_data( $data ) {
if ( self::TEMPLATE_TYPE === $data['type'] ) {
$data['widgetType'] = get_post_meta( $data['template_id'], self::WIDGET_TYPE_META_KEY, true );
}
return $data;
}
public function get_element_child_type( Element_Base $default_child_type, array $element_data ) {
if ( isset( $element_data['templateID'] ) ) {
$template_post = get_post( $element_data['templateID'] );
if ( ! $template_post || 'trash' === $template_post->post_status ) {
return false;
}
}
return $default_child_type;
}
public function is_post_type_support_elementor( $is_supported, $post_id, $post_type ) {
if ( ! $is_supported || Source_Local::CPT !== $post_type ) {
return $is_supported;
}
$is_widget_template = $this->is_widget_template( $post_id );
// FIX ME: Change `get_current_screen()` condition to better way.
if ( $is_widget_template && function_exists( 'get_current_screen' ) ) {
$screen = get_current_screen();
if ( ! empty( $screen->id ) && in_array( $screen->id, [ 'elementor_library', 'edit-elementor_library' ] ) ) {
$is_supported = false;
}
}
return $is_supported;
}
public function is_template_supports_export( $default_value, $template_id ) {
return $default_value && ! $this->is_widget_template( $template_id );
}
/**
* Remove user edit capabilities.
*
* Filters the user capabilities to disable editing in admin.
*
* @param array $allcaps An array of all the user's capabilities.
* @param array $caps Actual capabilities for meta capability.
* @param array $args Optional parameters passed to has_cap(), typically object ID.
*
* @return array
* @deprecated 3.1.0 Use `Plugin::elementor()->documents->remove_user_edit_cap()` instead.
*/
public function remove_user_edit_cap( $allcaps, $caps, $args ) {
Plugin::elementor()->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', 'Plugin::elementor()->documents->remove_user_edit_cap()' );
return Plugin::elementor()->documents->remove_user_edit_cap( $allcaps, $caps, $args );
}
public function is_widget_template( $template_id ) {
$template_type = Source_Local::get_template_type( $template_id );
return self::TEMPLATE_TYPE === $template_type;
}
public function set_global_widget_included_posts_list( $post_id, $editor_data ) {
$global_widget_ids = [];
Plugin::elementor()->db->iterate_data( $editor_data, function( $element_data ) use ( &$global_widget_ids ) {
if ( isset( $element_data['templateID'] ) ) {
$global_widget_ids[] = $element_data['templateID'];
}
} );
foreach ( $global_widget_ids as $widget_id ) {
$included_posts = get_post_meta( $widget_id, self::INCLUDED_POSTS_LIST_META_KEY, true );
if ( ! is_array( $included_posts ) ) {
$included_posts = [];
}
$included_posts[ $post_id ] = true;
update_post_meta( $widget_id, self::INCLUDED_POSTS_LIST_META_KEY, $included_posts );
}
}
private function delete_included_posts_css( $template_id ) {
$including_post_ids = (array) get_post_meta( $template_id, self::INCLUDED_POSTS_LIST_META_KEY, true );
if ( empty( $including_post_ids ) ) {
return;
}
foreach ( array_keys( $including_post_ids ) as $post_id ) {
delete_post_meta( $post_id, '_elementor_css' );
}
}
/**
* @param Documents_Manager $documents_manager
*/
public function register_documents( $documents_manager ) {
$documents_manager->register_document_type( self::TEMPLATE_TYPE, Documents\Widget::get_class_full_name() );
}
public function on_elementor_editor_init() {
if ( ! API::is_licence_has_feature( static::LICENSE_FEATURE_NAME, API::BC_VALIDATION_CALLBACK ) ) {
$promotion = Tiers::get_promotion_template( [
'title' => esc_html__( 'Meet Our Global Widget', 'elementor-pro' ),
'messages' => [
esc_html__( 'Create Global Widgets. Modify the content, style and setting of any widget and reuse it across your website to accelerate your workflow and stay consistent.', 'elementor-pro' ),
],
'link' => 'https://go.elementor.com/go-pro-advanced-global-widget/',
], true );
Plugin::elementor()->common->add_template( $promotion, 'text' );
return;
}
Plugin::elementor()->common->add_template( __DIR__ . '/views/panel-template.php' );
}
/**
* Get document data.
*
* Used to manipulate data of global widgets.
*
* @param $data
* @param $document
*
* @return array
*/
private function get_document_data( $data, $document ) {
// If not a global widget template document or does not have elements.
if ( ! ( $document instanceof Widget ) && ! empty( $data['elements'] ) ) {
$data['elements'] = Plugin::elementor()->db->iterate_data( $data['elements'], function( $element ) {
if ( ! empty( $element['templateID'] ) ) {
$element['originalWidgetType'] = $element['widgetType'];
$element['widgetType'] = 'global';
}
return $element;
} );
}
return $data;
}
private function add_hooks() {
add_action( 'elementor/documents/register', [ $this, 'register_documents' ] );
add_action( 'elementor/template-library/after_save_template', [ $this, 'set_template_widget_type_meta' ], 10, 2 );
add_action( 'elementor/template-library/after_update_template', [ $this, 'on_template_update' ], 10, 2 );
add_action( 'elementor/editor/init', [ $this, 'on_elementor_editor_init' ] );
add_action( 'elementor/editor/after_save', [ $this, 'set_global_widget_included_posts_list' ], 10, 2 );
add_filter( 'elementor_pro/editor/localize_settings', [ $this, 'add_templates_localize_data' ] );
add_filter( 'elementor/template-library/get_template', [ $this, 'filter_template_data' ] );
add_filter( 'elementor/element/get_child_type', [ $this, 'get_element_child_type' ], 10, 2 );
add_filter( 'elementor/utils/is_post_support', [ $this, 'is_post_type_support_elementor' ], 10, 3 );
add_filter( 'elementor/template_library/is_template_supports_export', [ $this, 'is_template_supports_export' ], 10, 2 );
add_filter( 'elementor/document/save/data', function ( $data, $document ) {
return $this->get_document_data( $data, $document );
}, 10, 2 );
}
}

View File

@@ -0,0 +1,33 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
?>
<script type="text/template" id="tmpl-elementor-panel-global-widget">
<div id="elementor-global-widget-locked-header" class="elementor-nerd-box elementor-panel-nerd-box">
<img class="elementor-nerd-box-icon" src="<?php echo ELEMENTOR_ASSETS_URL . 'images/information.svg'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" loading="lazy" />
<div class="elementor-nerd-box-title elementor-panel-nerd-box-title"><?php echo esc_html__( 'Your Global Widget is locked', 'elementor-pro' ); ?></div>
<div class="elementor-nerd-box-message elementor-panel-nerd-box-message"><?php echo esc_html__( 'Edit this global widget to simultaneously update every place you used it, or unlink it so it gets back to being regular widget.', 'elementor-pro' ); ?></div>
</div>
<div id="elementor-global-widget-locked-tools">
<div id="elementor-global-widget-locked-edit" class="elementor-global-widget-locked-tool">
<div class="elementor-global-widget-locked-tool-description"><?php echo esc_html__( 'Edit global widget', 'elementor-pro' ); ?></div>
<button class="elementor-button"><?php echo esc_html__( 'Edit', 'elementor-pro' ); ?></button>
</div>
<div id="elementor-global-widget-locked-unlink" class="elementor-global-widget-locked-tool">
<div class="elementor-global-widget-locked-tool-description"><?php echo esc_html__( 'Unlink from global', 'elementor-pro' ); ?></div>
<button class="elementor-button elementor-button-danger"><?php echo esc_html__( 'Unlink', 'elementor-pro' ); ?></button>
</div>
</div>
<div id="elementor-global-widget-loading" class="elementor-hidden">
<i class="eicon-loading eicon-animation-spin" aria-hidden="true"></i>
<span class="elementor-screen-only"><?php echo esc_html__( 'Loading', 'elementor-pro' ); ?></span>
</div>
</script>
<script type="text/template" id="tmpl-elementor-panel-global-widget-no-templates">
<img src="<?php echo ELEMENTOR_ASSETS_URL . 'images/information.svg'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" alt="Elementor Information Nerd Icon" loading="lazy" />
<div class="elementor-nerd-box-title elementor-panel-nerd-box-title"><?php echo esc_html__( 'Save Your First Global Widget', 'elementor-pro' ); ?></div>
<div class="elementor-nerd-box-message elementor-panel-nerd-box-message"><?php echo esc_html__( 'Save a widget as global, then add it to multiple areas. All areas will be editable from one single place.', 'elementor-pro' ); ?></div>
</script>

View File

@@ -0,0 +1,244 @@
<?php
namespace ElementorPro\Modules\GlobalWidget\Widgets;
use Elementor\Core\Base\Document;
use Elementor\Widget_Base;
use ElementorPro\Base\Base_Widget;
use ElementorPro\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Global_Widget extends Base_Widget {
/**
* @var Widget_Base
*/
private $original_element_instance;
/**
* @var array
*/
private $template_data;
/**
* @var array
*/
private $data;
/**
* @var Widget_Base
*/
private $original_widget_type;
public function __construct( $data = [], $args = null ) {
if ( $data && ! empty( $data['templateID'] ) ) {
$template_data = Plugin::elementor()->templates_manager->get_template_data( [
'source' => 'local',
'template_id' => $data['templateID'],
] );
if ( is_wp_error( $template_data ) ) {
throw new \Exception( $template_data->get_error_message() );
}
if ( empty( $template_data['content'] ) ) {
throw new \Exception( 'Template content not found.' );
}
$this->set_template_data( $template_data );
$template_widget_type = $this->get_template_widget_type();
$original_widget_type = Plugin::elementor()->widgets_manager->get_widget_types(
$template_widget_type
);
if ( ! $original_widget_type ) {
throw new \Exception( 'Original widget type not found.' );
}
// If it saved as draft it already have the recent settings.
if ( empty( $data['draft'] ) ) {
if ( empty( $data['originalWidgetType'] ) ) {
// If: `$data['originalWidgetType']` exists it means that the data was manipulated in saving process, from the backend.
// so `widgetType` is 'global' and have to be changed.
$data['widgetType'] = $template_widget_type;
}
if ( ! $this->is_draft_or_autosave_process() ) {
// If its not 'draft saving process' then settings should be according the template.
// Since draft saving process, already have the recent settings to save.
$data['settings'] = $this->get_template_settings();
}
}
$this->original_widget_type = $original_widget_type;
$this->data = $data;
}
parent::__construct( $data, $args );
}
public function show_in_panel() {
return false;
}
public function get_raw_data( $with_html_content = false ) {
$raw_data = parent::get_raw_data( $with_html_content );
// Save 'templateID' in all situations.
$raw_data['templateID'] = $this->get_data( 'templateID' );
if ( $this->is_draft_or_autosave_process() ) {
$raw_data['draft'] = true;
// Keep the current snapshot, just mark it as a draft.
return $raw_data;
}
if ( $this->is_saved_as_draft() ) {
// If: Item saved as draft
// Then: the the `$raw_data` hold recently saved draft template, with original widget type.
$raw_data['widgetType'] = $this->get_template_widget_type();
return $raw_data;
}
return $raw_data;
}
public function render_content() {
$this->get_original_element_instance()->render_content();
}
public function get_unique_selector() {
return '.elementor-global-' . $this->get_data( 'templateID' );
}
public function get_name() {
return 'global';
}
public function get_title() {
return esc_html__( 'Global', 'elementor-pro' );
}
public function get_script_depends() {
if ( $this->is_type_instance() ) {
return [];
}
return $this->get_original_element_instance()->get_script_depends();
}
public function get_style_depends() {
if ( $this->is_type_instance() ) {
return [];
}
return $this->get_original_element_instance()->get_style_depends();
}
public function get_controls( $control_id = null ) {
if ( $this->is_type_instance() ) {
return [];
}
return $this->get_original_element_instance()->get_controls();
}
public function get_original_element_instance() {
if ( ! $this->original_element_instance ) {
$this->init_original_element_instance();
}
return $this->original_element_instance;
}
public function on_export() {
return $this->get_template_content();
}
public function render_plain_content() {
$this->get_original_element_instance()->render_plain_content();
}
protected function add_render_attributes() {
// Never called from editor, this method is used only for frontend/preview.
parent::add_render_attributes();
$skin_type = $this->get_settings( '_skin' );
$original_widget_type = $this->get_original_element_instance()->get_data( 'widgetType' );
$this->set_render_attribute( '_wrapper', 'data-widget_type', $original_widget_type . '.' . ( $skin_type ? $skin_type : 'default' ) );
$this->add_render_attribute( '_wrapper', [
'class' => [
'elementor-global-' . $this->get_data( 'templateID' ),
'elementor-widget-' . $original_widget_type,
],
] );
}
private function init_original_element_instance() {
$widget_class = $this->original_widget_type->get_class_name();
$template_content = $this->get_template_or_draft_content();
$template_content['id'] = $this->get_id();
$this->original_element_instance = new $widget_class(
$template_content,
$this->original_widget_type->get_default_args()
);
}
private function is_draft_or_autosave_process() {
/**
* `Plugin::elementor()->common` is not available for guest/logged out users.
*/
if ( ! Plugin::elementor()->common ) {
return false;
}
$ajax = Plugin::elementor()->common->get_component( 'ajax' );
$ajax_data = $ajax->get_current_action_data();
// Is draft or autosave?
return $ajax_data && 'save_builder' === $ajax_data['action'] && in_array( $ajax_data['data']['status'], [
Document::STATUS_DRAFT,
Document::STATUS_AUTOSAVE,
], true );
}
private function is_saved_as_draft() {
return $this->get_data( 'draft' );
}
private function set_template_data( $template_data ) {
$this->template_data = $template_data;
}
private function get_template_widget_type() {
return $this->template_data['content'][0]['widgetType'];
}
private function get_template_settings() {
return $this->template_data['content'][0]['settings'];
}
private function get_template_content() {
return $this->template_data['content'][0];
}
private function get_template_or_draft_content() {
if ( $this->is_saved_as_draft() ) {
$draft_data = $this->data;
$draft_data['widgetType'] = $this->get_template_widget_type();
return $draft_data;
}
return $this->get_template_content();
}
}