Compare commits

...

5 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
5 changed files with 526 additions and 338 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,76 +1,141 @@
/* css/style.css */
/* -------------------------------------------------- */
/* Corrige le comportement “flex” sur mobile/tablette */
/* -------------------------------------------------- */
.e-con-inner {
display: block !important;
}
/* —————————————————————————————————————————— */
/* 1) Structure de la carte */
/* —————————————————————————————————————————— */
.elementor-category-grid .category-card {
position: relative;
overflow: hidden;
/* 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%;
}
/* pseudo-élément overlay */
/* Overlay via pseudo */
.elementor-category-grid .category-card::before {
content: "";
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
inset: 0;
background-color: transparent;
transition: opacity .3s ease;
pointer-events: none; /* laisse passer le clic */
pointer-events: none;
}
/* 1) Mise en place de la grille */
.elementor-category-grid {
display: grid;
gap: 1rem;
}
/* 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 */
.elementor-category-grid .category-card {
position: relative;
overflow: hidden;
height: 0;
padding-bottom: 40%; /* ratio 5:2 ajustez selon vos images */
/* border-radius: 8px; */
}
/* 3) Image pleine carte */
/* 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,184 +0,0 @@
<?php
/**
* Plugin Name: Elementor Category Grid Widget
* Plugin URI: https://git.mrraph.fr/WordPress/elementor-category-grid-widget
* Description: Grille responsive de catégories darticles avec image pour Elementor.
* Version: 1.2.1
* Author: MrRaph_
* Author URI: https://mrraph.photo
* Requires at least: 5.8
* Requires PHP: 7.0
* Text Domain: category-grid-widget
* 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 ) {
// 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.1',
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.1'
);
}
}
add_action( 'wp_enqueue_scripts', 'ccgw_enqueue_front_styles' );

View File

@@ -1,19 +1,17 @@
=== Elementor Category Grid Widget ===
=== 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.1
Tested up to: 6.8
Requires PHP: 7.0
Stable tag: 1.2.1
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.
Allows manual selection of categories to display, inclusion of subcategories, hiding categories without images, and control over number of columns, image sizes, style settings, and hover effects.
== Description ==
**Elementor Category Grid Widget** adds a simple yet powerful widget to Elementor for displaying your post categories in a grid:
**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
@@ -26,8 +24,8 @@ Allows manual selection of categories to display, inclusion of subcategories, hi
The widget uses WordPresss native category image feature (a “Category Image” field added in the admin).
== Installation ==
1. Upload the `elementor-category-grid-widget` folder to `/wp-content/plugins/`.
2. Activate **Elementor Category Grid Widget** in the **Plugins** menu.
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.
@@ -48,6 +46,15 @@ Yes, enable the **“Hide categories without image”** option in the widgets
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

View File

@@ -33,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');
}
/**
@@ -71,14 +71,14 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->start_controls_section(
'section_style_grid',
[
'label' => esc_html__('Grille', 'category-grid-widget'),
'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'),
'label' => esc_html__('Espacement (gap)', 'category-grid-widget-for-elementor'),
'type' => Controls_Manager::SLIDER,
'size_units' => ['px', 'em', '%'],
'range' => [
@@ -96,14 +96,14 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->start_controls_section(
'section_style_card',
[
'label' => esc_html__('Carte', 'category-grid-widget'),
'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'),
'label' => esc_html__('Fond de la carte', 'category-grid-widget-for-elementor'),
'type' => Controls_Manager::COLOR,
'default' => '',
'selectors' => [
@@ -115,14 +115,14 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
Group_Control_Typography::get_type(),
[
'name' => 'card_title_typography',
'label' => esc_html__('Typographie du titre', 'category-grid-widget'),
'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'),
'label' => esc_html__('Border radius', 'category-grid-widget-for-elementor'),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => ['px', '%'],
'selectors' => [
@@ -134,7 +134,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
\Elementor\Group_Control_Box_Shadow::get_type(),
[
'name' => 'card_box_shadow',
'label' => esc_html__('Ombre de la carte', 'category-grid-widget'),
'label' => esc_html__('Ombre de la carte', 'category-grid-widget-for-elementor'),
'selector' => '{{WRAPPER}} .category-card',
]
);
@@ -144,7 +144,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->start_controls_section(
'section_style_title',
[
'label' => esc_html__('Titre', 'category-grid-widget'),
'label' => esc_html__('Titre', 'category-grid-widget-for-elementor'),
'tab' => Controls_Manager::TAB_STYLE,
]
);
@@ -153,10 +153,10 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'disable_title_bg',
[
'label' => esc_html__('Désactiver le fond du titre', 'category-grid-widget'),
'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'),
'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',
'selectors' => [
@@ -170,7 +170,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'title_text_color',
[
'label' => esc_html__('Couleur du texte', 'category-grid-widget'),
'label' => esc_html__('Couleur du texte', 'category-grid-widget-for-elementor'),
'type' => Controls_Manager::COLOR,
'default' => '#ffffff',
'selectors' => [
@@ -183,7 +183,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'title_bg_color',
[
'label' => esc_html__('Fond du titre', 'category-grid-widget'),
'label' => esc_html__('Fond du titre', 'category-grid-widget-for-elementor'),
'type' => Controls_Manager::COLOR,
'default' => 'rgba(0,0,0,0.4)',
'selectors' => [
@@ -201,7 +201,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
Group_Control_Typography::get_type(),
[
'name' => 'card_title_typography',
'label' => esc_html__('Typographie du titre', 'category-grid-widget'),
'label' => esc_html__('Typographie du titre', 'category-grid-widget-for-elementor'),
'selector' => '{{WRAPPER}} .category-card-name',
]
);
@@ -212,7 +212,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->start_controls_section(
'section_style_hover',
[
'label' => esc_html__('Hover', 'category-grid-widget'),
'label' => esc_html__('Hover', 'category-grid-widget-for-elementor'),
'tab' => Controls_Manager::TAB_STYLE,
]
);
@@ -221,7 +221,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'hover_overlay_color',
[
'label' => esc_html__('Couleur de loverlay', 'category-grid-widget'),
'label' => esc_html__('Couleur de loverlay', 'category-grid-widget-for-elementor'),
'type' => Controls_Manager::COLOR,
'default' => 'rgba(0,0,0,0.3)',
'selectors' => [
@@ -235,7 +235,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'hover_overlay_opacity',
[
'label' => esc_html__('Opacité de loverlay', 'category-grid-widget'),
'label' => esc_html__('Opacité de loverlay', 'category-grid-widget-for-elementor'),
'type' => Controls_Manager::SLIDER,
'size_units' => ['%'],
'range' => [
@@ -253,7 +253,7 @@ class Elementor_Category_Grid_Widget extends \Elementor\Widget_Base
$this->add_control(
'title_hover_color',
[
'label' => esc_html__('Couleur du titre au hover', 'category-grid-widget'),
'label' => esc_html__('Couleur du titre au hover', 'category-grid-widget-for-elementor'),
'type' => Controls_Manager::COLOR,
'default' => '#ffffff',
'selectors' => [
@@ -274,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,
]
);
@@ -288,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'),
]
);
@@ -301,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'),
]
);
@@ -316,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'),
]
);
@@ -330,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',
]
@@ -345,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,
]
);
@@ -370,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',
@@ -388,13 +429,13 @@ 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',
]
@@ -412,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];
@@ -498,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
}
}