{ __( 'Navigation', 'cloudinary' ) }
+
+
{ __( 'Zoom Type', 'cloudinary' ) }
+
+
+ { __( 'Zoom Trigger', 'cloudinary' ) } +
+
+
{ __( 'Carousel Location', 'cloudinary' ) }
+
+
{ __( 'Carousel Style', 'cloudinary' ) }
+
+
{ __( 'Navigation Button Shape', 'cloudinary' ) }
+ { NAVIGATION_BUTTON_SHAPE.map( ( item ) => ( +{ __( 'Selected Style', 'cloudinary' ) }
+
+
{ __( 'Media Shape Icon', 'cloudinary' ) }
+ { MEDIA_ICON_SHAPE.map( ( item ) => ( +{ __( 'Indicators Shape', 'cloudinary' ) }
+ { INDICATOR_SHAPE.map( ( item ) => ( +cloudinary://API_KEY:API_SECRET@CLOUD_NAME'
);
@@ -321,6 +321,22 @@ function ( $a ) {
return $result;
}
+ /**
+ * After updating the cloudinary_connect option, remove flag.
+ */
+ public function updated_option() {
+ wp_safe_redirect(
+ add_query_arg(
+ array(
+ 'page' => 'dashboard',
+ 'tab' => 'connect',
+ ),
+ admin_url( 'admin.php' )
+ )
+ );
+ exit;
+ }
+
/**
* Check the status of Cloudinary.
*
@@ -328,7 +344,7 @@ function ( $a ) {
*/
public function check_status() {
$status = $this->test_ping();
- update_option( self::META_KEYS['status'], $status );
+ $this->settings->get_setting( 'status' )->save_value( $status );
return $status;
}
@@ -401,7 +417,7 @@ public function get_credentials() {
* @return string|null
*/
public function get_cloud_name() {
- return $this->credentials['cloud_name'] ? $this->credentials['cloud_name'] : null;
+ return ! empty( $this->credentials['cloud_name'] ) ? $this->credentials['cloud_name'] : null;
}
/**
@@ -464,13 +480,36 @@ public function config_from_url( $url ) {
* @since 0.1
*/
public function setup() {
- // Get the cloudinary url from plugin config.
- $config = $this->plugin->config['settings']['connect'];
- if ( ! empty( $config['cloudinary_url'] ) ) {
- $this->config_from_url( $config['cloudinary_url'] );
+ // Get the cloudinary url from settings.
+ $cloudinary_url = $this->settings->get_value( 'cloudinary_url' );
+ if ( ! empty( $cloudinary_url ) ) {
+ $this->config_from_url( $cloudinary_url );
$this->api = new Connect\Api( $this, $this->plugin->version );
$this->usage_stats();
$this->setup_status_cron();
+ $this->plugin->settings->set_param( 'connected', $this->is_connected() );
+
+ // Add cancel button.
+ if ( $this->switch_account() ) {
+ $link = array(
+ 'type' => 'link',
+ 'content' => 'Cancel',
+ 'url' => $this->settings->find_setting( 'connect' )->get_component()->get_url(),
+ 'target' => '_self',
+ 'attributes' => array(
+ 'class' => array(
+ 'button-secondary',
+ ),
+ 'link_tag' => array(
+ 'style' => array(
+ 'margin-left:12px;',
+ ),
+ ),
+ ),
+ );
+
+ $this->settings->create_setting( 'cancel_switch', $link, $this->settings->find_setting( 'connect_button' ) );
+ }
}
}
@@ -492,15 +531,17 @@ protected function setup_status_cron() {
public function usage_stats( $refresh = false ) {
$stats = get_transient( self::META_KEYS['usage'] );
if ( empty( $stats ) || true === $refresh ) {
+ $last_usage = $this->settings->get_setting( 'last_usage' );
// Get users plan.
$stats = $this->api->usage();
if ( ! is_wp_error( $stats ) && ! empty( $stats['media_limits'] ) ) {
$stats['max_image_size'] = $stats['media_limits']['image_max_size_bytes'];
$stats['max_video_size'] = $stats['media_limits']['video_max_size_bytes'];
+ $last_usage->save_value( $stats );// Save the last successful call to prevgent crashing.
} else {
// Handle error by logging and fetching the last success.
// @todo : log issue.
- $stats = get_option( self::META_KEYS['last_usage'] );
+ $stats = $last_usage->get_value();
}
// Set useage state to the results, either new or the last, to prevent API hits.
set_transient( self::META_KEYS['usage'], $stats, HOUR_IN_SECONDS );
@@ -543,27 +584,21 @@ public function get_usage_stat( $type, $stat = null ) {
/**
* Gets the config of a connection.
- *
- * @since 0.1
- *
- * @return array The array of the config options stored.
*/
public function get_config() {
- $old_version = get_option( self::META_KEYS['version'] );
+ $old_version = $this->settings->get_value( 'version' );
if ( version_compare( $this->plugin->version, $old_version, '>' ) ) {
/**
* Do action to allow upgrading of different areas.
*
* @since 2.3.1
*
- * @param string $old_version The version upgrading from.
* @param string $new_version The version upgrading to.
+ *
+ * @param string $old_version The version upgrading from.
*/
do_action( 'cloudinary_version_upgrade', $old_version, $this->plugin->version );
}
-
- // We get the signature here since the upgrade action, may change the signature.
- return get_option( self::META_KEYS['signature'], null );
}
/**
@@ -580,27 +615,24 @@ public function usage_notices() {
if ( empty( $usage ) ) {
continue;
}
- $link = null;
- $link_text = null;
+ $link = 'https://cloudinary.com/console/lui/upgrade_options';
+ $link_text = __( 'upgrade your account', 'cloudinary' );
if ( 90 <= $usage ) {
// 90% used - show error.
- $level = 'error';
- $link = 'https://cloudinary.com/console/lui/upgrade_options';
- $link_text = __( 'upgrade your account', 'cloudinary' );
+ $level = 'error';
} elseif ( 80 <= $usage ) {
- $level = 'warning';
- $link_text = __( 'upgrade your account', 'cloudinary' );
+ $level = 'warning';
} elseif ( 70 <= $usage ) {
- $level = 'neutral';
- $link_text = __( 'upgrade your account', 'cloudinary' );
+ $level = 'neutral';
} else {
continue;
}
+
// translators: Placeholders are URLS and percentage values.
$message = sprintf(
/* translators: %1$s quota size, %2$s amount in percent, %3$s link URL, %4$s link anchor text. */
__(
- ' You are %2$s of the way through your monthly quota for %1$s on your Cloudinary account. If you exceed your quota, the Cloudinary plugin will be deactivated until your next billing cycle and your media assets will be served from your WordPress Media Library. You may wish to %4$s and increase your quota to ensure you maintain full functionality.',
+ 'You are %2$s of the way through your monthly quota for %1$s on your Cloudinary account. If you exceed your quota, the Cloudinary plugin will be deactivated until your next billing cycle and your media assets will be served from your WordPress Media Library. You may wish to %4$s and increase your quota to ensure you maintain full functionality.',
'cloudinary'
),
ucwords( $stat ),
@@ -608,7 +640,9 @@ public function usage_notices() {
$link,
$link_text
);
+
$this->notices[] = array(
+ 'icon' => 'dashicons-cloudinary',
'message' => $message,
'type' => $level,
'dismissible' => true,
@@ -623,10 +657,14 @@ public function usage_notices() {
*/
public function get_notices() {
$this->usage_notices();
- $screen = get_current_screen();
- if ( empty( $this->plugin->config['connect'] ) ) {
- if ( is_object( $screen ) && in_array( $screen->id, $this->plugin->components['settings']->handles, true ) ) {
- $link = '' . __( 'Connect', 'cloudinary' ) . ' ';
+ $screen = get_current_screen();
+ $connection_setting = $this->settings->find_setting( self::META_KEYS['url'] );
+ $cloudinary_url = $connection_setting->get_value();
+ if ( empty( $cloudinary_url ) ) {
+ $page_base = $this->settings->get_root_setting()->get_slug();
+ if ( is_object( $screen ) && $page_base === $screen->parent_base ) {
+ $url = $connection_setting->get_option_parent()->get_component()->get_url();
+ $link = '' . __( 'Connect', 'cloudinary' ) . ' ';
$this->notices[] = array(
'message' => $link . __( 'your Cloudinary account with WordPress to get started.', 'cloudinary' ),
'type' => 'error',
@@ -687,8 +725,171 @@ public function upgrade_connection( $old_version ) {
update_option( self::META_KEYS['connect'], $data );
update_option( self::META_KEYS['signature'], $signature );
update_option( self::META_KEYS['version'], $this->plugin->version );
- delete_option( self::META_KEYS['cache'] ); // remove the cache.
- $this->plugin->config['settings']['connect'] = $data; // Set the connection url for this round.
}
}
+
+ /**
+ * Check if the switch account param is set.
+ *
+ * @return bool
+ */
+ public function switch_account() {
+
+ $return = false;
+ if ( filter_input( INPUT_GET, 'switch-account', FILTER_VALIDATE_BOOLEAN ) ) {
+ return true;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Define the Settings.
+ *
+ * @return array
+ */
+ public function settings() {
+ $self = $this;
+ $url = add_query_arg(
+ array(
+ 'page' => 'media',
+ 'tab' => 'media_display',
+ ),
+ admin_url( 'admin.php' )
+ );
+
+ $args = array(
+ 'menu_title' => __( 'Getting Started', 'cloudinary' ),
+ 'page_title' => __( 'Getting Started', 'cloudinary' ),
+ 'type' => 'page',
+ 'tabs' => array(
+ 'about' => array(
+ 'page_title' => __( 'About', 'cloudinary' ),
+ array(
+ 'type' => 'info_box',
+ 'title' => __( 'Welcome to Cloudinary.', 'cloudinary' ),
+ 'text' => __(
+ 'Cloudinary supercharges your application media! It enables you to easily upload images and videos to the cloud and deliver them optimized, via a lightning-fast CDN, using industry best practices. Perform smart resizing, add watermarks, apply effects, and much more without leaving your WordPress console or installing any software.',
+ 'cloudinary'
+ ),
+ ),
+ array(
+ 'type' => 'info_box',
+ 'icon' => $this->plugin->dir_url . 'css/crop.svg',
+ 'title' => __( 'Image Delivery Settings', 'cloudinary' ),
+ 'text' => __(
+ 'Configure how your images are shown on your site. You can apply transformations to adjust the quality, format or visual appearance and define other settings such as responsive images.',
+ 'cloudinary'
+ ),
+ 'url' => $url . '#panel-image-settings',
+ 'blank' => false,
+ 'link_text' => __( 'Image settings', 'cloudinary' ),
+ ),
+ array(
+ 'type' => 'info_box',
+ 'icon' => $this->plugin->dir_url . 'css/video.svg',
+ 'title' => __( 'Video Settings', 'cloudinary' ),
+ 'text' => __(
+ 'Configure how your videos are shown on your site. You can apply transformations to adjust the quality, format or visual appearance and define other settings such as whether to use the Cloudinary video player.',
+ 'cloudinary'
+ ),
+ 'url' => $url . '#panel-video-settings',
+ 'blank' => false,
+ 'link_text' => __( 'Video settings', 'cloudinary' ),
+ ),
+ array(
+ 'type' => 'info_box',
+ 'icon' => $this->plugin->dir_url . 'css/transformation.svg',
+ 'title' => __( 'Learn More', 'cloudinary' ),
+ 'text' => __(
+ 'You can upload and manage your images in Cloudinary directly from your WordPress interface. The plugin also supports automated (single-click) migration of all images from your existing posts to Cloudinary. Once your WordPress images are stored in Cloudinary, you can take advantage of Cloudinary\'s transformation, optimization, and responsive image features as well as fast CDN delivery.',
+ 'cloudinary'
+ ),
+ 'url' => 'https://cloudinary.com/documentation/image_transformations#quick_example',
+ 'link_text' => __( 'See Examples', 'cloudinary' ),
+ ),
+ ),
+ 'connect' => array(
+ 'page_title' => __( 'Connect', 'cloudinary' ),
+ array(
+ 'enabled' => function () use ( $self ) {
+ return ! $self->switch_account() && $this->is_connected();
+ },
+ array(
+ 'type' => 'panel',
+ array(
+ 'type' => 'connect',
+ ),
+ ),
+ array(
+ 'type' => 'switch_cloud',
+ ),
+ ),
+ array(
+ 'enabled' => function () use ( $self ) {
+ return $self->switch_account() || ! $this->is_connected();
+ },
+ array(
+ 'title' => __( 'Connect to Cloudinary!', 'cloudinary' ),
+ 'type' => 'panel',
+ array(
+ 'content' => __( 'You need to connect your Cloudinary account to WordPress by adding your unique connection string. See below for where to find this.', 'cloudinary' ),
+ ),
+ array(
+ 'placeholder' => 'cloudinary://API_KEY:API_SECRET@CLOUD_NAME',
+ 'slug' => self::META_KEYS['url'],
+ 'title' => __( 'Connection string', 'cloudinary' ),
+ 'tooltip_text' => __(
+ 'The connection string is made up of your Cloudinary Cloud name, API Key and API Secret and known as the API Environment Variable. This authenticates the Cloudinary WordPress plugin with your Cloudinary account.',
+ 'cloudinary'
+ ),
+ 'type' => 'text',
+ 'attributes' => array(
+ 'class' => array(
+ 'connection-string',
+ ),
+ ),
+ ),
+ ),
+ array(
+ 'label' => __( 'Connect', 'cloudinary' ),
+ 'type' => 'submit',
+ 'slug' => 'connect_button',
+ ),
+ array(
+ 'collapsible' => 'open',
+ 'title' => __( 'Where to find my Connection string?', 'cloudinary' ),
+ 'type' => 'panel',
+ array(
+ 'content' => $this->get_connection_string_content(),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ // Add data storage.
+ foreach ( self::META_KEYS as $slug => $option_name ) {
+ $args[] = array(
+ 'slug' => $slug,
+ 'option_name' => $option_name,
+ 'type' => 'data',
+ );
+ }
+
+ return $args;
+ }
+
+ /**
+ * Get Connection String content for old settings.
+ *
+ * @return string
+ */
+ protected function get_connection_string_content() {
+ ob_start();
+ include $this->plugin->dir_path . 'php/templates/connection-string.php';
+
+ return ob_get_clean();
+ }
}
diff --git a/php/class-deactivation.php b/php/class-deactivation.php
index 8bfcce379..11bd25a73 100644
--- a/php/class-deactivation.php
+++ b/php/class-deactivation.php
@@ -26,7 +26,7 @@ class Deactivation {
*
* @var string
*/
- protected static $cld_endpoint = 'https://analytics.cloudinary.com/wp_deactivate_reason';
+ protected static $cld_endpoint = 'https://analytics-api.cloudinary.com/wp_deactivate_reason';
/**
* The internal endpoint to capture the administrator feedback.
diff --git a/php/class-media.php b/php/class-media.php
index e60f9281c..153698a1c 100644
--- a/php/class-media.php
+++ b/php/class-media.php
@@ -10,14 +10,16 @@
use Cloudinary\Component\Setup;
use Cloudinary\Connect\Api;
use Cloudinary\Media\Filter;
-use Cloudinary\Media\Upgrade;
+use Cloudinary\Media\Gallery;
use Cloudinary\Media\Global_Transformations;
+use Cloudinary\Media\Upgrade;
use Cloudinary\Media\Video;
+use Cloudinary\Media\WooCommerceGallery;
/**
* Class Media
*/
-class Media implements Setup {
+class Media extends Settings_Component implements Setup {
/**
* Holds the plugin instance.
@@ -90,6 +92,20 @@ class Media implements Setup {
*/
public $video;
+ /**
+ * Gallery instance.
+ *
+ * @var \Cloudinary\Media\Gallery.
+ */
+ public $gallery;
+
+ /**
+ * WooCommerceGallery instance.
+ *
+ * @var \Cloudinary\Media\WooCommerceGallery
+ */
+ public $woocommerce_gallery;
+
/**
* Sync instance.
*
@@ -105,18 +121,18 @@ class Media implements Setup {
private $in_downsize = false;
/**
- * Holds the max image width registered in WordPress.
+ * Flag to determine if the Featured Image is currently being rendered.
*
- * @var int
+ * @var bool|int
*/
- private $max_width;
+ private $doing_featured_image = false;
/**
- * Flag to determine if the Featured Image is currently being rendered.
+ * Holds the media settings slug.
*
- * @var bool|int
+ * @var string
*/
- private $doing_featured_image = false;
+ const MEDIA_SETTINGS_SLUG = 'media_display';
/**
* Media constructor.
@@ -161,7 +177,6 @@ public function get_compatible_media_types() {
*/
public function get_convertible_extensions() {
-
// Add preferred formats in future.
$base_types = array(
'psd' => 'jpg',
@@ -320,6 +335,45 @@ public function uncropped_url( $url ) {
return $url;
}
+ /**
+ * Fetch a public id from a cloudinary url.
+ *
+ * @param string $url The url to fetch the public id from.
+ * @param bool $as_sync_key Whether to return a plugin-based sync key, which is used to fetch an attachment id.
+ *
+ * @return string|null
+ */
+ public function get_public_id_from_url( $url, $as_sync_key = false ) {
+ if ( ! $this->is_cloudinary_url( $url ) ) {
+ return null;
+ }
+
+ $path = wp_parse_url( $url, PHP_URL_PATH );
+ $parts = explode( '/', ltrim( $path, '/' ) );
+
+ // Need to find the version part as anything after this is the public id.
+ foreach ( $parts as $part ) {
+ array_shift( $parts ); // Get rid of the first element.
+ if ( 'v' === substr( $part, 0, 1 ) && is_numeric( substr( $part, 1 ) ) ) {
+ break; // Stop removing elements.
+ }
+ }
+
+ // The remaining items should be the file.
+ $file = implode( '/', $parts );
+ $path_info = pathinfo( $file );
+
+ $public_id = '.' !== $path_info['dirname'] ? $path_info['dirname'] : $path_info['filename'];
+ $public_id = trim( $public_id, './' );
+
+ if ( $as_sync_key ) {
+ $transformations = $this->get_transformations_from_string( $url );
+ $public_id .= ! empty( $transformations ) ? wp_json_encode( $transformations ) : '';
+ }
+
+ return $public_id;
+ }
+
/**
* Attempt to get an attachment_id from a url.
*
@@ -329,29 +383,9 @@ public function uncropped_url( $url ) {
*/
public function get_id_from_url( $url ) {
if ( $this->is_cloudinary_url( $url ) ) {
- $path = wp_parse_url( $url, PHP_URL_PATH );
- $parts = explode( '/', ltrim( $path, '/' ) );
- // Need to find the version part as anything after this is the public id.
- foreach ( $parts as $part ) {
- array_shift( $parts ); // Get rid of the first element.
- if ( 'v' === substr( $part, 0, 1 ) && is_numeric( substr( $part, 1 ) ) ) {
- break; // Stop removing elements.
- }
- }
-
- // The remaining items should be the file.
- $file = implode( '/', $parts );
- $pathinfo = pathinfo( $file );
- $public_id = trim( $pathinfo['dirname'] . '/' . $pathinfo['filename'], './' );
- $sync_key = $public_id;
- $transformations = $this->get_transformations_from_string( $url );
- if ( ! empty( $transformations ) ) {
- $sync_key .= wp_json_encode( $transformations );
- }
+ $sync_key = $this->get_public_id_from_url( $url, true );
$attachment_id = $this->get_id_from_sync_key( $sync_key );
-
} else {
-
// Clear out any params.
if ( wp_parse_url( $url, PHP_URL_QUERY ) ) {
$url = strstr( $url, '?', true );
@@ -504,6 +538,10 @@ public function get_crop( $url, $attachment_id ) {
'crop' => $cropped ? 'fill' : 'scale',
);
if ( $cropped ) {
+ // Special thumbnail size.
+ if ( 'thumbnail' === $size_name ) {
+ $wp_size['crop'] = 'thumb';
+ }
$wp_size['gravity'] = 'auto';
}
@@ -603,7 +641,7 @@ function ( $part ) {
*/
public function get_transformations_from_string( $str, $type = 'image' ) {
- $params = \Cloudinary\Connect\Api::$transformation_index[ $type ];
+ $params = Api::$transformation_index[ $type ];
$transformation_chains = explode( '/', $str );
$transformations = array();
@@ -669,7 +707,7 @@ public function apply_default_transformations( array $transformations, $type = '
// Base image level.
$new_transformations = array(
- 'image' => \Cloudinary\Connect\Api::generate_transformation_string( $transformations, $type ),
+ 'image' => Api::generate_transformation_string( $transformations, $type ),
'global' => array(),
'tax' => array(),
'qf' => array(),
@@ -677,39 +715,38 @@ public function apply_default_transformations( array $transformations, $type = '
// Get Taxonomies.
$new_transformations['tax'] = $this->global_transformations->get_taxonomy_transformations( $type );
if ( ! $this->global_transformations->is_taxonomy_overwrite() ) {
- // Get Lowest level.
- $global = $this->global_transformations->globals[ $type ];
- $default = array();
- if ( 'video' === $type ) {
- if ( isset( $global['video_limit_bitrate'] ) && 'on' === $global['video_limit_bitrate'] ) {
- $default['bit_rate'] = $global['video_bitrate'] . 'k';
- }
- } else {
- if ( 'auto' === $global[ $type . '_format' ] ) {
- $default['fetch_format'] = 'auto';
- }
- if ( isset( $global[ $type . '_quality' ] ) ) {
- $default['quality'] = 'none' !== $global[ $type . '_quality' ] ? $global[ $type . '_quality' ] : null;
- } else {
- $default['quality'] = 'auto';
- }
- }
+ /**
+ * Filter the default Quality and Format transformations for the specific media type.
+ *
+ * @param array $defaults The default transformations array.
+ * @param array $transformations The current transformations array.
+ *
+ * @return array
+ */
+ $default = apply_filters( "cloudinary_default_qf_transformations_{$type}", array(), $transformations );
$default = array_filter( $default ); // Clear out empty settings.
- $new_transformations['qf'] = \Cloudinary\Connect\Api::generate_transformation_string( array( $default ), $type );
+ $new_transformations['qf'] = Api::generate_transformation_string( array( $default ), $type );
+
+ /**
+ * Filter the default Freeform transformations for the specific media type.
+ *
+ * @param array $defaults The default transformations array.
+ * @param array $transformations The current transformations array.
+ *
+ * @return array
+ */
+ $freeform = apply_filters( "cloudinary_default_freeform_transformations_{$type}", array(), $transformations );
+ $freeform = array_filter( $freeform ); // Clear out empty settings.
// Add freeform global transformations.
- $freeform_type = $type . '_freeform';
- if ( ! empty( $global[ $freeform_type ] ) ) {
- $new_transformations['global'][] = trim( $global[ $freeform_type ] );
+ if ( ! empty( $freeform ) ) {
+ $new_transformations['global'] = implode( '/', $freeform );
}
-
- $new_transformations['global'] = implode( '/', $new_transformations['global'] );
-
}
// Clean out empty parts, and join into a sectioned string.
$new_transformations = array_filter( $new_transformations );
$new_transformations = implode( '/', $new_transformations );
// Take sectioned string, and create a transformation array set.
- $transformations = $this->get_transformations_from_string( $new_transformations );
+ $transformations = $this->get_transformations_from_string( $new_transformations, $type );
/**
* Filter the default cloudinary transformations.
*
@@ -725,19 +762,59 @@ public function apply_default_transformations( array $transformations, $type = '
return $defaults;
}
+ /**
+ * Apply default quality anf format image transformations.
+ *
+ * @param array $default The current default transformations.
+ *
+ * @return array
+ */
+ public function default_image_transformations( $default ) {
+
+ $config = $this->settings->get_value( 'image_settings' );
+
+ if ( 'on' === $config['image_optimization'] ) {
+ if ( 'auto' === $config['image_format'] ) {
+ $default['fetch_format'] = 'auto';
+ }
+ if ( isset( $config['image_quality'] ) ) {
+ $default['quality'] = 'none' !== $config['image_quality'] ? $config['image_quality'] : null;
+ } else {
+ $default['quality'] = 'auto';
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Apply default image freeform transformations.
+ *
+ * @param array $default The current default transformations.
+ *
+ * @return array
+ */
+ public function default_image_freeform_transformations( $default ) {
+ $config = $this->settings->get_value( 'image_settings' );
+ if ( ! empty( $config['image_freeform'] ) ) {
+ $default[] = trim( $config['image_freeform'] );
+ }
+
+ return $default;
+ }
+
/**
* Generate a Cloudinary URL based on attachment ID and required size.
*
- * @param int $attachment_id The id of the attachment.
- * @param array|string $size The wp size to set for the URL.
- * @param array $transformations Set of transformations to apply to this url.
- * @param string $cloudinary_id Optional forced cloudinary ID.
+ * @param int $attachment_id The id of the attachment.
+ * @param array|string $size The wp size to set for the URL.
+ * @param array $transformations Set of transformations to apply to this url.
+ * @param string $cloudinary_id Optional forced cloudinary ID.
* @param bool $overwrite_transformations Flag url is a breakpoint URL to stop re-applying default transformations.
*
* @return string The converted URL.
*/
public function cloudinary_url( $attachment_id, $size = array(), $transformations = array(), $cloudinary_id = null, $overwrite_transformations = false ) {
-
if ( ! ( $cloudinary_id ) ) {
$cloudinary_id = $this->cloudinary_id( $attachment_id );
if ( ! $cloudinary_id ) {
@@ -768,9 +845,11 @@ public function cloudinary_url( $attachment_id, $size = array(), $transformation
*
* @return array
*/
- $pre_args['transformation'] = apply_filters( 'cloudinary_transformations', $transformations, $attachment_id );
+ $pre_args['transformation'] = apply_filters( 'cloudinary_transformations', $transformations, $attachment_id );
+ $apply_default_transformations = apply_filters( 'cloudinary_apply_default_transformations', true );
+
// Defaults are only to be added on front, main images ( not breakpoints, since these are adapted down), and videos.
- if ( ( ! defined( 'REST_REQUEST' ) || false === REST_REQUEST ) && ! is_admin() && false === $overwrite_transformations ) {
+ if ( true === $apply_default_transformations && false === $overwrite_transformations && ! is_admin() ) {
$pre_args['transformation'] = $this->apply_default_transformations( $pre_args['transformation'], $resource_type );
}
@@ -917,9 +996,9 @@ public function get_cloudinary_id( $attachment_id ) {
// @todo: Make this use the globals, overrides, and application conversion.
$extension = pathinfo( $file, PATHINFO_EXTENSION );
if ( wp_attachment_is_image( $attachment_id ) ) {
- $settings = $this->global_transformations->globals['image'];
- if ( ! in_array( $settings['image_format'], array( 'none', 'auto' ), true ) ) {
- $extension = $settings['image_format'];
+ $image_format = $this->settings->find_setting( 'image_format' )->get_value();
+ if ( ! in_array( $image_format, array( 'none', 'auto' ), true ) ) {
+ $extension = $image_format;
}
}
$cloudinary_id = $public_id . '.' . $extension;
@@ -1066,7 +1145,7 @@ public function image_srcset( $sources, $size_array, $image_src, $image_meta, $a
$image_meta['overwrite_transformations'] = ! empty( $image_meta['overwrite_transformations'] ) ? $image_meta['overwrite_transformations'] : false;
- if ( 'on' === $this->plugin->config['settings']['global_transformations']['enable_breakpoints'] && wp_image_matches_ratio( $image_meta['width'], $image_meta['height'], $size_array[0], $size_array[1] ) ) {
+ if ( 'on' === $this->settings->get_setting( 'enable_breakpoints' )->get_value() && wp_image_matches_ratio( $image_meta['width'], $image_meta['height'], $size_array[0], $size_array[1] ) ) {
$meta = $this->get_post_meta( $attachment_id, Sync::META_KEYS['breakpoints'], true );
if ( ! empty( $meta ) ) {
// Since srcset is primary and src is a fallback, we need to set the first srcset with the main image.
@@ -1121,7 +1200,12 @@ function ( $item ) use ( $crop ) {
// Use current sources, but convert the URLS.
foreach ( $sources as &$source ) {
if ( ! $this->is_cloudinary_url( $source['url'] ) ) {
- $source['url'] = $this->convert_url( $source['url'], $attachment_id, $transformations, $image_meta['overwrite_transformations'] ); // Overwrite transformations applied, since the $transformations includes globals from the primary URL.
+ $source['url'] = $this->convert_url(
+ $source['url'],
+ $attachment_id,
+ $transformations,
+ $image_meta['overwrite_transformations']
+ ); // Overwrite transformations applied, since the $transformations includes globals from the primary URL.
}
}
@@ -1331,7 +1415,6 @@ public function down_sync_asset() {
$nonce = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_STRING );
if ( wp_verify_nonce( $nonce, 'wp_rest' ) ) {
-
$asset = $this->get_asset_payload();
// Set a base array for pulling an asset if needed.
$base_return = array(
@@ -1493,32 +1576,35 @@ public static function sanitize_breakpoints( $value, $field ) {
*
* @return int
*/
- public function get_max_width() {
- if ( empty( $this->max_width ) ) {
- if ( ! empty( $this->plugin->config['settings']['global_transformations']['max_width'] ) ) {
- $this->max_width = $this->plugin->config['settings']['global_transformations']['max_width'];
- } else {
- $core_sizes = array( 'thumbnail', 'medium', 'large', 'medium_large', 'large' );
- $additional_sizes = wp_get_additional_image_sizes();
- foreach ( $core_sizes as $size ) {
- $additional_sizes[ $size ] = get_option( $size . '_size_w' );
+ public function default_max_width() {
+ $core_sizes = array( 'thumbnail', 'medium', 'large', 'medium_large', 'large' );
+ $additional_sizes = wp_get_additional_image_sizes();
+ foreach ( $core_sizes as $size ) {
+ $additional_sizes[ $size ] = get_option( $size . '_size_w' );
+ }
+ $sizes = array_map(
+ function ( $item ) {
+ if ( is_array( $item ) ) {
+ $item = $item['width'];
}
- $sizes = array_map(
- function ( $item ) {
- if ( is_array( $item ) ) {
- $item = $item['width'];
- }
- return intval( $item );
- },
- $additional_sizes
- );
- rsort( $sizes );
- $this->max_width = array_shift( $sizes );
- }
- }
+ return intval( $item );
+ },
+ $additional_sizes
+ );
+ rsort( $sizes );
+ $max_width = array_shift( $sizes );
- return $this->max_width;
+ return $max_width;
+ }
+
+ /**
+ * Get the max image width registered in WordPress.
+ *
+ * @return int
+ */
+ public function get_max_width() {
+ return $this->settings->get_setting( 'max_width' )->get_value();
}
/**
@@ -1630,9 +1716,9 @@ public function delete_post_meta( $post_id, $key ) {
public function get_breakpoint_options( $attachment_id ) {
// Add breakpoints if we have an image.
$breakpoints = array();
- $settings = $this->plugin->config['settings']['global_transformations'];
+ $settings = $this->settings->get_setting( self::MEDIA_SETTINGS_SLUG )->get_value();
- if ( 'off' !== $settings['enable_breakpoints'] && wp_attachment_is_image( $attachment_id ) ) {
+ if ( 'on' === $settings['enable_breakpoints'] && wp_attachment_is_image( $attachment_id ) ) {
$meta = wp_get_attachment_metadata( $attachment_id );
// Get meta image size if non exists.
if ( empty( $meta ) ) {
@@ -1651,7 +1737,7 @@ public function get_breakpoint_options( $attachment_id ) {
);
$transformations = $this->get_transformation_from_meta( $attachment_id );
if ( ! empty( $transformations ) ) {
- $breakpoints['transformation'] = Api::generate_transformation_string( $transformations );
+ $breakpoints['transformation'] = Api::generate_transformation_string( $transformations, 'image' );
}
$breakpoints = array(
'public_id' => $this->get_public_id( $attachment_id ),
@@ -1870,21 +1956,44 @@ public function upgrade_media_settings() {
}
}
+ /**
+ * Checks if local URLS can be filtered out.
+ *
+ * @return bool
+ */
+ public function can_filter_out_local() {
+ $can = true;
+ if ( 'cld' !== $this->plugin->settings->find_setting( 'offload' )->get_value() ) {
+ /**
+ * Filter to allow stopping filtering out local.
+ *
+ * @param bool $can True as default.
+ *
+ * @return bool
+ */
+ $can = apply_filters( 'cloudinary_filter_out_local', true );
+ }
+
+ return $can;
+ }
+
/**
* Setup the hooks and base_url if configured.
*/
public function setup() {
- if ( $this->plugin->config['connect'] ) {
+ if ( $this->plugin->settings->get_param( 'connected' ) ) {
$this->base_url = $this->plugin->components['connect']->api->cloudinary_url();
$this->credentials = $this->plugin->components['connect']->get_credentials();
- $this->cloudinary_folder = $this->plugin->config['settings']['sync_media']['cloudinary_folder'] ? $this->plugin->config['settings']['sync_media']['cloudinary_folder'] : '';
+ $this->cloudinary_folder = $this->settings->get_value( 'cloudinary_folder' );
$this->sync = $this->plugin->components['sync'];
// Internal components.
+ $this->global_transformations = new Global_Transformations( $this );
+ $this->gallery = new Gallery( $this );
+ $this->woocommerce_gallery = new WooCommerceGallery( $this->gallery );
$this->filter = new Filter( $this );
$this->upgrade = new Upgrade( $this );
- $this->global_transformations = new Global_Transformations( $this );
$this->video = new Video( $this );
// Set the max image size registered in WordPress.
@@ -1899,17 +2008,104 @@ public function setup() {
add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
// Filter live URLS. (functions that return a URL).
- add_filter( 'wp_calculate_image_srcset', array( $this, 'image_srcset' ), 10, 5 );
- add_filter( 'wp_get_attachment_url', array( $this, 'attachment_url' ), 10, 2 );
- add_filter( 'image_downsize', array( $this, 'filter_downsize' ), 10, 3 );
+ if ( $this->can_filter_out_local() ) {
+ add_filter( 'wp_calculate_image_srcset', array( $this, 'image_srcset' ), 10, 5 );
+ add_filter( 'wp_get_attachment_url', array( $this, 'attachment_url' ), 10, 2 );
+ add_filter( 'image_downsize', array( $this, 'filter_downsize' ), 10, 3 );
+ // Hook into Featured Image cycle.
+ add_action( 'begin_fetch_post_thumbnail_html', array( $this, 'set_doing_featured' ), 10, 2 );
+ add_filter( 'post_thumbnail_html', array( $this, 'maybe_srcset_post_thumbnail' ), 10, 3 );
+ }
+ // Filter default image Quality and Format transformations.
+ add_filter( 'cloudinary_default_qf_transformations_image', array( $this, 'default_image_transformations' ), 10 );
+ add_filter( 'cloudinary_default_freeform_transformations_image', array( $this, 'default_image_freeform_transformations' ), 10 );
// Filter and action the custom column.
add_filter( 'manage_media_columns', array( $this, 'media_column' ) );
add_action( 'manage_media_custom_column', array( $this, 'media_column_value' ), 10, 2 );
+ }
+ }
+
+ /**
+ * Register sync settings.
+ *
+ * @return array
+ */
+ public function settings() {
+
+ $image_settings = array();
+ $video_settings = array();
+ $image_settings_file = $this->plugin->dir_path . 'ui-definitions/settings-image.php';
+ $video_settings_file = $this->plugin->dir_path . 'ui-definitions/settings-video.php';
+
+ if ( file_exists( $image_settings_file ) ) {
+ $image_settings = include $image_settings_file; //phpcs:ignore
+ }
+
+ if ( file_exists( $video_settings_file ) ) {
+ $video_settings = include $video_settings_file; //phpcs:ignore
+ }
+
+ $args = array(
+ 'type' => 'page',
+ 'menu_title' => __( 'Media Settings', 'cloudinary' ),
+ 'tabs' => array(
+ self::MEDIA_SETTINGS_SLUG => array(
+ 'page_title' => __( 'Media Display', 'cloudinary' ),
+ array(
+ 'type' => 'info_box',
+ 'icon' => $this->plugin->dir_url . 'css/transformation.svg',
+ 'title' => __( 'Transformations', 'cloudinary' ),
+ 'text' => __(
+ 'Cloudinary allows you to easily transform your images on-the-fly to any required format, style and dimension, and also optimizes images for minimal file size alongside high visual quality for an improved user experience and minimal bandwidth. You can do all of this by implementing dynamic image transformation and delivery URLs.',
+ 'cloudinary'
+ ),
+ 'url' => 'https://cloudinary.com/documentation/transformation_reference',
+ 'link_text' => __( 'See Examples', 'cloudinary' ),
+ ),
+ $image_settings,
+ $video_settings,
+ ),
+ ),
+ );
+
+ return $args;
+ }
+
+ /**
+ * Enabled method for version if settings are enabled.
+ *
+ * @param bool $enabled Flag to enable.
+ *
+ * @return bool
+ */
+ public function is_enabled( $enabled ) {
+ return $this->plugin->settings->get_param( 'connected' );
+ }
- // Hook into Featured Image cycle.
- add_action( 'begin_fetch_post_thumbnail_html', array( $this, 'set_doing_featured' ), 10, 2 );
- add_filter( 'post_thumbnail_html', array( $this, 'maybe_srcset_post_thumbnail' ), 10, 3 );
+ /**
+ * Upgrade settings from 2.4 to 2.5.
+ *
+ * @param string $previous_version Previous version.
+ * @param string $new_version New version.
+ */
+ public function upgrade_settings( $previous_version, $new_version ) {
+
+ if ( 2.4 === $previous_version ) {
+ // Setup new data from old.
+ $images = get_option( 'cloudinary_global_transformations', array() );
+ $video = get_option( 'cloudinary_global_video_transformations', array() );
+ $old_media = array_merge( $images, $video );
+ $setting = $this->settings->get_setting( 'media_display' );
+ // Get the current defaults.
+ $default = $setting->get_value();
+
+ $media = wp_parse_args( $old_media, $default );
+ // Update value.
+ $setting->set_value( $media );
+ // Save to DB.
+ $setting->save_value();
}
+
}
}
diff --git a/php/class-plugin.php b/php/class-plugin.php
index 945278b6f..e2ddce49f 100644
--- a/php/class-plugin.php
+++ b/php/class-plugin.php
@@ -11,13 +11,17 @@
use Cloudinary\Component\Config;
use Cloudinary\Component\Notice;
use Cloudinary\Component\Setup;
+use Cloudinary\Settings\Setting;
use Cloudinary\Sync\Storage;
-use Cloudinary\Deactivation;
+use WP_REST_Request;
+use WP_REST_Server;
+use const E_USER_WARNING;
+use const WPCOM_IS_VIP_ENV;
/**
* Main plugin bootstrap file.
*/
-class Plugin {
+final class Plugin {
/**
* Holds the components of the plugin
@@ -34,6 +38,13 @@ class Plugin {
*/
public $config = array();
+ /**
+ * The core Settings object.
+ *
+ * @var Setting
+ */
+ public $settings;
+
/**
* Plugin slug.
*
@@ -92,7 +103,6 @@ class Plugin {
*/
public $hooks;
-
/**
* Plugin_Base constructor.
*/
@@ -117,16 +127,13 @@ public function __construct() {
* that extend the Customizer to ensure resources are available in time.
*/
public function init() {
- $this->components['settings'] = new Settings_Page( $this );
+
$this->components['connect'] = new Connect( $this );
$this->components['deactivation'] = new Deactivation( $this );
-
- if ( $this->components['connect'] && $this->components['connect']->is_connected() ) {
- $this->components['sync'] = new Sync( $this );
- $this->components['api'] = new REST_API( $this );
- $this->components['media'] = new Media( $this );
- $this->components['storage'] = new Storage( $this );
- }
+ $this->components['sync'] = new Sync( $this );
+ $this->components['media'] = new Media( $this );
+ $this->components['api'] = new REST_API( $this );
+ $this->components['storage'] = new Storage( $this );
}
/**
@@ -134,7 +141,7 @@ public function init() {
*
* @param mixed $component The component.
*
- * @return \Cloudinary\Connect|\Cloudinary\Media|\Cloudinary\REST_API|\Cloudinary\Settings_Page|\Cloudinary\Sync|null
+ * @return Connect|Media|REST_API|Settings_Page|Sync|null
*/
public function get_component( $component ) {
$return = null;
@@ -146,18 +153,107 @@ public function get_component( $component ) {
}
/**
- * Register Hooks for the plugin.
+ * Get the core settings page structure for settings.
+ *
+ * @return array
*/
- public function set_config() {
- $components = array_filter( $this->components, array( $this, 'is_config_component' ) );
+ private function get_settings_page_structure() {
+
+ $parts = array(
+ 'header' => array(),
+ 'pages' => array(),
+ 'footer' => array(),
+ );
+
+ foreach ( $parts as $slug => $part ) {
+ if ( file_exists( $this->dir_path . "ui-definitions/settings-{$slug}.php" ) ) {
+ $parts[ $slug ] = include $this->dir_path . "ui-definitions/settings-{$slug}.php";
+ }
+ }
+
+ $structure = array(
+ 'version' => $this->version,
+ 'page_title' => __( 'Cloudinary', 'cloudinary' ),
+ 'menu_title' => __( 'Cloudinary', 'cloudinary' ),
+ 'capability' => 'manage_options',
+ 'icon' => 'dashicons-cloudinary',
+ 'option_name' => $this->slug,
+ 'page_header' => $parts['header'],
+ 'page_footer' => $parts['footer'],
+ 'pages' => $parts['pages'],
+ );
+
+ return $structure;
+ }
+
+ /**
+ * Setup settings.
+ */
+ public function setup_settings() {
+ $params = $this->get_settings_page_structure();
+ $this->settings = \Cloudinary\Settings::create_setting( $this->slug, $params );
+ $components = array_filter( $this->components, array( $this, 'is_setting_component' ) );
+ $this->init_component_settings( $components );
+ $this->register_component_settings( $components );
+
+ // Init settings.
+ \Cloudinary\Settings::init_setting( $this->slug );
+
+ // Add count notice if not connected.
+ if ( ! $this->get_component( 'connect' )->is_connected() ) {
+ $count = sprintf( ' %d', 1, number_format_i18n( 1 ) );
+ $main_title = $this->settings->get_param( 'menu_title' ) . $count;
+ $this->settings->set_param( 'menu_title', $main_title );
+ $this->settings->set_param( 'connect_count', $count );
+
+ // Set the Getting Started title.
+ $connect = $this->settings->find_setting( 'dashboard' );
+ $connect_title = $connect->get_param( 'menu_title' ) . $count;
+ $connect->set_param( 'menu_title', $connect_title );
+ }
+ }
+
+ /**
+ * Init component settings objects.
+ *
+ * @param Settings_Component[] $components of components to init settings for.
+ */
+ private function init_component_settings( $components ) {
+ foreach ( $components as $slug => $component ) {
+ /**
+ * Component that implements Settings.
+ *
+ * @var Component\Settings $component
+ */
+ $component->init_settings( $this->settings );
+ }
+ }
+ /**
+ * Register settings.
+ *
+ * @param Settings_Component[] $components Array of components to register settings for.
+ */
+ private function register_component_settings( $components ) {
foreach ( $components as $slug => $component ) {
/**
- * Component that implements Component\Config.
+ * Component that implements Settings.
*
- * @var Component\Config $component
+ * @var Component\Settings $component
*/
- $this->config[ $slug ] = $component->get_config();
+ $component->register_settings( $this->settings );
+ }
+ }
+
+ /**
+ * Register Hooks for the plugin.
+ */
+ public function set_config() {
+ $this->setup_settings();
+ $components = array_filter( $this->components, array( $this, 'is_config_component' ) );
+
+ foreach ( $components as $slug => $component ) {
+ $component->get_config();
}
}
@@ -184,7 +280,7 @@ public function register_hooks() {
public function rest_endpoints( $endpoints ) {
$endpoints['dismiss_notice'] = array(
- 'method' => \WP_REST_Server::CREATABLE,
+ 'method' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'rest_dismiss_notice' ),
'args' => array(),
);
@@ -248,7 +344,6 @@ private function is_asset_component( $component ) {
return $component instanceof Assets;
}
-
/**
* Check if an asset component is active.
*
@@ -288,6 +383,19 @@ private function is_config_component( $component ) {
return $component instanceof Config;
}
+ /**
+ * Check if component is a settings implementing component.
+ *
+ * @since 0.1
+ *
+ * @param object $component The component to check.
+ *
+ * @return bool If the component implements Setting.
+ */
+ private function is_setting_component( $component ) {
+ return $component instanceof Settings_Component;
+ }
+
/**
* Check if component is a notice implementing component.
*
@@ -295,7 +403,7 @@ private function is_config_component( $component ) {
*
* @param object $component The component to check.
*
- * @return bool If the component implements Config.
+ * @return bool If the component implements Notice.
*/
private function is_notice_component( $component ) {
return $component instanceof Notice;
@@ -321,14 +429,15 @@ public function setup() {
$component->setup();
}
+
}
/**
* Set a transient with the duration using a token as an identifier.
*
- * @param \WP_REST_Request $request The request object.
+ * @param WP_REST_Request $request The request object.
*/
- public function rest_dismiss_notice( \WP_REST_Request $request ) {
+ public function rest_dismiss_notice( WP_REST_Request $request ) {
$token = $request->get_param( 'token' );
$duration = $request->get_param( 'duration' );
@@ -341,58 +450,29 @@ public function rest_dismiss_notice( \WP_REST_Request $request ) {
* @since 0.1
*/
public function admin_notices() {
+
+ $setting = Utils::get_active_setting();
/**
* An array of classes that implement the Notice interface.
*
* @var $components Notice[]
*/
- $components = array_filter( $this->components, array( $this, 'is_notice_component' ) );
- $default = array(
+ $components = array_filter( $this->components, array( $this, 'is_notice_component' ) );
+ $default = array(
'message' => '',
'type' => 'error',
- 'dismissible' => true,
+ 'dismissible' => false,
'duration' => 10, // Default dismissible duration is 10 Seconds for save notices etc...
+ 'icon' => null,
);
- $has_dismissible_notices = false;
+
foreach ( $components as $component ) {
$notices = $component->get_notices();
foreach ( $notices as $notice ) {
- if ( ! empty( $notice ) && ! empty( $notice['message'] ) ) {
- $notice = wp_parse_args( $notice, $default );
- if ( true === $notice['dismissible'] ) {
- // Convert the whole notice data into a string, and make it a hash.
- // This allows the same notice to show if it has a change, i.e Quota limits change.
- $notice_key = md5( wp_json_encode( $notice ) );
- if ( ! get_transient( $notice_key ) ) {
- $html = sprintf(
- '%2$s
%2$s