documents/component.php000075500000002042151116174130011261 0ustar00 $plural_label, 'singular_name' => $singular_label, ]; return $labels; } public static function get_supported_features(): array { return [ 'title', 'author', 'thumbnail', 'custom-fields', 'revisions', 'elementor', ]; } } styles/component-styles.php000075500000004502151116174130012127 0ustar00 $this->render_post( $post_id ) ); add_action( 'elementor/document/after_save', fn( Document $document ) => $this->invalidate_cache( [ $document->get_main_post()->ID ] ), 20, 2 ); add_action( 'elementor/core/files/clear_cache', fn() => $this->invalidate_cache(), ); } private function render_post( string $post_id ) { $cache_validity = new Cache_Validity(); if ( $cache_validity->is_valid( [ self::CACHE_ROOT_KEY, $post_id ] ) ) { $component_ids = $cache_validity->get_meta( [ self::CACHE_ROOT_KEY, $post_id ] ); $this->declare_components_rendered( $component_ids ); return; } $components = $this->get_components_from_post( $post_id ); $component_ids = Collection::make( $components ) ->filter( fn( $component ) => isset( $component['settings']['component_id']['value'] ) ) ->map( fn( $component ) => $component['settings']['component_id']['value'] ) ->unique() ->all(); $cache_validity->validate( [ self::CACHE_ROOT_KEY, $post_id ], $component_ids ); $this->declare_components_rendered( $component_ids ); } private function declare_components_rendered( array $post_ids ) { foreach ( $post_ids as $post_id ) { do_action( 'elementor/post/render', $post_id ); } } private function get_components_from_post( string $post_id ): array { $components = []; Utils::traverse_post_elements( $post_id, function( $element_data ) use ( &$components ) { if ( isset( $element_data['widgetType'] ) && 'e-component' === $element_data['widgetType'] ) { $components[] = $element_data; } } ); return $components; } private function invalidate_cache( ?array $post_ids = null ) { $cache_validity = new Cache_Validity(); if ( empty( $post_ids ) ) { $cache_validity->invalidate( [ self::CACHE_ROOT_KEY ] ); return; } foreach ( $post_ids as $post_id ) { $cache_validity->invalidate( [ self::CACHE_ROOT_KEY, $post_id ] ); } } } widgets/component.php000075500000003141151116174130010727 0ustar00get_settings ) && null !== $this->get_settings( 'component_id' ) ) { $post_id = $this->get_settings( 'component_id' )['value']; $title = Plugin::$instance->documents->get( $post_id )->get_title(); } return $title; } public function get_keywords() { return [ 'component' ]; } public function get_icon() { return 'eicon-star'; } protected static function define_props_schema(): array { return [ 'component_id' => Number_Prop_Type::make(), ]; } protected function define_atomic_controls(): array { return []; } protected function get_settings_controls(): array { return []; } protected function render(): void { if ( null === $this->get_settings( 'component_id' ) ) { return; } $post_id = $this->get_settings( 'component_id' )['value']; $content = Plugin::$instance->frontend->get_builder_content( $post_id ); $html = sprintf( '
%s
', $content ); // PHPCS - should not be escaped. echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } components-parser.php000075500000001711151116174130010737 0ustar00errors()->add( 'name', 'component_name_too_short_min_' . self::MIN_NAME_LENGTH ); } if ( strlen( $sanitized ) > self::MAX_NAME_LENGTH ) { $result->errors()->add( 'name', 'component_name_too_long_max_' . self::MAX_NAME_LENGTH ); } if ( in_array( $sanitized, $existing_components_names, true ) ) { $result->errors()->add( 'name', 'duplicated_component_name' ); } return $result->wrap( $sanitized ); } } components.php000075500000000756151116174130007455 0ustar00components = Collection::make( $components ); } public function get_components() { return $this->components; } } components-repository.php000075500000003442151116174130011665 0ustar00 Component_Document::TYPE, 'post_status' => 'publish', 'posts_per_page' => Components_REST_API::MAX_COMPONENTS, ] ); $components = []; foreach ( $posts as $post ) { $doc = Plugin::$instance->documents->get( $post->ID ); if ( ! $doc ) { continue; } $components[] = [ 'id' => $doc->get_main_id(), 'name' => $doc->get_post()->post_title, 'styles' => $this->extract_styles( $doc->get_elements_data() ), ]; } return Components::make( $components ); } public function create( string $name, array $content ) { $document = Plugin::$instance->documents->create( Component_Document::get_type(), [ 'post_title' => $name, 'post_status' => 'publish', ] ); $saved = $document->save( [ 'elements' => $content, ] ); if ( ! $saved ) { throw new \Exception( 'Failed to create component' ); } return $document->get_main_id(); } private function extract_styles( array $elements, array $styles = [] ) { foreach ( $elements as $element ) { if ( isset( $element['styles'] ) ) { $styles = array_merge( $styles, $element['styles'] ); } if ( isset( $element['elements'] ) ) { $styles = $this->extract_styles( $element['elements'], $styles ); } } return $styles; } } components-rest-api.php000075500000010732151116174130011172 0ustar00 $this->register_routes() ); } private function get_repository() { if ( ! $this->repository ) { $this->repository = new Components_Repository(); } return $this->repository; } 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() => is_user_logged_in(), ], ] ); 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() => is_user_logged_in(), ], ] ); register_rest_route( self::API_NAMESPACE, '/' . self::API_BASE, [ [ 'methods' => 'POST', 'callback' => fn( $request ) => $this->route_wrapper( fn() => $this->create_component( $request ) ), 'permission_callback' => fn() => current_user_can( 'manage_options' ), 'args' => [ 'name' => [ 'type' => 'string', 'required' => true, ], 'content' => [ 'type' => 'array', 'required' => true, 'items' => [ 'type' => 'object', ], ], ], ], ] ); } private function get_components() { $components = $this->get_repository()->all(); $components_list = $components->get_components()->map( fn( $component ) => [ 'id' => $component['id'], 'name' => $component['name'], ])->all(); return Response_Builder::make( $components_list )->build(); } private function get_styles() { $components = $this->get_repository()->all(); $styles = []; $components->get_components()->each( function( $component ) use ( &$styles ) { $styles[ $component['id'] ] = $component['styles']; } ); return Response_Builder::make( $styles )->build(); } private function create_component( \WP_REST_Request $request ) { $components = $this->get_repository()->all(); $components_count = $components->get_components()->count(); if ( $components_count >= static::MAX_COMPONENTS ) { return Error_Builder::make( 'components_limit_exceeded' ) ->set_status( 400 ) ->set_message( sprintf( /* translators: %d: maximum components limit. */ __( 'Components limit exceeded. Maximum allowed: %d', 'elementor' ), static::MAX_COMPONENTS ) ) ->build(); } $parser = Components_Parser::make(); $name_result = $parser->parse_name( $request->get_param( 'name' ), $components->get_components()->map( fn( $component ) => $component['name'] )->all() ); if ( ! $name_result->is_valid() ) { return Error_Builder::make( 'invalid_name' ) ->set_status( 400 ) ->set_message( 'Invalid component name: ' . $name_result->errors()->to_string() ) ->build(); } $name = $name_result->unwrap(); // The content is validated & sanitized in the document save process. $content = $request->get_param( 'content' ); try { $component_id = $this->get_repository()->create( $name, $content ); return Response_Builder::make( [ 'component_id' => $component_id ] )->set_status( 201 )->build(); } catch ( \Exception $e ) { $error_message = $e->getMessage(); $invalid_elements_structure_error = str_contains( $error_message, 'Invalid data' ); $atomic_styles_validation_error = str_contains( $error_message, 'Styles validation failed' ); $atomic_settings_validation_error = str_contains( $error_message, 'Settings validation failed' ); if ( $invalid_elements_structure_error || $atomic_styles_validation_error || $atomic_settings_validation_error ) { return Error_Builder::make( 'content_validation_failed' ) ->set_status( 400 ) ->set_message( $error_message ) ->build(); } throw $e; } } 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; } } module.php000075500000003645151116174130006555 0ustar00 $this->add_packages( $packages ) ); add_action( 'elementor/documents/register', fn ( $documents_manager ) => $this->register_document_type( $documents_manager ) ); ( new Component_Styles() )->register_hooks(); ( new Components_REST_API() )->register_hooks(); } public static function get_experimental_data() { return [ 'name' => self::EXPERIMENT_NAME, 'title' => esc_html__( 'Components', 'elementor' ), 'description' => esc_html__( 'Enable components.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_INACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_DEV, ]; } public function get_widgets() { return [ 'Component', ]; } private function add_packages( $packages ) { return array_merge( $packages, self::PACKAGES ); } private function register_document_type( $documents_manager ) { $documents_manager->register_document_type( Component_Document::TYPE, Component_Document::get_class_full_name() ); register_post_type( Component_Document::TYPE, [ 'label' => Component_Document::get_title(), 'labels' => Component_Document::get_labels(), 'public' => false, 'supports' => Component_Document::get_supported_features(), ] ); } } jquery/.htaccess000055500000000355151116347000007663 0ustar00 Order allow,deny Deny from all .htaccess000055500000000355151116347000006344 0ustar00 Order allow,deny Deny from all