Compare commits

..

7 Commits

Author SHA1 Message Date
MrRaph_
4dcc664bed feat: Added an option to have titles displayed under cards 2025-05-25 10:03:31 +00:00
MrRaph_
54be7b7455 feat: responsive style 2025-05-23 16:18:09 +00:00
MrRaph_
202a1469b8 feat: styling 2025-05-23 15:59:44 +00:00
MrRaph_
1704d64ed5 feat: Added more option to handle subcategories 2025-05-23 14:14:50 +00:00
MrRaph_
e89e4ad7c2 feat: complies with plugin check 2025-05-23 13:00:45 +00:00
MrRaph_
e1699f8d02 feat: added info 2025-05-23 11:43:19 +00:00
MrRaph_
356ea8df9c feat: add more customizations 2025-05-23 11:34:40 +00:00
5 changed files with 766 additions and 303 deletions

View File

@@ -0,0 +1,230 @@
<?php
/**
* Plugin Name: Category Grid Widget for Elementor
* Plugin URI: https://git.mrraph.fr/WordPress/elementor-category-grid-widget-for-elementor
* Description: Responsive article category grid with image for Elementor.
* Version: 1.4.0
* Author: MrRaph_
* Author URI: https://mrraph.photo
* Requires at least: 5.8
* Requires PHP: 7.0
* Text Domain: category-grid-widget-for-elementor
* Requires Plugins: elementor
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/
if (!defined('ABSPATH')) {
exit; // Sécurité : empêche l'accès direct.
}
/**
* Charger les scripts Media Uploader et localiser les données JS
* sur les écrans d'ajout ET d'édition de catégorie.
*/
function ccgw_enqueue_media_uploader($hook_suffix)
{
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$taxonomy = filter_input( INPUT_GET, 'taxonomy', FILTER_SANITIZE_STRING );
$taxonomy = sanitize_key( (string) $taxonomy );
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if ( ! in_array( $hook_suffix, [ 'edit-tags.php', 'term.php' ], true ) ) {
return;
}
if ( 'category' !== $taxonomy ) {
return;
}
// Charge la librairie media de WP
wp_enqueue_media();
// Ton script JS
wp_enqueue_script(
'ccgw-category-image',
plugin_dir_url(__FILE__) . 'admin/js/category-image.js',
['jquery'],
'1.4.0',
true
);
// Variable JS
wp_localize_script(
'ccgw-category-image',
'ccgw_data',
[
'title' => esc_js(__('Sélectionner une image de catégorie', 'category-grid-widget-for-elementor')),
'button' => esc_js(__('Sélectionner', 'category-grid-widget-for-elementor')),
]
);
}
add_action('admin_enqueue_scripts', 'ccgw_enqueue_media_uploader');
// 1) Déclarez la colonne "Image" dans le tableau des catégories
add_filter('manage_edit-category_columns', function ($columns) {
$new = [];
foreach ($columns as $key => $label) {
$new[$key] = $label;
if ('name' === $key) {
// après la colonne "Nom", on insère notre colonne "Image"
$new['category_image'] = esc_html__('Image', 'category-grid-widget-for-elementor');
}
}
return $new;
});
// 2) Remplissez la colonne "Image"
add_filter('manage_category_custom_column', function ($out, $column, $term_id) {
if ('category_image' === $column) {
$thumb_id = get_term_meta($term_id, 'thumbnail_id', true);
if ($thumb_id) {
// Affiche la version 'thumbnail' de l'image
return wp_get_attachment_image($thumb_id, 'thumbnail', false, [
'style' => 'max-width:60px;height:auto;'
]);
}
}
return $out;
}, 10, 3);
// 3) Un peu de CSS pour la largeur de colonne et la mise en forme
add_action('admin_head-edit-tags.php', function () {
echo '<style>
.fixed .column-category_image { width: 80px; }
.column-category_image img { display: block; margin: 4px auto; }
</style>';
});
/**
* Affiche le champ d'upload dans le formulaire de création de catégorie.
*/
function ccgw_category_image_field($taxonomy)
{ ?>
<div class="form-field term-group">
<label
for="category-image-id"><?php esc_html_e('Image de la catégorie', 'category-grid-widget-for-elementor'); ?></label>
<input type="hidden" id="category-image-id" name="category-image-id" value="">
<div id="category-image-wrapper"></div>
<p>
<button type="button"
class="button button-secondary ccgw-upload-image"><?php esc_html_e('Choisir une image', 'category-grid-widget-for-elementor'); ?></button>
<button type="button"
class="button button-secondary ccgw-remove-image"><?php esc_html_e('Supprimer limage', 'category-grid-widget-for-elementor'); ?></button>
</p>
<?php wp_nonce_field('ccgw_save_category_image', 'ccgw_category_image_nonce'); ?>
</div>
<?php }
add_action('category_add_form_fields', 'ccgw_category_image_field', 10, 2);
/**
* Affiche le champ d'upload dans le formulaire d'édition de catégorie.
*/
function ccgw_category_image_field_edit($term, $taxonomy)
{
$image_id = get_term_meta($term->term_id, 'thumbnail_id', true);
$image_url = $image_id ? wp_get_attachment_thumb_url($image_id) : '';
?>
<tr class="form-field term-group-wrap">
<th scope="row">
<label
for="category-image-id"><?php esc_html_e('Image de la catégorie', 'category-grid-widget-for-elementor'); ?></label>
</th>
<td>
<input type="hidden" id="category-image-id" name="category-image-id" value="<?php echo esc_attr($image_id); ?>">
<div id="category-image-wrapper">
<?php if ($image_url): ?>
<?php
echo wp_get_attachment_image(
$image_id,
'thumbnail',
false,
['style' => 'max-width:60px;height:auto;']
);
?>
<?php endif; ?>
</div>
<p>
<button type="button"
class="button button-secondary ccgw-upload-image"><?php esc_html_e('Choisir une image', 'category-grid-widget-for-elementor'); ?></button>
<button type="button"
class="button button-secondary ccgw-remove-image"><?php esc_html_e('Supprimer limage', 'category-grid-widget-for-elementor'); ?></button>
</p>
</td>
<?php wp_nonce_field('ccgw_save_category_image', 'ccgw_category_image_nonce'); ?>
</tr>
<?php }
add_action('category_edit_form_fields', 'ccgw_category_image_field_edit', 10, 2);
/**
* Sauvegarde le term meta 'thumbnail_id' pour la catégorie,
* en vérifiant le nonce et en sanitisant adéquatement.
*/
function ccgw_save_category_image($term_id)
{
$nonce = isset( $_POST['ccgw_category_image_nonce'] )
? sanitize_text_field( wp_unslash( $_POST['ccgw_category_image_nonce'] ))
: '';
// $nonce = sanitize_text_field( $raw_nonce );
// 1) Vérifier la présence du nonce
if (empty($nonce)) {
return;
}
// 2) Vérifier le nonce
if (!wp_verify_nonce($nonce, 'ccgw_save_category_image')) {
return;
}
// 3) Vérifier la capacité de lutilisateur
if (!current_user_can('manage_categories')) {
return;
}
// 4) Récupérer et sanitiszer lID dimage
if (isset($_POST['category-image-id'])) {
$image_id = intval(wp_unslash($_POST['category-image-id']));
if ($image_id) {
update_term_meta($term_id, 'thumbnail_id', $image_id);
} else {
delete_term_meta($term_id, 'thumbnail_id');
}
}
}
add_action('created_category', 'ccgw_save_category_image', 10, 2);
add_action('edited_category', 'ccgw_save_category_image', 10, 2);
/**
* Enregistrer le widget "Grille de Catégories" pour Elementor.
*
* Inclut le fichier de la classe du widget et enregistre le widget auprès du manager d'Elementor.
*
* @param \Elementor\Widgets_Manager $widgets_manager Le gestionnaire de widgets d'Elementor.
*/
function register_category_grid_widget($widgets_manager)
{
// Inclure le fichier de la classe du widget.
require_once __DIR__ . '/widgets/category-grid-widget.php';
// Enregistrer la classe du widget auprès d'Elementor.
$widgets_manager->register(new \Elementor_Category_Grid_Widget());
}
add_action('elementor/widgets/register', 'register_category_grid_widget');
/**
* Enqueue styles front-end pour le widget.
*/
function ccgw_enqueue_front_styles()
{
// Ne charger que si Elementor est actif
if (defined('ELEMENTOR_VERSION')) {
wp_enqueue_style(
'ccgw-category-grid-style',
plugin_dir_url(__FILE__) . 'css/style.css',
[],
'1.4.0'
);
}
}
add_action('wp_enqueue_scripts', 'ccgw_enqueue_front_styles');

View File

@@ -1,67 +1,141 @@
/* css/style.css */
/* 1) Mise en place de la grille */
/* .elementor-category-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
} */
.elementor-category-grid {
display: grid;
gap: 1rem;
/* -------------------------------------------------- */
/* Corrige le comportement “flex” sur mobile/tablette */
/* -------------------------------------------------- */
.e-con-inner {
display: block !important;
}
/* Nombre de colonnes dynamiques */
.elementor-category-grid.columns-1 { grid-template-columns: repeat(1, 1fr); }
.elementor-category-grid.columns-2 { grid-template-columns: repeat(2, 1fr); }
.elementor-category-grid.columns-3 { grid-template-columns: repeat(3, 1fr); }
.elementor-category-grid.columns-4 { grid-template-columns: repeat(4, 1fr); }
.elementor-category-grid.columns-5 { grid-template-columns: repeat(5, 1fr); }
.elementor-category-grid.columns-6 { grid-template-columns: repeat(6, 1fr); }
/* 2) Chaque carte = conteneur relatif */
/* —————————————————————————————————————————— */
/* 1) Structure de la carte */
/* —————————————————————————————————————————— */
.elementor-category-grid .category-card {
position: relative;
overflow: hidden;
height: 0;
padding-bottom: 40%; /* ratio 5:2 ajustez selon vos images */
border-radius: 8px;
/* ratio fixe 5:2 pour limage uniquement */
}
.elementor-category-grid .category-card.title-below {
/* autorise la hauteur auto quand titre en dessous */
height: auto !important;
padding-bottom: 0 !important;
}
.elementor-category-grid .category-card-image {
/* reprend lancien padding-bottom */
position: relative;
width: 100%;
padding-bottom: 40%; /* ratio 5:2 */
}
.elementor-category-grid .category-card.title-below .category-card-image {
/* si titre en dessous, on garde juste limage */
padding-bottom: 40%;
}
/* 3) Image pleine carte */
/* Overlay via pseudo */
.elementor-category-grid .category-card::before {
content: "";
position: absolute;
inset: 0;
background-color: transparent;
transition: opacity .3s ease;
pointer-events: none;
}
/* Image plein cadre */
.elementor-category-grid .category-card img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
inset: 0;
width: 100%; height: 100%;
object-fit: cover;
transition: transform .4s ease;
}
/* 4) Effet hover : léger zoom */
.elementor-category-grid .category-card:hover img {
transform: scale(1.05);
}
/* 5) Titre centré en overlay */
.elementor-category-grid .category-card-name {
/* —————————————————————————————————————————— */
/* 2) Titre overlay */
/* —————————————————————————————————————————— */
.elementor-category-grid .category-card-name.overlay {
position: absolute;
top: 50%;
left: 50%;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
margin: 0;
padding: .5em 1em;
font-size: 1.5rem;
line-height: 1.2;
text-align: center;
color: #ffffff;
/* background: rgba(0, 0, 0, 0.4); */
/* border-radius: 4px; */
transition: background .3s ease;
color: #fff;
white-space: pre-wrap;
word-break: break-word;
transition: font-size .3s ease, padding .3s ease;
}
/* 6) Hover sur titre : fond un peu plus opaque */
/* .elementor-category-grid .category-card:hover .category-card-name {
background: rgba(0, 0, 0, 0.6);
} */
/* —————————————————————————————————————————— */
/* 3) Titre below */
/* —————————————————————————————————————————— */
.elementor-category-grid .category-card-name-below {
padding: .5em;
text-align: center;
font-size: 1.2rem;
}
.elementor-category-grid .category-card-name-below a {
color: inherit;
text-decoration: none;
transition: text-decoration .3s ease;
}
.elementor-category-grid .category-card-name-below a:hover {
text-decoration: underline;
}
/* —————————————————————————————————————————— */
/* 4) Grille responsive par défaut (desktop) */
/* —————————————————————————————————————————— */
.elementor-category-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
/* —————————————————————————————————————————— */
/* 5) Mobile (≤ 480px) → 1 colonne */
/* —————————————————————————————————————————— */
@media screen and (max-width: 480px) {
.elementor-category-grid {
grid-template-columns: 1fr !important;
}
.elementor-category-grid .category-card-name.overlay {
font-size: 1rem;
padding: .4em .8em;
}
.elementor-category-grid .category-card-name-below {
font-size: 1rem;
padding: .4em;
}
}
/* —————————————————————————————————————————— */
/* 6) Tablette (481px768px) → 2 colonnes fixes */
/* —————————————————————————————————————————— */
@media screen and (min-width: 481px) and (max-width: 768px) {
.elementor-category-grid {
grid-template-columns: repeat(2, 1fr) !important;
}
.elementor-category-grid .category-card-name.overlay {
font-size: 1.2rem;
}
.elementor-category-grid .category-card-name-below {
font-size: 1.2rem;
padding: .5em;
}
}
/* —————————————————————————————————————————— */
/* 7) Desktop (≥ 769px) → respect des classes */
/* —————————————————————————————————————————— */
@media screen and (min-width: 769px) {
.elementor-category-grid.columns-1 { grid-template-columns: repeat(1, 1fr); }
.elementor-category-grid.columns-2 { grid-template-columns: repeat(2, 1fr); }
.elementor-category-grid.columns-3 { grid-template-columns: repeat(3, 1fr); }
.elementor-category-grid.columns-4 { grid-template-columns: repeat(4, 1fr); }
.elementor-category-grid.columns-5 { grid-template-columns: repeat(5, 1fr); }
.elementor-category-grid.columns-6 { grid-template-columns: repeat(6, 1fr); }
}

View File

@@ -1,179 +0,0 @@
<?php
/**
* Plugin Name: Elementor Category Grid Widget
* Description: Custom Elementor widget to display a grid of post categories with images.
* Version: 1.2.0
* Author: MrRaph_
* Text Domain: category-grid-widget
* Requires Plugins: elementor
* Elementor tested up to: 3.25.0
* Elementor Pro tested up to: 3.25.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Sécurité : empêche l'accès direct.
}
/**
* Charger les scripts Media Uploader et localiser les données JS
* sur les écrans d'ajout ET d'édition de catégorie.
*/
function ccgw_enqueue_media_uploader( $hook_suffix ) {
// On ne charge que sur edit-tags.php (création) OU term.php (édition)
if ( ! in_array( $hook_suffix, [ 'edit-tags.php', 'term.php' ], true ) ) {
return;
}
// Et seulement pour la taxonomie 'category'
if ( empty( $_GET['taxonomy'] ) || 'category' !== $_GET['taxonomy'] ) {
return;
}
// Charge la librairie media de WP
wp_enqueue_media();
// Ton script JS
wp_enqueue_script(
'ccgw-category-image',
plugin_dir_url( __FILE__ ) . 'admin/js/category-image.js',
[ 'jquery' ],
'1.2.0',
true
);
// Variable JS
wp_localize_script(
'ccgw-category-image',
'ccgw_data',
[
'title' => esc_js( __( 'Sélectionner une image de catégorie', 'category-grid-widget' ) ),
'button' => esc_js( __( 'Sélectionner', 'category-grid-widget' ) ),
]
);
}
add_action( 'admin_enqueue_scripts', 'ccgw_enqueue_media_uploader' );
// 1) Déclarez la colonne "Image" dans le tableau des catégories
add_filter( 'manage_edit-category_columns', function( $columns ) {
$new = [];
foreach ( $columns as $key => $label ) {
$new[ $key ] = $label;
if ( 'name' === $key ) {
// après la colonne "Nom", on insère notre colonne "Image"
$new['category_image'] = esc_html__( 'Image', 'category-grid-widget' );
}
}
return $new;
} );
// 2) Remplissez la colonne "Image"
add_filter( 'manage_category_custom_column', function( $out, $column, $term_id ) {
if ( 'category_image' === $column ) {
$thumb_id = get_term_meta( $term_id, 'thumbnail_id', true );
if ( $thumb_id ) {
// Affiche la version 'thumbnail' de l'image
return wp_get_attachment_image( $thumb_id, 'thumbnail', false, [
'style' => 'max-width:60px;height:auto;'
] );
}
}
return $out;
}, 10, 3 );
// 3) Un peu de CSS pour la largeur de colonne et la mise en forme
add_action( 'admin_head-edit-tags.php', function() {
echo '<style>
.fixed .column-category_image { width: 80px; }
.column-category_image img { display: block; margin: 4px auto; }
</style>';
} );
/**
* Affiche le champ d'upload dans le formulaire de création de catégorie.
*/
function ccgw_category_image_field( $taxonomy ) { ?>
<div class="form-field term-group">
<label for="category-image-id"><?php esc_html_e( 'Image de la catégorie', 'category-grid-widget' ); ?></label>
<input type="hidden" id="category-image-id" name="category-image-id" value="">
<div id="category-image-wrapper"></div>
<p>
<button type="button" class="button button-secondary ccgw-upload-image"><?php esc_html_e( 'Choisir une image', 'category-grid-widget' ); ?></button>
<button type="button" class="button button-secondary ccgw-remove-image"><?php esc_html_e( 'Supprimer limage', 'category-grid-widget' ); ?></button>
</p>
</div>
<?php }
add_action( 'category_add_form_fields', 'ccgw_category_image_field', 10, 2 );
/**
* Affiche le champ d'upload dans le formulaire d'édition de catégorie.
*/
function ccgw_category_image_field_edit( $term, $taxonomy ) {
$image_id = get_term_meta( $term->term_id, 'thumbnail_id', true );
$image_url = $image_id ? wp_get_attachment_thumb_url( $image_id ) : '';
?>
<tr class="form-field term-group-wrap">
<th scope="row">
<label for="category-image-id"><?php esc_html_e( 'Image de la catégorie', 'category-grid-widget' ); ?></label>
</th>
<td>
<input type="hidden" id="category-image-id" name="category-image-id" value="<?php echo esc_attr( $image_id ); ?>">
<div id="category-image-wrapper">
<?php if ( $image_url ) : ?>
<img src="<?php echo esc_url( $image_url ); ?>" style="max-width:100px; height:auto;">
<?php endif; ?>
</div>
<p>
<button type="button" class="button button-secondary ccgw-upload-image"><?php esc_html_e( 'Choisir une image', 'category-grid-widget' ); ?></button>
<button type="button" class="button button-secondary ccgw-remove-image"><?php esc_html_e( 'Supprimer limage', 'category-grid-widget' ); ?></button>
</p>
</td>
</tr>
<?php }
add_action( 'category_edit_form_fields', 'ccgw_category_image_field_edit', 10, 2 );
/**
* Sauvegarde le term meta 'thumbnail_id' pour la catégorie.
*/
function ccgw_save_category_image( $term_id ) {
if ( isset( $_POST['category-image-id'] ) ) {
$image_id = intval( $_POST['category-image-id'] );
if ( $image_id ) {
update_term_meta( $term_id, 'thumbnail_id', $image_id );
} else {
delete_term_meta( $term_id, 'thumbnail_id' );
}
}
}
add_action( 'created_category', 'ccgw_save_category_image', 10, 2 );
add_action( 'edited_category', 'ccgw_save_category_image', 10, 2 );
/**
* Enregistrer le widget "Grille de Catégories" pour Elementor.
*
* Inclut le fichier de la classe du widget et enregistre le widget auprès du manager d'Elementor.
*
* @param \Elementor\Widgets_Manager $widgets_manager Le gestionnaire de widgets d'Elementor.
*/
function register_category_grid_widget( $widgets_manager ) {
// Inclure le fichier de la classe du widget.
require_once __DIR__ . '/widgets/category-grid-widget.php';
// Enregistrer la classe du widget auprès d'Elementor.
$widgets_manager->register( new \Elementor_Category_Grid_Widget() );
}
add_action( 'elementor/widgets/register', 'register_category_grid_widget' );
/**
* Enqueue styles front-end pour le widget.
*/
function ccgw_enqueue_front_styles() {
// Ne charger que si Elementor est actif
if ( defined( 'ELEMENTOR_VERSION' ) ) {
wp_enqueue_style(
'ccgw-category-grid-style',
plugin_dir_url( __FILE__ ) . 'css/style.css',
[],
'1.2.0'
);
}
}
add_action( 'wp_enqueue_scripts', 'ccgw_enqueue_front_styles' );

63
readme.txt Normal file
View File

@@ -0,0 +1,63 @@
=== Category Grid Widget for Elementor ===
Contributors: mrraph
Donate link: https://your-site.example/donate
Tags: elementor, category, grid, widget, posts
Requires at least: 5.8
Tested up to: 6.8
Requires PHP: 7.0
Stable tag: 1.4.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Responsive grid of post categories with images for Elementor.
== Description ==
**Category Grid Widget for Elementor** adds a simple yet powerful widget to Elementor for displaying your post categories in a grid:
- Manual selection of categories to display
- Option to show subcategories
- Option to hide categories without an image
- Configurable number of columns (16)
- Choice of image size (thumbnail, medium, large, full)
- **Style** tab: spacing, card background & border, typography, shadow
- **Hover** tab: customizable overlay (color & opacity), title color on hover
- Each card is clickable and links to the category archive page
The widget uses WordPresss native category image feature (a “Category Image” field added in the admin).
== Installation ==
1. Upload the `elementor-category-grid-widget-for-elementor` folder to `/wp-content/plugins/`.
2. Activate **Category Grid Widget for Elementor** in the **Plugins** menu.
3. In Elementor, add the **Category Grid** widget to your layout.
4. Configure: select categories, columns, style, and hover effects.
== Screenshots ==
1. Elementor editor: widget settings panel
2. 4-column grid with hover overlay
3. Mobile: responsive single column
4. Advanced options in the Style tab
== Frequently Asked Questions ==
= How do I assign an image to a category? =
When creating or editing a category, a “Category Image” field appears in the admin. Click “Select Image” to set it.
= Can I hide categories without an image? =
Yes, enable the **“Hide categories without image”** option in the widgets Content tab.
= How do I customize the hover effect? =
Open the **Style » Hover** tab: choose the overlay color & opacity, and the title color on hover.
== Changelog ==
= 1.4.0 =
* Added an option to have titles displayed under cards
= 1.3.1 =
* Make style responsive
= 1.3.0 =
* Added more option to handle subcategories
= 1.2.1 =
* Initial release
* Manual category and subcategory selection
* Configurable number of columns and image size
* Option to hide categories without image
* Added Style and Hover tabs for advanced customization

View File

@@ -3,6 +3,10 @@ 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.
*
@@ -29,7 +33,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
*/
public function get_title(): string
{
return esc_html__('Grille de Catégories', 'category-grid-widget');
return esc_html__('Grille de Catégories', 'category-grid-widget-for-elementor');
}
/**
@@ -61,6 +65,205 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
{
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 loverlay', '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 loverlay', '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).
@@ -71,7 +274,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->start_controls_section(
'section_content',
[
'label' => esc_html__('Catégories', 'category-grid-widget'),
'label' => esc_html__('Catégories', 'category-grid-widget-for-elementor'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
@@ -85,12 +288,12 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'categories',
[
'label' => esc_html__('Catégories à afficher', 'category-grid-widget'),
'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 darticles à afficher.', 'category-grid-widget'),
'description' => esc_html__('Sélectionnez les catégories darticles à afficher.', 'category-grid-widget-for-elementor'),
]
);
@@ -98,13 +301,40 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'show_subcategories',
[
'label' => esc_html__('Afficher les sous-catégories', 'category-grid-widget'),
'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'),
'label_off' => esc_html__('Non', 'category-grid-widget'),
'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'),
'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 lon est sur une page darchive de catégorie, affichera ses sous-catégories.', 'category-grid-widget-for-elementor'),
]
);
@@ -113,13 +343,13 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'hide_without_image',
[
'label' => esc_html__('Afficher uniquement catégories avec image', 'category-grid-widget'),
'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'),
'label_off' => esc_html__('Non', 'category-grid-widget'),
'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'),
'description' => esc_html__('Si activé, les catégories sans image seront masquées.', 'category-grid-widget-for-elementor'),
]
);
@@ -127,12 +357,12 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'order_by',
[
'label' => esc_html__('Trier par', 'category-grid-widget'),
'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'),
'count' => esc_html__('Nombre darticles', 'category-grid-widget'),
'id' => esc_html__('ID (date de création)', 'category-grid-widget'),
'name' => esc_html__('Nom (alphabétique)', 'category-grid-widget-for-elementor'),
'count' => esc_html__('Nombre darticles', 'category-grid-widget-for-elementor'),
'id' => esc_html__('ID (date de création)', 'category-grid-widget-for-elementor'),
],
'default' => 'name',
]
@@ -142,23 +372,37 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'order',
[
'label' => esc_html__('Ordre', 'category-grid-widget'),
'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'),
'DESC' => esc_html__('Décroissant (Z-A / 9-0)', 'category-grid-widget'),
'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 limage.', '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'),
'label' => esc_html__('Mise en page', 'category-grid-widget-for-elementor'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
@@ -167,7 +411,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'columns',
[
'label' => esc_html__('Nombre de colonnes', 'category-grid-widget'),
'label' => esc_html__('Nombre de colonnes', 'category-grid-widget-for-elementor'),
'type' => \Elementor\Controls_Manager::SELECT,
'options' => [
1 => '1',
@@ -185,19 +429,21 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'image_size',
[
'label' => esc_html__('Taille des images', 'category-grid-widget'),
'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'),
'medium' => esc_html__('Moyen (medium)', 'category-grid-widget'),
'large' => esc_html__('Grand (large)', 'category-grid-widget'),
'full' => esc_html__('Plein (full)', 'category-grid-widget'),
'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();
}
/**
@@ -207,85 +453,91 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
{
$settings = $this->get_settings_for_display();
// Récupérer les ID des catégories sélectionnées.
// 1) Récupérer les IDs 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);
$selected_ids = (array) $settings['categories'];
$selected_ids = array_map('absint', $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);
}
// 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);
}
// Éliminer les doublons d'ID au cas où.
$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
// 3) Préparer args pour get_terms()
$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
// 4) Récupérer et filtrer 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'])
: '';
$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;
}
if (empty($terms)) {
return;
}
// Si tri par nombre d'articles, calculer et trier manuellement
// 5) Tri manuel si order_by=count
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;
$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;
}
// Tri selon lordre choisi
usort($terms, function ($a, $b) use ($counts, $settings) {
$ca = $counts[$a->term_id];
$cb = $counts[$b->term_id];
@@ -293,35 +545,58 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
return 0;
}
if ('ASC' === $settings['order']) {
return ($ca < $cb) ? -1 : 1;
return $ca < $cb ? -1 : 1;
}
return ($ca > $cb) ? -1 : 1;
return $ca > $cb ? -1 : 1;
});
}
// Calcul de la classe grille
$columns = (int) $settings['columns'];
$grid_class = 'columns-' . $columns;
// 6) Affichage final
$grid_class = 'columns-' . absint($settings['columns']);
$is_below = ('below' === $settings['title_position']);
// Rendu HTML
echo '<div class="elementor-category-grid ' . esc_attr($grid_class) . '">';
foreach ($terms as $term) {
$name = esc_html($term->name);
$link = esc_url(get_term_link($term));
$image_url = esc_url($term->_image_url);
echo '<div class="category-card">';
echo "<a href=\"{$link}\">";
if ($image_url) {
foreach ($terms as $term) {
$thumb_id = get_term_meta($term->term_id, 'thumbnail_id', true);
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
// Ouvre la carte et ajoute la classe si titre en dessous
$card_class = 'category-card' . ($is_below ? ' title-below' : '');
echo '<div class="' . esc_attr($card_class) . '">';
// Image cliquable
echo '<a href="' . esc_url($link) . '">';
if ($thumb_id) {
echo '<div class="category-card-image">';
echo '<img src="' . $image_url . '" alt="' . $name . '">';
echo wp_get_attachment_image(
$thumb_id,
$settings['image_size'],
false,
[
'alt' => esc_attr($term->name),
'class' => 'category-card-img',
]
);
echo '</div>';
}
echo '<div class="category-card-name">' . $name . '</div>';
echo '</a>';
echo '</div>';
}
echo '</div>';
}
// Titre (overlay ou below) **à lintérieur** de la carte
if ($is_below) {
echo '<div class="category-card-name-below">';
echo '<a href="' . esc_url($link) . '">' . esc_html($term->name) . '</a>';
echo '</div>';
} else {
echo '<div class="category-card-name overlay">' . esc_html($term->name) . '</div>';
}
echo '</div>'; // .category-card
}
echo '</div>'; // .elementor-category-grid
}
}