| 1 |
<?php |
| 2 |
namespace Elementor\Data; |
| 3 |
|
| 4 |
use Elementor\Core\Base\Module as BaseModule; |
| 5 |
use Elementor\Data\Base\Processor; |
| 6 |
|
| 7 |
if ( ! defined( 'ABSPATH' ) ) { |
| 8 |
exit; // Exit if accessed directly. |
| 9 |
} |
| 10 |
|
| 11 |
class Manager extends BaseModule { |
| 12 |
|
| 13 |
const ROOT_NAMESPACE = 'elementor'; |
| 14 |
|
| 15 |
const REST_BASE = ''; |
| 16 |
|
| 17 |
const VERSION = '1'; |
| 18 |
|
| 19 |
/** |
| 20 |
* @var \WP_REST_Server |
| 21 |
*/ |
| 22 |
private $server; |
| 23 |
|
| 24 |
/** |
| 25 |
* @var boolean |
| 26 |
*/ |
| 27 |
private $is_internal = false; |
| 28 |
|
| 29 |
/** |
| 30 |
* @var array |
| 31 |
*/ |
| 32 |
private $cache = []; |
| 33 |
|
| 34 |
/** |
| 35 |
* Loaded controllers. |
| 36 |
* |
| 37 |
* @var \Elementor\Data\Base\Controller[] |
| 38 |
*/ |
| 39 |
public $controllers = []; |
| 40 |
|
| 41 |
/** |
| 42 |
* Loaded command(s) format. |
| 43 |
* |
| 44 |
* @var string[] |
| 45 |
*/ |
| 46 |
public $command_formats = []; |
| 47 |
|
| 48 |
/** |
| 49 |
* Fix issue with 'Potentially polymorphic call. The code may be inoperable depending on the actual class instance passed as the argument.'. |
| 50 |
* |
| 51 |
* @return \Elementor\Core\Base\Module|\Elementor\Data\Manager |
| 52 |
*/ |
| 53 |
public static function instance() { |
| 54 |
return ( parent::instance() ); |
| 55 |
} |
| 56 |
|
| 57 |
public function __construct() { |
| 58 |
add_action( 'rest_api_init', [ $this, 'register_rest_error_handler' ] ); |
| 59 |
} |
| 60 |
|
| 61 |
public function get_name() { |
| 62 |
return 'data-manager'; |
| 63 |
} |
| 64 |
|
| 65 |
/** |
| 66 |
* @return \Elementor\Data\Base\Controller[] |
| 67 |
*/ |
| 68 |
public function get_controllers() { |
| 69 |
return $this->controllers; |
| 70 |
} |
| 71 |
|
| 72 |
private function get_cache( $key ) { |
| 73 |
return self::get_items( $this->cache, $key ); |
| 74 |
} |
| 75 |
|
| 76 |
private function set_cache( $key, $value ) { |
| 77 |
$this->cache[ $key ] = $value; |
| 78 |
} |
| 79 |
|
| 80 |
/** |
| 81 |
* Register controller. |
| 82 |
* |
| 83 |
* @param string $controller_class_name |
| 84 |
* |
| 85 |
* @return \Elementor\Data\Base\Controller |
| 86 |
*/ |
| 87 |
public function register_controller( $controller_class_name ) { |
| 88 |
$controller_instance = new $controller_class_name(); |
| 89 |
|
| 90 |
return $this->register_controller_instance( $controller_instance ); |
| 91 |
} |
| 92 |
|
| 93 |
/** |
| 94 |
* Register controller instance. |
| 95 |
* |
| 96 |
* @param \Elementor\Data\Base\Controller $controller_instance |
| 97 |
* |
| 98 |
* @return \Elementor\Data\Base\Controller |
| 99 |
*/ |
| 100 |
public function register_controller_instance( $controller_instance ) { |
| 101 |
// TODO: Validate instance. |
| 102 |
|
| 103 |
$this->controllers[ $controller_instance->get_name() ] = $controller_instance; |
| 104 |
|
| 105 |
return $controller_instance; |
| 106 |
} |
| 107 |
|
| 108 |
/** |
| 109 |
* Register endpoint format. |
| 110 |
* |
| 111 |
* @param string $command |
| 112 |
* @param string $format |
| 113 |
*/ |
| 114 |
public function register_endpoint_format( $command, $format ) { |
| 115 |
$this->command_formats[ $command ] = rtrim( $format, '/' ); |
| 116 |
} |
| 117 |
|
| 118 |
public function register_rest_error_handler() { |
| 119 |
if ( ! $this->is_internal() ) { |
| 120 |
$logger_manager = \Elementor\Core\Logger\Manager::instance(); |
| 121 |
$logger_manager->register_error_handler(); |
| 122 |
} |
| 123 |
} |
| 124 |
|
| 125 |
/** |
| 126 |
* Find controller instance. |
| 127 |
* |
| 128 |
* By given command name. |
| 129 |
* |
| 130 |
* @param string $command |
| 131 |
* |
| 132 |
* @return false|\Elementor\Data\Base\Controller |
| 133 |
*/ |
| 134 |
public function find_controller_instance( $command ) { |
| 135 |
$command_parts = explode( '/', $command ); |
| 136 |
$assumed_command_parts = []; |
| 137 |
|
| 138 |
foreach ( $command_parts as $command_part ) { |
| 139 |
$assumed_command_parts [] = $command_part; |
| 140 |
|
| 141 |
foreach ( $this->controllers as $controller_name => $controller ) { |
| 142 |
$assumed_command = implode( '/', $assumed_command_parts ); |
| 143 |
|
| 144 |
if ( $assumed_command === $controller_name ) { |
| 145 |
return $controller; |
| 146 |
} |
| 147 |
} |
| 148 |
} |
| 149 |
|
| 150 |
return false; |
| 151 |
} |
| 152 |
|
| 153 |
/** |
| 154 |
* Command extract args. |
| 155 |
* |
| 156 |
* @param string $command |
| 157 |
* @param array $args |
| 158 |
* |
| 159 |
* @return \stdClass |
| 160 |
*/ |
| 161 |
public function command_extract_args( $command, $args = [] ) { |
| 162 |
$result = new \stdClass(); |
| 163 |
$result->command = $command; |
| 164 |
$result->args = $args; |
| 165 |
|
| 166 |
if ( false !== strpos( $command, '?' ) ) { |
| 167 |
$command_parts = explode( '?', $command ); |
| 168 |
$pure_command = $command_parts[0]; |
| 169 |
$query_string = $command_parts[1]; |
| 170 |
|
| 171 |
parse_str( $query_string, $temp ); |
| 172 |
|
| 173 |
$result->command = rtrim( $pure_command, '/' ); |
| 174 |
$result->args = array_merge( $args, $temp ); |
| 175 |
} |
| 176 |
|
| 177 |
return $result; |
| 178 |
} |
| 179 |
|
| 180 |
/** |
| 181 |
* Command to endpoint. |
| 182 |
* |
| 183 |
* Format is required otherwise $command will returned. |
| 184 |
* |
| 185 |
* @param string $command |
| 186 |
* @param string $format |
| 187 |
* @param array $args |
| 188 |
* |
| 189 |
* @return string endpoint |
| 190 |
*/ |
| 191 |
public function command_to_endpoint( $command, $format, $args ) { |
| 192 |
$endpoint = $command; |
| 193 |
|
| 194 |
if ( $format ) { |
| 195 |
$formatted = $format; |
| 196 |
|
| 197 |
array_walk( $args, function ( $val, $key ) use ( &$formatted ) { |
| 198 |
$formatted = str_replace( '{' . $key . '}', $val, $formatted ); |
| 199 |
} ); |
| 200 |
|
| 201 |
// Remove remaining format if not requested via `$args`. |
| 202 |
if ( strstr( $formatted, '/{' ) ) { |
| 203 |
/** |
| 204 |
* Example: |
| 205 |
* $command = 'example/documents'; |
| 206 |
* $format = 'example/documents/{document_id}/elements/{element_id}'; |
| 207 |
* $formatted = 'example/documents/1618/elements/{element_id}'; |
| 208 |
* Result: |
| 209 |
* $formatted = 'example/documents/1618/elements'; |
| 210 |
*/ |
| 211 |
$formatted = substr( $formatted, 0, strpos( $formatted, '/{' ) ); |
| 212 |
} |
| 213 |
|
| 214 |
$endpoint = $formatted; |
| 215 |
} |
| 216 |
|
| 217 |
return $endpoint; |
| 218 |
} |
| 219 |
|
| 220 |
/** |
| 221 |
* Run server. |
| 222 |
* |
| 223 |
* Init WordPress reset api. |
| 224 |
* |
| 225 |
* @return \WP_REST_Server |
| 226 |
*/ |
| 227 |
public function run_server() { |
| 228 |
/** |
| 229 |
* If run_server() called means, that rest api is simulated from the backend. |
| 230 |
*/ |
| 231 |
$this->is_internal = true; |
| 232 |
|
| 233 |
if ( ! $this->server ) { |
| 234 |
// Remove all 'rest_api_init' actions. |
| 235 |
remove_all_actions( 'rest_api_init' ); |
| 236 |
|
| 237 |
// Call custom reset api loader. |
| 238 |
do_action( 'elementor_rest_api_before_init' ); |
| 239 |
|
| 240 |
$this->server = rest_get_server(); // Init API. |
| 241 |
} |
| 242 |
|
| 243 |
return $this->server; |
| 244 |
} |
| 245 |
|
| 246 |
/** |
| 247 |
* Kill server. |
| 248 |
* |
| 249 |
* Free server and controllers. |
| 250 |
*/ |
| 251 |
public function kill_server() { |
| 252 |
global $wp_rest_server; |
| 253 |
|
| 254 |
$this->controllers = []; |
| 255 |
$this->command_formats = []; |
| 256 |
$this->server = false; |
| 257 |
$this->is_internal = false; |
| 258 |
$this->cache = []; |
| 259 |
$wp_rest_server = false; |
| 260 |
} |
| 261 |
|
| 262 |
/** |
| 263 |
* Run processor. |
| 264 |
* |
| 265 |
* @param Processor $processor |
| 266 |
* @param array $data |
| 267 |
* |
| 268 |
* @return mixed |
| 269 |
*/ |
| 270 |
public function run_processor( $processor, $data ) { |
| 271 |
if ( call_user_func_array( [ $processor, 'get_conditions' ], $data ) ) { |
| 272 |
return call_user_func_array( [ $processor, 'apply' ], $data ); |
| 273 |
} |
| 274 |
|
| 275 |
return null; |
| 276 |
} |
| 277 |
|
| 278 |
/** |
| 279 |
* Run processors. |
| 280 |
* |
| 281 |
* Filter them by class. |
| 282 |
* |
| 283 |
* @param Processor[] $processors |
| 284 |
* @param string $filter_by_class |
| 285 |
* @param array $data |
| 286 |
* |
| 287 |
* @return false|array |
| 288 |
*/ |
| 289 |
public function run_processors( $processors, $filter_by_class, $data ) { |
| 290 |
foreach ( $processors as $processor ) { |
| 291 |
if ( $processor instanceof $filter_by_class ) { |
| 292 |
if ( Processor\Before::class === $filter_by_class ) { |
| 293 |
$this->run_processor( $processor, $data ); |
| 294 |
} elseif ( Processor\After::class === $filter_by_class ) { |
| 295 |
$result = $this->run_processor( $processor, $data ); |
| 296 |
if ( $result ) { |
| 297 |
$data[1] = $result; |
| 298 |
} |
| 299 |
} else { |
| 300 |
// TODO: error |
| 301 |
break; |
| 302 |
} |
| 303 |
} |
| 304 |
} |
| 305 |
|
| 306 |
return isset( $data[1] ) ? $data[1] : false; |
| 307 |
} |
| 308 |
|
| 309 |
/** |
| 310 |
* Run request. |
| 311 |
* |
| 312 |
* Simulate rest API from within the backend. |
| 313 |
* Use args as query. |
| 314 |
* |
| 315 |
* @param string $endpoint |
| 316 |
* @param array $args |
| 317 |
* @param string $method |
| 318 |
* |
| 319 |
* @return \WP_REST_Response |
| 320 |
*/ |
| 321 |
private function run_request( $endpoint, $args, $method ) { |
| 322 |
$this->run_server(); |
| 323 |
|
| 324 |
$endpoint = '/' . self::ROOT_NAMESPACE . '/v' . self::VERSION . '/' . $endpoint; |
| 325 |
|
| 326 |
// Run reset api. |
| 327 |
$request = new \WP_REST_Request( $method, $endpoint ); |
| 328 |
|
| 329 |
if ( 'GET' === $method ) { |
| 330 |
$request->set_query_params( $args ); |
| 331 |
} else { |
| 332 |
$request->set_body_params( $args ); |
| 333 |
} |
| 334 |
|
| 335 |
return rest_do_request( $request ); |
| 336 |
} |
| 337 |
|
| 338 |
/** |
| 339 |
* Run endpoint. |
| 340 |
* |
| 341 |
* Wrapper for `$this->run_request` return `$response->getData()` instead of `$response`. |
| 342 |
* |
| 343 |
* @param string $endpoint |
| 344 |
* @param array $args |
| 345 |
* @param string $method |
| 346 |
* |
| 347 |
* @return array |
| 348 |
*/ |
| 349 |
public function run_endpoint( $endpoint, $args = [], $method = 'GET' ) { |
| 350 |
$response = $this->run_request( $endpoint, $args, $method ); |
| 351 |
|
| 352 |
return $response->get_data(); |
| 353 |
} |
| 354 |
|
| 355 |
/** |
| 356 |
* Run ( simulated reset api ). |
| 357 |
* |
| 358 |
* Do: |
| 359 |
* Init reset server. |
| 360 |
* Run before processors. |
| 361 |
* Run command as reset api endpoint from internal. |
| 362 |
* Run after processors. |
| 363 |
* |
| 364 |
* @param string $command |
| 365 |
* @param array $args |
| 366 |
* @param string $method |
| 367 |
* |
| 368 |
* @return array|false processed result |
| 369 |
*/ |
| 370 |
public function run( $command, $args = [], $method = 'GET' ) { |
| 371 |
$key = crc32( $command . '-' . wp_json_encode( $args ) . '-' . $method ); |
| 372 |
$cache = $this->get_cache( $key ); |
| 373 |
|
| 374 |
if ( $cache ) { |
| 375 |
return $cache; |
| 376 |
} |
| 377 |
|
| 378 |
$this->run_server(); |
| 379 |
|
| 380 |
$controller_instance = $this->find_controller_instance( $command ); |
| 381 |
|
| 382 |
if ( ! $controller_instance ) { |
| 383 |
$this->set_cache( $key, [] ); |
| 384 |
return []; |
| 385 |
} |
| 386 |
|
| 387 |
$extracted_command = $this->command_extract_args( $command, $args ); |
| 388 |
$command = $extracted_command->command; |
| 389 |
$args = $extracted_command->args; |
| 390 |
|
| 391 |
$format = isset( $this->command_formats[ $command ] ) ? $this->command_formats[ $command ] : false; |
| 392 |
|
| 393 |
$command_processors = $controller_instance->get_processors( $command ); |
| 394 |
|
| 395 |
$endpoint = $this->command_to_endpoint( $command, $format, $args ); |
| 396 |
|
| 397 |
$this->run_processors( $command_processors, Processor\Before::class, [ $args ] ); |
| 398 |
|
| 399 |
$response = $this->run_request( $endpoint, $args, $method ); |
| 400 |
$result = $response->get_data(); |
| 401 |
|
| 402 |
if ( $response->is_error() ) { |
| 403 |
$this->set_cache( $key, [] ); |
| 404 |
return []; |
| 405 |
} |
| 406 |
|
| 407 |
$result = $this->run_processors( $command_processors, Processor\After::class, [ $args, $result ] ); |
| 408 |
|
| 409 |
$this->set_cache( $key, $result ); |
| 410 |
|
| 411 |
return $result; |
| 412 |
} |
| 413 |
|
| 414 |
public function is_internal() { |
| 415 |
return $this->is_internal; |
| 416 |
} |
| 417 |
} |
| 418 |
|