--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/cloudinary.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/cloudinary.php
@@ -3,7 +3,7 @@
* Plugin Name: Cloudinary
* Plugin URI: https://cloudinary.com/documentation/wordpress_integration
* Description: With the Cloudinary plugin, you can upload and manage your media assets in the cloud, then deliver them to your users through a fast content delivery network, improving your website’s loading speed and overall user experience. Apply multiple transformations and take advantage of a full digital asset management solution without leaving WordPress.
- * Version: 3.3.0
+ * Version: 3.3.1
* Author: Cloudinary Ltd., XWP
* Author URI: https://cloudinary.com/
* License: GPLv2+
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/js/inline-loader.asset.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/js/inline-loader.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => 'e82dd225edea94edf605');
+<?php return array('dependencies' => array(), 'version' => '3a26d9a7f786a21ffdd9');
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/js/lazy-load.asset.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/js/lazy-load.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => 'f89102869f47abf4e354');
+<?php return array('dependencies' => array(), 'version' => '2b871bf412f8d66d43d2');
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-admin.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-admin.php
@@ -108,9 +108,12 @@
public function rest_endpoints( $endpoints ) {
$endpoints['dismiss_notice'] = array(
- 'method' => WP_REST_Server::CREATABLE,
- 'callback' => array( $this, 'rest_dismiss_notice' ),
- 'args' => array(),
+ 'method' => WP_REST_Server::CREATABLE,
+ 'callback' => array( $this, 'rest_dismiss_notice' ),
+ 'args' => array(),
+ 'permission_callback' => function () {
+ return Utils::user_can( 'manage_settings' );
+ },
);
$endpoints['save_settings'] = array(
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-assets.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-assets.php
@@ -494,12 +494,16 @@
}
}
}
-
// Get the disabled items.
foreach ( $this->asset_parents as $url => $parent ) {
if ( isset( $this->active_parents[ $url ] ) ) {
continue;
}
+
+ if ( ! $this->is_post_cloudinary_asset( $parent->ID ) ) {
+ continue;
+ }
+
$this->purge_parent( $parent->ID );
// Remove parent.
wp_delete_post( $parent->ID );
@@ -507,6 +511,21 @@
}
/**
+ * Check if a post is a Cloudinary asset.
+ *
+ * @param int $post_id The post ID to check.
+ *
+ * @return bool
+ */
+ public function is_post_cloudinary_asset( $post_id ) {
+ if ( get_post_type( $post_id ) === self::POST_TYPE_SLUG ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
* Activate a parent asset path.
*
* @param string $url The path to activate.
@@ -569,6 +588,10 @@
* @param callable $callback The callback function to execute on each post.
*/
private function process_parent_assets( $parent_id, $callback ) {
+ if ( ! $this->is_post_cloudinary_asset( $parent_id ) ) {
+ return;
+ }
+
$query_args = array(
'post_type' => self::POST_TYPE_SLUG,
'posts_per_page' => 100,
@@ -626,6 +649,10 @@
$this->process_parent_assets(
$parent_id,
function ( $post_id ) {
+ if ( ! $this->is_post_cloudinary_asset( $post_id ) ) {
+ return;
+ }
+
wp_delete_post( $post_id );
}
);
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-cache.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-cache.php
@@ -352,9 +352,11 @@
'args' => array(),
);
$endpoints['upload_cache'] = array(
- 'method' => WP_REST_Server::CREATABLE,
- 'callback' => array( $this, 'rest_upload_cache' ),
- 'args' => array(),
+ 'method' => WP_REST_Server::CREATABLE,
+ 'callback' => array( $this, 'rest_upload_cache' ),
+ 'permission_callback' => array( 'CloudinaryREST_API', 'validate_request' ),
+ 'args' => array(),
+
);
return $endpoints;
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-connect.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-connect.php
@@ -132,9 +132,10 @@
'permission_callback' => array( 'CloudinaryREST_API', 'rest_can_connect' ),
);
$endpoints['test_rest_api'] = array(
- 'method' => WP_REST_Server::READABLE,
- 'callback' => array( $this, 'rest_test_rest_api_connectivity' ),
- 'args' => array(),
+ 'method' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'rest_test_rest_api_connectivity' ),
+ 'args' => array(),
+ 'permission_callback' => array( 'CloudinaryREST_API', 'allow_public_health_check' ),
);
return $endpoints;
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-cron.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-cron.php
@@ -187,12 +187,12 @@
$endpoints['cron_watch'] = array(
'method' => WP_REST_Server::READABLE,
'callback' => array( $this, 'daemon_watcher' ),
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( 'CloudinaryREST_API', 'validate_request' ),
);
$endpoints['cron_process'] = array(
'method' => WP_REST_Server::READABLE,
'callback' => array( $this, 'run_queue' ),
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( 'CloudinaryREST_API', 'validate_request' ),
);
return $endpoints;
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php
@@ -2285,8 +2285,7 @@
*/
public function down_sync_asset() {
$nonce = Utils::get_sanitized_text( 'nonce', INPUT_POST );
- if ( wp_verify_nonce( $nonce, 'wp_rest' ) ) {
-
+ if ( is_user_logged_in() && wp_verify_nonce( $nonce, 'wp_rest' ) && current_user_can( 'upload_files' ) ) {
$asset = $this->get_asset_payload();
// Set a base array for pulling an asset if needed.
$base_return = array(
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-plugin.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-plugin.php
@@ -14,6 +14,7 @@
use CloudinaryDeliveryLazy_Load;
use CloudinaryDeliveryResponsive_Breakpoints;
use CloudinaryAssets as CLD_Assets;
+use CloudinaryIntegrationsElementor;
use CloudinaryIntegrationsWPML;
use CloudinaryMediaGallery;
use CloudinarySyncStorage;
@@ -31,7 +32,7 @@
*
* @since 0.1
*
- * @var Admin|CLD_Assets|Connect|Dashboard|Deactivation|Delivery|Extensions|Gallery|Lazy_Load|Media|Meta_Box|Relate|Report|Responsive_Breakpoints|REST_API|State|Storage|SVG|Sync|URL[]|WPML|null
+ * @var Admin|CLD_Assets|Connect|Dashboard|Deactivation|Delivery|Extensions|Gallery|Lazy_Load|Media|Meta_Box|Relate|Report|Responsive_Breakpoints|REST_API|State|Storage|SVG|Sync|URL[]|WPML|Elementor|null
*/
public $components;
/**
@@ -136,6 +137,7 @@
$this->components['metabox'] = new Meta_Box( $this );
$this->components['url'] = new URL( $this );
$this->components['wpml'] = new WPML( $this );
+ $this->components['elementor'] = new Elementor( $this );
$this->components['special_offer'] = new Special_Offer( $this );
}
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-rest-api.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-rest-api.php
@@ -12,6 +12,11 @@
*/
class REST_API {
+ /**
+ * Base path for the REST API endpoints.
+ *
+ * @var string
+ */
const BASE = 'cloudinary/v1';
/**
@@ -22,6 +27,13 @@
public $endpoints;
/**
+ * The nonce key used for WordPress REST API authentication.
+ *
+ * @var string
+ */
+ const NONCE_KEY = 'wp_rest';
+
+ /**
* REST_API constructor.
*
* @param Plugin $plugin Instance of the global Plugin.
@@ -39,13 +51,14 @@
'method' => WP_REST_Server::READABLE,
'callback' => __return_empty_array(),
'args' => array(),
- 'permission_callback' => '__return_true',
+ 'permission_callback' => array( __CLASS__, 'validate_request' ),
);
$this->endpoints = apply_filters( 'cloudinary_api_rest_endpoints', array() );
foreach ( $this->endpoints as $route => $endpoint ) {
$endpoint = wp_parse_args( $endpoint, $defaults );
+
register_rest_route(
static::BASE,
$route,
@@ -81,7 +94,7 @@
$url = Utils::rest_url( static::BASE . '/' . $endpoint );
// Setup a call for a background sync.
- $params['nonce'] = wp_create_nonce( 'wp_rest' );
+ $params['nonce'] = wp_create_nonce( static::NONCE_KEY );
$args = array(
'timeout' => 0.1,
'blocking' => false,
@@ -115,4 +128,27 @@
// Send request.
wp_remote_request( $url, $args );
}
+
+ /**
+ * Validation for request.
+ *
+ * @param WP_REST_Request $request The original request.
+ *
+ * @return bool
+ */
+ public static function validate_request( $request ) {
+ return wp_verify_nonce( $request->get_header( 'x_wp_nonce' ), self::NONCE_KEY );
+ }
+
+ /**
+ * Permission callback for public health check endpoints.
+ *
+ * Intentionally allows unauthenticated access for REST API connectivity testing.
+ * This endpoint is read-only and returns no sensitive data.
+ *
+ * @return bool Always returns true to allow public access.
+ */
+ public static function allow_public_health_check() {
+ return true;
+ }
}
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/integrations/class-elementor.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/integrations/class-elementor.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * Elementor integration class for the Cloudinary plugin.
+ *
+ * @package Cloudinary
+ */
+
+namespace CloudinaryIntegrations;
+
+use ElementorCoreFilesCSSPost;
+use ElementorElement_Base;
+use ElementorPlugin;
+
+/**
+ * Class Elementor
+ */
+class Elementor extends Integrations {
+
+ /**
+ * List of Elementor background image settings keys.
+ *
+ * @var array
+ */
+ const ELEMENTOR_BACKGROUND_IMAGES = array(
+ 'background_image',
+ 'background_hover_image',
+ 'background_image_tablet',
+ 'background_hover_image_tablet',
+ 'background_image_mobile',
+ 'background_hover_image_mobile',
+ 'background_overlay_image',
+ 'background_overlay_hover_image',
+ 'background_overlay_image_tablet',
+ 'background_overlay_hover_image_tablet',
+ 'background_overlay_image_mobile',
+ 'background_overlay_hover_image_mobile',
+ );
+
+ /**
+ * Check if the integration can be enabled.
+ *
+ * @return bool
+ */
+ public function can_enable() {
+ return class_exists( 'ElementorPlugin' );
+ }
+
+ /**
+ * Register hooks for the integration.
+ *
+ * @return void
+ */
+ public function register_hooks() {
+ add_action( 'elementor/element/parse_css', array( $this, 'replace_background_images_in_css' ), 10, 2 );
+ add_action( 'cloudinary_flush_cache', array( $this, 'clear_elementor_css_cache' ) );
+ }
+
+ /**
+ * Replace all background images URLs with Cloudinary URLs, within the generated Elementor CSS file.
+ *
+ * @param Post $post_css The post CSS object.
+ * @param Element_Base $element The Elementor element.
+ * @return void
+ */
+ public function replace_background_images_in_css( $post_css, $element ) {
+ if ( ! method_exists( $element, 'get_settings_for_display' ) ) {
+ return;
+ }
+
+ $settings = $element->get_settings_for_display();
+ $media = $this->plugin->get_component( 'media' );
+ $delivery = $this->plugin->get_component( 'delivery' );
+
+ if ( ! $media || ! $delivery ) {
+ return;
+ }
+
+ foreach ( self::ELEMENTOR_BACKGROUND_IMAGES as $background_key ) {
+ $background = null;
+ $is_container = false;
+
+ if ( isset( $settings[ $background_key ] ) ) {
+ // Elementor section/column elements store background settings without a leading underscore.
+ $background = $settings[ $background_key ];
+ $is_container = true;
+ } elseif ( isset( $settings[ '_' . $background_key ] ) ) {
+ // Elementor basic elements (e.g. heading) store background settings with a leading underscore.
+ $background = $settings[ '_' . $background_key ];
+ }
+
+ // If this specific background setting is not set, we can skip it and check for the next setting.
+ if ( empty( $background ) || empty( $background['id'] ) ) {
+ continue;
+ }
+
+ $media_id = $background['id'];
+ $media_size = isset( $background['size'] ) ? $background['size'] : array();
+
+ // Skip if the media is not deliverable via Cloudinary.
+ if ( ! $delivery->is_deliverable( $media_id ) ) {
+ continue;
+ }
+
+ // Generate the Cloudinary URL.
+ $cloudinary_url = $media->cloudinary_url( $media_id, $media_size );
+
+ // If URL generation failed, we should leave the original URL within the CSS.
+ if ( empty( $cloudinary_url ) ) {
+ continue;
+ }
+
+ $unique_selector = $this->find_unique_selector( $post_css, $element );
+ // If we can't find a unique selector via Elementor's internal API, we can't do any replacement.
+ if ( null === $unique_selector ) {
+ return;
+ }
+
+ // Build the CSS selector and rule for background image replacement.
+ $is_hover = ( strpos( $background_key, 'hover' ) !== false );
+ $is_overlay = ( strpos( $background_key, 'overlay' ) !== false );
+ $css_selector = $this->build_background_image_css_selector( $unique_selector, $is_container, $is_hover, $is_overlay );
+ $css_rule = array( 'background-image' => "url('$cloudinary_url')" );
+
+ // Retrieve the specific media query rule for non-desktop devices based on the setting key.
+ $media_query = null;
+ if ( strpos( $background_key, 'tablet' ) !== false ) {
+ $media_query = array( 'max' => 'tablet' );
+ } elseif ( strpos( $background_key, 'mobile' ) !== false ) {
+ $media_query = array( 'max' => 'mobile' );
+ }
+
+ $success = $this->override_elementor_css_rule( $post_css, $css_selector, $css_rule, $media_query );
+ if ( ! $success ) {
+ // If we couldn't override the CSS rule, likely due to Elementor internal API changes, we should stop further processing.
+ return;
+ }
+ }
+ }
+
+ /**
+ * Clear Elementor CSS cache.
+ * This is called when Cloudinary cache is flushed, so that any change in media URLs is reflected in Elementor CSS files.
+ *
+ * @return void
+ */
+ public function clear_elementor_css_cache() {
+ if ( class_exists( 'ElementorPlugin' ) ) {
+ $elementor = Plugin::instance();
+ $elementor->files_manager->clear_cache();
+ }
+ }
+
+ /**
+ * Find the unique selector for an Elementor element.
+ * Double-checks if the method exists before calling it, to ensure compatibility with different Elementor versions.
+ *
+ * @param Post $post_css The post CSS object.
+ * @param Element_Base $element The Elementor element.
+ *
+ * @return string|null
+ */
+ private function find_unique_selector( $post_css, $element ) {
+ if ( ! method_exists( $element, 'get_unique_selector' ) ) {
+ return null;
+ }
+
+ return $post_css->get_element_unique_selector( $element );
+ }
+
+ /**
+ * Override the Elementor CSS rule for a specific selector.
+ * Double-checks if the method exists before calling it, to ensure compatibility with different Elementor versions.
+ *
+ * @param Post $post_css The post CSS object.
+ * @param string $css_selector The CSS selector.
+ * @param array $css_rule The CSS rule to apply.
+ * @param array|null $media_query The media query conditions. Null for default (desktop) styles.
+ *
+ * @return bool True if the rule could be overridden, false if the internal Elementor methods aren't available.
+ */
+ private function override_elementor_css_rule( $post_css, $css_selector, $css_rule, $media_query ) {
+ if ( ! method_exists( $post_css, 'get_stylesheet' ) ) {
+ return false;
+ }
+
+ $stylesheet = $post_css->get_stylesheet();
+ if ( ! method_exists( $stylesheet, 'add_rules' ) ) {
+ return false;
+ }
+
+ $stylesheet->add_rules( $css_selector, $css_rule, $media_query );
+ return true;
+ }
+
+ /**
+ * Build the full CSS selector for background image replacement.
+ * We try to match the exact Elementor formatting and rules, so that our CSS overrides the previous rules,
+ * instead of adding new rules within the CSS which may not apply for specific edge cases (e.g. specific child elements).
+ *
+ * @param string $unique_selector The unique selector for the element.
+ * @param bool $is_container Whether the element is a container (section/column).
+ * @param bool $is_hover Whether the background is for hover state.
+ * @param bool $is_overlay Whether the background is for an overlay.
+ *
+ * @return string
+ */
+ private function build_background_image_css_selector( $unique_selector, $is_container, $is_hover, $is_overlay ) {
+ if ( $is_overlay ) {
+ // Overlay backgrounds need to target multiple pseudo-elements and child elements.
+ $overlay_selector = sprintf(
+ '%1$s%2$s::before,
+ %1$s%2$s > .elementor-background-video-container::before,
+ %1$s%2$s > .e-con-inner > .elementor-background-video-container::before,
+ %1$s > .elementor-background-slideshow%2$s::before,
+ %1$s > .e-con-inner > .elementor-background-slideshow%2$s::before',
+ $unique_selector,
+ $is_hover ? ':hover' : ''
+ );
+
+ // For non-hover overlays, we need to also target motion effects layers.
+ if ( ! $is_hover ) {
+ $overlay_selector = sprintf(
+ '%1$s,
+ %2$s > .elementor-motion-effects-container > .elementor-motion-effects-layer::before',
+ $overlay_selector,
+ $unique_selector
+ );
+ }
+
+ // Replace any newline and extra spaces to match the exact Elementor formatting.
+ return preg_replace( '/s+/', ' ', $overlay_selector );
+ }
+ // For hover backgrounds, we simply append :hover to the unique selector.
+ if ( $is_hover ) {
+ return $unique_selector . ':hover';
+ }
+
+ // For non-container elements, we can return the unique selector as is.
+ if ( ! $is_container ) {
+ return $unique_selector;
+ }
+
+ // For container elements, we need to target both the element itself and its motion effects layers.
+ return sprintf(
+ '%1$s:not(.elementor-motion-effects-element-type-background), %1$s > .elementor-motion-effects-container > .elementor-motion-effects-layer',
+ $unique_selector
+ );
+ }
+}
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php
@@ -124,14 +124,16 @@
);
$endpoints['queue'] = array(
- 'method' => WP_REST_Server::CREATABLE,
- 'callback' => array( $this, 'process_queue' ),
- 'args' => array(),
+ 'method' => WP_REST_Server::CREATABLE,
+ 'callback' => array( $this, 'process_queue' ),
+ 'args' => array(),
+ 'permission_callback' => array( 'CloudinaryREST_API', 'validate_request' ),
);
$endpoints['stats'] = array(
- 'method' => WP_REST_Server::READABLE,
- 'callback' => array( $this->queue, 'get_total_synced_media' ),
- 'args' => array(),
+ 'method' => WP_REST_Server::READABLE,
+ 'callback' => array( $this->queue, 'get_total_synced_media' ),
+ 'args' => array(),
+ 'permission_callback' => array( 'CloudinaryREST_API', 'validate_request' ),
);
return $endpoints;
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/ui/class-state.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/ui/class-state.php
@@ -98,22 +98,13 @@
'method' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'set_state' ),
'args' => array(),
- 'permission_callback' => array( $this, 'validate_request' ),
+ 'permission_callback' => array( 'CloudinaryREST_API', 'validate_request' ),
);
return $endpoints;
}
- /**
- * Validation for request.
- *
- * @param WP_REST_Request $request The original request.
- *
- * @return bool
- */
- public function validate_request( $request ) {
- return wp_verify_nonce( $request->get_header( 'x_wp_nonce' ), 'wp_rest' );
- }
+
/**
* Set the UI state.
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/ui/component/class-crops.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/ui/component/class-crops.php
@@ -24,9 +24,9 @@
* @var string
*/
protected $demo_files = array(
- 'leather_bag.jpg',
- 'lady.jpg',
- 'horses.jpg',
+ 'docs/addons/objectdetection/dirt-road-1851258_1280.jpg',
+ 'docs/model-993911_640.jpg',
+ 'docs/shoppable_bag.png',
);
/**
@@ -96,7 +96,6 @@
* @return array
*/
protected function input( $struct ) {
-
$mode = $this->setting->get_param( 'mode', 'demos' );
$wrapper = $this->get_part( 'div' );
$wrapper['attributes']['class'][] = 'cld-size-items';
@@ -112,89 +111,130 @@
}
$sizes = Utils::get_registered_sizes();
- $selector = $this->make_selector();
- $wrapper['children']['control-selector'] = $selector;
+ // Create size selector (tabs).
+ $size_selector = $this->make_size_selector( $sizes );
+ $wrapper['children']['size-selector'] = $size_selector;
+
+ // Get demo files.
+ $mode = $this->setting->get_param( 'mode', 'demos' );
+
+ /**
+ * Filter the demo files.
+ *
+ * @hook cloudinary_registered_sizes
+ * @since 3.1.3
+ *
+ * @param $demo_files {array} array of demo files.
+ *
+ * @return {array}
+ */
+ $examples = apply_filters( 'cloudinary_demo_crop_files', $this->demo_files );
+
+ if ( 'full' === $mode ) {
+ $public_id = $this->setting->get_root_setting()->get_param( 'preview_id' );
+ if ( ! empty( $public_id ) ) {
+ $examples = array( $public_id );
+ }
+ }
+
+ // Create content area for each size.
foreach ( $sizes as $size => $details ) {
if ( empty( $details['crop'] ) ) {
continue;
}
- $row = $this->get_part( 'div' );
- $row['attributes']['class'][] = 'cld-size-items-item';
- $row['attributes']['class'][] = 'crop-preview';
- $row['content'] = $size;
-
- $image = $this->get_part( 'img' );
- $image['content'] = $size;
- $size_array = array();
+
+ $size_content = $this->get_part( 'div' );
+ $size_content['attributes']['class'][] = 'cld-size-content';
+ $size_content['attributes']['data-size'] = $size;
+ $size_content['render'] = true;
+
+ $size_array = array();
if ( ! empty( $details['width'] ) ) {
- $size_array[] = 'w_' . $details['width'];
- $image['attributes']['width'] = $details['width'];
+ $size_array[] = 'w_' . $details['width'];
}
if ( ! empty( $details['height'] ) ) {
- $size_array[] = 'h_' . $details['height'];
- $image['attributes']['height'] = $details['height'];
+ $size_array[] = 'h_' . $details['height'];
+ }
+ $size_dimensions = implode( ',', $size_array );
+
+ // Create image previews container.
+ $images_container = $this->get_part( 'div' );
+ $images_container['attributes']['class'][] = 'cld-size-images';
+ $images_container['render'] = true;
+
+ foreach ( $examples as $index => $file ) {
+ $image_wrapper = $this->get_part( 'div' );
+ $image_wrapper['attributes']['class'][] = 'cld-size-image-wrapper';
+ $image_wrapper['render'] = true;
+
+ $image = $this->get_part( 'img' );
+ $image['attributes']['data-size'] = $size_dimensions;
+ $image['attributes']['data-file'] = $file;
+ $image['render'] = true;
+ if ( ! empty( $details['width'] ) ) {
+ $image['attributes']['width'] = $details['width'];
+ }
+ if ( ! empty( $details['height'] ) ) {
+ $image['attributes']['height'] = $details['height'];
+ }
+
+ $image_wrapper['children']['image'] = $image;
+ $images_container['children'][ 'image-' . $index ] = $image_wrapper;
}
- $image['attributes']['data-size'] = implode( ',', $size_array );
- $size_key = $details['width'] . 'x' . $details['height'];
+
+ // Create single input field with disable checkbox.
+ $size_key = $details['width'] . 'x' . $details['height'];
if ( empty( $value[ $size_key ] ) ) {
$value[ $size_key ] = '';
}
- $row['children']['size'] = $image;
- $row['children']['input'] = $this->make_input( $this->get_name() . '[' . $size_key . ']', $value[ $size_key ] );
+
+ $input_wrapper = $this->make_input( $this->get_name() . '[' . $size_key . ']', $value[ $size_key ] );
+
// Set the placeholder.
$placeholder = 'c_fill,g_auto';
-
if ( 'thumbnail' === $size ) {
$placeholder = 'c_thumb,g_auto';
}
- $row['children']['input']['children']['input']['attributes']['placeholder'] = $placeholder;
+ $input_wrapper['children']['input']['attributes']['placeholder'] = $placeholder;
- $wrapper['children'][ $size ] = $row;
+ $size_content['children']['input'] = $input_wrapper;
+ $size_content['children']['images'] = $images_container;
+ $wrapper['children'][ 'content-' . $size ] = $size_content;
}
return $wrapper;
}
/**
- * Make an image selector.
+ * Make a size selector (tabs).
+ *
+ * @param array $sizes The registered sizes.
+ * @return array
*/
- protected function make_selector() {
+ protected function make_size_selector( $sizes ) {
$selector = $this->get_part( 'div' );
- $selector['attributes']['class'][] = 'cld-image-selector';
- $mode = $this->setting->get_param( 'mode', 'demos' );
+ $selector['attributes']['class'][] = 'cld-size-selector';
+ $selector['render'] = true;
- /**
- * Filter the demo files.
- *
- * @hook cloudinary_registered_sizes
- * @since 3.1.3
- *
- * @param $demo_files {array} array of demo files.
- *
- * @return {array}
- */
- $examples = apply_filters( 'cloudinary_demo_crop_files', $this->demo_files );
- if ( 'full' === $mode ) {
- $public_id = $this->setting->get_root_setting()->get_param( 'preview_id' );
- if ( ! empty( $public_id ) ) {
- $examples = array(
- $public_id,
- );
+ $index = 0;
+ foreach ( $sizes as $size => $details ) {
+ if ( empty( $details['crop'] ) ) {
+ continue;
}
- }
- foreach ( $examples as $index => $file ) {
- $name = pathinfo( $file, PATHINFO_FILENAME );
- $item = $this->get_part( 'span' );
- $item['attributes']['data-image'] = $file;
+
+ $item = $this->get_part( 'span' );
+ $item['attributes']['data-size'] = $size;
+ $item['attributes']['class'][] = 'cld-size-selector-item';
+ $item['render'] = true;
+
if ( 0 === $index ) {
$item['attributes']['data-selected'] = true;
-
}
- $item['attributes']['class'][] = 'cld-image-selector-item';
- $item['content'] = $name;
- $selector['children'][ 'image-' . $index ] = $item;
+ $item['content'] = $size;
+ $selector['children'][ 'size-' . $size ] = $item;
+ ++$index;
}
return $selector;
@@ -215,9 +255,11 @@
'crop-size-inputs',
);
- $label = $this->get_part( 'label' );
- $label['attributes']['for'] = $name;
- $label['content'] = __( 'Disable', 'cloudinary' );
+ // Disable toggle control.
+ $control = $this->get_part( 'label' );
+ $control['attributes']['class'][] = 'cld-input-on-off-control';
+ $control['attributes']['class'][] = 'medium';
+ $control['attributes']['for'] = $name;
$check = $this->get_part( 'input' );
$check['attributes']['type'] = 'checkbox';
@@ -230,15 +272,34 @@
$check['attributes']['checked'] = 'checked';
}
+ $slider = $this->get_part( 'span' );
+ $slider['attributes']['class'][] = 'cld-input-on-off-control-slider';
+ $slider['render'] = true;
+
+ $control['children']['input'] = $check;
+ $control['children']['slider'] = $slider;
+
+ $label = $this->get_part( 'span' );
+ $label['attributes']['class'] = 'cld-input-on-off-control-label';
+ $label['content'] = __( 'Disable', 'cloudinary' );
+
$input = $this->get_part( 'input' );
$input['attributes']['type'] = 'text';
$input['attributes']['name'] = $name;
$input['attributes']['value'] = '--' !== $value ? $value : '';
$input['attributes']['class'][] = 'regular-text';
- $wrapper['children']['input'] = $input;
- $wrapper['children']['label'] = $label;
- $wrapper['children']['check'] = $check;
+ $clear_button = $this->get_part( 'button' );
+ $clear_button['attributes']['type'] = 'button';
+ $clear_button['attributes']['class'][] = 'button';
+ $clear_button['attributes']['class'][] = 'clear-crop-input';
+ $clear_button['attributes']['title'] = __( 'Reset input', 'cloudinary' );
+ $clear_button['content'] = Utils::get_inline_svg( 'css/images/undo.svg', false ) . '<span>' . __( 'Reset', 'cloudinary' ) . '</span>';
+
+ $wrapper['children']['input'] = $input;
+ $wrapper['children']['button'] = $clear_button;
+ $wrapper['children']['control'] = $control;
+ $wrapper['children']['label'] = $label;
return $wrapper;
}
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/ui/component/class-notice.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/ui/component/class-notice.php
@@ -117,7 +117,7 @@
if ( $this->setting->get_option_parent()->has_param( 'dismissible_notice' ) && ! $this->setting->get_option_parent()->has_param( 'notice_scripts' ) ) {
$args = array(
'url' => Utils::rest_url( REST_API::BASE . '/dismiss_notice' ),
- 'nonce' => wp_create_nonce( 'wp_rest' ),
+ 'nonce' => wp_create_nonce( REST_API::NONCE_KEY ),
);
wp_add_inline_script( 'cloudinary', 'var CLDIS = ' . wp_json_encode( $args ), 'before' );
$this->setting->get_option_parent()->set_param( 'notice_scripts', true ); // Prevent repeated rendering.
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/ui/component/class-progress-sync.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/ui/component/class-progress-sync.php
@@ -34,8 +34,9 @@
protected function wrap( $struct ) {
$struct = parent::wrap( $struct );
if ( true === $this->setting->get_param( 'poll' ) ) {
- $struct['attributes']['data-url'] = Utils::rest_url( REST_API::BASE . '/stats' );
- $struct['attributes']['data-poll'] = true;
+ $struct['attributes']['data-url'] = Utils::rest_url( REST_API::BASE . '/stats' );
+ $struct['attributes']['data-poll'] = true;
+ $struct['attributes']['data-nonce'] = wp_create_nonce( REST_API::NONCE_KEY );
}
return $struct;
--- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/ui-definitions/settings-image.php
+++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/ui-definitions/settings-image.php
@@ -208,25 +208,6 @@
'description' => __( 'Enable SVG support.', 'cloudinary' ),
'default' => 'off',
),
- array(
- 'type' => 'crops',
- 'slug' => 'crop_sizes',
- 'title' => __( 'Crop and Gravity control (beta)', 'cloudinary' ),
- 'enabled' => static function () {
- /**
- * Enable the Crop and Gravity control settings.
- *
- * @hook cloudinary_enable_crop_and_gravity_control
- * @since 3.1.3
- * @default {false}
- *
- * @param $enabeld {bool} Is the Crop and Gravity control enabled?
- *
- * @retrun {bool}
- */
- return apply_filters( 'cloudinary_enable_crop_and_gravity_control', true );
- },
- ),
),
),
array(
@@ -252,6 +233,30 @@
),
),
array(
+ 'type' => 'crops',
+ 'slug' => 'crop_sizes',
+ 'title' => __( 'Crop and Gravity control (beta)', 'cloudinary' ),
+ 'attributes' => array(
+ 'wrap' => array(
+ 'style' => 'max-width: 1000px;',
+ ),
+ ),
+ 'enabled' => static function () {
+ /**
+ * Enable the Crop and Gravity control settings.
+ *
+ * @hook cloudinary_enable_crop_and_gravity_control
+ * @since 3.1.3
+ * @default {false}
+ *
+ * @param $enabeld {bool} Is the Crop and Gravity control enabled?
+ *
+ * @retrun {bool}
+ */
+ return apply_filters( 'cloudinary_enable_crop_and_gravity_control', true );
+ },
+ ),
+ array(
'type' => 'info_box',
'icon' => $this->dir_url . 'css/images/academy-icon.svg',
'title' => __( 'Need help?', 'cloudinary' ),