start_controls_section( 'section_images', array( 'label' => __( 'Images', 'a1-tools' ) ) ); $repeater = new \Elementor\Repeater(); $repeater->add_control( 'image', array( 'label' => __( 'Image', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::MEDIA, 'default' => array( 'url' => \Elementor\Utils::get_placeholder_image_src() ), ) ); $repeater->add_control( 'category', array( 'label' => __( 'Category', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'label_block' => true, 'placeholder' => __( 'e.g. Chimney Repair', 'a1-tools' ), 'description' => __( 'Used for the filter dropdown. Reuse the same text to group images.', 'a1-tools' ), 'dynamic' => array( 'active' => true ), ) ); $repeater->add_control( 'before_image', array( 'label' => __( 'Before Image (optional)', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::MEDIA, 'description' => __( 'If set, this item shows as a Before / After split (this on the left, Image on the right).', 'a1-tools' ), ) ); $repeater->add_control( 'before_label', array( 'label' => __( 'Before Label', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => __( 'Before', 'a1-tools' ), 'condition' => array( 'before_image[url]!' => '' ), ) ); $repeater->add_control( 'after_label', array( 'label' => __( 'After Label', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => __( 'After', 'a1-tools' ), 'condition' => array( 'before_image[url]!' => '' ), ) ); $this->add_control( 'images', array( 'label' => __( 'Images', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::REPEATER, 'fields' => $repeater->get_controls(), 'title_field' => '{{{ category || "Image" }}}', ) ); $this->end_controls_section(); // ── Content : Bulk Add by Category ────────────────────────── $this->start_controls_section( 'section_bulk', array( 'label' => __( 'Bulk Add by Category', 'a1-tools' ) ) ); $this->add_control( 'bulk_note', array( 'type' => \Elementor\Controls_Manager::RAW_HTML, 'raw' => __( 'Add a row per category, then drop in many images at once for that category. These appear in the same grid as the Images above.', 'a1-tools' ), 'content_classes' => 'elementor-descriptor', ) ); $bulk = new \Elementor\Repeater(); $bulk->add_control( 'category', array( 'label' => __( 'Category', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'label_block' => true, 'placeholder' => __( 'e.g. Cleaning', 'a1-tools' ), ) ); $bulk->add_control( 'gallery', array( 'label' => __( 'Images (select many)', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::GALLERY, 'default' => array(), ) ); $this->add_control( 'bulk_groups', array( 'label' => __( 'Category Groups', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::REPEATER, 'fields' => $bulk->get_controls(), 'title_field' => '{{{ category || "Group" }}}', ) ); $this->end_controls_section(); // ── Content : Our Work Landing Gallery ────────────────────── $this->start_controls_section( 'section_our_work', array( 'label' => __( 'Our Work Landing Gallery', 'a1-tools' ) ) ); $this->add_control( 'work_gallery', array( 'label' => __( 'Our Work Images', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::GALLERY, 'default' => array(), 'description' => __( 'Shown first without search or filters. Existing project images remain unchanged.', 'a1-tools' ), ) ); $this->add_control( 'work_per_page', array( 'label' => __( 'Our Work Images Per Page', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::NUMBER, 'min' => 1, 'max' => 60, 'default' => 9, ) ); // #142 — responsive Our Work image height (control targets the work-item // selector; the CSS default of 360px is the fallback when unset). $this->add_responsive_control( 'work_image_height', array( 'label' => __( 'Our Work Image Height', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => array( 'px' ), 'range' => array( 'px' => array( 'min' => 160, 'max' => 800 ) ), 'default' => array( 'size' => 360, 'unit' => 'px' ), 'tablet_default' => array( 'size' => 300, 'unit' => 'px' ), 'mobile_default' => array( 'size' => 240, 'unit' => 'px' ), 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-work-item' => 'height: {{SIZE}}{{UNIT}};', ), ) ); $this->add_control( 'projects_button_label', array( 'label' => __( 'Projects Button Label', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => __( 'View Our Projects', 'a1-tools' ), ) ); $this->add_control( 'back_button_label', array( 'label' => __( 'Back Button Label', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => __( 'Back to Our Work', 'a1-tools' ), ) ); $this->end_controls_section(); // ── Content : Layout ──────────────────────────────────────── $this->start_controls_section( 'section_layout', array( 'label' => __( 'Layout', 'a1-tools' ) ) ); $this->add_responsive_control( 'columns', array( 'label' => __( 'Columns', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::NUMBER, 'min' => 1, 'max' => 6, 'default' => 3, 'tablet_default' => 2, 'mobile_default' => 1, 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-grid' => 'grid-template-columns: repeat({{VALUE}}, minmax(0, 1fr));', ), ) ); $this->add_control( 'per_page', array( 'label' => __( 'Images Per Page', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::NUMBER, 'min' => 1, 'max' => 60, 'default' => 9, ) ); $this->add_control( 'image_ratio', array( 'label' => __( 'Image Ratio', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => '4-3', 'options' => array( 'original' => __( 'Original', 'a1-tools' ), '1-1' => __( '1:1 (Square)', 'a1-tools' ), '4-3' => __( '4:3', 'a1-tools' ), '3-2' => __( '3:2', 'a1-tools' ), '16-9' => __( '16:9', 'a1-tools' ), ), ) ); $this->add_responsive_control( 'gap', array( 'label' => __( 'Gap', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => array( 'px' ), 'range' => array( 'px' => array( 'min' => 0, 'max' => 60 ) ), 'default' => array( 'size' => 16, 'unit' => 'px' ), 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-grid' => 'gap: {{SIZE}}{{UNIT}};' ), ) ); $this->add_control( 'hover_effect', array( 'label' => __( 'Image Hover Effect', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SELECT, 'default' => 'zoom', 'options' => array( 'none' => __( 'None', 'a1-tools' ), 'zoom' => __( 'Zoom', 'a1-tools' ), 'overlay' => __( 'Zoom + Dim', 'a1-tools' ), ), ) ); $this->add_control( 'enable_lightbox', array( 'label' => __( 'Click to Enlarge (Lightbox)', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', ) ); $this->add_control( 'highlight_category', array( 'label' => __( 'Highlight Same Category on Hover', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'description' => __( 'Hovering an image outlines every image that shares its category.', 'a1-tools' ), ) ); $this->add_control( 'show_category_badge', array( 'label' => __( 'Show Category Tag on Images', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '', ) ); $this->end_controls_section(); // ── Content : Search & Filter ─────────────────────────────── $this->start_controls_section( 'section_controls', array( 'label' => __( 'Search & Filter', 'a1-tools' ) ) ); $this->add_control( 'show_search', array( 'label' => __( 'Search Bar', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', ) ); $this->add_control( 'search_placeholder', array( 'label' => __( 'Search Placeholder', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => __( 'Search…', 'a1-tools' ), 'condition' => array( 'show_search' => 'yes' ), ) ); $this->add_control( 'show_filter', array( 'label' => __( 'Filter Dropdown', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', ) ); $this->add_control( 'filter_all_label', array( 'label' => __( 'All-Categories Label', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => __( 'All Categories', 'a1-tools' ), 'condition' => array( 'show_filter' => 'yes' ), ) ); $this->add_control( 'show_pagination', array( 'label' => __( 'Pagination', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', ) ); $this->add_control( 'empty_text', array( 'label' => __( 'No-Results Text', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::TEXT, 'default' => __( 'No images found.', 'a1-tools' ), ) ); $this->end_controls_section(); // ── Style : Search Bar ────────────────────────────────────── $this->register_bar_style_controls( 'search', __( 'Search Bar', 'a1-tools' ), '{{WRAPPER}} .a1tools-gallery-search', array( 'show_search' => 'yes' ) ); // ── Style : Filter Bar ────────────────────────────────────── $this->register_bar_style_controls( 'filter', __( 'Filter Bar', 'a1-tools' ), '{{WRAPPER}} .a1tools-gallery-filter', array( 'show_filter' => 'yes' ) ); // ── Style : Images ────────────────────────────────────────── $this->start_controls_section( 'style_items', array( 'label' => __( 'Images', 'a1-tools' ), 'tab' => \Elementor\Controls_Manager::TAB_STYLE ) ); $this->add_control( 'item_radius', array( 'label' => __( 'Corner Radius', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => array( 'px' ), 'range' => array( 'px' => array( 'min' => 0, 'max' => 40 ) ), 'default' => array( 'size' => 8, 'unit' => 'px' ), 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-item' => 'border-radius: {{SIZE}}{{UNIT}};' ), ) ); $this->add_group_control( \Elementor\Group_Control_Border::get_type(), array( 'name' => 'item_border', 'selector' => '{{WRAPPER}} .a1tools-gallery-item' ) ); $this->add_group_control( \Elementor\Group_Control_Box_Shadow::get_type(), array( 'name' => 'item_shadow', 'selector' => '{{WRAPPER}} .a1tools-gallery-item' ) ); $this->add_control( 'highlight_color', array( 'label' => __( 'Category Highlight Color', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'default' => '#F49321', 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-item.a1g-hl' => 'box-shadow: 0 0 0 3px {{VALUE}}; z-index: 2;', ), 'condition' => array( 'highlight_category' => 'yes' ), ) ); $this->end_controls_section(); // ── Style : Before / After Labels ─────────────────────────── $this->start_controls_section( 'style_ba', array( 'label' => __( 'Before / After Labels', 'a1-tools' ), 'tab' => \Elementor\Controls_Manager::TAB_STYLE ) ); $this->add_group_control( \Elementor\Group_Control_Typography::get_type(), array( 'name' => 'ba_typography', 'selector' => '{{WRAPPER}} .a1tools-gallery-ba-label' ) ); $this->add_control( 'ba_color', array( 'label' => __( 'Text Color', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-ba-label' => 'color: {{VALUE}};' ), ) ); $this->add_control( 'ba_bg', array( 'label' => __( 'Background', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'default' => 'rgba(0,0,0,0.6)', 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-ba-label' => 'background-color: {{VALUE}};' ), ) ); $this->end_controls_section(); // ── Style : Pagination ────────────────────────────────────── $this->start_controls_section( 'style_pagination', array( 'label' => __( 'Pagination', 'a1-tools' ), 'tab' => \Elementor\Controls_Manager::TAB_STYLE, 'condition' => array( 'show_pagination' => 'yes' ), ) ); $this->add_control( 'page_color', array( 'label' => __( 'Text Color', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-page' => 'color: {{VALUE}};' ), ) ); $this->add_control( 'page_bg', array( 'label' => __( 'Background', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-page' => 'background-color: {{VALUE}};' ), ) ); $this->add_control( 'page_active_color', array( 'label' => __( 'Active Text', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-page.is-active' => 'color: {{VALUE}};' ), ) ); $this->add_control( 'page_active_bg', array( 'label' => __( 'Active Background', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'default' => '#F49321', 'selectors' => array( '{{WRAPPER}} .a1tools-gallery-page.is-active' => 'background-color: {{VALUE}}; border-color: {{VALUE}};' ), ) ); $this->end_controls_section(); } /** * Register a reusable style section for a bar element (search / filter): * responsive width, typography, radius, and Normal / Hover / Focus states. * * @param string $prefix Control-name prefix (search|filter). * @param string $label Section label. * @param string $selector Base CSS selector for the element. * @param array $condition Section condition. */ private function register_bar_style_controls( $prefix, $label, $selector, $condition ) { $this->start_controls_section( 'style_' . $prefix, array( 'label' => $label, 'tab' => \Elementor\Controls_Manager::TAB_STYLE, 'condition' => $condition, ) ); $this->add_responsive_control( $prefix . '_width', array( 'label' => __( 'Width', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => array( '%', 'px' ), 'range' => array( '%' => array( 'min' => 10, 'max' => 100 ), 'px' => array( 'min' => 80, 'max' => 800 ), ), 'default' => 'search' === $prefix ? array( 'size' => 40, 'unit' => '%' ) : array( 'size' => 200, 'unit' => 'px' ), 'mobile_default' => array( 'size' => 100, 'unit' => '%' ), 'selectors' => array( $selector => 'width: {{SIZE}}{{UNIT}}; flex: 0 0 auto; max-width: 100%;', ), ) ); $this->add_group_control( \Elementor\Group_Control_Typography::get_type(), array( 'name' => $prefix . '_typography', 'selector' => $selector ) ); $this->add_control( $prefix . '_radius', array( 'label' => __( 'Border Radius', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::SLIDER, 'size_units' => array( 'px' ), 'range' => array( 'px' => array( 'min' => 0, 'max' => 40 ) ), 'default' => array( 'size' => 6, 'unit' => 'px' ), 'selectors' => array( $selector => 'border-radius: {{SIZE}}{{UNIT}};' ), ) ); $this->add_control( $prefix . '_padding', array( 'label' => __( 'Padding', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::DIMENSIONS, 'size_units' => array( 'px', 'em' ), 'selectors' => array( $selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};' ), ) ); $this->start_controls_tabs( $prefix . '_state_tabs' ); $states = array( 'normal' => array( 'label' => __( 'Normal', 'a1-tools' ), 'sel' => $selector ), 'hover' => array( 'label' => __( 'Hover', 'a1-tools' ), 'sel' => $selector . ':hover' ), 'focus' => array( 'label' => __( 'Active / Focus', 'a1-tools' ), 'sel' => $selector . ':focus' ), ); foreach ( $states as $state => $info ) { $this->start_controls_tab( $prefix . '_tab_' . $state, array( 'label' => $info['label'] ) ); $this->add_control( $prefix . '_' . $state . '_text', array( 'label' => __( 'Text Color', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => array( $info['sel'] => 'color: {{VALUE}};' ), ) ); $this->add_control( $prefix . '_' . $state . '_bg', array( 'label' => __( 'Background', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => array( $info['sel'] => 'background-color: {{VALUE}};' ), ) ); $this->add_control( $prefix . '_' . $state . '_border', array( 'label' => __( 'Border Color', 'a1-tools' ), 'type' => \Elementor\Controls_Manager::COLOR, 'selectors' => array( $info['sel'] => 'border-color: {{VALUE}};' ), ) ); $this->end_controls_tab(); } $this->end_controls_tabs(); $this->end_controls_section(); } /** * Normalize a category to a comparison key (lowercase, trimmed). */ private function cat_key( $cat ) { return strtolower( trim( (string) $cat ) ); } /** * Render widget output on the frontend. */ protected function render() { $settings = $this->get_settings_for_display(); $work_images = isset( $settings['work_gallery'] ) && is_array( $settings['work_gallery'] ) ? array_values( array_filter( $settings['work_gallery'], function ( $image ) { return ! empty( $image['url'] ); } ) ) : array(); $items = array(); $categories = array(); $add_cat = function ( $cat ) use ( &$categories ) { $key = $this->cat_key( $cat ); if ( '' !== $key && ! isset( $categories[ $key ] ) ) { $categories[ $key ] = trim( (string) $cat ); } return $key; }; // Per-image repeater. $images = isset( $settings['images'] ) && is_array( $settings['images'] ) ? $settings['images'] : array(); foreach ( $images as $img ) { $url = ! empty( $img['image']['url'] ) ? $img['image']['url'] : ''; if ( '' === $url ) { continue; } $cat = isset( $img['category'] ) ? (string) $img['category'] : ''; $items[] = array( 'url' => $url, 'before_url' => ! empty( $img['before_image']['url'] ) ? $img['before_image']['url'] : '', 'category' => trim( $cat ), 'cat_key' => $add_cat( $cat ), 'before_label' => isset( $img['before_label'] ) && '' !== $img['before_label'] ? $img['before_label'] : __( 'Before', 'a1-tools' ), 'after_label' => isset( $img['after_label'] ) && '' !== $img['after_label'] ? $img['after_label'] : __( 'After', 'a1-tools' ), ); } // Bulk category groups. $groups = isset( $settings['bulk_groups'] ) && is_array( $settings['bulk_groups'] ) ? $settings['bulk_groups'] : array(); foreach ( $groups as $g ) { $gcat = isset( $g['category'] ) ? (string) $g['category'] : ''; $gkey = $add_cat( $gcat ); $gal = isset( $g['gallery'] ) && is_array( $g['gallery'] ) ? $g['gallery'] : array(); foreach ( $gal as $gi ) { $gurl = ! empty( $gi['url'] ) ? $gi['url'] : ''; if ( '' === $gurl ) { continue; } $items[] = array( 'url' => $gurl, 'before_url' => '', 'category' => trim( $gcat ), 'cat_key' => $gkey, 'before_label' => '', 'after_label' => '', ); } } if ( empty( $items ) && empty( $work_images ) ) { if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) { echo '
' . esc_html__( 'Add images (Images or Bulk Add by Category) to display the gallery.', 'a1-tools' ) . '
'; } return; } $show_search = 'yes' === ( $settings['show_search'] ?? 'yes' ); $show_filter = 'yes' === ( $settings['show_filter'] ?? 'yes' ); $show_pagination = 'yes' === ( $settings['show_pagination'] ?? 'yes' ); $show_badge = 'yes' === ( $settings['show_category_badge'] ?? '' ); $do_highlight = 'yes' === ( $settings['highlight_category'] ?? 'yes' ); $do_lightbox = 'yes' === ( $settings['enable_lightbox'] ?? 'yes' ); $hover_effect = $settings['hover_effect'] ?? 'zoom'; $per_page = max( 1, absint( $settings['per_page'] ?? 9 ) ); $ratio = $settings['image_ratio'] ?? '4-3'; $search_ph = $settings['search_placeholder'] ?? __( 'Search…', 'a1-tools' ); $all_label = $settings['filter_all_label'] ?? __( 'All Categories', 'a1-tools' ); $empty_text = $settings['empty_text'] ?? __( 'No images found.', 'a1-tools' ); $work_per_page = max( 1, absint( $settings['work_per_page'] ?? 9 ) ); $projects_label = $settings['projects_button_label'] ?? __( 'View Our Projects', 'a1-tools' ); $back_label = $settings['back_button_label'] ?? __( 'Back to Our Work', 'a1-tools' ); $has_work = ! empty( $work_images ); $has_projects = ! empty( $items ); $ratio_style = ''; if ( 'original' !== $ratio ) { $rp = explode( '-', $ratio ); $rw = isset( $rp[0] ) ? max( 1, intval( $rp[0] ) ) : 4; $rh = isset( $rp[1] ) ? max( 1, intval( $rp[1] ) ) : 3; $ratio_style = '--a1g-rw:' . $rw . ';--a1g-rh:' . $rh . ';'; } $root_class = 'a1tools-gallery a1tools-gallery-hover-' . preg_replace( '/[^a-z]/', '', (string) $hover_effect ); if ( 'original' === $ratio ) { $root_class .= ' a1tools-gallery-ratio-original'; } if ( $do_lightbox ) { $root_class .= ' a1tools-gallery-has-lightbox'; } $html = '
'; if ( $has_work ) { $html .= ''; } if ( $has_projects ) { $html .= ''; } $html .= '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- all dynamic values escaped above; markup is built locally. echo $html; } }