Gestionnaire de fichiers - Editer - /home/wwgoat/public_html/blog/base.tar
Arrière
endpoint.php 0000644 00000021633 14720714745 0007115 0 ustar 00 <?php namespace Elementor\Data\Base; use Elementor\Data\Manager; use WP_REST_Server; abstract class Endpoint { const AVAILABLE_METHODS = [ WP_REST_Server::READABLE, WP_REST_Server::CREATABLE, WP_REST_Server::EDITABLE, WP_REST_Server::DELETABLE, WP_REST_Server::ALLMETHODS, ]; /** * Controller of current endpoint. * * @var \Elementor\Data\Base\Controller */ protected $controller; /** * Loaded sub endpoint(s). * * @var \Elementor\Data\Base\SubEndpoint[] */ private $sub_endpoints = []; /** * Get format suffix. * * Examples: * '{one_parameter_name}'. * '{one_parameter_name}/{two_parameter_name}/'. * '{one_parameter_name}/whatever/anything/{two_parameter_name}/' and so on for each endpoint or sub-endpoint. * * @return string current location will later be added automatically. */ public static function get_format() { return ''; } /** * Endpoint constructor. * * run `$this->>register()`. * * @param \Elementor\Data\Base\Controller $controller * * @throws \Exception */ public function __construct( $controller ) { if ( ! ( $controller instanceof Controller ) ) { throw new \Exception( 'Invalid controller.' ); } $this->controller = $controller; $this->register(); } /** * Get endpoint name. * * @return string */ abstract public function get_name(); /** * Get base route. * * Removing 'index' from endpoint. * * @return string */ public function get_base_route() { $endpoint_name = $this->get_name(); // TODO: Allow this only for internal routes. // TODO: Make difference between internal and external endpoints. if ( 'index' === $endpoint_name ) { $endpoint_name = ''; } return '/' . $this->controller->get_rest_base() . '/' . $endpoint_name; } /** * Register the endpoint. * * By default: register get items route. * * @throws \Exception */ protected function register() { $this->register_items_route(); } /** * Register sub endpoint. * * @param string $route * @param string $endpoint_class * * @return \Elementor\Data\Base\SubEndpoint * @throws \Exception */ protected function register_sub_endpoint( $route, $endpoint_class ) { $endpoint_instance = new $endpoint_class( $route, $this ); if ( ! ( $endpoint_instance instanceof SubEndpoint ) ) { throw new \Exception( 'Invalid endpoint instance.' ); } $endpoint_route = $route . '/' . $endpoint_instance->get_name(); $this->sub_endpoints[ $endpoint_route ] = $endpoint_instance; $component_name = $endpoint_instance->controller->get_rest_base(); $parent_instance = $endpoint_instance->get_parent(); $parent_name = $endpoint_instance->get_name(); $parent_format_suffix = $parent_instance::get_format(); $current_format_suffix = $endpoint_instance::get_format(); $command = $component_name . '/' . $parent_name; $format = $component_name . '/' . $parent_format_suffix . '/' . $parent_name . '/' . $current_format_suffix; Manager::instance()->register_endpoint_format( $command, $format ); return $endpoint_instance; } /** * Base callback. * * All reset requests from the client should pass this function. * * @param string $methods * @param \WP_REST_Request $request * @param bool $is_multi * * @return mixed|\WP_Error|\WP_HTTP_Response|\WP_REST_Response * @throws \Exception */ public function base_callback( $methods, $request, $is_multi = false ) { // TODO: Find better solution. $json_params = $request->get_json_params(); if ( $json_params ) { $request->set_body_params( $json_params ); } // TODO: Handle permission callback. switch ( $methods ) { case WP_REST_Server::READABLE: $result = $is_multi ? $this->get_items( $request ) : $this->get_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::CREATABLE: $result = $is_multi ? $this->create_items( $request ) : $this->create_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::EDITABLE: $result = $is_multi ? $this->update_items( $request ) : $this->update_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::DELETABLE: $result = $is_multi ? $this->delete_items( $request ) : $this->delete_item( $request->get_param( 'id' ), $request ); break; default: throw new \Exception( 'Invalid method.' ); } return rest_ensure_response( $result ); } /** * Retrieves a collection of items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { return $this->controller->get_items( $request ); } /** * Retrieves one item from the collection. * * @param string $id * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function get_item( $id, $request ) { return $this->controller->get_item( $request ); } /** * Get permission callback. * * By default get permission callback from the controller. * * @param \WP_REST_Request $request Full data about the request. * * @return boolean */ public function get_permission_callback( $request ) { return $this->controller->get_permission_callback( $request ); } /** * Creates one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function create_item( $id, $request ) { return $this->controller->create_item( $request ); } /** * Creates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function create_items( $request ) { return $this->controller->create_items( $request ); } /** * Updates one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_item( $id, $request ) { return $this->controller->update_item( $request ); } /** * Updates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_items( $request ) { return $this->controller->update_items( $request ); } /** * Delete one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function delete_item( $id, $request ) { return $this->controller->delete_item( $request ); } /** * Delete multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function delete_items( $request ) { return $this->controller->delete_items( $request ); } /** * Register item route. * * @param array $args * @param string $route * @param string $methods * * @throws \Exception */ public function register_item_route( $methods = WP_REST_Server::READABLE, $args = [], $route = '/' ) { $args = array_merge( [ 'id' => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', ], ], $args ); if ( isset( $args['id'] ) && $args['id'] ) { $route .= '(?P<id>[\w]+)/'; } $this->register_route( $route, $methods, function ( $request ) use ( $methods ) { return $this->base_callback( $methods, $request ); }, $args ); } /** * Register items route. * * @param string $methods * * @throws \Exception */ public function register_items_route( $methods = WP_REST_Server::READABLE ) { $this->register_route( '', $methods, function ( $request ) use ( $methods ) { return $this->base_callback( $methods, $request, true ); } ); } /** * Register route. * * @param string $route * @param string $methods * @param null $callback * @param array $args * * @return bool * @throws \Exception */ public function register_route( $route = '', $methods = WP_REST_Server::READABLE, $callback = null, $args = [] ) { if ( ! in_array( $methods, self::AVAILABLE_METHODS, true ) ) { throw new \Exception( 'Invalid method.' ); } $route = $this->get_base_route() . $route; return register_rest_route( $this->controller->get_namespace(), $route, [ [ 'args' => $args, 'methods' => $methods, 'callback' => $callback, 'permission_callback' => function ( $request ) { return $this->get_permission_callback( $request ); }, ], ] ); } } processor.php 0000644 00000000674 14720714745 0007316 0 ustar 00 <?php namespace Elementor\Data\Base; abstract class Processor { /** * Controller. * * @var \Elementor\Data\Base\Controller */ private $controller; /** * Processor constructor. * * @param \Elementor\Data\Base\Controller $controller */ public function __construct( $controller ) { $this->controller = $controller; } /** * Get processor command. * * @return string */ abstract public function get_command(); } processor/after.php 0000644 00000000724 14720714745 0010413 0 ustar 00 <?php namespace Elementor\Data\Base\Processor; use Elementor\Data\Base\Processor; abstract class After extends Processor { /** * Get conditions for running processor. * * @param array $args * @param mixed $result * * @return bool */ public function get_conditions( $args, $result ) { return true; } /** * Apply processor. * * @param $args * @param $result * * @return mixed */ abstract public function apply( $args, $result ); } processor/before.php 0000644 00000000631 14720714745 0010551 0 ustar 00 <?php namespace Elementor\Data\Base\Processor; use Elementor\Data\Base\Processor; abstract class Before extends Processor { /** * Get conditions for running processor. * @param array $args * * @return bool */ public function get_conditions( $args ) { return true; } /** * Apply processor. * * @param array $args * * @return mixed */ abstract public function apply( $args ); } controller.php 0000644 00000020153 14720714745 0007454 0 ustar 00 <?php namespace Elementor\Data\Base; use Elementor\Data\Manager; use Elementor\Plugin; use WP_REST_Controller; use WP_REST_Server; abstract class Controller extends WP_REST_Controller { /** * Loaded endpoint(s). * * @var \Elementor\Data\Base\Endpoint[] */ public $endpoints = []; /** * Loaded processor(s). * * @var \Elementor\Data\Base\Processor[][] */ public $processors = []; /** * Controller constructor. * * Register endpoints on 'rest_api_init'. * */ public function __construct() { // TODO: Controllers and endpoints can have common interface. // TODO: Uncomment when native 3rd plugins uses V2. //$this->deprecated(); $this->namespace = Manager::ROOT_NAMESPACE . '/v' . Manager::VERSION; $this->rest_base = Manager::REST_BASE . $this->get_name(); add_action( 'rest_api_init', function () { $this->register(); // Because 'register' is protected. } ); /** * Since all actions were removed for custom internal REST server. * Re-add the actions. */ add_action( 'elementor_rest_api_before_init', function () { add_action( 'rest_api_init', function() { $this->register(); } ); } ); } /** * Get controller name. * * @return string */ abstract public function get_name(); /** * Get controller namespace. * * @return string */ public function get_namespace() { return $this->namespace; } /** * Get controller reset base. * * @return string */ public function get_rest_base() { return $this->rest_base; } /** * Get controller route. * * @return string */ public function get_controller_route() { return $this->get_namespace() . '/' . $this->get_rest_base(); } /** * Retrieves the index for a controller. * * @return \WP_REST_Response|\WP_Error */ public function get_controller_index() { $server = rest_get_server(); $routes = $server->get_routes(); $endpoints = array_intersect_key( $server->get_routes(), $routes ); $controller_route = $this->get_controller_route(); array_walk( $endpoints, function ( &$item, $endpoint ) use ( &$endpoints, $controller_route ) { if ( ! strstr( $endpoint, $controller_route ) ) { unset( $endpoints[ $endpoint ] ); } } ); $data = [ 'namespace' => $this->get_namespace(), 'controller' => $controller_route, 'routes' => $server->get_data_for_routes( $endpoints ), ]; $response = rest_ensure_response( $data ); // Link to the root index. $response->add_link( 'up', rest_url( '/' ) ); return $response; } /** * Get processors. * * @param string $command * * @return \Elementor\Data\Base\Processor[] */ public function get_processors( $command ) { $result = []; if ( isset( $this->processors[ $command ] ) ) { $result = $this->processors[ $command ]; } return $result; } public function get_items( $request ) { return $this->get_controller_index(); } /** * Creates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function create_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Updates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function update_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Delete multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ public function delete_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Register endpoints. */ abstract public function register_endpoints(); /** * Register processors. */ public function register_processors() { } /** * Register internal endpoints. */ protected function register_internal_endpoints() { register_rest_route( $this->get_namespace(), '/' . $this->get_rest_base(), [ [ 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'args' => [], 'permission_callback' => function ( $request ) { return $this->get_permission_callback( $request ); }, ], ] ); } /** * Register endpoint. * * @param string $endpoint_class * * @return \Elementor\Data\Base\Endpoint */ protected function register_endpoint( $endpoint_class ) { $endpoint_instance = new $endpoint_class( $this ); // TODO: Validate instance like in register_sub_endpoint(). $endpoint_route = $this->get_name() . '/' . $endpoint_instance->get_name(); $this->endpoints[ $endpoint_route ] = $endpoint_instance; $command = $endpoint_route; $format = $endpoint_instance::get_format(); if ( $command ) { $format = $command . '/' . $format; } else { $format = $format . $command; } // `$e.data.registerFormat()`. Manager::instance()->register_endpoint_format( $command, $format ); return $endpoint_instance; } /** * Register a processor. * * That will be later attached to the endpoint class. * * @param string $processor_class * * @return \Elementor\Data\Base\Processor $processor_instance */ protected function register_processor( $processor_class ) { $processor_instance = new $processor_class( $this ); // TODO: Validate processor instance. $command = $processor_instance->get_command(); if ( ! isset( $this->processors[ $command ] ) ) { $this->processors[ $command ] = []; } $this->processors[ $command ] [] = $processor_instance; return $processor_instance; } /** * Register. * * Endpoints & processors. */ protected function register() { $this->register_internal_endpoints(); $this->register_endpoints(); // Aka hooks. $this->register_processors(); } /** * Retrieves a recursive collection of all endpoint(s), items. * * Get items recursive, will run overall endpoints of the current controller. * For each endpoint it will run `$endpoint->getItems( $request ) // the $request passed in get_items_recursive`. * Will skip $skip_endpoints endpoint(s). * * Example, scenario: * Controller 'test-controller'. * Controller endpoints: 'endpoint1', 'endpoint2'. * Endpoint2 get_items method: `get_items() { return 'test' }`. * Call `Controller.get_items_recursive( ['endpoint1'] )`, result: [ 'endpoint2' => 'test' ]; * * @param array $skip_endpoints * * @return array */ public function get_items_recursive( $skip_endpoints = [] ) { $response = []; foreach ( $this->endpoints as $endpoint ) { // Skip self. if ( in_array( $endpoint, $skip_endpoints, true ) ) { continue; } $response[ $endpoint->get_name() ] = $endpoint->get_items( null ); } return $response; } /** * Get permission callback. * * Default controller permission callback. * By default endpoint will inherit the permission callback from the controller. * By default permission is `current_user_can( 'administrator' );`. * * @param \WP_REST_Request $request * * @return bool */ public function get_permission_callback( $request ) { // The function is public since endpoint need to access it. switch ( $request->get_method() ) { case 'GET': case 'POST': case 'UPDATE': case 'PUT': case 'DELETE': case 'PATCH': return current_user_can( 'administrator' ); } return false; } private static $notify_deprecated = true; private function deprecated() { add_action( 'elementor/init', function () { if ( ! self::$notify_deprecated ) { return; } Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( 'Elementor\Data\Manager', '3.5.0', 'Elementor\Data\V2\Manager' ); self::$notify_deprecated = false; } ); } } sub-endpoint.php 0000644 00000001361 14720714745 0007700 0 ustar 00 <?php namespace Elementor\Data\Base; // TODO: Add test. abstract class SubEndpoint extends Endpoint { /** * @var Endpoint */ protected $parent_endpoint; /** * @var string */ protected $parent_route = ''; public function __construct( $parent_route, $parent_endpoint ) { $this->parent_endpoint = $parent_endpoint; $this->parent_route = $parent_route; parent::__construct( $this->parent_endpoint->controller ); } /** * Get parent route. * * @return \Elementor\Data\Base\Endpoint */ public function get_parent() { return $this->parent_endpoint; } public function get_base_route() { $controller_name = $this->controller->get_name(); return $controller_name . '/' . $this->parent_route . $this->get_name(); } } endpoint/index.php 0000644 00000003172 14720732773 0010223 0 ustar 00 <?php namespace Elementor\Data\V2\Base\Endpoint; use Elementor\Data\V2\Base\Endpoint; use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Index extends Endpoint { public function get_name() { return 'index'; } public function get_format() { return "{$this->controller->get_full_name()}/{id}"; } public function get_public_name() { return ''; } public function get_items( $request ) { return $this->controller->get_items( $request ); } public function get_item( $id, $request ) { return $this->controller->get_item( $request ); } public function create_items( $request ) { return $this->controller->create_items( $request ); } public function create_item( $id, $request ) { return $this->controller->create_item( $request ); } public function update_items( $request ) { return $this->controller->update_items( $request ); } public function update_item( $id, $request ) { return $this->controller->update_item( $request ); } public function delete_items( $request ) { return $this->controller->delete_items( $request ); } public function delete_item( $id, $request ) { return $this->controller->delete_item( $request ); } public function register_items_route( $methods = WP_REST_Server::READABLE, $args = [] ) { parent::register_items_route( $methods, array_merge( $this->controller->get_items_args( $methods ), $args ) ); } public function register_item_route( $methods = WP_REST_Server::READABLE, $args = [], $route = '/' ) { parent::register_item_route( $methods, array_merge( $this->controller->get_item_args( $methods ), $args ), $route ); } } endpoint/index/all-children.php 0000644 00000003570 14720732773 0012563 0 ustar 00 <?php namespace Elementor\Data\V2\Base\Endpoint\Index; use Elementor\Data\V2\Base\Endpoint\Index; use Elementor\Data\V2\Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * class AllChildren, is optional endpoint. * Used in cases where the endpoints are static & there no use of dynamic endpoints( alpha/{id} ), eg: * 'settings' - controller * 'settings/products' - endpoint * 'settings/partners' - endpoint * * When 'settings' is requested, it should return results of all endpoints ( except it self ): * 'settings/products * 'settings/partners' * By running 'get_items' of each endpoint. */ class AllChildren extends Index { public function get_format() { return $this->controller->get_name() . '/index'; } /* * Retrieves a result(s) of all controller endpoint(s), items. * * Run overall endpoints of the current controller. * * Example, scenario: * 'settings' - controller * 'settings/products' - endpoint * 'settings/partners' - endpoint * Result: * [ * 'products' => [ * 0 => ... * 1 => ... * ], * 'partners' => [ * 0 => ... * 1 => ... * ], * ] */ public function get_items( $request ) { $response = []; foreach ( $this->controller->get_sub_controllers() as $controller ) { $controller_route = $this->get_controller()->get_base_route() . '/' . $controller->get_name(); $result = Manager::instance()->run_request( $controller_route ); if ( ! $result->is_error() ) { $response[ $controller->get_name() ] = $result->get_data(); } } foreach ( $this->controller->endpoints as $endpoint ) { // Skip self. if ( $endpoint === $this ) { continue; } $result = Manager::instance()->run_request( $endpoint->get_base_route() ); if ( ! $result->is_error() ) { $response[ $endpoint->get_name() ] = $result->get_data(); } } return $response; } } endpoint/index/sub-index-endpoint.php 0000644 00000002740 14720732773 0013737 0 ustar 00 <?php namespace Elementor\Data\V2\Base\Endpoint\Index; use Elementor\Data\V2\Base\Endpoint\Index; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class SubIndexEndpoint is default `Base\Endpoint\Index` of `SubController`, * it was created to handle base_route and format for child controller, index endpoint. * In case `SubController` were used and the default method of `Controller::register_index_endpoint` ain't overridden. * this class will give support to have such routes, eg: 'alpha/{id}/beta/{sub_id}' without using additional endpoints. */ final class Sub_Index_Endpoint extends Index { /*** * @var \Elementor\Data\V2\Base\Controller */ public $controller; public function get_format() { return $this->controller->get_parent()->get_name() . '/{id}/' . $this->controller->get_name() . '/{sub_id}'; } public function get_base_route() { $parent_controller = $this->controller->get_parent(); $parent_index_endpoint = $parent_controller->index_endpoint; $parent_controller_route = ''; // In case `$parent_index_endpoint` is AllChildren, it cannot support id_arg_name. if ( ! $parent_index_endpoint instanceof AllChildren ) { $parent_controller_route = "(?P<{$parent_index_endpoint->id_arg_name}>[\w]+)"; } return untrailingslashit('/' . implode( '/', array_filter( [ trim( $parent_index_endpoint->get_base_route(), '/' ), $parent_controller_route, $this->controller->get_name(), $this->get_public_name(), ] ) ) ); } } base-route.php 0000644 00000024422 14720732773 0007343 0 ustar 00 <?php namespace Elementor\Data\V2\Base; use Elementor\Data\V2\Base\Exceptions\Data_Exception; use Elementor\Data\V2\Base\Exceptions\Error_500; use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Class purpose is to separate routing logic into one file. */ abstract class Base_Route { const AVAILABLE_METHODS = [ WP_REST_Server::READABLE, WP_REST_Server::CREATABLE, WP_REST_Server::EDITABLE, WP_REST_Server::DELETABLE, WP_REST_Server::ALLMETHODS, ]; /** * Controller of current endpoint. * * @var \Elementor\Data\V2\Base\Controller */ protected $controller; /** * Current route, effect only in case the endpoint behave like sub-endpoint. * * @var string */ protected $route; /** * All register routes. * * @var array */ protected $routes = []; /** * Registered item route. * * @var array|null */ protected $item_route = null; protected $id_arg_name = 'id'; protected $id_arg_type_regex = '[\d]+'; /** * Ensure start-with and end-with slashes. * * '/' => '/' * 'abc' => '/abc/' * '/abc' => '/abc/' * 'abc/' => '/abc/' * '/abc/' => '/abc/' * * @param string $route * * @return string */ private function ensure_slashes( $route ) { if ( '/' !== $route[0] ) { $route = '/' . $route; } return trailingslashit( $route ); } /** * Get base route. * This method should always return the base route starts with '/' and ends without '/'. * * @return string */ public function get_base_route() { $name = $this->get_public_name(); $parent = $this->get_parent(); $parent_base = $parent->get_base_route(); $route = '/'; if ( ! ( $parent instanceof Controller ) ) { $route = $parent->item_route ? $parent->item_route['route'] . '/' : $this->route; } return untrailingslashit( '/' . trim( $parent_base . $route . $name, '/' ) ); } /** * Get permission callback. * * By default get permission callback from the controller. * * @param \WP_REST_Request $request Full data about the request. * * @return boolean */ public function get_permission_callback( $request ) { return $this->controller->get_permission_callback( $request ); } /** * Retrieves a collection of items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function get_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Retrieves one item from the collection. * * @param string $id * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function get_item( $id, $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Creates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function create_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Creates one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function create_item( $id, $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Updates multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function update_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Updates one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function update_item( $id, $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Delete multiple items. * * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function delete_items( $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Delete one item. * * @param string $id id of request item. * @param \WP_REST_Request $request Full data about the request. * * @return \WP_Error|\WP_REST_Response Response object on success, or WP_Error object on failure. */ protected function delete_item( $id, $request ) { return new \WP_Error( 'invalid-method', sprintf( "Method '%s' not implemented. Must be overridden in subclass.", __METHOD__ ), [ 'status' => 405 ] ); } /** * Register the endpoint. * * By default: register get items route. */ protected function register() { $this->register_items_route(); } protected function register_route( $route = '', $methods = WP_REST_Server::READABLE, $args = [] ) { if ( ! in_array( $methods, self::AVAILABLE_METHODS, true ) ) { trigger_error( "Invalid method: '$methods'.", E_USER_ERROR ); // phpcs:ignore } $route = $this->get_base_route() . $route; $this->routes [] = [ 'args' => $args, 'route' => $route, ]; /** * Determine behaviour of `base_callback()` and `get_permission_callback()`: * For `base_callback()` which applying the action. * Whether it's a one item request and should call `get_item_permission_callback()` or it's mutil items request and should call `get_items_permission_callback()`. */ $is_multi = ! empty( $args['is_multi'] ); if ( $is_multi ) { unset( $args['is_multi'] ); } $callback = function ( $request ) use ( $methods, $args, $is_multi ) { return $this->base_callback( $methods, $request, $is_multi ); }; return register_rest_route( $this->controller->get_namespace(), $route, [ [ 'args' => $args, 'methods' => $methods, 'callback' => $callback, 'permission_callback' => function ( $request ) { return $this->get_permission_callback( $request ); }, ], ] ); } /** * Register items route. * * @param string $methods * @param array $args */ public function register_items_route( $methods = WP_REST_Server::READABLE, $args = [] ) { $args['is_multi'] = true; $this->register_route( '', $methods, $args ); } /** * Register item route. * * @param string $route * @param array $args * @param string $methods */ public function register_item_route( $methods = WP_REST_Server::READABLE, $args = [], $route = '/' ) { if ( ! empty( $args['id_arg_name'] ) ) { $this->id_arg_name = $args['id_arg_name']; unset( $args['id_arg_name'] ); } if ( ! empty( $args['id_arg_type_regex'] ) ) { $this->id_arg_type_regex = $args['id_arg_type_regex']; unset( $args['id_arg_type_regex'] ); } $args = array_merge( [ $this->id_arg_name => [ 'description' => 'Unique identifier for the object.', 'type' => 'string', 'required' => true, ], ], $args ); $route .= '(?P<' . $this->id_arg_name . '>' . $this->id_arg_type_regex . ')'; $this->item_route = [ 'args' => $args, 'route' => $route, ]; $this->register_route( $route, $methods, $args ); } /** * Base callback. * All reset requests from the client should pass this function. * * @param string $methods * @param \WP_REST_Request $request * @param bool $is_multi * @param array $args * * @return mixed|\WP_Error|\WP_HTTP_Response|\WP_REST_Response */ public function base_callback( $methods, $request, $is_multi = false, $args = [] ) { if ( $request ) { $json_params = $request->get_json_params(); if ( $json_params ) { $request->set_body_params( $json_params ); } } $args = wp_parse_args( $args, [ 'is_debug' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ), ] ); $result = new \WP_Error( 'invalid_methods', 'route not supported.' ); $request->set_param( 'is_multi', $is_multi ); try { switch ( $methods ) { case WP_REST_Server::READABLE: $result = $is_multi ? $this->get_items( $request ) : $this->get_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::CREATABLE: $result = $is_multi ? $this->create_items( $request ) : $this->create_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::EDITABLE: $result = $is_multi ? $this->update_items( $request ) : $this->update_item( $request->get_param( 'id' ), $request ); break; case WP_REST_Server::DELETABLE: $result = $is_multi ? $this->delete_items( $request ) : $this->delete_item( $request->get_param( 'id' ), $request ); break; } } catch ( Data_Exception $e ) { $result = $e->to_wp_error(); } catch ( \Exception $e ) { if ( empty( $args['is_debug'] ) ) { $result = ( new Error_500() )->to_wp_error(); } else { // For frontend. $exception_mapping = [ 'trace' => $e->getTrace(), 'file' => $e->getFile(), 'line' => $e->getLine(), ]; $e->debug = $exception_mapping; $result = ( new Data_Exception( $e->getMessage(), $e->getCode(), $e ) )->to_wp_error(); } } return rest_ensure_response( $result ); } /** * Constructor. * * run `$this->register()`. * * @param \Elementor\Data\V2\Base\Controller $controller * @param string $route */ protected function __construct( Controller $controller, $route ) { $this->controller = $controller; $this->route = $this->ensure_slashes( $route ); $this->register(); } } exceptions/error-404.php 0000644 00000000533 14720732773 0011111 0 ustar 00 <?php namespace Elementor\Data\V2\Base\Exceptions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Error_404 extends Data_Exception { protected function get_http_error_code() { return 404; } public function get_code() { return 'not-found'; } public function get_message() { return '404 not found'; } } exceptions/wp-error-exception.php 0000644 00000000561 14720732773 0013225 0 ustar 00 <?php namespace Elementor\Data\V2\Base\Exceptions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class WP_Error_Exception extends Data_Exception { public function __construct( \WP_Error $wp_error ) { parent::__construct( $wp_error->get_error_message(), $wp_error->get_error_code(), [ 'status' => $wp_error->get_error_code(), ] ); } } exceptions/data-exception.php 0000644 00000002155 14720732773 0012362 0 ustar 00 <?php namespace Elementor\Data\V2\Base\Exceptions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Data_Exception extends \Exception { protected $custom_data = [ 'code' => '', 'data' => [], ]; public function get_code() { return 'reset-http-error'; } public function get_message() { return '501 Not Implemented'; } public function get_data() { return [ 'status' => $this->get_http_error_code(), // 'status' is used by WP to pass the http error code. ]; } public function to_wp_error() { return new \WP_Error( $this->custom_data['code'], $this->message, $this->custom_data['data'] ); } protected function get_http_error_code() { return 501; // 501 Not Implemented } protected function apply() {} public function __construct( $message = '', $code = '', $data = [] ) { $this->message = empty( $message ) ? $this->get_message() : $message; $this->custom_data['code'] = empty( $code ) ? $this->get_code() : $code; $this->custom_data['data'] = empty( $data ) ? $this->get_data() : $data; parent::__construct( $this->message, 0, null ); $this->apply(); } } exceptions/error-500.php 0000644 00000000556 14720732773 0011113 0 ustar 00 <?php namespace Elementor\Data\V2\Base\Exceptions; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Error_500 extends Data_Exception { protected function get_http_error_code() { return 500; } public function get_code() { return 'internal-server-error'; } public function get_message() { return 'Something went wrong'; } } module.php 0000644 00000016611 14722104033 0006543 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Elementor module. * * An abstract class that provides the needed properties and methods to * manage and handle modules in inheriting classes. * * @since 1.7.0 * @abstract */ abstract class Module extends Base_Object { /** * Module class reflection. * * Holds the information about a class. * * @since 1.7.0 * @access private * * @var \ReflectionClass */ private $reflection; /** * Module components. * * Holds the module components. * * @since 1.7.0 * @access private * * @var array */ private $components = []; /** * Module instance. * * Holds the module instance. * * @since 1.7.0 * @access protected * * @var Module */ protected static $_instances = []; /** * Get module name. * * Retrieve the module name. * * @since 1.7.0 * @access public * @abstract * * @return string Module name. */ abstract public function get_name(); /** * Instance. * * Ensures only one instance of the module class is loaded or can be loaded. * * @since 1.7.0 * @access public * @static * * @return Module An instance of the class. */ public static function instance() { $class_name = static::class_name(); if ( empty( static::$_instances[ $class_name ] ) ) { static::$_instances[ $class_name ] = new static(); } return static::$_instances[ $class_name ]; } /** * @since 2.0.0 * @access public * @static */ public static function is_active() { return true; } /** * Class name. * * Retrieve the name of the class. * * @since 1.7.0 * @access public * @static */ public static function class_name() { return get_called_class(); } public static function get_experimental_data() { return []; } /** * Clone. * * Disable class cloning and throw an error on object clone. * * The whole idea of the singleton design pattern is that there is a single * object. Therefore, we don't want the object to be cloned. * * @since 1.7.0 * @access public */ public function __clone() { _doing_it_wrong( __FUNCTION__, sprintf( 'Cloning instances of the singleton "%s" class is forbidden.', get_class( $this ) ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped '1.0.0' ); } /** * Wakeup. * * Disable unserializing of the class. * * @since 1.7.0 * @access public */ public function __wakeup() { _doing_it_wrong( __FUNCTION__, sprintf( 'Unserializing instances of the singleton "%s" class is forbidden.', get_class( $this ) ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped '1.0.0' ); } /** * @since 2.0.0 * @access public */ public function get_reflection() { if ( null === $this->reflection ) { $this->reflection = new \ReflectionClass( $this ); } return $this->reflection; } /** * Add module component. * * Add new component to the current module. * * @since 1.7.0 * @access public * * @param string $id Component ID. * @param mixed $instance An instance of the component. */ public function add_component( $id, $instance ) { $this->components[ $id ] = $instance; } /** * @since 2.3.0 * @access public * @return Module[] */ public function get_components() { return $this->components; } /** * Get module component. * * Retrieve the module component. * * @since 1.7.0 * @access public * * @param string $id Component ID. * * @return mixed An instance of the component, or `false` if the component * doesn't exist. */ public function get_component( $id ) { if ( isset( $this->components[ $id ] ) ) { return $this->components[ $id ]; } return false; } /** * Get assets url. * * @since 2.3.0 * @access protected * * @param string $file_name * @param string $file_extension * @param string $relative_url Optional. Default is null. * @param string $add_min_suffix Optional. Default is 'default'. * * @return string */ final protected function get_assets_url( $file_name, $file_extension, $relative_url = null, $add_min_suffix = 'default' ) { static $is_test_mode = null; if ( null === $is_test_mode ) { $is_test_mode = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || defined( 'ELEMENTOR_TESTS' ) && ELEMENTOR_TESTS; } if ( ! $relative_url ) { $relative_url = $this->get_assets_relative_url() . $file_extension . '/'; } $url = $this->get_assets_base_url() . $relative_url . $file_name; if ( 'default' === $add_min_suffix ) { $add_min_suffix = ! $is_test_mode; } if ( $add_min_suffix ) { $url .= '.min'; } return $url . '.' . $file_extension; } /** * Get js assets url * * @since 2.3.0 * @access protected * * @param string $file_name * @param string $relative_url Optional. Default is null. * @param string $add_min_suffix Optional. Default is 'default'. * * @return string */ final protected function get_js_assets_url( $file_name, $relative_url = null, $add_min_suffix = 'default' ) { return $this->get_assets_url( $file_name, 'js', $relative_url, $add_min_suffix ); } /** * Get css assets url * * @since 2.3.0 * @access protected * * @param string $file_name * @param string $relative_url Optional. Default is null. * @param string $add_min_suffix Optional. Default is 'default'. * @param bool $add_direction_suffix Optional. Default is `false` * * @return string */ final protected function get_css_assets_url( $file_name, $relative_url = null, $add_min_suffix = 'default', $add_direction_suffix = false ) { static $direction_suffix = null; if ( ! $direction_suffix ) { $direction_suffix = is_rtl() ? '-rtl' : ''; } if ( $add_direction_suffix ) { $file_name .= $direction_suffix; } return $this->get_assets_url( $file_name, 'css', $relative_url, $add_min_suffix ); } /** * Get Frontend File URL * * Returns the URL for the CSS file to be loaded in the front end. If requested via the second parameter, a custom * file is generated based on a passed template file name. Otherwise, the URL for the default CSS file is returned. * * @since 3.24.0 * * @access public * * @param string $file_name * @param boolean $has_custom_breakpoints * * @return string frontend file URL */ public function get_frontend_file_url( $file_name, $has_custom_breakpoints ) { return Plugin::$instance->frontend->get_frontend_file_url( $file_name, $has_custom_breakpoints ); } /** * Get assets base url * * @since 2.6.0 * @access protected * * @return string */ protected function get_assets_base_url() { return ELEMENTOR_URL; } /** * Get assets relative url * * @since 2.3.0 * @access protected * * @return string */ protected function get_assets_relative_url() { return 'assets/'; } /** * Get the module's associated widgets. * * @return string[] */ protected function get_widgets() { return []; } /** * Initialize the module related widgets. */ public function init_widgets() { $widget_manager = Plugin::instance()->widgets_manager; foreach ( $this->get_widgets() as $widget ) { $class_name = $this->get_reflection()->getNamespaceName() . '\Widgets\\' . $widget; $widget_manager->register( new $class_name() ); } } public function __construct() { add_action( 'elementor/widgets/register', [ $this, 'init_widgets' ] ); } } providers/social-network-provider.php 0000644 00000016233 14722104033 0014064 0 ustar 00 <?php namespace Elementor\Core\Base\Providers; class Social_Network_Provider { private static array $social_networks = []; public const FACEBOOK = 'Facebook'; public const TWITTER = 'X (Twitter)'; public const INSTAGRAM = 'Instagram'; public const LINKEDIN = 'LinkedIn'; public const PINTEREST = 'Pinterest'; public const YOUTUBE = 'YouTube'; public const TIKTOK = 'TikTok'; public const WHATSAPP = 'WhatsApp'; public const APPLEMUSIC = 'Apple Music'; public const SPOTIFY = 'Spotify'; public const SOUNDCLOUD = 'SoundCloud'; public const BEHANCE = 'Behance'; public const DRIBBBLE = 'Dribbble'; public const VIMEO = 'Vimeo'; public const WAZE = 'Waze'; public const MESSENGER = 'Messenger'; public const TELEPHONE = 'Telephone'; public const EMAIL = 'Email'; public const URL = 'Url'; public const FILE_DOWNLOAD = 'File Download'; public const SMS = 'SMS'; public const VIBER = 'VIBER'; public const SKYPE = 'Skype'; public const VCF = 'Save contact (vCard)'; public static function get_social_networks_icons(): array { static::init_social_networks_array_if_empty(); static $icons = []; if ( empty( $icons ) ) { foreach ( static::$social_networks as $network => $data ) { $icons[ $network ] = $data['icon']; } } return $icons; } public static function get_icon_mapping( string $platform ): string { static::init_social_networks_array_if_empty(); if ( isset( self::$social_networks[ $platform ]['icon'] ) ) { return self::$social_networks[ $platform ]['icon']; } return ''; } public static function get_name_mapping( string $platform ): string { static::init_social_networks_array_if_empty(); if ( isset( self::$social_networks[ $platform ]['name'] ) ) { return self::$social_networks[ $platform ]['name']; } return ''; } public static function get_text_mapping( string $platform ): string { static::init_social_networks_array_if_empty(); if ( isset( self::$social_networks[ $platform ]['text'] ) ) { return self::$social_networks[ $platform ]['text']; } return ''; } public static function get_social_networks_text( $providers = [] ): array { static::init_social_networks_array_if_empty(); static $texts = []; if ( empty( $texts ) ) { foreach ( static::$social_networks as $network => $data ) { $texts[ $network ] = $data['text']; } } if ( $providers ) { return array_intersect_key( $texts, array_flip( $providers ) ); } return $texts; } private static function init_social_networks_array_if_empty(): void { if ( ! empty( static::$social_networks ) ) { return; } static::$social_networks[ static::VCF ] = [ 'text' => esc_html__( 'Save contact (vCard)', 'elementor' ), 'icon' => 'fab fa-outlook', 'name' => 'vcf', ]; static::$social_networks[ static::FACEBOOK ] = [ 'text' => esc_html__( 'Facebook', 'elementor' ), 'icon' => 'fab fa-facebook', 'name' => 'facebook', ]; static::$social_networks[ static::TWITTER ] = [ 'text' => esc_html__( 'X (Twitter)', 'elementor' ), 'icon' => 'fab fa-x-twitter', 'name' => 'x-twitter', ]; static::$social_networks[ static::INSTAGRAM ] = [ 'text' => esc_html__( 'Instagram', 'elementor' ), 'icon' => 'fab fa-instagram', 'name' => 'instagram', ]; static::$social_networks[ static::LINKEDIN ] = [ 'text' => esc_html__( 'LinkedIn', 'elementor' ), 'icon' => 'fab fa-linkedin-in', 'name' => 'linkedin', ]; static::$social_networks[ static::PINTEREST ] = [ 'text' => esc_html__( 'Pinterest', 'elementor' ), 'icon' => 'fab fa-pinterest', 'name' => 'pinterest', ]; static::$social_networks[ static::YOUTUBE ] = [ 'text' => esc_html__( 'YouTube', 'elementor' ), 'icon' => 'fab fa-youtube', 'name' => 'youtube', ]; static::$social_networks[ static::TIKTOK ] = [ 'text' => esc_html__( 'TikTok', 'elementor' ), 'icon' => 'fab fa-tiktok', 'name' => 'tiktok', ]; static::$social_networks[ static::WHATSAPP ] = [ 'text' => esc_html__( 'WhatsApp', 'elementor' ), 'icon' => 'fab fa-whatsapp', 'name' => 'whatsapp', ]; static::$social_networks[ static::APPLEMUSIC ] = [ 'text' => esc_html__( 'Apple Music', 'elementor' ), 'icon' => 'fa fa-music', 'name' => 'apple-music', ]; static::$social_networks[ static::SPOTIFY ] = [ 'text' => esc_html__( 'Spotify', 'elementor' ), 'icon' => 'fab fa-spotify', 'name' => 'spotify', ]; static::$social_networks[ static::SOUNDCLOUD ] = [ 'text' => esc_html__( 'SoundCloud', 'elementor' ), 'icon' => 'fab fa-soundcloud', 'name' => 'soundcloud', ]; static::$social_networks[ static::BEHANCE ] = [ 'text' => esc_html__( 'Behance', 'elementor' ), 'icon' => 'fab fa-behance', 'name' => 'behance', ]; static::$social_networks[ static::DRIBBBLE ] = [ 'text' => esc_html__( 'Dribbble', 'elementor' ), 'icon' => 'fab fa-dribbble', 'name' => 'dribble', ]; static::$social_networks[ static::VIMEO ] = [ 'text' => esc_html__( 'Vimeo', 'elementor' ), 'icon' => 'fab fa-vimeo-v', 'name' => 'vimeo', ]; static::$social_networks[ static::WAZE ] = [ 'text' => esc_html__( 'Waze', 'elementor' ), 'icon' => 'fab fa-waze', 'name' => 'waze', ]; static::$social_networks[ static::MESSENGER ] = [ 'text' => esc_html__( 'Messenger', 'elementor' ), 'icon' => 'fab fa-facebook-messenger', 'name' => 'messenger', ]; static::$social_networks[ static::TELEPHONE ] = [ 'text' => esc_html__( 'Telephone', 'elementor' ), 'icon' => 'fas fa-phone-alt', 'name' => 'phone', ]; static::$social_networks[ static::EMAIL ] = [ 'text' => esc_html__( 'Email', 'elementor' ), 'icon' => 'fas fa-envelope', 'name' => 'email', ]; static::$social_networks[ static::URL ] = [ 'text' => esc_html__( 'URL', 'elementor' ), 'icon' => 'fas fa-globe', 'name' => 'url', ]; static::$social_networks[ static::FILE_DOWNLOAD ] = [ 'text' => esc_html__( 'File Download', 'elementor' ), 'icon' => 'fas fa-download', 'name' => 'download', ]; static::$social_networks[ static::SMS ] = [ 'text' => esc_html__( 'SMS', 'elementor' ), 'icon' => 'fas fa-sms', 'name' => 'sms', ]; static::$social_networks[ static::VIBER ] = [ 'text' => esc_html__( 'Viber', 'elementor' ), 'icon' => 'fab fa-viber', 'name' => 'viber', ]; static::$social_networks[ static::SKYPE ] = [ 'text' => esc_html__( 'Skype', 'elementor' ), 'icon' => 'fab fa-skype', 'name' => 'skype', ]; } public static function build_messenger_link( string $username ) { return 'https://m.me/' . $username; } public static function build_email_link( array $data, string $prefix ) { $email = $data[ $prefix . '_mail' ] ?? ''; $subject = $data[ $prefix . '_mail_subject' ] ?? ''; $body = $data[ $prefix . '_mail_body' ] ?? ''; if ( ! $email ) { return ''; } $link = 'mailto:' . $email; if ( $subject ) { $link .= '?subject=' . $subject; } if ( $body ) { $link .= $subject ? '&' : '?'; $link .= 'body=' . $body; } return $link; } public static function build_viber_link( string $action, string $number ) { if ( empty( $number ) ) { return ''; } return add_query_arg( [ 'number' => urlencode( $number ), ], 'viber://' . $action ); } } traits/shared-widget-controls-trait.php 0000644 00000015766 14722104033 0014307 0 ustar 00 <?php namespace Elementor\Core\Base\Traits; use Elementor\Controls_Manager; use Elementor\Modules\FloatingButtons\Control\Hover_Animation_Floating_Buttons; use Elementor\Plugin; use Elementor\Shapes; use Elementor\Utils; trait Shared_Widget_Controls_Trait { protected $border_width_range = [ 'min' => 0, 'max' => 10, 'step' => 1, ]; protected function add_html_tag_control( string $name, string $default = 'h2' ): void { $this->add_control( $name, [ 'label' => esc_html__( 'HTML Tag', 'elementor' ), 'type' => Controls_Manager::SELECT, 'options' => [ 'h1' => 'H1', 'h2' => 'H2', 'h3' => 'H3', 'h4' => 'H4', 'h5' => 'H5', 'h6' => 'H6', 'div' => 'div', 'span' => 'span', 'p' => 'p', ], 'default' => $default, ] ); } /** * Remove any child arrays where all properties are empty */ protected function clean_array( $input_array = [] ) { $output_array = array_filter( $input_array, function( $sub_array ) { // Use array_filter on the sub array $filtered_sub_array = array_filter( $sub_array, function( $val ) { // Filter out empty or null values return ! is_null( $val ) && '' !== $val; } ); // A non-empty result means the sub array contains some non-empty value(s) return ! empty( $filtered_sub_array ); } ); return $output_array; } protected function get_link_attributes( $link = [], $other_attributes = [] ) { $url_attrs = []; $rel_string = ''; if ( ! empty( $link['url'] ) ) { $url_attrs['href'] = esc_url( $link['url'] ); } if ( ! empty( $link['is_external'] ) ) { $url_attrs['target'] = '_blank'; $rel_string .= 'noopener '; } if ( ! empty( $link['nofollow'] ) ) { $rel_string .= 'nofollow '; } if ( ! empty( $rel_string ) ) { $url_attrs['rel'] = $rel_string; } /** * Note - we deliberately merge $other_attributes second * to allow overriding default attributes values such as a more formatted href */ $url_combined_attrs = array_merge( $url_attrs, $other_attributes, Utils::parse_custom_attributes( $link['custom_attributes'] ?? '' ), ); return $url_combined_attrs; } protected function add_icons_per_row_control( string $name = 'icons_per_row', $options = [ '2' => '2', '3' => '3', ], string $default = '3', $label = '', $selector_custom_property = '--e-link-in-bio-icon-columns' ): void { if ( ! $label ) { $label = esc_html__( 'Icons Per Row', 'elementor' ); } $this->add_control( $name, [ 'label' => $label, 'type' => Controls_Manager::SELECT, 'options' => $options, 'default' => $default, 'render_type' => 'template', 'selectors' => [ '{{WRAPPER}} .e-link-in-bio' => $selector_custom_property . ': {{VALUE}};', ], ] ); } protected function add_slider_control( string $name, array $args = [] ): void { $default_args = [ 'type' => Controls_Manager::SLIDER, 'default' => [ 'unit' => 'px', ], 'size_units' => [ 'px', '%', 'em', 'rem', 'vw', 'custom' ], 'range' => [ 'px' => [ 'min' => 0, 'max' => 100, 'step' => 1, ], ], ]; $this->add_control( $name, array_merge_recursive( $default_args, $args ) ); } protected function add_borders_control( string $prefix, array $show_border_args = [], array $border_width_args = [], array $border_color_args = [] ): void { $show_border = [ 'label' => esc_html__( 'Border', 'elementor' ), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__( 'Yes', 'elementor' ), 'label_off' => esc_html__( 'No', 'elementor' ), 'return_value' => 'yes', 'default' => '', ]; $this->add_control( $prefix . '_show_border', array_merge( $show_border, $show_border_args ) ); $condition = [ $prefix . '_show_border' => 'yes', ]; if ( isset( $border_width_args['condition'] ) ) { $condition = array_merge( $condition, $border_width_args['condition'] ); unset( $border_width_args['condition'] ); } $border_width = [ 'label' => esc_html__( 'Border Width', 'elementor' ) . ' (px)', 'type' => Controls_Manager::SLIDER, 'size_units' => [ 'px' ], 'range' => [ 'px' => $this->border_width_range, ], 'condition' => $condition, 'default' => [ 'unit' => 'px', 'size' => 1, ], ]; $this->add_responsive_control( $prefix . '_border_width', array_merge( $border_width, $border_width_args ), ); $condition = [ $prefix . '_show_border' => 'yes', ]; if ( isset( $border_color_args['condition'] ) ) { $condition = array_merge( $condition, $border_color_args['condition'] ); unset( $border_color_args['condition'] ); } $border_color = [ 'label' => esc_html__( 'Border Color', 'elementor' ), 'type' => Controls_Manager::COLOR, 'condition' => $condition, 'default' => '#000000', ]; $this->add_control( $prefix . '_border_color', array_merge( $border_color, $border_color_args ) ); } protected function get_shape_divider( $side = 'bottom' ) { $settings = $this->settings; $base_setting_key = "identity_section_style_cover_divider_$side"; $file_name = $settings[ $base_setting_key ]; if ( empty( $file_name ) ) { return []; } $negative = ! empty( $settings[ $base_setting_key . '_negative' ] ); $shape_path = Shapes::get_shape_path( $file_name, $negative ); if ( ! is_file( $shape_path ) || ! is_readable( $shape_path ) ) { return []; } return [ 'negative' => $negative, 'svg' => Utils::file_get_contents( $shape_path ), ]; } protected function print_shape_divider( $side = 'bottom' ) { $shape_divider = $this->get_shape_divider( $side ); if ( empty( $shape_divider ) ) { return; } ?> <div class="elementor-shape elementor-shape-<?php echo esc_attr( $side ); ?>" data-negative="<?php echo esc_attr( $shape_divider['negative'] ? 'true' : 'false' ); ?>" > <?php // PHPCS - The file content is being read from a strict file path structure. echo $shape_divider['svg']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> </div> <?php } protected function get_configured_breakpoints( $add_desktop = 'true' ) { $active_devices = Plugin::$instance->breakpoints->get_active_devices_list( [ 'reverse' => true ] ); $active_breakpoint_instances = Plugin::$instance->breakpoints->get_active_breakpoints(); $devices_options = []; foreach ( $active_devices as $device_key ) { $device_label = 'desktop' === $device_key ? esc_html__( 'Desktop', 'elementor' ) : $active_breakpoint_instances[ $device_key ]->get_label(); $devices_options[ $device_key ] = $device_label; } return [ 'active_devices' => $active_devices, 'devices_options' => $devices_options, ]; } protected function add_hover_animation_control( string $name, array $args = [] ): void { $this->add_control( $name, array_merge( [ 'label' => esc_html__( 'Hover Animation', 'elementor' ), 'type' => Hover_Animation_Floating_Buttons::TYPE, 'frontend_available' => true, 'default' => 'grow', ], $args ) ); } } app.php 0000644 00000002344 14722104033 0006034 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Base App * * Base app utility class that provides shared functionality of apps. * * @since 2.3.0 */ abstract class App extends Module { /** * Print config. * * Used to print the app and its components settings as a JavaScript object. * * @param string $handle Optional * * @since 2.3.0 * @since 2.6.0 added the `$handle` parameter * @access protected */ final protected function print_config( $handle = null ) { $name = $this->get_name(); $js_var = 'elementor' . str_replace( ' ', '', ucwords( str_replace( '-', ' ', $name ) ) ) . 'Config'; $config = $this->get_settings() + $this->get_components_config(); if ( ! $handle ) { $handle = 'elementor-' . $name; } Utils::print_js_config( $handle, $js_var, $config ); } /** * Get components config. * * Retrieves the app components settings. * * @since 2.3.0 * @access private * * @return array */ private function get_components_config() { $settings = []; foreach ( $this->get_components() as $id => $instance ) { $settings[ $id ] = $instance->get_settings(); } return $settings; } } background-task-manager.php 0000644 00000004744 14722104033 0011751 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Core\Base\Module as BaseModule; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Background_Task_Manager extends BaseModule { /** * @var Background_Task */ protected $task_runner; abstract public function get_action(); abstract public function get_plugin_name(); abstract public function get_plugin_label(); abstract public function get_task_runner_class(); abstract public function get_query_limit(); abstract protected function start_run(); public function on_runner_start() { $logger = Plugin::$instance->logger->get_logger(); $logger->info( $this->get_plugin_name() . '::' . $this->get_action() . ' Started' ); } public function on_runner_complete( $did_tasks = false ) { $logger = Plugin::$instance->logger->get_logger(); $logger->info( $this->get_plugin_name() . '::' . $this->get_action() . ' Completed' ); } public function get_task_runner() { if ( empty( $this->task_runner ) ) { $class_name = $this->get_task_runner_class(); $this->task_runner = new $class_name( $this ); } return $this->task_runner; } // TODO: Replace with a db settings system. protected function add_flag( $flag ) { add_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag, 1 ); } protected function get_flag( $flag ) { return get_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag ); } protected function delete_flag( $flag ) { delete_option( $this->get_plugin_name() . '_' . $this->get_action() . '_' . $flag ); } protected function get_start_action_url() { return wp_nonce_url( add_query_arg( $this->get_action(), 'run' ), $this->get_action() . 'run' ); } protected function get_continue_action_url() { return wp_nonce_url( add_query_arg( $this->get_action(), 'continue' ), $this->get_action() . 'continue' ); } private function continue_run() { $runner = $this->get_task_runner(); $runner->continue_run(); } public function __construct() { if ( empty( $_GET[ $this->get_action() ] ) ) { return; } Plugin::$instance->init_common(); if ( 'run' === $_GET[ $this->get_action() ] && check_admin_referer( $this->get_action() . 'run' ) ) { $this->start_run(); } if ( 'continue' === $_GET[ $this->get_action() ] && check_admin_referer( $this->get_action() . 'continue' ) ) { $this->continue_run(); } wp_safe_redirect( remove_query_arg( [ $this->get_action(), '_wpnonce' ] ) ); die; } } background-task.php 0000644 00000021572 14722104033 0010337 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Plugin; use Elementor\Core\Base\BackgroundProcess\WP_Background_Process; /** * Based on https://github.com/woocommerce/woocommerce/blob/master/includes/abstracts/class-wc-background-process.php * & https://github.com/woocommerce/woocommerce/blob/master/includes/class-wc-background-updater.php */ defined( 'ABSPATH' ) || exit; /** * WC_Background_Process class. */ abstract class Background_Task extends WP_Background_Process { protected $current_item; /** * Dispatch updater. * * Updater will still run via cron job if this fails for any reason. */ public function dispatch() { $dispatched = parent::dispatch(); if ( is_wp_error( $dispatched ) ) { wp_die( esc_html( $dispatched ) ); } } public function query_col( $sql ) { global $wpdb; // Add Calc. $item = $this->get_current_item(); if ( empty( $item['total'] ) ) { $sql = preg_replace( '/^SELECT/', 'SELECT SQL_CALC_FOUND_ROWS', $sql ); } // Add offset & limit. $sql = preg_replace( '/;$/', '', $sql ); $sql .= ' LIMIT %d, %d;'; $results = $wpdb->get_col( $wpdb->prepare( $sql, $this->get_current_offset(), $this->get_limit() ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared if ( ! empty( $results ) ) { $this->set_total(); } return $results; } public function should_run_again( $updated_rows ) { return count( $updated_rows ) === $this->get_limit(); } public function get_current_offset() { $limit = $this->get_limit(); return ( $this->current_item['iterate_num'] - 1 ) * $limit; } public function get_limit() { return $this->manager->get_query_limit(); } public function set_total() { global $wpdb; if ( empty( $this->current_item['total'] ) ) { $total_rows = $wpdb->get_var( 'SELECT FOUND_ROWS();' ); $total_iterates = ceil( $total_rows / $this->get_limit() ); $this->current_item['total'] = $total_iterates; } } /** * Complete * * Override if applicable, but ensure that the below actions are * performed, or, call parent::complete(). */ protected function complete() { $this->manager->on_runner_complete( true ); parent::complete(); } public function continue_run() { // Used to fire an action added in WP_Background_Process::_construct() that calls WP_Background_Process::handle_cron_healthcheck(). // This method will make sure the database updates are executed even if cron is disabled. Nothing will happen if the updates are already running. do_action( $this->cron_hook_identifier ); } /** * @return mixed */ public function get_current_item() { return $this->current_item; } /** * Get batch. * * @return \stdClass Return the first batch from the queue. */ protected function get_batch() { $batch = parent::get_batch(); $batch->data = array_filter( (array) $batch->data ); return $batch; } /** * Handle cron healthcheck * * Restart the background process if not already running * and data exists in the queue. */ public function handle_cron_healthcheck() { if ( $this->is_process_running() ) { // Background process already running. return; } if ( $this->is_queue_empty() ) { // No data to process. $this->clear_scheduled_event(); return; } $this->handle(); } /** * Schedule fallback event. */ protected function schedule_event() { if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { wp_schedule_event( time() + 10, $this->cron_interval_identifier, $this->cron_hook_identifier ); } } /** * Is the updater running? * * @return boolean */ public function is_running() { return false === $this->is_queue_empty(); } /** * See if the batch limit has been exceeded. * * @return bool */ protected function batch_limit_exceeded() { return $this->time_exceeded() || $this->memory_exceeded(); } /** * Handle. * * Pass each queue item to the task handler, while remaining * within server memory and time limit constraints. */ protected function handle() { $this->manager->on_runner_start(); $this->lock_process(); do { $batch = $this->get_batch(); foreach ( $batch->data as $key => $value ) { $task = $this->task( $value ); if ( false !== $task ) { $batch->data[ $key ] = $task; } else { unset( $batch->data[ $key ] ); } if ( $this->batch_limit_exceeded() ) { // Batch limits reached. break; } } // Update or delete current batch. if ( ! empty( $batch->data ) ) { $this->update( $batch->key, $batch->data ); } else { $this->delete( $batch->key ); } } while ( ! $this->batch_limit_exceeded() && ! $this->is_queue_empty() ); $this->unlock_process(); // Start next batch or complete process. if ( ! $this->is_queue_empty() ) { $this->dispatch(); } else { $this->complete(); } } /** * Use the protected `is_process_running` method as a public method. * @return bool */ public function is_process_locked() { return $this->is_process_running(); } public function handle_immediately( $callbacks ) { $this->manager->on_runner_start(); $this->lock_process(); foreach ( $callbacks as $callback ) { $item = [ 'callback' => $callback, ]; do { $item = $this->task( $item ); } while ( $item ); } $this->unlock_process(); } /** * Task * * Override this method to perform any actions required on each * queue item. Return the modified item for further processing * in the next pass through. Or, return false to remove the * item from the queue. * * @param array $item * * @return array|bool */ protected function task( $item ) { $result = false; if ( ! isset( $item['iterate_num'] ) ) { $item['iterate_num'] = 1; } $logger = Plugin::$instance->logger->get_logger(); $callback = $this->format_callback_log( $item ); if ( is_callable( $item['callback'] ) ) { $progress = ''; if ( 1 < $item['iterate_num'] ) { if ( empty( $item['total'] ) ) { $progress = sprintf( '(x%s)', $item['iterate_num'] ); } else { $percent = ceil( $item['iterate_num'] / ( $item['total'] / 100 ) ); $progress = sprintf( '(%s of %s, %s%%)', $item['iterate_num'], $item['total'], $percent ); } } $logger->info( sprintf( '%s Start %s', $callback, $progress ) ); $this->current_item = $item; $result = (bool) call_user_func( $item['callback'], $this ); // get back the updated item. $item = $this->current_item; $this->current_item = null; if ( $result ) { if ( empty( $item['total'] ) ) { $logger->info( sprintf( '%s callback needs to run again', $callback ) ); } elseif ( 1 === $item['iterate_num'] ) { $logger->info( sprintf( '%s callback needs to run more %d times', $callback, $item['total'] - $item['iterate_num'] ) ); } $item['iterate_num']++; } else { $logger->info( sprintf( '%s Finished', $callback ) ); } } else { $logger->notice( sprintf( 'Could not find %s callback', $callback ) ); } return $result ? $item : false; } /** * Schedule cron healthcheck. * * @param array $schedules Schedules. * @return array */ public function schedule_cron_healthcheck( $schedules ) { $interval = apply_filters( $this->identifier . '_cron_interval', 5 ); // Adds every 5 minutes to the existing schedules. $schedules[ $this->identifier . '_cron_interval' ] = array( 'interval' => MINUTE_IN_SECONDS * $interval, 'display' => sprintf( /* translators: %d: Interval in minutes. */ esc_html__( 'Every %d minutes', 'elementor' ), $interval ), ); return $schedules; } /** * See if the batch limit has been exceeded. * * @return bool */ public function is_memory_exceeded() { return $this->memory_exceeded(); } /** * Delete all batches. * * @return self */ public function delete_all_batches() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; $wpdb->query( $wpdb->prepare( "DELETE FROM {$table} WHERE {$column} LIKE %s", $key ) ); // @codingStandardsIgnoreLine. return $this; } /** * Kill process. * * Stop processing queue items, clear cronjob and delete all batches. */ public function kill_process() { if ( ! $this->is_queue_empty() ) { $this->delete_all_batches(); wp_clear_scheduled_hook( $this->cron_hook_identifier ); } } public function set_current_item( $item ) { $this->current_item = $item; } protected function format_callback_log( $item ) { return implode( '::', (array) $item['callback'] ); } /** * @var \Elementor\Core\Base\Background_Task_Manager */ protected $manager; public function __construct( $manager ) { $this->manager = $manager; // Uses unique prefix per blog so each blog has separate queue. $this->prefix = 'elementor_' . get_current_blog_id(); $this->action = $this->manager->get_action(); parent::__construct(); } } document.php 0000644 00000144610 14722104033 0007075 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Core\Base\Elements_Iteration_Actions\Assets as Assets_Iteration_Action; use Elementor\Core\Base\Elements_Iteration_Actions\Base as Elements_Iteration_Action; use Elementor\Core\Behaviors\Interfaces\Lock_Behavior; use Elementor\Core\Files\CSS\Post as Post_CSS; use Elementor\Core\Settings\Page\Model as Page_Model; use Elementor\Core\Utils\Exceptions; use Elementor\Includes\Elements\Container; use Elementor\Plugin; use Elementor\Controls_Manager; use Elementor\Controls_Stack; use Elementor\TemplateLibrary\Source_Local; use Elementor\User; use Elementor\Core\Settings\Manager as SettingsManager; use Elementor\Utils; use Elementor\Widget_Base; use Elementor\Core\Settings\Page\Manager as PageManager; use ElementorPro\Modules\Library\Widgets\Template; use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Elementor document. * * An abstract class that provides the needed properties and methods to * manage and handle documents in inheriting classes. * * @since 2.0.0 * @abstract */ abstract class Document extends Controls_Stack { /** * Document type meta key. */ const TYPE_META_KEY = '_elementor_template_type'; const PAGE_META_KEY = '_elementor_page_settings'; const BUILT_WITH_ELEMENTOR_META_KEY = '_elementor_edit_mode'; const CACHE_META_KEY = '_elementor_element_cache'; /** * Document publish status. */ const STATUS_PUBLISH = 'publish'; /** * Document draft status. */ const STATUS_DRAFT = 'draft'; /** * Document private status. */ const STATUS_PRIVATE = 'private'; /** * Document autosave status. */ const STATUS_AUTOSAVE = 'autosave'; /** * Document pending status. */ const STATUS_PENDING = 'pending'; /** * @var int */ private $main_id; /** * @var bool */ private $is_saving = false; private static $properties = []; /** * @var Elements_Iteration_Action[] */ private $elements_iteration_actions = []; /** * Document post data. * * Holds the document post data. * * @since 2.0.0 * @access protected * * @var \WP_Post WordPress post data. */ protected $post; /** * @param array $internal_elements * * @return array[] */ private function get_container_elements_data( array $internal_elements ): array { return [ [ 'id' => Utils::generate_random_string(), 'elType' => 'container', 'elements' => $internal_elements, ], ]; } /** * @param array $internal_elements * * @return array[] */ private function get_sections_elements_data( array $internal_elements ): array { return [ [ 'id' => Utils::generate_random_string(), 'elType' => 'section', 'elements' => [ [ 'id' => Utils::generate_random_string(), 'elType' => 'column', 'elements' => $internal_elements, ], ], ], ]; } /** * @since 2.1.0 * @access protected * @static */ protected static function get_editor_panel_categories() { return Plugin::$instance->elements_manager->get_categories(); } /** * Get properties. * * Retrieve the document properties. * * @since 2.0.0 * @access public * @static * * @return array Document properties. */ public static function get_properties() { return [ 'has_elements' => true, 'is_editable' => true, 'edit_capability' => '', 'show_in_finder' => true, 'show_on_admin_bar' => true, 'support_kit' => false, 'show_navigator' => true, 'allow_adding_widgets' => true, 'support_page_layout' => true, 'show_copy_and_share' => false, 'library_close_title' => esc_html__( 'Close', 'elementor' ), 'publish_button_title' => esc_html__( 'Publish', 'elementor' ), 'allow_closing_remote_library' => true, ]; } /** * @since 2.1.0 * @access public * @static */ public static function get_editor_panel_config() { $default_route = 'panel/elements/categories'; if ( ! Plugin::instance()->role_manager->user_can( 'design' ) ) { $default_route = 'panel/page-settings/settings'; } return [ 'title' => static::get_title(), // JS Container title. 'widgets_settings' => [], 'elements_categories' => self::get_filtered_editor_panel_categories(), 'default_route' => $default_route, 'has_elements' => static::get_property( 'has_elements' ), 'support_kit' => static::get_property( 'support_kit' ), 'messages' => [ 'publish_notification' => sprintf( /* translators: %s: Document title. */ esc_html__( 'Hurray! Your %s is live.', 'elementor' ), static::get_title() ), ], 'show_navigator' => static::get_property( 'show_navigator' ), 'allow_adding_widgets' => static::get_property( 'allow_adding_widgets' ), 'show_copy_and_share' => static::get_property( 'show_copy_and_share' ), 'library_close_title' => static::get_property( 'library_close_title' ), 'publish_button_title' => static::get_property( 'publish_button_title' ), 'allow_closing_remote_library' => static::get_property( 'allow_closing_remote_library' ), ]; } public static function get_filtered_editor_panel_categories(): array { $categories = static::get_editor_panel_categories(); $has_pro = Utils::has_pro(); foreach ( $categories as $index => $category ) { if ( isset( $category['promotion'] ) ) { $categories = self::get_panel_category_item( $category['promotion'], $index, $categories, $has_pro ); } } return $categories; } /** * @param $promotion * @param $index * @param array $categories * * @return array */ private static function get_panel_category_item( $promotion, $index, array $categories, bool $has_pro ): array { if ( ! $has_pro ) { $categories[ $index ]['promotion'] = Filtered_Promotions_Manager::get_filtered_promotion_data( $promotion, 'elementor/panel/' . $index . '/custom_promotion', 'url' ); } else { unset( $categories[ $index ]['promotion'] ); } return $categories; } /** * Get element title. * * Retrieve the element title. * * @since 2.0.0 * @access public * @static * * @return string Element title. */ public static function get_title() { return esc_html__( 'Document', 'elementor' ); } public static function get_plural_title() { return static::get_title(); } public static function get_add_new_title() { return sprintf( /* translators: %s: Document title. */ esc_html__( 'Add New %s', 'elementor' ), static::get_title() ); } /** * Get property. * * Retrieve the document property. * * @since 2.0.0 * @access public * @static * * @param string $key The property key. * * @return mixed The property value. */ public static function get_property( $key ) { $id = static::get_class_full_name(); if ( ! isset( self::$properties[ $id ] ) ) { self::$properties[ $id ] = static::get_properties(); } return self::get_items( self::$properties[ $id ], $key ); } /** * @since 2.0.0 * @access public * @static */ public static function get_class_full_name() { return get_called_class(); } public static function get_create_url() { $properties = static::get_properties(); // BC Support - Each document should define it own CPT this code is for BC support. $cpt = Source_Local::CPT; if ( isset( $properties['cpt'][0] ) ) { $cpt = $properties['cpt'][0]; } return Plugin::$instance->documents->get_create_new_post_url( $cpt, static::get_type() ); } public function get_name() { return static::get_type(); } /** * @since 2.0.0 * @access public */ public function get_unique_name() { return static::get_type() . '-' . $this->post->ID; } /** * @since 2.3.0 * @access public */ public function get_post_type_title() { $post_type_object = get_post_type_object( $this->post->post_type ); return $post_type_object->labels->singular_name; } /** * @since 2.0.0 * @access public */ public function get_main_id() { if ( ! $this->main_id ) { $post_id = $this->post->ID; $parent_post_id = wp_is_post_revision( $post_id ); if ( $parent_post_id ) { $post_id = $parent_post_id; } $this->main_id = $post_id; } return $this->main_id; } /** * @return null|Lock_Behavior */ public static function get_lock_behavior_v2() { return null; } /** * @since 2.0.0 * @access public * * @param $data * * @throws \Exception If the widget was not found. * * @return string */ public function render_element( $data ) { // Start buffering ob_start(); /** @var Widget_Base $widget */ $widget = Plugin::$instance->elements_manager->create_element_instance( $data ); if ( ! $widget ) { throw new \Exception( 'Widget not found.' ); } $widget->render_content(); $render_html = ob_get_clean(); return $render_html; } /** * @since 2.0.0 * @access public */ public function get_main_post() { return get_post( $this->get_main_id() ); } public function get_container_attributes() { $id = $this->get_main_id(); $attributes = [ 'data-elementor-type' => $this->get_name(), 'data-elementor-id' => $id, 'class' => 'elementor elementor-' . $id, ]; $version_meta = $this->get_main_meta( '_elementor_version' ); if ( version_compare( $version_meta, '2.5.0', '<' ) ) { $attributes['class'] .= ' elementor-bc-flex-widget'; } if ( Plugin::$instance->preview->is_preview() ) { $attributes['data-elementor-title'] = static::get_title(); } else { $elementor_settings = $this->get_frontend_settings(); if ( ! empty( $elementor_settings ) ) { $attributes['data-elementor-settings'] = wp_json_encode( $elementor_settings ); } } // apply this filter to allow the attributes to be modified by different sources return apply_filters( 'elementor/document/wrapper_attributes', $attributes, $this ); } /** * @since 2.0.0 * @access public */ public function get_wp_preview_url() { $main_post_id = $this->get_main_id(); $document = $this; // Ajax request from editor. $initial_document_id = Utils::get_super_global_value( $_POST, 'initial_document_id' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $initial_document_id ) ) { $document = Plugin::$instance->documents->get( $initial_document_id ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } $url = get_preview_post_link( $document->get_main_id(), [ 'preview_id' => $main_post_id, 'preview_nonce' => wp_create_nonce( 'post_preview_' . $main_post_id ), ] ); /** * Document "WordPress preview" URL. * * Filters the WordPress preview URL. * * @since 2.0.0 * * @param string $url WordPress preview URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/wp_preview', $url, $this ); return $url; } /** * @since 2.0.0 * @access public */ public function get_exit_to_dashboard_url() { $url = get_edit_post_link( $this->get_main_id(), 'raw' ); /** * Document "exit to dashboard" URL. * * Filters the "Exit To Dashboard" URL. * * @since 2.0.0 * * @param string $url The exit URL * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/exit_to_dashboard', $url, $this ); return $url; } /** * Get All Post Type URL * * Get url of the page which display all the posts of the current active document's post type. * * @since 3.7.0 * * @return string $url */ public function get_all_post_type_url() { $post_type = get_post_type( $this->get_main_id() ); $url = get_admin_url() . 'edit.php'; if ( 'post' !== $post_type ) { $url .= '?post_type=' . $post_type; } /** * Document "display all post type" URL. * * @since 3.7.0 * * @param string $url The URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/all_post_type', $url, $this ); return $url; } /** * Get Main WP dashboard URL. * * @since 3.7.0 * * @return string $url */ protected function get_main_dashboard_url() { $url = get_dashboard_url(); /** * Document "Main Dashboard" URL. * * @since 3.7.0 * * @param string $url The URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/main_dashboard', $url, $this ); return $url; } /** * Get auto-saved post revision. * * Retrieve the auto-saved post revision that is newer than current post. * * @since 2.0.0 * @access public * * * @return bool|Document */ public function get_newer_autosave() { $autosave = $this->get_autosave(); // Detect if there exists an autosave newer than the post. if ( $autosave && mysql2date( 'U', $autosave->get_post()->post_modified_gmt, false ) > mysql2date( 'U', $this->post->post_modified_gmt, false ) ) { return $autosave; } return false; } /** * @since 2.0.0 * @access public */ public function is_autosave() { return wp_is_post_autosave( $this->post->ID ); } /** * Check if the current document is a 'revision' * * @return bool */ public function is_revision() { return 'revision' === $this->post->post_type; } /** * Checks if the current document status is 'trash'. * * @return bool */ public function is_trash() { return 'trash' === $this->post->post_status; } /** * @since 2.0.0 * @access public * * @param int $user_id * @param bool $create * * @return bool|Document */ public function get_autosave( $user_id = 0, $create = false ) { if ( ! $user_id ) { $user_id = get_current_user_id(); } $autosave_id = $this->get_autosave_id( $user_id ); if ( $autosave_id ) { $document = Plugin::$instance->documents->get( $autosave_id ); } elseif ( $create ) { $autosave_id = wp_create_post_autosave( [ 'post_ID' => $this->post->ID, 'post_type' => $this->post->post_type, 'post_title' => $this->post->post_title, 'post_excerpt' => $this->post->post_excerpt, // Hack to cause $autosave_is_different=true in `wp_create_post_autosave`. 'post_content' => '<!-- Created With Elementor -->', 'post_modified' => current_time( 'mysql' ), ] ); Plugin::$instance->db->copy_elementor_meta( $this->post->ID, $autosave_id ); $document = Plugin::$instance->documents->get( $autosave_id ); $document->save_template_type(); } else { $document = false; } return $document; } /** * Add/Remove edit link in dashboard. * * Add or remove an edit link to the post/page action links on the post/pages list table. * * Fired by `post_row_actions` and `page_row_actions` filters. * * @access public * * @param array $actions An array of row action links. * * @return array An updated array of row action links. */ public function filter_admin_row_actions( $actions ) { if ( $this->is_built_with_elementor() && $this->is_editable_by_current_user() ) { $actions['edit_with_elementor'] = sprintf( '<a href="%1$s">%2$s</a>', $this->get_edit_url(), __( 'Edit with Elementor', 'elementor' ) ); } return $actions; } /** * @since 2.0.0 * @access public */ public function is_editable_by_current_user() { $edit_capability = static::get_property( 'edit_capability' ); if ( $edit_capability && ! current_user_can( $edit_capability ) ) { return false; } return self::get_property( 'is_editable' ) && User::is_current_user_can_edit( $this->get_main_id() ); } /** * @since 2.9.0 * @access protected */ protected function get_initial_config() { // Get document data *after* the scripts hook - so plugins can run compatibility before get data, but *before* enqueue the editor script - so elements can enqueue their own scripts that depended in editor script. $locked_user = Plugin::$instance->editor->get_locked_user( $this->get_main_id() ); if ( $locked_user ) { $locked_user = $locked_user->display_name; } $post = $this->get_main_post(); $post_type_object = get_post_type_object( $post->post_type ); $settings = SettingsManager::get_settings_managers_config(); $config = [ 'id' => $this->get_main_id(), 'type' => $this->get_name(), 'version' => $this->get_main_meta( '_elementor_version' ), 'settings' => $settings['page'], 'remoteLibrary' => $this->get_remote_library_config(), 'last_edited' => $this->get_last_edited(), 'panel' => static::get_editor_panel_config(), 'container' => 'body', 'post_type_title' => $this->get_post_type_title(), 'user' => [ 'can_publish' => current_user_can( $post_type_object->cap->publish_posts ), // Deprecated config since 2.9.0. 'locked' => $locked_user, ], 'urls' => [ 'exit_to_dashboard' => $this->get_exit_to_dashboard_url(), // WP post type edit page 'all_post_type' => $this->get_all_post_type_url(), 'preview' => $this->get_preview_url(), 'wp_preview' => $this->get_wp_preview_url(), 'permalink' => $this->get_permalink(), 'have_a_look' => $this->get_have_a_look_url(), 'main_dashboard' => $this->get_main_dashboard_url(), ], ]; $post_status_object = get_post_status_object( $post->post_status ); if ( $post_status_object ) { $config['status'] = [ 'value' => $post_status_object->name, 'label' => $post_status_object->label, ]; } do_action( 'elementor/document/before_get_config', $this ); if ( static::get_property( 'has_elements' ) ) { $container_config = []; $experiments_manager = Plugin::$instance->experiments; if ( $experiments_manager->is_feature_active( 'container' ) ) { $container_config = [ 'container' => Plugin::$instance->elements_manager->get_element_types( 'container' )->get_config(), ]; } $config['elements'] = $this->get_elements_raw_data( null, true ); $config['widgets'] = $container_config + Plugin::$instance->widgets_manager->get_widget_types_config(); } $additional_config = []; /** * Additional document configuration. * * Filters the document configuration by adding additional configuration. * External developers can use this hook to add custom configuration in * addition to Elementor's initial configuration. * * Use the $post_id to add custom configuration for different pages. * * @param array $additional_config The additional document configuration. * @param int $post_id The post ID of the document. */ $additional_config = apply_filters( 'elementor/document/config', $additional_config, $this->get_main_id() ); if ( ! empty( $additional_config ) ) { $config = array_replace_recursive( $config, $additional_config ); } return $config; } /** * @since 3.1.0 * @access protected */ protected function register_controls() { $this->register_document_controls(); /** * Register document controls. * * Fires after Elementor registers the document controls. * * External developers can use this hook to add new controls to the document. * * @since 2.0.0 * * @param Document $this The document instance. */ do_action( 'elementor/documents/register_controls', $this ); } /** * @since 2.0.0 * @access public * * @param $data * * @return bool */ public function save( $data ) { /** * Set locale to "C" to avoid issues with comma as decimal separator. * * @see https://github.com/elementor/elementor/issues/10992 */ $original_lc = setlocale( LC_NUMERIC, 0 ); setlocale( LC_NUMERIC, 'C' ); /** * Document save data. * * Filter the document data before saving process starts. * * External developers can use this hook to change the data before * saving it to the database. * * @since 3.3.0 * * @param array $data The document data. * @param \Elementor\Core\Base\Document $this The document instance. */ $data = apply_filters( 'elementor/document/save/data', $data, $this ); $this->add_handle_revisions_changed_filter(); if ( ! $this->is_editable_by_current_user() ) { return false; } $this->set_is_saving( true ); /** * Before document save. * * Fires when document save starts on Elementor. * * @since 2.5.12 * * @param \Elementor\Core\Base\Document $this The current document. * @param $data. */ do_action( 'elementor/document/before_save', $this, $data ); if ( ! current_user_can( 'unfiltered_html' ) ) { $data = wp_kses_post_deep( $data ); } if ( ! empty( $data['settings'] ) ) { if ( isset( $data['settings']['post_status'] ) && self::STATUS_AUTOSAVE === $data['settings']['post_status'] ) { if ( ! defined( 'DOING_AUTOSAVE' ) ) { define( 'DOING_AUTOSAVE', true ); } } $this->save_settings( $data['settings'] ); $this->refresh_post(); } // Don't check is_empty, because an empty array should be saved. if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) { $this->save_elements( $data['elements'] ); } $this->save_template_type(); $this->save_version(); // Remove Post CSS $post_css = Post_CSS::create( $this->post->ID ); $post_css->delete(); // Remove Document Cache $this->delete_cache(); /** * After document save. * * Fires when document save is complete. * * @since 2.5.12 * * @param \Elementor\Core\Base\Document $this The current document. * @param $data. */ do_action( 'elementor/document/after_save', $this, $data ); $this->set_is_saving( false ); $this->remove_handle_revisions_changed_filter(); setlocale( LC_NUMERIC, $original_lc ); return true; } public function refresh_post() { $this->post = get_post( $this->post->ID ); } /** * @param array $new_settings * * @return static */ public function update_settings( array $new_settings ) { $document_settings = $this->get_meta( PageManager::META_KEY ); if ( ! $document_settings ) { $document_settings = []; } $this->save_settings( array_replace_recursive( $document_settings, $new_settings ) ); return $this; } /** * Is built with Elementor. * * Check whether the post was built with Elementor. * * @since 2.0.0 * @access public * * @return bool Whether the post was built with Elementor. */ public function is_built_with_elementor() { return ! ! $this->get_meta( self::BUILT_WITH_ELEMENTOR_META_KEY ); } /** * Mark the post as "built with elementor" or not. * * @param bool $is_built_with_elementor * * @return $this */ public function set_is_built_with_elementor( $is_built_with_elementor ) { if ( $is_built_with_elementor ) { // Use the string `builder` and not a boolean for rollback compatibility $this->update_meta( self::BUILT_WITH_ELEMENTOR_META_KEY, 'builder' ); } else { $this->delete_meta( self::BUILT_WITH_ELEMENTOR_META_KEY ); } return $this; } /** * @since 2.0.0 * @access public * @static * * @return mixed */ public function get_edit_url() { $url = add_query_arg( [ 'post' => $this->get_main_id(), 'action' => 'elementor', ], admin_url( 'post.php' ) ); /** * Document edit url. * * Filters the document edit url. * * @since 2.0.0 * * @param string $url The edit url. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/edit', $url, $this ); return $url; } /** * @since 2.0.0 * @access public */ public function get_preview_url() { /** * Use a static var - to avoid change the `ver` parameter on every call. */ static $url; if ( empty( $url ) ) { add_filter( 'pre_option_permalink_structure', '__return_empty_string' ); $url = set_url_scheme( add_query_arg( [ 'elementor-preview' => $this->get_main_id(), 'ver' => time(), ], $this->get_permalink() ) ); remove_filter( 'pre_option_permalink_structure', '__return_empty_string' ); /** * Document preview URL. * * Filters the document preview URL. * * @since 2.0.0 * * @param string $url The preview URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/preview', $url, $this ); } return $url; } /** * @since 2.0.0 * @access public * * @param string $key * * @return array */ public function get_json_meta( $key ) { $meta = get_post_meta( $this->post->ID, $key, true ); if ( is_string( $meta ) && ! empty( $meta ) ) { $meta = json_decode( $meta, true ); } if ( empty( $meta ) ) { $meta = []; } return $meta; } public function update_json_meta( $key, $value ) { $this->update_meta( $key, // `wp_slash` in order to avoid the unslashing during the `update_post_meta` wp_slash( wp_json_encode( $value ) ) ); } /** * @since 2.0.0 * @access public * * @param null $data * @param bool $with_html_content * * @return array */ public function get_elements_raw_data( $data = null, $with_html_content = false ) { if ( ! static::get_property( 'has_elements' ) ) { return []; } if ( is_null( $data ) ) { $data = $this->get_elements_data(); } // Change the current documents, so widgets can use `documents->get_current` and other post data Plugin::$instance->documents->switch_to_document( $this ); $editor_data = []; foreach ( $data as $element_data ) { if ( ! is_array( $element_data ) ) { throw new \Exception( 'Invalid data: ' . wp_json_encode( [ 'data' => $data, 'element' => $element_data, ] ) ); } $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element ) { continue; } if ( $this->is_saving ) { $element_data = $element->get_data_for_save(); } else { $element_data = $element->get_raw_data( $with_html_content ); } $editor_data[] = $element_data; } // End foreach(). Plugin::$instance->documents->restore_document(); return $editor_data; } /** * @since 2.0.0 * @access public * * @param string $status * * @return array */ public function get_elements_data( $status = self::STATUS_PUBLISH ) { $elements = $this->get_json_meta( '_elementor_data' ); if ( self::STATUS_DRAFT === $status ) { $autosave = $this->get_newer_autosave(); if ( is_object( $autosave ) ) { $autosave_elements = Plugin::$instance->documents ->get( $autosave->get_post()->ID ) ->get_json_meta( '_elementor_data' ); } } if ( Plugin::$instance->editor->is_edit_mode() ) { if ( empty( $elements ) && empty( $autosave_elements ) ) { // Convert to Elementor. $elements = $this->convert_to_elementor(); if ( $this->is_autosave() ) { Plugin::$instance->db->copy_elementor_meta( $this->post->post_parent, $this->post->ID ); } } } if ( ! empty( $autosave_elements ) ) { $elements = $autosave_elements; } return $elements; } /** * Get document setting from DB. * * @return array */ public function get_db_document_settings() { return $this->get_meta( static::PAGE_META_KEY ); } /** * @since 2.3.0 * @access public */ public function convert_to_elementor() { $this->save( [] ); if ( empty( $this->post->post_content ) ) { return []; } // Check if it's only a shortcode. preg_match_all( '/' . get_shortcode_regex() . '/', $this->post->post_content, $matches, PREG_SET_ORDER ); if ( ! empty( $matches ) ) { foreach ( $matches as $shortcode ) { if ( trim( $this->post->post_content ) === $shortcode[0] ) { $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'shortcode' ); $settings = [ 'shortcode' => $this->post->post_content, ]; break; } } } if ( empty( $widget_type ) ) { $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'text-editor' ); $settings = [ 'editor' => $this->post->post_content, ]; } // TODO: Better coding to start template for editor $converted_blocks = [ [ 'id' => Utils::generate_random_string(), 'elType' => $widget_type::get_type(), 'widgetType' => $widget_type->get_name(), 'settings' => $settings, ], ]; return Plugin::$instance->experiments->is_feature_active( 'container' ) ? $this->get_container_elements_data( $converted_blocks ) : $this->get_sections_elements_data( $converted_blocks ); } /** * @since 2.1.3 * @access public */ public function print_elements_with_wrapper( $elements_data = null ) { if ( ! $elements_data ) { $elements_data = $this->get_elements_data(); } ?> <div <?php Utils::print_html_attributes( $this->get_container_attributes() ); ?>> <?php $this->print_elements( $elements_data ); ?> </div> <?php } /** * @since 2.0.0 * @access public */ public function get_css_wrapper_selector() { return ''; } /** * @since 2.0.0 * @access public */ public function get_panel_page_settings() { return [ 'title' => sprintf( /* translators: %s: Document title. */ esc_html__( '%s Settings', 'elementor' ), static::get_title() ), ]; } /** * @since 2.0.0 * @access public */ public function get_post() { return $this->post; } /** * @since 2.0.0 * @access public */ public function get_permalink() { return get_permalink( $this->get_main_id() ); } /** * @since 2.0.8 * @access public */ public function get_content( $with_css = false ) { return Plugin::$instance->frontend->get_builder_content( $this->post->ID, $with_css ); } /** * @since 2.0.0 * @access public */ public function delete() { if ( 'revision' === $this->post->post_type ) { $deleted = wp_delete_post_revision( $this->post ); } else { $deleted = wp_delete_post( $this->post->ID ); } return $deleted && ! is_wp_error( $deleted ); } public function force_delete() { $deleted = wp_delete_post( $this->post->ID, true ); return $deleted && ! is_wp_error( $deleted ); } /** * On import update dynamic content (e.g. post and term IDs). * * @since 3.8.0 * * @param array $config The config of the passed element. * @param array $data The data that requires updating/replacement when imported. * @param array|null $controls The available controls. * * @return array Element data. */ public static function on_import_update_dynamic_content( array $config, array $data, $controls = null ) : array { foreach ( $config as &$element_config ) { $element_instance = Plugin::$instance->elements_manager->create_element_instance( $element_config ); if ( is_null( $element_instance ) ) { continue; } if ( $element_instance->has_own_method( 'on_import_replace_dynamic_content' ) ) { // TODO: Remove this check in the future. $element_config = $element_instance::on_import_replace_dynamic_content( $element_config, $data['post_ids'] ); } else { $element_config = $element_instance::on_import_update_dynamic_content( $element_config, $data, $element_instance->get_controls() ); } $element_config['elements'] = static::on_import_update_dynamic_content( $element_config['elements'], $data ); } return $config; } /** * Update dynamic settings in the document for import. * * @param array $settings The settings of the document. * @param array $config Import config to update the settings. * * @return array */ public function on_import_update_settings( array $settings, array $config ): array { $controls = $this->get_controls(); $controls_manager = Plugin::$instance->controls_manager; foreach ( $settings as $key => $value ) { if ( ! isset( $controls[ $key ] ) ) { continue; } $control = $controls[ $key ]; $control_instance = $controls_manager->get_control( $control['type'] ); if ( ! $control_instance ) { continue; } $settings[ $key ] = $control_instance->on_import_update_settings( $value, $control, $config ); } return $settings; } /** * Save editor elements. * * Save data from the editor to the database. * * @since 2.0.0 * @access protected * * @param array $elements */ protected function save_elements( $elements ) { $editor_data = $this->get_elements_raw_data( $elements ); // We need the `wp_slash` in order to avoid the unslashing during the `update_post_meta` $json_value = wp_slash( wp_json_encode( $editor_data ) ); // Don't use `update_post_meta` that can't handle `revision` post type $is_meta_updated = update_metadata( 'post', $this->post->ID, '_elementor_data', $json_value ); /** * Before saving data. * * Fires before Elementor saves data to the database. * * @since 1.0.0 * * @param string $status Post status. * @param int|bool $is_meta_updated Meta ID if the key didn't exist, true on successful update, false on failure. */ do_action( 'elementor/db/before_save', $this->post->post_status, $is_meta_updated ); Plugin::$instance->db->save_plain_text( $this->post->ID ); $elements_iteration_actions = $this->get_elements_iteration_actions(); if ( $elements_iteration_actions ) { $this->iterate_elements( $elements, $elements_iteration_actions, 'save' ); } /** * After saving data. * * Fires after Elementor saves data to the database. * * @since 1.0.0 * * @param int $post_id The ID of the post. * @param array $editor_data Sanitize posted data. */ do_action( 'elementor/editor/after_save', $this->post->ID, $editor_data ); } /** * @since 2.0.0 * @access public * * @param int $user_id Optional. User ID. Default value is `0`. * * @return bool|int */ public function get_autosave_id( $user_id = 0 ) { if ( ! $user_id ) { $user_id = get_current_user_id(); } $autosave = Utils::get_post_autosave( $this->post->ID, $user_id ); if ( $autosave ) { return $autosave->ID; } return false; } public function save_version() { if ( ! defined( 'IS_ELEMENTOR_UPGRADE' ) ) { // Save per revision. $this->update_meta( '_elementor_version', ELEMENTOR_VERSION ); /** * Document version save. * * Fires when document version is saved on Elementor. * Will not fire during Elementor Upgrade. * * @since 2.5.12 * * @param \Elementor\Core\Base\Document $this The current document. * */ do_action( 'elementor/document/save_version', $this ); } } /** * @since 2.3.0 * @access public */ public function save_template_type() { return $this->update_main_meta( self::TYPE_META_KEY, $this->get_name() ); } /** * @since 2.3.0 * @access public */ public function get_template_type() { return $this->get_main_meta( self::TYPE_META_KEY ); } /** * @since 2.0.0 * @access public * * @param string $key Meta data key. * * @return mixed */ public function get_main_meta( $key ) { return get_post_meta( $this->get_main_id(), $key, true ); } /** * @since 2.0.4 * @access public * * @param string $key Meta data key. * @param mixed $value Meta data value. * * @return bool|int */ public function update_main_meta( $key, $value ) { return update_post_meta( $this->get_main_id(), $key, $value ); } /** * @since 2.0.4 * @access public * * @param string $key Meta data key. * @param string $value Optional. Meta data value. Default is an empty string. * * @return bool */ public function delete_main_meta( $key, $value = '' ) { return delete_post_meta( $this->get_main_id(), $key, $value ); } /** * @since 2.0.0 * @access public * * @param string $key Meta data key. * * @return mixed */ public function get_meta( $key ) { return get_post_meta( $this->post->ID, $key, true ); } /** * @since 2.0.0 * @access public * * @param string $key Meta data key. * @param mixed $value Meta data value. * * @return bool|int */ public function update_meta( $key, $value ) { // Use `update_metadata` in order to work also with revisions. return update_metadata( 'post', $this->post->ID, $key, $value ); } /** * @since 2.0.3 * @access public * * @param string $key Meta data key. * @param string $value Meta data value. * * @return bool */ public function delete_meta( $key, $value = '' ) { // Use `delete_metadata` in order to work also with revisions. return delete_metadata( 'post', $this->post->ID, $key, $value ); } /** * @since 2.0.0 * @access public */ public function get_last_edited() { $post = $this->post; $autosave_post = $this->get_autosave(); if ( $autosave_post ) { $post = $autosave_post->get_post(); } $date = date_i18n( _x( 'M j, H:i', 'revision date format', 'elementor' ), strtotime( $post->post_modified ) ); $display_name = get_the_author_meta( 'display_name', $post->post_author ); if ( $autosave_post || 'revision' === $post->post_type ) { $last_edited = sprintf( /* translators: 1: Saving date, 2: Author display name. */ esc_html__( 'Draft saved on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name ); } else { $last_edited = sprintf( /* translators: 1: Editing date, 2: Author display name. */ esc_html__( 'Last edited on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name ); } return $last_edited; } /** * @return bool */ public function is_saving() { return $this->is_saving; } /** * @param $is_saving * * @return $this */ public function set_is_saving( $is_saving ) { $this->is_saving = $is_saving; return $this; } /** * @since 2.0.0 * @access public * * @param array $data * * @throws \Exception If the post does not exist. */ public function __construct( array $data = [] ) { if ( $data ) { if ( empty( $data['post_id'] ) ) { $this->post = new \WP_Post( (object) [] ); } else { $this->post = get_post( $data['post_id'] ); if ( ! $this->post ) { throw new \Exception( sprintf( 'Post ID #%s does not exist.', $data['post_id'] ), Exceptions::NOT_FOUND ); } } // Each Control_Stack is based on a unique ID. $data['id'] = $data['post_id']; if ( ! isset( $data['settings'] ) ) { $data['settings'] = []; } $saved_settings = get_post_meta( $this->post->ID, '_elementor_page_settings', true ); if ( ! empty( $saved_settings ) && is_array( $saved_settings ) ) { $data['settings'] += $saved_settings; } } parent::__construct( $data ); } /* * Get Export Data * * Filters a document's data on export * * @since 3.2.0 * @access public * * @return array The data to export */ public function get_export_data() { $content = Plugin::$instance->db->iterate_data( $this->get_elements_data(), function( $element_data ) { $element_data['id'] = Utils::generate_random_string(); $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); // If the widget/element does not exist, like a plugin that creates a widget but deactivated. if ( ! $element ) { return null; } return $this->process_element_import_export( $element, 'on_export' ); } ); return [ 'content' => $content, 'settings' => $this->get_data( 'settings' ), 'metadata' => $this->get_export_metadata(), ]; } public function get_export_summary() { return [ 'title' => $this->post->post_title, 'doc_type' => $this->get_name(), 'thumbnail' => get_the_post_thumbnail_url( $this->post ), ]; } /* * Get Import Data * * Filters a document's data on import * * @since 3.2.0 * @access public * * @return array The data to import */ public function get_import_data( array $data ) { $data['content'] = Plugin::$instance->db->iterate_data( $data['content'], function( $element_data ) { $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); // If the widget/element isn't exist, like a plugin that creates a widget but deactivated if ( ! $element ) { return null; } return $this->process_element_import_export( $element, 'on_import' ); } ); if ( ! empty( $data['settings'] ) ) { $template_model = new Page_Model( [ 'id' => 0, 'settings' => $data['settings'], ] ); $page_data = $this->process_element_import_export( $template_model, 'on_import' ); $data['settings'] = $page_data['settings']; } return $data; } /** * Import * * Allows to import an external data to a document * * @since 3.2.0 * @access public * * @param array $data */ public function import( array $data ) { $data = $this->get_import_data( $data ); $this->save( [ 'elements' => $data['content'], 'settings' => $data['settings'], ] ); if ( $data['import_settings']['thumbnail'] ) { $attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import( [ 'url' => $data['import_settings']['thumbnail'] ] ); set_post_thumbnail( $this->get_main_post(), $attachment['id'] ); } if ( ! empty( $data['metadata'] ) ) { foreach ( $data['metadata'] as $key => $value ) { $this->update_meta( $key, $value ); } } } public function process_element_import_export( Controls_Stack $element, $method, $element_data = null ) { if ( null === $element_data ) { $element_data = $element->get_data(); } if ( method_exists( $element, $method ) ) { // TODO: Use the internal element data without parameters. $element_data = $element->{$method}( $element_data ); } foreach ( $element->get_controls() as $control ) { $control_class = Plugin::$instance->controls_manager->get_control( $control['type'] ); // If the control isn't exist, like a plugin that creates the control but deactivated. if ( ! $control_class ) { return $element_data; } // Do not add default value to the final settings, if there is no value at the // data before the methods `on_import` or `on_export` called. $has_value = isset( $element_data['settings'][ $control['name'] ] ); if ( $has_value && method_exists( $control_class, $method ) ) { $element_data['settings'][ $control['name'] ] = $control_class->{$method}( $element_data['settings'][ $control['name'] ], $control ); } // On Export, check if the control has an argument 'export' => false. if ( 'on_export' === $method && isset( $control['export'] ) && false === $control['export'] ) { unset( $element_data['settings'][ $control['name'] ] ); } } return $element_data; } protected function get_export_metadata() { $metadata = get_post_meta( $this->get_main_id() ); foreach ( $metadata as $meta_key => $meta_value ) { if ( is_protected_meta( $meta_key, 'post' ) ) { unset( $metadata[ $meta_key ] ); continue; } $metadata[ $meta_key ] = $meta_value[0]; } return $metadata; } protected function get_remote_library_config() { $config = [ 'type' => 'block', 'default_route' => 'templates/blocks', 'category' => $this->get_name(), 'autoImportSettings' => false, ]; return $config; } /** * @since 2.0.4 * @access protected * * @param $settings */ protected function save_settings( $settings ) { $page_settings_manager = SettingsManager::get_settings_managers( 'page' ); $page_settings_manager->ajax_before_save_settings( $settings, $this->post->ID ); $page_settings_manager->save_settings( $settings, $this->post->ID ); } /** * @since 2.1.3 * @access protected */ protected function print_elements( $elements_data ) { if ( ! Plugin::$instance->experiments->is_feature_active( 'e_element_cache' ) ) { $this->do_print_elements( $elements_data ); return; } $cached_data = $this->get_document_cache(); if ( false === $cached_data ) { add_filter( 'elementor/element/should_render_shortcode', '__return_true' ); $scripts_to_queue = []; $styles_to_queue = []; global $wp_scripts, $wp_styles; $should_store_scripts = $wp_scripts instanceof \WP_Scripts && $wp_styles instanceof \WP_Styles; if ( $should_store_scripts ) { $scripts_ignored = $wp_scripts->queue; $styles_ignored = $wp_styles->queue; } ob_start(); $this->do_print_elements( $elements_data ); if ( $should_store_scripts ) { $scripts_to_queue = array_values( array_diff( $wp_scripts->queue, $scripts_ignored ) ); $styles_to_queue = array_values( array_diff( $wp_styles->queue, $styles_ignored ) ); } $cached_data = [ 'content' => ob_get_clean(), 'scripts' => $scripts_to_queue, 'styles' => $styles_to_queue, ]; if ( $this->should_store_cache_elements() ) { $this->set_document_cache( $cached_data ); } remove_filter( 'elementor/element/should_render_shortcode', '__return_true' ); } else { if ( ! empty( $cached_data['scripts'] ) ) { foreach ( $cached_data['scripts'] as $script_handle ) { wp_enqueue_script( $script_handle ); } } if ( ! empty( $cached_data['styles'] ) ) { foreach ( $cached_data['styles'] as $style_handle ) { wp_enqueue_style( $style_handle ); } } } if ( ! empty( $cached_data['content'] ) ) { echo do_shortcode( $cached_data['content'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } protected function do_print_elements( $elements_data ) { // Collect all data updaters that should be updated on runtime. $runtime_elements_iteration_actions = $this->get_runtime_elements_iteration_actions(); if ( $runtime_elements_iteration_actions ) { $this->iterate_elements( $elements_data, $runtime_elements_iteration_actions, 'render' ); } foreach ( $elements_data as $element_data ) { $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element ) { continue; } $element->print_element(); } } public function set_document_cache( $value ) { $expiration_hours = get_option( 'elementor_element_cache_ttl', '' ); if ( empty( $expiration_hours ) || ! is_numeric( $expiration_hours ) ) { $expiration_hours = '24'; } $expiration_hours = absint( $expiration_hours ); $expiration = '+' . $expiration_hours . ' hours'; $data = [ 'timeout' => strtotime( $expiration, current_time( 'timestamp' ) ), 'value' => $value, ]; $this->update_json_meta( static::CACHE_META_KEY, $data ); } private function get_document_cache() { $cache = $this->get_json_meta( static::CACHE_META_KEY ); if ( empty( $cache['timeout'] ) ) { return false; } if ( current_time( 'timestamp' ) > $cache['timeout'] ) { return false; } if ( ! is_array( $cache['value'] ) ) { return false; } return $cache['value']; } protected function delete_cache() { $this->delete_meta( static::CACHE_META_KEY ); } private function should_store_cache_elements() { static $should_store_cache_elements = null; if ( null === $should_store_cache_elements ) { $should_store_cache_elements = ( ! is_admin() && ! Plugin::$instance->preview->is_preview_mode() ); } return $should_store_cache_elements; } protected function register_document_controls() { $this->start_controls_section( 'document_settings', [ 'label' => esc_html__( 'General Settings', 'elementor' ), 'tab' => Controls_Manager::TAB_SETTINGS, ] ); $this->add_control( 'post_title', [ 'label' => esc_html__( 'Title', 'elementor' ), 'type' => Controls_Manager::TEXT, 'default' => $this->post->post_title, 'label_block' => true, ] ); $post_type_object = get_post_type_object( $this->post->post_type ); $can_publish = $post_type_object && current_user_can( $post_type_object->cap->publish_posts ); $is_published = self::STATUS_PUBLISH === $this->post->post_status || self::STATUS_PRIVATE === $this->post->post_status; if ( $is_published || $can_publish || ! Plugin::$instance->editor->is_edit_mode() ) { $statuses = $this->get_post_statuses(); if ( 'future' === $this->get_main_post()->post_status ) { $statuses['future'] = esc_html__( 'Future', 'elementor' ); } $this->add_control( 'post_status', [ 'label' => esc_html__( 'Status', 'elementor' ), 'type' => Controls_Manager::SELECT, 'default' => $this->get_main_post()->post_status, 'options' => $statuses, ] ); } $this->end_controls_section(); } protected function get_post_statuses() { return get_post_statuses(); } protected function get_have_a_look_url() { return $this->get_permalink(); } public function handle_revisions_changed( $post_has_changed, $last_revision, $post ) { // In case default, didn't determine the changes. if ( ! $post_has_changed ) { $last_revision_id = $last_revision->ID; $last_revision_document = Plugin::instance()->documents->get( $last_revision_id ); $post_document = Plugin::instance()->documents->get( $post->ID ); $last_revision_settings = $last_revision_document->get_settings(); $post_settings = $post_document->get_settings(); // TODO: Its better to add crc32 signature for each revision and then only compare one part of the checksum. $post_has_changed = $last_revision_settings !== $post_settings; } return $post_has_changed; } private function add_handle_revisions_changed_filter() { add_filter( 'wp_save_post_revision_post_has_changed', [ $this, 'handle_revisions_changed' ], 10, 3 ); } private function remove_handle_revisions_changed_filter() { remove_filter( 'wp_save_post_revision_post_has_changed', [ $this, 'handle_revisions_changed' ] ); } private function get_runtime_elements_iteration_actions() { $runtime_elements_iteration_actions = []; $elements_iteration_actions = $this->get_elements_iteration_actions(); foreach ( $elements_iteration_actions as $elements_iteration_action ) { if ( $elements_iteration_action->is_action_needed() ) { $runtime_elements_iteration_actions[] = $elements_iteration_action; } } return $runtime_elements_iteration_actions; } private function iterate_elements( $elements, $elements_iteration_actions, $mode ) { $unique_page_elements = []; foreach ( $elements_iteration_actions as $elements_iteration_action ) { $elements_iteration_action->set_mode( $mode ); } Plugin::$instance->db->iterate_data( $elements, function( array $element_data ) use ( &$unique_page_elements, $elements_iteration_actions ) { $element_type = 'widget' === $element_data['elType'] ? $element_data['widgetType'] : $element_data['elType']; $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( $element ) { if ( ! in_array( $element_type, $unique_page_elements, true ) ) { $unique_page_elements[] = $element_type; foreach ( $elements_iteration_actions as $elements_iteration_action ) { $elements_iteration_action->unique_element_action( $element ); } } foreach ( $elements_iteration_actions as $elements_iteration_action ) { $elements_iteration_action->element_action( $element ); } } return $element_data; } ); foreach ( $elements_iteration_actions as $elements_iteration_action ) { $elements_iteration_action->after_elements_iteration(); } } private function get_elements_iteration_actions() { if ( ! $this->elements_iteration_actions ) { $this->elements_iteration_actions[] = new Assets_Iteration_Action( $this ); } return $this->elements_iteration_actions; } } background-process/wp-async-request.php 0000644 00000005605 14722104033 0014301 0 ustar 00 <?php namespace Elementor\Core\Base\BackgroundProcess; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * https://github.com/A5hleyRich/wp-background-processing GPL v2.0 * * WP Async Request * * @package WP-Background-Processing */ /** * Abstract WP_Async_Request class. * * @abstract */ abstract class WP_Async_Request { /** * Prefix * * (default value: 'wp') * * @var string * @access protected */ protected $prefix = 'wp'; /** * Action * * (default value: 'async_request') * * @var string * @access protected */ protected $action = 'async_request'; /** * Identifier * * @var mixed * @access protected */ protected $identifier; /** * Data * * (default value: array()) * * @var array * @access protected */ protected $data = array(); /** * Initiate new async request */ public function __construct() { $this->identifier = $this->prefix . '_' . $this->action; add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); } /** * Set data used during the request * * @param array $data Data. * * @return $this */ public function data( $data ) { $this->data = $data; return $this; } /** * Dispatch the async request * * @return array|\WP_Error */ public function dispatch() { $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); $args = $this->get_post_args(); return wp_remote_post( esc_url_raw( $url ), $args ); } /** * Get query args * * @return array */ protected function get_query_args() { if ( property_exists( $this, 'query_args' ) ) { return $this->query_args; } return array( 'action' => $this->identifier, 'nonce' => wp_create_nonce( $this->identifier ), ); } /** * Get query URL * * @return string */ protected function get_query_url() { if ( property_exists( $this, 'query_url' ) ) { return $this->query_url; } return admin_url( 'admin-ajax.php' ); } /** * Get post args * * @return array */ protected function get_post_args() { if ( property_exists( $this, 'post_args' ) ) { return $this->post_args; } return array( 'timeout' => 0.01, 'blocking' => false, 'body' => $this->data, 'cookies' => $_COOKIE, /** This filter is documented in wp-includes/class-wp-http-streams.php */ 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), ); } /** * Maybe handle * * Check for correct nonce and pass to handler. */ public function maybe_handle() { // Don't lock up other requests while processing session_write_close(); check_ajax_referer( $this->identifier, 'nonce' ); $this->handle(); wp_die(); } /** * Handle * * Override this method to perform any actions required * during the async request. */ abstract protected function handle(); } background-process/wp-background-process.php 0000644 00000025510 14722104033 0015266 0 ustar 00 <?php namespace Elementor\Core\Base\BackgroundProcess; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * https://github.com/A5hleyRich/wp-background-processing GPL v2.0 * * WP Background Process * * @package WP-Background-Processing */ /** * Abstract WP_Background_Process class. * * @abstract * @extends WP_Async_Request */ abstract class WP_Background_Process extends WP_Async_Request { /** * Action * * (default value: 'background_process') * * @var string * @access protected */ protected $action = 'background_process'; /** * Start time of current process. * * (default value: 0) * * @var int * @access protected */ protected $start_time = 0; /** * Cron_hook_identifier * * @var mixed * @access protected */ protected $cron_hook_identifier; /** * Cron_interval_identifier * * @var mixed * @access protected */ protected $cron_interval_identifier; /** * Initiate new background process */ public function __construct() { parent::__construct(); $this->cron_hook_identifier = $this->identifier . '_cron'; $this->cron_interval_identifier = $this->identifier . '_cron_interval'; add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) ); add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) ); } /** * Dispatch * * @access public * @return array|\WP_Error */ public function dispatch() { // Schedule the cron healthcheck. $this->schedule_event(); // Perform remote post. return parent::dispatch(); } /** * Push to queue * * @param mixed $data Data. * * @return $this */ public function push_to_queue( $data ) { $this->data[] = $data; return $this; } /** * Save queue * * @return $this */ public function save() { $key = $this->generate_key(); if ( ! empty( $this->data ) ) { update_site_option( $key, $this->data ); } return $this; } /** * Update queue * * @param string $key Key. * @param array $data Data. * * @return $this */ public function update( $key, $data ) { if ( ! empty( $data ) ) { update_site_option( $key, $data ); } return $this; } /** * Delete queue * * @param string $key Key. * * @return $this */ public function delete( $key ) { delete_site_option( $key ); return $this; } /** * Generate key * * Generates a unique key based on microtime. Queue items are * given a unique key so that they can be merged upon save. * * @param int $length Length. * * @return string */ protected function generate_key( $length = 64 ) { $unique = md5( microtime() . rand() ); $prepend = $this->identifier . '_batch_'; return substr( $prepend . $unique, 0, $length ); } /** * Maybe process queue * * Checks whether data exists within the queue and that * the process is not already running. */ public function maybe_handle() { // Don't lock up other requests while processing session_write_close(); if ( $this->is_process_running() ) { // Background process already running. wp_die(); } if ( $this->is_queue_empty() ) { // No data to process. wp_die(); } check_ajax_referer( $this->identifier, 'nonce' ); $this->handle(); wp_die(); } /** * Is queue empty * * @return bool */ protected function is_queue_empty() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // Can't use placeholders for table/column names, it will be wrapped by a single quote (') instead of a backquote (`). $count = $wpdb->get_var( $wpdb->prepare( " SELECT COUNT(*) FROM {$table} WHERE {$column} LIKE %s ", $key ) ); // phpcs:enable return ( $count > 0 ) ? false : true; } /** * Is process running * * Check whether the current process is already running * in a background process. */ protected function is_process_running() { if ( get_site_transient( $this->identifier . '_process_lock' ) ) { // Process already running. return true; } return false; } /** * Lock process * * Lock the process so that multiple instances can't run simultaneously. * Override if applicable, but the duration should be greater than that * defined in the time_exceeded() method. */ protected function lock_process() { $this->start_time = time(); // Set start time of current process. $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration ); set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration ); } /** * Unlock process * * Unlock the process so that other instances can spawn. * * @return $this */ protected function unlock_process() { delete_site_transient( $this->identifier . '_process_lock' ); return $this; } /** * Get batch * * @return \stdClass Return the first batch from the queue */ protected function get_batch() { global $wpdb; $table = $wpdb->options; $column = 'option_name'; $key_column = 'option_id'; $value_column = 'option_value'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; $key_column = 'meta_id'; $value_column = 'meta_value'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared // Can't use placeholders for table/column names, it will be wrapped by a single quote (') instead of a backquote (`). $query = $wpdb->get_row( $wpdb->prepare( " SELECT * FROM {$table} WHERE {$column} LIKE %s ORDER BY {$key_column} ASC LIMIT 1 ", $key ) ); // phpcs:enable $batch = new \stdClass(); $batch->key = $query->$column; $batch->data = maybe_unserialize( $query->$value_column ); return $batch; } /** * Handle * * Pass each queue item to the task handler, while remaining * within server memory and time limit constraints. */ protected function handle() { $this->lock_process(); do { $batch = $this->get_batch(); foreach ( $batch->data as $key => $value ) { $task = $this->task( $value ); if ( false !== $task ) { $batch->data[ $key ] = $task; } else { unset( $batch->data[ $key ] ); } if ( $this->time_exceeded() || $this->memory_exceeded() ) { // Batch limits reached. break; } } // Update or delete current batch. if ( ! empty( $batch->data ) ) { $this->update( $batch->key, $batch->data ); } else { $this->delete( $batch->key ); } } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() ); $this->unlock_process(); // Start next batch or complete process. if ( ! $this->is_queue_empty() ) { $this->dispatch(); } else { $this->complete(); } wp_die(); } /** * Memory exceeded * * Ensures the batch process never exceeds 90% * of the maximum WordPress memory. * * @return bool */ protected function memory_exceeded() { $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory $current_memory = memory_get_usage( true ); $return = false; if ( $current_memory >= $memory_limit ) { $return = true; } return apply_filters( $this->identifier . '_memory_exceeded', $return ); } /** * Get memory limit * * @return int */ protected function get_memory_limit() { if ( function_exists( 'ini_get' ) ) { $memory_limit = ini_get( 'memory_limit' ); } else { // Sensible default. $memory_limit = '128M'; } if ( ! $memory_limit || -1 === intval( $memory_limit ) ) { // Unlimited, set to 32GB. $memory_limit = '32000M'; } return intval( $memory_limit ) * 1024 * 1024; } /** * Time exceeded. * * Ensures the batch never exceeds a sensible time limit. * A timeout limit of 30s is common on shared hosting. * * @return bool */ protected function time_exceeded() { $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds $return = false; if ( time() >= $finish ) { $return = true; } return apply_filters( $this->identifier . '_time_exceeded', $return ); } /** * Complete. * * Override if applicable, but ensure that the below actions are * performed, or, call parent::complete(). */ protected function complete() { // Unschedule the cron healthcheck. $this->clear_scheduled_event(); } /** * Schedule cron healthcheck * * @access public * @param mixed $schedules Schedules. * @return mixed */ public function schedule_cron_healthcheck( $schedules ) { $interval = apply_filters( $this->identifier . '_cron_interval', 5 ); if ( property_exists( $this, 'cron_interval' ) ) { $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval ); } // Adds every 5 minutes to the existing schedules. $schedules[ $this->identifier . '_cron_interval' ] = array( 'interval' => MINUTE_IN_SECONDS * $interval, 'display' => sprintf( /* translators: %d: Interval in minutes. */ esc_html__( 'Every %d minutes', 'elementor' ), $interval, ), ); return $schedules; } /** * Handle cron healthcheck * * Restart the background process if not already running * and data exists in the queue. */ public function handle_cron_healthcheck() { if ( $this->is_process_running() ) { // Background process already running. exit; } if ( $this->is_queue_empty() ) { // No data to process. $this->clear_scheduled_event(); exit; } $this->handle(); exit; } /** * Schedule event */ protected function schedule_event() { if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier ); } } /** * Clear scheduled event */ protected function clear_scheduled_event() { $timestamp = wp_next_scheduled( $this->cron_hook_identifier ); if ( $timestamp ) { wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); } } /** * Cancel Process * * Stop processing queue items, clear cronjob and delete batch. * */ public function cancel_process() { if ( ! $this->is_queue_empty() ) { $batch = $this->get_batch(); $this->delete( $batch->key ); wp_clear_scheduled_hook( $this->cron_hook_identifier ); } } /** * Task * * Override this method to perform any actions required on each * queue item. Return the modified item for further processing * in the next pass through. Or, return false to remove the * item from the queue. * * @param mixed $item Queue item to iterate over. * * @return mixed */ abstract protected function task( $item ); } db-upgrades-manager.php 0000644 00000013751 14722104033 0011065 0 ustar 00 <?php namespace Elementor\Core\Base; use Elementor\Core\Admin\Admin_Notices; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class DB_Upgrades_Manager extends Background_Task_Manager { protected $current_version = null; protected $query_limit = 100; abstract public function get_new_version(); abstract public function get_version_option_name(); abstract public function get_upgrades_class(); abstract public function get_updater_label(); public function get_task_runner_class() { return 'Elementor\Core\Upgrade\Updater'; } public function get_query_limit() { return $this->query_limit; } public function set_query_limit( $limit ) { $this->query_limit = $limit; } public function get_current_version() { if ( null === $this->current_version ) { $this->current_version = get_option( $this->get_version_option_name() ); } return $this->current_version; } public function should_upgrade() { $current_version = $this->get_current_version(); // It's a new install. if ( ! $current_version ) { $this->update_db_version(); return false; } return version_compare( $this->get_new_version(), $current_version, '>' ); } public function on_runner_start() { parent::on_runner_start(); if ( ! defined( 'IS_ELEMENTOR_UPGRADE' ) ) { define( 'IS_ELEMENTOR_UPGRADE', true ); } } public function on_runner_complete( $did_tasks = false ) { $logger = Plugin::$instance->logger->get_logger(); $logger->info( 'Elementor data updater process has been completed.', [ 'meta' => [ 'plugin' => $this->get_plugin_label(), 'from' => $this->current_version, 'to' => $this->get_new_version(), ], ] ); $this->clear_cache(); $this->update_db_version(); if ( $did_tasks ) { $this->add_flag( 'completed' ); } } protected function clear_cache() { Plugin::$instance->files_manager->clear_cache(); } public function admin_notice_start_upgrade() { /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' ); $options = [ 'title' => $this->get_updater_label(), 'description' => esc_html__( 'Your site database needs to be updated to the latest version.', 'elementor' ), 'type' => 'error', 'icon' => false, 'button' => [ 'text' => esc_html__( 'Update Now', 'elementor' ), 'url' => $this->get_start_action_url(), 'class' => 'e-button e-button--cta', ], ]; $admin_notices->print_admin_notice( $options ); } public function admin_notice_upgrade_is_running() { /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' ); $options = [ 'title' => $this->get_updater_label(), 'description' => esc_html__( 'Database update process is running in the background. Taking a while?', 'elementor' ), 'type' => 'warning', 'icon' => false, 'button' => [ 'text' => esc_html__( 'Click here to run it now', 'elementor' ), 'url' => $this->get_continue_action_url(), 'class' => 'e-button e-button--primary', ], ]; $admin_notices->print_admin_notice( $options ); } public function admin_notice_upgrade_is_completed() { $this->delete_flag( 'completed' ); $message = esc_html__( 'The database update process is now complete. Thank you for updating to the latest version!', 'elementor' ); /** * @var Admin_Notices $admin_notices */ $admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' ); $options = [ 'description' => '<b>' . $this->get_updater_label() . '</b> - ' . $message, 'type' => 'success', 'icon' => false, ]; $admin_notices->print_admin_notice( $options ); } /** * @access protected */ protected function start_run() { $updater = $this->get_task_runner(); if ( $updater->is_running() ) { return; } $upgrade_callbacks = $this->get_upgrade_callbacks(); if ( empty( $upgrade_callbacks ) ) { $this->on_runner_complete(); return; } $this->clear_cache(); foreach ( $upgrade_callbacks as $callback ) { $updater->push_to_queue( [ 'callback' => $callback, ] ); } $updater->save()->dispatch(); Plugin::$instance->logger->get_logger()->info( 'Elementor data updater process has been queued.', [ 'meta' => [ 'plugin' => $this->get_plugin_label(), 'from' => $this->current_version, 'to' => $this->get_new_version(), ], ] ); } protected function update_db_version() { update_option( $this->get_version_option_name(), $this->get_new_version() ); } public function get_upgrade_callbacks() { $prefix = '_v_'; $upgrades_class = $this->get_upgrades_class(); $upgrades_reflection = new \ReflectionClass( $upgrades_class ); $callbacks = []; foreach ( $upgrades_reflection->getMethods() as $method ) { $method_name = $method->getName(); if ( '_on_each_version' === $method_name ) { $callbacks[] = [ $upgrades_class, $method_name ]; continue; } if ( false === strpos( $method_name, $prefix ) ) { continue; } if ( ! preg_match_all( "/$prefix(\d+_\d+_\d+)/", $method_name, $matches ) ) { continue; } $method_version = str_replace( '_', '.', $matches[1][0] ); if ( ! version_compare( $method_version, $this->current_version, '>' ) ) { continue; } $callbacks[] = [ $upgrades_class, $method_name ]; } return $callbacks; } public function __construct() { // If upgrade is completed - show the notice only for admins. // Note: in this case `should_upgrade` returns false, because it's already upgraded. if ( is_admin() && current_user_can( 'update_plugins' ) && $this->get_flag( 'completed' ) ) { add_action( 'admin_notices', [ $this, 'admin_notice_upgrade_is_completed' ] ); } if ( ! $this->should_upgrade() ) { return; } $updater = $this->get_task_runner(); $this->start_run(); if ( $updater->is_running() && current_user_can( 'update_plugins' ) ) { add_action( 'admin_notices', [ $this, 'admin_notice_upgrade_is_running' ] ); } parent::__construct(); } } base-object.php 0000644 00000013064 14722104033 0007433 0 ustar 00 <?php namespace Elementor\Core\Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * Base Object * * Base class that provides basic settings handling functionality. * * @since 2.3.0 */ class Base_Object { /** * Settings. * * Holds the object settings. * * @access private * * @var array */ private $settings; /** * Get Settings. * * @since 2.3.0 * @access public * * @param string $setting Optional. The key of the requested setting. Default is null. * * @return mixed An array of all settings, or a single value if `$setting` was specified. */ final public function get_settings( $setting = null ) { $this->ensure_settings(); return self::get_items( $this->settings, $setting ); } /** * Set settings. * * @since 2.3.0 * @access public * * @param array|string $key If key is an array, the settings are overwritten by that array. Otherwise, the * settings of the key will be set to the given `$value` param. * * @param mixed $value Optional. Default is null. */ final public function set_settings( $key, $value = null ) { $this->ensure_settings(); if ( is_array( $key ) ) { $this->settings = $key; } else { $this->settings[ $key ] = $value; } } /** * Delete setting. * * Deletes the settings array or a specific key of the settings array if `$key` is specified. * @since 2.3.0 * @access public * * @param string $key Optional. Default is null. */ public function delete_setting( $key = null ) { if ( $key ) { unset( $this->settings[ $key ] ); } else { $this->settings = []; } } final public function merge_properties( array $default_props, array $custom_props, array $allowed_props_keys = [] ) { $props = array_replace_recursive( $default_props, $custom_props ); if ( $allowed_props_keys ) { $props = array_intersect_key( $props, array_flip( $allowed_props_keys ) ); } return $props; } /** * Get items. * * Utility method that receives an array with a needle and returns all the * items that match the needle. If needle is not defined the entire haystack * will be returned. * * @since 2.3.0 * @access protected * @static * * @param array $haystack An array of items. * @param string $needle Optional. Needle. Default is null. * * @return mixed The whole haystack or the needle from the haystack when requested. */ final protected static function get_items( array $haystack, $needle = null ) { if ( $needle ) { return isset( $haystack[ $needle ] ) ? $haystack[ $needle ] : null; } return $haystack; } /** * Get init settings. * * Used to define the default/initial settings of the object. Inheriting classes may implement this method to define * their own default/initial settings. * * @since 2.3.0 * @access protected * * @return array */ protected function get_init_settings() { return []; } /** * Ensure settings. * * Ensures that the `$settings` member is initialized * * @since 2.3.0 * @access private */ private function ensure_settings() { if ( null === $this->settings ) { $this->settings = $this->get_init_settings(); } } /** * Has Own Method * * Used for check whether the method passed as a parameter was declared in the current instance or inherited. * If a base_class_name is passed, it checks whether the method was declared in that class. If the method's * declaring class is the class passed as $base_class_name, it returns false. Otherwise (method was NOT declared * in $base_class_name), it returns true. * * Example #1 - only $method_name is passed: * The initial declaration of `register_controls()` happens in the `Controls_Stack` class. However, all * widgets which have their own controls declare this function as well, overriding the original * declaration. If `has_own_method()` would be called by a Widget's class which implements `register_controls()`, * with 'register_controls' passed as the first parameter - `has_own_method()` will return true. If the Widget * does not declare `register_controls()`, `has_own_method()` will return false. * * Example #2 - both $method_name and $base_class_name are passed * In this example, the widget class inherits from a base class `Widget_Base`, and the base implements * `register_controls()` to add certain controls to all widgets inheriting from it. `has_own_method()` is called by * the widget, with the string 'register_controls' passed as the first parameter, and 'Elementor\Widget_Base' (its full name * including the namespace) passed as the second parameter. If the widget class implements `register_controls()`, * `has_own_method` will return true. If the widget class DOESN'T implement `register_controls()`, it will return * false (because `Widget_Base` is the declaring class for `register_controls()`, and not the class that called * `has_own_method()`). * * @since 3.1.0 * * @param string $method_name * @param string $base_class_name * * @return bool True if the method was declared by the current instance, False if it was inherited. */ public function has_own_method( $method_name, $base_class_name = null ) { try { $reflection_method = new \ReflectionMethod( $this, $method_name ); // If a ReflectionMethod is successfully created, get its declaring class. $declaring_class = $reflection_method->getDeclaringClass(); } catch ( \Exception $e ) { return false; } if ( $base_class_name ) { return $base_class_name !== $declaring_class->name; } return get_called_class() === $declaring_class->name; } } elements-iteration-actions/base.php 0000644 00000003307 14722104033 0013434 0 ustar 00 <?php namespace Elementor\Core\Base\Elements_Iteration_Actions; use Elementor\Element_Base; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } abstract class Base { /** * The current document that the Base class instance was created from. */ protected $document; /** * Indicates if the methods are being triggered on page save or at render time (value will be either 'save' or 'render'). * * @var string */ protected $mode = ''; /** * Is Action Needed. * * Runs only at runtime and used as a flag to determine if all methods should run on page render. * If returns false, all methods will run only on page save. * If returns true, all methods will run on both page render and on save. * * @since 3.3.0 * @access public * * @return bool */ abstract public function is_action_needed(); /** * Unique Element Action. * * Will be triggered for each unique page element - section / column / widget unique type (heading, icon etc.). * * @since 3.3.0 * @access public * * @return void */ public function unique_element_action( Element_Base $element_data ) {} /** * Element Action. * * Will be triggered for each page element - section / column / widget. * * @since 3.3.0 * @access public * * @return void */ public function element_action( Element_Base $element_data ) {} /** * After Elements Iteration. * * Will be triggered after all page elements iteration has ended. * * @since 3.3.0 * @access public * * @return void */ public function after_elements_iteration() {} public function set_mode( $mode ) { $this->mode = $mode; } public function __construct( $document ) { $this->document = $document; } } elements-iteration-actions/assets.php 0000644 00000012637 14722104033 0014032 0 ustar 00 <?php namespace Elementor\Core\Base\Elements_Iteration_Actions; use Elementor\Conditions; use Elementor\Element_Base; use Elementor\Plugin; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } class Assets extends Base { const ASSETS_META_KEY = '_elementor_page_assets'; // Default value must be empty. private $page_assets; // Default value must be empty. private $saved_page_assets; public function element_action( Element_Base $element_data ) { $settings = $element_data->get_active_settings(); $controls = $element_data->get_controls(); $element_assets = $this->get_assets( $settings, $controls ); $element_assets_depend = [ 'styles' => $element_data->get_style_depends(), 'scripts' => $element_data->get_script_depends(), ]; if ( $element_assets_depend ) { foreach ( $element_assets_depend as $assets_type => $assets ) { if ( empty( $assets ) ) { continue; } if ( ! isset( $element_assets[ $assets_type ] ) ) { $element_assets[ $assets_type ] = []; } foreach ( $assets as $asset_name ) { if ( ! in_array( $asset_name, $element_assets[ $assets_type ], true ) ) { $element_assets[ $assets_type ][] = $asset_name; } } } } if ( $element_assets ) { $this->update_page_assets( $element_assets ); } } public function is_action_needed() { // No need to evaluate in preview mode, will be made in the saving process. if ( Plugin::$instance->preview->is_preview_mode() ) { return false; } $page_assets = $this->get_saved_page_assets(); // When $page_assets is array it means that the assets registration has already been made at least once. if ( is_array( $page_assets ) ) { return false; } return true; } public function after_elements_iteration() { // In case that the page assets value is empty, it should still be saved as an empty array as an indication that at lease one iteration has occurred. if ( ! is_array( $this->page_assets ) ) { $this->page_assets = []; } $this->get_document_assets(); // Saving the page assets data. $this->document->update_meta( self::ASSETS_META_KEY, $this->page_assets ); if ( 'render' === $this->mode && $this->page_assets ) { Plugin::$instance->assets_loader->enable_assets( $this->page_assets ); } } private function get_saved_page_assets( $force_meta_fetch = false ) { if ( ! is_array( $this->saved_page_assets ) || $force_meta_fetch ) { $this->saved_page_assets = $this->document->get_meta( self::ASSETS_META_KEY ); } return $this->saved_page_assets; } private function update_page_assets( $new_assets ) { if ( ! is_array( $this->page_assets ) ) { $this->page_assets = []; } foreach ( $new_assets as $assets_type => $assets_type_data ) { if ( ! isset( $this->page_assets[ $assets_type ] ) ) { $this->page_assets[ $assets_type ] = []; } foreach ( $assets_type_data as $asset_name ) { if ( ! in_array( $asset_name, $this->page_assets[ $assets_type ], true ) ) { $this->page_assets[ $assets_type ][] = $asset_name; } } } } private function get_assets( $settings, $controls ) { $assets = []; foreach ( $settings as $setting_key => $setting ) { if ( ! isset( $controls[ $setting_key ] ) ) { continue; } $control = $controls[ $setting_key ]; // Enabling assets loading from the registered control fields. if ( ! empty( $control['assets'] ) ) { foreach ( $control['assets'] as $assets_type => $dependencies ) { foreach ( $dependencies as $dependency ) { if ( ! empty( $dependency['conditions'] ) ) { $is_condition_fulfilled = Conditions::check( $dependency['conditions'], $settings ); if ( ! $is_condition_fulfilled ) { continue; } } if ( ! isset( $assets[ $assets_type ] ) ) { $assets[ $assets_type ] = []; } $assets[ $assets_type ][] = $dependency['name']; } } } // Enabling assets loading from the control object. $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); $control_conditional_assets = $control_obj::get_assets( $setting ); if ( $control_conditional_assets ) { foreach ( $control_conditional_assets as $assets_type => $dependencies ) { foreach ( $dependencies as $dependency ) { if ( ! isset( $assets[ $assets_type ] ) ) { $assets[ $assets_type ] = []; } $assets[ $assets_type ][] = $dependency; } } } } return $assets; } private function get_document_assets() { $document_id = $this->document->get_post()->ID; // Getting the document instance in order to get the most updated settings. $updated_document = Plugin::$instance->documents->get( $document_id, false ); $document_settings = $updated_document->get_settings(); $document_controls = $this->document->get_controls(); $document_assets = $this->get_assets( $document_settings, $document_controls ); if ( $document_assets ) { $this->update_page_assets( $document_assets ); } } public function __construct( $document ) { parent::__construct( $document ); // No need to enable assets in preview mode, all assets will be loaded by default by the assets loader. if ( Plugin::$instance->preview->is_preview_mode() ) { return; } $page_assets = $this->get_saved_page_assets(); // If $page_assets is not empty then enabling the assets for loading. if ( $page_assets ) { Plugin::$instance->assets_loader->enable_assets( $page_assets ); } } }
| ver. 1.4 |
Github
|
.
| PHP 8.0.30 | Génération de la page: 0 |
proxy
|
phpinfo
|
Réglages