first commit
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Base;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base as New_Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
// TODO: Remove this class after 3.36 is released.
|
||||
/**
|
||||
* @deprecated 3.34 Use \Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base instead.
|
||||
*/
|
||||
abstract class Atomic_Control_Base extends New_Atomic_Control_Base {}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\CacheValidity;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity_Item as New_Cache_Validity_Item;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
// TODO: Remove this class after 3.37 is released.
|
||||
/**
|
||||
* @deprecated 3.35 Use \Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity_Item instead.
|
||||
*/
|
||||
class Cache_Validity_Item extends New_Cache_Validity_Item {}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\CacheValidity;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity as New_Cache_Validity;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
// TODO: Remove this class after 3.37 is released.
|
||||
/**
|
||||
* @deprecated 3.35 Use \Elementor\Modules\AtomicWidgets\Styles\CacheValidity\Cache_Validity instead.
|
||||
*/
|
||||
class Cache_Validity extends New_Cache_Validity {}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Base;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Atomic_Control_Base implements JsonSerializable {
|
||||
private string $bind;
|
||||
private $label = null;
|
||||
private $description = null;
|
||||
private $meta = null;
|
||||
|
||||
abstract public function get_type(): string;
|
||||
|
||||
abstract public function get_props(): array;
|
||||
|
||||
public static function bind_to( string $prop_name ) {
|
||||
return new static( $prop_name );
|
||||
}
|
||||
|
||||
protected function __construct( string $prop_name ) {
|
||||
$this->bind = $prop_name;
|
||||
}
|
||||
|
||||
public function get_bind() {
|
||||
return $this->bind;
|
||||
}
|
||||
|
||||
public function set_label( string $label ): self {
|
||||
$this->label = html_entity_decode( $label );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_description( string $description ): self {
|
||||
$this->description = html_entity_decode( $description );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_meta( $meta ): self {
|
||||
$this->meta = $meta;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'type' => 'control',
|
||||
'value' => [
|
||||
'type' => $this->get_type(),
|
||||
'bind' => $this->get_bind(),
|
||||
'label' => $this->label,
|
||||
'description' => $this->description,
|
||||
'props' => $this->get_props(),
|
||||
'meta' => $this->meta,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Base;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Element_Control_Base implements JsonSerializable {
|
||||
private $label = null;
|
||||
private $meta = null;
|
||||
|
||||
abstract public function get_type(): string;
|
||||
|
||||
abstract public function get_props(): array;
|
||||
|
||||
public static function make(): self {
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function set_label( string $label ): self {
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_label(): string {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function set_meta( $meta ): self {
|
||||
$this->meta = $meta;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_meta(): array {
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'type' => 'element-control',
|
||||
'value' => [
|
||||
'label' => $this->get_label(),
|
||||
'meta' => $this->get_meta(),
|
||||
'type' => $this->get_type(),
|
||||
'props' => $this->get_props(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Section implements JsonSerializable {
|
||||
private ?string $id = null;
|
||||
private $label = null;
|
||||
private $description = null;
|
||||
private array $items = [];
|
||||
|
||||
public static function make(): self {
|
||||
return new static();
|
||||
}
|
||||
|
||||
public function set_id( string $id ): self {
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_id() {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function set_label( string $label ): self {
|
||||
$this->label = html_entity_decode( $label );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_label(): ?string {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function set_description( string $description ): self {
|
||||
$this->description = html_entity_decode( $description );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_items( array $items ): self {
|
||||
$this->items = $items;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function add_item( $item ): self {
|
||||
$this->items[] = $item;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_items() {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'type' => 'section',
|
||||
'value' => [
|
||||
'label' => $this->label,
|
||||
'description' => $this->description,
|
||||
'items' => $this->items,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Chips_Control extends Atomic_Control_Base {
|
||||
private array $options = [];
|
||||
|
||||
public function get_type(): string {
|
||||
return 'chips';
|
||||
}
|
||||
|
||||
public function set_options( array $options ): self {
|
||||
$this->options = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'options' => $this->options,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Date_Time_Control extends Atomic_Control_Base {
|
||||
public function get_type(): string {
|
||||
return 'date-time';
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types\Elements;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Element_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Tabs_Control extends Element_Control_Base {
|
||||
public function get_type(): string {
|
||||
return 'tabs';
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Email_Form_Action_Control extends Atomic_Control_Base {
|
||||
public function get_type(): string {
|
||||
return 'email';
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Html_Tag_Control extends Select_Control {
|
||||
public function get_type(): string {
|
||||
return 'html-tag';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Image\Image_Sizes;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Image_Control extends Atomic_Control_Base {
|
||||
public function get_type(): string {
|
||||
return 'image';
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'sizes' => Image_Sizes::get_all(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Inline_Editing_Control extends Atomic_Control_Base {
|
||||
private ?string $placeholder = null;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'inline-editing';
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = $placeholder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'placeholder' => $this->placeholder,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Query\Query_Builder_Factory as Query_Builder;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Link_Control extends Atomic_Control_Base {
|
||||
private bool $allow_custom_values = true;
|
||||
private int $minimum_input_length = 2;
|
||||
private ?array $query_config = null;
|
||||
private ?string $placeholder = null;
|
||||
private ?string $aria_label = null;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'link';
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = $placeholder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_allow_custom_values( bool $allow_custom_values ): self {
|
||||
$this->allow_custom_values = $allow_custom_values;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_query_config( $config ): self {
|
||||
$this->query_config = $config;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'allowCustomValues' => $this->allow_custom_values,
|
||||
'placeholder' => $this->placeholder,
|
||||
'queryOptions' => Query_Builder::create( $this->query_config )->build(),
|
||||
'minInputLength' => $this->minimum_input_length,
|
||||
'ariaLabel' => 'Link URL',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Number_Control extends Atomic_Control_Base {
|
||||
private ?string $placeholder = null;
|
||||
private ?int $max = null;
|
||||
private ?int $min = null;
|
||||
private ?int $step = null;
|
||||
private ?bool $should_force_int = null;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'number';
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = $placeholder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_max( ?int $max ): self {
|
||||
$this->max = $max;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_min( ?int $min ): self {
|
||||
$this->min = $min;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_step( ?int $step ): self {
|
||||
$this->step = $step;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_should_force_int( ?bool $should_force_int ): self {
|
||||
$this->should_force_int = $should_force_int ?? false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'placeholder' => $this->placeholder,
|
||||
'max' => $this->max,
|
||||
'min' => $this->min,
|
||||
'step' => $this->step,
|
||||
'shouldForceInt' => $this->should_force_int,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Query\Query_Builder_Factory as Query_Builder;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Query_Control extends Atomic_Control_Base {
|
||||
private bool $allow_custom_values = true;
|
||||
private int $minimum_input_length = 2;
|
||||
private ?array $query_config = null;
|
||||
private ?string $placeholder = null;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'query';
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = $placeholder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_allow_custom_values( bool $allow_custom_values ): self {
|
||||
$this->allow_custom_values = $allow_custom_values;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_query_config( $config ): self {
|
||||
$this->query_config = $config;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'allowCustomValues' => $this->allow_custom_values,
|
||||
'placeholder' => $this->placeholder,
|
||||
'queryOptions' => Query_Builder::create( $this->query_config )->build(),
|
||||
'minInputLength' => $this->minimum_input_length,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Repeatable_Control extends Atomic_Control_Base {
|
||||
|
||||
private string $child_control_type;
|
||||
private object $child_control_props;
|
||||
private bool $show_duplicate = true;
|
||||
private bool $show_toggle = true;
|
||||
private string $repeater_label;
|
||||
private ?object $initial_values;
|
||||
private ?string $pattern_label;
|
||||
private ?string $placeholder;
|
||||
private ?string $prop_key = '';
|
||||
|
||||
public function get_type(): string {
|
||||
return 'repeatable';
|
||||
}
|
||||
|
||||
public function set_child_control_type( $control_type ): self {
|
||||
$this->child_control_type = $control_type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_child_control_props( $control_props ): self {
|
||||
$this->child_control_props = (object) $control_props;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hide_duplicate(): self {
|
||||
$this->show_duplicate = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hide_toggle(): self {
|
||||
$this->show_toggle = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_initialValues( $initial_values ): self {
|
||||
$this->initial_values = (object) $initial_values;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_patternLabel( $pattern_label ): self {
|
||||
$this->pattern_label = $pattern_label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_repeaterLabel( string $label ): self {
|
||||
$this->repeater_label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = $placeholder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_prop_key( string $prop_key ): self {
|
||||
$this->prop_key = $prop_key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'childControlType' => $this->child_control_type,
|
||||
'childControlProps' => $this->child_control_props,
|
||||
'showDuplicate' => $this->show_duplicate,
|
||||
'showToggle' => $this->show_toggle,
|
||||
'initialValues' => $this->initial_values,
|
||||
'patternLabel' => $this->pattern_label,
|
||||
'repeaterLabel' => $this->repeater_label,
|
||||
'placeholder' => $this->placeholder,
|
||||
'propKey' => $this->prop_key,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Select_Control extends Atomic_Control_Base {
|
||||
private array $options = [];
|
||||
private ?array $fallback_labels = null;
|
||||
private ?string $collection_id = null;
|
||||
private ?string $placeholder = null;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'select';
|
||||
}
|
||||
|
||||
public function set_options( array $options ): self {
|
||||
$this->options = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_collection_id( string $collection_id ): self {
|
||||
$this->collection_id = $collection_id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = $placeholder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
$props = [
|
||||
'options' => $this->options,
|
||||
'fallbackLabels' => $this->fallback_labels,
|
||||
'placeholder' => $this->placeholder,
|
||||
];
|
||||
|
||||
if ( $this->collection_id ) {
|
||||
$props['collectionId'] = $this->collection_id;
|
||||
}
|
||||
return $props;
|
||||
}
|
||||
|
||||
public function set_fallback_labels( array $fallback_labels ): self {
|
||||
$this->fallback_labels = $fallback_labels;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Size_Control extends Atomic_Control_Base {
|
||||
private ?string $placeholder = null;
|
||||
private ?string $variant = 'length';
|
||||
private ?array $units = null;
|
||||
private ?string $default_unit = null;
|
||||
private ?bool $disable_custom = false;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'size';
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = $placeholder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_variant( string $variant ): self {
|
||||
$this->variant = $variant;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_units( array $units ): self {
|
||||
$this->units = $units;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_default_unit( string $default_unit ): self {
|
||||
$this->default_unit = $default_unit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_disable_custom( bool $disable_custom ): self {
|
||||
$this->disable_custom = $disable_custom;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'placeholder' => $this->placeholder,
|
||||
'variant' => $this->variant,
|
||||
'units' => $this->units,
|
||||
'defaultUnit' => $this->default_unit,
|
||||
'disableCustom' => $this->disable_custom,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Image\Image_Sizes;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Svg_Control extends Atomic_Control_Base {
|
||||
public function get_type(): string {
|
||||
return 'svg-media';
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'type' => $this->get_type(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Switch_Control extends Atomic_Control_Base {
|
||||
public function get_type(): string {
|
||||
return 'switch';
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Text_Control extends Atomic_Control_Base {
|
||||
private ?string $placeholder = null;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'text';
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = $placeholder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'placeholder' => $this->placeholder,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Textarea_Control extends Atomic_Control_Base {
|
||||
private $placeholder = null;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'textarea';
|
||||
}
|
||||
|
||||
public function set_placeholder( string $placeholder ): self {
|
||||
$this->placeholder = html_entity_decode( $placeholder );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'placeholder' => $this->placeholder,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Toggle_Control extends Atomic_Control_Base {
|
||||
private array $options = [];
|
||||
private bool $full_width = false;
|
||||
private string $size = 'tiny';
|
||||
private bool $exclusive = true;
|
||||
private bool $convert_options = false;
|
||||
|
||||
public function get_type(): string {
|
||||
return 'toggle';
|
||||
}
|
||||
|
||||
public function add_options( array $control_options ): self {
|
||||
$this->options = [];
|
||||
|
||||
foreach ( $control_options as $value => $config ) {
|
||||
$this->options[] = [
|
||||
'value' => $value,
|
||||
'label' => $config['title'] ?? $value,
|
||||
'icon' => $config['atomic-icon'] ?? null,
|
||||
'showTooltip' => true,
|
||||
'exclusive' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_size( string $size ): self {
|
||||
$allowed_sizes = [ 'tiny', 'small', 'medium', 'large' ];
|
||||
|
||||
if ( in_array( $size, $allowed_sizes, true ) ) {
|
||||
$this->size = $size;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_full_width( bool $full_width ): self {
|
||||
$this->full_width = $full_width;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_exclusive( bool $exclusive ): self {
|
||||
$this->exclusive = $exclusive;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to convert the v3 options to v4 compatible
|
||||
*
|
||||
* @param bool $convert_options
|
||||
* @return $this
|
||||
*/
|
||||
public function set_convert_options( bool $convert_options ): self {
|
||||
$this->convert_options = $convert_options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [
|
||||
'options' => $this->options,
|
||||
'fullWidth' => $this->full_width,
|
||||
'size' => $this->size,
|
||||
'exclusive' => $this->exclusive,
|
||||
'convertOptions' => $this->convert_options,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Controls\Types;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Video_Control extends Atomic_Control_Base {
|
||||
public function get_type(): string {
|
||||
return 'video';
|
||||
}
|
||||
|
||||
public function get_props(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Database;
|
||||
|
||||
use Elementor\Core\Database\Base_Database_Updater;
|
||||
use Elementor\Modules\AtomicWidgets\Database\Migrations\Add_Capabilities;
|
||||
|
||||
class Atomic_Widgets_Database_Updater extends Base_Database_Updater {
|
||||
const DB_VERSION = 1;
|
||||
const OPTION_NAME = 'elementor_atomic_widgets_db_version';
|
||||
|
||||
protected function get_migrations(): array {
|
||||
return [
|
||||
1 => new Add_Capabilities(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_db_version() {
|
||||
return static::DB_VERSION;
|
||||
}
|
||||
|
||||
protected function get_db_version_option_name(): string {
|
||||
return static::OPTION_NAME;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Database\Migrations;
|
||||
|
||||
use Elementor\Core\Database\Base_Migration;
|
||||
|
||||
class Add_Capabilities extends Base_Migration {
|
||||
const ACCESS_STYLES_TAB = 'elementor_atomic_widgets_access_styles_tab';
|
||||
const EDIT_LOCAL_CSS_CLASS = 'elementor_atomic_widgets_edit_local_css_class';
|
||||
|
||||
public function up() {
|
||||
$capabilities = [
|
||||
self::ACCESS_STYLES_TAB => [ 'administrator', 'editor', 'author', 'contributor', 'shop_manager' ],
|
||||
self::EDIT_LOCAL_CSS_CLASS => [ 'administrator', 'editor', 'author', 'contributor', 'shop_manager' ],
|
||||
];
|
||||
|
||||
foreach ( $capabilities as $cap => $roles ) {
|
||||
foreach ( $roles as $role_name ) {
|
||||
$role = get_role( $role_name );
|
||||
|
||||
if ( $role ) {
|
||||
$role->add_cap( $cap );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\DynamicTags;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_Prop_Type extends Plain_Prop_Type {
|
||||
|
||||
const META_KEY = 'dynamic';
|
||||
|
||||
/**
|
||||
* Return a tuple that lets the developer ignore the dynamic prop type in the props schema
|
||||
* using `Prop_Type::meta()`, e.g. `String_Prop_Type::make()->meta( Dynamic_Prop_Type::ignore() )`.
|
||||
*/
|
||||
public static function ignore(): array {
|
||||
return [ static::META_KEY, false ];
|
||||
}
|
||||
|
||||
public static function get_key(): string {
|
||||
return 'dynamic';
|
||||
}
|
||||
|
||||
public function categories( array $categories ) {
|
||||
$this->settings['categories'] = $categories;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get_categories() {
|
||||
return $this->settings['categories'] ?? [];
|
||||
}
|
||||
|
||||
public static function is_dynamic_prop_value( $value ): bool {
|
||||
return isset( $value['$$type'] ) && self::get_key() === $value['$$type'];
|
||||
}
|
||||
|
||||
protected function validate_value( $value ): bool {
|
||||
$is_valid_structure = (
|
||||
isset( $value['name'] ) &&
|
||||
is_string( $value['name'] ) &&
|
||||
isset( $value['group'] ) &&
|
||||
is_string( $value['group'] ) &&
|
||||
isset( $value['settings'] ) &&
|
||||
is_array( $value['settings'] )
|
||||
);
|
||||
|
||||
if ( ! $is_valid_structure ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tag = Dynamic_Tags_Module::instance()->registry->get_tag( $value['name'] );
|
||||
|
||||
if ( ! $tag || ! $this->is_tag_in_supported_categories( $tag ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Props_Parser::make( $tag['props_schema'] )
|
||||
->validate( $value['settings'] )
|
||||
->is_valid();
|
||||
}
|
||||
|
||||
protected function sanitize_value( $value ): array {
|
||||
$tag = Dynamic_Tags_Module::instance()->registry->get_tag( $value['name'] );
|
||||
|
||||
$sanitized = Props_Parser::make( $tag['props_schema'] )
|
||||
->sanitize( $value['settings'] )
|
||||
->unwrap();
|
||||
|
||||
return [
|
||||
'name' => $value['name'],
|
||||
'group' => $value['group'],
|
||||
'settings' => $sanitized,
|
||||
];
|
||||
}
|
||||
|
||||
private function is_tag_in_supported_categories( array $tag ): bool {
|
||||
$intersection = array_intersect(
|
||||
$tag['categories'],
|
||||
$this->get_categories()
|
||||
);
|
||||
|
||||
return ! empty( $intersection );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\DynamicTags;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Utils\Prop_Types_Schema_Extender;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Src_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Url_Prop_Type;
|
||||
use Elementor\Modules\DynamicTags\Module as V1_Dynamic_Tags_Module;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Union_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_Prop_Types_Mapping extends Prop_Types_Schema_Extender {
|
||||
|
||||
public static function make(): self {
|
||||
return new static();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the dynamic prop type to add to the prop type
|
||||
*
|
||||
* @param Prop_Type $prop_type
|
||||
*/
|
||||
protected function get_prop_types_to_add( Prop_Type $prop_type ): array {
|
||||
$categories = [];
|
||||
|
||||
$transformable_prop_types = $prop_type instanceof Union_Prop_Type ?
|
||||
$prop_type->get_prop_types() :
|
||||
[ $prop_type ];
|
||||
|
||||
foreach ( $transformable_prop_types as $transformable_prop_type ) {
|
||||
if ( $transformable_prop_type instanceof Transformable_Prop_Type ) {
|
||||
// When the prop type is originally a union, we need to merge all the categories
|
||||
// of each prop type in the union and create one dynamic prop type with all the categories.
|
||||
$categories = array_merge( $categories, $this->get_related_categories( $transformable_prop_type ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $categories ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [ Dynamic_Prop_Type::make()->categories( $categories ) ];
|
||||
}
|
||||
|
||||
private function get_related_categories( Transformable_Prop_Type $prop_type ): array {
|
||||
if ( ! $prop_type->get_meta_item( Dynamic_Prop_Type::META_KEY, true ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( $prop_type instanceof Number_Prop_Type ) {
|
||||
return [ V1_Dynamic_Tags_Module::NUMBER_CATEGORY ];
|
||||
}
|
||||
|
||||
if ( $prop_type instanceof Svg_Src_Prop_Type ) {
|
||||
return [ V1_Dynamic_Tags_Module::SVG_CATEGORY ];
|
||||
}
|
||||
|
||||
if ( $prop_type instanceof Image_Src_Prop_Type ) {
|
||||
return [ V1_Dynamic_Tags_Module::IMAGE_CATEGORY ];
|
||||
}
|
||||
|
||||
if ( $prop_type instanceof Url_Prop_Type ) {
|
||||
return [ V1_Dynamic_Tags_Module::URL_CATEGORY ];
|
||||
}
|
||||
|
||||
if ( $prop_type instanceof Html_V3_Prop_Type ) {
|
||||
return [ V1_Dynamic_Tags_Module::TEXT_CATEGORY ];
|
||||
}
|
||||
|
||||
if ( $prop_type instanceof Color_Prop_Type ) {
|
||||
return [ V1_Dynamic_Tags_Module::COLOR_CATEGORY ];
|
||||
}
|
||||
|
||||
if ( $prop_type instanceof String_Prop_Type && empty( $prop_type->get_enum() ) ) {
|
||||
return [ V1_Dynamic_Tags_Module::TEXT_CATEGORY ];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\DynamicTags;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Base\Object_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Base\Plain_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Date_Time_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Query_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_Tags_Converter {
|
||||
|
||||
/**
|
||||
* @param array $control
|
||||
* @return Plain_Prop_Type|Object_Prop_Type|null
|
||||
*/
|
||||
public static function convert_control_to_prop_type( array $control ) {
|
||||
$control_type = $control['type'];
|
||||
|
||||
switch ( $control_type ) {
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
case 'select':
|
||||
$prop_type = String_Prop_Type::make()
|
||||
->default( $control['default'] ?? null );
|
||||
break;
|
||||
|
||||
case 'date_time':
|
||||
$prop_type = Date_Time_Prop_Type::make()
|
||||
->default( $control['default'] ?? null );
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
$prop_type = Number_Prop_Type::make()
|
||||
->set_required( $control['required'] ?? false )
|
||||
->default( $control['default'] ?? null );
|
||||
break;
|
||||
|
||||
case 'switcher':
|
||||
$default = $control['default'];
|
||||
|
||||
$prop_type = Boolean_Prop_Type::make()
|
||||
->default( 'yes' === $default || true === $default );
|
||||
break;
|
||||
|
||||
case 'choose':
|
||||
$prop_type = String_Prop_Type::make()
|
||||
->default( $control['default'] ?? null )
|
||||
->enum( array_keys( $control['options'] ?? [] ) );
|
||||
break;
|
||||
|
||||
case 'query':
|
||||
$prop_type = Query_Prop_Type::make()
|
||||
->set_required( $control['required'] ?? false )
|
||||
->default( $control['default'] ?? null );
|
||||
break;
|
||||
|
||||
case 'media':
|
||||
$prop_type = Image_Prop_Type::make()
|
||||
->default_url( Placeholder_Image::get_placeholder_image() )
|
||||
->default_size( 'full' )
|
||||
->set_shape_meta( 'src', [ 'isDynamic' => true ] );
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
$prop_type->set_dependencies( self::create_dependencies_from_condition( $control['condition'] ?? null ) );
|
||||
|
||||
return $prop_type;
|
||||
}
|
||||
|
||||
private static function create_dependencies_from_condition( $condition ): ?array {
|
||||
if ( ! is_array( $condition ) || empty( $condition ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$manager = Dependency_Manager::make( Dependency_Manager::RELATION_AND );
|
||||
|
||||
foreach ( $condition as $raw_key => $value ) {
|
||||
$is_negated = false !== strpos( (string) $raw_key, '!' );
|
||||
$key = rtrim( (string) $raw_key, '!' );
|
||||
$path = self::parse_condition_path( $key );
|
||||
|
||||
if ( is_array( $value ) ) {
|
||||
$manager->where( [
|
||||
'operator' => $is_negated ? 'nin' : 'in',
|
||||
'path' => $path,
|
||||
'value' => $value,
|
||||
] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$manager->where( [
|
||||
'operator' => $is_negated ? 'ne' : 'eq',
|
||||
'path' => $path,
|
||||
'value' => $value,
|
||||
] );
|
||||
}
|
||||
|
||||
return $manager->get();
|
||||
}
|
||||
|
||||
private static function parse_condition_path( string $key ): array {
|
||||
if ( false === strpos( $key, '[' ) ) {
|
||||
return [ $key ];
|
||||
}
|
||||
|
||||
$key = str_replace( ']', '', $key );
|
||||
$tokens = explode( '[', $key );
|
||||
|
||||
return array_values( array_filter( $tokens, static fn( $t ) => '' !== $t ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\DynamicTags;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Date_Time_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Image_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Toggle_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Query_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Number_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Textarea_Control;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Transformable_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Query\Query_Builder;
|
||||
use Elementor\Modules\AtomicWidgets\Query\Query_Builder_Factory;
|
||||
use Elementor\Modules\WpRest\Base\Query as Query_Base;
|
||||
use Elementor\Modules\WpRest\Classes\Post_Query;
|
||||
use Elementor\Modules\WpRest\Classes\Term_Query;
|
||||
use Elementor\Modules\WpRest\Classes\User_Query;
|
||||
use Elementor\TemplateLibrary\Source_Local;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_Tags_Editor_Config {
|
||||
|
||||
private Dynamic_Tags_Schemas $schemas;
|
||||
|
||||
private ?array $tags = null;
|
||||
|
||||
public function __construct( Dynamic_Tags_Schemas $schemas ) {
|
||||
$this->schemas = $schemas;
|
||||
}
|
||||
|
||||
public function get_tags(): array {
|
||||
if ( null !== $this->tags ) {
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
$atomic_tags = [];
|
||||
$dynamic_tags = Plugin::$instance->dynamic_tags->get_tags_config();
|
||||
|
||||
foreach ( $dynamic_tags as $name => $tag ) {
|
||||
$atomic_tag = $this->convert_dynamic_tag_to_atomic( $tag );
|
||||
|
||||
if ( $atomic_tag ) {
|
||||
$atomic_tags[ $name ] = $atomic_tag;
|
||||
}
|
||||
}
|
||||
|
||||
$this->tags = $atomic_tags;
|
||||
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return null|array{
|
||||
* name: string,
|
||||
* categories: string[],
|
||||
* label: string,
|
||||
* group: string,
|
||||
* atomic_controls: array,
|
||||
* props_schema: array<string, Transformable_Prop_Type>
|
||||
* }
|
||||
*/
|
||||
public function get_tag( string $name ): ?array {
|
||||
$tags = $this->get_tags();
|
||||
|
||||
return $tags[ $name ] ?? null;
|
||||
}
|
||||
|
||||
private function convert_dynamic_tag_to_atomic( $tag ) {
|
||||
if ( empty( $tag['name'] ) || empty( $tag['categories'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$converted_tag = [
|
||||
'name' => $tag['name'],
|
||||
'categories' => $tag['categories'],
|
||||
'label' => $tag['title'] ?? '',
|
||||
'group' => $tag['atomic_group'] ?? $tag['group'] ?? '',
|
||||
'atomic_controls' => [],
|
||||
'props_schema' => $this->schemas->get( $tag['name'] ),
|
||||
'meta' => $tag['meta'] ?? [],
|
||||
];
|
||||
|
||||
if ( ! isset( $tag['controls'] ) ) {
|
||||
return $converted_tag;
|
||||
}
|
||||
|
||||
try {
|
||||
$atomic_controls = $this->convert_controls_to_atomic( $tag );
|
||||
} catch ( \Exception $e ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( null === $atomic_controls ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$converted_tag['atomic_controls'] = $atomic_controls;
|
||||
|
||||
return $converted_tag;
|
||||
}
|
||||
|
||||
private function convert_controls_to_atomic( $tag ) {
|
||||
$atomic_controls = [];
|
||||
|
||||
$controls = $tag['controls'] ?? null;
|
||||
$force = $tag['force_convert_to_atomic'] ?? false;
|
||||
|
||||
if ( ! is_array( $controls ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ( $controls as $control ) {
|
||||
if ( 'section' === $control['type'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$atomic_control = $this->convert_control_to_atomic( $control, $tag );
|
||||
|
||||
if ( ! $atomic_control ) {
|
||||
if ( $force ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$section_name = $control['section'];
|
||||
|
||||
if ( ! isset( $atomic_controls[ $section_name ] ) ) {
|
||||
$atomic_controls[ $section_name ] = Section::make()
|
||||
->set_label( $controls[ $section_name ]['label'] );
|
||||
}
|
||||
|
||||
$atomic_controls[ $section_name ] = $atomic_controls[ $section_name ]->add_item( $atomic_control );
|
||||
}
|
||||
|
||||
return array_values( $atomic_controls );
|
||||
}
|
||||
|
||||
private function convert_control_to_atomic( $control, $tag = [] ) {
|
||||
$map = [
|
||||
'select' => fn( $control ) => $this->convert_select_control_to_atomic( $control, $tag ),
|
||||
'text' => fn( $control ) => $this->convert_text_control_to_atomic( $control ),
|
||||
'textarea' => fn( $control ) => $this->convert_textarea_control_to_atomic( $control ),
|
||||
'switcher' => fn( $control ) => $this->convert_switch_control_to_atomic( $control ),
|
||||
'number' => fn( $control ) => $this->convert_number_control_to_atomic( $control ),
|
||||
'query' => fn( $control ) => $this->convert_autocomplete_control_to_atomic( $control ),
|
||||
'choose' => fn( $control ) => $this->convert_choose_control_to_atomic( $control ),
|
||||
'media' => fn( $control ) => $this->convert_media_control_to_atomic( $control ),
|
||||
'date_time' => fn( $control ) => $this->convert_date_time_control_to_atomic( $control ),
|
||||
];
|
||||
|
||||
if ( ! isset( $map[ $control['type'] ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$is_convertable = ! isset( $control['name'], $control['section'], $control['label'], $control['default'] );
|
||||
|
||||
if ( $is_convertable ) {
|
||||
throw new \Exception( 'Control must have name, section, label, and default' );
|
||||
}
|
||||
|
||||
return $map[ $control['type'] ]( $control );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $control
|
||||
*
|
||||
* @return Select_Control
|
||||
* @throws \Exception If control is missing options.
|
||||
*/
|
||||
private function convert_select_control_to_atomic( $control, $tag = [] ) {
|
||||
$options = $this->extract_select_options_from_control( $control );
|
||||
|
||||
if ( empty( $options ) ) {
|
||||
throw new \Exception( 'Select control must have options' );
|
||||
}
|
||||
|
||||
$options = apply_filters( 'elementor/atomic/dynamic_tags/select_control_options', $options, $control, $tag );
|
||||
|
||||
$options = array_map(
|
||||
fn( $key, $value ) => [
|
||||
'value' => $key,
|
||||
'label' => $value,
|
||||
],
|
||||
array_keys( $options ),
|
||||
$options
|
||||
);
|
||||
|
||||
$select_control = Select_Control::bind_to( $control['name'] )
|
||||
->set_placeholder( $control['placeholder'] ?? '' )
|
||||
->set_options( $options )
|
||||
->set_label( $control['atomic_label'] ?? $control['label'] );
|
||||
|
||||
if ( isset( $control['collection_id'] ) ) {
|
||||
$select_control->set_collection_id( $control['collection_id'] );
|
||||
}
|
||||
|
||||
return $select_control;
|
||||
}
|
||||
|
||||
private function extract_select_options_from_control( $control ): array {
|
||||
$options = $control['options'] ?? [];
|
||||
|
||||
if ( ! empty( $options ) ) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
if ( empty( $control['groups'] ) || ! is_array( $control['groups'] ) ) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
foreach ( $control['groups'] as $group ) {
|
||||
if ( empty( $group['options'] ) || ! is_array( $group['options'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered = array_filter(
|
||||
$group['options'],
|
||||
static function ( $label, $key ) {
|
||||
return is_string( $key );
|
||||
},
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
);
|
||||
|
||||
$options = array_merge( $options, $filtered );
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $control
|
||||
*
|
||||
* @return Text_Control
|
||||
*/
|
||||
private function convert_text_control_to_atomic( $control ) {
|
||||
return Text_Control::bind_to( $control['name'] )
|
||||
->set_label( $control['label'] );
|
||||
}
|
||||
|
||||
private function convert_date_time_control_to_atomic( $control ) {
|
||||
return Date_Time_Control::bind_to( $control['name'] )
|
||||
->set_label( $control['label'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $control
|
||||
*
|
||||
* @return Switch_Control
|
||||
*/
|
||||
private function convert_switch_control_to_atomic( $control ) {
|
||||
return Switch_Control::bind_to( $control['name'] )
|
||||
->set_label( $control['atomic_label'] ?? $control['label'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $control
|
||||
*
|
||||
* @return Number_Control
|
||||
*/
|
||||
private function convert_number_control_to_atomic( $control ) {
|
||||
return Number_Control::bind_to( $control['name'] )
|
||||
->set_placeholder( $control['placeholder'] ?? '' )
|
||||
->set_max( $control['max'] ?? null )
|
||||
->set_min( $control['min'] ?? null )
|
||||
->set_step( $control['step'] ?? null )
|
||||
->set_should_force_int( $control['should_force_int'] ?? false )
|
||||
->set_label( $control['label'] );
|
||||
}
|
||||
|
||||
private function convert_textarea_control_to_atomic( $control ) {
|
||||
return Textarea_Control::bind_to( $control['name'] )
|
||||
->set_placeholder( $control['placeholder'] ?? '' )
|
||||
->set_label( $control['label'] );
|
||||
}
|
||||
|
||||
private function convert_autocomplete_control_to_atomic( $control ) {
|
||||
$query_config = [];
|
||||
$query_type = Post_Query::ENDPOINT;
|
||||
|
||||
switch ( true ) {
|
||||
case $this->is_querying_wp_terms( $control ):
|
||||
$query_type = Term_Query::ENDPOINT;
|
||||
$included_types = null;
|
||||
$excluded_types = null;
|
||||
break;
|
||||
|
||||
case $this->is_control_elementor_query( $control ):
|
||||
$included_types = [ Source_Local::CPT ];
|
||||
$excluded_types = [];
|
||||
break;
|
||||
|
||||
case $this->is_querying_wp_media( $control ):
|
||||
$included_types = [ 'attachment' ];
|
||||
$excluded_types = [];
|
||||
$query_config[ Query_Base::IS_PUBLIC_KEY ] = false;
|
||||
break;
|
||||
|
||||
case $this->is_querying_wp_users( $control ):
|
||||
$included_types = [ $control['autocomplete']['object'] ];
|
||||
$excluded_types = null;
|
||||
$query_type = User_Query::ENDPOINT;
|
||||
break;
|
||||
|
||||
default:
|
||||
$included_types = isset( $control['autocomplete']['query']['post_type'] ) ? $control['autocomplete']['query']['post_type'] : [];
|
||||
$included_types = ! empty( $included_types ) && 'any' !== $included_types ? $included_types : null;
|
||||
$excluded_types = null;
|
||||
}
|
||||
|
||||
$query_config[ Query_Base::ITEMS_COUNT_KEY ] = $this->extract_item_count_from_control( $control );
|
||||
$post_status[ Query_Base::IS_PUBLIC_KEY ] = $this->extract_post_status_from_control( $control );
|
||||
$query_config[ Query_Base::INCLUDED_TYPE_KEY ] = $included_types;
|
||||
$query_config[ Query_Base::EXCLUDED_TYPE_KEY ] = $excluded_types;
|
||||
$query_config[ Query_Builder_Factory::ENDPOINT_KEY ] = $query_type;
|
||||
$query_config[ Query_Base::META_QUERY_KEY ] = $this->extract_meta_query_from_control( $control );
|
||||
|
||||
$query_control = Query_Control::bind_to( $control['name'] );
|
||||
$query_control->set_query_config( $query_config );
|
||||
$query_control->set_placeholder( $control['placeholder'] ?? '' );
|
||||
$query_control->set_label( $control['label'] );
|
||||
$query_control->set_allow_custom_values( false );
|
||||
|
||||
return $query_control;
|
||||
}
|
||||
|
||||
private function is_control_elementor_query( $control ): bool {
|
||||
return isset( $control['autocomplete']['object'] ) && 'library_template' === $control['autocomplete']['object'];
|
||||
}
|
||||
|
||||
private function is_querying_wp_terms( $control ): bool {
|
||||
return isset( $control['autocomplete']['object'] ) && in_array( $control['autocomplete']['object'], [ 'tax', 'taxonomy', 'term' ], true );
|
||||
}
|
||||
|
||||
private function is_querying_wp_media( $control ): bool {
|
||||
return isset( $control['autocomplete']['object'] ) && 'attachment' === $control['autocomplete']['object'];
|
||||
}
|
||||
|
||||
private function is_querying_wp_users( $control ): bool {
|
||||
global $wp_roles;
|
||||
$roles = array_keys( $wp_roles->roles );
|
||||
|
||||
return isset( $control['autocomplete']['object'] ) && in_array( $control['autocomplete']['object'], $roles, true );
|
||||
}
|
||||
|
||||
private function convert_choose_control_to_atomic( $control ) {
|
||||
return Toggle_Control::bind_to( $control['name'] )
|
||||
->set_label( $control['atomic_label'] ?? $control['label'] )
|
||||
->add_options( $control['options'] )
|
||||
->set_size( 'tiny' )
|
||||
->set_exclusive( true )
|
||||
->set_convert_options( true );
|
||||
}
|
||||
|
||||
private function convert_media_control_to_atomic( $control ) {
|
||||
return Image_Control::bind_to( $control['name'] )
|
||||
->set_label( $control['label'] );
|
||||
}
|
||||
|
||||
private function extract_post_status_from_control( $control ): ?bool {
|
||||
$status = $control['autocomplete']['query']['post_status'] ?? null;
|
||||
|
||||
return isset( $status ) && in_array( 'private', $status )
|
||||
? false
|
||||
: null;
|
||||
}
|
||||
|
||||
private function extract_item_count_from_control( $control ): ?int {
|
||||
$count = $control['autocomplete']['query']['posts_per_page'] ?? null;
|
||||
|
||||
return isset( $count ) && is_numeric( $count )
|
||||
? $count
|
||||
: null;
|
||||
}
|
||||
|
||||
private function extract_meta_query_from_control( $control ): ?array {
|
||||
return $control['autocomplete']['query']['meta_query'] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\DynamicTags;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\DynamicTags\ImportExport\Dynamic_Transformer as Import_Export_Dynamic_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers_Registry;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_Tags_Module {
|
||||
|
||||
private static ?self $instance = null;
|
||||
|
||||
public Dynamic_Tags_Editor_Config $registry;
|
||||
|
||||
private Dynamic_Tags_Schemas $schemas;
|
||||
|
||||
private function __construct() {
|
||||
$this->schemas = new Dynamic_Tags_Schemas();
|
||||
$this->registry = new Dynamic_Tags_Editor_Config( $this->schemas );
|
||||
}
|
||||
|
||||
public static function instance( $fresh = false ): self {
|
||||
if ( null === static::$instance || $fresh ) {
|
||||
static::$instance = new static();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
public static function fresh(): self {
|
||||
return static::instance( true );
|
||||
}
|
||||
|
||||
public function register_hooks() {
|
||||
add_filter(
|
||||
'elementor/editor/localize_settings',
|
||||
fn( array $settings ) => $this->add_atomic_dynamic_tags_to_editor_settings( $settings )
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'elementor/atomic-widgets/props-schema',
|
||||
fn( array $schema ) => Dynamic_Prop_Types_Mapping::make()->get_extended_schema( $schema )
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'elementor/atomic-widgets/styles/schema',
|
||||
fn( array $schema ) => Dynamic_Prop_Types_Mapping::make()->get_extended_style_schema( $schema ),
|
||||
8,
|
||||
2
|
||||
);
|
||||
|
||||
add_action(
|
||||
'elementor/atomic-widgets/settings/transformers/register',
|
||||
fn ( $transformers, $prop_resolver ) => $this->register_transformers( $transformers, $prop_resolver ),
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
add_action(
|
||||
'elementor/atomic-widgets/styles/transformers/register',
|
||||
fn ( $transformers, $prop_resolver ) => $this->register_transformers( $transformers, $prop_resolver ),
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
add_action(
|
||||
'elementor/atomic-widgets/import/transformers/register',
|
||||
fn ( $transformers ) => $this->register_import_export_transformer( $transformers )
|
||||
);
|
||||
|
||||
add_action(
|
||||
'elementor/atomic-widgets/export/transformers/register',
|
||||
fn ( $transformers ) => $this->register_import_export_transformer( $transformers )
|
||||
);
|
||||
}
|
||||
|
||||
private function add_atomic_dynamic_tags_to_editor_settings( $settings ) {
|
||||
if ( isset( $settings['dynamicTags']['tags'] ) ) {
|
||||
$settings['atomicDynamicTags'] = [
|
||||
'tags' => $this->registry->get_tags(),
|
||||
'groups' => Plugin::$instance->dynamic_tags->get_config()['groups'],
|
||||
];
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function register_transformers( Transformers_Registry $transformers, Render_Props_Resolver $props_resolver ) {
|
||||
$transformers->register(
|
||||
Dynamic_Prop_Type::get_key(),
|
||||
new Dynamic_Transformer(
|
||||
Plugin::$instance->dynamic_tags,
|
||||
$this->schemas,
|
||||
$props_resolver
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function register_import_export_transformer( Transformers_Registry $transformers ) {
|
||||
$transformers->register(
|
||||
Dynamic_Prop_Type::get_key(),
|
||||
new Import_Export_Dynamic_Transformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\DynamicTags;
|
||||
|
||||
use Elementor\Core\DynamicTags\Base_Tag;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Query_Prop_Type;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_Tags_Schemas {
|
||||
private array $tags_schemas = [];
|
||||
|
||||
public function get( string $tag_name ) {
|
||||
if ( isset( $this->tags_schemas[ $tag_name ] ) ) {
|
||||
return $this->tags_schemas[ $tag_name ];
|
||||
}
|
||||
|
||||
$tag = $this->get_tag( $tag_name );
|
||||
|
||||
$this->tags_schemas[ $tag_name ] = [];
|
||||
|
||||
foreach ( $tag->get_controls() as $control ) {
|
||||
if ( ! isset( $control['type'] ) || 'section' === $control['type'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prop_type = Dynamic_Tags_Converter::convert_control_to_prop_type( $control );
|
||||
|
||||
if ( ! $prop_type ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->tags_schemas[ $tag_name ][ $control['name'] ] = $prop_type;
|
||||
}
|
||||
|
||||
return $this->tags_schemas[ $tag_name ];
|
||||
}
|
||||
|
||||
private function get_tag( string $tag_name ): Base_Tag {
|
||||
$tag_info = Plugin::$instance->dynamic_tags->get_tag_info( $tag_name );
|
||||
|
||||
if ( ! $tag_info || empty( $tag_info['instance'] ) ) {
|
||||
throw new \Exception( 'Tag not found' );
|
||||
}
|
||||
|
||||
if ( ! $tag_info['instance'] instanceof Base_Tag ) {
|
||||
throw new \Exception( 'Tag is not an instance of Tag' );
|
||||
}
|
||||
|
||||
return $tag_info['instance'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\DynamicTags;
|
||||
|
||||
use Elementor\Core\DynamicTags\Manager as Dynamic_Tags_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_Transformer extends Transformer_Base {
|
||||
private Dynamic_Tags_Manager $dynamic_tags_manager;
|
||||
private Dynamic_Tags_Schemas $dynamic_tags_schemas;
|
||||
private Render_Props_Resolver $props_resolver;
|
||||
|
||||
public function __construct(
|
||||
Dynamic_Tags_Manager $dynamic_tags_manager,
|
||||
Dynamic_Tags_Schemas $dynamic_tags_schemas,
|
||||
Render_Props_Resolver $props_resolver
|
||||
) {
|
||||
$this->dynamic_tags_manager = $dynamic_tags_manager;
|
||||
$this->dynamic_tags_schemas = $dynamic_tags_schemas;
|
||||
$this->props_resolver = $props_resolver;
|
||||
}
|
||||
|
||||
public function transform( $value, $key ) {
|
||||
if ( ! isset( $value['name'] ) || ! is_string( $value['name'] ) ) {
|
||||
throw new \Exception( 'Dynamic tag name must be a string' );
|
||||
}
|
||||
|
||||
if ( isset( $value['settings'] ) && ! is_array( $value['settings'] ) ) {
|
||||
throw new \Exception( 'Dynamic tag settings must be an array' );
|
||||
}
|
||||
|
||||
$schema = $this->dynamic_tags_schemas->get( $value['name'] );
|
||||
|
||||
$settings = $this->props_resolver->resolve(
|
||||
$schema,
|
||||
$value['settings'] ?? []
|
||||
);
|
||||
|
||||
return $this->dynamic_tags_manager->get_tag_data_content( null, $value['name'], $settings );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\DynamicTags\ImportExport;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Tags_Module;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Props_Resolver_Context;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformer_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Dynamic_Transformer extends Transformer_Base {
|
||||
public function transform( $value, Props_Resolver_Context $context ): ?array {
|
||||
if ( empty( $value['name'] ) || ! is_string( $value['name'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tag = Dynamic_Tags_Module::instance()->registry->get_tag( $value['name'] );
|
||||
|
||||
if ( ! $tag ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$group = $value['group'] ?? $tag['group'] ?? '';
|
||||
|
||||
return Dynamic_Prop_Type::generate( [
|
||||
'name' => $value['name'],
|
||||
'group' => $group,
|
||||
'settings' => $value['settings'] ?? [],
|
||||
], $context->is_disabled() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{% if settings.text is not empty %}
|
||||
{%- set allowed_tags = '<b><strong><sup><sub><s><em><i><u><del><span><br>' %}
|
||||
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %}
|
||||
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
|
||||
{% if settings.link.href %}
|
||||
<{{ settings.link.tag | e('html_tag') }}
|
||||
{% set linkAttr = settings.link.tag == 'a' ? 'href' : 'data-action-link' %}
|
||||
{{ linkAttr }}="{{ settings.link.href | raw }}"
|
||||
target="{{ settings.link.target }}"
|
||||
class="{{ classes }}"
|
||||
data-interaction-id="{{ interaction_id }}"
|
||||
{{ id_attribute }} {{ settings.attributes | raw }}
|
||||
>
|
||||
{{ settings.text | striptags(allowed_tags) | raw }}
|
||||
</{{ settings.link.tag | e('html_tag') }}>
|
||||
{% else %}
|
||||
<button class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{ id_attribute }} {{ settings.attributes | raw }}>
|
||||
{{ settings.text | striptags(allowed_tags) | raw }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Button;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Atomic_Button extends Atomic_Widget_Base {
|
||||
use Has_Template;
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-button';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Button', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-e-button';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
$props = [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
|
||||
'text' => Html_V3_Prop_Type::make()
|
||||
->default( [
|
||||
'content' => String_Prop_Type::generate( __( 'Click here', 'elementor' ) ),
|
||||
'children' => [],
|
||||
] )
|
||||
->description( 'The text displayed on the button.' ),
|
||||
|
||||
'link' => Link_Prop_Type::make(),
|
||||
|
||||
'tag' => String_Prop_Type::make()
|
||||
->default( 'button' )
|
||||
->description( 'The HTML tag for the button element.' ),
|
||||
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
|
||||
return $props;
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Content', 'elementor' ) )
|
||||
->set_items( [
|
||||
Inline_Editing_Control::bind_to( 'text' )
|
||||
->set_placeholder( __( 'Type your button text here', 'elementor' ) )
|
||||
->set_label( __( 'Button text', 'elementor' ) ),
|
||||
] ),
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( $this->get_settings_controls() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_settings_controls(): array {
|
||||
return [
|
||||
Link_Control::bind_to( 'link' )
|
||||
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
|
||||
->set_label( __( 'Link', 'elementor' ) ),
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$background_color_value = Background_Prop_Type::generate( [
|
||||
'color' => Color_Prop_Type::generate( '#375EFB' ),
|
||||
] );
|
||||
$display_value = String_Prop_Type::generate( 'inline-block' );
|
||||
$padding_value = Dimensions_Prop_Type::generate( [
|
||||
'block-start' => Size_Prop_Type::generate( [
|
||||
'size' => 12,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
'inline-end' => Size_Prop_Type::generate( [
|
||||
'size' => 24,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
'block-end' => Size_Prop_Type::generate( [
|
||||
'size' => 12,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
'inline-start' => Size_Prop_Type::generate( [
|
||||
'size' => 24,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
]);
|
||||
$border_radius_value = Size_Prop_Type::generate( [
|
||||
'size' => 2,
|
||||
'unit' => 'px',
|
||||
] );
|
||||
$border_width_value = Size_Prop_Type::generate( [
|
||||
'size' => 0,
|
||||
'unit' => 'px',
|
||||
] );
|
||||
$align_text_value = String_Prop_Type::generate( 'center' );
|
||||
|
||||
return [
|
||||
'base' => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'background', $background_color_value )
|
||||
->add_prop( 'display', $display_value )
|
||||
->add_prop( 'padding', $padding_value )
|
||||
->add_prop( 'border-radius', $border_radius_value )
|
||||
->add_prop( 'border-width', $border_width_value )
|
||||
->add_prop( 'text-align', $align_text_value )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-button' => __DIR__ . '/atomic-button.html.twig',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %}
|
||||
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
|
||||
|
||||
<hr class="{{ classes }}" data-interaction-id="{{ interaction_id }}" {{ id_attribute }} {{ settings.attributes | raw }} />
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Divider;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Atomic_Divider extends Atomic_Widget_Base {
|
||||
|
||||
use Has_Template;
|
||||
|
||||
protected function get_css_id_control_meta(): array {
|
||||
return [
|
||||
'layout' => 'two-columns',
|
||||
'topDivider' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-divider';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Divider', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic', 'divider', 'hr', 'line', 'border', 'separator' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-e-divider';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( $this->get_settings_controls() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_settings_controls(): array {
|
||||
return [
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$border_width_value = Size_Prop_Type::generate([
|
||||
'size' => 0,
|
||||
'unit' => 'px',
|
||||
]);
|
||||
|
||||
$height_value = Size_Prop_Type::generate([
|
||||
'size' => 1,
|
||||
'unit' => 'px',
|
||||
]);
|
||||
|
||||
$background_value = Background_Prop_Type::generate([
|
||||
'color' => Color_Prop_Type::generate( '#000' ),
|
||||
]);
|
||||
|
||||
return [
|
||||
'base' => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'border-width', $border_width_value )
|
||||
->add_prop( 'border-color', 'transparent' )
|
||||
->add_prop( 'border-style', 'none' )
|
||||
->add_prop( 'background', $background_value )
|
||||
->add_prop( 'height', $height_value )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-divider' => __DIR__ . '/atomic-divider.html.twig',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{%- set form_state = form_state | default(settings['form-state']) | default('default') -%}
|
||||
{%- set classes = ['e-con', 'e-atomic-element', base_styles.base, 'form-state-' ~ form_state] | merge(settings.classes | default([])) | join(' ') -%}
|
||||
<form class="{{ classes }} {{ editor_classes | default('') }}"
|
||||
data-id="{{ id }}"
|
||||
data-element_type="{{ type }}"
|
||||
data-e-type="{{ type }}"
|
||||
data-interaction-id="{{ interaction_id }}"
|
||||
data-interactions="{{ interactions | json_encode | e('html_attr') }}"
|
||||
x-data="eForm{{ id }}"
|
||||
x-on:submit="submit"
|
||||
{%- if settings['form-name'] is not empty %}
|
||||
aria-label="{{ settings['form-name'] | e('html_attr') }}"
|
||||
data-form-name="{{ settings['form-name'] | e('html_attr') }}"
|
||||
{% endif -%}
|
||||
{%- if settings['_cssid'] is not empty %}
|
||||
id="{{ settings['_cssid'] | e('html_attr') }}"
|
||||
{% endif -%}
|
||||
{{ editor_attributes | default('') | raw }}>
|
||||
<!-- elementor-children-placeholder -->
|
||||
</form>
|
||||
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Chips_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Email_Form_Action_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Toggle_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Success_Message\Form_Success_Message;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Error_Message\Form_Error_Message;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Element_Builder;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Widget_Builder;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Email_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Array_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Form extends Atomic_Element_Base {
|
||||
use Has_Element_Template;
|
||||
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
public const ACTION_COLLECT_SUBMISSIONS = 'collect-submissions';
|
||||
public const METADATA_REMOTE_IP = 'remote_ip';
|
||||
public const METADATA_USER_AGENT = 'user_agent';
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'is_container', true );
|
||||
}
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-form';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return self::get_type();
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Atomic form', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'atomic', 'form' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-atomic-form';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
$email_dependencies = Dependency_Manager::make()
|
||||
->where( [
|
||||
'operator' => 'contains',
|
||||
'path' => [ 'actions-after-submit' ],
|
||||
'value' => 'email',
|
||||
'effect' => 'hide',
|
||||
] )
|
||||
->get();
|
||||
|
||||
$submissions_metadata_dependencies = Dependency_Manager::make()
|
||||
->where( [
|
||||
'operator' => 'contains',
|
||||
'path' => [ 'actions-after-submit' ],
|
||||
'value' => self::ACTION_COLLECT_SUBMISSIONS,
|
||||
'effect' => 'hide',
|
||||
] )
|
||||
->get();
|
||||
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'form-name' => String_Prop_Type::make()
|
||||
->default( __( 'Form', 'elementor' ) ),
|
||||
'form-state' => String_Prop_Type::make()
|
||||
->enum( [ 'default', 'success', 'error' ] )
|
||||
->default( 'default' )
|
||||
->meta( 'generates_class', 'form-state-{value}' ),
|
||||
'actions-after-submit' => String_Array_Prop_Type::make()
|
||||
->default( [ String_Prop_Type::generate( 'email' ) ] ),
|
||||
'submissions_metadata' => String_Array_Prop_Type::make()
|
||||
->set_dependencies( $submissions_metadata_dependencies )
|
||||
->default( [
|
||||
String_Prop_Type::generate( self::METADATA_REMOTE_IP ),
|
||||
String_Prop_Type::generate( self::METADATA_USER_AGENT ),
|
||||
] ),
|
||||
'email' => Email_Prop_Type::make()
|
||||
->set_dependencies( $email_dependencies )
|
||||
->meta( Overridable_Prop_Type::ignore() )
|
||||
->default( [] ),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
$state_control = Toggle_Control::bind_to( 'form-state' )
|
||||
->set_label( __( 'States', 'elementor' ) )
|
||||
->set_meta( [ 'topDivider' => true ] );
|
||||
|
||||
if ( $state_control instanceof Toggle_Control ) {
|
||||
$state_control
|
||||
->add_options( [
|
||||
'default' => [
|
||||
'title' => __( 'Normal', 'elementor' ),
|
||||
],
|
||||
'success' => [
|
||||
'title' => __( 'Success', 'elementor' ),
|
||||
],
|
||||
'error' => [
|
||||
'title' => __( 'Error', 'elementor' ),
|
||||
],
|
||||
] )
|
||||
->set_exclusive( true )
|
||||
->set_convert_options( true )
|
||||
->set_size( 'tiny' )
|
||||
->set_full_width( true );
|
||||
}
|
||||
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Content', 'elementor' ) )
|
||||
->set_items( [
|
||||
Text_Control::bind_to( 'form-name' )
|
||||
->set_label( __( 'Form name', 'elementor' ) ),
|
||||
$state_control,
|
||||
Chips_Control::bind_to( 'actions-after-submit' )
|
||||
->set_label( __( 'Actions after submit', 'elementor' ) )
|
||||
->set_meta( [ 'topDivider' => true ] )
|
||||
->set_options( [
|
||||
[
|
||||
'label' => __( 'Collect submissions', 'elementor' ),
|
||||
'value' => self::ACTION_COLLECT_SUBMISSIONS,
|
||||
],
|
||||
[
|
||||
'label' => __( 'Email', 'elementor' ),
|
||||
'value' => 'email',
|
||||
],
|
||||
] ),
|
||||
Chips_Control::bind_to( 'submissions_metadata' )
|
||||
->set_label( __( 'Include metadata', 'elementor' ) )
|
||||
->set_meta( [ 'topDivider' => true ] )
|
||||
->set_options( [
|
||||
[
|
||||
'label' => __( 'User IP', 'elementor' ),
|
||||
'value' => self::METADATA_REMOTE_IP,
|
||||
],
|
||||
[
|
||||
'label' => __( 'User Agent', 'elementor' ),
|
||||
'value' => self::METADATA_USER_AGENT,
|
||||
],
|
||||
] ),
|
||||
Email_Form_Action_Control::bind_to( 'email' )
|
||||
->set_meta( [
|
||||
'topDivider' => true,
|
||||
] ),
|
||||
] ),
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->set_breakpoint( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP )
|
||||
->add_prop( 'display', String_Prop_Type::generate( 'flex' ) )
|
||||
->add_prop( 'flex', String_Prop_Type::generate( '1' ) )
|
||||
->add_prop( 'flex-direction', String_Prop_Type::generate( 'row' ) )
|
||||
->add_prop( 'flex-wrap', String_Prop_Type::generate( 'wrap' ) )
|
||||
->add_prop( 'align-items', String_Prop_Type::generate( 'flex-start' ) )
|
||||
->add_prop( 'align-content', String_Prop_Type::generate( 'start' ) )
|
||||
->add_prop( 'gap', Size_Prop_Type::generate( [
|
||||
'size' => 10,
|
||||
'unit' => 'px',
|
||||
] ) )
|
||||
->add_prop( 'padding', Size_Prop_Type::generate( [
|
||||
'size' => 20,
|
||||
'unit' => 'px',
|
||||
] ) )
|
||||
),
|
||||
static::BASE_STYLE_KEY . ' .e-form-checkbox-row' => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'align-items', String_Prop_Type::generate( 'center' ) )
|
||||
->add_prop( 'gap', Size_Prop_Type::generate( [
|
||||
'size' => 8,
|
||||
'unit' => 'px',
|
||||
] ) )
|
||||
->add_prop( 'padding', Size_Prop_Type::generate( [
|
||||
'size' => 0,
|
||||
'unit' => 'px',
|
||||
] ) )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_panel_categories(): array {
|
||||
return [ 'atomic-form' ];
|
||||
}
|
||||
|
||||
protected function define_default_html_tag() {
|
||||
return 'form';
|
||||
}
|
||||
|
||||
protected function define_default_children() {
|
||||
|
||||
$prefix = 'e-form-';
|
||||
|
||||
return [
|
||||
$this->build_label( __( 'First name', 'elementor' ), $prefix . 'first-name' ),
|
||||
$this->build_input( __( 'First name', 'elementor' ), 'text', $prefix . 'first-name' ),
|
||||
|
||||
$this->build_label( __( 'Last name', 'elementor' ), $prefix . 'last-name' ),
|
||||
$this->build_input( __( 'Last name', 'elementor' ), 'text', $prefix . 'last-name' ),
|
||||
|
||||
$this->build_label( __( 'Email', 'elementor' ), $prefix . 'email' ),
|
||||
$this->build_input( __( 'your@mail.com', 'elementor' ), 'email', $prefix . 'email' ),
|
||||
|
||||
$this->build_label( __( 'Message', 'elementor' ), $prefix . 'message' ),
|
||||
$this->build_input( __( 'Your message', 'elementor' ), 'textarea', $prefix . 'message' ),
|
||||
|
||||
$this->build_checkbox_row( __( 'Checkbox', 'elementor' ), $prefix . 'checkbox' ),
|
||||
|
||||
Widget_Builder::make( 'e-form-submit-button' )
|
||||
->settings( [
|
||||
'text' => Html_V3_Prop_Type::generate( [
|
||||
'content' => String_Prop_Type::generate( __( 'Submit', 'elementor' ) ),
|
||||
'children' => [],
|
||||
] ),
|
||||
] )
|
||||
->build(),
|
||||
$this->build_status_message(
|
||||
__( 'Great! We’ve received your information.', 'elementor' ),
|
||||
'success',
|
||||
__( 'Success message', 'elementor' )
|
||||
),
|
||||
$this->build_status_message(
|
||||
__( 'We couldn’t process your submission. Please retry', 'elementor' ),
|
||||
'error',
|
||||
__( 'Error message', 'elementor' )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
private function build_checkbox_row( string $label_text, string $checkbox_id ): array {
|
||||
$checkbox = Widget_Builder::make( 'e-form-checkbox' )
|
||||
->settings( [
|
||||
'_cssid' => String_Prop_Type::generate( $checkbox_id ),
|
||||
] )
|
||||
->build();
|
||||
|
||||
$label = $this->build_label( $label_text, $checkbox_id );
|
||||
|
||||
return Element_Builder::make( 'e-flexbox' )
|
||||
->children( [ $checkbox, $label ] )
|
||||
->settings( [
|
||||
'classes' => Classes_Prop_Type::generate( [ 'e-form-checkbox-row' ] ),
|
||||
] )
|
||||
->build();
|
||||
}
|
||||
|
||||
private function build_label( string $text, string $input_id ): array {
|
||||
return Widget_Builder::make( 'e-form-label' )
|
||||
->settings( [
|
||||
'text' => Html_V3_Prop_Type::generate( [
|
||||
'content' => String_Prop_Type::generate( $text ),
|
||||
'children' => [],
|
||||
] ),
|
||||
'input-id' => String_Prop_Type::generate( $input_id ),
|
||||
] )
|
||||
->build();
|
||||
}
|
||||
|
||||
private function build_input( string $placeholder, string $type = 'text', $input_id = '' ): array {
|
||||
if ( 'textarea' === $type ) {
|
||||
return Widget_Builder::make( 'e-form-textarea' )
|
||||
->settings( [
|
||||
'placeholder' => String_Prop_Type::generate( $placeholder ),
|
||||
'rows' => Number_Prop_Type::generate( 4 ),
|
||||
'_cssid' => String_Prop_Type::generate( $input_id ),
|
||||
] )
|
||||
->build();
|
||||
}
|
||||
|
||||
return Widget_Builder::make( 'e-form-input' )
|
||||
->settings( [
|
||||
'placeholder' => String_Prop_Type::generate( $placeholder ),
|
||||
'type' => String_Prop_Type::generate( $type ),
|
||||
'_cssid' => String_Prop_Type::generate( $input_id ),
|
||||
] )
|
||||
->build();
|
||||
}
|
||||
|
||||
private function build_status_message( string $message, string $state, string $title ): array {
|
||||
$paragraph_value = Html_V3_Prop_Type::generate( [
|
||||
'content' => String_Prop_Type::generate( $message ),
|
||||
'children' => [],
|
||||
] );
|
||||
|
||||
$element_type = 'success' === $state
|
||||
? Form_Success_Message::get_element_type()
|
||||
: Form_Error_Message::get_element_type();
|
||||
|
||||
return Element_Builder::make( $element_type )
|
||||
->settings( [
|
||||
'attributes' => Attributes_Prop_Type::generate( [
|
||||
Key_Value_Prop_Type::generate( [] ),
|
||||
] ),
|
||||
] )
|
||||
->editor_settings( [
|
||||
'title' => $title,
|
||||
] )
|
||||
->children( [
|
||||
Widget_Builder::make( Atomic_Paragraph::get_element_type() )
|
||||
->settings( [
|
||||
'paragraph' => $paragraph_value,
|
||||
] )
|
||||
->build(),
|
||||
] )
|
||||
->is_locked( true )
|
||||
->build();
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-form' => __DIR__ . '/atomic-form.html.twig',
|
||||
];
|
||||
}
|
||||
|
||||
protected function build_template_context(): array {
|
||||
$context = $this->build_base_template_context();
|
||||
|
||||
$context['form_state'] = 'default';
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Error_Message;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message\Form_Message;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Form_Error_Message extends Form_Message {
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-form-error-message';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-form-error-message';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Error message', 'elementor' );
|
||||
}
|
||||
|
||||
protected static function get_background_color(): string {
|
||||
return '#ffdede';
|
||||
}
|
||||
|
||||
protected static function get_text_color(): string {
|
||||
return '#870000';
|
||||
}
|
||||
|
||||
protected function get_css_id_control_meta(): array {
|
||||
return [
|
||||
'layout' => 'two-columns',
|
||||
'topDivider' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
|
||||
{% set message_type = type == 'e-form-success-message' ? 'message-success' : 'message-error' %}
|
||||
<div class="{{ classes }} {{ message_type }} {{ editor_classes | default('') }}"
|
||||
data-id="{{ id }}"
|
||||
data-element_type="{{ type }}"
|
||||
data-e-type="{{ type }}"
|
||||
data-interaction-id="{{ interaction_id }}"
|
||||
{{ editor_attributes | default('') | raw }}>
|
||||
<!-- elementor-children-placeholder -->
|
||||
</div>
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
abstract class Form_Message extends Atomic_Element_Base {
|
||||
use Has_Element_Template;
|
||||
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
abstract protected static function get_background_color(): string;
|
||||
|
||||
abstract protected static function get_text_color(): string;
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'is_container', true );
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-div-block';
|
||||
}
|
||||
|
||||
public function should_show_in_panel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_props( [
|
||||
'display' => String_Prop_Type::generate( 'none' ),
|
||||
'background' => Background_Prop_Type::generate( [
|
||||
'color' => Color_Prop_Type::generate( static::get_background_color() ),
|
||||
] ),
|
||||
'color' => Color_Prop_Type::generate( static::get_text_color() ),
|
||||
'padding' => Size_Prop_Type::generate( [
|
||||
'size' => 12,
|
||||
'unit' => 'px',
|
||||
] ),
|
||||
'text-align' => String_Prop_Type::generate( 'center' ),
|
||||
'font-size' => Size_Prop_Type::generate( [
|
||||
'size' => 12,
|
||||
'unit' => 'px',
|
||||
] ),
|
||||
'font-family' => String_Prop_Type::generate( 'Poppins' ),
|
||||
] )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/form-message' => __DIR__ . '/form-message.html.twig',
|
||||
];
|
||||
}
|
||||
|
||||
protected function build_template_context(): array {
|
||||
return $this->build_base_template_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Success_Message;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Message\Form_Message;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Form_Success_Message extends Form_Message {
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-form-success-message';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-form-success-message';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Success message', 'elementor' );
|
||||
}
|
||||
|
||||
protected static function get_background_color(): string {
|
||||
return '#D4E9D6';
|
||||
}
|
||||
|
||||
protected static function get_text_color(): string {
|
||||
return '#2F532E';
|
||||
}
|
||||
|
||||
protected function get_css_id_control_meta(): array {
|
||||
return [
|
||||
'layout' => 'two-columns',
|
||||
'topDivider' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% if settings.title is not empty %}
|
||||
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
|
||||
<{{ settings.tag | e('html_tag') }}
|
||||
data-interaction-id="{{ interaction_id }}"
|
||||
class="{{ settings.classes | merge( [ base_styles.base ] ) | join(' ') }}"
|
||||
{{ id_attribute }}
|
||||
{{ settings.attributes | raw }}
|
||||
>
|
||||
{% set allowed_tags = '<b><strong><sup><sub><s><em><i><u><a><del><span><br>' %}
|
||||
|
||||
{% if settings.link.href %}
|
||||
<{{ settings.link.tag | e('html_tag') }}
|
||||
{% set linkAttr = settings.link.tag == 'a' ? 'href' : 'data-action-link' %}
|
||||
{{ linkAttr }}="{{ settings.link.href | raw }}"
|
||||
target="{{ settings.link.target }}"
|
||||
class="{{ base_styles['link-base'] }}">
|
||||
{{ settings.title | striptags(allowed_tags) | raw }}
|
||||
</{{ settings.link.tag | e('html_tag') }}>
|
||||
{% else %}
|
||||
{{ settings.title | striptags(allowed_tags) | raw }}
|
||||
{% endif %}
|
||||
</{{ settings.tag | e('html_tag') }}>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Heading;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Heading extends Atomic_Widget_Base {
|
||||
use Has_Template;
|
||||
|
||||
const LINK_BASE_STYLE_KEY = 'link-base';
|
||||
|
||||
public static $widget_description = 'Display a heading with customizable tag, styles, and link options.';
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-heading';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Heading', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-e-heading';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
|
||||
'tag' => String_Prop_Type::make()
|
||||
->enum( [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ] )
|
||||
->default( 'h2' )
|
||||
->description( 'The HTML tag for the heading element. Could be h1, h2, up to h6' ),
|
||||
|
||||
'title' => Html_V3_Prop_Type::make()
|
||||
->default( [
|
||||
'content' => String_Prop_Type::generate( __( 'This is a title', 'elementor' ) ),
|
||||
'children' => [],
|
||||
] )
|
||||
->description( 'The text content of the heading.' ),
|
||||
|
||||
'link' => Link_Prop_Type::make(),
|
||||
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
$content_section = Section::make()
|
||||
->set_label( __( 'Content', 'elementor' ) )
|
||||
->set_items( [
|
||||
Inline_Editing_Control::bind_to( 'title' )
|
||||
->set_placeholder( __( 'Type your title here', 'elementor' ) )
|
||||
->set_label( __( 'Title', 'elementor' ) ),
|
||||
] );
|
||||
|
||||
return [
|
||||
$content_section,
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( $this->get_settings_controls() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_settings_controls(): array {
|
||||
return [
|
||||
Select_Control::bind_to( 'tag' )
|
||||
->set_options([
|
||||
[
|
||||
'value' => 'h1',
|
||||
'label' => 'H1',
|
||||
],
|
||||
[
|
||||
'value' => 'h2',
|
||||
'label' => 'H2',
|
||||
],
|
||||
[
|
||||
'value' => 'h3',
|
||||
'label' => 'H3',
|
||||
],
|
||||
[
|
||||
'value' => 'h4',
|
||||
'label' => 'H4',
|
||||
],
|
||||
[
|
||||
'value' => 'h5',
|
||||
'label' => 'H5',
|
||||
],
|
||||
[
|
||||
'value' => 'h6',
|
||||
'label' => 'H6',
|
||||
],
|
||||
])
|
||||
->set_label( __( 'Tag', 'elementor' ) ),
|
||||
Link_Control::bind_to( 'link' )
|
||||
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
|
||||
->set_label( __( 'Link', 'elementor' ) )
|
||||
->set_meta( [
|
||||
'topDivider' => true,
|
||||
] ),
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$margin_value = Size_Prop_Type::generate( [
|
||||
'unit' => 'px',
|
||||
'size' => 0 ,
|
||||
] );
|
||||
|
||||
return [
|
||||
'base' => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'margin', $margin_value )
|
||||
),
|
||||
self::LINK_BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'all', 'unset' )
|
||||
->add_prop( 'cursor', 'pointer' )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-heading' => __DIR__ . '/atomic-heading.html.twig',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{% if settings.image.src is not empty %}
|
||||
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
|
||||
{% if settings.link.href %}
|
||||
<{{ settings.link.tag | e('html_tag') }}
|
||||
{% set linkAttr = settings.link.tag == 'a' ? 'href' : 'data-action-link' %}
|
||||
{{ linkAttr }}="{{ settings.link.href | raw }}"
|
||||
class="{{ base_styles['link-base'] }}"
|
||||
target="{{ settings.link.target }}"
|
||||
data-interaction-id="{{ interaction_id }}"
|
||||
>
|
||||
{% endif %}
|
||||
<img class="{{ base_styles['base'] }} {{ settings.classes | join(' ') }}"
|
||||
{% if not settings.link.href %}
|
||||
data-interaction-id="{{ interaction_id }}"
|
||||
{% endif %}
|
||||
{{ id_attribute }} {{ settings.attributes | raw }}
|
||||
{% for attr, value in settings.image %}
|
||||
{% if attr == 'src' %}
|
||||
src="{{ value | e('full_url') }}"
|
||||
{% else %}
|
||||
{{ attr | e('html_attr') }}="{{ value }}"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
/>
|
||||
{% if settings.link.href %}
|
||||
</{{ settings.link.tag | e('html_tag') }}>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Image;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Image_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Image extends Atomic_Widget_Base {
|
||||
use Has_Template;
|
||||
|
||||
public static $widget_description = 'Display an image with customizable styles and link options.';
|
||||
|
||||
const LINK_BASE_STYLE_KEY = 'link-base';
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-image';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Image', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-e-image';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
$props = [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
|
||||
'image' => Image_Prop_Type::make()
|
||||
->default_url( Placeholder_Image::get_placeholder_image() )
|
||||
->default_size( 'full' ),
|
||||
|
||||
'link' => Link_Prop_Type::make(),
|
||||
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
|
||||
return $props;
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( esc_html__( 'Content', 'elementor' ) )
|
||||
->set_items( [
|
||||
Image_Control::bind_to( 'image' )
|
||||
->set_label( __( 'Image', 'elementor' ) ),
|
||||
] ),
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( $this->get_settings_controls() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_settings_controls(): array {
|
||||
return [
|
||||
Link_Control::bind_to( 'link' )
|
||||
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
|
||||
->set_label( __( 'Link', 'elementor' ) ),
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
return [
|
||||
self::LINK_BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'display', 'inherit' )
|
||||
->add_prop( 'width', 'fit-content' )
|
||||
),
|
||||
self::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'display', 'block' )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-image' => __DIR__ . '/atomic-image.html.twig',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{% if settings.paragraph is not empty %}
|
||||
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
|
||||
<{{ settings.tag | e('html_tag') }} class="{{ settings.classes | merge( [ base_styles.base ] ) | join(' ') }}" data-interaction-id="{{ interaction_id }}" {{ id_attribute }} {{ settings.attributes | raw }}>
|
||||
{% if settings.link.href %}
|
||||
<{{ settings.link.tag | e('html_tag') }}
|
||||
{% set linkAttr = settings.link.tag == 'a' ? 'href' : 'data-action-link' %}
|
||||
{{ linkAttr }}="{{ settings.link.href | raw }}"
|
||||
target="{{ settings.link.target }}"
|
||||
class="{{ base_styles['link-base'] }}"
|
||||
>
|
||||
{{ settings.paragraph | striptags(allowed_tags) | raw }}
|
||||
</{{ settings.link.tag | e('html_tag') }}>
|
||||
{% else %}
|
||||
{{ settings.paragraph | striptags('<b><strong><sup><sub><s><em><u><ul><ol><li><blockquote><a><del><span><br>') | raw }}
|
||||
{% endif %}
|
||||
</{{ settings.tag | e('html_tag') }}>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Inline_Editing_Control;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Atomic_Paragraph extends Atomic_Widget_Base {
|
||||
use Has_Template;
|
||||
|
||||
const LINK_BASE_STYLE_KEY = 'link-base';
|
||||
|
||||
public static $widget_description = 'Display a paragraph with customizable tag, styles, and link options.';
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-paragraph';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Paragraph', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-paragraph';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
|
||||
'paragraph' => Html_V3_Prop_Type::make()
|
||||
->default( [
|
||||
'content' => String_Prop_Type::generate( __( 'Type your paragraph here', 'elementor' ) ),
|
||||
'children' => [],
|
||||
] )
|
||||
->description( 'The text content of the paragraph.' ),
|
||||
|
||||
'tag' => String_Prop_Type::make()
|
||||
->enum( [ 'p', 'span' ] )
|
||||
->default( 'p' ),
|
||||
|
||||
'link' => Link_Prop_Type::make(),
|
||||
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Content', 'elementor' ) )
|
||||
->set_items( [
|
||||
Inline_Editing_Control::bind_to( 'paragraph' )
|
||||
->set_placeholder( __( 'Type your paragraph here', 'elementor' ) )
|
||||
->set_label( __( 'Paragraph', 'elementor' ) ),
|
||||
] ),
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( $this->get_settings_controls() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_settings_controls(): array {
|
||||
return [
|
||||
Select_Control::bind_to( 'tag' )
|
||||
->set_options([
|
||||
[
|
||||
'value' => 'p',
|
||||
'label' => 'p',
|
||||
],
|
||||
[
|
||||
'value' => 'span',
|
||||
'label' => 'span',
|
||||
],
|
||||
])
|
||||
->set_label( __( 'Tag', 'elementor' ) ),
|
||||
Link_Control::bind_to( 'link' )
|
||||
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
|
||||
->set_label( __( 'Link', 'elementor' ) )
|
||||
->set_meta( [
|
||||
'topDivider' => true,
|
||||
] ),
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$margin_value = Size_Prop_Type::generate( [
|
||||
'unit' => 'px',
|
||||
'size' => 0 ,
|
||||
] );
|
||||
|
||||
return [
|
||||
'base' => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'margin', $margin_value )
|
||||
),
|
||||
self::LINK_BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'all', 'unset' )
|
||||
->add_prop( 'cursor', 'pointer' )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-paragraph' => __DIR__ . '/atomic-paragraph.html.twig',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %}
|
||||
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
|
||||
{% set video_classes = base_styles.base %}
|
||||
{% set video_start_time = settings.start_time %}
|
||||
{% set video_end_time = settings.end_time %}
|
||||
{% if video_start_time is not empty %}
|
||||
{% set video_timings = '#t=' ~ video_start_time %}
|
||||
{% else %}
|
||||
{% set video_timings = '#t=0' %}
|
||||
{% endif %}
|
||||
{% if video_end_time is not empty and video_end_time > video_start_time %}
|
||||
{% set video_timings = video_timings ~ ',' ~ video_end_time %}
|
||||
{% endif %}
|
||||
{% if settings.source.url is not empty %}
|
||||
{% set video_url = settings.source.url | e('full_url') %}
|
||||
<video
|
||||
data-id="{{ id }}" data-interaction-id="{{ interaction_id }}"
|
||||
data-e-type="{{ type }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}"
|
||||
{{ id_attribute }}
|
||||
class="{{ classes }}"
|
||||
{{ settings.attributes | raw }}
|
||||
class="{{ video_classes }}"
|
||||
{% if settings.poster_enabled %}poster="{{ settings.poster.src | e('full_url') }}"{% endif %}
|
||||
{% if settings.autoplay %}autoplay{% endif %}
|
||||
{% if settings.mute %}muted{% endif %}
|
||||
{% if settings.loop %}loop{% endif %}
|
||||
{% if settings.controls %}controls{% endif %}
|
||||
{% if settings.playsinline %}playsinline{% endif %}
|
||||
{% if not settings.download %}controlslist="nodownload"{% endif %}
|
||||
preload="{{ settings.preload }}"
|
||||
>
|
||||
<source src="{{ video_url ~ video_timings }}">
|
||||
</video>
|
||||
{% else %}
|
||||
{% if settings.poster_enabled %}
|
||||
<div style="width:100%; aspect-ratio: 16/9; background-image: url({{ settings.poster.src | e('full_url') }}); background-size: contain; background-position: center center; background-repeat: no-repeat; min-height: 100px;"></div>
|
||||
{% else %}
|
||||
<div style="width:100%; background-color: rgb(245, 245, 245); aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center;">
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.6902 8.92189C18.3227 8.56844 19.0991 8.58381 19.7162 8.96355L54.3829 30.2969C54.9738 30.661 55.3334 31.3059 55.3334 32C55.3334 32.6941 54.9738 33.339 54.3829 33.7031L19.7162 55.0365C19.0991 55.4162 18.3227 55.4316 17.6902 55.0781C17.0584 54.7245 16.6667 54.0574 16.6667 53.3333V10.6667L16.685 10.3984C16.7685 9.7807 17.1373 9.23132 17.6902 8.92189ZM20.6667 49.7526L49.5157 32L20.6667 14.2448V49.7526Z" fill="black" fill-opacity="0.54"/>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Self_Hosted_Video;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Image_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Number_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Video_Control;
|
||||
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Video_Src_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Image\Placeholder_Image;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Atomic_Self_Hosted_Video extends Atomic_Widget_Base {
|
||||
|
||||
use Has_Template;
|
||||
|
||||
protected static function get_preload_options() {
|
||||
return [
|
||||
'auto' => esc_html__( 'Auto', 'elementor' ),
|
||||
'metadata' => esc_html__( 'Metadata', 'elementor' ),
|
||||
'none' => esc_html__( 'None (Lazy Load)', 'elementor' ),
|
||||
]; }
|
||||
|
||||
protected function get_css_id_control_meta(): array {
|
||||
return [
|
||||
'layout' => 'two-columns',
|
||||
'topDivider' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-self-hosted-video';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Video', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic', 'video', 'player', 'media', 'hosted' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-video';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
$playsinline_dependencies = Dependency_Manager::make()
|
||||
->where([
|
||||
'operator' => 'eq',
|
||||
'path' => [ 'autoplay' ],
|
||||
'value' => true,
|
||||
'effect' => 'hide',
|
||||
])
|
||||
->get();
|
||||
|
||||
// NOTE: restore the dependency when dependencies works in overridables
|
||||
$poster_dependencies = Dependency_Manager::make()
|
||||
->where([
|
||||
'operator' => 'eq',
|
||||
'path' => [ 'poster_enabled' ],
|
||||
'value' => true,
|
||||
'effect' => 'hide',
|
||||
])
|
||||
->get();
|
||||
|
||||
$allow_download_dependencies = Dependency_Manager::make()
|
||||
->where([
|
||||
'operator' => 'eq',
|
||||
'path' => [ 'controls' ],
|
||||
'value' => true,
|
||||
'effect' => 'hide',
|
||||
])
|
||||
->get();
|
||||
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'source' => Video_Src_Prop_Type::make(),
|
||||
'autoplay' => Boolean_Prop_Type::make()->default( false ),
|
||||
'playsinline' => Boolean_Prop_Type::make()
|
||||
->default( false )
|
||||
->set_dependencies( $playsinline_dependencies )
|
||||
->meta( Overridable_Prop_Type::ignore() ),
|
||||
'mute' => Boolean_Prop_Type::make()->default( false ),
|
||||
'loop' => Boolean_Prop_Type::make()->default( false ),
|
||||
'controls' => Boolean_Prop_Type::make()->default( true ),
|
||||
'preload' => String_Prop_Type::make()
|
||||
->default( 'metadata' )
|
||||
->enum( array_keys( self::get_preload_options() ) ),
|
||||
'download' => Boolean_Prop_Type::make()->default( false )
|
||||
->set_dependencies( $allow_download_dependencies ),
|
||||
'start_time' => Number_Prop_Type::make()
|
||||
->default( null )
|
||||
->meta( Dynamic_Prop_Type::ignore() )
|
||||
->meta( 'suffix', 'SEC' ),
|
||||
'end_time' => Number_Prop_Type::make()
|
||||
->default( null )
|
||||
->meta( 'suffix', 'SEC' )
|
||||
->meta( Dynamic_Prop_Type::ignore() ),
|
||||
'poster_enabled' => Boolean_Prop_Type::make()->default( false ),
|
||||
'poster' => Image_Prop_Type::make()
|
||||
->default_size( 'medium_large' )
|
||||
->default_url( Placeholder_Image::get_placeholder_image() ),
|
||||
// TODO: restore the dependency when dependencies works in overridables
|
||||
// ->set_dependencies( $poster_dependencies ),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Content', 'elementor' ) )
|
||||
->set_items([
|
||||
Video_Control::bind_to( 'source' )
|
||||
->set_label( esc_html__( 'Video', 'elementor' ) ),
|
||||
Number_Control::bind_to( 'start_time' )
|
||||
->set_label( esc_html__( 'Start Time', 'elementor' ) )
|
||||
->set_min( 0 )
|
||||
->set_max( 10000 ),
|
||||
Number_Control::bind_to( 'end_time' )
|
||||
->set_label( esc_html__( 'End Time', 'elementor' ) )
|
||||
->set_min( 0 )
|
||||
->set_max( 10000 ),
|
||||
Switch_Control::bind_to( 'autoplay' )->set_label( esc_html__( 'Autoplay', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'playsinline' )
|
||||
->set_label( esc_html__( 'Play on mobile', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'mute' )->set_label( esc_html__( 'Mute', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'loop' )->set_label( esc_html__( 'Loop', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'controls' )->set_label( esc_html__( 'Player Controls', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'download' )->set_label( esc_html__( 'Allow Download', 'elementor' ) ),
|
||||
Select_Control::bind_to( 'preload' )
|
||||
->set_label( esc_html__( 'Preload', 'elementor' ) )
|
||||
->set_options( self::format_options( self::get_preload_options() ) ),
|
||||
Switch_Control::bind_to( 'poster_enabled' )
|
||||
->set_label( esc_html__( 'Poster Image', 'elementor' ) ),
|
||||
Image_Control::bind_to( 'poster' )
|
||||
->set_label( esc_html__( 'Image', 'elementor' ) ),
|
||||
]),
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( $this->get_settings_controls() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_settings_controls(): array {
|
||||
return [
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$max_width = Size_Prop_Type::generate([
|
||||
'unit' => 'vw',
|
||||
'size' => 100,
|
||||
]);
|
||||
return [
|
||||
'base' => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'max-width', $max_width )
|
||||
->add_prop( 'display', String_Prop_Type::generate( 'inline-block' ) )
|
||||
->add_prop( 'aspect-ratio', String_Prop_Type::generate( '16/9' ) )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-self-hosted-video' => __DIR__ . '/atomic-self-hosted-video.html.twig',
|
||||
];
|
||||
}
|
||||
|
||||
private static function format_options( array $options ): array {
|
||||
return array_map(
|
||||
fn( $value, $key ) => [
|
||||
'value' => $key,
|
||||
'label' => $value,
|
||||
],
|
||||
$options,
|
||||
array_keys( $options )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{%- if settings.svg.html is defined and settings.svg.html is not empty -%}
|
||||
{%- set classes = settings.classes | merge([base_styles.base]) | join(' ') -%}
|
||||
{%- set id_attribute = settings._cssid is not empty ? 'id="' ~ settings._cssid | e('html_attr') ~ '"' : '' -%}
|
||||
{%- if settings.link.href is defined and settings.link.href is not empty -%}
|
||||
{%- set linkAttr = settings.link.tag == 'a' ? 'href' : 'data-action-link' -%}
|
||||
<{{ settings.link.tag | e('html_tag') }} {{ linkAttr }}="{{ settings.link.href | raw }}" target="{{ settings.link.target }}" class="{{ classes }}" data-interaction-id="{{ interaction_id }}"
|
||||
{%- if id_attribute is not empty %} {{ id_attribute }}{% endif -%}
|
||||
{%- if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }}{% endif -%}
|
||||
>{{ settings.svg.html | raw }}</{{ settings.link.tag | e('html_tag') }}>
|
||||
{%- else -%}
|
||||
<div class="{{ classes }}" data-interaction-id="{{ interaction_id }}"
|
||||
{%- if id_attribute is not empty %} {{ id_attribute }}{% endif -%}
|
||||
{%- if settings.attributes is defined and settings.attributes is not empty %} {{ settings.attributes | raw }}{% endif -%}
|
||||
>{{ settings.svg.html | raw }}</div>
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Svg;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Svg_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Svg extends Atomic_Widget_Base {
|
||||
use Has_Template;
|
||||
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
const DEFAULT_SVG = 'images/default-svg.svg';
|
||||
const DEFAULT_SVG_PATH = ELEMENTOR_ASSETS_PATH . self::DEFAULT_SVG;
|
||||
const DEFAULT_SVG_URL = ELEMENTOR_ASSETS_URL . self::DEFAULT_SVG;
|
||||
|
||||
public static $widget_description = 'Display an SVG image with customizable styles and link options.';
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-svg';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'SVG', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-svg';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()->default( [] ),
|
||||
'svg' => Svg_Src_Prop_Type::make()->default_url( static::DEFAULT_SVG_URL ),
|
||||
'link' => Link_Prop_Type::make(),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( esc_html__( 'Content', 'elementor' ) )
|
||||
->set_items( [
|
||||
Svg_Control::bind_to( 'svg' )
|
||||
->set_label( __( 'SVG', 'elementor' ) ),
|
||||
] ),
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( $this->get_settings_controls() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_settings_controls(): array {
|
||||
return [
|
||||
Link_Control::bind_to( 'link' )
|
||||
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
|
||||
->set_label( __( 'Link', 'elementor' ) ),
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$display_value = String_Prop_Type::generate( 'inline-block' );
|
||||
|
||||
$size = Size_Prop_Type::generate( [
|
||||
'size' => 65,
|
||||
'unit' => 'px',
|
||||
] );
|
||||
|
||||
return [
|
||||
self::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'display', $display_value )
|
||||
->add_prop( 'width', $size )
|
||||
->add_prop( 'height', $size )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-svg' => __DIR__ . '/atomic-svg.html.twig',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
|
||||
<div class="{{ classes }} {{ editor_classes | default('') }}{{ is_active ? ' e--selected' : '' }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tabpanel" x-bind="tabContent" id="{{ tab_content_id }}" aria-labelledby="{{ tab_id }}"{% if not is_active %} hidden="true" style="display: none;"{% endif %} {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div>
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab_Content;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Render_Context;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs\Atomic_Tabs;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Tab_Content extends Atomic_Element_Base {
|
||||
use Has_Element_Template;
|
||||
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'llm_support', false );
|
||||
}
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-tab-content';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-tab-content';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Tab content', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic', 'tab', 'content', 'tabs' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-layout';
|
||||
}
|
||||
|
||||
public function should_show_in_panel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'tab-id' => String_Prop_Type::make(),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_style_states(): array {
|
||||
$selected_state = Style_States::get_class_states_map()['selected'];
|
||||
|
||||
return [ $selected_state ];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$styles = [
|
||||
'display' => String_Prop_Type::generate( 'block' ),
|
||||
'padding' => Size_Prop_Type::generate( [
|
||||
'size' => 10,
|
||||
'unit' => 'px',
|
||||
] ),
|
||||
'min-width' => Size_Prop_Type::generate( [
|
||||
'size' => 30,
|
||||
'unit' => 'px',
|
||||
] ),
|
||||
];
|
||||
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_props( $styles )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_initial_attributes() {
|
||||
return [
|
||||
'role' => 'tabpanel',
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-tab-content' => __DIR__ . '/atomic-tab-content.html.twig',
|
||||
];
|
||||
}
|
||||
|
||||
protected function build_template_context(): array {
|
||||
$tabs_context = Render_Context::get( Atomic_Tabs::class );
|
||||
$default_active_tab = $tabs_context['default-active-tab'];
|
||||
$get_tab_content_index = $tabs_context['get-tab-content-index'];
|
||||
$tabs_id = $tabs_context['tabs-id'];
|
||||
|
||||
$index = $get_tab_content_index( $this->get_id() );
|
||||
$is_active = $default_active_tab === $index;
|
||||
|
||||
return array_merge( $this->build_base_template_context(), [
|
||||
'is_active' => $is_active,
|
||||
'tab_id' => Atomic_Tabs::get_tab_id( $tabs_id, $index ),
|
||||
'tab_content_id' => Atomic_Tabs::get_tab_content_id( $tabs_id, $index ),
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
|
||||
<button class="{{ classes }} {{ editor_classes | default('') }}{{ is_active ? ' e--selected' : '' }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tab" tabindex="{{ is_active ? '0' : '-1' }}" aria-selected="{{ is_active ? 'true' : 'false' }}" x-bind="tab" x-ref="{{ id }}" id="{{ tab_id }}" aria-controls="{{ tab_content_id }}" {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></button>
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Render_Context;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs\Atomic_Tabs;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Tab extends Atomic_Element_Base {
|
||||
use Has_Element_Template;
|
||||
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'llm_support', false );
|
||||
}
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-tab';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-tab';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Tab trigger', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-layout';
|
||||
}
|
||||
|
||||
public function should_show_in_panel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_style_states(): array {
|
||||
$selected_state = Style_States::get_class_states_map()['selected'];
|
||||
|
||||
return [ $selected_state ];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$styles = [
|
||||
'display' => String_Prop_Type::generate( 'block' ),
|
||||
'cursor' => String_Prop_Type::generate( 'pointer' ),
|
||||
'color' => Color_Prop_Type::generate( '#0C0D0E' ),
|
||||
'border-style' => String_Prop_Type::generate( 'solid' ),
|
||||
'border-color' => Color_Prop_Type::generate( '#E0E0E0' ),
|
||||
'border-width' => Size_Prop_Type::generate( [
|
||||
'size' => 2,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
'padding' => Size_Prop_Type::generate( [
|
||||
'size' => 8,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
'width' => Size_Prop_Type::generate( [
|
||||
'size' => 160,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
'background' => Background_Prop_Type::generate( [
|
||||
'color' => Color_Prop_Type::generate( '#FFFFFF' ),
|
||||
]),
|
||||
];
|
||||
|
||||
$selected_styles = [
|
||||
'outline-width' => Size_Prop_Type::generate( [
|
||||
'size' => 0,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
'border-color' => Color_Prop_Type::generate( '#0C0D0E' ),
|
||||
];
|
||||
|
||||
$hover_styles = [
|
||||
'background' => Background_Prop_Type::generate( [
|
||||
'color' => Color_Prop_Type::generate( '#E0E0E0' ),
|
||||
]),
|
||||
];
|
||||
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_props( $styles )
|
||||
)
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->set_state( Style_States::SELECTED )
|
||||
->add_props( $selected_styles )
|
||||
)
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->set_state( Style_States::FOCUS )
|
||||
->add_props( $selected_styles )
|
||||
)
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->set_state( Style_States::HOVER )
|
||||
->add_props( $hover_styles )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_initial_attributes() {
|
||||
return [
|
||||
'role' => 'tab',
|
||||
'tabindex' => '-1',
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_default_html_tag() {
|
||||
return 'button';
|
||||
}
|
||||
|
||||
protected function define_default_children() {
|
||||
return [
|
||||
Atomic_Paragraph::generate()
|
||||
->settings( [
|
||||
'paragraph' => Html_V3_Prop_Type::generate( [
|
||||
'content' => String_Prop_Type::generate( 'Tab' ),
|
||||
'children' => [],
|
||||
] ),
|
||||
'tag' => String_Prop_Type::generate( 'span' ),
|
||||
] )
|
||||
->build(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-tab' => __DIR__ . '/atomic-tab.html.twig',
|
||||
];
|
||||
}
|
||||
|
||||
protected function build_template_context(): array {
|
||||
$tabs_context = Render_Context::get( Atomic_Tabs::class );
|
||||
$default_active_tab = $tabs_context['default-active-tab'];
|
||||
$get_tab_index = $tabs_context['get-tab-index'];
|
||||
$tabs_id = $tabs_context['tabs-id'];
|
||||
|
||||
$index = $get_tab_index( $this->get_id() );
|
||||
$is_active = $default_active_tab === $index;
|
||||
|
||||
return array_merge( $this->build_base_template_context(), [
|
||||
'is_active' => $is_active,
|
||||
'tab_id' => Atomic_Tabs::get_tab_id( $tabs_id, $index ),
|
||||
'tab_content_id' => Atomic_Tabs::get_tab_content_id( $tabs_id, $index ),
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
|
||||
<div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div>
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Content_Area;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Tabs_Content_Area extends Atomic_Element_Base {
|
||||
use Has_Element_Template;
|
||||
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'llm_support', false );
|
||||
}
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-tabs-content-area';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-tabs-content-area';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Tabs content area', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-tab-content';
|
||||
}
|
||||
|
||||
public function should_show_in_panel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( [
|
||||
'layout' => 'two-columns',
|
||||
] ),
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$styles = [
|
||||
'display' => String_Prop_Type::generate( 'block' ),
|
||||
];
|
||||
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_props( $styles )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-tabs-content-area' => __DIR__ . '/atomic-tabs-content-area.html.twig',
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_allowed_child_types() {
|
||||
return [ 'e-tab-content', 'container' ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
|
||||
<div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" role="tablist" {{ editor_attributes | default('') | raw }}><!-- elementor-children-placeholder --></div>
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Menu;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Tabs_Menu extends Atomic_Element_Base {
|
||||
use Has_Element_Template;
|
||||
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'llm_support', false );
|
||||
}
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-tabs-menu';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-tabs-menu';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Tabs menu', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-tab-menu';
|
||||
}
|
||||
|
||||
public function should_show_in_panel() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function define_initial_attributes(): array {
|
||||
return [
|
||||
'role' => 'tablist',
|
||||
];
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( [
|
||||
'layout' => 'two-columns',
|
||||
] ),
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$styles = [
|
||||
'display' => String_Prop_Type::generate( 'flex' ),
|
||||
'justify-content' => String_Prop_Type::generate( 'center' ),
|
||||
];
|
||||
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_props( $styles )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-tabs-menu' => __DIR__ . '/atomic-tabs-menu.html.twig',
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_allowed_child_types() {
|
||||
return [ 'e-tab', 'container' ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% set default_tab_index = settings['default-active-tab'] | default(0) %}
|
||||
{% set default_active_tab_id = id ~ '-tab-' ~ default_tab_index %}
|
||||
{% set e_settings = {
|
||||
'default-active-tab': default_active_tab_id,
|
||||
} %}
|
||||
{% set classes = ['e-con', 'e-atomic-element', base_styles.base] | merge(settings.classes | default([])) | join(' ') %}
|
||||
<div class="{{ classes }} {{ editor_classes | default('') }}" data-id="{{ id }}" data-element_type="{{ type }}" data-e-type="{{ type }}" data-interaction-id="{{ interaction_id }}" data-interactions="{{ interactions | json_encode | e('html_attr') }}" x-data="eTabs{{ id }}" data-e-settings="{{ e_settings | json_encode | e('html_attr') }}" {{ editor_attributes | default('') | raw }}>
|
||||
<!-- elementor-children-placeholder -->
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Element_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Elements\Tabs_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab\Atomic_Tab;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab_Content\Atomic_Tab_Content;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Menu\Atomic_Tabs_Menu;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Content_Area\Atomic_Tabs_Content_Area;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Utils;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Tabs extends Atomic_Element_Base {
|
||||
use Has_Element_Template;
|
||||
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
const ELEMENT_TYPE_TABS_MENU = 'e-tabs-menu';
|
||||
const ELEMENT_TYPE_TABS_CONTENT_AREA = 'e-tabs-content-area';
|
||||
const ELEMENT_TYPE_TAB = 'e-tab';
|
||||
const ELEMENT_TYPE_TAB_CONTENT = 'e-tab-content';
|
||||
|
||||
public static $widget_description = 'Create a tabbed interface with customizable tabs and content areas. LLM support: Each child element will be represented as a tab, the menu auto-generates based on the children';
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'is_container', true );
|
||||
}
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-tabs';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-tabs';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Tabs', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-tabs';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'default-active-tab' => Number_Prop_Type::make()
|
||||
->default( 0 )
|
||||
->meta( Overridable_Prop_Type::ignore() ),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Content', 'elementor' ) )
|
||||
->set_id( 'content' )
|
||||
->set_items( [
|
||||
Tabs_Control::make()
|
||||
->set_label( __( 'Menu items', 'elementor' ) )
|
||||
->set_meta( [
|
||||
'layout' => 'custom',
|
||||
] ),
|
||||
] ),
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( [
|
||||
'layout' => 'two-columns',
|
||||
] ),
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$styles = [
|
||||
'display' => String_Prop_Type::generate( 'flex' ),
|
||||
'flex-direction' => String_Prop_Type::generate( 'column' ),
|
||||
'gap' => Size_Prop_Type::generate( [
|
||||
'size' => 30,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
'padding' => Dimensions_Prop_Type::generate( [
|
||||
'block-start' => Size_Prop_Type::generate( [
|
||||
'size' => 0,
|
||||
'unit' => 'px',
|
||||
]),
|
||||
] ),
|
||||
];
|
||||
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_props( $styles )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_default_children() {
|
||||
$default_tab_count = 3;
|
||||
$tab_elements = [];
|
||||
$tab_content_elements = [];
|
||||
|
||||
foreach ( range( 1, $default_tab_count ) as $i ) {
|
||||
$tab_elements[] = Atomic_Tab::generate()
|
||||
->editor_settings( [
|
||||
'title' => "Tab {$i} trigger",
|
||||
'initial_position' => $i,
|
||||
] )
|
||||
->is_locked( true )
|
||||
->build();
|
||||
|
||||
$tab_content_elements[] = Atomic_Tab_Content::generate()
|
||||
->is_locked( true )
|
||||
->editor_settings( [
|
||||
'title' => "Tab {$i} content",
|
||||
'initial_position' => $i,
|
||||
] )
|
||||
->build();
|
||||
}
|
||||
|
||||
$tabs_menu = Atomic_Tabs_Menu::generate()
|
||||
->children( $tab_elements )
|
||||
->is_locked( true )
|
||||
->build();
|
||||
|
||||
$tabs_content_area = Atomic_Tabs_Content_Area::generate()
|
||||
->children( $tab_content_elements )
|
||||
->is_locked( true )
|
||||
->build();
|
||||
|
||||
return [
|
||||
$tabs_menu,
|
||||
$tabs_content_area,
|
||||
];
|
||||
}
|
||||
|
||||
public function get_script_depends() {
|
||||
$global_depends = parent::get_script_depends();
|
||||
|
||||
if ( Plugin::$instance->preview->is_preview_mode() ) {
|
||||
return array_merge( $global_depends, [ 'elementor-tabs-handler', 'elementor-tabs-preview-handler' ] );
|
||||
}
|
||||
|
||||
return array_merge( $global_depends, [ 'elementor-tabs-handler' ] );
|
||||
}
|
||||
|
||||
public function register_frontend_handlers() {
|
||||
$assets_url = ELEMENTOR_ASSETS_URL;
|
||||
$min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min';
|
||||
|
||||
wp_register_script(
|
||||
'elementor-tabs-handler',
|
||||
"{$assets_url}js/tabs-handler{$min_suffix}.js",
|
||||
[ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE, Frontend_Assets_Loader::ALPINEJS_HANDLE ],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
'elementor-tabs-preview-handler',
|
||||
"{$assets_url}js/tabs-preview-handler{$min_suffix}.js",
|
||||
[ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE, Frontend_Assets_Loader::ALPINEJS_HANDLE ],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private function get_filtered_children_ids( $parent_element, $child_type ) {
|
||||
if ( ! $parent_element ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Collection::make( $parent_element->get_children() )
|
||||
->filter( fn( $element ) => $element->get_type() === $child_type )
|
||||
->map( fn( $element ) => $element->get_id() )
|
||||
->flip()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function get_tab_index( $tab_id ) {
|
||||
$direct_children = Collection::make( $this->get_children() );
|
||||
$tabs_menu = $direct_children->filter( fn( $child ) => $child->get_type() === self::ELEMENT_TYPE_TABS_MENU )->first();
|
||||
|
||||
$tab_ids = $this->get_filtered_children_ids( $tabs_menu, self::ELEMENT_TYPE_TAB );
|
||||
|
||||
return $tab_ids[ $tab_id ];
|
||||
}
|
||||
|
||||
private function get_tab_content_index( $tab_content_id ) {
|
||||
$direct_children = Collection::make( $this->get_children() );
|
||||
$tabs_content_area = $direct_children->filter( fn( $child ) => $child->get_type() === self::ELEMENT_TYPE_TABS_CONTENT_AREA )->first();
|
||||
|
||||
$tab_content_ids = $this->get_filtered_children_ids( $tabs_content_area, self::ELEMENT_TYPE_TAB_CONTENT );
|
||||
|
||||
return $tab_content_ids[ $tab_content_id ];
|
||||
}
|
||||
|
||||
protected function define_render_context(): array {
|
||||
$default_active_tab = $this->get_atomic_setting( 'default-active-tab' );
|
||||
|
||||
return [
|
||||
[
|
||||
'context' => [
|
||||
'default-active-tab' => $default_active_tab,
|
||||
'get-tab-index' => fn( $tab_id ) => $this->get_tab_index( $tab_id ),
|
||||
'get-tab-content-index' => fn( $tab_content_id ) => $this->get_tab_content_index( $tab_content_id ),
|
||||
'tabs-id' => $this->get_id(),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-tabs' => __DIR__ . '/atomic-tabs.html.twig',
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_tab_id( $tabs_id, $index ) {
|
||||
return "{$tabs_id}-tab-{$index}";
|
||||
}
|
||||
|
||||
public static function get_tab_content_id( $tabs_id, $index ) {
|
||||
return "{$tabs_id}-tab-content-{$index}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { register } from '@elementor/frontend-handlers';
|
||||
import { Alpine } from '@elementor/alpinejs';
|
||||
import { TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE, getTabId, getTabContentId, getIndex, getNextTab } from './utils';
|
||||
|
||||
const SELECTED_CLASS = 'e--selected';
|
||||
|
||||
register( {
|
||||
elementType: 'e-tabs',
|
||||
id: 'e-tabs-handler',
|
||||
callback: ( { element, settings } ) => {
|
||||
const tabsId = element.dataset.id;
|
||||
|
||||
Alpine.data( `eTabs${ tabsId }`, () => ( {
|
||||
activeTab: settings[ 'default-active-tab' ],
|
||||
|
||||
navigateTabs( { key, target: tab } ) {
|
||||
const nextTab = getNextTab( key, tab );
|
||||
|
||||
nextTab.focus();
|
||||
},
|
||||
tab: {
|
||||
':id'() {
|
||||
const index = getIndex( this.$el, TAB_ELEMENT_TYPE );
|
||||
|
||||
return getTabId( tabsId, index );
|
||||
},
|
||||
'@click'() {
|
||||
const id = this.$el.id;
|
||||
|
||||
this.activeTab = id;
|
||||
},
|
||||
'@keydown.arrow-right.prevent'( event ) {
|
||||
this.navigateTabs( event );
|
||||
},
|
||||
'@keydown.arrow-left.prevent'( event ) {
|
||||
this.navigateTabs( event );
|
||||
},
|
||||
':class'() {
|
||||
const id = this.$el.id;
|
||||
|
||||
return { [ SELECTED_CLASS ]: this.activeTab === id };
|
||||
},
|
||||
':aria-selected'() {
|
||||
const id = this.$el.id;
|
||||
|
||||
return this.activeTab === id ? 'true' : 'false';
|
||||
},
|
||||
':tabindex'() {
|
||||
const id = this.$el.id;
|
||||
|
||||
return this.activeTab === id ? '0' : '-1';
|
||||
},
|
||||
':aria-controls'() {
|
||||
const index = getIndex( this.$el, TAB_ELEMENT_TYPE );
|
||||
|
||||
return getTabContentId( tabsId, index );
|
||||
},
|
||||
},
|
||||
|
||||
tabContent: {
|
||||
':aria-labelledby'() {
|
||||
const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE );
|
||||
|
||||
return getTabId( tabsId, index );
|
||||
},
|
||||
'x-show'() {
|
||||
const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE );
|
||||
const tabId = getTabId( tabsId, index );
|
||||
|
||||
const isActive = this.activeTab === tabId;
|
||||
|
||||
this.$nextTick( () => {
|
||||
this.$el.classList.toggle( SELECTED_CLASS, isActive );
|
||||
} );
|
||||
|
||||
return isActive;
|
||||
},
|
||||
':id'() {
|
||||
const index = getIndex( this.$el, TAB_CONTENT_ELEMENT_TYPE );
|
||||
|
||||
return getTabContentId( tabsId, index );
|
||||
},
|
||||
},
|
||||
} ) );
|
||||
},
|
||||
} );
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { register } from '@elementor/frontend-handlers';
|
||||
import { Alpine, refreshTree } from '@elementor/alpinejs';
|
||||
import { TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE, getTabId, getIndex } from './utils';
|
||||
|
||||
register( {
|
||||
elementType: 'e-tabs',
|
||||
id: 'e-tabs-preview-handler',
|
||||
callback: ( { element, signal, listenToChildren } ) => {
|
||||
window?.parent.addEventListener( 'elementor/navigator/item/click', ( event ) => {
|
||||
const { id, type } = event.detail;
|
||||
|
||||
if ( type !== TAB_ELEMENT_TYPE && type !== TAB_CONTENT_ELEMENT_TYPE ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetElement = Alpine.$data( element ).$refs[ id ];
|
||||
|
||||
if ( ! targetElement ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetIndex = getIndex( targetElement, type );
|
||||
Alpine.$data( element ).activeTab = getTabId( element.dataset.id, targetIndex );
|
||||
}, { signal } );
|
||||
|
||||
// Re-initialize Alpine to sync with editor DOM manipulations that bypass Alpine's reactivity.
|
||||
listenToChildren( [ TAB_ELEMENT_TYPE, TAB_CONTENT_ELEMENT_TYPE ] )
|
||||
.render( () => refreshTree( element ) );
|
||||
},
|
||||
} );
|
||||
@@ -0,0 +1,44 @@
|
||||
export const TAB_ELEMENT_TYPE = 'e-tab';
|
||||
export const TAB_CONTENT_ELEMENT_TYPE = 'e-tab-content';
|
||||
export const TABS_CONTENT_AREA_ELEMENT_TYPE = 'e-tabs-content-area';
|
||||
export const TABS_MENU_ELEMENT_TYPE = 'e-tabs-menu';
|
||||
|
||||
const NAVIGATE_UP_KEYS = [ 'ArrowUp', 'ArrowLeft' ];
|
||||
const NAVIGATE_DOWN_KEYS = [ 'ArrowDown', 'ArrowRight' ];
|
||||
|
||||
export const getTabId = ( tabsId, tabIndex ) => {
|
||||
return `${ tabsId }-tab-${ tabIndex }`;
|
||||
};
|
||||
|
||||
export const getTabContentId = ( tabsId, tabIndex ) => {
|
||||
return `${ tabsId }-tab-content-${ tabIndex }`;
|
||||
};
|
||||
|
||||
export const getChildren = ( el, elementType ) => {
|
||||
const parent = el.parentElement;
|
||||
|
||||
return Array.from( parent.children ).filter( ( child ) => {
|
||||
return child.dataset.element_type === elementType;
|
||||
} );
|
||||
};
|
||||
|
||||
export const getIndex = ( el, elementType ) => {
|
||||
const children = getChildren( el, elementType );
|
||||
|
||||
return children.indexOf( el );
|
||||
};
|
||||
|
||||
export const getNextTab = ( key, tab ) => {
|
||||
const tabs = getChildren( tab, TAB_ELEMENT_TYPE );
|
||||
const tabsLength = tabs.length;
|
||||
|
||||
const currentIndex = getIndex( tab, TAB_ELEMENT_TYPE );
|
||||
|
||||
if ( NAVIGATE_DOWN_KEYS.includes( key ) ) {
|
||||
return tabs[ ( currentIndex + 1 ) % tabsLength ];
|
||||
}
|
||||
|
||||
if ( NAVIGATE_UP_KEYS.includes( key ) ) {
|
||||
return tabs[ ( currentIndex - 1 + tabsLength ) % tabsLength ];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
{% if settings.source is not empty %}
|
||||
{% set id_attribute = settings._cssid is not empty ? 'id=' ~ settings._cssid | e('html_attr') : '' %}
|
||||
{% set classes = settings.classes | merge( [ base_styles.base ] ) | join(' ') %}
|
||||
{% set data_settings = {
|
||||
'source': settings.source,
|
||||
'autoplay': settings.autoplay,
|
||||
'mute': settings.mute,
|
||||
'controls': settings.player_controls,
|
||||
'cc_load_policy': settings.captions,
|
||||
'loop': settings.loop,
|
||||
'rel': settings.rel,
|
||||
'start': settings.start,
|
||||
'end': settings.end,
|
||||
'privacy': settings.privacy_mode,
|
||||
'lazyload': settings.lazyload,
|
||||
} %}
|
||||
<div data-id="{{ id }}" data-interaction-id="{{ interaction_id }}" data-e-type="{{ type }}" {{ id_attribute }} class="{{ classes }}" {{ settings.attributes | raw }} data-settings="{{ data_settings|json_encode|e('html_attr') }}"></div>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Atomic_Youtube;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Has_Template;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Youtube extends Atomic_Widget_Base {
|
||||
use Has_Template;
|
||||
|
||||
protected function get_css_id_control_meta(): array {
|
||||
return [
|
||||
'layout' => 'two-columns',
|
||||
'topDivider' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-youtube';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'YouTube', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-e-youtube';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
|
||||
'source' => String_Prop_Type::make()
|
||||
->default( 'https://www.youtube.com/watch?v=XHOmBV4js_E' ),
|
||||
|
||||
'start' => String_Prop_Type::make()->meta( Dynamic_Prop_Type::ignore() ),
|
||||
'end' => String_Prop_Type::make()->meta( Dynamic_Prop_Type::ignore() ),
|
||||
'autoplay' => Boolean_Prop_Type::make()->default( false ),
|
||||
'mute' => Boolean_Prop_Type::make()->default( false ),
|
||||
'loop' => Boolean_Prop_Type::make()->default( false ),
|
||||
'lazyload' => Boolean_Prop_Type::make()->default( false ),
|
||||
'player_controls' => Boolean_Prop_Type::make()->default( true ),
|
||||
'captions' => Boolean_Prop_Type::make()->default( false ),
|
||||
'privacy_mode' => Boolean_Prop_Type::make()->default( false ),
|
||||
'rel' => Boolean_Prop_Type::make()->default( true ),
|
||||
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Content', 'elementor' ) )
|
||||
->set_items( [
|
||||
Text_Control::bind_to( 'source' )
|
||||
->set_placeholder( esc_html__( 'Type or paste your URL', 'elementor' ) )
|
||||
->set_label( esc_html__( 'YouTube URL', 'elementor' ) ),
|
||||
|
||||
Text_Control::bind_to( 'start' )->set_label( esc_html__( 'Start time', 'elementor' ) ),
|
||||
Text_Control::bind_to( 'end' )->set_label( esc_html__( 'End time', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'autoplay' )->set_label( esc_html__( 'Autoplay', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'mute' )->set_label( esc_html__( 'Mute', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'loop' )->set_label( esc_html__( 'Loop', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'lazyload' )->set_label( esc_html__( 'Lazy load', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'player_controls' )->set_label( esc_html__( 'Player controls', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'captions' )->set_label( esc_html__( 'Captions', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'privacy_mode' )->set_label( esc_html__( 'Privacy mode', 'elementor' ) ),
|
||||
Switch_Control::bind_to( 'rel' )->set_label( esc_html__( 'Related videos', 'elementor' ) ),
|
||||
] ),
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( $this->get_settings_controls() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_settings_controls(): array {
|
||||
return [
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
return [
|
||||
'base' => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'aspect-ratio', String_Prop_Type::generate( '16/9' ) )
|
||||
->add_prop( 'overflow', String_Prop_Type::generate( 'hidden' ) )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
public function get_script_depends() {
|
||||
return array_merge(
|
||||
parent::get_script_depends(),
|
||||
[ 'elementor-youtube-handler' ],
|
||||
);
|
||||
}
|
||||
|
||||
public function register_frontend_handlers() {
|
||||
$assets_url = ELEMENTOR_ASSETS_URL;
|
||||
$min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min';
|
||||
|
||||
wp_register_script(
|
||||
'elementor-youtube-handler',
|
||||
"{$assets_url}js/youtube-handler{$min_suffix}.js",
|
||||
[ Frontend_Assets_Loader::FRONTEND_HANDLERS_HANDLE ],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
protected function get_templates(): array {
|
||||
return [
|
||||
'elementor/elements/atomic-youtube' => __DIR__ . '/atomic-youtube.html.twig',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { register } from '@elementor/frontend-handlers';
|
||||
|
||||
const getYoutubeVideoIdFromUrl = ( url ) => {
|
||||
const regex = /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?vi?=|(?:embed|v|vi|user|shorts)\/))([^?&"'>]+)/;
|
||||
const match = url.match( regex );
|
||||
return match ? match[ 1 ] : null;
|
||||
};
|
||||
|
||||
const loadYouTubeAPI = () => {
|
||||
return new Promise( ( resolve ) => {
|
||||
if ( window.YT && window.YT.loaded ) {
|
||||
resolve( window.YT );
|
||||
return;
|
||||
}
|
||||
|
||||
const YOUTUBE_IFRAME_API_URL = 'https://www.youtube.com/iframe_api';
|
||||
if ( ! document.querySelector( `script[src="${ YOUTUBE_IFRAME_API_URL }"]` ) ) {
|
||||
const tag = document.createElement( 'script' );
|
||||
tag.src = YOUTUBE_IFRAME_API_URL;
|
||||
const firstScriptTag = document.getElementsByTagName( 'script' )[ 0 ];
|
||||
firstScriptTag.parentNode.insertBefore( tag, firstScriptTag );
|
||||
}
|
||||
|
||||
const checkYT = () => {
|
||||
if ( window.YT && window.YT.loaded ) {
|
||||
resolve( window.YT );
|
||||
} else {
|
||||
setTimeout( checkYT, 350 );
|
||||
}
|
||||
};
|
||||
checkYT();
|
||||
} );
|
||||
};
|
||||
|
||||
register( {
|
||||
elementType: 'e-youtube',
|
||||
id: 'e-youtube-handler',
|
||||
callback: ( { element } ) => {
|
||||
const youtubeElement = document.createElement( 'div' );
|
||||
youtubeElement.style.height = '100%';
|
||||
element.appendChild( youtubeElement );
|
||||
|
||||
const settingsAttr = element.getAttribute( 'data-settings' );
|
||||
const parsedSettings = settingsAttr ? JSON.parse( settingsAttr ) : {};
|
||||
|
||||
const videoId = getYoutubeVideoIdFromUrl( parsedSettings.source );
|
||||
|
||||
if ( ! videoId ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let player;
|
||||
let observer;
|
||||
|
||||
const prepareYTVideo = ( YT ) => {
|
||||
const playerOptions = {
|
||||
videoId,
|
||||
events: {
|
||||
onReady: () => {
|
||||
if ( parsedSettings.mute ) {
|
||||
player.mute();
|
||||
}
|
||||
|
||||
if ( parsedSettings.autoplay ) {
|
||||
player.playVideo();
|
||||
}
|
||||
},
|
||||
onStateChange: ( event ) => {
|
||||
if ( event.data === YT.PlayerState.ENDED && parsedSettings.loop ) {
|
||||
player.seekTo( parsedSettings.start || 0 );
|
||||
}
|
||||
},
|
||||
},
|
||||
playerVars: {
|
||||
controls: parsedSettings.controls ? 1 : 0,
|
||||
rel: parsedSettings.rel ? 0 : 1,
|
||||
cc_load_policy: parsedSettings.cc_load_policy ? 1 : 0,
|
||||
autoplay: parsedSettings.autoplay ? 1 : 0,
|
||||
start: parsedSettings.start,
|
||||
end: parsedSettings.end,
|
||||
},
|
||||
};
|
||||
|
||||
// To handle CORS issues, when the default host is changed, the origin parameter has to be set.
|
||||
if ( parsedSettings.privacy ) {
|
||||
playerOptions.host = 'https://www.youtube-nocookie.com';
|
||||
playerOptions.origin = window.location.hostname;
|
||||
}
|
||||
|
||||
player = new YT.Player( youtubeElement, playerOptions );
|
||||
|
||||
return player;
|
||||
};
|
||||
|
||||
if ( parsedSettings.lazyload ) {
|
||||
observer = new IntersectionObserver(
|
||||
( entries ) => {
|
||||
if ( entries[ 0 ].isIntersecting ) {
|
||||
loadYouTubeAPI().then( ( apiObject ) => prepareYTVideo( apiObject ) );
|
||||
observer.unobserve( element );
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
observer.observe( element );
|
||||
} else {
|
||||
loadYouTubeAPI().then( ( apiObject ) => prepareYTVideo( apiObject ) );
|
||||
}
|
||||
|
||||
return () => {
|
||||
if ( player && 'function' === typeof player.destroy ) {
|
||||
player.destroy();
|
||||
player = null;
|
||||
}
|
||||
|
||||
if ( element.contains( youtubeElement ) ) {
|
||||
element.removeChild( youtubeElement );
|
||||
}
|
||||
|
||||
if ( observer && 'function' === typeof observer.disconnect ) {
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
}
|
||||
};
|
||||
},
|
||||
} );
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
use Elementor\Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Meta;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
abstract class Atomic_Element_Base extends Element_Base {
|
||||
use Has_Atomic_Base;
|
||||
use Has_Meta;
|
||||
|
||||
protected $version = '0.0';
|
||||
protected $styles = [];
|
||||
protected $interactions = [];
|
||||
protected $editor_settings = [];
|
||||
protected $origin_id = null;
|
||||
|
||||
public static $widget_description = null;
|
||||
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
|
||||
$this->version = $data['version'] ?? '0.0';
|
||||
$this->styles = $data['styles'] ?? [];
|
||||
$this->interactions = $this->parse_atomic_interactions( $data['interactions'] ?? [] );
|
||||
$this->editor_settings = $data['editor_settings'] ?? [];
|
||||
$this->add_script_depends( Frontend_Assets_Loader::ATOMIC_WIDGETS_HANDLER );
|
||||
if ( static::$widget_description ) {
|
||||
$this->description( static::$widget_description );
|
||||
}
|
||||
$this->origin_id = $data['origin_id'] ?? null;
|
||||
}
|
||||
|
||||
private function parse_atomic_interactions( $interactions ) {
|
||||
if ( empty( $interactions ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( is_string( $interactions ) ) {
|
||||
$decoded = json_decode( $interactions, true );
|
||||
if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
|
||||
$interactions = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_array( $interactions ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $interactions;
|
||||
}
|
||||
|
||||
abstract protected function define_atomic_controls(): array;
|
||||
|
||||
protected function define_atomic_style_states(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function define_atomic_pseudo_states(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function get_global_scripts() {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function get_initial_config() {
|
||||
$config = parent::get_initial_config();
|
||||
$props_schema = static::get_props_schema();
|
||||
|
||||
$config['atomic'] = true;
|
||||
$config['atomic_controls'] = $this->get_atomic_controls();
|
||||
$config['atomic_props_schema'] = $props_schema;
|
||||
$config['atomic_style_states'] = $this->define_atomic_style_states();
|
||||
$config['atomic_pseudo_states'] = $this->define_atomic_pseudo_states();
|
||||
$config['dependencies_per_target_mapping'] = Dependency_Manager::get_source_to_dependents( $props_schema );
|
||||
$config['base_styles'] = $this->get_base_styles();
|
||||
$config['version'] = $this->version;
|
||||
$config['show_in_panel'] = $this->should_show_in_panel();
|
||||
$config['categories'] = $this->define_panel_categories();
|
||||
$config['hide_on_search'] = false;
|
||||
$config['controls'] = [];
|
||||
$config['keywords'] = $this->get_keywords();
|
||||
$config['default_children'] = $this->define_default_children();
|
||||
$config['initial_attributes'] = $this->define_initial_attributes();
|
||||
$config['include_in_widgets_config'] = true;
|
||||
$config['default_html_tag'] = $this->define_default_html_tag();
|
||||
$config['meta'] = $this->get_meta();
|
||||
$config['allowed_child_types'] = $this->define_allowed_child_types();
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function should_show_in_panel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function define_panel_categories(): array {
|
||||
return [ 'v4-elements' ];
|
||||
}
|
||||
|
||||
protected function define_default_children() {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function define_default_html_tag() {
|
||||
return 'div';
|
||||
}
|
||||
|
||||
protected function define_initial_attributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function define_allowed_child_types() {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function get_interaction_id() {
|
||||
return $this->origin_id ?? $this->get_id();
|
||||
}
|
||||
|
||||
protected function add_render_attributes() {
|
||||
parent::add_render_attributes();
|
||||
|
||||
$this->add_render_attribute( '_wrapper', 'data-interaction-id', $this->get_interaction_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Element keywords.
|
||||
*
|
||||
* Retrieve the element keywords.
|
||||
*
|
||||
* @since 3.29
|
||||
* @access public
|
||||
*
|
||||
* @return array Element keywords.
|
||||
*/
|
||||
public function get_keywords() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Prop_Type>
|
||||
*/
|
||||
abstract protected static function define_props_schema(): array;
|
||||
|
||||
/**
|
||||
* Get the HTML tag for rendering.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_html_tag(): string {
|
||||
$settings = $this->get_atomic_settings();
|
||||
$default_html_tag = $this->define_default_html_tag();
|
||||
|
||||
return ! empty( $settings['link']['href'] ) ? $settings['link']['tag'] : ( $settings['tag'] ?? $default_html_tag );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print safe HTML tag for the element based on the element settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function print_html_tag() {
|
||||
$html_tag = $this->get_html_tag();
|
||||
Utils::print_validated_html_tag( $html_tag );
|
||||
}
|
||||
|
||||
/**
|
||||
* Print custom attributes if they exist.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function print_custom_attributes() {
|
||||
$settings = $this->get_atomic_settings();
|
||||
$attributes = $settings['attributes'] ?? '';
|
||||
if ( ! empty( $attributes ) && is_string( $attributes ) ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo ' ' . $attributes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default child type for container elements.
|
||||
*
|
||||
* @param array $element_data
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _get_default_child_type( array $element_data ) {
|
||||
$el_types = array_keys( Plugin::$instance->elements_manager->get_element_types() );
|
||||
|
||||
if ( in_array( $element_data['elType'], $el_types, true ) ) {
|
||||
return Plugin::$instance->elements_manager->get_element_types( $element_data['elType'] );
|
||||
}
|
||||
|
||||
if ( ! isset( $element_data['widgetType'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Plugin::$instance->widgets_manager->get_widget_types( $element_data['widgetType'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Default before render for container elements.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before_render() {
|
||||
?>
|
||||
<<?php $this->print_html_tag(); ?> <?php $this->print_render_attribute_string( '_wrapper' );
|
||||
$this->print_custom_attributes(); ?>>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Default after render for container elements.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function after_render() {
|
||||
?>
|
||||
</<?php $this->print_html_tag(); ?>>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Default content template - can be overridden by elements that need custom templates.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function content_template() {
|
||||
?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function generate() {
|
||||
return Element_Builder::make( static::get_type() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Concerns\Has_Meta;
|
||||
use Elementor\Widget_Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
abstract class Atomic_Widget_Base extends Widget_Base {
|
||||
use Has_Atomic_Base;
|
||||
use Has_Meta;
|
||||
|
||||
public static $widget_description = null;
|
||||
|
||||
protected $version = '0.0';
|
||||
protected $styles = [];
|
||||
protected $interactions = [];
|
||||
protected $editor_settings = [];
|
||||
protected $origin_id = null;
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
|
||||
$this->version = $data['version'] ?? '0.0';
|
||||
$this->styles = $data['styles'] ?? [];
|
||||
$this->interactions = $this->parse_atomic_interactions( $data['interactions'] ?? [] );
|
||||
$this->editor_settings = $data['editor_settings'] ?? [];
|
||||
if ( static::$widget_description ) {
|
||||
$this->description( static::$widget_description );
|
||||
}
|
||||
$this->origin_id = $data['origin_id'] ?? null;
|
||||
}
|
||||
|
||||
private function parse_atomic_interactions( $interactions ) {
|
||||
if ( empty( $interactions ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( is_string( $interactions ) ) {
|
||||
$decoded = json_decode( $interactions, true );
|
||||
if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
|
||||
$interactions = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_array( $interactions ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $interactions;
|
||||
}
|
||||
|
||||
abstract protected function define_atomic_controls(): array;
|
||||
|
||||
protected function define_atomic_pseudo_states(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function get_global_scripts() {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function get_initial_config() {
|
||||
$config = parent::get_initial_config();
|
||||
$props_schema = static::get_props_schema();
|
||||
|
||||
$config['atomic'] = true;
|
||||
$config['atomic_controls'] = $this->get_atomic_controls();
|
||||
$config['base_styles'] = $this->get_base_styles();
|
||||
$config['base_styles_dictionary'] = $this->get_base_styles_dictionary();
|
||||
$config['atomic_props_schema'] = $props_schema;
|
||||
$config['atomic_pseudo_states'] = $this->define_atomic_pseudo_states();
|
||||
$config['dependencies_per_target_mapping'] = Dependency_Manager::get_source_to_dependents( $props_schema );
|
||||
$config['version'] = $this->version;
|
||||
$config['meta'] = $this->get_meta();
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function get_categories(): array {
|
||||
return [ 'v4-elements' ];
|
||||
}
|
||||
|
||||
public function before_render() {}
|
||||
|
||||
public function after_render() {}
|
||||
|
||||
abstract protected static function define_props_schema(): array;
|
||||
|
||||
public static function generate() {
|
||||
return Widget_Builder::make( static::get_element_type() );
|
||||
}
|
||||
|
||||
public function get_script_depends() {
|
||||
return [ Frontend_Assets_Loader::ATOMIC_WIDGETS_HANDLER ];
|
||||
}
|
||||
|
||||
public function get_interaction_id() {
|
||||
return $this->origin_id ?? $this->get_id();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
class Element_Builder {
|
||||
protected $element_type;
|
||||
protected $settings = [];
|
||||
protected $is_locked = false;
|
||||
protected $children = [];
|
||||
protected $editor_settings = [];
|
||||
|
||||
public static function make( string $element_type ) {
|
||||
return new self( $element_type );
|
||||
}
|
||||
|
||||
private function __construct( string $element_type ) {
|
||||
$this->element_type = $element_type;
|
||||
}
|
||||
|
||||
public function settings( array $settings ) {
|
||||
$this->settings = $settings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function is_locked( $is_locked ) {
|
||||
$this->is_locked = $is_locked;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function editor_settings( array $editor_settings ) {
|
||||
$this->editor_settings = $editor_settings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function children( array $children ) {
|
||||
$this->children = $children;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build() {
|
||||
$element_data = [
|
||||
'elType' => $this->element_type,
|
||||
'settings' => $this->settings,
|
||||
'isLocked' => $this->is_locked,
|
||||
'editor_settings' => $this->editor_settings,
|
||||
'elements' => $this->children,
|
||||
];
|
||||
|
||||
return $element_data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
use Elementor\Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Render_Props_Resolver;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
|
||||
use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser;
|
||||
use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Key_Value_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Utils;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* @mixin Element_Base
|
||||
*/
|
||||
trait Has_Atomic_Base {
|
||||
use Has_Base_Styles;
|
||||
|
||||
public function has_widget_inner_wrapper(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract public static function get_element_type(): string;
|
||||
|
||||
final public function get_name() {
|
||||
return static::get_element_type();
|
||||
}
|
||||
|
||||
private function get_valid_controls( array $schema, array $controls ): array {
|
||||
$valid_controls = [];
|
||||
|
||||
foreach ( $controls as $control ) {
|
||||
if ( $control instanceof Section ) {
|
||||
$cloned_section = clone $control;
|
||||
|
||||
$cloned_section->set_items(
|
||||
$this->get_valid_controls( $schema, $control->get_items() )
|
||||
);
|
||||
|
||||
$valid_controls[] = $cloned_section;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ( $control instanceof Atomic_Control_Base ) ) {
|
||||
$prop_name = $control->get_bind();
|
||||
|
||||
if ( ! $prop_name ) {
|
||||
Utils::safe_throw( 'Control is missing a bound prop from the schema.' );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $prop_name, $schema ) ) {
|
||||
Utils::safe_throw( "Prop `{$prop_name}` is not defined in the schema of `{$this->get_name()}`." );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$valid_controls[] = $control;
|
||||
}
|
||||
|
||||
return $valid_controls;
|
||||
}
|
||||
|
||||
private static function validate_schema( array $schema ) {
|
||||
$widget_name = static::class;
|
||||
|
||||
foreach ( $schema as $key => $prop ) {
|
||||
if ( ! ( $prop instanceof Prop_Type ) ) {
|
||||
Utils::safe_throw( "Prop `$key` must be an instance of `Prop_Type` in `{$widget_name}`." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parse_atomic_styles( array $data ): array {
|
||||
$styles = $data['styles'] ?? [];
|
||||
$style_parser = Style_Parser::make( Style_Schema::get() );
|
||||
|
||||
foreach ( $styles as $style_id => $style ) {
|
||||
$result = $style_parser->parse( $style );
|
||||
|
||||
if ( ! $result->is_valid() ) {
|
||||
$widget_id = $data['id'] ?? 'unknown';
|
||||
throw new \Exception( esc_html( "Styles validation failed for style `$style_id`. Widget ID: `$widget_id`. " . $result->errors()->to_string() ) );
|
||||
}
|
||||
|
||||
$styles[ $style_id ] = $result->unwrap();
|
||||
}
|
||||
|
||||
return $styles;
|
||||
}
|
||||
|
||||
private function parse_atomic_settings( array $settings ): array {
|
||||
$schema = static::get_props_schema();
|
||||
$props_parser = Props_Parser::make( $schema );
|
||||
|
||||
$result = $props_parser->parse( $settings );
|
||||
|
||||
if ( ! $result->is_valid() ) {
|
||||
throw new \Exception( esc_html( 'Settings validation failed. ' . $result->errors()->to_string() ) );
|
||||
}
|
||||
|
||||
return $result->unwrap();
|
||||
}
|
||||
|
||||
private function parse_atomic_interactions( $interactions ) {
|
||||
|
||||
if ( empty( $interactions ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( is_string( $interactions ) ) {
|
||||
$decoded = json_decode( $interactions, true );
|
||||
if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_array( $interactions ) ) {
|
||||
return $interactions;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function extract_prop_value( $data, $key, $default = '' ) {
|
||||
if ( ! is_array( $data ) || ! isset( $data[ $key ] ) ) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$value = $data[ $key ];
|
||||
|
||||
if ( is_array( $value ) && isset( $value['$$type'] ) && isset( $value['value'] ) ) {
|
||||
return $value['value'];
|
||||
}
|
||||
|
||||
return null !== $value ? $value : $default;
|
||||
}
|
||||
|
||||
public function get_atomic_controls() {
|
||||
$controls = apply_filters(
|
||||
'elementor/atomic-widgets/controls',
|
||||
$this->define_atomic_controls(),
|
||||
$this
|
||||
);
|
||||
|
||||
$schema = static::get_props_schema();
|
||||
|
||||
// Validate the schema only in the Editor.
|
||||
static::validate_schema( $schema );
|
||||
|
||||
return $this->get_valid_controls( $schema, $controls );
|
||||
}
|
||||
|
||||
protected function get_css_id_control_meta(): array {
|
||||
return [
|
||||
'layout' => 'two-columns',
|
||||
'topDivider' => true,
|
||||
];
|
||||
}
|
||||
|
||||
final public function get_controls( $control_id = null ) {
|
||||
if ( ! empty( $control_id ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
final public function get_data_for_save() {
|
||||
$data = parent::get_data_for_save();
|
||||
|
||||
$data['version'] = $this->version;
|
||||
$data['settings'] = $this->parse_atomic_settings( $data['settings'] );
|
||||
$data['styles'] = $this->parse_atomic_styles( $data );
|
||||
$data['editor_settings'] = $this->parse_editor_settings( $data['editor_settings'] );
|
||||
|
||||
if ( isset( $data['interactions'] ) && ! empty( $data['interactions'] ) ) {
|
||||
$data['interactions'] = $this->transform_interactions_for_save( $data['interactions'] );
|
||||
} else {
|
||||
$data['interactions'] = [];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function transform_interactions_for_save( $interactions ) {
|
||||
$decoded = $this->decode_interactions_data( $interactions );
|
||||
|
||||
if ( empty( $decoded['items'] ) ) {
|
||||
return [];
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
private function decode_interactions_data( $interactions ) {
|
||||
if ( is_array( $interactions ) ) {
|
||||
return $interactions;
|
||||
}
|
||||
|
||||
if ( is_string( $interactions ) ) {
|
||||
$decoded = json_decode( $interactions, true );
|
||||
if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'items' => [],
|
||||
'version' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
final public function get_raw_data( $with_html_content = false ) {
|
||||
$raw_data = parent::get_raw_data( $with_html_content );
|
||||
|
||||
$raw_data['styles'] = Atomic_Widget_Styles::get_license_based_filtered_styles( $this->styles ?? [] );
|
||||
$raw_data['interactions'] = $this->interactions ?? [];
|
||||
$raw_data['editor_settings'] = $this->editor_settings;
|
||||
|
||||
return $raw_data;
|
||||
}
|
||||
|
||||
final public function get_stack( $with_common_controls = true ) {
|
||||
return [
|
||||
'controls' => [],
|
||||
'tabs' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function get_atomic_settings(): array {
|
||||
$schema = static::get_props_schema();
|
||||
$props = $this->get_settings();
|
||||
$initial_attributes = $this->get_initial_attributes();
|
||||
|
||||
$props['attributes'] = Attributes_Prop_Type::generate( array_merge(
|
||||
$initial_attributes['value'] ?? [],
|
||||
$props['attributes']['value'] ?? []
|
||||
) );
|
||||
|
||||
return Render_Props_Resolver::for_settings()->resolve( $schema, $props );
|
||||
}
|
||||
|
||||
protected function get_initial_attributes() {
|
||||
return Attributes_Prop_Type::generate( [
|
||||
Key_Value_Prop_Type::generate( [
|
||||
'key' => String_Prop_Type::generate( 'data-e-type' ),
|
||||
'value' => $this->get_type(),
|
||||
] ),
|
||||
Key_Value_Prop_Type::generate( [
|
||||
'key' => String_Prop_Type::generate( 'data-id' ),
|
||||
'value' => $this->get_id(),
|
||||
] ),
|
||||
] );
|
||||
}
|
||||
|
||||
public function get_atomic_setting( string $key ) {
|
||||
$schema = static::get_props_schema();
|
||||
|
||||
if ( ! isset( $schema[ $key ] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$props = $this->get_settings();
|
||||
$prop_value = $props[ $key ] ?? null;
|
||||
|
||||
$single_schema = [ $key => $schema[ $key ] ];
|
||||
$single_props = [ $key => $prop_value ];
|
||||
|
||||
$resolved = Render_Props_Resolver::for_settings()->resolve( $single_schema, $single_props );
|
||||
|
||||
return $resolved[ $key ] ?? null;
|
||||
}
|
||||
|
||||
protected function parse_editor_settings( array $data ): array {
|
||||
$editor_data = [];
|
||||
|
||||
if ( isset( $data['title'] ) && is_string( $data['title'] ) ) {
|
||||
$editor_data['title'] = sanitize_text_field( $data['title'] );
|
||||
}
|
||||
|
||||
return $editor_data;
|
||||
}
|
||||
|
||||
public static function get_props_schema(): array {
|
||||
$schema = static::define_props_schema();
|
||||
$schema['_cssid'] = String_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() );
|
||||
|
||||
return apply_filters(
|
||||
'elementor/atomic-widgets/props-schema',
|
||||
$schema
|
||||
);
|
||||
}
|
||||
|
||||
protected function set_render_context( array $context_pairs ): void {
|
||||
foreach ( $context_pairs as $context_pair ) {
|
||||
$context_key = $context_pair['context_key'] ?? static::class;
|
||||
$context = $context_pair['context'];
|
||||
Render_Context::push( $context_key, $context );
|
||||
}
|
||||
}
|
||||
|
||||
protected function clear_render_context( array $context_pairs ): void {
|
||||
foreach ( $context_pairs as $context_pair ) {
|
||||
$context_key = $context_pair['context_key'] ?? static::class;
|
||||
Render_Context::pop( $context_key );
|
||||
}
|
||||
}
|
||||
|
||||
public function print_content() {
|
||||
$defined_context = $this->define_render_context();
|
||||
|
||||
if ( empty( $defined_context ) ) {
|
||||
return parent::print_content();
|
||||
}
|
||||
|
||||
$this->set_render_context( $defined_context );
|
||||
|
||||
parent::print_content();
|
||||
|
||||
$this->clear_render_context( $defined_context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the context for element's Render_Context.
|
||||
*
|
||||
* @return array Array of context pairs. Each pair is an associative array with:
|
||||
* - 'context_key' (optional): The context key. Defaults to static::class if not provided.
|
||||
* - 'context' (required): The context value (can be any type).
|
||||
*
|
||||
* @example
|
||||
* [
|
||||
* [
|
||||
* 'context_key' => 'custom-key',
|
||||
* 'context' => ['some' => 'data'],
|
||||
* ],
|
||||
* [
|
||||
* 'context' => ['instance_id' => $this->get_id()],
|
||||
* ],
|
||||
* ]
|
||||
*/
|
||||
protected function define_render_context(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function get_link_attributes( $link_settings, $add_key_to_result = false ) {
|
||||
$tag = $link_settings['tag'] ?? Link_Prop_Type::DEFAULT_TAG;
|
||||
$href = $link_settings['href'];
|
||||
$target = $link_settings['target'] ?? '_self';
|
||||
|
||||
$is_button = 'button' === $tag;
|
||||
$href_attribute_key = $is_button ? 'data-action-link' : 'href';
|
||||
|
||||
$result = [
|
||||
$href_attribute_key => $href,
|
||||
'target' => $target,
|
||||
];
|
||||
|
||||
if ( $add_key_to_result ) {
|
||||
$result['key'] = $href_attribute_key;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* @mixin Has_Atomic_Base
|
||||
*/
|
||||
trait Has_Base_Styles {
|
||||
public function get_base_styles() {
|
||||
$base_styles = $this->define_base_styles();
|
||||
$style_definitions = [];
|
||||
|
||||
foreach ( $base_styles as $key => $style ) {
|
||||
$id = $this->generate_base_style_id( $key );
|
||||
|
||||
$style_definitions[ $id ] = $style->build( $id );
|
||||
}
|
||||
|
||||
return $style_definitions;
|
||||
}
|
||||
|
||||
public function get_base_styles_dictionary() {
|
||||
$result = [];
|
||||
|
||||
$base_styles = array_keys( $this->define_base_styles() );
|
||||
|
||||
foreach ( $base_styles as $key ) {
|
||||
$result[ $key ] = $this->generate_base_style_id( $key );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function generate_base_style_id( string $key ): string {
|
||||
return static::get_element_type() . '-' . $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Style_Definition>
|
||||
*/
|
||||
protected function define_base_styles(): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer\Template_Renderer;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trait for nested elements that render using Twig templates.
|
||||
* Provides Twig-based rendering with children support for nested elements.
|
||||
*
|
||||
* @mixin Has_Atomic_Base
|
||||
* @mixin Atomic_Element_Base
|
||||
*/
|
||||
trait Has_Element_Template {
|
||||
|
||||
public function get_initial_config() {
|
||||
$config = parent::get_initial_config();
|
||||
|
||||
$config['support_nesting'] = true;
|
||||
$config['twig_main_template'] = $this->get_main_template();
|
||||
$config['twig_templates'] = $this->get_templates_contents();
|
||||
$config['base_styles_dictionary'] = $this->get_base_styles_dictionary();
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function get_templates_contents() {
|
||||
return array_map(
|
||||
fn ( $path ) => Utils::file_get_contents( $path ),
|
||||
$this->get_templates()
|
||||
);
|
||||
}
|
||||
|
||||
protected function render() {
|
||||
try {
|
||||
$renderer = Template_Renderer::instance();
|
||||
|
||||
foreach ( $this->get_templates() as $name => $path ) {
|
||||
if ( $renderer->is_registered( $name ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$renderer->register( $name, $path );
|
||||
}
|
||||
|
||||
$context = $this->build_template_context();
|
||||
$template_html = $renderer->render( $this->get_main_template(), $context );
|
||||
$children_html = $this->render_children_to_html();
|
||||
$output = str_replace( $this->get_children_placeholder(), $children_html, $template_html );
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $output;
|
||||
} catch ( \Exception $e ) {
|
||||
if ( Utils::is_elementor_debug() ) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function render_children_to_html(): string {
|
||||
$html = '';
|
||||
|
||||
foreach ( $this->get_children() as $child ) {
|
||||
ob_start();
|
||||
$child->print_element();
|
||||
$html .= ob_get_clean();
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function get_children_placeholder(): string {
|
||||
return '<!-- elementor-children-placeholder -->';
|
||||
}
|
||||
|
||||
protected function build_base_template_context(): array {
|
||||
return [
|
||||
'id' => $this->get_id(),
|
||||
'interaction_id' => $this->get_interaction_id(),
|
||||
'type' => $this->get_name(),
|
||||
'settings' => $this->get_atomic_settings(),
|
||||
'base_styles' => $this->get_base_styles_dictionary(),
|
||||
'children_placeholder' => $this->get_children_placeholder(),
|
||||
];
|
||||
}
|
||||
|
||||
public function before_render() {
|
||||
// Intentionally empty - Twig template handles full rendering
|
||||
}
|
||||
|
||||
public function after_render() {
|
||||
// Intentionally empty - Twig template handles full rendering
|
||||
}
|
||||
|
||||
public function print_content() {
|
||||
$defined_context = $this->define_render_context();
|
||||
|
||||
if ( empty( $defined_context ) ) {
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
$this->set_render_context( $defined_context );
|
||||
|
||||
$this->render();
|
||||
|
||||
$this->clear_render_context( $defined_context );
|
||||
}
|
||||
|
||||
protected function get_main_template(): string {
|
||||
$templates = $this->get_templates();
|
||||
|
||||
foreach ( $templates as $key => $path ) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
abstract protected function get_templates(): array;
|
||||
|
||||
protected function build_template_context(): array {
|
||||
return $this->build_base_template_context();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer\Template_Renderer;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* @mixin Has_Atomic_Base
|
||||
*/
|
||||
trait Has_Template {
|
||||
|
||||
public function get_initial_config() {
|
||||
$config = parent::get_initial_config();
|
||||
|
||||
$config['twig_main_template'] = $this->get_main_template();
|
||||
$config['twig_templates'] = $this->get_templates_contents();
|
||||
return $config;
|
||||
}
|
||||
|
||||
protected function render() {
|
||||
try {
|
||||
$renderer = Template_Renderer::instance();
|
||||
|
||||
foreach ( $this->get_templates() as $name => $path ) {
|
||||
if ( $renderer->is_registered( $name ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$renderer->register( $name, $path );
|
||||
}
|
||||
|
||||
$context = [
|
||||
'id' => $this->get_id(),
|
||||
'interaction_id' => $this->get_interaction_id(),
|
||||
'type' => $this->get_name(),
|
||||
'settings' => $this->get_atomic_settings(),
|
||||
'base_styles' => $this->get_base_styles_dictionary(),
|
||||
];
|
||||
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $renderer->render( $this->get_main_template(), $context );
|
||||
} catch ( \Exception $e ) {
|
||||
if ( Utils::is_elementor_debug() ) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function get_templates_contents() {
|
||||
return array_map(
|
||||
fn ( $path ) => Utils::file_get_contents( $path ),
|
||||
$this->get_templates()
|
||||
);
|
||||
}
|
||||
|
||||
protected function get_main_template() {
|
||||
$templates = $this->get_templates();
|
||||
|
||||
if ( count( $templates ) > 1 ) {
|
||||
Utils::safe_throw( 'When having more than one template, you should override this method to return the main template.' );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ( $templates as $key => $path ) {
|
||||
// Returns first key in the array.
|
||||
return $key;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
abstract protected function get_templates(): array;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Render_Context {
|
||||
private static $context_stack = [];
|
||||
|
||||
public static function push( string $key, array $context ): void {
|
||||
if ( ! self::get( $key ) ) {
|
||||
self::$context_stack[ $key ] = [];
|
||||
}
|
||||
|
||||
self::$context_stack[ $key ][] = $context;
|
||||
}
|
||||
|
||||
public static function pop( string $key ): void {
|
||||
if ( isset( self::$context_stack[ $key ] ) && ! empty( self::$context_stack[ $key ] ) ) {
|
||||
array_pop( self::$context_stack[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
public static function get( string $key ): array {
|
||||
if ( ! isset( self::$context_stack[ $key ] ) || empty( self::$context_stack[ $key ] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$last_key = array_key_last( self::$context_stack[ $key ] );
|
||||
return self::$context_stack[ $key ][ $last_key ];
|
||||
}
|
||||
|
||||
public static function clear(): void {
|
||||
self::$context_stack = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Base;
|
||||
|
||||
class Widget_Builder {
|
||||
protected $widget_type;
|
||||
protected $settings = [];
|
||||
protected $is_locked = false;
|
||||
protected $editor_settings = [];
|
||||
|
||||
public static function make( string $widget_type ) {
|
||||
return new self( $widget_type );
|
||||
}
|
||||
|
||||
private function __construct( string $widget_type ) {
|
||||
$this->widget_type = $widget_type;
|
||||
}
|
||||
|
||||
public function settings( array $settings ) {
|
||||
$this->settings = $settings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function is_locked( $is_locked ) {
|
||||
$this->is_locked = $is_locked;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function editor_settings( array $editor_settings ) {
|
||||
$this->editor_settings = $editor_settings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build() {
|
||||
$widget_data = [
|
||||
'elType' => 'widget',
|
||||
'widgetType' => $this->widget_type,
|
||||
'settings' => $this->settings,
|
||||
'isLocked' => $this->is_locked,
|
||||
'editor_settings' => $this->editor_settings,
|
||||
];
|
||||
|
||||
return $widget_data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Div_Block;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Html_Tag_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Div_Block extends Atomic_Element_Base {
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'is_container', true );
|
||||
}
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-div-block';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-div-block';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Div block', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-div-block';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
$tag_dependencies = Dependency_Manager::make( Dependency_Manager::RELATION_AND )
|
||||
->where( [
|
||||
'operator' => 'ne',
|
||||
'path' => [ 'link', 'destination' ],
|
||||
'nestedPath' => [ 'group' ],
|
||||
'value' => 'action',
|
||||
'newValue' => [
|
||||
'$$type' => 'string',
|
||||
'value' => 'button',
|
||||
],
|
||||
] )->where( [
|
||||
'operator' => 'not_exist',
|
||||
'path' => [ 'link', 'destination' ],
|
||||
'newValue' => [
|
||||
'$$type' => 'string',
|
||||
'value' => 'a',
|
||||
],
|
||||
] )->get();
|
||||
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'tag' => String_Prop_Type::make()
|
||||
->enum( [ 'div', 'header', 'section', 'article', 'aside', 'footer', 'a', 'button' ] )
|
||||
->default( 'div' )
|
||||
->set_dependencies( $tag_dependencies ),
|
||||
'link' => Link_Prop_Type::make(),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [
|
||||
Html_Tag_Control::bind_to( 'tag' )
|
||||
->set_options( [
|
||||
[
|
||||
'value' => 'div',
|
||||
'label' => 'Div',
|
||||
],
|
||||
[
|
||||
'value' => 'header',
|
||||
'label' => 'Header',
|
||||
],
|
||||
[
|
||||
'value' => 'section',
|
||||
'label' => 'Section',
|
||||
],
|
||||
[
|
||||
'value' => 'article',
|
||||
'label' => 'Article',
|
||||
],
|
||||
[
|
||||
'value' => 'aside',
|
||||
'label' => 'Aside',
|
||||
],
|
||||
[
|
||||
'value' => 'footer',
|
||||
'label' => 'Footer',
|
||||
],
|
||||
])
|
||||
->set_fallback_labels( [
|
||||
'a' => 'a (link)',
|
||||
] )
|
||||
->set_label( esc_html__( 'HTML Tag', 'elementor' ) ),
|
||||
Link_Control::bind_to( 'link' )
|
||||
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
|
||||
->set_label( __( 'Link', 'elementor' ) )
|
||||
->set_meta( [
|
||||
'topDivider' => true,
|
||||
] ),
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$display = String_Prop_Type::generate( 'block' );
|
||||
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'display', $display )
|
||||
->add_prop( 'padding', $this->get_base_padding() )
|
||||
->add_prop( 'min-width', $this->get_base_min_width() )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_base_padding(): array {
|
||||
return Size_Prop_Type::generate( [
|
||||
'size' => 10,
|
||||
'unit' => 'px',
|
||||
] );
|
||||
}
|
||||
|
||||
protected function get_base_min_width(): array {
|
||||
return Size_Prop_Type::generate( [
|
||||
'size' => 30,
|
||||
'unit' => 'px',
|
||||
] );
|
||||
}
|
||||
|
||||
protected function add_render_attributes() {
|
||||
parent::add_render_attributes();
|
||||
$settings = $this->get_atomic_settings();
|
||||
$base_style_class = $this->get_base_styles_dictionary()[ static::BASE_STYLE_KEY ];
|
||||
$initial_attributes = $this->define_initial_attributes();
|
||||
|
||||
$attributes = [
|
||||
'class' => [
|
||||
'e-con',
|
||||
'e-atomic-element',
|
||||
$base_style_class,
|
||||
...( $settings['classes'] ?? [] ),
|
||||
],
|
||||
];
|
||||
|
||||
if ( ! empty( $settings['_cssid'] ) ) {
|
||||
$attributes['id'] = esc_attr( $settings['_cssid'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $settings['link']['href'] ) ) {
|
||||
$link_attributes = $this->get_link_attributes( $settings['link'] );
|
||||
$attributes = array_merge( $attributes, $link_attributes );
|
||||
}
|
||||
|
||||
$this->add_render_attribute( '_wrapper', array_merge( $initial_attributes, $attributes ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Flexbox;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\PropDependencies\Manager as Dependency_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Definition;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Variant;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Section;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Html_Tag_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Link_Control;
|
||||
use Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\Components\PropTypes\Overridable_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Flexbox extends Atomic_Element_Base {
|
||||
const BASE_STYLE_KEY = 'base';
|
||||
|
||||
public function __construct( $data = [], $args = null ) {
|
||||
parent::__construct( $data, $args );
|
||||
$this->meta( 'is_container', true );
|
||||
}
|
||||
|
||||
public static function get_type() {
|
||||
return 'e-flexbox';
|
||||
}
|
||||
|
||||
public static function get_element_type(): string {
|
||||
return 'e-flexbox';
|
||||
}
|
||||
|
||||
public function get_title() {
|
||||
return esc_html__( 'Flexbox', 'elementor' );
|
||||
}
|
||||
|
||||
public function get_keywords() {
|
||||
return [ 'ato', 'atom', 'atoms', 'atomic' ];
|
||||
}
|
||||
|
||||
public function get_icon() {
|
||||
return 'eicon-flexbox';
|
||||
}
|
||||
|
||||
protected static function define_props_schema(): array {
|
||||
$tag_dependencies = Dependency_Manager::make( Dependency_Manager::RELATION_AND )
|
||||
->where( [
|
||||
'operator' => 'ne',
|
||||
'path' => [ 'link', 'destination' ],
|
||||
'nestedPath' => [ 'group' ],
|
||||
'value' => 'action',
|
||||
'newValue' => [
|
||||
'$$type' => 'string',
|
||||
'value' => 'button',
|
||||
],
|
||||
] )->where( [
|
||||
'operator' => 'not_exist',
|
||||
'path' => [ 'link', 'destination' ],
|
||||
'newValue' => [
|
||||
'$$type' => 'string',
|
||||
'value' => 'a',
|
||||
],
|
||||
] )->get();
|
||||
|
||||
return [
|
||||
'classes' => Classes_Prop_Type::make()
|
||||
->default( [] ),
|
||||
'tag' => String_Prop_Type::make()
|
||||
->enum( [ 'div', 'header', 'section', 'article', 'aside', 'footer', 'a', 'button' ] )
|
||||
->default( 'div' )
|
||||
->description( 'The HTML tag for the flexbox container. Could be div, header, section, article, aside, footer, or a (link).' )
|
||||
->set_dependencies( $tag_dependencies ),
|
||||
'link' => Link_Prop_Type::make(),
|
||||
'attributes' => Attributes_Prop_Type::make()->meta( Overridable_Prop_Type::ignore() ),
|
||||
];
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
protected function define_atomic_controls(): array {
|
||||
return [
|
||||
Section::make()
|
||||
->set_label( __( 'Settings', 'elementor' ) )
|
||||
->set_id( 'settings' )
|
||||
->set_items( [
|
||||
Html_Tag_Control::bind_to( 'tag' )
|
||||
->set_options( [
|
||||
[
|
||||
'value' => 'div',
|
||||
'label' => 'Div',
|
||||
],
|
||||
[
|
||||
'value' => 'header',
|
||||
'label' => 'Header',
|
||||
],
|
||||
[
|
||||
'value' => 'section',
|
||||
'label' => 'Section',
|
||||
],
|
||||
[
|
||||
'value' => 'article',
|
||||
'label' => 'Article',
|
||||
],
|
||||
[
|
||||
'value' => 'aside',
|
||||
'label' => 'Aside',
|
||||
],
|
||||
[
|
||||
'value' => 'footer',
|
||||
'label' => 'Footer',
|
||||
],
|
||||
])
|
||||
->set_label( esc_html__( 'HTML Tag', 'elementor' ) )
|
||||
->set_fallback_labels( [
|
||||
'a' => 'a (link)',
|
||||
] ),
|
||||
Link_Control::bind_to( 'link' )
|
||||
->set_placeholder( __( 'Type or paste your URL', 'elementor' ) )
|
||||
->set_label( __( 'Link', 'elementor' ) )
|
||||
->set_meta( [
|
||||
'topDivider' => true,
|
||||
] ),
|
||||
Text_Control::bind_to( '_cssid' )
|
||||
->set_label( __( 'ID', 'elementor' ) )
|
||||
->set_meta( $this->get_css_id_control_meta() ),
|
||||
] ),
|
||||
];
|
||||
}
|
||||
|
||||
protected function define_base_styles(): array {
|
||||
$display = String_Prop_Type::generate( 'flex' );
|
||||
$flex_direction = String_Prop_Type::generate( 'row' );
|
||||
|
||||
return [
|
||||
static::BASE_STYLE_KEY => Style_Definition::make()
|
||||
->add_variant(
|
||||
Style_Variant::make()
|
||||
->add_prop( 'display', $display )
|
||||
->add_prop( 'flex-direction', $flex_direction )
|
||||
->add_prop( 'padding', $this->get_base_padding() )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function get_base_padding(): array {
|
||||
return Size_Prop_Type::generate( [
|
||||
'size' => 10,
|
||||
'unit' => 'px',
|
||||
] );
|
||||
}
|
||||
|
||||
protected function add_render_attributes() {
|
||||
parent::add_render_attributes();
|
||||
$settings = $this->get_atomic_settings();
|
||||
$base_style_class = $this->get_base_styles_dictionary()[ static::BASE_STYLE_KEY ];
|
||||
$initial_attributes = $this->define_initial_attributes();
|
||||
|
||||
$attributes = [
|
||||
'class' => [
|
||||
'e-con',
|
||||
'e-atomic-element',
|
||||
$base_style_class,
|
||||
...( $settings['classes'] ?? [] ),
|
||||
],
|
||||
];
|
||||
|
||||
if ( ! empty( $settings['_cssid'] ) ) {
|
||||
$attributes['id'] = esc_attr( $settings['_cssid'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $settings['link']['href'] ) ) {
|
||||
$link_attributes = $this->get_link_attributes( $settings['link'] );
|
||||
$attributes = array_merge( $attributes, $link_attributes );
|
||||
}
|
||||
|
||||
$this->add_render_attribute( '_wrapper', array_merge( $initial_attributes, $attributes ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\Loader;
|
||||
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Frontend_Assets_Loader {
|
||||
const ALPINEJS_HANDLE = 'elementor-v2-alpinejs';
|
||||
const FRONTEND_HANDLERS_HANDLE = 'elementor-v2-frontend-handlers';
|
||||
const ATOMIC_WIDGETS_HANDLER = 'elementor-v2-widgets-frontend';
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function register_scripts() {
|
||||
$this->register_package_scripts();
|
||||
|
||||
do_action( 'elementor/atomic-widgets/frontend/loader/scripts/register', $this );
|
||||
}
|
||||
|
||||
private function register_package_scripts() {
|
||||
$assets_url = ELEMENTOR_ASSETS_URL;
|
||||
$min_suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min';
|
||||
|
||||
wp_register_script(
|
||||
self::FRONTEND_HANDLERS_HANDLE,
|
||||
"{$assets_url}js/packages/frontend-handlers/frontend-handlers{$min_suffix}.js",
|
||||
[],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
self::ALPINEJS_HANDLE,
|
||||
"{$assets_url}js/packages/alpinejs/alpinejs{$min_suffix}.js",
|
||||
[],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_register_script(
|
||||
self::ATOMIC_WIDGETS_HANDLER,
|
||||
"{$assets_url}js/atomic-widgets-frontend-handler{$min_suffix}.js",
|
||||
[ self::FRONTEND_HANDLERS_HANDLE, self::ALPINEJS_HANDLE ],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer;
|
||||
|
||||
use ElementorDeps\Twig\Error\LoaderError;
|
||||
use ElementorDeps\Twig\Loader\LoaderInterface;
|
||||
use ElementorDeps\Twig\Source;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Single_File_Loader implements LoaderInterface {
|
||||
private $templates = [];
|
||||
|
||||
private $validity_cache = [];
|
||||
|
||||
public function getSourceContext( string $name ): Source {
|
||||
$path = $this->get_template_path( $name );
|
||||
|
||||
return new Source(
|
||||
// This is safe to use because we're validating the file path inside `get_template_path`.
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
file_get_contents( $path ),
|
||||
$name,
|
||||
$path
|
||||
);
|
||||
}
|
||||
|
||||
public function getCacheKey( string $name ): string {
|
||||
return $this->get_template_path( $name );
|
||||
}
|
||||
|
||||
public function isFresh( string $name, int $time ): bool {
|
||||
$path = $this->get_template_path( $name );
|
||||
|
||||
return filemtime( $path ) < $time;
|
||||
}
|
||||
|
||||
public function exists( string $name ) {
|
||||
$path = $this->templates[ $name ] ?? null;
|
||||
|
||||
return $this->is_valid_file( $path );
|
||||
}
|
||||
|
||||
public function is_registered( string $name ): bool {
|
||||
return isset( $this->templates[ $name ] );
|
||||
}
|
||||
|
||||
public function register( string $name, string $path ): self {
|
||||
if ( ! $this->is_valid_file( $path ) ) {
|
||||
throw new LoaderError( esc_html( "Invalid template '{$name}': {$path}" ) );
|
||||
}
|
||||
|
||||
$this->templates[ $name ] = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function get_template_path( string $name ): string {
|
||||
$path = $this->templates[ $name ] ?? null;
|
||||
|
||||
if ( ! $this->is_valid_file( $path ) ) {
|
||||
throw new LoaderError( esc_html( "Invalid template '{$name}': {$path}" ) );
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function is_valid_file( $path ): bool {
|
||||
if ( ! $path ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isset( $this->validity_cache[ $path ] ) ) {
|
||||
return $this->validity_cache[ $path ];
|
||||
}
|
||||
|
||||
// Ref: https://github.com/twigphp/Twig/blob/8432946eeeca009d75fc7fc568f3c3f4650f5a0f/src/Loader/FilesystemLoader.php#L260
|
||||
if ( str_contains( $path, "\0" ) ) {
|
||||
throw new LoaderError( 'A template name cannot contain NULL bytes.' );
|
||||
}
|
||||
|
||||
$is_valid = is_file( $path ) && is_readable( $path );
|
||||
|
||||
$this->validity_cache[ $path ] = $is_valid;
|
||||
|
||||
return $is_valid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Elements\TemplateRenderer;
|
||||
|
||||
use Elementor\Utils;
|
||||
use ElementorDeps\Twig\Environment;
|
||||
use ElementorDeps\Twig\Runtime\EscaperRuntime;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Template_Renderer {
|
||||
private static ?self $instance = null;
|
||||
|
||||
private Single_File_Loader $loader;
|
||||
|
||||
private Environment $env;
|
||||
|
||||
private function __construct() {
|
||||
$this->loader = new Single_File_Loader();
|
||||
|
||||
$this->env = new Environment(
|
||||
$this->loader,
|
||||
[
|
||||
'debug' => Utils::is_elementor_debug(),
|
||||
'autoescape' => 'name',
|
||||
]
|
||||
);
|
||||
|
||||
$escaper = $this->env->getRuntime( EscaperRuntime::class );
|
||||
|
||||
$escaper->setEscaper( 'full_url', 'esc_url' );
|
||||
$escaper->setEscaper( 'html_tag', [ Utils::class, 'validate_html_tag' ] );
|
||||
}
|
||||
|
||||
public static function instance(): self {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public static function reset() {
|
||||
self::$instance = null;
|
||||
}
|
||||
|
||||
public function is_registered( string $name ): bool {
|
||||
return $this->loader->is_registered( $name );
|
||||
}
|
||||
|
||||
public function register( string $name, string $path ): self {
|
||||
$this->loader->register( $name, $path );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render( string $name, array $context = [] ): string {
|
||||
return $this->env->render( $name, $context );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\ImportExport;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Element_Base;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base;
|
||||
use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Interactions_Ids_Modifier;
|
||||
use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Interactions_Props_Modifier;
|
||||
use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Settings_Props_Modifier;
|
||||
use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Styles_Ids_Modifier;
|
||||
use Elementor\Modules\AtomicWidgets\ImportExport\Modifiers\Styles_Props_Modifier;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Import_Export_Props_Resolver;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Utils;
|
||||
use Elementor\Modules\Interactions\Schema\Interactions_Schema;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Atomic_Import_Export {
|
||||
public function register_hooks() {
|
||||
add_filter(
|
||||
'elementor/template_library/sources/local/import/elements',
|
||||
fn( $elements ) => $this->run( $elements, Import_Export_Props_Resolver::for_import() )
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'elementor/template_library/sources/cloud/import/elements',
|
||||
fn( $elements ) => $this->run( $elements, Import_Export_Props_Resolver::for_import() )
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'elementor/template_library/sources/local/export/elements',
|
||||
fn( $elements ) => $this->run( $elements, Import_Export_Props_Resolver::for_export() )
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'elementor/document/element/replace_id',
|
||||
fn( $element ) => $this->replace_styles_ids( $element )
|
||||
);
|
||||
}
|
||||
|
||||
private function run( $elements, Import_Export_Props_Resolver $props_resolver ) {
|
||||
if ( empty( $elements ) || ! is_array( $elements ) ) {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
return Plugin::$instance->db->iterate_data( $elements, function ( $element ) use ( $props_resolver ) {
|
||||
$element_instance = Plugin::$instance->elements_manager->create_element_instance( $element );
|
||||
|
||||
/** @var Atomic_Element_Base | Atomic_Widget_Base $element_instance */
|
||||
if ( ! Utils::is_atomic( $element_instance ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$interactions_schema = Interactions_Schema::get();
|
||||
$interaction_item_schema = ! empty( $interactions_schema['items'][0] ) ? $interactions_schema['items'][0]->get_shape() : [];
|
||||
|
||||
$runners = [
|
||||
Settings_Props_Modifier::make( $props_resolver, $element_instance::get_props_schema() ),
|
||||
Styles_Props_Modifier::make( $props_resolver, Style_Schema::get() ),
|
||||
Interactions_Props_Modifier::make( $props_resolver, $interaction_item_schema ),
|
||||
];
|
||||
|
||||
foreach ( $runners as $runner ) {
|
||||
$element = $runner->run( $element );
|
||||
}
|
||||
|
||||
return $element;
|
||||
} );
|
||||
}
|
||||
|
||||
private function replace_styles_ids( $element ) {
|
||||
if ( empty( $element ) || ! is_array( $element ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$element_instance = Plugin::$instance->elements_manager->create_element_instance( $element );
|
||||
|
||||
/** @var Atomic_Element_Base | Atomic_Widget_Base $element_instance */
|
||||
if ( ! Utils::is_atomic( $element_instance ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$element = Styles_Ids_Modifier::make()->run( $element );
|
||||
|
||||
return Interactions_Ids_Modifier::make()->run( $element );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Interactions_Ids_Modifier {
|
||||
|
||||
public static function make() {
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function run( array $element ) {
|
||||
if ( empty( $element['id'] ) || ! isset( $element['interactions'] ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$interactions = $element['interactions'];
|
||||
if ( is_string( $interactions ) ) {
|
||||
$decoded = json_decode( $interactions, true );
|
||||
if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $decoded ) ) {
|
||||
return $element;
|
||||
}
|
||||
$interactions = $decoded;
|
||||
}
|
||||
|
||||
if ( empty( $interactions['items'] ) || ! is_array( $interactions['items'] ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$existing_ids = [];
|
||||
$prefix = "e-{$element['id']}-";
|
||||
|
||||
foreach ( $interactions['items'] as $index => $item ) {
|
||||
if ( ! is_array( $item ) || ( $item['$$type'] ?? '' ) !== 'interaction-item' || ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_id = Utils::generate_id( $prefix, $existing_ids );
|
||||
$existing_ids[] = $new_id;
|
||||
|
||||
$interactions['items'][ $index ]['value']['interaction_id'] = [
|
||||
'$$type' => 'string',
|
||||
'value' => $new_id,
|
||||
];
|
||||
}
|
||||
|
||||
$element['interactions'] = $interactions;
|
||||
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Import_Export_Props_Resolver;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Interactions_Props_Modifier {
|
||||
private Import_Export_Props_Resolver $props_resolver;
|
||||
|
||||
private array $schema;
|
||||
|
||||
public function __construct( Import_Export_Props_Resolver $props_resolver, array $schema ) {
|
||||
$this->props_resolver = $props_resolver;
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public static function make( Import_Export_Props_Resolver $props_resolver, array $schema ) {
|
||||
return new self( $props_resolver, $schema );
|
||||
}
|
||||
|
||||
public function run( array $element ) {
|
||||
if ( ! isset( $element['interactions'] ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$interactions = $element['interactions'];
|
||||
if ( is_string( $interactions ) ) {
|
||||
$decoded = json_decode( $interactions, true );
|
||||
if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $decoded ) ) {
|
||||
return $element;
|
||||
}
|
||||
$interactions = $decoded;
|
||||
}
|
||||
|
||||
if ( empty( $interactions['items'] ) || ! is_array( $interactions['items'] ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
foreach ( $interactions['items'] as $index => $item ) {
|
||||
if ( ! is_array( $item ) || empty( $item['$$type'] ) || ! array_key_exists( 'value', $item ) || ! is_array( $item['value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$interactions['items'][ $index ]['value'] = $this->props_resolver->resolve(
|
||||
$this->schema,
|
||||
$item['value']
|
||||
);
|
||||
}
|
||||
|
||||
$element['interactions'] = $interactions;
|
||||
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Import_Export_Props_Resolver;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Settings_Props_Modifier {
|
||||
private Import_Export_Props_Resolver $props_resolver;
|
||||
|
||||
private array $schema;
|
||||
|
||||
public function __construct( Import_Export_Props_Resolver $props_resolver, array $schema ) {
|
||||
$this->props_resolver = $props_resolver;
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public static function make( Import_Export_Props_Resolver $props_resolver, array $schema ) {
|
||||
return new self( $props_resolver, $schema );
|
||||
}
|
||||
|
||||
public function run( array $element ) {
|
||||
if ( empty( $element['settings'] ) || ! is_array( $element['settings'] ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$element['settings'] = $this->props_resolver->resolve(
|
||||
$this->schema,
|
||||
$element['settings']
|
||||
);
|
||||
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Styles_Ids_Modifier {
|
||||
private Collection $old_to_new_ids;
|
||||
|
||||
public static function make() {
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function run( array $element ) {
|
||||
$this->old_to_new_ids = Collection::make();
|
||||
|
||||
$element = $this->replace_styles_ids( $element );
|
||||
$element = $this->replace_references( $element );
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
private function replace_styles_ids( array $element ) {
|
||||
if ( empty( $element['styles'] ) || empty( $element['id'] ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$styles = Collection::make( $element['styles'] )->map_with_keys( function ( $style, $id ) use ( $element ) {
|
||||
$style['id'] = $this->generate_id( $element['id'], $id );
|
||||
|
||||
return [ $style['id'] => $style ];
|
||||
} )->all();
|
||||
|
||||
$element['styles'] = $styles;
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
private function replace_references( array $element ) {
|
||||
if ( empty( $element['settings'] ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
$element['settings'] = Collection::make( $element['settings'] )->map( function ( $setting ) {
|
||||
if ( ! $setting || ! Classes_Prop_Type::make()->validate( $setting ) ) {
|
||||
return $setting;
|
||||
}
|
||||
|
||||
$setting['value'] = Collection::make( $setting['value'] )
|
||||
->map( fn( $style_id ) => $this->old_to_new_ids->get( $style_id ) ?? $style_id )
|
||||
->all();
|
||||
|
||||
return $setting;
|
||||
} )->all();
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
private function generate_id( $element_id, $old_id ): string {
|
||||
$id = Utils::generate_id( "e-{$element_id}-", $this->old_to_new_ids->values() );
|
||||
|
||||
$this->old_to_new_ids[ $old_id ] = $id;
|
||||
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\ImportExport\Modifiers;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Import_Export_Props_Resolver;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Styles_Props_Modifier {
|
||||
private Import_Export_Props_Resolver $props_resolver;
|
||||
|
||||
private array $schema;
|
||||
|
||||
public function __construct( Import_Export_Props_Resolver $props_resolver, array $schema ) {
|
||||
$this->props_resolver = $props_resolver;
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public static function make( Import_Export_Props_Resolver $props_resolver, array $schema ) {
|
||||
return new self( $props_resolver, $schema );
|
||||
}
|
||||
|
||||
public function run( array $element ) {
|
||||
if ( empty( $element['styles'] ) && ! is_array( $element['styles'] ) ) {
|
||||
return $element;
|
||||
}
|
||||
|
||||
foreach ( $element['styles'] as $style_key => $style ) {
|
||||
if ( empty( $style['variants'] ) || ! is_array( $style['variants'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $style['variants'] as $variant_key => $variant ) {
|
||||
if ( empty( $variant['props'] ) || ! is_array( $variant['props'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$element['styles'][ $style_key ]['variants'][ $variant_key ]['props'] = $this->props_resolver->resolve(
|
||||
$this->schema,
|
||||
$variant['props']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Library;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Atomic_Widgets_Library {
|
||||
public function register_hooks() {
|
||||
add_action( 'elementor/documents/register', fn() => $this->register_documents() );
|
||||
}
|
||||
|
||||
public function register_documents() {
|
||||
Plugin::$instance->documents
|
||||
->register_document_type( 'e-div-block', Div_Block::get_class_full_name() )
|
||||
->register_document_type( 'e-flexbox', Flexbox::get_class_full_name() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Library;
|
||||
|
||||
use Elementor\Modules\Library\Documents\Library_Document;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor Div_Block library document.
|
||||
*
|
||||
* Elementor div block library document handler class is responsible for
|
||||
* handling a document of a div block type.
|
||||
*
|
||||
* @since 3.29.0
|
||||
*/
|
||||
class Div_Block 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 'e-div-block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document title.
|
||||
*
|
||||
* Retrieve the document title.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return string Document title.
|
||||
*/
|
||||
public static function get_title() {
|
||||
return esc_html__( 'Div Block', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Return the div block document type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_type() {
|
||||
return 'e-div-block';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\AtomicWidgets\Library;
|
||||
|
||||
use Elementor\Modules\Library\Documents\Library_Document;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Elementor Flexbox library document.
|
||||
*
|
||||
* Elementor flexbox library document handler class is responsible for
|
||||
* handling a document of a flexbox type.
|
||||
*
|
||||
* @since 3.29.0
|
||||
*/
|
||||
class Flexbox 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 'e-flexbox';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document title.
|
||||
*
|
||||
* Retrieve the document title.
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return string Document title.
|
||||
*/
|
||||
public static function get_title() {
|
||||
return esc_html__( 'Flexbox', 'elementor' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Return the flexbox document type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_type() {
|
||||
return 'e-flexbox';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Logger;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple logger for Atomic Widgets that writes to wp-content/debug.log (if WP_DEBUG_LOG enabled
|
||||
* and WP_DEBUG_DISPLAY disabled) or optionally to Elementor's DB logger.
|
||||
* Never displays errors on screen.
|
||||
*/
|
||||
class Logger {
|
||||
|
||||
public static function info( string $message, array $context = [], bool $use_elementor_logger = false ): void {
|
||||
self::log_message( $message, $context, $use_elementor_logger, 'info' );
|
||||
}
|
||||
|
||||
public static function warning( string $message, array $context = [], bool $use_elementor_logger = false ): void {
|
||||
self::log_message( $message, $context, $use_elementor_logger, 'warning' );
|
||||
}
|
||||
|
||||
public static function error( string $message, array $context = [], bool $use_elementor_logger = false ): void {
|
||||
self::log_message( $message, $context, $use_elementor_logger, 'error' );
|
||||
}
|
||||
|
||||
private static function log_message( string $message, array $context, bool $use_elementor_logger, string $level ): void {
|
||||
if ( $use_elementor_logger ) {
|
||||
self::log_to_elementor_db( $message, $context, $level );
|
||||
return;
|
||||
}
|
||||
|
||||
self::log_to_wp_debug_file( $message, $context, $level );
|
||||
}
|
||||
|
||||
private static function log_to_wp_debug_file( string $message, array $context, string $level ): void {
|
||||
if ( ! self::should_log_to_file() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$formatted_message = self::format_message( $message, $context, $level );
|
||||
error_log( $formatted_message );
|
||||
}
|
||||
|
||||
private static function log_to_elementor_db( string $message, array $context, string $level ): void {
|
||||
if ( ! isset( \Elementor\Plugin::$instance->logger ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$logger = \Elementor\Plugin::$instance->logger;
|
||||
|
||||
switch ( $level ) {
|
||||
case 'error':
|
||||
$logger->error( $message, $context );
|
||||
break;
|
||||
case 'warning':
|
||||
$logger->warning( $message, $context );
|
||||
break;
|
||||
default:
|
||||
$logger->info( $message, $context );
|
||||
break;
|
||||
}
|
||||
} catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Logger must not throw exceptions
|
||||
}
|
||||
}
|
||||
|
||||
private static function should_log_to_file(): bool {
|
||||
$debug_log_enabled = defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG;
|
||||
$debug_display_disabled = ! defined( 'WP_DEBUG_DISPLAY' ) || ! WP_DEBUG_DISPLAY;
|
||||
|
||||
return $debug_log_enabled && $debug_display_disabled;
|
||||
}
|
||||
|
||||
private static function format_message( string $message, array $context, string $level ): string {
|
||||
$level_prefix = strtoupper( $level );
|
||||
$formatted = "[Elementor Atomic Widgets] [{$level_prefix}] " . $message;
|
||||
|
||||
if ( ! empty( $context ) ) {
|
||||
$context_json = wp_json_encode( $context, JSON_UNESCAPED_SLASHES );
|
||||
|
||||
if ( false !== $context_json ) {
|
||||
$formatted .= ' | Context: ' . $context_json;
|
||||
}
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
}
|
||||
453
wp-content/plugins/elementor/modules/atomic-widgets/module.php
Normal file
453
wp-content/plugins/elementor/modules/atomic-widgets/module.php
Normal file
@@ -0,0 +1,453 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Core\Experiments\Manager as Experiments_Manager;
|
||||
use Elementor\Elements_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\DynamicTags\Dynamic_Tags_Module;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Youtube\Atomic_Youtube;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Div_Block\Div_Block;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Flexbox\Flexbox;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Heading\Atomic_Heading;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Image\Atomic_Image;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Paragraph\Atomic_Paragraph;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Button\Atomic_Button;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Divider\Atomic_Divider;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Svg\Atomic_Svg;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs\Atomic_Tabs;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Menu\Atomic_Tabs_Menu;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab\Atomic_Tab;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tabs_Content_Area\Atomic_Tabs_Content_Area;
|
||||
use Elementor\Modules\AtomicWidgets\ImportExport\Atomic_Import_Export;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Loader\Frontend_Assets_Loader;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Combine_Array_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Export\Image_Src_Export_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Export\Svg_Src_Export_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Image_Src_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Image_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Import\Image_Src_Import_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Import\Svg_Src_Import_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Svg_Src_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Import_Export_Plain_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Classes_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Date_Time_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Html_V2_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Html_V3_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Link_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Plain_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Color_Overlay_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Gradient_Overlay_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Color_Stop_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Multi_Props_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Position_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Shadow_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Size_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Stroke_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Image_Overlay_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Image_Overlay_Size_Scale_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Background_Overlay_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Filter_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Origin_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transition_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Rotate_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Skew_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Functions_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Move_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Flex_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Transform_Scale_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Attributes_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers_Registry;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Color_Overlay_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Gradient_Overlay_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Size_Scale_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Position_Offset_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Overlay_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Box_Shadow_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Border_Radius_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Border_Width_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Color_Stop_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Date_Time_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Backdrop_Filter_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Filters\Filter_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Gradient_Color_Stop_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V2_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Html_V3_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Layout_Direction_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Flex_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Link_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Image_Src_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Svg_Src_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Shadow_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Stroke_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Move_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Functions_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Origin_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Scale_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Transform_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Rotate_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Functions\Transform_Skew_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transition_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Atomic_Styles_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Base_Styles;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Size_Constants;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
|
||||
use Elementor\Modules\AtomicWidgets\Database\Atomic_Widgets_Database_Updater;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Tabs\Atomic_Tab_Content\Atomic_Tab_Content;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Atomic_Form;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Success_Message\Form_Success_Message;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Form\Form_Error_Message\Form_Error_Message;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypeMigrations\Migrations_Orchestrator;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Widgets_Manager;
|
||||
use Elementor\Modules\AtomicWidgets\Library\Atomic_Widgets_Library;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Settings\Query_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Styles\Perspective_Origin_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Query_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Transform\Perspective_Origin_Prop_Type;
|
||||
use Elementor\Modules\AtomicWidgets\Utils\Utils;
|
||||
use Elementor\Modules\AtomicWidgets\Elements\Atomic_Self_Hosted_Video\Atomic_Self_Hosted_Video;
|
||||
use Elementor\Modules\AtomicWidgets\PropsResolver\Transformers\Video_Src_Transformer;
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Video_Src_Prop_Type;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Module extends BaseModule {
|
||||
const EXPERIMENT_NAME = 'e_atomic_elements';
|
||||
const ENFORCE_CAPABILITIES_EXPERIMENT = 'atomic_widgets_should_enforce_capabilities';
|
||||
const EXPERIMENT_EDITOR_MCP = 'editor_mcp';
|
||||
|
||||
const PACKAGES = [
|
||||
'editor-canvas',
|
||||
'editor-controls', // TODO: Need to be registered and not enqueued.
|
||||
'editor-editing-panel',
|
||||
'editor-elements', // TODO: Need to be registered and not enqueued.
|
||||
'editor-props', // TODO: Need to be registered and not enqueued.
|
||||
'editor-styles', // TODO: Need to be registered and not enqueued.
|
||||
'editor-styles-repository',
|
||||
'editor-interactions',
|
||||
'editor-templates',
|
||||
];
|
||||
|
||||
public function get_name() {
|
||||
return 'atomic-widgets';
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
if ( ! self::is_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->register_experimental_features();
|
||||
$this->register_hooks();
|
||||
|
||||
add_filter( 'elementor/editor/v2/packages', fn ( $packages ) => $this->add_packages( $packages ) );
|
||||
add_filter( 'elementor/editor/localize_settings', fn ( $settings ) => $this->add_styles_schema( $settings ) );
|
||||
add_filter( 'elementor/editor/localize_settings', fn ( $settings ) => $this->add_supported_units( $settings ) );
|
||||
add_filter( 'elementor/widgets/register', fn ( Widgets_Manager $widgets_manager ) => $this->register_widgets( $widgets_manager ) );
|
||||
add_filter( 'elementor/usage/elements/element_title', fn ( $title, $type ) => $this->get_element_usage_name( $title, $type ), 10, 2 );
|
||||
|
||||
add_action( 'elementor/elements/elements_registered', fn ( $elements_manager ) => $this->register_elements( $elements_manager ) );
|
||||
add_action( 'elementor/editor/after_enqueue_scripts', fn () => $this->enqueue_scripts() );
|
||||
add_action( 'elementor/frontend/before_register_scripts', fn () => $this->register_frontend_scripts() );
|
||||
add_action( 'elementor/frontend/after_enqueue_styles', fn () => $this->add_inline_styles() );
|
||||
|
||||
add_action( 'elementor/atomic-widgets/settings/transformers/register', fn ( $transformers ) => $this->register_settings_transformers( $transformers ) );
|
||||
add_action( 'elementor/atomic-widgets/styles/transformers/register', fn ( $transformers ) => $this->register_styles_transformers( $transformers ) );
|
||||
add_action( 'elementor/atomic-widgets/import/transformers/register', fn ( $transformers ) => $this->register_import_transformers( $transformers ) );
|
||||
add_action( 'elementor/atomic-widgets/export/transformers/register', fn ( $transformers ) => $this->register_export_transformers( $transformers ) );
|
||||
add_action( 'elementor/editor/templates/panel/category', fn () => $this->render_panel_category_chip() );
|
||||
}
|
||||
|
||||
public static function get_experimental_data() {
|
||||
return [
|
||||
'name' => self::EXPERIMENT_NAME,
|
||||
'title' => esc_html__( 'Atomic Widgets', 'elementor' ),
|
||||
'description' => esc_html__( 'Enable atomic widgets.', 'elementor' ),
|
||||
'hidden' => true,
|
||||
'default' => Experiments_Manager::STATE_INACTIVE,
|
||||
'release_status' => Experiments_Manager::RELEASE_STATUS_BETA,
|
||||
'new_site' => [
|
||||
'default_active' => true,
|
||||
'minimum_installation_version' => '4.0.0',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function register_experimental_features() {
|
||||
Plugin::$instance->experiments->add_feature( [
|
||||
'name' => 'e_indications_popover',
|
||||
'title' => esc_html__( 'V4 Indications Popover', 'elementor' ),
|
||||
'description' => esc_html__( 'Enable V4 Indication Popovers', 'elementor' ),
|
||||
'hidden' => true,
|
||||
'default' => Experiments_Manager::STATE_INACTIVE,
|
||||
] );
|
||||
|
||||
Plugin::$instance->experiments->add_feature( [
|
||||
'name' => self::ENFORCE_CAPABILITIES_EXPERIMENT,
|
||||
'title' => esc_html__( 'Enforce atomic widgets capabilities', 'elementor' ),
|
||||
'description' => esc_html__( 'Enforce atomic widgets capabilities.', 'elementor' ),
|
||||
'hidden' => true,
|
||||
'default' => Experiments_Manager::STATE_ACTIVE,
|
||||
'release_status' => Experiments_Manager::RELEASE_STATUS_DEV,
|
||||
] );
|
||||
|
||||
Plugin::$instance->experiments->add_feature([
|
||||
'name' => self::EXPERIMENT_EDITOR_MCP,
|
||||
'title' => esc_html__( 'Editor MCP for atomic widgets', 'elementor' ),
|
||||
'description' => esc_html__( 'Editor MCP for atomic widgets.', 'elementor' ),
|
||||
'hidden' => true,
|
||||
'default' => Experiments_Manager::STATE_ACTIVE,
|
||||
'release_status' => Experiments_Manager::RELEASE_STATUS_DEV,
|
||||
]);
|
||||
|
||||
Plugin::$instance->experiments->add_feature([
|
||||
'name' => Migrations_Orchestrator::EXPERIMENT_BC_MIGRATIONS,
|
||||
'title' => esc_html__( 'Backward compatibility migrations', 'elementor' ),
|
||||
'description' => esc_html__( 'Enable automatic prop type migrations for atomic widgets', 'elementor' ),
|
||||
'hidden' => true,
|
||||
'default' => Experiments_Manager::STATE_ACTIVE,
|
||||
'release_status' => Experiments_Manager::RELEASE_STATUS_DEV,
|
||||
]);
|
||||
|
||||
// When a new feature affects settings or style schema, global class, interactions, variable, etc
|
||||
// anything in need of addressing migration for BC purposes, add it here.
|
||||
$migrations_affecting_features = [];
|
||||
|
||||
Migrations_Orchestrator::register_affecting_feature_flag_hooks( $migrations_affecting_features );
|
||||
}
|
||||
|
||||
private function register_hooks() {
|
||||
Dynamic_Tags_Module::instance()->register_hooks();
|
||||
Atomic_Styles_Manager::instance()->register_hooks();
|
||||
Migrations_Orchestrator::make()->register_hooks();
|
||||
( new Atomic_Widget_Styles() )->register_hooks();
|
||||
( new Atomic_Widget_Base_Styles() )->register_hooks();
|
||||
( new Atomic_Widgets_Library() )->register_hooks();
|
||||
( new Atomic_Import_Export() )->register_hooks();
|
||||
( new Atomic_Widgets_Database_Updater() )->register();
|
||||
}
|
||||
|
||||
private function add_packages( $packages ) {
|
||||
return array_merge( $packages, self::PACKAGES );
|
||||
}
|
||||
|
||||
private function add_styles_schema( $settings ) {
|
||||
if ( ! isset( $settings['atomic'] ) ) {
|
||||
$settings['atomic'] = [];
|
||||
}
|
||||
|
||||
$settings['atomic']['styles_schema'] = Style_Schema::get();
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function add_supported_units( $settings ) {
|
||||
$settings['supported_size_units'] = Size_Constants::all_supported_units();
|
||||
|
||||
$settings['size_units'] = Size_Constants::grouped_units();
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function register_widgets( Widgets_Manager $widgets_manager ) {
|
||||
$widgets_manager->register( new Atomic_Heading() );
|
||||
$widgets_manager->register( new Atomic_Image() );
|
||||
$widgets_manager->register( new Atomic_Paragraph() );
|
||||
$widgets_manager->register( new Atomic_Svg() );
|
||||
$widgets_manager->register( new Atomic_Button() );
|
||||
$widgets_manager->register( new Atomic_Youtube() );
|
||||
$widgets_manager->register( new Atomic_Divider() );
|
||||
$widgets_manager->register( new Atomic_Self_Hosted_Video() );
|
||||
}
|
||||
|
||||
private function register_elements( Elements_Manager $elements_manager ) {
|
||||
$elements_manager->register_element_type( new Div_Block() );
|
||||
$elements_manager->register_element_type( new Flexbox() );
|
||||
|
||||
$elements_manager->register_element_type( new Atomic_Tabs() );
|
||||
$elements_manager->register_element_type( new Atomic_Tabs_Menu() );
|
||||
$elements_manager->register_element_type( new Atomic_Tab() );
|
||||
$elements_manager->register_element_type( new Atomic_Tabs_Content_Area() );
|
||||
$elements_manager->register_element_type( new Atomic_Tab_Content() );
|
||||
|
||||
if ( \Elementor\Utils::has_pro() && Plugin::$instance->experiments->is_feature_active( 'e_pro_atomic_form' ) ) {
|
||||
$elements_manager->register_element_type( new Atomic_Form() );
|
||||
$elements_manager->register_element_type( new Form_Success_Message() );
|
||||
$elements_manager->register_element_type( new Form_Error_Message() );
|
||||
}
|
||||
}
|
||||
|
||||
private function register_settings_transformers( Transformers_Registry $transformers ) {
|
||||
$transformers->register_fallback( new Plain_Transformer() );
|
||||
$transformers->register( Classes_Prop_Type::get_key(), new Classes_Transformer() );
|
||||
$transformers->register( Image_Prop_Type::get_key(), new Image_Transformer() );
|
||||
$transformers->register( Image_Src_Prop_Type::get_key(), new Image_Src_Transformer() );
|
||||
$transformers->register( Svg_Src_Prop_Type::get_key(), new Svg_Src_Transformer() );
|
||||
$transformers->register( Video_Src_Prop_Type::get_key(), new Video_Src_Transformer() );
|
||||
$transformers->register( Link_Prop_Type::get_key(), new Link_Transformer() );
|
||||
$transformers->register( Query_Prop_Type::get_key(), new Query_Transformer() );
|
||||
$transformers->register( Attributes_Prop_Type::get_key(), new Attributes_Transformer() );
|
||||
$transformers->register( Date_Time_Prop_Type::get_key(), new Date_Time_Transformer() );
|
||||
$transformers->register( Html_V2_Prop_Type::get_key(), new Html_V2_Transformer() );
|
||||
$transformers->register( Html_V3_Prop_Type::get_key(), new Html_V3_Transformer() );
|
||||
}
|
||||
|
||||
private function register_styles_transformers( Transformers_Registry $transformers ) {
|
||||
$transformers->register_fallback( new Plain_Transformer() );
|
||||
|
||||
$this->register_basic_styles_transformers( $transformers );
|
||||
$this->register_background_styles_transformers( $transformers );
|
||||
$this->register_filter_styles_transformers( $transformers );
|
||||
$this->register_transform_styles_transformers( $transformers );
|
||||
$this->register_layout_styles_transformers( $transformers );
|
||||
}
|
||||
|
||||
private function register_basic_styles_transformers( Transformers_Registry $transformers ): void {
|
||||
$transformers->register( Size_Prop_Type::get_key(), new Size_Transformer() );
|
||||
$transformers->register( Box_Shadow_Prop_Type::get_key(), new Combine_Array_Transformer( ',' ) );
|
||||
$transformers->register( Shadow_Prop_Type::get_key(), new Shadow_Transformer() );
|
||||
$transformers->register( Flex_Prop_Type::get_key(), new Flex_Transformer() );
|
||||
$transformers->register( Stroke_Prop_Type::get_key(), new Stroke_Transformer() );
|
||||
$transformers->register( Image_Prop_Type::get_key(), new Image_Transformer() );
|
||||
$transformers->register( Image_Src_Prop_Type::get_key(), new Image_Src_Transformer() );
|
||||
}
|
||||
|
||||
private function register_background_styles_transformers( Transformers_Registry $transformers ): void {
|
||||
$transformers->register( Background_Image_Overlay_Prop_Type::get_key(), new Background_Image_Overlay_Transformer() );
|
||||
$transformers->register( Background_Image_Overlay_Size_Scale_Prop_Type::get_key(), new Background_Image_Overlay_Size_Scale_Transformer() );
|
||||
$transformers->register( Background_Image_Position_Offset_Prop_Type::get_key(), new Position_Transformer() );
|
||||
$transformers->register( Background_Color_Overlay_Prop_Type::get_key(), new Background_Color_Overlay_Transformer() );
|
||||
$transformers->register( Background_Overlay_Prop_Type::get_key(), new Background_Overlay_Transformer() );
|
||||
$transformers->register( Background_Prop_Type::get_key(), new Background_Transformer() );
|
||||
$transformers->register( Background_Gradient_Overlay_Prop_Type::get_key(), new Background_Gradient_Overlay_Transformer() );
|
||||
}
|
||||
|
||||
private function register_filter_styles_transformers( Transformers_Registry $transformers ): void {
|
||||
$transformers->register( Filter_Prop_Type::get_key(), new Filter_Transformer() );
|
||||
$transformers->register( Backdrop_Filter_Prop_Type::get_key(), new Filter_Transformer() );
|
||||
$transformers->register( Transition_Prop_Type::get_key(), new Transition_Transformer() );
|
||||
$transformers->register( Color_Stop_Prop_Type::get_key(), new Color_Stop_Transformer() );
|
||||
$transformers->register( Gradient_Color_Stop_Prop_Type::get_key(), new Combine_Array_Transformer( ',' ) );
|
||||
$transformers->register( Position_Prop_Type::get_key(), new Position_Transformer() );
|
||||
}
|
||||
|
||||
private function register_transform_styles_transformers( Transformers_Registry $transformers ): void {
|
||||
$transformers->register( Transform_Move_Prop_Type::get_key(), new Transform_Move_Transformer() );
|
||||
$transformers->register( Transform_Scale_Prop_Type::get_key(), new Transform_Scale_Transformer() );
|
||||
$transformers->register( Transform_Rotate_Prop_Type::get_key(), new Transform_Rotate_Transformer() );
|
||||
$transformers->register( Transform_Skew_Prop_Type::get_key(), new Transform_Skew_Transformer() );
|
||||
$transformers->register( Transform_Functions_Prop_Type::get_key(), new Transform_Functions_Transformer() );
|
||||
$transformers->register( Transform_Origin_Prop_Type::get_key(), new Transform_Origin_Transformer() );
|
||||
$transformers->register( Perspective_Origin_Prop_Type::get_key(), new Perspective_Origin_Transformer() );
|
||||
$transformers->register(
|
||||
Transform_Prop_Type::get_key(),
|
||||
new Multi_Props_Transformer(
|
||||
[ 'transform-functions', 'transform-origin', 'perspective', 'perspective-origin' ],
|
||||
fn( $_, $key ) => 'transform-functions' === $key ? 'transform' : $key
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function register_layout_styles_transformers( Transformers_Registry $transformers ): void {
|
||||
$transformers->register(
|
||||
Border_Radius_Prop_Type::get_key(),
|
||||
new Multi_Props_Transformer( [ 'start-start', 'start-end', 'end-start', 'end-end' ], fn ( $_, $key ) => "border-{$key}-radius" )
|
||||
);
|
||||
$transformers->register(
|
||||
Border_Width_Prop_Type::get_key(),
|
||||
new Multi_Props_Transformer( [ 'block-start', 'block-end', 'inline-start', 'inline-end' ], fn ( $_, $key ) => "border-{$key}-width" )
|
||||
);
|
||||
$transformers->register(
|
||||
Layout_Direction_Prop_Type::get_key(),
|
||||
new Multi_Props_Transformer( [ 'column', 'row' ], fn ( $prop_key, $key ) => "{$key}-{$prop_key}" )
|
||||
);
|
||||
$transformers->register(
|
||||
Dimensions_Prop_Type::get_key(),
|
||||
new Multi_Props_Transformer( [ 'block-start', 'block-end', 'inline-start', 'inline-end' ], fn ( $prop_key, $key ) => "{$prop_key}-{$key}" )
|
||||
);
|
||||
}
|
||||
|
||||
public function register_import_transformers( Transformers_Registry $transformers ) {
|
||||
$transformers->register_fallback( new Import_Export_Plain_Transformer() );
|
||||
|
||||
$transformers->register( Image_Src_Prop_Type::get_key(), new Image_Src_Import_Transformer() );
|
||||
$transformers->register( Svg_Src_Prop_Type::get_key(), new Svg_Src_Import_Transformer() );
|
||||
}
|
||||
|
||||
public function register_export_transformers( Transformers_Registry $transformers ) {
|
||||
$transformers->register_fallback( new Import_Export_Plain_Transformer() );
|
||||
|
||||
$transformers->register( Image_Src_Prop_Type::get_key(), new Image_Src_Export_Transformer() );
|
||||
$transformers->register( Svg_Src_Prop_Type::get_key(), new Svg_Src_Export_Transformer() );
|
||||
}
|
||||
|
||||
public static function is_active(): bool {
|
||||
return Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_NAME );
|
||||
}
|
||||
|
||||
private function get_element_usage_name( $title, $type ) {
|
||||
$element_instance = Plugin::$instance->elements_manager->get_element_types( $type );
|
||||
$widget_instance = Plugin::$instance->widgets_manager->get_widget_types( $type );
|
||||
|
||||
if ( Utils::is_atomic( $element_instance ) || Utils::is_atomic( $widget_instance ) ) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the module scripts.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function enqueue_scripts() {
|
||||
wp_enqueue_script(
|
||||
'elementor-atomic-widgets-editor',
|
||||
$this->get_js_assets_url( 'atomic-widgets-editor' ),
|
||||
[ 'elementor-editor' ],
|
||||
ELEMENTOR_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private function render_panel_category_chip() {
|
||||
?><# if ( 'v4-elements' === name ) { #>
|
||||
<span class="elementor-panel-heading-category-chip">
|
||||
<?php echo esc_html__( 'New', 'elementor' ); ?><i class="eicon-info"></i>
|
||||
<span class="e-promotion-react-wrapper" data-promotion="v4_chip"></span>
|
||||
</span>
|
||||
<# } #><?php
|
||||
}
|
||||
|
||||
private function register_frontend_scripts() {
|
||||
$loader = new Frontend_Assets_Loader();
|
||||
$loader->register_scripts();
|
||||
}
|
||||
|
||||
private function add_inline_styles() {
|
||||
$inline_css = implode( '', [
|
||||
'.e-heading-base a, .e-paragraph-base a { all: unset; cursor: pointer; }',
|
||||
'form[data-element_type="e-form"].form-state-success [data-element_type="e-form-success-message"],',
|
||||
'form[data-element_type="e-form"].form-state-error [data-element_type="e-form-error-message"]',
|
||||
'{ display: block; }',
|
||||
] );
|
||||
wp_add_inline_style( 'elementor-frontend', $inline_css );
|
||||
wp_add_inline_style( 'elementor-editor', $inline_css );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\OptIn;
|
||||
|
||||
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
|
||||
use Elementor\Core\Experiments\Manager as Experiments_Manager;
|
||||
use Elementor\Modules\GlobalClasses\Module as GlobalClassesModule;
|
||||
use Elementor\Modules\NestedElements\Module as NestedElementsModule;
|
||||
use Elementor\Modules\AtomicWidgets\Module as AtomicWidgetsModule;
|
||||
use Elementor\Modules\Variables\Module as VariablesModule;
|
||||
use Elementor\Modules\Components\Module as ComponentsModule;
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Opt_In {
|
||||
const EXPERIMENT_NAME = 'e_opt_in_v4';
|
||||
|
||||
const OPT_OUT_FEATURES = [
|
||||
self::EXPERIMENT_NAME,
|
||||
AtomicWidgetsModule::EXPERIMENT_NAME,
|
||||
GlobalClassesModule::NAME,
|
||||
VariablesModule::EXPERIMENT_NAME,
|
||||
ComponentsModule::EXPERIMENT_NAME,
|
||||
];
|
||||
|
||||
const OPT_IN_FEATURES = [
|
||||
self::EXPERIMENT_NAME,
|
||||
'container',
|
||||
NestedElementsModule::EXPERIMENT_NAME,
|
||||
AtomicWidgetsModule::EXPERIMENT_NAME,
|
||||
GlobalClassesModule::NAME,
|
||||
VariablesModule::EXPERIMENT_NAME,
|
||||
ComponentsModule::EXPERIMENT_NAME,
|
||||
];
|
||||
|
||||
public function init() {
|
||||
$this->register_feature();
|
||||
|
||||
add_action( 'elementor/ajax/register_actions', fn( Ajax $ajax ) => $this->add_ajax_actions( $ajax ) );
|
||||
add_action( 'rest_api_init', fn() => $this->register_routes() );
|
||||
}
|
||||
|
||||
private function register_feature() {
|
||||
Plugin::$instance->experiments->add_feature([
|
||||
'name' => self::EXPERIMENT_NAME,
|
||||
'title' => esc_html__( 'Editor V4', 'elementor' ),
|
||||
'description' => esc_html__( 'Enable Editor V4.', 'elementor' ),
|
||||
'hidden' => true,
|
||||
'default' => Experiments_Manager::STATE_INACTIVE,
|
||||
'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA,
|
||||
'new_site' => [
|
||||
'default_active' => true,
|
||||
'minimum_installation_version' => '4.0.0',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function opt_out_v4() {
|
||||
foreach ( self::OPT_OUT_FEATURES as $feature ) {
|
||||
$feature_key = Plugin::$instance->experiments->get_feature_option_key( $feature );
|
||||
update_option( $feature_key, Experiments_Manager::STATE_INACTIVE );
|
||||
}
|
||||
}
|
||||
|
||||
private function opt_in_v4() {
|
||||
foreach ( self::OPT_IN_FEATURES as $feature ) {
|
||||
$feature_key = Plugin::$instance->experiments->get_feature_option_key( $feature );
|
||||
update_option( $feature_key, Experiments_Manager::STATE_ACTIVE );
|
||||
}
|
||||
}
|
||||
|
||||
public function ajax_opt_out_v4() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
throw new \Exception( 'Permission denied' );
|
||||
}
|
||||
|
||||
$this->opt_out_v4();
|
||||
}
|
||||
|
||||
public function ajax_opt_in_v4() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
throw new \Exception( 'Permission denied' );
|
||||
}
|
||||
|
||||
$this->opt_in_v4();
|
||||
}
|
||||
|
||||
private function add_ajax_actions( Ajax $ajax ) {
|
||||
$ajax->register_ajax_action( 'editor_v4_opt_in', fn() => $this->ajax_opt_in_v4() );
|
||||
$ajax->register_ajax_action( 'editor_v4_opt_out', fn() => $this->ajax_opt_out_v4() );
|
||||
}
|
||||
|
||||
private function handle_rest_opt_in_v4() {
|
||||
$this->ajax_opt_in_v4();
|
||||
return new \WP_REST_Response( [
|
||||
'success' => true,
|
||||
], 200 );
|
||||
}
|
||||
|
||||
private function register_routes() {
|
||||
register_rest_route( 'elementor/v1', '/operations/opt-in-v4', [
|
||||
'methods' => 'POST',
|
||||
'callback' => fn() => $this->handle_rest_opt_in_v4(),
|
||||
'permission_callback' => fn() => current_user_can( 'manage_options' ),
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Parsers;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
|
||||
use Elementor\Core\Utils\Api\Parse_Result;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Props_Parser {
|
||||
|
||||
private array $schema;
|
||||
|
||||
public function __construct( array $schema ) {
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public static function make( array $schema ): self {
|
||||
return new static( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* The key of each item represents the prop name (should match the schema),
|
||||
* and the value is the prop value to validate
|
||||
*/
|
||||
public function validate( array $props ): Parse_Result {
|
||||
$result = Parse_Result::make();
|
||||
|
||||
$validated = [];
|
||||
|
||||
foreach ( $this->schema as $key => $prop_type ) {
|
||||
if ( ! ( $prop_type instanceof Prop_Type ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $props[ $key ] ?? null;
|
||||
|
||||
$is_valid = $prop_type->validate( $value ?? $prop_type->get_default() );
|
||||
|
||||
if ( ! $is_valid ) {
|
||||
$result->errors()->add( $key, 'invalid_value' );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! is_null( $value ) ) {
|
||||
$validated[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result->wrap( $validated );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* The key of each item represents the prop name (should match the schema),
|
||||
* and the value is the prop value to sanitize
|
||||
*/
|
||||
public function sanitize( array $props ): Parse_Result {
|
||||
$sanitized = [];
|
||||
|
||||
foreach ( $this->schema as $key => $prop_type ) {
|
||||
if ( ! isset( $props[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sanitized[ $key ] = $prop_type->sanitize( $props[ $key ] );
|
||||
}
|
||||
|
||||
return Parse_Result::make()->wrap( $sanitized );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $props
|
||||
* The key of each item represents the prop name (should match the schema),
|
||||
* and the value is the prop value to parse
|
||||
*/
|
||||
public function parse( array $props ): Parse_Result {
|
||||
$validate_result = $this->validate( $props );
|
||||
|
||||
$sanitize_result = $this->sanitize( $validate_result->unwrap() );
|
||||
|
||||
$sanitize_result->errors()->merge( $validate_result->errors() );
|
||||
|
||||
return $sanitize_result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\Parsers;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\OptIn\Opt_In;
|
||||
use Elementor\Plugin;
|
||||
use Elementor\Utils;
|
||||
use Elementor\Core\Utils\Api\Parse_Result;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_States;
|
||||
|
||||
class Style_Parser {
|
||||
const VALID_TYPES = [
|
||||
'class',
|
||||
];
|
||||
|
||||
|
||||
private array $schema;
|
||||
|
||||
public function __construct( array $schema ) {
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
public static function make( array $schema ): self {
|
||||
return new static( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $style
|
||||
* the style object to validate
|
||||
*/
|
||||
private function validate( array $style ): Parse_Result {
|
||||
$validated_style = $style;
|
||||
$result = Parse_Result::make();
|
||||
|
||||
if ( ! isset( $style['id'] ) || ! is_string( $style['id'] ) ) {
|
||||
$result->errors()->add( 'id', 'missing_or_invalid' );
|
||||
}
|
||||
|
||||
if ( ! isset( $style['type'] ) || ! in_array( $style['type'], self::VALID_TYPES, true ) ) {
|
||||
$result->errors()->add( 'type', 'missing_or_invalid' );
|
||||
}
|
||||
|
||||
if ( ! isset( $style['label'] ) || ! is_string( $style['label'] ) ) {
|
||||
$result->errors()->add( 'label', 'missing_or_invalid' );
|
||||
} elseif ( Plugin::$instance->experiments->is_feature_active( Opt_In::EXPERIMENT_NAME ) ) {
|
||||
$label_validation = $this->validate_style_label( $style['label'] );
|
||||
|
||||
if ( ! $label_validation['is_valid'] ) {
|
||||
$result->errors()->add( 'label', $label_validation['error_message'] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $style['variants'] ) || ! is_array( $style['variants'] ) ) {
|
||||
$result->errors()->add( 'variants', 'missing_or_invalid' );
|
||||
|
||||
unset( $validated_style['variants'] );
|
||||
|
||||
return $result->wrap( $validated_style );
|
||||
}
|
||||
|
||||
$props_parser = Props_Parser::make( $this->schema );
|
||||
|
||||
foreach ( $style['variants'] as $variant_index => $variant ) {
|
||||
if ( ! isset( $variant['meta'] ) ) {
|
||||
$result->errors()->add( 'meta', 'missing' );
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$meta_result = $this->validate_meta( $variant['meta'] );
|
||||
$custom_css_result = $this->validate_custom_css( $variant );
|
||||
|
||||
$result->errors()->merge( $meta_result->errors(), 'meta' );
|
||||
$result->errors()->merge( $custom_css_result->errors(), 'custom_css' );
|
||||
|
||||
if ( $meta_result->is_valid() ) {
|
||||
$variant_result = $props_parser->validate( $variant['props'] );
|
||||
|
||||
$result->errors()->merge( $variant_result->errors(), "variants[$variant_index]" );
|
||||
|
||||
$validated_style['variants'][ $variant_index ]['props'] = $variant_result->unwrap();
|
||||
} else {
|
||||
unset( $validated_style['variants'][ $variant_index ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $result->wrap( $validated_style );
|
||||
}
|
||||
|
||||
private function validate_style_label( string $label ): array {
|
||||
$label = strtolower( $label );
|
||||
|
||||
$reserved_class_names = [ 'container' ];
|
||||
|
||||
if ( strlen( $label ) > 50 ) {
|
||||
return [
|
||||
'is_valid' => false,
|
||||
'error_message' => 'class_name_too_long',
|
||||
];
|
||||
}
|
||||
|
||||
if ( strlen( $label ) < 2 ) {
|
||||
return [
|
||||
'is_valid' => false,
|
||||
'error_message' => 'class_name_too_short',
|
||||
];
|
||||
}
|
||||
|
||||
if ( in_array( $label, $reserved_class_names, true ) ) {
|
||||
return [
|
||||
'is_valid' => false,
|
||||
'error_message' => 'reserved_class_name',
|
||||
];
|
||||
}
|
||||
|
||||
$regexes = [
|
||||
[
|
||||
'pattern' => '/^(|[^0-9].*)$/',
|
||||
'message' => 'class_name_starts_with_digit',
|
||||
],
|
||||
[
|
||||
'pattern' => '/^\S*$/',
|
||||
'message' => 'class_name_contains_spaces',
|
||||
],
|
||||
[
|
||||
'pattern' => '/^(|[a-zA-Z0-9_-]+)$/',
|
||||
'message' => 'class_name_invalid_chars',
|
||||
],
|
||||
[
|
||||
'pattern' => '/^(?!--).*/',
|
||||
'message' => 'class_name_double_hyphen',
|
||||
],
|
||||
[
|
||||
'pattern' => '/^(?!-[0-9])/',
|
||||
'message' => 'class_name_starts_with_hyphen_digit',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ( $regexes as $rule ) {
|
||||
if ( ! preg_match( $rule['pattern'], $label ) ) {
|
||||
return [
|
||||
'is_valid' => false,
|
||||
'error_message' => $rule['message'],
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
'is_valid' => true,
|
||||
'error_message' => null,
|
||||
];
|
||||
}
|
||||
|
||||
private function validate_meta( $meta ): Parse_Result {
|
||||
$result = Parse_Result::make();
|
||||
|
||||
if ( ! is_array( $meta ) ) {
|
||||
$result->errors()->add( 'meta', 'invalid_type' );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( 'state', $meta ) || ! Style_States::is_valid_state( $meta['state'] ) ) {
|
||||
$result->errors()->add( 'state', 'missing_or_invalid_value' );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// TODO: Validate breakpoint based on the existing breakpoints in the system [EDS-528]
|
||||
if ( ! isset( $meta['breakpoint'] ) || ! is_string( $meta['breakpoint'] ) ) {
|
||||
$result->errors()->add( 'breakpoint', 'missing_or_invalid_value' );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function validate_custom_css( array $variant ): Parse_Result {
|
||||
$result = Parse_Result::make();
|
||||
|
||||
if ( ! empty( $variant['custom_css']['raw'] ) && (
|
||||
! is_string( $variant['custom_css']['raw'] ) ||
|
||||
null === Utils::decode_string( $variant['custom_css']['raw'], null )
|
||||
)
|
||||
) {
|
||||
$result->errors()->add( 'custom_css', 'invalid_type' );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function sanitize_meta( $meta ) {
|
||||
if ( ! is_array( $meta ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( isset( $meta['breakpoint'] ) ) {
|
||||
$meta['breakpoint'] = sanitize_key( $meta['breakpoint'] );
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function sanitize_custom_css( array $variant ) {
|
||||
if ( empty( $variant['custom_css']['raw'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$custom_css = Utils::decode_string( $variant['custom_css']['raw'] );
|
||||
$custom_css = sanitize_textarea_field( $custom_css );
|
||||
$custom_css = [ 'raw' => Utils::encode_string( $custom_css ) ];
|
||||
|
||||
return empty( $custom_css['raw'] ) ? null : $custom_css;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $style
|
||||
* the style object to sanitize
|
||||
*/
|
||||
private function sanitize( array $style ): Parse_Result {
|
||||
$props_parser = Props_Parser::make( $this->schema );
|
||||
|
||||
if ( isset( $style['label'] ) ) {
|
||||
$style['label'] = sanitize_text_field( $style['label'] );
|
||||
}
|
||||
|
||||
if ( isset( $style['id'] ) ) {
|
||||
$style['id'] = sanitize_key( $style['id'] );
|
||||
}
|
||||
|
||||
if ( isset( $style['sync_to_v3'] ) ) {
|
||||
$style['sync_to_v3'] = (bool) $style['sync_to_v3'];
|
||||
}
|
||||
|
||||
if ( ! empty( $style['variants'] ) ) {
|
||||
foreach ( $style['variants'] as $variant_index => $variant ) {
|
||||
$style['variants'][ $variant_index ]['props'] = $props_parser->sanitize( $variant['props'] )->unwrap();
|
||||
$style['variants'][ $variant_index ]['meta'] = $this->sanitize_meta( $variant['meta'] );
|
||||
$style['variants'][ $variant_index ]['custom_css'] = $this->sanitize_custom_css( $variant );
|
||||
}
|
||||
}
|
||||
|
||||
return Parse_Result::make()->wrap( $style );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $style
|
||||
* the style object to parse
|
||||
*/
|
||||
public function parse( array $style ): Parse_Result {
|
||||
$validate_result = $this->validate( $style );
|
||||
|
||||
$sanitize_result = $this->sanitize( $validate_result->unwrap() );
|
||||
|
||||
$sanitize_result->errors()->merge( $validate_result->errors() );
|
||||
|
||||
return $sanitize_result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\AtomicWidgets\PropDependencies;
|
||||
|
||||
use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
|
||||
use Elementor\Utils;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Manager {
|
||||
|
||||
const RELATION_OR = 'or';
|
||||
const RELATION_AND = 'and';
|
||||
|
||||
const OPERATORS = [
|
||||
'lt',
|
||||
'lte',
|
||||
'eq',
|
||||
'ne',
|
||||
'gte',
|
||||
'gt',
|
||||
'exists',
|
||||
'not_exist',
|
||||
'in',
|
||||
'nin',
|
||||
'contains',
|
||||
'ncontains',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var ?array{
|
||||
* relation: self::RELATION_OR|self::RELATION_AND,
|
||||
* terms: array{
|
||||
* operator: string,
|
||||
* path: array<string>,
|
||||
* value?: mixed,
|
||||
* newValue?: array,
|
||||
* effect?: 'hide'|'disable'
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
private ?array $dependencies;
|
||||
|
||||
public function __construct( string $relation = self::RELATION_OR ) {
|
||||
$this->new( $relation );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public static function make( string $relation = self::RELATION_OR ): self {
|
||||
return new self( $relation );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, Prop_Type> $props_schema
|
||||
* @return array<string, array<string>> Returns source prop path => array of dependent prop paths
|
||||
*/
|
||||
public static function get_source_to_dependents( array $props_schema ): array {
|
||||
$dependency_graph = self::build_dependency_graph( $props_schema );
|
||||
|
||||
if ( self::has_circular_dependencies( $dependency_graph ) ) {
|
||||
Utils::safe_throw( 'Circular prop dependencies detected' );
|
||||
}
|
||||
|
||||
return $dependency_graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $config array{
|
||||
* operator: string,
|
||||
* path: array<string>,
|
||||
* value?: mixed,
|
||||
* newValue?: array,
|
||||
* effect?: 'hide'|'disable'
|
||||
* }
|
||||
* @return self
|
||||
*/
|
||||
public function where( array $config, $new_value = null ): self {
|
||||
if ( isset( $config['terms'] ) ) {
|
||||
if ( empty( $this->dependencies ) ) {
|
||||
$this->new();
|
||||
}
|
||||
|
||||
$term = [
|
||||
'terms' => $config['terms'],
|
||||
'relation' => $config['relation'] ?? self::RELATION_OR,
|
||||
'newValue' => $new_value ?? null,
|
||||
'effect' => $config['effect'] ?? 'disable',
|
||||
];
|
||||
$this->dependencies['terms'][] = $term;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ( ! isset( $config['operator'] ) || ! isset( $config['path'] ) ) {
|
||||
Utils::safe_throw( 'Term missing mandatory configurations' );
|
||||
}
|
||||
|
||||
if ( ! in_array( $config['operator'], self::OPERATORS, true ) ) {
|
||||
Utils::safe_throw( "Invalid operator: {$config['operator']}." );
|
||||
}
|
||||
|
||||
$term = [
|
||||
'operator' => $config['operator'],
|
||||
'path' => $config['path'],
|
||||
'nestedPath' => $config['nestedPath'] ?? null,
|
||||
'value' => $config['value'] ?? null,
|
||||
'newValue' => $config['newValue'] ?? null,
|
||||
'effect' => $config['effect'] ?? 'disable',
|
||||
];
|
||||
|
||||
if ( empty( $this->dependencies ) ) {
|
||||
$this->new();
|
||||
}
|
||||
|
||||
$this->dependencies['terms'][] = $term;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function new( string $relation = self::RELATION_OR ): self {
|
||||
if ( ! in_array( $relation, [ self::RELATION_OR, self::RELATION_AND ], true ) ) {
|
||||
Utils::safe_throw( "Invalid relation: $relation. Must be one of: " . implode( ', ', [ self::RELATION_OR, self::RELATION_AND ] ) );
|
||||
}
|
||||
|
||||
$this->dependencies = [
|
||||
'relation' => $relation,
|
||||
'terms' => [],
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get(): ?array {
|
||||
return empty( $this->dependencies['terms'] ?? [] ) ? null : $this->dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, Prop_Type> $props_schema The props schema to analyze, where keys are prop names
|
||||
* @param ?array<string> $current_path The current property path being processed
|
||||
* @param ?array<string, array<string>> $dependency_graph The dependency graph to build
|
||||
*/
|
||||
private static function build_dependency_graph( array $props_schema, ?array $current_path = [], ?array $dependency_graph = [] ): array {
|
||||
foreach ( $props_schema as $prop_name => $prop_type ) {
|
||||
$dependency_graph = self::build_nested_prop_dependency_graph( $prop_name, $prop_type, $current_path, $dependency_graph );
|
||||
$dependencies = $prop_type->get_dependencies();
|
||||
|
||||
if ( ! $dependencies ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $dependencies['terms'] as $term ) {
|
||||
$dependency_graph = self::process_dependency_term( $term, $current_path, $prop_name, $dependency_graph );
|
||||
}
|
||||
}
|
||||
|
||||
return $dependency_graph;
|
||||
}
|
||||
|
||||
private static function build_nested_prop_dependency_graph( string $prop_name, Prop_Type $prop_type, array $current_path, array $dependency_graph ): array {
|
||||
$nested_prop_path = array_merge( $current_path, [ $prop_name ] );
|
||||
|
||||
switch ( $prop_type->get_type() ) {
|
||||
case 'object':
|
||||
foreach ( $prop_type->get_shape() as $nested_prop_name => $nested_prop_type ) {
|
||||
$dependency_graph = self::build_dependency_graph( [ $nested_prop_name => $nested_prop_type ], $nested_prop_path, $dependency_graph );
|
||||
}
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
$item_prop_type = $prop_type->get_item_type();
|
||||
$dependency_graph = self::build_dependency_graph( [ $prop_name => $item_prop_type ], $current_path, $dependency_graph );
|
||||
break;
|
||||
|
||||
case 'union':
|
||||
foreach ( $prop_type->get_prop_types() as $nested_prop_type ) {
|
||||
$dependency_graph = self::build_dependency_graph( [ $prop_name => $nested_prop_type ], $current_path, $dependency_graph );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $dependency_graph;
|
||||
}
|
||||
|
||||
private static function process_dependency_term( array $term, array $current_path, string $prop_name, array $dependency_graph ): array {
|
||||
if ( self::is_term_nested( $term ) ) {
|
||||
foreach ( $term['terms'] as $nested_term ) {
|
||||
$dependency_graph = self::process_dependency_term( $nested_term, $current_path, $prop_name, $dependency_graph );
|
||||
}
|
||||
|
||||
return $dependency_graph;
|
||||
}
|
||||
|
||||
if ( ! isset( $term['path'] ) || empty( $term['path'] ) ) {
|
||||
Utils::safe_throw( 'Invalid term path in dependency.' );
|
||||
}
|
||||
|
||||
$target_path = implode( '.', $term['path'] );
|
||||
$source = array_merge( $current_path, [ $prop_name ] );
|
||||
$source_path = implode( '.', $source );
|
||||
|
||||
if ( ! isset( $dependency_graph[ $target_path ] ) ) {
|
||||
$dependency_graph[ $target_path ] = [];
|
||||
}
|
||||
|
||||
if ( ! in_array( $source_path, $dependency_graph[ $target_path ] ) ) {
|
||||
$dependency_graph[ $target_path ][] = $source_path;
|
||||
}
|
||||
|
||||
return $dependency_graph;
|
||||
}
|
||||
|
||||
private static function has_circular_dependencies( array $dependency_graph ): bool {
|
||||
$visited_nodes = [];
|
||||
$current_path_stack = [];
|
||||
|
||||
foreach ( array_keys( $dependency_graph ) as $node ) {
|
||||
if ( isset( $visited_nodes[ $node ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( self::detect_cycle_from_node( $dependency_graph, $node, $visited_nodes, $current_path_stack ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function detect_cycle_from_node( array $dependency_graph, string $current_node, array &$visited_nodes, array &$current_path_stack ): bool {
|
||||
if ( isset( $current_path_stack[ $current_node ] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( isset( $visited_nodes[ $current_node ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$visited_nodes[ $current_node ] = true;
|
||||
$current_path_stack[ $current_node ] = true;
|
||||
|
||||
foreach ( $dependency_graph[ $current_node ] ?? [] as $dependent_node ) {
|
||||
$is_circular = self::detect_cycle_from_node( $dependency_graph, $dependent_node, $visited_nodes, $current_path_stack );
|
||||
|
||||
if ( $is_circular ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $current_path_stack[ $current_node ] );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function is_term_nested( $term ): bool {
|
||||
return isset( $term['terms'] ) && is_array( $term['terms'] );
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user