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