Un widget Elementor léger qui interroge le statut stock du produit en cours de boucle et affiche un badge conditionnel (visible uniquement quand le produit est hors stock, ou configurable pour afficher tous les statuts).
Deux modes d’affichage configurables dans les contrôles du widget : soit uniquement visible quand le produit est en rupture/réapprovisionnement (mode par défaut, idéal pour les cards), soit toujours visible avec les 4 statuts (en stock, stock faible, réappro, rupture).
Détection intelligente du stock faible : il croise get_stock_status() avec get_stock_quantity() et le seuil woocommerce_notify_low_stock_amount pour distinguer un produit « en stock » d’un produit « stock faible » — ce que WooCommerce ne fait pas nativement dans son statut.
Contrôles Elementor complets dans l’onglet Style : typographie, couleurs texte/fond par statut (via des onglets séparés), padding, border-radius, alignement, taille et espacement de l’icône. Tout est piloté par les selectors Elementor, donc ça fonctionne en responsive et dans le preview éditeur.
Compatibilité Loop Builder : le widget récupère le produit courant via wc_get_product( get_the_ID() ) en fallback du global $product, et affiche un aperçu « Rupture de stock » dans l’éditeur quand il n’y a pas de produit en contexte.
Pour l’installer : dépose le fichier dans wp-content/mu-plugins/.
Le widget apparaîtra dans la catégorie « WooCommerce » d’Elementor sous le nom « Wycan – Statut Stock », prêt à être glissé dans le template Loop Builder.
start_controls_section( 'section_content', [
'label' => 'Contenu',
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
] );
$this->add_control( 'display_mode', [
'label' => 'Affichage',
'type' => \Elementor\Controls_Manager::SELECT,
'default' => 'out_of_stock_only',
'options' => [
'out_of_stock_only' => 'Uniquement si rupture de stock',
'always' => 'Toujours (tous les statuts)',
],
] );
$this->add_control( 'label_in_stock', [
'label' => 'Libellé – En stock',
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'En stock',
'condition' => [ 'display_mode' => 'always' ],
] );
$this->add_control( 'label_low_stock', [
'label' => 'Libellé – Stock faible',
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'Stock faible',
'condition' => [ 'display_mode' => 'always' ],
] );
$this->add_control( 'label_on_backorder', [
'label' => 'Libellé – En réapprovisionnement',
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'En réapprovisionnement',
] );
$this->add_control( 'label_out_of_stock', [
'label' => 'Libellé – Rupture de stock',
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'Rupture de stock',
] );
$this->add_control( 'show_icon', [
'label' => 'Afficher une icône',
'type' => \Elementor\Controls_Manager::SWITCHER,
'default' => 'yes',
'label_on' => 'Oui',
'label_off' => 'Non',
] );
$this->end_controls_section();
/* ---- Section : Style – Badge ---- */
$this->start_controls_section( 'section_style_badge', [
'label' => 'Badge',
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
] );
$this->add_responsive_control( 'badge_alignment', [
'label' => 'Alignement',
'type' => \Elementor\Controls_Manager::CHOOSE,
'options' => [
'flex-start' => [ 'title' => 'Gauche', 'icon' => 'eicon-text-align-left' ],
'center' => [ 'title' => 'Centre', 'icon' => 'eicon-text-align-center' ],
'flex-end' => [ 'title' => 'Droite', 'icon' => 'eicon-text-align-right' ],
],
'default' => 'flex-start',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status' => 'justify-content: {{VALUE}};',
],
] );
$this->add_group_control( \Elementor\Group_Control_Typography::get_type(), [
'name' => 'badge_typography',
'selector' => '{{WRAPPER}} .wycan-stock-status__badge',
] );
/* -- Onglets couleurs par statut -- */
$this->start_controls_tabs( 'tabs_badge_colors' );
// En stock
$this->start_controls_tab( 'tab_in_stock', [
'label' => 'En stock',
'condition' => [ 'display_mode' => 'always' ],
] );
$this->add_control( 'color_in_stock', [
'label' => 'Couleur texte',
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#16a34a',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge--instock' => 'color: {{VALUE}};',
],
] );
$this->add_control( 'bg_in_stock', [
'label' => 'Couleur fond',
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#dcfce7',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge--instock' => 'background-color: {{VALUE}};',
],
] );
$this->end_controls_tab();
// Stock faible
$this->start_controls_tab( 'tab_low_stock', [
'label' => 'Stock faible',
'condition' => [ 'display_mode' => 'always' ],
] );
$this->add_control( 'color_low_stock', [
'label' => 'Couleur texte',
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#ca8a04',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge--lowstock' => 'color: {{VALUE}};',
],
] );
$this->add_control( 'bg_low_stock', [
'label' => 'Couleur fond',
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#fef9c3',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge--lowstock' => 'background-color: {{VALUE}};',
],
] );
$this->end_controls_tab();
// Réapprovisionnement
$this->start_controls_tab( 'tab_backorder', [ 'label' => 'Réappro.' ] );
$this->add_control( 'color_backorder', [
'label' => 'Couleur texte',
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#2563eb',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge--onbackorder' => 'color: {{VALUE}};',
],
] );
$this->add_control( 'bg_backorder', [
'label' => 'Couleur fond',
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#dbeafe',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge--onbackorder' => 'background-color: {{VALUE}};',
],
] );
$this->end_controls_tab();
// Rupture
$this->start_controls_tab( 'tab_out_of_stock', [ 'label' => 'Rupture' ] );
$this->add_control( 'color_out_of_stock', [
'label' => 'Couleur texte',
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#dc2626',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge--outofstock' => 'color: {{VALUE}};',
],
] );
$this->add_control( 'bg_out_of_stock', [
'label' => 'Couleur fond',
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#fee2e2',
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge--outofstock' => 'background-color: {{VALUE}};',
],
] );
$this->end_controls_tab();
$this->end_controls_tabs();
$this->add_responsive_control( 'badge_padding', [
'label' => 'Padding',
'type' => \Elementor\Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', 'em', '%' ],
'default' => [
'top' => '4',
'right' => '10',
'bottom' => '4',
'left' => '10',
'unit' => 'px',
],
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
'separator' => 'before',
] );
$this->add_control( 'badge_border_radius', [
'label' => 'Border Radius',
'type' => \Elementor\Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%' ],
'default' => [
'top' => '4',
'right' => '4',
'bottom' => '4',
'left' => '4',
'unit' => 'px',
],
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
] );
$this->end_controls_section();
/* ---- Section : Style – Icône ---- */
$this->start_controls_section( 'section_style_icon', [
'label' => 'Icône',
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
'condition' => [ 'show_icon' => 'yes' ],
] );
$this->add_responsive_control( 'icon_size', [
'label' => 'Taille',
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => [ 'px' ],
'range' => [ 'px' => [ 'min' => 8, 'max' => 32 ] ],
'default' => [ 'size' => 14, 'unit' => 'px' ],
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__icon svg' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
],
] );
$this->add_responsive_control( 'icon_gap', [
'label' => 'Espacement',
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => [ 'px' ],
'range' => [ 'px' => [ 'min' => 0, 'max' => 20 ] ],
'default' => [ 'size' => 6, 'unit' => 'px' ],
'selectors' => [
'{{WRAPPER}} .wycan-stock-status__badge' => 'gap: {{SIZE}}{{UNIT}};',
],
] );
$this->end_controls_section();
}
/* =====================================================================
RENDU
===================================================================== */
protected function render() {
global $product;
// Compatibilité Loop Builder : récupérer le produit courant
if ( ! $product instanceof \WC_Product ) {
$product = wc_get_product( get_the_ID() );
}
if ( ! $product instanceof \WC_Product ) {
if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
echo 'Rupture de stock (aperçu)';
}
return;
}
$settings = $this->get_settings_for_display();
$stock_status = $product->get_stock_status(); // instock | outofstock | onbackorder
$stock_qty = $product->get_stock_quantity();
$low_amount = absint( get_option( 'woocommerce_notify_low_stock_amount', 2 ) );
// Détection stock faible
$is_low_stock = ( 'instock' === $stock_status && $product->managing_stock() && null !== $stock_qty && $stock_qty <= $low_amount && $stock_qty > 0 );
$resolved_status = $is_low_stock ? 'lowstock' : $stock_status;
// Mode "rupture uniquement" : ne rien afficher si en stock / stock faible
if ( 'out_of_stock_only' === $settings['display_mode'] && ! in_array( $resolved_status, [ 'outofstock', 'onbackorder' ], true ) ) {
return;
}
// Mapping libellé / classe CSS / icône SVG
$map = [
'instock' => [
'label' => $settings['label_in_stock'] ?? 'En stock',
'icon' => '',
],
'lowstock' => [
'label' => $settings['label_low_stock'] ?? 'Stock faible',
'icon' => '',
],
'onbackorder' => [
'label' => $settings['label_on_backorder'] ?? 'En réapprovisionnement',
'icon' => '',
],
'outofstock' => [
'label' => $settings['label_out_of_stock'] ?? 'Rupture de stock',
'icon' => '',
],
];
if ( ! isset( $map[ $resolved_status ] ) ) {
return;
}
$data = $map[ $resolved_status ];
$class = 'wycan-stock-status__badge wycan-stock-status__badge--' . esc_attr( $resolved_status );
echo '';
echo '';
if ( 'yes' === $settings['show_icon'] ) {
echo '' . $data['icon'] . '';
}
echo '' . esc_html( $data['label'] ) . '';
echo '';
echo '';
}
protected function content_template() {
// Rendu côté serveur uniquement (données WooCommerce dynamiques)
}
}
$widgets_manager->register( new Wycan_Stock_Status_Widget() );
} );
/**
* Styles de base (inline, pas de fichier CSS externe)
*/
add_action( 'wp_head', function () {
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
?>

