Initial commit: Atomaste website
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\GlobalClasses;
|
||||
|
||||
use Elementor\Core\Files\CSS\Post as Post_CSS;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Styles_Renderer;
|
||||
use Elementor\Plugin;
|
||||
|
||||
class Global_Classes_CSS {
|
||||
public function register_hooks() {
|
||||
add_action( 'elementor/css-file/post/parse', fn( Post_CSS $post ) => $this->inject_global_classes( $post ), 20 );
|
||||
|
||||
add_action( 'elementor/global_classes/create', fn() => $this->clear_kit_css_cache() );
|
||||
add_action( 'elementor/global_classes/update', fn() => $this->clear_kit_css_cache() );
|
||||
add_action( 'elementor/global_classes/delete', fn() => $this->clear_kit_css_cache() );
|
||||
add_action( 'elementor/global_classes/arrange', fn() => $this->clear_kit_css_cache() );
|
||||
}
|
||||
|
||||
private function inject_global_classes( Post_CSS $post ) {
|
||||
if ( ! Plugin::$instance->kits_manager->is_kit( $post->get_post_id() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$global_classes = Global_Classes_Repository::make()->all();
|
||||
|
||||
if ( $global_classes->get_items()->is_empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sorted_items = $global_classes
|
||||
->get_order()
|
||||
->map(
|
||||
fn( $id ) => $global_classes->get_items()->get( $id )
|
||||
);
|
||||
|
||||
$css = Styles_Renderer::make(
|
||||
Plugin::$instance->breakpoints->get_breakpoints_config()
|
||||
)->on_prop_transform( function( $key, $value ) use ( &$post ) {
|
||||
if ( 'font-family' !== $key ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post->add_font( $value );
|
||||
} )->render( $sorted_items->all() );
|
||||
|
||||
$post->get_stylesheet()->add_raw_css( $css );
|
||||
}
|
||||
|
||||
private function clear_kit_css_cache() {
|
||||
$kit_id = Plugin::$instance->kits_manager->get_active_id();
|
||||
|
||||
Post_CSS::create( $kit_id )->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace Elementor\Modules\GlobalClasses;
|
||||
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Global_Classes_Repository {
|
||||
const META_KEY = '_elementor_global_classes';
|
||||
|
||||
public static function make(): Global_Classes_Repository {
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function all() {
|
||||
$all = Plugin::$instance->kits_manager->get_active_kit()->get_json_meta( self::META_KEY );
|
||||
|
||||
return Global_Classes::make( $all['items'] ?? [], $all['order'] ?? [] );
|
||||
}
|
||||
|
||||
public function put( array $items, array $order ) {
|
||||
$current_value = $this->all()->get();
|
||||
|
||||
$updated_value = [
|
||||
'items' => $items,
|
||||
'order' => $order,
|
||||
];
|
||||
|
||||
// `update_metadata` fails for identical data, so we skip it.
|
||||
if ( $current_value === $updated_value ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = Plugin::$instance->kits_manager->get_active_kit()->update_json_meta( self::META_KEY, $updated_value );
|
||||
|
||||
if ( ! $value ) {
|
||||
throw new \Exception( 'Failed to update global classes' );
|
||||
}
|
||||
|
||||
do_action( 'elementor/global_classes/update' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\GlobalClasses;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Style_Schema;
|
||||
use Elementor\Modules\AtomicWidgets\Parsers\Style_Parser;
|
||||
use Elementor\Modules\GlobalClasses\Utils\Error_Builder;
|
||||
use Elementor\Modules\GlobalClasses\Utils\Response_Builder;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Global_Classes_REST_API {
|
||||
const API_NAMESPACE = 'elementor/v1';
|
||||
const API_BASE = 'global-classes';
|
||||
|
||||
private $repository = null;
|
||||
|
||||
public function register_hooks() {
|
||||
add_action( 'rest_api_init', fn() => $this->register_routes() );
|
||||
}
|
||||
|
||||
private function get_repository() {
|
||||
if ( ! $this->repository ) {
|
||||
$this->repository = new Global_Classes_Repository();
|
||||
}
|
||||
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Add sanitization when implemented on prop types [EDS-574]
|
||||
*/
|
||||
private function register_routes() {
|
||||
register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [
|
||||
[
|
||||
'methods' => 'GET',
|
||||
'callback' => fn() => $this->route_wrapper( fn() => $this->all() ),
|
||||
'permission_callback' => fn() => current_user_can( 'manage_options' ),
|
||||
],
|
||||
] );
|
||||
|
||||
register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [
|
||||
[
|
||||
'methods' => 'PUT',
|
||||
'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->put( $request ) ),
|
||||
'permission_callback' => fn() => current_user_can( 'manage_options' ),
|
||||
'args' => [
|
||||
'items' => [
|
||||
'required' => true,
|
||||
'type' => 'object',
|
||||
'additionalProperties' => false,
|
||||
'patternProperties' => [
|
||||
'^g-[a-z0-9]+$' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'type' => 'string',
|
||||
'pattern' => '^g-[a-z0-9]+$',
|
||||
'required' => true,
|
||||
],
|
||||
'variants' => [
|
||||
'type' => 'array',
|
||||
'required' => true,
|
||||
],
|
||||
'type' => [
|
||||
'type' => 'string',
|
||||
'enum' => [ 'class' ],
|
||||
'required' => true,
|
||||
],
|
||||
'label' => [
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'order' => [
|
||||
'required' => true,
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
'pattern' => '^g-[a-z0-9]+$',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
private function all() {
|
||||
$classes = $this->get_repository()->all();
|
||||
|
||||
return Response_Builder::make( (object) $classes->get_items()->all() )
|
||||
->set_meta( [ 'order' => $classes->get_order()->all() ] )
|
||||
->build();
|
||||
}
|
||||
|
||||
private function put( \WP_REST_Request $request ) {
|
||||
$items = $request->get_param( 'items' );
|
||||
|
||||
[$is_valid, $sanitized_items, $errors] = $this->sanitize_items( $items );
|
||||
|
||||
if ( ! $is_valid ) {
|
||||
return Error_Builder::make( 'invalid_items' )
|
||||
->set_status( 400 )
|
||||
->set_message( 'Invalid items: ' . join( ', ', array_keys( $errors ) ) )
|
||||
->build();
|
||||
}
|
||||
|
||||
$order = $request->get_param( 'order' );
|
||||
|
||||
if ( ! $this->is_valid_order( $order, $sanitized_items ) ) {
|
||||
return Error_Builder::make( 'invalid_order' )
|
||||
->set_status( 400 )
|
||||
->set_message( 'Invalid order' )
|
||||
->build();
|
||||
}
|
||||
|
||||
$this->get_repository()->put(
|
||||
$sanitized_items,
|
||||
$order
|
||||
);
|
||||
|
||||
return Response_Builder::make()->no_content()->build();
|
||||
}
|
||||
|
||||
private function sanitize_items( array $items ) {
|
||||
$errors = [];
|
||||
$sanitized_items = [];
|
||||
|
||||
foreach ( $items as $item_id => $item ) {
|
||||
[$is_item_valid, $sanitized_item, $item_errors] = Style_Parser::make( Style_Schema::get() )->parse( $item );
|
||||
|
||||
if ( ! $is_item_valid ) {
|
||||
$errors[ $item_id ] = $item_errors;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $item_id !== $sanitized_item['id'] ) {
|
||||
$errors[ $item_id ] = [ 'id' ];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sanitized_items[ $sanitized_item['id'] ] = $sanitized_item;
|
||||
}
|
||||
|
||||
$is_valid = count( $errors ) === 0;
|
||||
|
||||
return [ $is_valid, $sanitized_items, $errors ];
|
||||
}
|
||||
|
||||
private function is_valid_order( array $order, array $items ) {
|
||||
$existing_ids = array_keys( $items );
|
||||
|
||||
$excess_ids = Collection::make( $order )->diff( $existing_ids );
|
||||
$missing_ids = Collection::make( $existing_ids )->diff( $order );
|
||||
|
||||
$has_duplications = Collection::make( $order )->unique()->all() !== $order;
|
||||
|
||||
return (
|
||||
$excess_ids->is_empty() &&
|
||||
$missing_ids->is_empty() &&
|
||||
! $has_duplications
|
||||
);
|
||||
}
|
||||
|
||||
private function route_wrapper( callable $cb ) {
|
||||
try {
|
||||
$response = $cb();
|
||||
} catch ( \Exception $e ) {
|
||||
return Error_Builder::make( 'unexpected_error' )
|
||||
->set_message( __( 'Something went wrong', 'elementor' ) )
|
||||
->build();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\GlobalClasses;
|
||||
|
||||
use Elementor\Core\Utils\Collection;
|
||||
|
||||
class Global_Classes {
|
||||
private Collection $items;
|
||||
private Collection $order;
|
||||
|
||||
public static function make( array $items = [], array $order = [] ) {
|
||||
return new static( $items, $order );
|
||||
}
|
||||
|
||||
private function __construct( array $data = [], array $order = [] ) {
|
||||
$this->items = Collection::make( $data );
|
||||
$this->order = Collection::make( $order );
|
||||
}
|
||||
|
||||
public function get_items() {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function get_order() {
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
return [
|
||||
'items' => $this->get_items()->all(),
|
||||
'order' => $this->get_order()->all(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\GlobalClasses;
|
||||
|
||||
use Elementor\Core\Base\Module as BaseModule;
|
||||
use Elementor\Core\Experiments\Manager as Experiments_Manager;
|
||||
use Elementor\Core\Files\CSS\Post;
|
||||
use Elementor\Modules\AtomicWidgets\Module as Atomic_Widgets_Module;
|
||||
use Elementor\Modules\AtomicWidgets\Styles\Styles_Renderer;
|
||||
use Elementor\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
class Module extends BaseModule {
|
||||
const NAME = 'global_classes';
|
||||
|
||||
// TODO: Add global classes package
|
||||
const PACKAGES = [
|
||||
'editor-global-classes',
|
||||
];
|
||||
|
||||
public function get_name() {
|
||||
return 'global-classes';
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$is_feature_active = Plugin::$instance->experiments->is_feature_active( self::NAME );
|
||||
$is_atomic_widgets_active = Plugin::$instance->experiments->is_feature_active( Atomic_Widgets_Module::EXPERIMENT_NAME );
|
||||
|
||||
// TODO: When the `Atomic_Widgets` feature is not hidden, add it as a dependency
|
||||
if ( $is_feature_active && $is_atomic_widgets_active ) {
|
||||
add_filter( 'elementor/editor/v2/packages', fn( $packages ) => $this->add_packages( $packages ) );
|
||||
|
||||
( new Global_Classes_REST_API() )->register_hooks();
|
||||
( new Global_Classes_CSS() )->register_hooks();
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_experimental_data(): array {
|
||||
return [
|
||||
'name' => self::NAME,
|
||||
'title' => esc_html__( 'Global Classes', 'elementor' ),
|
||||
'description' => esc_html__( 'Enable global CSS classes.', 'elementor' ),
|
||||
'hidden' => true,
|
||||
'default' => Experiments_Manager::STATE_INACTIVE,
|
||||
'release_status' => Experiments_Manager::RELEASE_STATUS_ALPHA,
|
||||
];
|
||||
}
|
||||
|
||||
private function add_packages( $packages ) {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return $packages;
|
||||
}
|
||||
|
||||
return array_merge( $packages, self::PACKAGES );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\GlobalClasses\Utils;
|
||||
|
||||
class Error_Builder {
|
||||
private string $message;
|
||||
private int $status;
|
||||
private string $code;
|
||||
|
||||
private function __construct( $code, $status = 500 ) {
|
||||
$this->code = $code;
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public static function make( $code, $status = 500 ) {
|
||||
return new self( $code, $status );
|
||||
}
|
||||
|
||||
public function set_status( int $status ) {
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_message( string $message ) {
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function build() {
|
||||
return new \WP_Error( $this->code, $this->message, [ 'status' => $this->status ] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Elementor\Modules\GlobalClasses\Utils;
|
||||
|
||||
class Response_Builder {
|
||||
private $data;
|
||||
private int $status;
|
||||
private array $meta = [];
|
||||
private bool $empty = false;
|
||||
|
||||
const NO_CONTENT = 204;
|
||||
|
||||
private function __construct( $data, $status ) {
|
||||
$this->data = $data;
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public static function make( $data = null, $status = 200 ) {
|
||||
return new self( $data, $status );
|
||||
}
|
||||
|
||||
public function set_meta( array $meta ) {
|
||||
$this->meta = $meta;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function set_status( int $status ) {
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function no_content() {
|
||||
return $this->set_status( static::NO_CONTENT );
|
||||
}
|
||||
|
||||
public function build() {
|
||||
$res_data = static::NO_CONTENT === $this->status
|
||||
? null
|
||||
: [
|
||||
'data' => $this->data,
|
||||
'meta' => $this->meta,
|
||||
];
|
||||
|
||||
return new \WP_REST_Response( $res_data, $this->status );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user