first commit

This commit is contained in:
2026-03-26 13:48:22 +01:00
commit 6af83e92ed
7795 changed files with 2766332 additions and 0 deletions

View File

@@ -0,0 +1,242 @@
<?php
namespace Elementor\Modules\CloudKitLibrary\Connect;
use Elementor\Core\Common\Modules\Connect\Apps\Library;
use Elementor\Core\Utils\Exceptions;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Cloud_Kits extends Library {
const THRESHOLD_UNLIMITED = -1;
const FAILED_TO_FETCH_QUOTA_KEY = 'failed-to-fetch-quota';
const FAILED_TO_UPLOAD_KIT = 'cloud-upload-failed';
const INSUFFICIENT_QUOTA_KEY = 'insufficient-quota';
const INSUFFICIENT_STORAGE_QUOTA = 'insufficient-storage-quota';
public function get_title() {
return esc_html__( 'Cloud Kits', 'elementor' );
}
protected function get_api_url(): string {
return 'https://cloud-library.prod.builder.elementor.red/api/v1/cloud-library';
}
/**
* @return array|\WP_Error
*/
public function get_all( $args = [] ) {
return $this->http_request( 'GET', 'kits', [], [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
}
/**
* @return array|\WP_Error
*/
public function get_quota() {
if ( ! $this->is_connected() ) {
return new \WP_Error( 'not_connected', esc_html__( 'Not connected', 'elementor' ) );
}
return $this->http_request( 'GET', 'quota/kits', [], [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
}
public function validate_quota( $quota ) {
if ( is_wp_error( $quota ) ) {
throw new \Error( static::FAILED_TO_FETCH_QUOTA_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
$is_unlimited = self::THRESHOLD_UNLIMITED === $quota['threshold'];
$has_quota = $quota['currentUsage'] < $quota['threshold'];
if ( ! $is_unlimited && ! $has_quota ) {
throw new \Error( static::INSUFFICIENT_QUOTA_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
}
public function validate_storage_quota( $intended_usage, $quota ) {
if ( is_wp_error( $quota ) ) {
throw new \Error( static::FAILED_TO_FETCH_QUOTA_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
if ( empty( $quota['storage']['currentUsage'] ) ) {
return;
}
$has_quota = $quota['storage']['currentUsage'] + $intended_usage < $quota['storage']['threshold'];
if ( ! $has_quota ) {
throw new \Error( static::INSUFFICIENT_STORAGE_QUOTA ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
}
public function check_eligibility() {
$quota = $this->get_quota();
if ( is_wp_error( $quota ) ) {
return [
'is_eligible' => false,
'subscription_id' => '',
];
}
return [
'is_eligible' => isset( $quota['threshold'] ) && 0 !== $quota['threshold'],
'subscription_id' => ! empty( $quota['subscriptionId'] ) ? $quota['subscriptionId'] : '',
];
}
public function create_kit( $title, $description, $content_file_data, $preview_file_data, array $includes, string $media_format = 'link', $file_size = 0 ) {
$quota = $this->get_quota();
$this->validate_quota( $quota );
$this->validate_storage_quota( $file_size, $quota );
$endpoint = 'kits';
$boundary = wp_generate_password( 24, false );
$headers = [
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
];
$body = $this->create_multipart_body(
[
'title' => $title,
'description' => $description,
'includes' => wp_json_encode( $includes ),
'mediaFormat' => $media_format,
],
[
'previewFile' => [
'filename' => 'preview.png',
'content' => $preview_file_data,
'content_type' => 'image/png',
],
],
$boundary
);
$payload = [
'headers' => $headers,
'body' => $body,
'timeout' => 120,
];
$response = $this->http_request( 'POST', $endpoint, $payload, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
if ( is_wp_error( $response ) || empty( $response['id'] ) ) {
throw new \Exception( static::FAILED_TO_UPLOAD_KIT, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
if ( empty( $response['uploadUrl'] ) ) {
$this->delete_kit( $response['id'] );
throw new \Exception( static::FAILED_TO_UPLOAD_KIT, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
$upload_success = $this->upload_content_file( $response['uploadUrl'], $content_file_data );
if ( ! $upload_success ) {
$this->delete_kit( $response['id'] );
throw new \Exception( static::FAILED_TO_UPLOAD_KIT, Exceptions::INTERNAL_SERVER_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
return $response;
}
public function upload_content_file( $upload_url, $content_file_data ) {
$upload_response = wp_remote_request( $upload_url, [
'method' => 'PUT',
'body' => $content_file_data,
'headers' => [
'Content-Type' => 'application/zip',
'Content-Length' => strlen( $content_file_data ),
],
'timeout' => 120,
] );
if ( is_wp_error( $upload_response ) ) {
return false;
}
$response_code = wp_remote_retrieve_response_code( $upload_response );
return $response_code >= 200 && $response_code < 300;
}
public function get_kit( array $args ) {
$args = array_merge_recursive( $args, [
'timeout' => 60, // just in case if zip is big
] );
return $this->http_request( 'GET', 'kits/' . $args['id'], $args, [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
}
public function delete_kit( int $id ) {
return $this->http_request( 'DELETE', 'kits/' . $id, [], [
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
] );
}
private function create_multipart_body( $fields, $files, $boundary ): string {
$eol = "\r\n";
$body = '';
foreach ( $fields as $name => $value ) {
$body .= "--{$boundary}{$eol}";
$body .= "Content-Disposition: form-data; name=\"{$name}\"{$eol}{$eol}";
$body .= "{$value}{$eol}";
}
foreach ( $files as $name => $file ) {
$filename = basename( $file['filename'] );
$content_type = $file['content_type'];
$content = $file['content'];
$body .= "--{$boundary}{$eol}";
$body .= "Content-Disposition: form-data; name=\"{$name}\"; filename=\"{$filename}\"{$eol}";
$body .= "Content-Type: {$content_type}{$eol}{$eol}";
$body .= $content . $eol;
}
$body .= "--{$boundary}--{$eol}";
return $body;
}
public function update_kit( $id, array $kit_data ) {
$endpoint = 'kits/' . $id;
$request = $this->http_request(
'PATCH',
$endpoint,
[
'body' => wp_json_encode( $kit_data ),
'headers' => [
'Content-Type' => 'application/json',
],
],
[
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
],
);
if ( is_wp_error( $request ) ) {
return false;
}
return true;
}
protected function init() {}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Elementor\Modules\CloudKitLibrary\Data;
use Elementor\Modules\CloudKitLibrary\Connect\Cloud_Kits;
use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary;
use Elementor\App\Modules\KitLibrary\Data\Base_Controller;
use Elementor\Core\Utils\Collection;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Controller extends Base_Controller {
public function get_name() {
return 'cloud-kits';
}
public function get_items( $request ) {
$data = $this->get_app()->get_all();
if ( is_wp_error( $data ) ) {
return [
'data' => [],
];
}
$kits = ( new Collection( $data ) )->map( function ( $kit ) {
return [
'id' => $kit['id'],
'title' => $kit['title'],
'thumbnail_url' => $kit['thumbnailUrl'],
'created_at' => $kit['createdAt'],
'updated_at' => $kit['updatedAt'],
'status' => isset( $kit['status'] ) ? $kit['status'] : 'active',
];
} );
return [
'data' => $kits->values(),
];
}
public function delete_item( $request ) {
return [
'data' => $this->get_app()->delete_kit( $request->get_param( 'id' ) ),
];
}
public function get_item( $request ) {
return [
'data' => $this->get_app()->get_kit( [ 'id' => $request->get_param( 'id' ) ] ),
];
}
public function register_endpoints() {
$this->index_endpoint->register_item_route( \WP_REST_Server::DELETABLE, [
'id' => [
'description' => 'Unique identifier for the object.',
'type' => 'integer',
'required' => true,
],
] );
$this->register_endpoint( new Endpoints\Eligibility( $this ) );
$this->register_endpoint( new Endpoints\Quota( $this ) );
}
public function get_permission_callback( $request ) {
return current_user_can( 'manage_options' );
}
protected function get_app(): Cloud_Kits {
return CloudKitLibrary::get_app();
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Elementor\Modules\CloudKitLibrary\Data\Endpoints;
use Elementor\Modules\CloudKitLibrary\Data\Controller;
use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary;
use Elementor\Data\V2\Base\Endpoint;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* @property Controller $controller
*/
class Eligibility extends Endpoint {
public function get_name() {
return 'eligibility';
}
public function get_format() {
return 'cloud-kits/eligibility';
}
public function get_items( $request ) {
return CloudKitLibrary::get_app()->check_eligibility();
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Elementor\Modules\CloudKitLibrary\Data\Endpoints;
use Elementor\Modules\CloudKitLibrary\Data\Controller;
use Elementor\Modules\CloudKitLibrary\Module as CloudKitLibrary;
use Elementor\Data\V2\Base\Endpoint;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* @property Controller $controller
*/
class Quota extends Endpoint {
public function get_name() {
return 'quota';
}
public function get_format() {
return 'cloud-kits/quota';
}
public function get_items( $request ) {
return CloudKitLibrary::get_app()->get_quota();
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace Elementor\Modules\CloudKitLibrary;
use Elementor\Modules\CloudKitLibrary\Data\Controller as Cloud_Kits_Controller;
use Elementor\Core\Utils\Exceptions;
use Elementor\Plugin;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Modules\CloudKitLibrary\Connect\Cloud_Kits;
use Elementor\Core\Common\Modules\Connect\Module as ConnectModule;
use Elementor\App\Modules\ImportExportCustomization\Module as ImportExportCustomization_Module;
use Elementor\App\Modules\KitLibrary\Connect\Kit_Library as Kit_Library_Api;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
public function get_name(): string {
return 'cloud-kit-library';
}
public function __construct() {
parent::__construct();
add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
$connect_module->register_app( 'cloud-kits', Cloud_Kits::get_class_name() );
} );
add_filter( 'elementor/export/kit/export-result', [ $this, 'handle_export_kit_result' ], 10, 6 );
add_filter( 'elementor/import/kit/result/cloud', [ $this, 'handle_import_kit_from_cloud' ], 10, 1 );
add_filter( 'elementor/import/kit_thumbnail', [ $this, 'handle_import_kit_thumbnail' ], 10, 3 );
add_action( 'elementor/kit_library/registered', function () {
Plugin::$instance->data_manager_v2->register_controller( new Cloud_Kits_Controller() );
} );
}
public function handle_import_kit_thumbnail( $thumbnail, $kit_id, $referrer ) {
if ( ImportExportCustomization_Module::REFERRER_KIT_LIBRARY === $referrer ) {
if ( empty( $kit_id ) ) {
return '';
}
$api = new Kit_Library_Api();
$kit = $api->get_by_id( $kit_id );
if ( is_wp_error( $kit ) ) {
return '';
}
return $kit->thumbnail;
}
if ( ImportExportCustomization_Module::REFERRER_CLOUD === $referrer ) {
if ( empty( $kit_id ) ) {
return '';
}
$kit = self::get_app()->get_kit( [ 'id' => $kit_id ] );
if ( is_wp_error( $kit ) ) {
return '';
}
return $kit['thumbnailUrl'] ?? '';
}
return $thumbnail;
}
public function handle_export_kit_result( $result, $source, $export, $settings, $file, $file_size ) {
if ( ImportExportCustomization_Module::EXPORT_SOURCE_CLOUD !== $source ) {
return $result;
}
unset( $result['file'] );
$raw_screen_shot = base64_decode( substr( $settings['screenShotBlob'], strlen( 'data:image/png;base64,' ) ) );
$title = $export['manifest']['title'];
$description = $export['manifest']['description'];
$kit = self::get_app()->create_kit(
$title,
$description,
$file,
$raw_screen_shot,
$settings['include'],
$settings['customization']['content']['mediaFormat'] ?? 'link',
$file_size,
);
if ( is_wp_error( $kit ) ) {
return $kit;
}
$result['kit'] = $kit;
return $result;
}
public function handle_import_kit_from_cloud( $args ) {
$kit = self::get_app()->get_kit( [
'id' => $args['kit_id'],
] );
if ( is_wp_error( $kit ) ) {
throw new \Error( ImportExportCustomization_Module::CLOUD_KIT_LIBRARY_ERROR_LOADING_RESOURCE ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
if ( empty( $kit['downloadUrl'] ) ) {
throw new \Error( ImportExportCustomization_Module::KIT_LIBRARY_ERROR_KEY ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
$data = [
'file_name' => self::get_remote_kit_zip( $kit['downloadUrl'] ),
'referrer' => ImportExportCustomization_Module::REFERRER_CLOUD,
'file_url' => $kit['downloadUrl'],
'kit' => $kit,
];
if ( ! empty( $kit['mediaDownloadUrl'] ) ) {
$media_zip = self::get_remote_kit_zip( $kit['mediaDownloadUrl'], 'media.zip' );
$data['media_file_name'] = $media_zip;
}
return $data;
}
public static function get_remote_kit_zip( $url, $file_name = 'kit.zip' ) {
$remote_zip_request = wp_safe_remote_get( $url, [
'timeout' => 300,
] );
if ( is_wp_error( $remote_zip_request ) ) {
Plugin::$instance->logger->get_logger()->error( $remote_zip_request->get_error_message() );
throw new \Error( ImportExportCustomization_Module::CLOUD_KIT_LIBRARY_ERROR_LOADING_RESOURCE ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
if ( 200 !== $remote_zip_request['response']['code'] ) {
Plugin::$instance->logger->get_logger()->error( $remote_zip_request['response']['message'] );
throw new \Error( ImportExportCustomization_Module::CLOUD_KIT_LIBRARY_ERROR_LOADING_RESOURCE ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
return Plugin::$instance->uploads_manager->create_temp_file( $remote_zip_request['body'], $file_name );
}
public static function get_app(): Cloud_Kits {
$cloud_kits_app = Plugin::$instance->common->get_component( 'connect' )->get_app( 'cloud-kits' );
if ( ! $cloud_kits_app ) {
$error_message = esc_html__( 'Cloud-Kits is not instantiated.', 'elementor' );
throw new \Exception( $error_message, Exceptions::FORBIDDEN ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
}
return $cloud_kits_app;
}
}