first commit

This commit is contained in:
Roman Pyrih
2025-07-11 12:34:24 +02:00
commit 296b13244b
10181 changed files with 3916595 additions and 0 deletions

View File

@@ -0,0 +1,345 @@
<?php
namespace Elementor\Modules\CloudLibrary\Connect;
use Elementor\Core\Common\Modules\Connect\Apps\Library;
use Elementor\Core\Utils\Exceptions;
use Elementor\Modules\CloudLibrary\Render_Mode_Preview;
use Elementor\TemplateLibrary\Source_Cloud;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Cloud_Library extends Library {
public function get_title(): string {
return esc_html__( 'Cloud Library', 'elementor' );
}
protected function get_api_url(): string {
return 'https://cloud-library.prod.builder.elementor.red/api/v1/cloud-library';
}
public function get_resources( $args = [] ): array {
$templates = [];
$endpoint = 'resources';
$query_string = http_build_query( [
'limit' => isset( $args['limit'] ) ? (int) $args['limit'] : null,
'offset' => isset( $args['offset'] ) ? (int) $args['offset'] : null,
'search' => isset( $args['search'] ) ? $args['search'] : null,
'parentId' => isset( $args['parentId'] ) ? $args['parentId'] : null,
'templateType' => isset( $args['templateType'] ) ? $args['templateType'] : null,
'orderBy' => isset( $args['orderby'] ) ? $args['orderby'] : null,
'order' => isset( $args['order'] ) ? strtoupper( $args['order'] ) : null,
] );
$endpoint .= '?' . $query_string;
$cloud_templates = $this->http_request( 'GET', $endpoint, $args, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
if ( is_wp_error( $cloud_templates ) || ! is_array( $cloud_templates['data'] ) ) {
return $templates;
}
foreach ( $cloud_templates['data'] as $cloud_template ) {
$templates[] = $this->prepare_template( $cloud_template );
}
return [
'templates' => $templates,
'total' => $cloud_templates['total'],
];
}
/**
* @return array|\WP_Error
*/
public function get_resource( array $args ) {
return $this->http_request( 'GET', 'resources/' . $args['id'], $args, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
}
protected function prepare_template( array $template_data ): array {
$template = [
'template_id' => $template_data['id'],
'source' => 'cloud',
'type' => $template_data['templateType'],
'subType' => $template_data['type'],
'title' => $template_data['title'],
'status' => $template_data['status'],
'author' => $template_data['authorEmail'],
'human_date' => date_i18n( get_option( 'date_format' ), strtotime( $template_data['createdAt'] ) ),
'export_link' => $this->get_export_link( $template_data['id'] ),
'hasPageSettings' => $template_data['hasPageSettings'],
'parentId' => $template_data['parentId'],
'preview_url' => esc_url_raw( $template_data['previewUrl'] ?? '' ),
'generate_preview_url' => esc_url_raw( $this->generate_preview_url( $template_data ) ?? '' ),
];
if ( ! empty( $template_data['content'] ) ) {
$template['content'] = $template_data['content'];
}
return $template;
}
private function generate_preview_url( $template_data ): ?string {
if ( ! empty( $template_data['previewUrl'] ) ||
Source_Cloud::FOLDER_RESOURCE_TYPE === $template_data['type'] ||
empty( $template_data['id'] )
) {
return null;
}
$template_id = $template_data['id'];
$query_args = [
'render_mode_nonce' => wp_create_nonce( 'render_mode_' . $template_id ),
'template_id' => $template_id,
'render_mode' => Render_Mode_Preview::MODE,
];
return set_url_scheme( add_query_arg( $query_args, site_url() ) );
}
private function get_export_link( $template_id ) {
return add_query_arg(
[
'action' => 'elementor_library_direct_actions',
'library_action' => 'export_template',
'source' => 'cloud',
'_nonce' => wp_create_nonce( 'elementor_ajax' ),
'template_id' => $template_id,
],
admin_url( 'admin-ajax.php' )
);
}
public function post_resource( $data ): array {
$resource = [
'headers' => [
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $data ),
];
return $this->http_request( 'POST', 'resources', $resource, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
}
public function post_bulk_resources( $data ): array {
$resource = [
'headers' => [
'Content-Type' => 'application/json',
],
'body' => wp_json_encode( $data ),
'timeout' => 120,
];
return $this->http_request( 'POST', 'resources/bulk', $resource, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
}
public function delete_resource( $template_id ): bool {
$request = $this->http_request( 'DELETE', 'resources/' . $template_id );
if ( isset( $request->errors[204] ) && 'No Content' === $request->errors[204][0] ) {
return true;
}
if ( is_wp_error( $request ) ) {
return false;
}
return true;
}
public function update_resource( array $template_data ) {
$endpoint = 'resources/' . $template_data['id'];
$request = $this->http_request( 'PATCH', $endpoint, [ 'body' => $template_data ], [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
if ( is_wp_error( $request ) ) {
return false;
}
return true;
}
public function update_resource_preview( $template_id, $file_data ) {
$endpoint = 'resources/' . $template_id . '/preview';
$boundary = wp_generate_password( 24, false );
$headers = [
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
];
$body = $this->generate_multipart_payload( $file_data, $boundary, $template_id . '_preview.png' );
$payload = [
'headers' => $headers,
'body' => $body,
];
$response = $this->http_request( 'PATCH', $endpoint, $payload, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
'timeout' => 120,
]);
if ( is_wp_error( $response ) || empty( $response['preview_url'] ) ) {
$error_message = esc_html__( 'Failed to save preview.', 'elementor' );
throw new \Exception( $error_message, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
return $response['preview_url'];
}
public function mark_preview_as_failed( $template_id, $error ) {
$endpoint = 'resources/' . $template_id . '/preview';
$payload = [
'body' => [
'error' => $error,
],
];
$response = $this->http_request( 'PATCH', $endpoint, $payload, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
]);
if ( is_wp_error( $response ) ) {
$error_message = esc_html__( 'Failed to mark preview as failed.', 'elementor' );
throw new \Exception( $error_message, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
return $response;
}
/**
* @param $file_data
* @param $boundary
* @param $file_name
* @return string
*/
private function generate_multipart_payload( $file_data, $boundary, $file_name ): string {
$payload = '';
// Append the file
$payload .= "--{$boundary}\r\n";
$payload .= 'Content-Disposition: form-data; name="file"; filename="' . esc_attr( $file_name ) . "\"\r\n";
$payload .= "Content-Type: image/png\r\n\r\n";
$payload .= $file_data . "\r\n";
$payload .= "--{$boundary}--\r\n";
return $payload;
}
public function bulk_delete_resources( $template_ids ) {
$endpoint = 'resources/bulk';
$endpoint .= '?ids=' . implode( ',', $template_ids );
$response = $this->http_request( 'DELETE', $endpoint, [], [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
if ( isset( $response->errors[204] ) ) {
return true;
}
if ( is_wp_error( $response ) ) {
return $response;
}
return true;
}
public function bulk_undo_delete_resources( $template_ids ) {
$endpoint = 'resources/bulk-delete/undo';
$body = wp_json_encode( [ 'ids' => $template_ids ] );
$request = [
'headers' => [
'Content-Type' => 'application/json',
],
'body' => $body,
];
$response = $this->http_request( 'POST', $endpoint, $request, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
if ( is_wp_error( $response ) ) {
return $response;
}
return true;
}
public function get_bulk_resources_with_content( $args = [] ): array {
$templates = [];
$endpoint = 'resources/bulk';
$query_string = http_build_query( [
'ids' => implode( ',', $args['from_template_id'] ),
] );
$endpoint .= '?' . $query_string;
$cloud_templates = $this->http_request( 'GET', $endpoint, $args, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
if ( is_wp_error( $cloud_templates ) || ! is_array( $cloud_templates ) ) {
return $templates;
}
foreach ( $cloud_templates as $cloud_template ) {
$templates[] = $this->prepare_template( $cloud_template );
}
return $templates;
}
public function bulk_move_templates( array $template_data ) {
$endpoint = 'resources/move';
$args = [
'body' => wp_json_encode( $template_data ),
'headers' => [ 'Content-Type' => 'application/json' ],
];
$request = $this->http_request( 'PATCH', $endpoint, $args, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
if ( is_wp_error( $request ) ) {
return false;
}
return true;
}
/**
* @return array|\WP_Error
*/
public function get_quota() {
return $this->http_request( 'GET', 'quota', [], [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
}
protected function init() {}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Elementor\Modules\CloudLibrary\Documents;
use Elementor\Core\Base\Document;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor preview library document.
*
* @since 3.29.0
*/
class Cloud_Template_Preview extends Document {
const TYPE = 'cloud-template-preview';
public static function get_properties() {
$properties = parent::get_properties();
$properties['admin_tab_group'] = '';
$properties['has_elements'] = true;
$properties['is_editable'] = true;
$properties['show_in_library'] = false;
$properties['show_on_admin_bar'] = false;
$properties['show_in_finder'] = false;
$properties['register_type'] = true;
$properties['support_conditions'] = false;
$properties['support_page_layout'] = false;
return $properties;
}
public static function get_type(): string {
return self::TYPE;
}
public static function get_title(): string {
return esc_html__( 'Cloud Template Preview', 'elementor' );
}
public static function get_plural_title(): string {
return esc_html__( 'Cloud Template Previews', 'elementor' );
}
public function get_content( $with_css = false ) {
return do_shortcode( parent::get_content( $with_css ) );
}
}

View File

@@ -0,0 +1,205 @@
<?php
namespace Elementor\Modules\CloudLibrary;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Common\Modules\Connect\Module as ConnectModule;
use Elementor\Core\Documents_Manager;
use Elementor\Core\Frontend\Render_Mode_Manager;
use Elementor\Modules\CloudLibrary\Connect\Cloud_Library;
use Elementor\Core\Common\Modules\Connect\Apps\Library;
use Elementor\Core\Experiments\Manager as ExperimentsManager;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
/**
* @var callable
*/
protected $print_preview_callback;
public function get_name(): string {
return 'cloud-library';
}
public function __construct() {
parent::__construct();
$this->register_experiments();
if ( Plugin::$instance->experiments->is_feature_active( $this->get_name() ) ) {
$this->register_app();
add_action( 'elementor/init', function () {
$this->set_cloud_library_settings();
}, 12 /** After the initiation of the connect cloud library */ );
add_filter( 'elementor/editor/localize_settings', function ( $settings ) {
return $this->localize_settings( $settings );
}, 11 /** After Elementor Core */ );
add_filter( 'elementor/render_mode/module', function( $module_name ) {
$render_mode_manager = \Elementor\Plugin::$instance->frontend->render_mode_manager;
if ( $render_mode_manager && $render_mode_manager->get_current() instanceof \Elementor\Modules\CloudLibrary\Render_Mode_Preview ) {
return 'cloud-library';
}
return $module_name;
}, 12);
if ( $this->is_screenshot_proxy_mode( $_GET ) ) { // phpcs:ignore -- Checking nonce inside the method.
echo $this->get_proxy_data( htmlspecialchars( $_GET['href'] ) ); // phpcs:ignore -- Nonce was checked on the above method
die;
}
}
}
public function get_proxy_data( $url ) {
$response = wp_safe_remote_get( utf8_decode( $url ) );
if ( is_wp_error( $response ) ) {
return '';
}
$content_type = wp_remote_retrieve_headers( $response )->offsetGet( 'content-type' );
header( 'content-type: ' . $content_type );
return wp_remote_retrieve_body( $response );
}
public function localize_settings( $settings ) {
if ( isset( $settings['i18n'] ) ) {
$settings['i18n']['folder'] = esc_html__( 'Folder', 'elementor' );
}
$settings['library']['doc_types'] = $this->get_document_types();
return $settings;
}
private function register_experiments() {
Plugin::$instance->experiments->add_feature( [
'name' => $this->get_name(),
'title' => esc_html__( 'Cloud Library', 'elementor' ),
'description' => esc_html__( 'Cloud Templates and Website Templates empowers you to save and manage design elements across all your projects. This feature is associated and connected to your Elementor Pro account and can be accessed from any website associated with your account.', 'elementor' ),
'release_status' => ExperimentsManager::RELEASE_STATUS_BETA,
'default' => ExperimentsManager::STATE_ACTIVE,
] );
}
private function register_app() {
add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
$connect_module->register_app( 'cloud-library', Cloud_Library::get_class_name() );
} );
add_action( 'elementor/frontend/render_mode/register', [ $this, 'register_render_mode' ] );
add_action( 'elementor/documents/register', function ( Documents_Manager $documents_manager ) {
$documents_manager->register_document_type(
Documents\Cloud_Template_Preview::TYPE,
Documents\Cloud_Template_Preview::get_class_full_name()
);
});
}
/**
* @param Render_Mode_Manager $manager
*
* @throws \Exception
*/
public function register_render_mode( Render_Mode_Manager $manager ) {
$manager->register_render_mode( Render_Mode_Preview::class );
}
private function set_cloud_library_settings() {
if ( ! Plugin::$instance->common ) {
return;
}
/** @var ConnectModule $connect */
$connect = Plugin::$instance->common->get_component( 'connect' );
/** @var Library $library */
$library = $connect->get_app( 'library' );
if ( ! $library ) {
return;
}
Plugin::$instance->app->set_settings( 'cloud-library', [
'library_connect_url' => esc_url( $library->get_admin_url( 'authorize', [
'utm_source' => 'template-library',
'utm_medium' => 'wp-dash',
'utm_campaign' => 'library-connect',
'utm_content' => 'cloud-library',
'source' => 'cloud-library',
] ) ),
'library_connect_title_copy' => esc_html__( 'Connect to your Elementor account', 'elementor' ),
'library_connect_sub_title_copy' => esc_html__( 'Then you can find all your templates in one convenient library.', 'elementor' ),
'library_connect_button_copy' => esc_html__( 'Connect', 'elementor' ),
] );
}
private function get_document_types() {
$document_types = Plugin::$instance->documents->get_document_types( [
'show_in_library' => true,
] );
$data = [];
foreach ( $document_types as $name => $document_type ) {
$data[ $name ] = $document_type::get_title();
}
return $data;
}
public function print_content() {
if ( ! $this->print_preview_callback ) {
$this->print_preview_callback = [ $this, 'print_thumbnail_preview_callback' ];
}
call_user_func( $this->print_preview_callback );
}
private function print_thumbnail_preview_callback() {
$doc = Plugin::$instance->documents->get_current();
// PHPCS - should not be escaped.
echo Plugin::$instance->frontend->get_builder_content_for_display( $doc->get_main_id(), true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
wp_delete_post( $doc->get_main_id(), true );
}
protected function is_screenshot_proxy_mode( array $query_params ) {
$is_proxy = isset( $query_params['screenshot_proxy'] );
if ( $is_proxy ) {
if ( ! wp_verify_nonce( $query_params['nonce'], 'screenshot-proxy' ) ) {
// WP >= 6.2-alpha
if ( class_exists( '\WpOrg\Requests\Exception\Http\Status403' ) ) {
throw new \WpOrg\Requests\Exception\Http\Status403();
} else {
throw new \Requests_Exception_HTTP_403();
}
}
if ( ! $query_params['href'] ) {
// WP >= 6.2-alpha
if ( class_exists( '\WpOrg\Requests\Exception\Http\Status400' ) ) {
throw new \WpOrg\Requests\Exception\Http\Status400();
} else {
throw new \Requests_Exception_HTTP_400();
}
}
}
return $is_proxy;
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace Elementor\Modules\CloudLibrary;
use Elementor\Core\Frontend\RenderModes\Render_Mode_Base;
use Elementor\Plugin;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Render_Mode_Preview extends Render_Mode_Base {
const ENQUEUE_SCRIPTS_PRIORITY = 1000;
const MODE = 'cloud-template-preview';
protected int $template_id;
public function __construct( $template_id ) {
$this->template_id = $template_id;
$this->document = $this->create_document();
parent::__construct( $this->document->get_main_id() );
}
public static function get_name() {
return self::MODE;
}
public function prepare_render() {
parent::prepare_render();
show_admin_bar( false );
add_filter( 'template_include', [ $this, 'filter_template' ] );
}
public function filter_template() {
return ELEMENTOR_PATH . 'modules/page-templates/templates/canvas.php';
}
public function enqueue_scripts() {
$suffix = ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min';
wp_enqueue_script(
'dom-to-image',
ELEMENTOR_ASSETS_URL . "/lib/dom-to-image/js/dom-to-image{$suffix}.js",
[],
'2.6.0',
true
);
wp_enqueue_script(
'html2canvas',
ELEMENTOR_ASSETS_URL . "/lib/html2canvas/js/html2canvas{$suffix}.js",
[],
'1.4.1',
true
);
wp_enqueue_script(
'cloud-library-screenshot',
ELEMENTOR_ASSETS_URL . "/js/cloud-library-screenshot{$suffix}.js",
[ 'dom-to-image', 'html2canvas' ],
ELEMENTOR_VERSION,
true
);
$config = [
'selector' => '.elementor-' . $this->document->get_main_id(),
'home_url' => home_url(),
'post_id' => $this->document->get_main_id(),
'template_id' => $this->template_id,
];
wp_add_inline_script( 'cloud-library-screenshot', 'var ElementorScreenshotConfig = ' . wp_json_encode( $config ) . ';' );
}
private function create_document() {
if ( ! Plugin::$instance->common ) {
Plugin::$instance->init_common();
}
$document = Plugin::$instance->templates_manager->get_source( 'cloud' )->create_document_for_preview( $this->template_id );
if ( is_wp_error( $document ) ) {
wp_die();
}
Plugin::$instance->documents->switch_to_document( $document );
return $document;
}
}