Files
Instagram-Gallery-Sync-Pro/includes/class-shortcode.php

533 lines
19 KiB
PHP

<?php
/**
* Shortcode Handler Class
*
* Handles the [instagram_gallery] shortcode rendering.
*
* @package Instagram_Gallery_Sync_Pro
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Class IGSP_Shortcode
*/
class IGSP_Shortcode
{
/**
* Default shortcode attributes
*
* @var array
*/
private $defaults;
/**
* Initialize shortcode
*
* @return void
*/
public function init()
{
add_shortcode('instagram_gallery', array($this, 'render'));
add_action('wp_enqueue_scripts', array($this, 'register_assets'));
}
/**
* Get default attributes
*
* @return array
*/
private function get_defaults()
{
if (!$this->defaults) {
$this->defaults = array(
'layout' => get_option('igsp_layout_type', 'grid'),
'columns' => get_option('igsp_columns_desktop', 3),
'spacing' => get_option('igsp_spacing', 10),
'limit' => get_option('igsp_display_limit', 12),
'order' => get_option('igsp_order', 'newest'),
'lightbox' => get_option('igsp_lightbox', 'yes'),
'captions' => get_option('igsp_show_caption', 'no'),
'autoplay' => 'false',
'class' => '',
);
}
return $this->defaults;
}
/**
* Register frontend assets
*
* @return void
*/
public function register_assets()
{
$prefix = get_option('igsp_css_prefix', 'igsp');
// Base styles
wp_register_style(
'igsp-gallery-base',
IGSP_PLUGIN_URL . 'public/css/gallery-base.css',
array(),
IGSP_VERSION
);
// Layout-specific styles
wp_register_style(
'igsp-gallery-masonry',
IGSP_PLUGIN_URL . 'public/css/gallery-masonry.css',
array('igsp-gallery-base'),
IGSP_VERSION
);
wp_register_style(
'igsp-gallery-grid',
IGSP_PLUGIN_URL . 'public/css/gallery-grid.css',
array('igsp-gallery-base'),
IGSP_VERSION
);
wp_register_style(
'igsp-gallery-slider',
IGSP_PLUGIN_URL . 'public/css/gallery-slider.css',
array('igsp-gallery-base'),
IGSP_VERSION
);
// Lightbox
wp_register_style(
'igsp-lightbox',
IGSP_PLUGIN_URL . 'public/css/lightbox.css',
array(),
IGSP_VERSION
);
// Scripts
wp_register_script(
'igsp-masonry',
IGSP_PLUGIN_URL . 'public/js/masonry.pkgd.min.js',
array(),
'4.2.2',
true
);
wp_register_script(
'igsp-imagesloaded',
IGSP_PLUGIN_URL . 'public/js/imagesloaded.pkgd.min.js',
array(),
'5.0.0',
true
);
wp_register_script(
'igsp-glightbox',
IGSP_PLUGIN_URL . 'public/js/glightbox.min.js',
array(),
'3.2.0',
true
);
wp_register_script(
'igsp-swiper',
IGSP_PLUGIN_URL . 'public/js/swiper.min.js',
array(),
'11.0.5',
true
);
wp_register_script(
'igsp-gallery-frontend',
IGSP_PLUGIN_URL . 'public/js/gallery-frontend.js',
array('jquery'),
IGSP_VERSION,
true
);
}
/**
* Render shortcode
*
* @param array $atts Shortcode attributes
* @return string
*/
public function render($atts = array())
{
$atts = shortcode_atts($this->get_defaults(), $atts, 'instagram_gallery');
// Sanitize attributes
$atts = $this->sanitize_atts($atts);
// Check cache
$cache_key = 'igsp_gallery_' . md5(serialize($atts));
$cache_duration = (int) get_option('igsp_cache_duration', 3600);
$cached = get_transient($cache_key);
if ($cached !== false && !is_admin()) {
$this->enqueue_assets($atts);
return $cached;
}
// Get posts from database
$database = new IGSP_Database();
$posts = $database->get_posts(array(
'limit' => $atts['limit'],
'order' => $atts['order'],
));
if (empty($posts)) {
return $this->render_empty_state();
}
// Enqueue assets
$this->enqueue_assets($atts);
// Render gallery
$output = $this->render_gallery($posts, $atts);
// Cache output
set_transient($cache_key, $output, $cache_duration);
return $output;
}
/**
* Sanitize attributes
*
* @param array $atts Attributes
* @return array
*/
private function sanitize_atts($atts)
{
$atts['layout'] = sanitize_key($atts['layout']);
$atts['columns'] = absint($atts['columns']);
$atts['spacing'] = absint($atts['spacing']);
$atts['limit'] = absint($atts['limit']);
$atts['order'] = sanitize_key($atts['order']);
$atts['lightbox'] = in_array($atts['lightbox'], array('yes', 'true', '1'), true) ? true : false;
$atts['captions'] = in_array($atts['captions'], array('yes', 'true', '1'), true) ? true : false;
$atts['autoplay'] = in_array($atts['autoplay'], array('yes', 'true', '1'), true) ? true : false;
$atts['class'] = sanitize_html_class($atts['class']);
// Clamp values
$atts['columns'] = max(1, min(6, $atts['columns']));
$atts['spacing'] = max(0, min(50, $atts['spacing']));
$atts['limit'] = max(1, min(100, $atts['limit']));
return $atts;
}
/**
* Enqueue required assets
*
* @param array $atts Attributes
* @return void
*/
private function enqueue_assets($atts)
{
// Base CSS
wp_enqueue_style('igsp-gallery-base');
// Layout-specific CSS
switch ($atts['layout']) {
case 'masonry':
wp_enqueue_style('igsp-gallery-masonry');
wp_enqueue_script('igsp-masonry');
wp_enqueue_script('igsp-imagesloaded');
break;
case 'slider':
wp_enqueue_style('igsp-gallery-slider');
wp_enqueue_script('igsp-swiper');
break;
default:
wp_enqueue_style('igsp-gallery-grid');
}
// Lightbox
if ($atts['lightbox']) {
wp_enqueue_style('igsp-lightbox');
wp_enqueue_script('igsp-glightbox');
}
// Frontend JS
wp_enqueue_script('igsp-gallery-frontend');
// Pass settings to JS
wp_localize_script('igsp-gallery-frontend', 'igspFrontend', array(
'lightbox' => $atts['lightbox'],
'layout' => $atts['layout'],
'autoplay' => $atts['autoplay'],
));
// Add inline custom CSS
$this->add_custom_styles();
}
/**
* Add custom inline styles
*
* @return void
*/
private function add_custom_styles()
{
$primary = get_option('igsp_primary_color', '#e1306c');
$hover = get_option('igsp_hover_color', '#c13584');
$text = get_option('igsp_text_color', '#ffffff');
$font_size = get_option('igsp_caption_font_size', 14);
$border_radius = get_option('igsp_border_radius', 0);
$custom_css = get_option('igsp_custom_css', '');
$css = "
:root {
--igsp-primary: {$primary};
--igsp-hover: {$hover};
--igsp-text: {$text};
--igsp-font-size: {$font_size}px;
--igsp-radius: {$border_radius}px;
}
";
if (!empty($custom_css)) {
$css .= "\n" . $custom_css;
}
wp_add_inline_style('igsp-gallery-base', $css);
}
/**
* Render gallery HTML
*
* @param array $posts Posts
* @param array $atts Attributes
* @return string
*/
private function render_gallery($posts, $atts)
{
$prefix = get_option('igsp_css_prefix', 'igsp');
$hover_effect = get_option('igsp_hover_effect', 'zoom');
$aspect_ratio = get_option('igsp_aspect_ratio', 'square');
$object_fit = get_option('igsp_object_fit', 'cover');
$caption_position = get_option('igsp_caption_position', 'overlay');
$link_behavior = get_option('igsp_link_behavior', 'new_tab');
$lazy_loading = get_option('igsp_lazy_loading', 'yes') === 'yes';
$columns_tablet = get_option('igsp_columns_tablet', 2);
$columns_mobile = get_option('igsp_columns_mobile', 1);
$wrapper_class = array(
$prefix . '-gallery',
$prefix . '-' . $atts['layout'],
$prefix . '-hover-' . $hover_effect,
$prefix . '-aspect-' . $aspect_ratio,
);
if (!empty($atts['class'])) {
$wrapper_class[] = $atts['class'];
}
// Build gallery container
ob_start();
?>
<div class="<?php echo esc_attr(implode(' ', $wrapper_class)); ?>"
data-columns="<?php echo esc_attr($atts['columns']); ?>"
data-columns-tablet="<?php echo esc_attr($columns_tablet); ?>"
data-columns-mobile="<?php echo esc_attr($columns_mobile); ?>"
data-spacing="<?php echo esc_attr($atts['spacing']); ?>" data-layout="<?php echo esc_attr($atts['layout']); ?>"
style="--columns: <?php echo esc_attr($atts['columns']); ?>; --spacing: <?php echo esc_attr($atts['spacing']); ?>px;">
<?php if ($atts['layout'] === 'slider'): ?>
<div class="swiper">
<div class="swiper-wrapper">
<?php endif; ?>
<?php foreach ($posts as $post): ?>
<?php echo $this->render_item($post, $atts, $lazy_loading, $caption_position, $link_behavior); ?>
<?php endforeach; ?>
<?php if ($atts['layout'] === 'slider'): ?>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<?php endif; ?>
</div>
<?php if (get_option('igsp_show_instagram_btn', 'yes') === 'yes'): ?>
<?php echo $this->render_instagram_button(); ?>
<?php endif; ?>
<?php
return ob_get_clean();
}
/**
* Render single gallery item
*
* @param object $post Post object
* @param array $atts Attributes
* @param bool $lazy_loading Use lazy loading
* @param string $caption_position Caption position
* @param string $link_behavior Link behavior
* @return string
*/
private function render_item($post, $atts, $lazy_loading, $caption_position, $link_behavior)
{
$prefix = get_option('igsp_css_prefix', 'igsp');
$image_handler = new IGSP_Image_Handler();
// Get image URL
$image_url = '';
if (!empty($post->image_local_path)) {
$image_url = $image_handler->get_image_url($post->image_local_path);
}
$thumb_url = '';
if (!empty($post->image_thumbnail_path)) {
$thumb_url = $image_handler->get_image_url($post->image_thumbnail_path);
}
// Fallback to main image if no thumbnail
$display_url = !empty($thumb_url) ? $thumb_url : $image_url;
$full_url = $image_url;
if (empty($display_url)) {
return '';
}
// Determine link
$link_url = '';
$link_target = '';
$lightbox_attr = '';
if ($link_behavior === 'lightbox' && $atts['lightbox']) {
$link_url = $full_url;
$lightbox_attr = 'data-gallery="igsp-lightbox"';
} elseif ($link_behavior === 'new_tab') {
$link_url = $post->post_url;
$link_target = 'target="_blank" rel="noopener noreferrer"';
} elseif ($link_behavior === 'same_window') {
$link_url = $post->post_url;
}
// Caption
$caption = '';
if ($atts['captions'] && !empty($post->caption)) {
$caption = wp_trim_words($post->caption, 20);
}
// Build class based on layout
$item_class = $prefix . '-item';
if ($atts['layout'] === 'slider') {
$item_class = 'swiper-slide ' . $item_class;
}
ob_start();
?>
<div class="<?php echo esc_attr($item_class); ?>">
<?php if (!empty($link_url)): ?>
<a href="<?php echo esc_url($link_url); ?>" class="<?php echo esc_attr($prefix . '-link'); ?>" <?php echo $link_target; ?>
<?php echo $lightbox_attr; ?>
title="
<?php echo esc_attr($caption); ?>">
<?php endif; ?>
<div class="<?php echo esc_attr($prefix . '-image-wrapper'); ?>">
<img src="<?php echo $lazy_loading ? '' : esc_url($display_url); ?>" <?php echo $lazy_loading ? 'data-src="' . esc_url($display_url) . '"' : ''; ?>
class="
<?php echo esc_attr($prefix . '-image'); ?>
<?php echo $lazy_loading ? 'lazyload' : ''; ?>"
alt="
<?php echo esc_attr($caption); ?>"
<?php echo $lazy_loading ? 'loading="lazy"' : ''; ?>>
<?php if ($caption_position === 'overlay' && $atts['captions'] && !empty($caption)): ?>
<div class="<?php echo esc_attr($prefix . '-overlay'); ?>">
<span class="<?php echo esc_attr($prefix . '-caption'); ?>">
<?php echo esc_html($caption); ?>
</span>
</div>
<?php endif; ?>
<div class="<?php echo esc_attr($prefix . '-hover-overlay'); ?>">
<span class="<?php echo esc_attr($prefix . '-icon'); ?>">
<svg viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm0 7.082c1.602 0 1.792.006 2.425.035.598.028.922.127 1.138.211.286.111.491.244.705.458s.347.419.458.705c.084.216.183.54.211 1.138.029.633.035.823.035 2.425s-.006 1.792-.035 2.425c-.028.598-.127.922-.211 1.138-.111.286-.244.491-.458.705s-.419.347-.705.458c-.216.084-.54.183-1.138.211-.633.029-.823.035-2.425.035s-1.792-.006-2.425-.035c-.598-.028-.922-.127-1.138-.211-.286-.111-.491-.244-.705-.458s-.347-.419-.458-.705c-.084-.216-.183-.54-.211-1.138-.029-.633-.035-.823-.035-2.425s.006-1.792.035-2.425c.028-.598.127-.922.211-1.138.111-.286.244-.491.458-.705s.419-.347.705-.458c.216-.084.54-.183 1.138-.211.633-.029.823-.035 2.425-.035zm0-1.082c-1.63 0-1.833.007-2.474.036-.639.029-1.076.131-1.459.28-.396.154-.731.359-1.066.693s-.539.67-.693 1.066c-.149.383-.251.82-.28 1.459-.029.641-.036.844-.036 2.474s.007 1.833.036 2.474c.029.639.131 1.076.28 1.459.154.396.359.731.693 1.066s.67.539 1.066.693c.383.149.82.251 1.459.28.641.029.844.036 2.474.036s1.833-.007 2.474-.036c.639-.029 1.076-.131 1.459-.28.396-.154.731-.359 1.066-.693s.539-.67.693-1.066c.149-.383.251-.82.28-1.459.029-.641.036-.844.036-2.474s-.007-1.833-.036-2.474c-.029-.639-.131-1.076-.28-1.459-.154-.396-.359-.731-.693-1.066s-.67-.539-1.066-.693c-.383-.149-.82-.251-1.459-.28-.641-.029-.844-.036-2.474-.036zm0 2.919c-1.701 0-3.081 1.379-3.081 3.081s1.379 3.081 3.081 3.081 3.081-1.379 3.081-3.081-1.379-3.081-3.081-3.081zm0 5.081c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2zm3.202-5.922c-.398 0-.72.322-.72.72s.322.72.72.72.72-.322.72-.72-.322-.72-.72-.72z" />
</svg>
</span>
</div>
</div>
<?php if (!empty($link_url)): ?>
</a>
<?php endif; ?>
<?php if ($caption_position === 'below' && $atts['captions'] && !empty($caption)): ?>
<div class="<?php echo esc_attr($prefix . '-caption-below'); ?>">
<?php echo esc_html($caption); ?>
</div>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
/**
* Render Instagram button
*
* @return string
*/
private function render_instagram_button()
{
$prefix = get_option('igsp_css_prefix', 'igsp');
$username = get_option('igsp_username', '');
if (empty($username)) {
return '';
}
ob_start();
?>
<div class="<?php echo esc_attr($prefix . '-follow-btn-wrapper'); ?>">
<a href="https://www.instagram.com/<?php echo esc_attr($username); ?>/" target="_blank" rel="noopener noreferrer"
class="<?php echo esc_attr($prefix . '-follow-btn'); ?>">
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
<path
d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
</svg>
<?php esc_html_e('Follow on Instagram', 'instagram-gallery-sync-pro'); ?>
</a>
</div>
<?php
return ob_get_clean();
}
/**
* Render empty state
*
* @return string
*/
private function render_empty_state()
{
$prefix = get_option('igsp_css_prefix', 'igsp');
ob_start();
?>
<div class="<?php echo esc_attr($prefix . '-empty'); ?>">
<p>
<?php esc_html_e('No Instagram posts found. Please sync your Instagram account in the admin settings.', 'instagram-gallery-sync-pro'); ?>
</p>
</div>
<?php
return ob_get_clean();
}
}