JFIF    $         d d  7    * Z qn )ZP 0   !1 A "#a$2Qr   D8 a Ri [f  \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H  ~ , l s / 9  ć: [=/#f ; ) x { ٛE Q )~     = 𘙲r*2~  a _V = '  ku m F  D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq  1dđ #̩ ͑ 0  ; H ] u 搂 @:~ ו K L  N s h } O I R* 8 :2 ! lD JV o (  3 = M ( z Ȱ+i *N A  r6K n S l ) ! J J Ӂ* %  ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c  P A\IL Nj! J ۱+ O\ ͔ ] ޒ S߼ J ȧ c%A N ol  ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f  h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0  'G=i 2N +0 e 2 ]] 9 VT P  O׮ 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P  < 0 x <     !1AQ "Raq 02Br #S CTb  ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV׸ YT D I 6 2 '8Y2  7'\T P.6 d &˦ @V q i  |8 -O Ε  ] ʔ U=   T L 8= ;6c |  !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U# 6w +  +s &r+n ڐ   { @ 29 gL u " T Ù  M=6  (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 !  OS  r D  5 7 % | ?x> L |  / nD 6?/8 w # [ ) L 7 +6〼T A T g   !  %5  Mm Z /c  -{1_Je "| ^$ 'O  & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@  u6  BԒ  i YN * f 0 _ GhKW u  lT <nj- 0{  M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q  $ H8o Q˼EV ~ k 0 h' L  Pp M -M"  m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @񄞅~p   #   !1QAa q  ? AL0L  x LU 4 d ; x   " 16ad0 7.\ߋ 0  = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X  &Q  З non]я u ג./ ; gZ$ M QР 2M ]h ni 0  P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _  1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3  6 #Ik+ v  {ƒ v i ( ވ  ? a e)  X xy/*@Q Ǽ Ծ ) $ IWO l c G;  !i  k{ gOŀ L ('x  T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ)  X 5 c QɎMy W ?  Q *!R > 6=7)Xj 5`J ] e  8%t ! + ' ! 1Q 5   !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $  v , ,m   㵜5u bV  =sY+ @d { N ! dn O < .- B;  _w J t 6;Q  Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш  e黅X ۢ F :)[ F G X ƹ / w _c MeD   ,ʡc c .  W D  tA $ j@ : ) - # u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P  6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L !  ~ y5 g9T̅J kbM' +s :S +B) v @ M  j e C f j E 0  Y\Qn z G 1 д~ W o{T 9?`  Rm yhsy3 !HAD ] m  c1 ~ 2 L  S u7x T ;j$ `} 4-> L#  vzŏ I L S  ֭  T { r  jGK C ; b  pU= - ` Bs K. S F w4  M q] Z dH S 0 ) tLg Tiny File Manager | components-rest-api.php
  • File: components-rest-api.php
  • Full Path: /home/mata8712/public_html/on-me-trouve/wp-content/plugins/elementor/modules/components/components-rest-api.php
  • File size: 21.37 KB
  • MIME-type: text/x-php
  • Charset: utf-8
<?php

namespace Elementor\Modules\Components;

use Elementor\Core\Base\Document;
use Elementor\Core\Utils\Api\Error_Builder;
use Elementor\Core\Utils\Api\Response_Builder;
use Elementor\Core\Utils\Collection;
use Elementor\Modules\Components\Documents\Component;
use Elementor\Modules\Components\OverridableProps\Component_Overridable_Props_Parser;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

class Components_REST_API {
	const API_NAMESPACE = 'elementor/v1';
	const API_BASE = 'components';
	const LOCK_DOCUMENT_TYPE_NAME = 'components';
	const STYLES_ROUTE = 'styles';
	const MAX_COMPONENTS = 100;

	private $repository = null;
	public function register_hooks() {
		add_action( 'rest_api_init', fn() => $this->register_routes() );
	}

	private function get_repository() {
		if ( ! $this->repository ) {
			$this->repository = new Components_Repository();
		}

		return $this->repository;
	}

	/**
	 * @return Component_Lock_Manager instance
	 */
	private function get_component_lock_manager() {
		return Component_Lock_Manager::get_instance();
	}

	private function register_routes() {
		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [
			[
				'methods' => 'GET',
				'callback' => fn() => $this->route_wrapper( fn() => $this->get_components() ),
				'permission_callback' => fn() => current_user_can( 'edit_posts' ),
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/' . self::STYLES_ROUTE, [
			[
				'methods' => 'GET',
				'callback' => fn() => $this->route_wrapper( fn() => $this->get_styles() ),
				'permission_callback' => fn() => current_user_can( 'edit_posts' ),
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [
			[
				'methods' => 'POST',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->create_components( $request ) ),
				'permission_callback' => fn() => current_user_can( 'manage_options' ),
				'args' => [
					'status' => [
						'type' => 'string',
						'enum' => [ Document::STATUS_PUBLISH, Document::STATUS_DRAFT, Document::STATUS_AUTOSAVE ],
						'required' => true,
					],
					'items' => [
						'type' => 'array',
						'required' => true,
						'items' => [
							'type' => 'object',
							'properties' => [
								'uid' => [
									'type' => 'string',
									'required' => true,
								],
								'title' => [
									'type' => 'string',
									'required' => true,
									'minLength' => 2,
									'maxLength' => 200,
								],
								'elements' => [
									'type' => 'array',
									'required' => true,
									'items' => [
										'type' => 'object',
									],
								],
								'settings' => [
									'type' => 'object',
									'required' => false,
								],
							],
						],
					],
				],
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/create-validate', [
			[
				'methods' => 'POST',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->create_validate_components( $request ) ),
				'permission_callback' => fn() => current_user_can( 'manage_options' ),
				'args' => [
					'items' => [
						'type' => 'array',
						'required' => true,
						'items' => [
							'type' => 'object',
							'properties' => [
								'uid' => [
									'type' => 'string',
									'required' => true,
								],
								'title' => [
									'type' => 'string',
									'required' => true,
									'minLength' => 2,
									'maxLength' => 200,
								],
								'elements' => [
									'type' => 'array',
									'required' => true,
									'items' => [
										'type' => 'object',
									],
								],
								'settings' => [
									'type' => 'object',
									'required' => false,
								],
							],
						],
					],
				],
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/overridable-props', [
			[
				'methods' => 'GET',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->get_overridable_props( $request ) ),
				'permission_callback' => fn() => current_user_can( 'edit_posts' ),
				'args' => [
					'componentIds' => [
						'type' => 'array',
						'items' => [
							'type' => 'integer',
						],
						'required' => true,
						'description' => 'The component IDs to get overridable props for',
					],
				],
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/status', [
			[
				'methods' => 'PUT',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->update_statuses( $request ) ),
				'permission_callback' => fn() => current_user_can( 'manage_options' ),
				'args' => [
					'status' => [
						'type' => 'string',
						'required' => true,
						'enum' => [ Document::STATUS_PUBLISH ],
					],
					'ids' => [
						'type' => 'array',
						'required' => true,
						'items' => [
							'type' => 'number',
							'required' => true,
						],
					],
				],
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/lock', [
			[
				'methods' => 'POST',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->lock_component( $request ) ),
				'permission_callback' => fn() => current_user_can( 'manage_options' ),
				'args' => [
					'componentId' => [
						'type' => 'number',
						'required' => true,
						'description' => 'The component ID to unlock',
					],
				],
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/unlock', [
			[
				'methods' => 'POST',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->unlock_component( $request ) ),
				'permission_callback' => fn() => current_user_can( 'manage_options' ),
				'args' => [
					'componentId' => [
						'type' => 'number',
						'required' => true,
						'description' => 'The component ID to unlock',
					],
				],
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/lock-status', [
			[
				'methods' => 'GET',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->get_lock_status( $request ) ),
				'permission_callback' => fn() => current_user_can( 'manage_options' ),
				'args' => [
					'componentId' => [
						'type' => 'string',
						'required' => true,
						'description' => 'The component ID to check lock status',
					],
				],
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/archive', [
			[
				'methods' => 'POST',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->archive_components( $request ) ),
				'permission_callback' => fn() => current_user_can( 'manage_options' ),
				'args' => [
					'componentIds' => [
						'type' => 'array',
						'items' => [
							'type' => 'number',
							'required' => true,
						],
						'required' => true,
						'description' => 'The component IDs to archive',
					],
					'status' => [
						'type' => 'string',
						'enum' => [ Document::STATUS_PUBLISH, Document::STATUS_DRAFT, Document::STATUS_AUTOSAVE ],
						'required' => true,
					],
				],
			],
		] );

		register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE . '/update-titles', [
			[
				'methods' => 'POST',
				'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->update_components_title( $request ) ),
				'permission_callback' => fn() => current_user_can( 'manage_options' ),
				'args' => [
					'components' => [
						'type' => 'array',
						'required' => true,
						'items' => [
							'type' => 'object',
							'properties' => [
								'componentId' => [
									'type' => 'number',
									'required' => true,
									'description' => 'The component ID to update title',
								],
								'title' => [
									'type' => 'string',
									'required' => true,
									'description' => 'The new title for the component',
								],
							],
						],
					],
					'status' => [
						'type' => 'string',
						'enum' => [ Document::STATUS_PUBLISH, Document::STATUS_DRAFT, Document::STATUS_AUTOSAVE ],
						'required' => true,
					],
				],
			],
		] );
	}

	private function get_components() {
		$components = $this->get_repository()->all();

		$components_list = array_values( $components
			->map( fn( $component ) => [
				'id' => $component['id'],
				'name' => $component['title'],
				'uid' => $component['uid'],
				'isArchived' => $component['is_archived'] ?? false,
			] )
		->all() );

		return Response_Builder::make( $components_list )->build();
	}

	private function get_styles() {
		$components = $this->get_repository()->all();

		$styles = [];
		$components->each( function( $component ) use ( &$styles ) {
			$styles[ $component['id'] ] = $component['styles'];
		} );

		return Response_Builder::make( $styles )->build();
	}

	private function get_overridable_props( \WP_REST_Request $request ) {
		$component_ids = $request->get_param( 'componentIds' );

		$data = [];
		$errors = [];

		foreach ( $component_ids as $component_id ) {
			$component_id = (int) $component_id;

			/** @var Component $document */
			$document = $this->get_repository()->get( $component_id );

			if ( ! $document ) {
				$errors[ $component_id ] = 'component_not_found';
				continue;
			}

			// This is a fix for the case where overridable props in element settings where migrated
			// but the overridable props metadata were not aligned with the new origin values.
			// In version 4.0.1, we fixed this by running the align_overridable_props_with_elements method after the migration.
			$document_version = $document->get_elementor_version();
			$overridable_props_migration_fix_version = '4.0.1';
			$should_align_overridable_props = version_compare( $document_version, $overridable_props_migration_fix_version, '<=' );
			if ( $should_align_overridable_props ) {
				$document->align_overridable_props_with_elements();
			}

			$overridable = $document->get_json_meta( Component::OVERRIDABLE_PROPS_META_KEY );

			$data[ $component_id ] = empty( $overridable ) ? null : $overridable;
		}

		return Response_Builder::make( $data )
			->set_meta( [ 'errors' => $errors ] )
			->build();
	}

	private function create_components( \WP_REST_Request $request ) {
		if ( ! Components_Access_Controller::can_create() ) {
			return $this->get_insufficient_permissions_error( 'create' );
		}

		$save_status = $request->get_param( 'status' );

		$items = Collection::make( $request->get_param( 'items' ) );
		$components = $this->get_repository()->all();

		$result = Save_Components_Validator::make( $components )->validate( $items );

		if ( ! $result['success'] ) {
			return Error_Builder::make( 'components_validation_failed' )
				->set_status( 422 )
				->set_message( 'Validation failed: ' . implode( ', ', $result['messages'] ) )
				->build();
		}

		$circular_result = Circular_Dependency_Validator::make()->validate_new_components( $items );

		if ( ! $circular_result['success'] ) {
			return Error_Builder::make( 'circular_dependency_detected' )
				->set_status( 422 )
				->set_message( __( "Can't add this component - components that contain each other can't be nested.", 'elementor' ) )
				->set_meta( [ 'caused_by' => $circular_result['messages'] ] )
				->build();
		}

		$non_atomic_result = Non_Atomic_Widget_Validator::make()->validate_items( $items );

		if ( ! $non_atomic_result['success'] ) {
			return Error_Builder::make( Non_Atomic_Widget_Validator::ERROR_CODE )
				->set_status( 422 )
				->set_message( __( 'Components require atomic elements only. Remove widgets to create this component.', 'elementor' ) )
				->set_meta( [ 'non_atomic_elements' => $non_atomic_result['non_atomic_elements'] ] )
				->build();
		}

		$validation_errors = [];

		$created = $items->map_with_keys( function ( $item ) use ( $save_status, &$validation_errors ) {
			$title = sanitize_text_field( $item['title'] );
			$content = $item['elements'];
			$uid = $item['uid'];

			try {
				$settings = isset( $item['settings'] ) ? $this->parse_settings( $item['settings'] ) : [];

				$status = Document::STATUS_AUTOSAVE === $save_status
					? Document::STATUS_DRAFT
					: $save_status;

				$component_id = $this->get_repository()->create( $title, $content, $status, $uid, $settings );

				return [ $uid => $component_id ];
			} catch ( \Exception $e ) {
				$validation_errors[ $uid ] = $e->getMessage();
				return [ $uid => null ];
			}
		} );

		if ( ! empty( $validation_errors ) ) {
			return Error_Builder::make( 'settings_validation_failed' )
				->set_status( 422 )
				->set_message( 'Settings validation failed: ' . json_encode( $validation_errors ) )
				->build();
		}

		return Response_Builder::make( $created->all() )
			->set_status( 201 )
			->build();
	}

	private function update_statuses( \WP_REST_Request $request ) {
		if ( ! Components_Access_Controller::can_publish() ) {
			return $this->get_insufficient_permissions_error( 'publish' );
		}

		$result = Collection::make( $request->get_param( 'ids' ) )
			->reduce(
				function ( $result, int $component_id ) {
					$component = $this->get_repository()->get( $component_id );

					if ( ! $component ) {
						$result['failed'][] = $component_id;
						return $result;
					}

					$publish_result = $this->get_repository()->publish_component( $component );

					$result[ $publish_result ? 'success' : 'failed' ][] = $component_id;

					return $result;
				},
				[
					'success' => [],
					'failed' => [],
				]
			);

		return Response_Builder::make( $result )->build();
	}

	private function lock_component( \WP_REST_Request $request ) {
		if ( ! Components_Access_Controller::can_lock() ) {
			return $this->get_insufficient_permissions_error( 'lock' );
		}

		$component_id = $request->get_param( 'componentId' );
		try {
			$success = $this->get_component_lock_manager()->lock( $component_id );
		} catch ( \Exception $e ) {
			error_log( 'Components REST API lock_component error: ' . $e->getMessage() );
			return Error_Builder::make( 'lock_failed' )
				->set_status( 500 )
				->set_message( __( 'Failed to lock component', 'elementor' ) )
				->build();
		}

		if ( ! $success ) {
			return Error_Builder::make( 'lock_failed' )
				->set_status( 500 )
				->set_message( __( 'Failed to lock component', 'elementor' ) )
				->build();
		}

		return Response_Builder::make( [ 'locked' => $success ] )->build();
	}

	private function unlock_component( \WP_REST_Request $request ) {
		if ( ! Components_Access_Controller::can_lock() ) {
			return $this->get_insufficient_permissions_error( 'unlock' );
		}

		$component_id = $request->get_param( 'componentId' );
		try {
			$success = $this->get_component_lock_manager()->unlock( $component_id );
		} catch ( \Exception $e ) {
			error_log( 'Components REST API unlock_component error: ' . $e->getMessage() );
			return Error_Builder::make( 'unlock_failed' )
				->set_status( 500 )
				->set_message( __( 'Failed to unlock component', 'elementor' ) )
				->build();
		}

		if ( ! $success ) {
			return Error_Builder::make( 'unlock_failed' )
				->set_status( 500 )
				->set_message( __( 'Failed to unlock component', 'elementor' ) )
				->build();
		}
		return Response_Builder::make( [ 'unlocked' => $success ] )->build();
	}

	private function get_lock_status( \WP_REST_Request $request ) {
		if ( ! Components_Access_Controller::can_lock() ) {
			return $this->get_insufficient_permissions_error( 'lock_status' );
		}

		$component_id = (int) $request->get_param( 'componentId' );
		try {
			$lock_manager = $this->get_component_lock_manager();
			if ( $lock_manager->is_lock_expired( $component_id ) ) {
				$lock_manager->unlock( $component_id );
			}

			$lock_data = $lock_manager->get_lock_data( $component_id );
			$current_user_id = get_current_user_id();

			// if current  user is the lock user, return true
			if ( $lock_data['locked_by'] && $lock_data['locked_by'] === $current_user_id ) {
				return Response_Builder::make( [
					'is_current_user_allow_to_edit' => true,
					'locked_by' => get_user_by( 'id', $lock_data['locked_by'] )->display_name,
				] )->build();
			}

			// if the user is not the lock user, return false
			if ( $lock_data['locked_by'] && $lock_data['locked_by'] !== $current_user_id ) {
				return Response_Builder::make( [
					'is_current_user_allow_to_edit' => false,
					'locked_by' => get_user_by( 'id', $lock_data['locked_by'] )->display_name,
				] )->build();
			}

			// if the component is not locked, return true
			if ( ! $lock_data['locked_by'] ) {
				return Response_Builder::make( [
					'is_current_user_allow_to_edit' => true,
					'locked_by' => null,
				] )->build();
			}
		} catch ( \Exception $e ) {
			error_log( 'Components REST API get_lock_status error: ' . $e->getMessage() );
			return Error_Builder::make( 'get_lock_status_failed' )
				->set_status( 500 )
				->set_message( __( 'Failed to get lock status', 'elementor' ) )
				->build();
		}
	}

	private function archive_components( \WP_REST_Request $request ) {
		if ( ! Components_Access_Controller::can_delete() ) {
			return $this->get_insufficient_permissions_error( 'delete' );
		}

		$component_ids = $request->get_param( 'componentIds' );
		$status = $request->get_param( 'status' );

		try {
			$result = $this->get_repository()->archive( $component_ids, $status );
		} catch ( \Exception $e ) {
			error_log( 'Components REST API archive_components error: ' . $e->getMessage() );
			return Error_Builder::make( 'archive_failed' )
				->set_meta( [ 'error' => $e->getMessage() ] )
				->set_status( 500 )
				->set_message( __( 'Failed to archive components', 'elementor' ) )
				->build();
		}
		return Response_Builder::make( $result )->build();
	}

	private function update_components_title( \WP_REST_Request $request ) {
		if ( ! Components_Access_Controller::can_rename() ) {
			return $this->get_insufficient_permissions_error( 'rename' );
		}

		$failed_ids = [];
		$success_ids = [];
		$components = $request->get_param( 'components' );
		$status = $request->get_param( 'status' );

		foreach ( $components as $component ) {
			$is_success = $this->get_repository()->update_title( $component['componentId'], $component['title'], $status );

			if ( ! $is_success ) {
				$failed_ids[] = $component['componentId'];
				continue;
			}
			$success_ids[] = $component['componentId'];

		}
		return Response_Builder::make( [
			'failedIds' => $failed_ids,
			'successIds' => $success_ids,
		] )->build();
	}

	private function create_validate_components( \WP_REST_Request $request ) {
		if ( ! Components_Access_Controller::can_create() ) {
			return $this->get_insufficient_permissions_error( 'create' );
		}

		$items = Collection::make( $request->get_param( 'items' ) );
		$components = $this->get_repository()->all();

		$result = Save_Components_Validator::make( $components )->validate( $items );

		if ( ! $result['success'] ) {
			return Error_Builder::make( 'components_validation_failed' )
				->set_status( 422 )
				->set_message( 'Validation failed: ' . implode( ', ', $result['messages'] ) )
				->build();
		}

		$circular_result = Circular_Dependency_Validator::make()->validate_new_components( $items );

		if ( ! $circular_result['success'] ) {
			return Error_Builder::make( 'circular_dependency_detected' )
				->set_status( 422 )
				->set_message( __( "Can't add this component - components that contain each other can't be nested.", 'elementor' ) )
				->set_meta( [ 'caused_by' => $circular_result['messages'] ] )
				->build();
		}

		$non_atomic_result = Non_Atomic_Widget_Validator::make()->validate_items( $items );

		if ( ! $non_atomic_result['success'] ) {
			return Error_Builder::make( Non_Atomic_Widget_Validator::ERROR_CODE )
				->set_status( 422 )
				->set_message( __( 'Components require atomic elements only. Remove widgets to create this component.', 'elementor' ) )
				->set_meta( [ 'non_atomic_elements' => $non_atomic_result['non_atomic_elements'] ] )
				->build();
		}

		$validation_errors = $items->map_with_keys( function ( $item ) {
			try {
				if ( isset( $item['settings'] ) ) {
					$this->parse_settings( $item['settings'] );
				}
			} catch ( \Exception $e ) {
				return [ $item['uid'] => $e->getMessage() ];
			}

			return [ $item['uid'] => null ];
		} )
		->filter( fn( $value ) => null !== $value );

		if ( ! $validation_errors->is_empty() ) {
			return Error_Builder::make( 'settings_validation_failed' )
				->set_status( 422 )
				->set_message( 'Settings validation failed: ' . json_encode( $validation_errors->all() ) )
				->build();
		}

		return Response_Builder::make()
			->set_status( 200 )
			->build();
	}

	private function parse_settings( array $settings ): array {
		$result = [];

		if ( empty( $settings ) ) {
			return $result;
		}

		if ( isset( $settings['overridable_props'] ) ) {
			$parser = Component_Overridable_Props_Parser::make();
			$overridable_props_result = $parser->parse( $settings['overridable_props'] );

			if ( ! $overridable_props_result->is_valid() ) {
				throw new \Exception(
					esc_html( 'Validation failed for overridable_props: ' . $overridable_props_result->errors()->to_string() )
				);
			}

			$result['overridable_props'] = $overridable_props_result->unwrap();
		}

		return $result;
	}

	private function route_wrapper( callable $cb ) {
		try {
			$response = $cb();
		} catch ( \Exception $e ) {
			return Error_Builder::make( 'unexpected_error' )
			->set_message( __( 'Something went wrong', 'elementor' ) )
			->build();
		}

		return $response;
	}

	private function get_insufficient_permissions_error( string $action ) {
		return Error_Builder::make( 'insufficient_permissions' )
			->set_status( 403 )
			->set_message( __( 'You do not have permission to perform this action.', 'elementor' ) )
			->set_meta( [
				'action' => $action,
				'tier' => Components_Access_Controller::get_access_tier(),
			] )
			->build();
	}
}