balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

documents-manager.php (17746B)


      1 <?php
      2 namespace Elementor\Core;
      3 
      4 use Elementor\Core\Base\Document;
      5 use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
      6 use Elementor\Core\DocumentTypes\Page;
      7 use Elementor\Core\DocumentTypes\Post;
      8 use Elementor\Plugin;
      9 use Elementor\TemplateLibrary\Source_Local;
     10 use Elementor\Utils;
     11 
     12 if ( ! defined( 'ABSPATH' ) ) {
     13 	exit; // Exit if accessed directly
     14 }
     15 
     16 /**
     17  * Elementor documents manager.
     18  *
     19  * Elementor documents manager handler class is responsible for registering and
     20  * managing Elementor documents.
     21  *
     22  * @since 2.0.0
     23  */
     24 class Documents_Manager {
     25 
     26 	/**
     27 	 * Registered types.
     28 	 *
     29 	 * Holds the list of all the registered types.
     30 	 *
     31 	 * @since 2.0.0
     32 	 * @access protected
     33 	 *
     34 	 * @var Document[]
     35 	 */
     36 	protected $types = [];
     37 
     38 	/**
     39 	 * Registered documents.
     40 	 *
     41 	 * Holds the list of all the registered documents.
     42 	 *
     43 	 * @since 2.0.0
     44 	 * @access protected
     45 	 *
     46 	 * @var Document[]
     47 	 */
     48 	protected $documents = [];
     49 
     50 	/**
     51 	 * Current document.
     52 	 *
     53 	 * Holds the current document.
     54 	 *
     55 	 * @since 2.0.0
     56 	 * @access protected
     57 	 *
     58 	 * @var Document
     59 	 */
     60 	protected $current_doc;
     61 
     62 	/**
     63 	 * Switched data.
     64 	 *
     65 	 * Holds the current document when changing to the requested post.
     66 	 *
     67 	 * @since 2.0.0
     68 	 * @access protected
     69 	 *
     70 	 * @var array
     71 	 */
     72 	protected $switched_data = [];
     73 
     74 	protected $cpt = [];
     75 
     76 	/**
     77 	 * Documents manager constructor.
     78 	 *
     79 	 * Initializing the Elementor documents manager.
     80 	 *
     81 	 * @since 2.0.0
     82 	 * @access public
     83 	 */
     84 	public function __construct() {
     85 		add_action( 'elementor/documents/register', [ $this, 'register_default_types' ], 0 );
     86 		add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
     87 		add_filter( 'post_row_actions', [ $this, 'filter_post_row_actions' ], 11, 2 );
     88 		add_filter( 'page_row_actions', [ $this, 'filter_post_row_actions' ], 11, 2 );
     89 		add_filter( 'user_has_cap', [ $this, 'remove_user_edit_cap' ], 10, 3 );
     90 		add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
     91 	}
     92 
     93 	/**
     94 	 * Register ajax actions.
     95 	 *
     96 	 * Process ajax action handles when saving data and discarding changes.
     97 	 *
     98 	 * Fired by `elementor/ajax/register_actions` action.
     99 	 *
    100 	 * @since 2.0.0
    101 	 * @access public
    102 	 *
    103 	 * @param Ajax $ajax_manager An instance of the ajax manager.
    104 	 */
    105 	public function register_ajax_actions( $ajax_manager ) {
    106 		$ajax_manager->register_ajax_action( 'save_builder', [ $this, 'ajax_save' ] );
    107 		$ajax_manager->register_ajax_action( 'discard_changes', [ $this, 'ajax_discard_changes' ] );
    108 		$ajax_manager->register_ajax_action( 'get_document_config', [ $this, 'ajax_get_document_config' ] );
    109 	}
    110 
    111 	/**
    112 	 * Register default types.
    113 	 *
    114 	 * Registers the default document types.
    115 	 *
    116 	 * @since 2.0.0
    117 	 * @access public
    118 	 */
    119 	public function register_default_types() {
    120 		$default_types = [
    121 			'post' => Post::get_class_full_name(), // BC.
    122 			'wp-post' => Post::get_class_full_name(),
    123 			'wp-page' => Page::get_class_full_name(),
    124 		];
    125 
    126 		foreach ( $default_types as $type => $class ) {
    127 			$this->register_document_type( $type, $class );
    128 		}
    129 	}
    130 
    131 	/**
    132 	 * Register document type.
    133 	 *
    134 	 * Registers a single document.
    135 	 *
    136 	 * @since 2.0.0
    137 	 * @access public
    138 	 *
    139 	 * @param string $type  Document type name.
    140 	 * @param string $class The name of the class that registers the document type.
    141 	 *                      Full name with the namespace.
    142 	 *
    143 	 * @return Documents_Manager The updated document manager instance.
    144 	 */
    145 	public function register_document_type( $type, $class ) {
    146 		$this->types[ $type ] = $class;
    147 
    148 		$cpt = $class::get_property( 'cpt' );
    149 
    150 		if ( $cpt ) {
    151 			foreach ( $cpt as $post_type ) {
    152 				$this->cpt[ $post_type ] = $type;
    153 			}
    154 		}
    155 
    156 		if ( $class::get_property( 'register_type' ) ) {
    157 			Source_Local::add_template_type( $type );
    158 		}
    159 
    160 		return $this;
    161 	}
    162 
    163 	/**
    164 	 * Get document.
    165 	 *
    166 	 * Retrieve the document data based on a post ID.
    167 	 *
    168 	 * @since 2.0.0
    169 	 * @access public
    170 	 *
    171 	 * @param int  $post_id    Post ID.
    172 	 * @param bool $from_cache Optional. Whether to retrieve cached data. Default is true.
    173 	 *
    174 	 * @return false|Document Document data or false if post ID was not entered.
    175 	 */
    176 	public function get( $post_id, $from_cache = true ) {
    177 		$this->register_types();
    178 
    179 		$post_id = absint( $post_id );
    180 
    181 		if ( ! $post_id || ! get_post( $post_id ) ) {
    182 			return false;
    183 		}
    184 
    185 		/**
    186 		 * Retrieve document post ID.
    187 		 *
    188 		 * Filters the document post ID.
    189 		 *
    190 		 * @since 2.0.7
    191 		 *
    192 		 * @param int $post_id The post ID of the document.
    193 		 */
    194 		$post_id = apply_filters( 'elementor/documents/get/post_id', $post_id );
    195 
    196 		if ( ! $from_cache || ! isset( $this->documents[ $post_id ] ) ) {
    197 
    198 			if ( wp_is_post_autosave( $post_id ) ) {
    199 				$post_type = get_post_type( wp_get_post_parent_id( $post_id ) );
    200 			} else {
    201 				$post_type = get_post_type( $post_id );
    202 			}
    203 
    204 			$doc_type = 'post';
    205 
    206 			if ( isset( $this->cpt[ $post_type ] ) ) {
    207 				$doc_type = $this->cpt[ $post_type ];
    208 			}
    209 
    210 			$meta_type = get_post_meta( $post_id, Document::TYPE_META_KEY, true );
    211 
    212 			if ( $meta_type && isset( $this->types[ $meta_type ] ) ) {
    213 				$doc_type = $meta_type;
    214 			}
    215 
    216 			$doc_type_class = $this->get_document_type( $doc_type );
    217 			$this->documents[ $post_id ] = new $doc_type_class( [
    218 				'post_id' => $post_id,
    219 			] );
    220 		}
    221 
    222 		return $this->documents[ $post_id ];
    223 	}
    224 
    225 	/**
    226 	 * Get document or autosave.
    227 	 *
    228 	 * Retrieve either the document or the autosave.
    229 	 *
    230 	 * @since 2.0.0
    231 	 * @access public
    232 	 *
    233 	 * @param int $id      Optional. Post ID. Default is `0`.
    234 	 * @param int $user_id Optional. User ID. Default is `0`.
    235 	 *
    236 	 * @return false|Document The document if it exist, False otherwise.
    237 	 */
    238 	public function get_doc_or_auto_save( $id, $user_id = 0 ) {
    239 		$document = $this->get( $id );
    240 		if ( $document && $document->get_autosave_id( $user_id ) ) {
    241 			$document = $document->get_autosave( $user_id );
    242 		}
    243 
    244 		return $document;
    245 	}
    246 
    247 	/**
    248 	 * Get document for frontend.
    249 	 *
    250 	 * Retrieve the document for frontend use.
    251 	 *
    252 	 * @since 2.0.0
    253 	 * @access public
    254 	 *
    255 	 * @param int $post_id Optional. Post ID. Default is `0`.
    256 	 *
    257 	 * @return false|Document The document if it exist, False otherwise.
    258 	 */
    259 	public function get_doc_for_frontend( $post_id ) {
    260 		if ( is_preview() || Plugin::$instance->preview->is_preview_mode() ) {
    261 			$document = $this->get_doc_or_auto_save( $post_id, get_current_user_id() );
    262 		} else {
    263 			$document = $this->get( $post_id );
    264 		}
    265 
    266 		return $document;
    267 	}
    268 
    269 	/**
    270 	 * Get document type.
    271 	 *
    272 	 * Retrieve the type of any given document.
    273 	 *
    274 	 * @since  2.0.0
    275 	 * @access public
    276 	 *
    277 	 * @param string $type
    278 	 *
    279 	 * @param string $fallback
    280 	 *
    281 	 * @return Document|bool The type of the document.
    282 	 */
    283 	public function get_document_type( $type, $fallback = 'post' ) {
    284 		$types = $this->get_document_types();
    285 
    286 		if ( isset( $types[ $type ] ) ) {
    287 			return $types[ $type ];
    288 		}
    289 
    290 		if ( isset( $types[ $fallback ] ) ) {
    291 			return $types[ $fallback ];
    292 		}
    293 
    294 		return false;
    295 	}
    296 
    297 	/**
    298 	 * Get document types.
    299 	 *
    300 	 * Retrieve the all the registered document types.
    301 	 *
    302 	 * @since  2.0.0
    303 	 * @access public
    304 	 *
    305 	 * @param array $args      Optional. An array of key => value arguments to match against
    306 	 *                               the properties. Default is empty array.
    307 	 * @param string $operator Optional. The logical operation to perform. 'or' means only one
    308 	 *                               element from the array needs to match; 'and' means all elements
    309 	 *                               must match; 'not' means no elements may match. Default 'and'.
    310 	 *
    311 	 * @return Document[] All the registered document types.
    312 	 */
    313 	public function get_document_types( $args = [], $operator = 'and' ) {
    314 		$this->register_types();
    315 
    316 		if ( ! empty( $args ) ) {
    317 			$types_properties = $this->get_types_properties();
    318 
    319 			$filtered = wp_filter_object_list( $types_properties, $args, $operator );
    320 
    321 			return array_intersect_key( $this->types, $filtered );
    322 		}
    323 
    324 		return $this->types;
    325 	}
    326 
    327 	/**
    328 	 * Get document types with their properties.
    329 	 *
    330 	 * @return array A list of properties arrays indexed by the type.
    331 	 */
    332 	public function get_types_properties() {
    333 		$types_properties = [];
    334 
    335 		foreach ( $this->get_document_types() as $type => $class ) {
    336 			$types_properties[ $type ] = $class::get_properties();
    337 		}
    338 		return $types_properties;
    339 	}
    340 
    341 	/**
    342 	 * Create a document.
    343 	 *
    344 	 * Create a new document using any given parameters.
    345 	 *
    346 	 * @since 2.0.0
    347 	 * @access public
    348 	 *
    349 	 * @param string $type      Document type.
    350 	 * @param array  $post_data An array containing the post data.
    351 	 * @param array  $meta_data An array containing the post meta data.
    352 	 *
    353 	 * @return Document The type of the document.
    354 	 */
    355 	public function create( $type, $post_data = [], $meta_data = [] ) {
    356 		$class = $this->get_document_type( $type, false );
    357 
    358 		if ( ! $class ) {
    359 			return new \WP_Error( 500, sprintf( 'Type %s does not exist.', $type ) );
    360 		}
    361 
    362 		if ( empty( $post_data['post_title'] ) ) {
    363 			$post_data['post_title'] = esc_html__( 'Elementor', 'elementor' );
    364 			if ( 'post' !== $type ) {
    365 				$post_data['post_title'] = sprintf(
    366 					/* translators: %s: Document title */
    367 					__( 'Elementor %s', 'elementor' ),
    368 					call_user_func( [ $class, 'get_title' ] )
    369 				);
    370 			}
    371 			$update_title = true;
    372 		}
    373 
    374 		$meta_data['_elementor_edit_mode'] = 'builder';
    375 
    376 		// Save the type as-is for plugins that hooked at `wp_insert_post`.
    377 		$meta_data[ Document::TYPE_META_KEY ] = $type;
    378 
    379 		$post_data['meta_input'] = $meta_data;
    380 
    381 		$post_id = wp_insert_post( $post_data );
    382 
    383 		if ( ! empty( $update_title ) ) {
    384 			$post_data['ID'] = $post_id;
    385 			$post_data['post_title'] .= ' #' . $post_id;
    386 
    387 			// The meta doesn't need update.
    388 			unset( $post_data['meta_input'] );
    389 
    390 			wp_update_post( $post_data );
    391 		}
    392 
    393 		/** @var Document $document */
    394 		$document = new $class( [
    395 			'post_id' => $post_id,
    396 		] );
    397 
    398 		// Let the $document to re-save the template type by his way + version.
    399 		$document->save( [] );
    400 
    401 		return $document;
    402 	}
    403 
    404 	/**
    405 	 * Remove user edit capabilities if document is not editable.
    406 	 *
    407 	 * Filters the user capabilities to disable editing in admin.
    408 	 *
    409 	 * @param array $allcaps An array of all the user's capabilities.
    410 	 * @param array $caps    Actual capabilities for meta capability.
    411 	 * @param array $args    Optional parameters passed to has_cap(), typically object ID.
    412 	 *
    413 	 * @return array
    414 	 */
    415 	public function remove_user_edit_cap( $allcaps, $caps, $args ) {
    416 		global $pagenow;
    417 
    418 		if ( ! in_array( $pagenow, [ 'post.php', 'edit.php' ], true ) ) {
    419 			return $allcaps;
    420 		}
    421 
    422 		// Don't touch not existing or not allowed caps.
    423 		if ( empty( $caps[0] ) || empty( $allcaps[ $caps[0] ] ) ) {
    424 			return $allcaps;
    425 		}
    426 
    427 		$capability = $args[0];
    428 
    429 		if ( 'edit_post' !== $capability ) {
    430 			return $allcaps;
    431 		}
    432 
    433 		if ( empty( $args[2] ) ) {
    434 			return $allcaps;
    435 		}
    436 
    437 		$post_id = $args[2];
    438 
    439 		$document = Plugin::$instance->documents->get( $post_id );
    440 
    441 		if ( ! $document ) {
    442 			return $allcaps;
    443 		}
    444 
    445 		$allcaps[ $caps[0] ] = $document::get_property( 'is_editable' );
    446 
    447 		return $allcaps;
    448 	}
    449 
    450 	/**
    451 	 * Filter Post Row Actions.
    452 	 *
    453 	 * Let the Document to filter the array of row action links on the Posts list table.
    454 	 *
    455 	 * @param array $actions
    456 	 * @param \WP_Post $post
    457 	 *
    458 	 * @return array
    459 	 */
    460 	public function filter_post_row_actions( $actions, $post ) {
    461 		$document = $this->get( $post->ID );
    462 
    463 		if ( $document ) {
    464 			$actions = $document->filter_admin_row_actions( $actions );
    465 		}
    466 
    467 		return $actions;
    468 	}
    469 
    470 	/**
    471 	 * Save document data using ajax.
    472 	 *
    473 	 * Save the document on the builder using ajax, when saving the changes, and refresh the editor.
    474 	 *
    475 	 * @since 2.0.0
    476 	 * @access public
    477 	 *
    478 	 * @param $request Post ID.
    479 	 *
    480 	 * @throws \Exception If current user don't have permissions to edit the post or the post is not using Elementor.
    481 	 *
    482 	 * @return array The document data after saving.
    483 	 */
    484 	public function ajax_save( $request ) {
    485 		$document = $this->get( $request['editor_post_id'] );
    486 
    487 		if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) {
    488 			throw new \Exception( 'Access denied.' );
    489 		}
    490 
    491 		$this->switch_to_document( $document );
    492 
    493 		// Set the post as global post.
    494 		Plugin::$instance->db->switch_to_post( $document->get_post()->ID );
    495 
    496 		$status = Document::STATUS_DRAFT;
    497 
    498 		if ( isset( $request['status'] ) && in_array( $request['status'], [ Document::STATUS_PUBLISH, Document::STATUS_PRIVATE, Document::STATUS_PENDING, Document::STATUS_AUTOSAVE ], true ) ) {
    499 			$status = $request['status'];
    500 		}
    501 
    502 		if ( Document::STATUS_AUTOSAVE === $status ) {
    503 			// If the post is a draft - save the `autosave` to the original draft.
    504 			// Allow a revision only if the original post is already published.
    505 			if ( in_array( $document->get_post()->post_status, [ Document::STATUS_PUBLISH, Document::STATUS_PRIVATE ], true ) ) {
    506 				$document = $document->get_autosave( 0, true );
    507 			}
    508 		}
    509 
    510 		// Set default page template because the footer-saver doesn't send default values,
    511 		// But if the template was changed from canvas to default - it needed to save.
    512 		if ( Utils::is_cpt_custom_templates_supported() && ! isset( $request['settings']['template'] ) ) {
    513 			$request['settings']['template'] = 'default';
    514 		}
    515 
    516 		$data = [
    517 			'elements' => $request['elements'],
    518 			'settings' => $request['settings'],
    519 		];
    520 
    521 		$document->save( $data );
    522 
    523 		// Refresh after save.
    524 		$document = $this->get( $document->get_post()->ID, false );
    525 
    526 		$return_data = [
    527 			'status' => $document->get_post()->post_status,
    528 			'config' => [
    529 				'document' => [
    530 					'last_edited' => $document->get_last_edited(),
    531 					'urls' => [
    532 						'wp_preview' => $document->get_wp_preview_url(),
    533 					],
    534 				],
    535 			],
    536 		];
    537 
    538 		/**
    539 		 * Returned documents ajax saved data.
    540 		 *
    541 		 * Filters the ajax data returned when saving the post on the builder.
    542 		 *
    543 		 * @since 2.0.0
    544 		 *
    545 		 * @param array    $return_data The returned data.
    546 		 * @param Document $document    The document instance.
    547 		 */
    548 		$return_data = apply_filters( 'elementor/documents/ajax_save/return_data', $return_data, $document );
    549 
    550 		return $return_data;
    551 	}
    552 
    553 	/**
    554 	 * Ajax discard changes.
    555 	 *
    556 	 * Load the document data from an autosave, deleting unsaved changes.
    557 	 *
    558 	 * @since 2.0.0
    559 	 * @access public
    560 	 *
    561 	 * @param $request
    562 	 *
    563 	 * @return bool True if changes discarded, False otherwise.
    564 	 */
    565 	public function ajax_discard_changes( $request ) {
    566 		$document = $this->get( $request['editor_post_id'] );
    567 
    568 		$autosave = $document->get_autosave();
    569 
    570 		if ( $autosave ) {
    571 			$success = $autosave->delete();
    572 		} else {
    573 			$success = true;
    574 		}
    575 
    576 		return $success;
    577 	}
    578 
    579 	public function ajax_get_document_config( $request ) {
    580 		$post_id = absint( $request['id'] );
    581 
    582 		Plugin::$instance->editor->set_post_id( $post_id );
    583 
    584 		$document = $this->get_doc_or_auto_save( $post_id );
    585 
    586 		if ( ! $document ) {
    587 			throw new \Exception( 'Not Found.' );
    588 		}
    589 
    590 		if ( ! $document->is_editable_by_current_user() ) {
    591 			throw new \Exception( 'Access denied.' );
    592 		}
    593 
    594 		// Set the global data like $post, $authordata and etc
    595 		Plugin::$instance->db->switch_to_post( $post_id );
    596 
    597 		$this->switch_to_document( $document );
    598 
    599 		// Change mode to Builder
    600 		$document->set_is_built_with_elementor( true );
    601 
    602 		$doc_config = $document->get_config();
    603 
    604 		return $doc_config;
    605 	}
    606 
    607 	/**
    608 	 * Switch to document.
    609 	 *
    610 	 * Change the document to any new given document type.
    611 	 *
    612 	 * @since 2.0.0
    613 	 * @access public
    614 	 *
    615 	 * @param Document $document The document to switch to.
    616 	 */
    617 	public function switch_to_document( $document ) {
    618 		// If is already switched, or is the same post, return.
    619 		if ( $this->current_doc === $document ) {
    620 			$this->switched_data[] = false;
    621 			return;
    622 		}
    623 
    624 		$this->switched_data[] = [
    625 			'switched_doc' => $document,
    626 			'original_doc' => $this->current_doc, // Note, it can be null if the global isn't set
    627 		];
    628 
    629 		$this->current_doc = $document;
    630 	}
    631 
    632 	/**
    633 	 * Restore document.
    634 	 *
    635 	 * Rollback to the original document.
    636 	 *
    637 	 * @since 2.0.0
    638 	 * @access public
    639 	 */
    640 	public function restore_document() {
    641 		$data = array_pop( $this->switched_data );
    642 
    643 		// If not switched, return.
    644 		if ( ! $data ) {
    645 			return;
    646 		}
    647 
    648 		$this->current_doc = $data['original_doc'];
    649 	}
    650 
    651 	/**
    652 	 * Get current document.
    653 	 *
    654 	 * Retrieve the current document.
    655 	 *
    656 	 * @since 2.0.0
    657 	 * @access public
    658 	 *
    659 	 * @return Document The current document.
    660 	 */
    661 	public function get_current() {
    662 		return $this->current_doc;
    663 	}
    664 
    665 	public function localize_settings( $settings ) {
    666 		$translations = [];
    667 
    668 		foreach ( $this->get_document_types() as $type => $class ) {
    669 			$translations[ $type ] = $class::get_title();
    670 		}
    671 
    672 		return array_replace_recursive( $settings, [
    673 			'i18n' => $translations,
    674 		] );
    675 	}
    676 
    677 	private function register_types() {
    678 		if ( ! did_action( 'elementor/documents/register' ) ) {
    679 			/**
    680 			 * Register Elementor documents.
    681 			 *
    682 			 * @since 2.0.0
    683 			 *
    684 			 * @param Documents_Manager $this The document manager instance.
    685 			 */
    686 			do_action( 'elementor/documents/register', $this );
    687 		}
    688 	}
    689 
    690 	/**
    691 	 * Get create new post URL.
    692 	 *
    693 	 * Retrieve a custom URL for creating a new post/page using Elementor.
    694 	 *
    695 	 * @param string $post_type Optional. Post type slug. Default is 'page'.
    696 	 * @param string|null $template_type Optional. Query arg 'template_type'. Default is null.
    697 	 *
    698 	 * @return string A URL for creating new post using Elementor.
    699 	 */
    700 	public static function get_create_new_post_url( $post_type = 'page', $template_type = null ) {
    701 		$query_args = [
    702 			'action' => 'elementor_new_post',
    703 			'post_type' => $post_type,
    704 		];
    705 
    706 		if ( $template_type ) {
    707 			$query_args['template_type'] = $template_type;
    708 		}
    709 
    710 		$new_post_url = add_query_arg( $query_args, admin_url( 'edit.php' ) );
    711 
    712 		$new_post_url = add_query_arg( '_wpnonce', wp_create_nonce( 'elementor_action_new_post' ), $new_post_url );
    713 
    714 		return $new_post_url;
    715 	}
    716 }