$image[0], 'width' => $image[1], 'height' => $image[2], 'srcset' => wp_get_attachment_image_srcset($id, $size) ?: '', 'alt' => (string) get_post_meta($id, '_wp_attachment_image_alt', true), ]; } } $url = esc_url_raw((string) ($value['url'] ?? ''), ['http', 'https']); return $url ? ['url' => $url, 'width' => 0, 'height' => 0, 'srcset' => '', 'alt' => ''] : []; } function image(array $item, array $settings, bool $first): string { $size = option($settings['image_size'] ?? '', ['medium_large', 'large', 'full'], 'large'); $desktop = media($item['image'] ?? [], $size); $mobile = media($item['mobile_image'] ?? [], $size); if ( ! $desktop ) { $desktop = $mobile; } if ( ! $desktop ) { return ''; } $priority = $first && 'yes' === ($settings['hero_priority'] ?? ''); $prefix = $first ? '' : 'data-'; $alt = 'yes' === ($item['decorative'] ?? 'yes') ? '' : (trim((string) ($item['image_alt'] ?? '')) ?: $desktop['alt']); $breakpoint = 767.98; if ( class_exists('\\Elementor\\Plugin') && isset(\Elementor\Plugin::instance()->breakpoints) ) { $points = \Elementor\Plugin::instance()->breakpoints->get_active_breakpoints(); if ( isset($points['mobile']) ) { $breakpoint = (float) $points['mobile']->get_value() + 0.98; } } $html = ''; if ( $mobile && $mobile['url'] !== $desktop['url'] ) { $html .= ''; } $html .= '' . esc_attr($alt) . ''; } function link_attributes(array $link): string { $url = esc_url((string) ($link['url'] ?? '')); if ( ! $url ) { return ''; } $attrs = ' href="' . $url . '"'; $rel = []; if ( ! empty($link['is_external']) ) { $attrs .= ' target="_blank"'; $rel[] = 'noopener'; $rel[] = 'noreferrer'; } if ( ! empty($link['nofollow']) ) { $rel[] = 'nofollow'; } if ( $rel ) { $attrs .= ' rel="' . esc_attr(implode(' ', $rel)) . '"'; } return $attrs; } /** Delegate icons to Elementor; never enqueue an icon pack ourselves. */ function button_icon($icon): string { if (!is_array($icon) || !is_string($icon['library'] ?? null) || empty($icon['library'])) { return ''; } if ($icon['library']==='svg') { if (!is_array($icon['value'] ?? null) || !is_numeric($icon['value']['id'] ?? null) || get_post_mime_type(absint($icon['value']['id']))!=='image/svg+xml') { return ''; } } elseif (!is_string($icon['value'] ?? null) || trim($icon['value'])==='') { return ''; } ob_start(); \Elementor\Icons_Manager::render_icon($icon, ['aria-hidden'=>'true','focusable'=>'false']); $html = trim(ob_get_clean()); // Also covers uploaded SVGs whose native renderer does not forward attributes. return $html!=='' ? '' : ''; } /** * Return display-ready HTML: escape every dynamic value for its output context. * Rich text is allowlisted with wp_kses(); dynamic tags/options are allowlisted. * Icons use Elementor's native renderer (including its uploaded-SVG sanitizer). * Do not append unescaped settings to this return value at the output boundary. * Public HTML exists without JS; only slide rotation needs the frontend script. */ function render(array $settings, bool $editor = false): string { $items = $settings['slides'] ?? []; $items = is_array($items) ? array_values(array_filter($items, 'is_array')) : []; if ( ! $items ) { return $editor ? '

' . esc_html__('AGY Slider: Add a slide.', 'agy-slider') . '

' : ''; } $id = wp_unique_id('ags-'); $count = count($items); $navigation = option($settings['navigation'] ?? '', ['both', 'arrows', 'dots', 'none'], 'both'); $autoplay = ! $editor && 'yes' === ($settings['autoplay'] ?? '') && $count > 1; $config = [ 'autoplay' => $autoplay, 'interval' => number($settings['interval'] ?? null, 3000, 30000, 6000), 'duration' => number($settings['duration'] ?? null, 0, 1600, 450), 'loop' => 'yes' === ($settings['loop'] ?? 'yes'), 'effect' => option($settings['effect'] ?? '', ['fade', 'slide', 'scale'], 'fade'), 'swipe' => 'yes' === ($settings['swipe'] ?? 'yes'), 'editor' => $editor, 'playLabel' => __('Play slides automatically', 'agy-slider'), 'pauseLabel' => __('Pause autoplay', 'agy-slider'), 'reducedMotionLabel' => __('Reduced motion is enabled; autoplay is off.', 'agy-slider'), ]; $label = trim(wp_strip_all_tags((string) ($settings['accessible_label'] ?? ''))); if ( '' === $label ) { $label = __('Featured content', 'agy-slider'); } $html = '
'; // First in the keyboard order, per the accessible carousel pattern. if ( $autoplay ) { $html .= ''; } $html .= '
'; foreach ( $items as $i => $item ) { $active = 0 === $i; $item_id = sanitize_html_class((string) ($item['_id'] ?? ('item-' . $i))); $slide_id = $id . '-slide-' . ($i + 1); $title = (string) ($item['title'] ?? ''); $kicker = is_string($item['kicker'] ?? null) ? trim(wp_strip_all_tags($item['kicker'])) : ''; $tag = option($settings['heading_tag'] ?? '', ['h2', 'h3', 'h4', 'div'], 'h2'); if ( $active && 'yes' === ($settings['first_h1'] ?? '') ) { $tag = 'h1'; } $tag_html = wp_kses($title, ['br' => [], 'strong' => [], 'em' => []]); $description = wp_kses(wpautop((string) ($item['description'] ?? '')), ['p' => [], 'br' => [], 'strong' => [], 'em' => [], 'b' => [], 'i' => [], 'ul' => [], 'ol' => [], 'li' => []]); $link = is_array($item['link'] ?? null) ? $item['link'] : []; $attrs = link_attributes($link); $whole = 'slide' === ($item['link_on'] ?? '') && '' !== $attrs; $button = trim((string) ($item['button_text'] ?? '')); $ken = option($item['ken_burns'] ?? '', ['none', 'in', 'out'], 'none'); $animation = option($settings['content_animation'] ?? '', ['none', 'fade', 'up', 'down', 'left', 'right'], 'none'); /* translators: 1: Current slide number, 2: Total number of slides. */ $slide_label = sprintf(__('%1$d / %2$d', 'agy-slider'), $i + 1, $count); $html .= ''; } $html .= '
'; if ( $count > 1 ) { if ( in_array($navigation, ['both', 'arrows'], true) ) { foreach ( ['prev' => __('Previous slide', 'agy-slider'), 'next' => __('Next slide', 'agy-slider')] as $direction => $name ) { $path = 'prev' === $direction ? 'm15 5-7 7 7 7' : 'm9 5 7 7-7 7'; $html .= ''; } } if ( in_array($navigation, ['both', 'dots'], true) ) { $html .= ''; } $html .= '
'; } return $html . '
'; }