JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
// Le sous-menu Fil d\'Ariane est enregistré dans settings-page.php (cet_add_admin_menu)
// ─────────────────────────────────────────────
// CSS admin pour la page Fil d\'Ariane
// ─────────────────────────────────────────────
function cet_breadcrumb_admin_styles( $hook ) {
if ( $hook !== 'templates_page_cet-breadcrumb' ) return;
?>
<style>
.cet-bc-section { background:#fff; border:1px solid #ddd; border-radius:4px; margin-bottom:20px; }
.cet-bc-section-head { padding:12px 16px; background:#f9f9f9; border-bottom:1px solid #eee; font-size:14px; font-weight:600; }
.cet-bc-section-body { padding:16px 20px; }
.cet-bc-row { display:flex; align-items:flex-start; gap:16px; margin-bottom:14px; }
.cet-bc-row label { min-width:200px; font-size:13px; padding-top:6px; color:#444; }
.cet-bc-row .cet-bc-input { flex:1; max-width:400px; }
.cet-bc-row input[type=text],
.cet-bc-row input[type=color],
.cet-bc-row select { width:100%; }
.cet-bc-row input[type=color] { width:60px; height:34px; padding:2px; cursor:pointer; }
.cet-bc-hint { font-size:11px; color:#888; margin-top:4px; }
.cet-bc-preview { background:#f5f5f5; border:1px solid #ddd; border-radius:4px; padding:12px 16px; margin-top:8px; font-size:13px; }
.cet-multiselect { width:100%; min-height:110px; font-size:13px; border:1px solid #ddd; border-radius:4px; }
.cet-bc-color-row { display:flex; align-items:center; gap:10px; }
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
function updatePreview() {
var sep = document.getElementById('bc_separator')?.value || '/';
var home = document.getElementById('bc_home_label')?.value || 'Accueil';
var color = document.getElementById('bc_color')?.value || '#555555';
var acolor = document.getElementById('bc_link_color')?.value || '#0073aa';
var size = document.getElementById('bc_font_size')?.value || '13';
var preview = document.getElementById('cet-bc-preview');
if (!preview) return;
preview.innerHTML =
'<span style="color:' + acolor + ';font-size:' + size + 'px;">' + home + '</span>' +
'<span style="color:' + color + ';font-size:' + size + 'px;margin:0 6px;">' + sep + '</span>' +
'<span style="color:' + color + ';font-size:' + size + 'px;">Catégorie</span>' +
'<span style="color:' + color + ';font-size:' + size + 'px;margin:0 6px;">' + sep + '</span>' +
'<span style="color:' + color + ';font-size:' + size + 'px;font-weight:500;">Article en cours</span>';
}
['bc_separator','bc_home_label','bc_color','bc_link_color','bc_font_size'].forEach(function(id) {
var el = document.getElementById(id);
if (el) el.addEventListener('input', updatePreview);
});
updatePreview();
});
</script>
<?php
}
add_action( 'admin_enqueue_scripts', 'cet_breadcrumb_admin_styles' );
// ─────────────────────────────────────────────
// Page de réglages Fil d\'Ariane
// ─────────────────────────────────────────────
function cet_render_breadcrumb_settings_form() {
cet_render_breadcrumb_page_inner();
}
function cet_render_breadcrumb_page() {
echo '<div class="wrap" style="max-width:780px;"><h1>Fil d\'Ariane</h1>';
cet_render_breadcrumb_page_inner();
echo '</div>';
}
function cet_render_breadcrumb_page_inner() {
// Sauvegarde
if ( isset( $_POST['cet_bc_nonce'] ) &&
wp_verify_nonce( $_POST['cet_bc_nonce'], 'cet_save_breadcrumb' ) &&
current_user_can( 'manage_options' ) ) {
$fields = array(
'cet_bc_home_label' => 'sanitize_text_field',
'cet_bc_home_url' => 'esc_url_raw',
'cet_bc_separator' => 'sanitize_text_field',
'cet_bc_depth' => 'absint',
'cet_bc_show_current' => 'absint',
'cet_bc_font_size' => 'absint',
'cet_bc_color' => 'sanitize_hex_color',
'cet_bc_link_color' => 'sanitize_hex_color',
'cet_bc_weight' => 'sanitize_key',
'cet_bc_auto_position'=> 'sanitize_key',
);
foreach ( $fields as $key => $sanitizer ) {
if ( isset( $_POST[ $key ] ) ) {
update_option( $key, call_user_func( $sanitizer, $_POST[ $key ] ) );
}
}
// Post types où le fil d'ariane s'affiche automatiquement
$auto_types = isset( $_POST['cet_bc_auto_types'] ) && is_array( $_POST['cet_bc_auto_types'] )
? array_map( 'sanitize_key', $_POST['cet_bc_auto_types'] )
: array();
update_option( 'cet_bc_auto_types', $auto_types );
// Pages exclues (IDs)
$excluded = isset( $_POST['cet_bc_excluded'] ) && is_array( $_POST['cet_bc_excluded'] )
? array_map( 'absint', $_POST['cet_bc_excluded'] )
: array();
update_option( 'cet_bc_excluded', $excluded );
echo '<div class="notice notice-success is-dismissible"><p>Réglages du fil d\'Ariane enregistrés.</p></div>';
}
// Valeurs actuelles
$home_label = get_option( 'cet_bc_home_label', 'Accueil' );
$home_url = get_option( 'cet_bc_home_url', home_url() );
$separator = get_option( 'cet_bc_separator', '/' );
$depth = (int) get_option( 'cet_bc_depth', 0 ); // 0 = tout le chemin
$show_current = (int) get_option( 'cet_bc_show_current', 1 );
$font_size = (int) get_option( 'cet_bc_font_size', 13 );
$color = get_option( 'cet_bc_color', '#555555' );
$link_color = get_option( 'cet_bc_link_color', '#0073aa' );
$weight = get_option( 'cet_bc_weight', 'normal' );
$auto_types = (array) get_option( 'cet_bc_auto_types', array() );
$auto_position = get_option( 'cet_bc_auto_position', 'before_content' );
$excluded_ids = (array) get_option( 'cet_bc_excluded', array() );
// Types de contenu (singles)
$builtin = get_post_types( array( 'public' => true, '_builtin' => true ), 'objects' );
$custom = get_post_types( array( 'public' => true, '_builtin' => false ), 'objects' );
$all_types = array_merge( $builtin, $custom );
foreach ( array( 'attachment', 'cet_template' ) as $ex ) unset( $all_types[ $ex ] );
// Archives : page des articles + toutes les taxonomies publiques
$blog_page_id = (int) get_option( 'page_for_posts' );
$blog_label = $blog_page_id ? get_the_title( $blog_page_id ) : 'Blog';
$all_archives = array(
'__blog__' => (object) array( 'labels' => (object) array( 'name' => 'Archive — Page des articles (' . $blog_label . ')' ) ),
);
$all_taxes = get_taxonomies( array( 'public' => true ), 'objects' );
foreach ( array( 'post_format', 'nav_menu', 'link_category', 'wp_theme', 'wp_template_part_area' ) as $ex ) {
unset( $all_taxes[ $ex ] );
}
foreach ( $all_taxes as $slug => $tax_obj ) {
$all_archives[ 'tax__' . $slug ] = (object) array(
'labels' => (object) array( 'name' => 'Archive — ' . $tax_obj->labels->name . ' (' . $slug . ')' ),
);
}
// Toutes les pages pour les exclusions
$all_pages = get_posts( array(
'post_type' => array_keys( $all_types ),
'posts_per_page' => 300,
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
) );
?>
<div class="wrap" style="max-width:780px;">
<h1>Fil d\'Ariane</h1>
<p>Configurez le fil d\'Ariane et assignez-le à vos types de contenu. Utilisez aussi le shortcode <code>[cet_breadcrumb]</code> directement dans Elementor.</p>
<form method="post">
<?php wp_nonce_field( 'cet_save_breadcrumb', 'cet_bc_nonce' ); ?>
<!-- SECTION : Contenu -->
<div class="cet-bc-section">
<div class="cet-bc-section-head">Contenu</div>
<div class="cet-bc-section-body">
<div class="cet-bc-row">
<label for="bc_home_label">Libellé de l'accueil</label>
<div class="cet-bc-input">
<input type="text" id="bc_home_label" name="cet_bc_home_label" value="<?php echo esc_attr( $home_label ); ?>">
<p class="cet-bc-hint">Texte affiché pour le premier maillon.</p>
</div>
</div>
<div class="cet-bc-row">
<label for="bc_home_url">URL de l'accueil</label>
<div class="cet-bc-input">
<input type="text" id="bc_home_url" name="cet_bc_home_url" value="<?php echo esc_attr( $home_url ); ?>">
<p class="cet-bc-hint">Par défaut : racine du site. Modifiable si votre page d'accueil est ailleurs.</p>
</div>
</div>
<div class="cet-bc-row">
<label for="bc_separator">Séparateur</label>
<div class="cet-bc-input">
<input type="text" id="bc_separator" name="cet_bc_separator" value="<?php echo esc_attr( $separator ); ?>" style="max-width:80px;">
<p class="cet-bc-hint">Exemples : / › » • | —</p>
</div>
</div>
<div class="cet-bc-row">
<label for="bc_depth">Profondeur maximale</label>
<div class="cet-bc-input">
<select id="bc_depth" name="cet_bc_depth" style="max-width:220px;">
<option value="0" <?php selected( $depth, 0 ); ?>>Tout le chemin</option>
<option value="2" <?php selected( $depth, 2 ); ?>>2 niveaux (Accueil › Page)</option>
<option value="3" <?php selected( $depth, 3 ); ?>>3 niveaux</option>
<option value="4" <?php selected( $depth, 4 ); ?>>4 niveaux</option>
</select>
<p class="cet-bc-hint">Limite le nombre de maillons affichés. Les niveaux intermédiaires sont remplacés par « … ».</p>
</div>
</div>
<div class="cet-bc-row">
<label>Page courante</label>
<div class="cet-bc-input">
<label style="font-size:13px;">
<input type="checkbox" name="cet_bc_show_current" value="1" <?php checked( $show_current, 1 ); ?>>
Afficher la page courante dans le fil
</label>
<p class="cet-bc-hint">Si décoché, le dernier maillon (page en cours) est omis.</p>
</div>
</div>
</div>
</div>
<!-- SECTION : Style -->
<div class="cet-bc-section">
<div class="cet-bc-section-head">Style</div>
<div class="cet-bc-section-body">
<div class="cet-bc-row">
<label for="bc_font_size">Taille de police</label>
<div class="cet-bc-input">
<input type="text" id="bc_font_size" name="cet_bc_font_size" value="<?php echo esc_attr( $font_size ); ?>" style="max-width:80px;"> px
</div>
</div>
<div class="cet-bc-row">
<label for="bc_weight">Graisse</label>
<div class="cet-bc-input">
<select id="bc_weight" name="cet_bc_weight" style="max-width:180px;">
<option value="normal" <?php selected( $weight, 'normal' ); ?>>Normale</option>
<option value="500" <?php selected( $weight, '500' ); ?>>Medium (500)</option>
<option value="bold" <?php selected( $weight, 'bold' ); ?>>Gras</option>
</select>
</div>
</div>
<div class="cet-bc-row">
<label>Couleur du texte</label>
<div class="cet-bc-input">
<div class="cet-bc-color-row">
<input type="color" id="bc_color" name="cet_bc_color" value="<?php echo esc_attr( $color ); ?>">
<span style="font-size:12px;color:#666;">Séparateurs et page courante</span>
</div>
</div>
</div>
<div class="cet-bc-row">
<label>Couleur des liens</label>
<div class="cet-bc-input">
<div class="cet-bc-color-row">
<input type="color" id="bc_link_color" name="cet_bc_link_color" value="<?php echo esc_attr( $link_color ); ?>">
<span style="font-size:12px;color:#666;">Maillons cliquables</span>
</div>
</div>
</div>
<div class="cet-bc-row">
<label>Aperçu</label>
<div class="cet-bc-input">
<div class="cet-bc-preview" id="cet-bc-preview"></div>
</div>
</div>
</div>
</div>
<!-- SECTION : Assignation automatique -->
<div class="cet-bc-section">
<div class="cet-bc-section-head">Affichage automatique</div>
<div class="cet-bc-section-body">
<div class="cet-bc-row">
<label>Types de contenu</label>
<div class="cet-bc-input">
<select name="cet_bc_auto_types[]" class="cet-multiselect" multiple style="min-height:160px;">
<optgroup label="Pages et types de contenu">
<?php foreach ( $all_types as $slug => $obj ) : ?>
<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( in_array( $slug, $auto_types, true ), true ); ?>>
<?php echo esc_html( $obj->labels->name ); ?> (<?php echo esc_html( $slug ); ?>)
</option>
<?php endforeach; ?>
</optgroup>
<optgroup label="Pages d\'archives">
<?php foreach ( $all_archives as $slug => $obj ) : ?>
<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( in_array( $slug, $auto_types, true ), true ); ?>>
<?php echo esc_html( $obj->labels->name ); ?>
</option>
<?php endforeach; ?>
</optgroup>
</select>
<p class="cet-bc-hint">Ctrl+clic pour sélectionner plusieurs. Le fil s'affiche automatiquement sur ces types et archives.</p>
</div>
</div>
<div class="cet-bc-row">
<label for="bc_auto_position">Position automatique</label>
<div class="cet-bc-input">
<select id="bc_auto_position" name="cet_bc_auto_position" style="max-width:320px;">
<option value="before_content" <?php selected( $auto_position, 'before_content' ); ?>>Avant le contenu</option>
<option value="after_h1" <?php selected( $auto_position, 'after_h1' ); ?>>Sous le titre H1</option>
<option value="after_wrapper" <?php selected( $auto_position, 'after_wrapper' ); ?>>Début du contenu principal (wrapper)</option>
<option value="wp_body_open" <?php selected( $auto_position, 'wp_body_open' ); ?>>Après l\'ouverture du body</option>
<option value="shortcode_only" <?php selected( $auto_position, 'shortcode_only' ); ?>>Shortcode uniquement — pas d\'injection automatique</option>
</select>
<p class="cet-bc-hint">Pour un placement précis, utilisez le shortcode <code>[cet_breadcrumb]</code> dans Elementor.</p>
</div>
</div>
<div class="cet-bc-row">
<label>Pages exclues</label>
<div class="cet-bc-input">
<select name="cet_bc_excluded[]" class="cet-multiselect" multiple style="min-height:140px;">
<?php
// Entrée spéciale : page des articles
$blog_page_id = (int) get_option( 'page_for_posts' );
if ( $blog_page_id ) : ?>
<optgroup label="Archives">
<option value="<?php echo $blog_page_id; ?>" <?php selected( in_array( $blog_page_id, $excluded_ids, true ), true ); ?>>
Page des articles — <?php echo esc_html( get_the_title( $blog_page_id ) ); ?>
</option>
</optgroup>
<?php endif; ?>
<optgroup label="Pages et contenus">
<?php foreach ( $all_pages as $p ) : ?>
<option value="<?php echo $p->ID; ?>" <?php selected( in_array( $p->ID, $excluded_ids, true ), true ); ?>>
<?php echo esc_html( $p->post_title ); ?>
— <?php echo esc_html( get_post_type_object( $p->post_type )->labels->singular_name ); ?>
</option>
<?php endforeach; ?>
</optgroup>
</select>
<p class="cet-bc-hint">Ces pages n'afficheront jamais le fil, même si leur type est coché ci-dessus.</p>
</div>
</div>
</div>
</div>
<?php submit_button( "Enregistrer le fil d\'Ariane" ); ?>
</form>
</div>
<?php
}
// ─────────────────────────────────────────────
// Génération du fil d\'Ariane (HTML)
// ─────────────────────────────────────────────
function cet_build_breadcrumb() {
$home_label = get_option( 'cet_bc_home_label', 'Accueil' );
$home_url = get_option( 'cet_bc_home_url', home_url() );
$separator = get_option( 'cet_bc_separator', '/' );
$max_depth = (int) get_option( 'cet_bc_depth', 0 );
$show_current = (int) get_option( 'cet_bc_show_current', 1 );
$font_size = (int) get_option( 'cet_bc_font_size', 13 );
$color = get_option( 'cet_bc_color', '#555555' );
$link_color = get_option( 'cet_bc_link_color', '#0073aa' );
$weight = get_option( 'cet_bc_weight', 'normal' );
// Construction des maillons : tableau de [ 'label' => '', 'url' => '' ou '' ]
$crumbs = array();
// Maillon 1 : Accueil
$crumbs[] = array( 'label' => $home_label, 'url' => $home_url );
if ( is_singular() ) {
$post = get_queried_object();
$post_type = get_post_type();
// Taxonomie principale si disponible (catégorie pour les posts)
if ( $post_type === 'post' ) {
$cats = get_the_category( $post->ID );
if ( $cats ) {
// Remonte la hiérarchie de la catégorie
$cat_chain = array();
$cat = $cats[0];
while ( $cat ) {
$cat_chain[] = array( 'label' => $cat->name, 'url' => get_term_link( $cat ) );
$cat = $cat->parent ? get_term( $cat->parent, 'category' ) : null;
}
foreach ( array_reverse( $cat_chain ) as $c ) {
$crumbs[] = $c;
}
}
} elseif ( $post_type !== 'page' ) {
// CPT : ajoute le label du post type
$pto = get_post_type_object( $post_type );
if ( $pto ) {
$archive_url = get_post_type_archive_link( $post_type );
$crumbs[] = array(
'label' => $pto->labels->name,
'url' => $archive_url ?: '',
);
}
}
// Pages parentes (hiérarchie page/sous-page)
if ( $post_type === 'page' && $post->post_parent ) {
$parents = array();
$parent_id = $post->post_parent;
while ( $parent_id ) {
$parent = get_post( $parent_id );
$parents[] = array( 'label' => get_the_title( $parent ), 'url' => get_permalink( $parent ) );
$parent_id = $parent->post_parent;
}
foreach ( array_reverse( $parents ) as $p ) {
$crumbs[] = $p;
}
}
// Maillon final : page courante
if ( $show_current ) {
$crumbs[] = array( 'label' => get_the_title( $post ), 'url' => '' );
}
} elseif ( is_category() || is_tag() || is_tax() ) {
$term = get_queried_object();
if ( $term->parent ) {
$parent_term = get_term( $term->parent, $term->taxonomy );
if ( $parent_term ) {
$crumbs[] = array( 'label' => $parent_term->name, 'url' => get_term_link( $parent_term ) );
}
}
$crumbs[] = array( 'label' => $term->name, 'url' => '' );
} elseif ( is_author() ) {
$crumbs[] = array( 'label' => get_queried_object()->display_name, 'url' => '' );
} elseif ( is_search() ) {
$crumbs[] = array( 'label' => 'Résultats pour : ' . get_search_query(), 'url' => '' );
} elseif ( is_404() ) {
$crumbs[] = array( 'label' => 'Page introuvable', 'url' => '' );
}
// Application de la profondeur max
if ( $max_depth > 0 && count( $crumbs ) > $max_depth ) {
$first = array_slice( $crumbs, 0, 1 );
$last = array_slice( $crumbs, -( $max_depth - 1 ) );
$crumbs = array_merge( $first, array( array( 'label' => '…', 'url' => '' ) ), $last );
}
// Rendu HTML
$sep_html = '<span class="cet-bc-sep" aria-hidden="true" style="margin:0 6px;color:' . esc_attr( $color ) . ';">'
. esc_html( $separator ) . '</span>';
$link_style = 'color:' . esc_attr( $link_color ) . ';text-decoration:none;font-size:' . (int) $font_size . 'px;font-weight:' . esc_attr( $weight ) . ';';
$current_style = 'color:' . esc_attr( $color ) . ';font-size:' . (int) $font_size . 'px;font-weight:' . esc_attr( $weight ) . ';';
$parts = array();
$total = count( $crumbs );
foreach ( $crumbs as $i => $crumb ) {
$is_last = ( $i === $total - 1 );
if ( ! empty( $crumb['url'] ) && ! $is_last ) {
$parts[] = '<a href="' . esc_url( $crumb['url'] ) . '" style="' . $link_style . '">'
. esc_html( $crumb['label'] ) . '</a>';
} else {
$aria = $is_last ? ' aria-current="page"' : '';
$parts[] = '<span style="' . $current_style . '"' . $aria . '>'
. esc_html( $crumb['label'] ) . '</span>';
}
}
$html = '<nav class="cet-breadcrumb" aria-label="Fil d\'Ariane" style="line-height:1.5;">';
$html .= implode( $sep_html, $parts );
$html .= '</nav>';
$html = apply_filters( 'cet_breadcrumb_html', $html );
return $html;
}
// ─────────────────────────────────────────────
// Shortcode [cet_breadcrumb]
// ─────────────────────────────────────────────
function cet_breadcrumb_shortcode( $atts ) {
return cet_build_breadcrumb();
}
add_shortcode( 'cet_breadcrumb', 'cet_breadcrumb_shortcode' );
// ─────────────────────────────────────────────
// Injection automatique selon les réglages
// ─────────────────────────────────────────────
// Injection via the_content (before_content / after_h1 / after_wrapper)
function cet_maybe_auto_breadcrumb_before_content( $content ) {
// Archives : le fil est injecté directement dans cet_render_archive_loop(), pas via the_content
if ( is_archive() || is_home() ) return $content;
if ( ! is_singular() ) return $content;
if ( ! cet_breadcrumb_is_active() ) return $content;
$position = get_option( 'cet_bc_auto_position', 'before_content' );
$bc = cet_build_breadcrumb();
switch ( $position ) {
case 'before_content':
return $bc . $content;
case 'after_h1':
// Insère après le premier <h1>…</h1> trouvé dans le contenu
if ( preg_match( '/<h1[^>]*>.*?<\/h1>/is', $content, $matches, PREG_OFFSET_CAPTURE ) ) {
$offset = $matches[0][1] + strlen( $matches[0][0] );
$content = substr( $content, 0, $offset ) . $bc . substr( $content, $offset );
} else {
// Pas de H1 trouvé : fallback avant le contenu
$content = $bc . $content;
}
return $content;
case 'after_wrapper':
// Insère après la première balise ouvrante de bloc Elementor principal
// (.elementor-widget-container ou .elementor-section de premier niveau)
if ( preg_match( '/(<div[^>]+class="[^"]*elementor-widget-container[^"]*"[^>]*>)/i', $content, $matches, PREG_OFFSET_CAPTURE ) ) {
$offset = $matches[0][1] + strlen( $matches[0][0] );
$content = substr( $content, 0, $offset ) . $bc . substr( $content, $offset );
} else {
$content = $bc . $content;
}
return $content;
default:
return $content;
}
}
add_filter( 'the_content', 'cet_maybe_auto_breadcrumb_before_content', 5 );
// Injection via wp_body_open
function cet_maybe_auto_breadcrumb_body_open() {
if ( get_option( 'cet_bc_auto_position', 'before_content' ) !== 'wp_body_open' ) return;
if ( ! is_singular() && ! is_archive() && ! is_home() ) return;
if ( ! cet_breadcrumb_is_active() ) return;
echo cet_build_breadcrumb();
}
add_action( 'wp_body_open', 'cet_maybe_auto_breadcrumb_body_open', 20 );
// Vérifie si le fil d\'Ariane doit s'afficher sur la page courante
function cet_breadcrumb_is_active() {
$auto_types = (array) get_option( 'cet_bc_auto_types', array() );
$excluded = (array) get_option( 'cet_bc_excluded', array() );
// Contexte : page des articles (is_home)
if ( is_home() ) {
if ( ! in_array( '__blog__', $auto_types, true ) ) return false;
// Vérifie l'exclusion via l'ID de la page définie dans Réglages > Lecture
$blog_page_id = (int) get_option( 'page_for_posts' );
if ( $blog_page_id && in_array( $blog_page_id, $excluded, true ) ) return false;
return true;
}
// Contexte : archives de taxonomies
if ( is_category() || is_tag() || is_tax() ) {
$taxonomy = get_queried_object()->taxonomy ?? '';
if ( ! in_array( 'tax__' . $taxonomy, $auto_types, true ) ) return false;
// Exclure un terme précis via son term_id stocké dans les exclusions
$term_id = (int) get_queried_object_id();
if ( $term_id && in_array( 'term_' . $term_id, $excluded, true ) ) return false;
return true;
}
// Contexte : single (post, page, CPT)
$post_type = get_post_type();
$post_id = get_the_ID();
if ( ! in_array( $post_type, $auto_types, true ) ) return false;
if ( in_array( $post_id, $excluded, true ) ) return false;
return true;
}