<?php
/**
* Manages Gallery Widget and Block settings.
*
* @package Cloudinary
*/
namespace Cloudinary\Media;
use Cloudinary\Plugin;
use Cloudinary\Settings\Setting;
use Cloudinary\Media;
use Cloudinary\REST_API;
use Cloudinary\Settings_Component;
use Cloudinary\Utils;
use WP_Screen;
/**
* Class Gallery.
*
* Handles gallery.
*/
class Gallery extends Settings_Component {
/**
* The enqueue script handle for the gallery widget lib.
*
* @var string
*/
const GALLERY_LIBRARY_HANDLE = 'cld-gallery';
/**
* Holds the settings slug.
*
* @var string
*/
public $settings_slug = 'gallery';
/**
* Holds the sync settings object.
*
* @var Setting
*/
public $settings;
/**
* The default config in case no settings are saved.
*
* @var array
*/
public static $default_config = array(
'mediaAssets' => array(),
'transition' => 'fade',
'aspectRatio' => '3:4',
'navigation' => 'always',
'zoom' => true,
'carouselLocation' => 'top',
'carouselOffset' => 5,
'carouselStyle' => 'thumbnails',
'displayProps' => array(
'mode' => 'classic',
'columns' => 1,
),
'indicatorProps' => array( 'shape' => 'round' ),
'themeProps' => array(
'primary' => '#cf2e2e',
'onPrimary' => '#000000',
'active' => '#777777',
),
'zoomProps' => array(
'type' => 'popup',
'viewerPosition' => 'bottom',
'trigger' => 'click',
),
'thumbnailProps' => array(
'width' => 64,
'height' => 64,
'navigationShape' => 'radius',
'selectedStyle' => 'gradient',
'selectedBorderPosition' => 'all',
'selectedBorderWidth' => 4,
'mediaSymbolShape' => 'round',
),
'customSettings' => '',
);
/**
* Holds instance of the Media class.
*
* @var Media
*/
public $media;
/**
* Holds the current config.
*
* @var array
*/
protected $config = array();
/**
* Init gallery.
*
* @param Plugin $plugin Main class instance.
*/
public function __construct( Plugin $plugin ) {
parent::__construct( $plugin );
$this->media = $plugin->get_component( 'media' );
$this->setup_hooks();
}
/**
* Gets the gallery settings in the expected json format.
*
* @return array
*/
public function get_config() {
$screen = null;
if ( function_exists( 'get_current_screen' ) ) {
$screen = get_current_screen();
}
$config = ! empty( $this->settings->get_value( 'gallery_config' ) ) ?
$this->settings->get_value( 'gallery_config' ) :
self::$default_config;
$this->config = $this->maybe_decode_config( $config );
$config = Utils::array_filter_recursive( $this->config ); // Remove empty values.
$config['cloudName'] = $this->plugin->components['connect']->get_cloud_name();
/**
* Filter the gallery HTML container.
*
* @hook cloudinary_gallery_html_container
* @default ''
*
* @param $selector {string} The target HTML selector.
*
* @return {string}
*/
$config['container'] = apply_filters( 'cloudinary_gallery_html_container', '' );
$credentials = $this->plugin->components['connect']->get_credentials();
// Do not use privateCdn and secureDistribution on gallery settings page.
if (
! empty( $credentials['cname'] )
&& ( ! $screen instanceof WP_Screen || 'cloudinary_page_cloudinary_gallery' !== $screen->id )
) {
$config['secureDistribution'] = $credentials['cname'];
$config['privateCdn'] = true;
}
/**
* Filter the gallery configuration.
*
* @hook cloudinary_gallery_config
*
* @param $config {array} The current gallery config.
*
* @return {array}
*/
$config = apply_filters( 'cloudinary_gallery_config', $config );
$config['queryParam'] = 'AA';
// Make sure custom settings are a json string.
if ( ! empty( $config['customSettings'] ) && is_array( $config['customSettings'] ) ) {
$config['customSettings'] = wp_json_encode( $config['customSettings'] );
}
return $config;
}
/**
* Register frontend assets for the gallery.
*/
public function enqueue_gallery_library() {
// Bail enqueuing the scripts if conditions aren't met.
if ( ! $this->maybe_enqueue_scripts() ) {
return;
}
wp_enqueue_script(
self::GALLERY_LIBRARY_HANDLE,
CLOUDINARY_ENDPOINTS_GALLERY,
array(),
$this->plugin->version,
true
);
$json_config = wp_json_encode( $this->get_config() );
wp_add_inline_script( self::GALLERY_LIBRARY_HANDLE, "var CLD_GALLERY_CONFIG = {$json_config};" );
wp_enqueue_script(
'cloudinary-gallery-init',
$this->plugin->dir_url . 'js/gallery-init.js',
array( self::GALLERY_LIBRARY_HANDLE ),
$this->plugin->version,
true
);
}
/**
* Enqueue admin UI scripts if needed.
*/
public function enqueue_admin_scripts() {
if ( Utils::get_active_setting() !== $this->settings->get_setting( $this->settings_slug ) ) {
return;
}
$this->block_editor_scripts_styles();
wp_enqueue_style(
'cloudinary-gallery-settings-css',
$this->plugin->dir_url . 'css/gallery-ui.css',
array(),
$this->plugin->version
);
$script = array(
'slug' => 'gallery_config',
'src' => $this->plugin->dir_url . 'js/gallery.js',
'in_footer' => true,
);
$asset = $this->get_asset();
wp_enqueue_script( $script['slug'], $script['src'], $asset['dependencies'], $asset['version'], $script['in_footer'] );
$color_palette = array_filter( (array) get_theme_support( 'editor-color-palette' ) );
if ( ! empty( $color_palette ) ) {
$color_palette = array_shift( $color_palette );
}
$color_palette = wp_json_encode( $color_palette );
wp_add_inline_script( $script['slug'], "var CLD_THEME_COLORS = $color_palette;", 'before' );
}
/**
* Retrieve asset dependencies.
*
* @return array
*/
private function get_asset() {
$asset = require $this->plugin->dir_path . 'js/gallery.asset.php'; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$asset['dependencies'] = array_filter(
$asset['dependencies'],
static function ( $dependency ) {
return false === strpos( $dependency, '/' );
}
);
return $asset;
}
/**
* Register blocked editor assets for the gallery.
*/
public function block_editor_scripts_styles() {
$this->enqueue_gallery_library();
wp_enqueue_style(
'cloudinary-gallery-block-css',
$this->plugin->dir_url . 'css/gallery-block.css',
array(),
$this->plugin->version
);
wp_enqueue_script(
'cloudinary-gallery-block-js',
$this->plugin->dir_url . 'js/gallery-block.js',
array( 'wp-blocks', 'wp-editor', 'wp-element', self::GALLERY_LIBRARY_HANDLE ),
$this->plugin->version,
true
);
wp_add_inline_script( 'cloudinary-gallery-block-js', 'var CLD_REST_ENDPOINT = "/' . REST_API::BASE . '";', 'before' );
}
/**
* Fetches image public id and transformations.
*
* @param array|int[]|array[] $images An array of image IDs or a multi-dimensional array with url and id keys.
*
* @return array
*/
public function get_image_data( array $images ) {
$image_data = array();
foreach ( $images as $index => $image ) {
$image_id = is_int( $image ) ? $image : $image['id'];
$transformations = null;
$data = array();
// Send back the attachment id.
$data['attachmentId'] = $image_id;
// Fetch the public id by either syncing NOW or getting the current public id.
if ( ! $this->media->has_public_id( $image_id ) ) {
$res = $this->media->sync->managers['upload']->upload_asset( $image_id );
if ( is_wp_error( $res ) ) {
// Skip and move on to the next image as this is unlikely to get synced.
continue;
}
}
// If synced now, the ID will be available in the meta.
$data['publicId'] = $this->media->get_public_id( $image_id, true );
$transformations = $this->media->get_transformations( $image_id );
if ( $transformations ) {
$data['transformation'] = array( 'transformation' => $transformations );
}
// Add to output array.
$image_data[] = $data;
}
return $image_data;
}
/**
* This rest endpoint handler will fetch the public_id and transformations off of a list of images.
*
* @param \WP_REST_Request $request The request.
*
* @return \WP_REST_Response|\WP_Error
*/
public function rest_cloudinary_image_data( \WP_REST_Request $request ) {
$request_body = json_decode( $request->get_body(), true );
if ( empty( $request_body['images'] ) ) {
return new \WP_Error( 400, 'The "images" key must be present in the request body.' );
}
$image_data = $this->get_image_data( $request_body['images'] );
return new \WP_REST_Response( $image_data );
}
/**
* Add endpoints to the \Cloudinary\REST_API::$endpoints array.
*
* @param array $endpoints Endpoints from the filter.
*
* @return array
*/
public function rest_endpoints( $endpoints ) {
$endpoints['image_data'] = array(
'method' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'rest_cloudinary_image_data' ),
'args' => array(),
'permission_callback' => function () {
return Utils::user_can( 'add_gallery', 'edit_posts' );
},
);
return $endpoints;
}
/**
* Add settings to pages.
*
* @param array $pages The pages to add to.
*
* @return array
*/
public function register_settings( $pages ) {
$pages['gallery'] = array(
'page_title' => __( 'Gallery settings', 'cloudinary' ),
'menu_title' => __( 'Gallery settings', 'cloudinary' ),
'priority' => 5,
'requires_connection' => true,
'sidebar' => true,
);
$panel = array(
'type' => 'panel',
'title' => __( 'Gallery Settings', 'cloudinary' ),
'option_name' => 'gallery',
);
if ( WooCommerceGallery::woocommerce_active() ) {
$panel[] = array(
'type' => 'group',
array(
'type' => 'on_off',
'slug' => 'gallery_woocommerce_enabled',
'title' => __( 'Replace WooCommerce Gallery', 'cloudinary' ),
'tooltip_text' => __( 'Replace the default WooCommerce gallery with the Cloudinary Gallery on product pages.', 'cloudinary' ),
),
);
} else {
$panel[] = array(
array(
'type' => 'tag',
'element' => 'h3',
'content' => __( 'Cloudinary Gallery block defaults', 'cloudinary' ),
),
array(
'content' => __(
'The Cloudinary Gallery is available as a new block type which can be inserted to any post or page. Note, this is not available when using the classic editor.',
'cloudinary'
),
),
);
}
$panel[] = array(
'type' => 'react',
'slug' => 'gallery_config',
'script' => array(
'slug' => 'gallery-widget',
'src' => $this->plugin->dir_url . 'js/gallery.js',
),
);
$panel[] = array(
'type' => 'info_box',
'icon' => $this->plugin->dir_url . 'css/images/academy-icon.svg',
'title' => __( 'Need help?', 'cloudinary' ),
'text' => sprintf(
// Translators: The HTML for opening and closing link tags.
__(
'Watch free lessons on how to use the Gallery Settings in the %1$sCloudinary Academy%2$s.',
'cloudinary'
),
'<a href="https://training.cloudinary.com/learn/course/introduction-to-cloudinary-for-wordpress-administrators-70-minute-course-1h85/lessons/using-cloudinary-for-product-galleries-in-woocommerce-and-page-content-906?page=1" target="_blank" rel="noopener noreferrer">',
'</a>'
),
);
$pages['gallery']['settings'] = array(
$panel,
);
return $pages;
}
/**
* Inits the cloudinary gallery using block attributes.
*
* @param string $content The post content.
* @param array $block Block data.
*
* @return string
*/
public function prepare_block_render( $content, $block ) {
if ( 'cloudinary/gallery' !== $block['blockName'] ) {
return $content;
}
// Ensure library is enqueued. Deals with archive pages that render the content.
$this->enqueue_gallery_library();
$attributes = Utils::expand_dot_notation( $block['attrs'], '_' );
$attributes = array_merge( self::$default_config, $attributes );
// Gallery without images. Don't render.
if ( empty( $attributes['selectedImages'] ) ) {
return $content;
}
$attributes['mediaAssets'] = array();
foreach ( $attributes['selectedImages'] as $attachment ) {
// Since the gallery widget uses public ID's, if an attachment is deleted, it can still remain working in a gallery.
if ( ! wp_get_attachment_url( $attachment['attachmentId'] ) ) {
continue;
}
$transformations = $this->media->get_transformations( $attachment['attachmentId'] );
if ( ! empty( $transformations ) ) {
$attachment['transformation'] = array( 'transformation' => $transformations );
}
$attributes['mediaAssets'][] = $attachment;
}
$attributes['cloudName'] = $this->plugin->components['connect']->get_cloud_name();
$credentials = $this->plugin->components['connect']->get_credentials();
if ( ! empty( $credentials['cname'] ) ) {
$attributes['secureDistribution'] = $credentials['cname'];
$attributes['privateCdn'] = true;
}
if ( ! empty( $attributes['customSettings'] ) ) {
if ( is_string( $attributes['customSettings'] ) && ! is_admin() ) {
$attributes['customSettings'] = json_decode( $attributes['customSettings'], true );
}
$attributes = wp_parse_args( $attributes['customSettings'], $attributes );
}
unset( $attributes['selectedImages'], $attributes['customSettings'] );
$attributes['queryParam'] = 'AA';
ob_start();
?>
<script>
window.addEventListener( 'load', function() {
if ( cloudinary && cloudinary.galleryWidget ) {
var attributes = <?php echo wp_json_encode( $attributes ); ?>;
attributes.container = '.' + attributes.container;
cloudinary.galleryWidget( attributes ).render();
}
}, false );
</script>
<?php
return $content . ob_get_clean();
}
/**
* Maybe decode the gallery configuration.
* This has historical reasons, as it was used to be stored as encoded information.
*
* @param array|string $config The configuration for the gallery.
*
* @return array
*/
protected function maybe_decode_config( $config ) {
if ( ! is_array( $config ) ) {
$config = json_decode( $config, true );
}
return $config;
}
/**
* Maybe enqueue gallery scripts.
*
* @return bool
*/
protected function maybe_enqueue_scripts() {
$can = false;
// Can if front end and have the block.
if (
function_exists( 'has_block' ) && has_block( 'cloudinary/gallery' )
&&
! is_admin()
) {
$can = true;
}
// Can on back end on block editor and gallery settings page.
if ( is_admin() && function_exists( 'get_current_screen' ) ) {
$screen = get_current_screen();
if (
! is_null( $screen )
&& (
'cloudinary_page_cloudinary_gallery' === $screen->id || ( method_exists( $screen, 'is_block_editor' ) && $screen->is_block_editor() )
)
) {
$can = true;
}
}
// Bail enqueuing the script several times.
if ( wp_script_is( self::GALLERY_LIBRARY_HANDLE ) ) {
$can = false;
}
/**
* Filter the enqueue of gallery script.
*
* @param bool $can Default value.
*
* @return bool
*/
$can = apply_filters( 'cloudinary_enqueue_gallery_script', $can );
return $can;
}
/**
* Setup hooks for the gallery.
*/
public function setup_hooks() {
add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'block_editor_scripts_styles' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_gallery_library' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
add_filter( 'render_block', array( $this, 'prepare_block_render' ), 10, 2 );
add_filter( 'cloudinary_admin_pages', array( $this, 'register_settings' ) );
}
}