start_controls_section( 'section_style_grid', [ 'label' => esc_html__('Grille', 'category-grid-widget-for-elementor'), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_responsive_control( 'grid_gap', [ 'label' => esc_html__('Espacement (gap)', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::SLIDER, 'size_units' => ['px', 'em', '%'], 'range' => [ 'px' => ['min' => 0, 'max' => 100], 'em' => ['min' => 0, 'max' => 10], ], 'selectors' => [ '{{WRAPPER}} .elementor-category-grid' => 'gap: {{SIZE}}{{UNIT}};', ], ] ); $this->end_controls_section(); // 2) Styles des cartes (fond, bordure, ombre) $this->start_controls_section( 'section_style_card', [ 'label' => esc_html__('Carte', 'category-grid-widget-for-elementor'), 'tab' => Controls_Manager::TAB_STYLE, ] ); $this->add_control( 'card_bg_color', [ 'label' => esc_html__('Fond de la carte', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::COLOR, 'default' => '', 'selectors' => [ '{{WRAPPER}} .category-card' => 'background-color: {{VALUE}};', ], ] ); $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'card_title_typography', 'label' => esc_html__('Typographie du titre', 'category-grid-widget-for-elementor'), 'selector' => '{{WRAPPER}} .category-card-name', ] ); $this->add_control( 'card_border_radius', [ 'label' => esc_html__('Border radius', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => ['px', '%'], 'selectors' => [ '{{WRAPPER}} .category-card' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};', ], ] ); $this->add_group_control( \Elementor\Group_Control_Box_Shadow::get_type(), [ 'name' => 'card_box_shadow', 'label' => esc_html__('Ombre de la carte', 'category-grid-widget-for-elementor'), 'selector' => '{{WRAPPER}} .category-card', ] ); $this->end_controls_section(); // 3) Styles du titre (couleur, arrière-plan du badge) $this->start_controls_section( 'section_style_title', [ 'label' => esc_html__('Titre', 'category-grid-widget-for-elementor'), 'tab' => Controls_Manager::TAB_STYLE, ] ); // 3.1) Switcher pour désactiver le fond $this->add_control( 'disable_title_bg', [ 'label' => esc_html__('Désactiver le fond du titre', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::SWITCHER, 'label_on' => esc_html__('Oui', 'category-grid-widget-for-elementor'), 'label_off' => esc_html__('Non', 'category-grid-widget-for-elementor'), 'return_value' => 'yes', 'default' => 'no', 'selectors' => [ // Quand activé => on rend le fond totalement transparent '{{WRAPPER}} .category-card-name' => 'background-color: transparent !important;', ], ] ); // 3.2) Couleur du texte $this->add_control( 'title_text_color', [ 'label' => esc_html__('Couleur du texte', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}} .category-card-name' => 'color: {{VALUE}};', ], ] ); // 3.3) Couleur de fond du titre, **uniquement si** le fond n'est pas désactivé $this->add_control( 'title_bg_color', [ 'label' => esc_html__('Fond du titre', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::COLOR, 'default' => 'rgba(0,0,0,0.4)', 'selectors' => [ '{{WRAPPER}} .category-card-name' => 'background-color: {{VALUE}};', ], 'condition' => [ // n'affiche ce picker que si disable_title_bg != 'yes' 'disable_title_bg!' => 'yes', ], ] ); // 3.4) Typographie du titre $this->add_group_control( Group_Control_Typography::get_type(), [ 'name' => 'card_title_typography', 'label' => esc_html__('Typographie du titre', 'category-grid-widget-for-elementor'), 'selector' => '{{WRAPPER}} .category-card-name', ] ); $this->end_controls_section(); // 4) Section Hover $this->start_controls_section( 'section_style_hover', [ 'label' => esc_html__('Hover', 'category-grid-widget-for-elementor'), 'tab' => Controls_Manager::TAB_STYLE, ] ); // 4.1) Couleur de l'overlay $this->add_control( 'hover_overlay_color', [ 'label' => esc_html__('Couleur de l’overlay', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::COLOR, 'default' => 'rgba(0,0,0,0.3)', 'selectors' => [ // sur l’élément ::before (défini en CSS ci-dessous) '{{WRAPPER}} .category-card::before' => 'background-color: {{VALUE}};', ], ] ); // 4.2) Opacité de l'overlay $this->add_control( 'hover_overlay_opacity', [ 'label' => esc_html__('Opacité de l’overlay', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::SLIDER, 'size_units' => ['%'], 'range' => [ '%' => ['min' => 0, 'max' => 100], ], 'default' => ['size' => 30], 'selectors' => [ '{{WRAPPER}} .category-card::before' => 'opacity: 0;', '{{WRAPPER}} .category-card:hover::before' => 'opacity: {{SIZE}}%;', ], ] ); // 4.3) Couleur du titre au hover $this->add_control( 'title_hover_color', [ 'label' => esc_html__('Couleur du titre au hover', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff', 'selectors' => [ '{{WRAPPER}} .category-card:hover .category-card-name' => 'color: {{VALUE}};', ], ] ); $this->end_controls_section(); } /** * Enregistrer les contrôles du widget (champs configurables dans Elementor). */ protected function register_controls(): void { /* Section "Contenu" pour le choix des catégories et options de requête */ $this->start_controls_section( 'section_content', [ 'label' => esc_html__('Catégories', 'category-grid-widget-for-elementor'), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, ] ); // Contrôle : Sélection des catégories à afficher (multisélection). $options = []; $categories = get_categories(['hide_empty' => false]); foreach ($categories as $cat) { $options[$cat->term_id] = $cat->name; } $this->add_control( 'categories', [ 'label' => esc_html__('Catégories à afficher', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SELECT2, 'multiple' => true, 'label_block' => true, 'options' => $options, 'description' => esc_html__('Sélectionnez les catégories d’articles à afficher.', 'category-grid-widget-for-elementor'), ] ); // Contrôle : Inclure les sous-catégories. $this->add_control( 'show_subcategories', [ 'label' => esc_html__('Afficher les sous-catégories', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => esc_html__('Oui', 'category-grid-widget-for-elementor'), 'label_off' => esc_html__('Non', 'category-grid-widget-for-elementor'), 'return_value' => 'yes', 'default' => 'yes', 'description' => esc_html__('Inclure aussi les sous-catégories des catégories sélectionnées.', 'category-grid-widget-for-elementor'), ] ); $this->add_control( 'only_subcats_of_selected', [ 'label' => esc_html__('Afficher seulement les sous-catégories des catégories sélectionnées', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => esc_html__('Oui', 'category-grid-widget-for-elementor'), 'label_off' => esc_html__('Non', 'category-grid-widget-for-elementor'), 'return_value' => 'yes', 'default' => 'no', 'description' => esc_html__('Si activé, seules les sous-catégories des catégories choisies seront affichées (et pas les catégories parentes).', 'category-grid-widget-for-elementor'), ] ); // Contrôle : Afficher les sous-catégories de la page catégorie actuelle $this->add_control( 'subcats_of_current', [ 'label' => esc_html__('Afficher les sous-catégories de la catégorie actuelle', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => esc_html__('Oui', 'category-grid-widget-for-elementor'), 'label_off' => esc_html__('Non', 'category-grid-widget-for-elementor'), 'return_value' => 'yes', 'default' => 'no', 'description' => esc_html__('Si activé et que l’on est sur une page d’archive de catégorie, affichera ses sous-catégories.', 'category-grid-widget-for-elementor'), ] ); // ---------------------------------------------------------------------- // Contrôle : Ne montrer que les catégories avec image $this->add_control( 'hide_without_image', [ 'label' => esc_html__('Afficher uniquement catégories avec image', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SWITCHER, 'label_on' => esc_html__('Oui', 'category-grid-widget-for-elementor'), 'label_off' => esc_html__('Non', 'category-grid-widget-for-elementor'), 'return_value' => 'yes', 'default' => 'no', 'description' => esc_html__('Si activé, les catégories sans image seront masquées.', 'category-grid-widget-for-elementor'), ] ); // Contrôle : Trier par (critère de tri des catégories). $this->add_control( 'order_by', [ 'label' => esc_html__('Trier par', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'name' => esc_html__('Nom (alphabétique)', 'category-grid-widget-for-elementor'), 'count' => esc_html__('Nombre d’articles', 'category-grid-widget-for-elementor'), 'id' => esc_html__('ID (date de création)', 'category-grid-widget-for-elementor'), ], 'default' => 'name', ] ); // Contrôle : Ordre croissant/décroissant. $this->add_control( 'order', [ 'label' => esc_html__('Ordre', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'ASC' => esc_html__('Croissant (A-Z / 0-9)', 'category-grid-widget-for-elementor'), 'DESC' => esc_html__('Décroissant (Z-A / 9-0)', 'category-grid-widget-for-elementor'), ], 'default' => 'ASC', ] ); $this->add_control( 'title_position', [ 'label' => esc_html__('Position du titre', 'category-grid-widget-for-elementor'), 'type' => Controls_Manager::SELECT, 'options' => [ 'overlay' => esc_html__('Sur la carte', 'category-grid-widget-for-elementor'), 'below' => esc_html__('Sous la carte', 'category-grid-widget-for-elementor'), ], 'default' => 'overlay', 'description' => esc_html__('Choisissez si le nom de la catégorie apparaît en overlay ou sous l’image.', 'category-grid-widget-for-elementor'), ] ); $this->end_controls_section(); /* Section "Mise en page" pour les options d'affichage (colonnes, taille d'image) */ $this->start_controls_section( 'section_layout', [ 'label' => esc_html__('Mise en page', 'category-grid-widget-for-elementor'), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, ] ); // Contrôle : Nombre de colonnes de la grille. $this->add_control( 'columns', [ 'label' => esc_html__('Nombre de colonnes', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 1 => '1', 2 => '2', 3 => '3', 4 => '4', 5 => '5', 6 => '6', ], 'default' => 3, ] ); // Contrôle : Taille des images affichées. $this->add_control( 'image_size', [ 'label' => esc_html__('Taille des images', 'category-grid-widget-for-elementor'), 'type' => \Elementor\Controls_Manager::SELECT, 'options' => [ 'thumbnail' => esc_html__('Miniature (thumbnail)', 'category-grid-widget-for-elementor'), 'medium' => esc_html__('Moyen (medium)', 'category-grid-widget-for-elementor'), 'large' => esc_html__('Grand (large)', 'category-grid-widget-for-elementor'), 'full' => esc_html__('Plein (full)', 'category-grid-widget-for-elementor'), ], 'default' => 'medium', ] ); $this->end_controls_section(); $this->register_style_controls(); } /** * Générer l'affichage du widget côté front-end. */ protected function render(): void { $settings = $this->get_settings_for_display(); // 1) Récupérer les IDs des catégories sélectionnées $selected_ids = []; if (!empty($settings['categories'])) { $selected_ids = (array) $settings['categories']; $selected_ids = array_map('absint', $selected_ids); } if (empty($selected_ids)) { return; } // 2) Construire la liste des IDs à afficher if ('yes' === $settings['subcats_of_current']) { $queried = get_queried_object(); if ($queried instanceof WP_Term && 'category' === $queried->taxonomy) { $category_ids_to_show = get_term_children($queried->term_id, 'category'); } else { return; } } else { $category_ids_to_show = $selected_ids; if ('yes' === $settings['only_subcats_of_selected']) { $subs = []; foreach ($selected_ids as $cat_id) { $children = get_term_children($cat_id, 'category'); if (is_array($children)) { $subs = array_merge($subs, $children); } } $category_ids_to_show = array_unique($subs); } elseif ('yes' === $settings['show_subcategories']) { foreach ($selected_ids as $cat_id) { $children = get_term_children($cat_id, 'category') ?: []; $category_ids_to_show = array_merge($category_ids_to_show, $children); } $category_ids_to_show = array_unique($category_ids_to_show); } } // 3) Préparer args pour get_terms() $term_args = [ 'taxonomy' => 'category', 'include' => $category_ids_to_show, 'hide_empty' => false, ]; if ('count' !== $settings['order_by']) { $term_args['orderby'] = $settings['order_by']; $term_args['order'] = $settings['order']; } // 4) Récupérer et filtrer les termes $raw_terms = get_terms($term_args); if (is_wp_error($raw_terms) || empty($raw_terms)) { return; } $terms = []; foreach ($raw_terms as $term) { $thumb_id = get_term_meta($term->term_id, 'thumbnail_id', true); $image_url = $thumb_id ? wp_get_attachment_image_url($thumb_id, $settings['image_size']) : ''; if ('yes' === $settings['hide_without_image'] && empty($image_url)) { continue; } $term->_image_url = $image_url; $terms[] = $term; } if (empty($terms)) { return; } // 5) Tri manuel si order_by=count if ('count' === $settings['order_by']) { $counts = []; foreach ($terms as $term) { $total = intval($term->count); $children = get_term_children($term->term_id, 'category'); if (is_array($children)) { foreach ($children as $child_id) { $child = get_term($child_id, 'category'); if (!is_wp_error($child)) { $total += intval($child->count); } } } $counts[$term->term_id] = $total; } usort($terms, function ($a, $b) use ($counts, $settings) { $ca = $counts[$a->term_id]; $cb = $counts[$b->term_id]; if ($ca === $cb) { return 0; } if ('ASC' === $settings['order']) { return $ca < $cb ? -1 : 1; } return $ca > $cb ? -1 : 1; }); } // 6) Affichage final $grid_class = 'columns-' . absint($settings['columns']); $is_below = ('below' === $settings['title_position']); echo '