manager.php (15317B)
1 <?php 2 namespace Elementor\TemplateLibrary; 3 4 use Elementor\Api; 5 use Elementor\Core\Common\Modules\Ajax\Module as Ajax; 6 use Elementor\Core\Settings\Manager as SettingsManager; 7 use Elementor\TemplateLibrary\Classes\Import_Images; 8 use Elementor\Plugin; 9 use Elementor\User; 10 11 if ( ! defined( 'ABSPATH' ) ) { 12 exit; // Exit if accessed directly. 13 } 14 15 /** 16 * Elementor template library manager. 17 * 18 * Elementor template library manager handler class is responsible for 19 * initializing the template library. 20 * 21 * @since 1.0.0 22 */ 23 class Manager { 24 25 /** 26 * Registered template sources. 27 * 28 * Holds a list of all the supported sources with their instances. 29 * 30 * @access protected 31 * 32 * @var Source_Base[] 33 */ 34 protected $_registered_sources = []; 35 36 /** 37 * Imported template images. 38 * 39 * Holds an instance of `Import_Images` class. 40 * 41 * @access private 42 * 43 * @var Import_Images 44 */ 45 private $_import_images = null; 46 47 /** 48 * Template library manager constructor. 49 * 50 * Initializing the template library manager by registering default template 51 * sources and initializing ajax calls. 52 * 53 * @since 1.0.0 54 * @access public 55 */ 56 public function __construct() { 57 $this->register_default_sources(); 58 59 $this->add_actions(); 60 } 61 62 /** 63 * @since 2.3.0 64 * @access public 65 */ 66 public function add_actions() { 67 add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] ); 68 add_action( 'wp_ajax_elementor_library_direct_actions', [ $this, 'handle_direct_actions' ] ); 69 } 70 71 /** 72 * Get `Import_Images` instance. 73 * 74 * Retrieve the instance of the `Import_Images` class. 75 * 76 * @since 1.0.0 77 * @access public 78 * 79 * @return Import_Images Imported images instance. 80 */ 81 public function get_import_images_instance() { 82 if ( null === $this->_import_images ) { 83 $this->_import_images = new Import_Images(); 84 } 85 86 return $this->_import_images; 87 } 88 89 /** 90 * Register template source. 91 * 92 * Used to register new template sources displayed in the template library. 93 * 94 * @since 1.0.0 95 * @access public 96 * 97 * @param string $source_class The name of source class. 98 * @param array $args Optional. Class arguments. Default is an 99 * empty array. 100 * 101 * @return \WP_Error|true True if the source was registered, `WP_Error` 102 * otherwise. 103 */ 104 public function register_source( $source_class, $args = [] ) { 105 if ( ! class_exists( $source_class ) ) { 106 return new \WP_Error( 'source_class_name_not_exists' ); 107 } 108 109 $source_instance = new $source_class( $args ); 110 111 if ( ! $source_instance instanceof Source_Base ) { 112 return new \WP_Error( 'wrong_instance_source' ); 113 } 114 115 $source_id = $source_instance->get_id(); 116 117 if ( isset( $this->_registered_sources[ $source_id ] ) ) { 118 return new \WP_Error( 'source_exists' ); 119 } 120 121 $this->_registered_sources[ $source_id ] = $source_instance; 122 123 return true; 124 } 125 126 /** 127 * Unregister template source. 128 * 129 * Remove an existing template sources from the list of registered template 130 * sources. 131 * 132 * @deprecated 2.7.0 133 * 134 * @since 1.0.0 135 * @access public 136 * 137 * @param string $id The source ID. 138 * 139 * @return bool Whether the source was unregistered. 140 */ 141 public function unregister_source( $id ) { 142 return true; 143 } 144 145 /** 146 * Get registered template sources. 147 * 148 * Retrieve registered template sources. 149 * 150 * @since 1.0.0 151 * @access public 152 * 153 * @return Source_Base[] Registered template sources. 154 */ 155 public function get_registered_sources() { 156 return $this->_registered_sources; 157 } 158 159 /** 160 * Get template source. 161 * 162 * Retrieve single template sources for a given template ID. 163 * 164 * @since 1.0.0 165 * @access public 166 * 167 * @param string $id The source ID. 168 * 169 * @return false|Source_Base Template sources if one exist, False otherwise. 170 */ 171 public function get_source( $id ) { 172 $sources = $this->get_registered_sources(); 173 174 if ( ! isset( $sources[ $id ] ) ) { 175 return false; 176 } 177 178 return $sources[ $id ]; 179 } 180 181 /** 182 * Get templates. 183 * 184 * Retrieve all the templates from all the registered sources. 185 * 186 * @since 1.0.0 187 * @access public 188 * 189 * @return array Templates array. 190 */ 191 public function get_templates() { 192 $templates = []; 193 194 foreach ( $this->get_registered_sources() as $source ) { 195 $templates = array_merge( $templates, $source->get_items() ); 196 } 197 198 return $templates; 199 } 200 201 /** 202 * Get library data. 203 * 204 * Retrieve the library data. 205 * 206 * @since 1.9.0 207 * @access public 208 * 209 * @param array $args Library arguments. 210 * 211 * @return array Library data. 212 */ 213 public function get_library_data( array $args ) { 214 $library_data = Api::get_library_data( ! empty( $args['sync'] ) ); 215 216 // Ensure all document are registered. 217 Plugin::$instance->documents->get_document_types(); 218 219 return [ 220 'templates' => $this->get_templates(), 221 'config' => $library_data['types_data'], 222 ]; 223 } 224 225 /** 226 * Save template. 227 * 228 * Save new or update existing template on the database. 229 * 230 * @since 1.0.0 231 * @access public 232 * 233 * @param array $args Template arguments. 234 * 235 * @return \WP_Error|int The ID of the saved/updated template. 236 */ 237 public function save_template( array $args ) { 238 $validate_args = $this->ensure_args( [ 'post_id', 'source', 'content', 'type' ], $args ); 239 240 if ( is_wp_error( $validate_args ) ) { 241 return $validate_args; 242 } 243 244 $source = $this->get_source( $args['source'] ); 245 246 if ( ! $source ) { 247 return new \WP_Error( 'template_error', 'Template source not found.' ); 248 } 249 250 $args['content'] = json_decode( $args['content'], true ); 251 252 $page = SettingsManager::get_settings_managers( 'page' )->get_model( $args['post_id'] ); 253 254 $args['page_settings'] = $page->get_data( 'settings' ); 255 256 $template_id = $source->save_item( $args ); 257 258 if ( is_wp_error( $template_id ) ) { 259 return $template_id; 260 } 261 262 return $source->get_item( $template_id ); 263 } 264 265 /** 266 * Update template. 267 * 268 * Update template on the database. 269 * 270 * @since 1.0.0 271 * @access public 272 * 273 * @param array $template_data New template data. 274 * 275 * @return \WP_Error|Source_Base Template sources instance if the templates 276 * was updated, `WP_Error` otherwise. 277 */ 278 public function update_template( array $template_data ) { 279 $validate_args = $this->ensure_args( [ 'source', 'content', 'type' ], $template_data ); 280 281 if ( is_wp_error( $validate_args ) ) { 282 return $validate_args; 283 } 284 285 $source = $this->get_source( $template_data['source'] ); 286 287 if ( ! $source ) { 288 return new \WP_Error( 'template_error', 'Template source not found.' ); 289 } 290 291 $template_data['content'] = json_decode( $template_data['content'], true ); 292 293 $update = $source->update_item( $template_data ); 294 295 if ( is_wp_error( $update ) ) { 296 return $update; 297 } 298 299 return $source->get_item( $template_data['id'] ); 300 } 301 302 /** 303 * Update templates. 304 * 305 * Update template on the database. 306 * 307 * @since 1.0.0 308 * @access public 309 * 310 * @param array $args Template arguments. 311 * 312 * @return \WP_Error|true True if templates updated, `WP_Error` otherwise. 313 */ 314 public function update_templates( array $args ) { 315 foreach ( $args['templates'] as $template_data ) { 316 $result = $this->update_template( $template_data ); 317 318 if ( is_wp_error( $result ) ) { 319 return $result; 320 } 321 } 322 323 return true; 324 } 325 326 /** 327 * Get template data. 328 * 329 * Retrieve the template data. 330 * 331 * @since 1.5.0 332 * @access public 333 * 334 * @param array $args Template arguments. 335 * 336 * @return \WP_Error|bool|array ?? 337 */ 338 public function get_template_data( array $args ) { 339 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args ); 340 341 if ( is_wp_error( $validate_args ) ) { 342 return $validate_args; 343 } 344 345 if ( isset( $args['edit_mode'] ) ) { 346 Plugin::$instance->editor->set_edit_mode( $args['edit_mode'] ); 347 } 348 349 $source = $this->get_source( $args['source'] ); 350 351 if ( ! $source ) { 352 return new \WP_Error( 'template_error', 'Template source not found.' ); 353 } 354 355 do_action( 'elementor/template-library/before_get_source_data', $args, $source ); 356 357 $data = $source->get_data( $args ); 358 359 do_action( 'elementor/template-library/after_get_source_data', $args, $source ); 360 361 return $data; 362 } 363 364 /** 365 * Delete template. 366 * 367 * Delete template from the database. 368 * 369 * @since 1.0.0 370 * @access public 371 * 372 * @param array $args Template arguments. 373 * 374 * @return \WP_Post|\WP_Error|false|null Post data on success, false or null 375 * or 'WP_Error' on failure. 376 */ 377 public function delete_template( array $args ) { 378 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args ); 379 380 if ( is_wp_error( $validate_args ) ) { 381 return $validate_args; 382 } 383 384 $source = $this->get_source( $args['source'] ); 385 386 if ( ! $source ) { 387 return new \WP_Error( 'template_error', 'Template source not found.' ); 388 } 389 390 return $source->delete_template( $args['template_id'] ); 391 } 392 393 /** 394 * Export template. 395 * 396 * Export template to a file. 397 * 398 * @since 1.0.0 399 * @access public 400 * 401 * @param array $args Template arguments. 402 * 403 * @return mixed Whether the export succeeded or failed. 404 */ 405 public function export_template( array $args ) { 406 $validate_args = $this->ensure_args( [ 'source', 'template_id' ], $args ); 407 408 if ( is_wp_error( $validate_args ) ) { 409 return $validate_args; 410 } 411 412 $source = $this->get_source( $args['source'] ); 413 414 if ( ! $source ) { 415 return new \WP_Error( 'template_error', 'Template source not found' ); 416 } 417 418 return $source->export_template( $args['template_id'] ); 419 } 420 421 /** 422 * @since 2.3.0 423 * @access public 424 */ 425 public function direct_import_template() { 426 /** @var Source_Local $source */ 427 $source = $this->get_source( 'local' ); 428 429 return $source->import_template( $_FILES['file']['name'], $_FILES['file']['tmp_name'] ); 430 } 431 432 /** 433 * Import template. 434 * 435 * Import template from a file. 436 * 437 * @since 1.0.0 438 * @access public 439 * 440 * @param array $data 441 * 442 * @return mixed Whether the export succeeded or failed. 443 */ 444 public function import_template( array $data ) { 445 // Imported templates can be either JSON files, or Zip files containing multiple JSON files 446 $upload_result = Plugin::$instance->uploads_manager->handle_elementor_upload( $data, [ 'zip', 'json' ] ); 447 448 if ( is_wp_error( $upload_result ) ) { 449 return $upload_result; 450 } 451 452 /** @var Source_Local $source_local */ 453 $source_local = $this->get_source( 'local' ); 454 455 return $source_local->import_template( $upload_result['name'], $upload_result['tmp_name'] ); 456 } 457 458 /** 459 * Mark template as favorite. 460 * 461 * Add the template to the user favorite templates. 462 * 463 * @since 1.9.0 464 * @access public 465 * 466 * @param array $args Template arguments. 467 * 468 * @return mixed Whether the template marked as favorite. 469 */ 470 public function mark_template_as_favorite( $args ) { 471 $validate_args = $this->ensure_args( [ 'source', 'template_id', 'favorite' ], $args ); 472 473 if ( is_wp_error( $validate_args ) ) { 474 return $validate_args; 475 } 476 477 $source = $this->get_source( $args['source'] ); 478 479 return $source->mark_as_favorite( $args['template_id'], filter_var( $args['favorite'], FILTER_VALIDATE_BOOLEAN ) ); 480 } 481 482 /** 483 * Register default template sources. 484 * 485 * Register the 'local' and 'remote' template sources that Elementor use by 486 * default. 487 * 488 * @since 1.0.0 489 * @access private 490 */ 491 private function register_default_sources() { 492 $sources = [ 493 'local', 494 'remote', 495 ]; 496 497 foreach ( $sources as $source_filename ) { 498 $class_name = ucwords( $source_filename ); 499 $class_name = str_replace( '-', '_', $class_name ); 500 501 $this->register_source( __NAMESPACE__ . '\Source_' . $class_name ); 502 } 503 } 504 505 /** 506 * Handle ajax request. 507 * 508 * Fire authenticated ajax actions for any given ajax request. 509 * 510 * @since 1.0.0 511 * @access private 512 * 513 * @param string $ajax_request Ajax request. 514 * 515 * @param array $data 516 * 517 * @return mixed 518 * @throws \Exception 519 */ 520 private function handle_ajax_request( $ajax_request, array $data ) { 521 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) { 522 throw new \Exception( 'Access Denied' ); 523 } 524 525 if ( ! empty( $data['editor_post_id'] ) ) { 526 $editor_post_id = absint( $data['editor_post_id'] ); 527 528 if ( ! get_post( $editor_post_id ) ) { 529 throw new \Exception( esc_html__( 'Post not found.', 'elementor' ) ); 530 } 531 532 Plugin::$instance->db->switch_to_post( $editor_post_id ); 533 } 534 535 $result = call_user_func( [ $this, $ajax_request ], $data ); 536 537 if ( is_wp_error( $result ) ) { 538 throw new \Exception( $result->get_error_message() ); 539 } 540 541 return $result; 542 } 543 544 /** 545 * Init ajax calls. 546 * 547 * Initialize template library ajax calls for allowed ajax requests. 548 * 549 * @since 2.3.0 550 * @access public 551 * 552 * @param Ajax $ajax 553 */ 554 public function register_ajax_actions( Ajax $ajax ) { 555 $library_ajax_requests = [ 556 'get_library_data', 557 'get_template_data', 558 'save_template', 559 'update_templates', 560 'delete_template', 561 'import_template', 562 'mark_template_as_favorite', 563 ]; 564 565 foreach ( $library_ajax_requests as $ajax_request ) { 566 $ajax->register_ajax_action( $ajax_request, function( $data ) use ( $ajax_request ) { 567 return $this->handle_ajax_request( $ajax_request, $data ); 568 } ); 569 } 570 } 571 572 /** 573 * @since 2.3.0 574 * @access public 575 */ 576 public function handle_direct_actions() { 577 if ( ! User::is_current_user_can_edit_post_type( Source_Local::CPT ) ) { 578 return; 579 } 580 581 /** @var Ajax $ajax */ 582 $ajax = Plugin::$instance->common->get_component( 'ajax' ); 583 584 if ( ! $ajax->verify_request_nonce() ) { 585 $this->handle_direct_action_error( 'Access Denied' ); 586 } 587 588 $action = $_REQUEST['library_action']; 589 590 $result = $this->$action( $_REQUEST ); 591 592 if ( is_wp_error( $result ) ) { 593 /** @var \WP_Error $result */ 594 $this->handle_direct_action_error( $result->get_error_message() . '.' ); 595 } 596 597 $callback = "on_{$action}_success"; 598 599 if ( method_exists( $this, $callback ) ) { 600 $this->$callback( $result ); 601 } 602 603 die; 604 } 605 606 /** 607 * On successful template import. 608 * 609 * Redirect the user to the template library after template import was 610 * successful finished. 611 * 612 * @since 2.3.0 613 * @access private 614 */ 615 private function on_direct_import_template_success() { 616 wp_safe_redirect( admin_url( Source_Local::ADMIN_MENU_SLUG ) ); 617 } 618 619 /** 620 * @since 2.3.0 621 * @access private 622 */ 623 private function handle_direct_action_error( $message ) { 624 _default_wp_die_handler( $message, 'Elementor Library' ); 625 } 626 627 /** 628 * Ensure arguments exist. 629 * 630 * Checks whether the required arguments exist in the specified arguments. 631 * 632 * @since 1.0.0 633 * @access private 634 * 635 * @param array $required_args Required arguments to check whether they 636 * exist. 637 * @param array $specified_args The list of all the specified arguments to 638 * check against. 639 * 640 * @return \WP_Error|true True on success, 'WP_Error' otherwise. 641 */ 642 private function ensure_args( array $required_args, array $specified_args ) { 643 $not_specified_args = array_diff( $required_args, array_keys( array_filter( $specified_args ) ) ); 644 645 if ( $not_specified_args ) { 646 return new \WP_Error( 'arguments_not_specified', sprintf( 'The required argument(s) "%s" not specified.', implode( ', ', $not_specified_args ) ) ); 647 } 648 649 return true; 650 } 651 }