598 lines
23 KiB
PHP
598 lines
23 KiB
PHP
<?php
|
||
if (!defined('ABSPATH')) {
|
||
exit; // Empêche l'accès direct.
|
||
}
|
||
|
||
use Elementor\Controls_Manager;
|
||
use Elementor\Group_Control_Typography;
|
||
use Elementor\Group_Control_Box_Shadow;
|
||
|
||
/**
|
||
* Classe Elementor_Category_Grid_Widget.
|
||
*
|
||
* Widget Elementor personnalisé qui affiche une grille de catégories d'articles.
|
||
* Chaque catégorie est affichée sous forme de carte avec son nom, son image et un lien vers son archive.
|
||
*/
|
||
class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
|
||
{
|
||
|
||
/**
|
||
* Identifiant unique du widget.
|
||
*
|
||
* @return string Slug du widget.
|
||
*/
|
||
public function get_name(): string
|
||
{
|
||
return 'category-grid';
|
||
}
|
||
|
||
/**
|
||
* Titre du widget affiché dans l'interface Elementor.
|
||
*
|
||
* @return string Titre du widget.
|
||
*/
|
||
public function get_title(): string
|
||
{
|
||
return esc_html__('Grille de Catégories', 'category-grid-widget-for-elementor');
|
||
}
|
||
|
||
/**
|
||
* Icône du widget (bibliothèque d'icônes Elementor).
|
||
*
|
||
* @return string Classe de l'icône du widget.
|
||
*/
|
||
public function get_icon(): string
|
||
{
|
||
return 'eicon-posts-grid';
|
||
}
|
||
|
||
/**
|
||
* Catégories Elementor dans lesquelles le widget apparaîtra.
|
||
*
|
||
* @return array Catégories du panneau Elementor.
|
||
*/
|
||
public function get_categories(): array
|
||
{
|
||
return ['general'];
|
||
}
|
||
|
||
/**
|
||
* Mots-clés facilitant la recherche du widget.
|
||
*
|
||
* @return array Mots-clés du widget.
|
||
*/
|
||
public function get_keywords(): array
|
||
{
|
||
return ['category', 'catégorie', 'grid', 'grille'];
|
||
}
|
||
protected function register_style_controls()
|
||
{
|
||
// 1) Styles de la grille (espacement)
|
||
$this->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->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();
|
||
|
||
// Récupérer les ID des catégories sélectionnées.
|
||
$selected_ids = [];
|
||
if (!empty($settings['categories'])) {
|
||
// S'assurer d'avoir un tableau d'ID (entiers).
|
||
$selected_ids = is_array($settings['categories']) ? $settings['categories'] : [$settings['categories']];
|
||
$selected_ids = array_map('intval', $selected_ids);
|
||
}
|
||
if (empty($selected_ids)) {
|
||
// Aucune catégorie sélectionnée : ne rien afficher.
|
||
return;
|
||
}
|
||
|
||
// Déterminer les catégories à afficher (inclure les sous-catégories si demandé).
|
||
$category_ids_to_show = $selected_ids;
|
||
// if (!empty($settings['show_subcategories']) && $settings['show_subcategories'] === 'yes') {
|
||
// // Ajouter les sous-catégories de chaque catégorie sélectionnée.
|
||
// foreach ($selected_ids as $cat_id) {
|
||
// $child_ids = get_term_children($cat_id, 'category');
|
||
// if (is_array($child_ids) && !empty($child_ids)) {
|
||
// $category_ids_to_show = array_merge($category_ids_to_show, $child_ids);
|
||
// }
|
||
// }
|
||
// // Éliminer les doublons d'ID au cas où.
|
||
// $category_ids_to_show = array_unique($category_ids_to_show);
|
||
// }
|
||
// 1) Cas : on veut les sous-catégories de la page actuelle
|
||
if ('yes' === $settings['subcats_of_current']) {
|
||
$queried = get_queried_object();
|
||
if ($queried && isset($queried->term_id) && 'category' === $queried->taxonomy) {
|
||
$category_ids_to_show = get_term_children($queried->term_id, 'category');
|
||
} else {
|
||
// si pas en archive catégorie, rien à afficher
|
||
return;
|
||
}
|
||
|
||
// 2) Sinon, on part des catégories sélectionnées
|
||
} else {
|
||
$category_ids_to_show = $selected_ids;
|
||
|
||
// Si on veut uniquement leurs sous-catégories
|
||
if ('yes' === $settings['only_subcats_of_selected']) {
|
||
$sub_ids = [];
|
||
foreach ($selected_ids as $cat_id) {
|
||
$children = get_term_children($cat_id, 'category');
|
||
if (is_array($children)) {
|
||
$sub_ids = array_merge($sub_ids, $children);
|
||
}
|
||
}
|
||
$category_ids_to_show = array_unique($sub_ids);
|
||
}
|
||
// Sinon, l’ancienne logique : on ajoute ou pas les sous-catégories selon show_subcategories
|
||
elseif ('yes' === $settings['show_subcategories']) {
|
||
foreach ($selected_ids as $cat_id) {
|
||
$category_ids_to_show = array_merge($category_ids_to_show, get_term_children($cat_id, 'category') ?: []);
|
||
}
|
||
$category_ids_to_show = array_unique($category_ids_to_show);
|
||
}
|
||
}
|
||
|
||
// Préparer les args pour get_terms() – on ne précise PAS le tri si c'est par count
|
||
$term_args = [
|
||
'taxonomy' => 'category',
|
||
'include' => $category_ids_to_show,
|
||
'hide_empty' => false,
|
||
];
|
||
if ('count' !== $settings['order_by']) {
|
||
// tri natif pour 'name' ou 'id'
|
||
$term_args['orderby'] = $settings['order_by'];
|
||
$term_args['order'] = $settings['order'];
|
||
}
|
||
|
||
// Récupérer les termes
|
||
$raw_terms = get_terms($term_args);
|
||
if (is_wp_error($raw_terms) || empty($raw_terms)) {
|
||
return;
|
||
}
|
||
|
||
// Filtrer les termes sans image si demandé
|
||
$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;
|
||
}
|
||
|
||
// Stocker image_url temporaire pour éviter de recalculer
|
||
$term->_image_url = $image_url;
|
||
$terms[] = $term;
|
||
}
|
||
|
||
// Si tri par nombre d'articles, calculer et trier manuellement
|
||
if ('count' === $settings['order_by']) {
|
||
// Calcul du total d'articles (directs + descendants) pour chaque terme
|
||
$counts = [];
|
||
foreach ($terms as $term) {
|
||
$total = (int) $term->count;
|
||
$desc_ids = get_term_children($term->term_id, 'category');
|
||
if (is_array($desc_ids)) {
|
||
foreach ($desc_ids as $desc_id) {
|
||
$desc = get_term($desc_id, 'category');
|
||
if (!is_wp_error($desc)) {
|
||
$total += (int) $desc->count;
|
||
}
|
||
}
|
||
}
|
||
$counts[$term->term_id] = $total;
|
||
}
|
||
// Tri selon l’ordre choisi
|
||
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;
|
||
});
|
||
}
|
||
|
||
// Calcul de la classe grille
|
||
$columns = (int) $settings['columns'];
|
||
$grid_class = 'columns-' . $columns;
|
||
|
||
// Rendu HTML
|
||
echo '<div class="elementor-category-grid ' . esc_attr($grid_class) . '">';
|
||
foreach ($terms as $term) {
|
||
$thumbnail_id = get_term_meta($term->term_id, 'thumbnail_id', true);
|
||
echo '<div class="category-card">';
|
||
echo '<a href="' . esc_url(get_term_link($term)) . '">';
|
||
if (esc_url($term->_image_url)) {
|
||
echo '<div class="category-card-image">';
|
||
echo wp_get_attachment_image(
|
||
$thumbnail_id,
|
||
$settings['image_size'],
|
||
false,
|
||
[
|
||
'alt' => esc_attr($term->name),
|
||
'class' => 'category-card-img', // optionnel : ajoutez vos classes CSS
|
||
]
|
||
);
|
||
echo '</div>';
|
||
}
|
||
echo '<div class="category-card-name">' . esc_html($term->name) . '</div>';
|
||
echo '</a>';
|
||
echo '</div>';
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
}
|