nom, 'items' => [ID, ID…] ]. * Les items sont des réalisations ou des articles ; le premier est la carte * de devant dans le paquet. Nom court d'un article sur les cartes : méta `_yb_short_label`. * * Admin : Profil → Compétences (créer / renommer / réordonner / supprimer * des compétences, y ajouter des réalisations et des articles). */ /** Liste prête pour le gabarit : [ nom => [ [post, kind, name, tag, thumb, wipe], … ] ]. */ function yb2026_get_proofs() { $rows = get_option('yb_proofs', array()); $out = array(); foreach ((array) $rows as $row) { $links = array(); foreach ((array) ($row['items'] ?? array()) as $id) { $p = get_post((int) $id); if (!$p || $p->post_status !== 'publish' || !in_array($p->post_type, array('realisation', 'post'), true)) continue; $is_post = $p->post_type === 'post'; $short = $is_post ? (string) get_post_meta($p->ID, '_yb_short_label', true) : ''; $cat = $is_post ? get_the_category($p->ID) : array(); $links[] = array( 'post' => $p, 'kind' => $is_post ? 'article' : 'projet', 'name' => $short !== '' ? $short : yb2026_short_title($p), 'tag' => $is_post ? 'ARTICLE · ' . mb_strtoupper($cat ? $cat[0]->name : 'BLOG') : (string) get_post_meta($p->ID, '_yb_tag', true), 'thumb' => get_the_post_thumbnail_url($p, 'large'), 'wipe' => $is_post ? 'LECTURE' : mb_strtoupper(yb2026_short_title($p)), ); } if ($links && !empty($row['skill'])) $out[$row['skill']] = $links; } return $out; } /* ── Admin ──────────────────────────────────────────────────────────── */ add_action('admin_menu', function () { add_submenu_page('yb-profil', 'Compétences', 'Compétences', 'edit_pages', 'yb-competences', 'yb2026_proofs_page'); }); function yb2026_proofs_page() { $rows = (array) get_option('yb_proofs', array()); $reals = get_posts(array('post_type' => 'realisation', 'post_status' => 'publish', 'numberposts' => -1, 'orderby' => 'title', 'order' => 'ASC')); $arts = get_posts(array('post_type' => 'post', 'post_status' => 'publish', 'numberposts' => -1)); $label = function ($p) { $s = $p->post_type === 'post' ? (string) get_post_meta($p->ID, '_yb_short_label', true) : ''; return ($p->post_type === 'post' ? 'Article · ' : 'Réalisation · ') . ($s !== '' ? $s : yb2026_short_title($p)); }; $all = array(); foreach (array_merge($reals, $arts) as $p) $all[$p->ID] = $label($p); /* Contenus rattachés à aucune compétence : signalés en haut de page */ $used = array(); foreach ($rows as $r) foreach ((array) ($r['items'] ?? array()) as $id) $used[(int) $id] = true; $orphans = array_diff_key($all, $used); $options = function () use ($reals, $arts, $label) { $h = ''; foreach ($reals as $p) $h .= ''; $h .= ''; foreach ($arts as $p) $h .= ''; return $h . ''; }; $chip = function ($id, $row) use ($all) { return '' . esc_html($all[$id] ?? ('#' . $id)) . ' '; }; ?>

Compétences

Chaque compétence apparaît sur la page Compétences, dans « Chaque compétence a son projet ». Le premier contenu d'une compétence est la carte de devant ; avec plusieurs contenus, un clic ouvre le menu circulaire.

Compétences enregistrées.

Pas encore rattachés :

$r) : ?>

Noms courts des articles

Affichés sur les cartes et dans le menu circulaire (les titres d'articles sont souvent trop longs). Laisser vide pour utiliser le début du titre.

$skill !== '' ? $skill : 'Sans nom', 'items' => $items); } update_option('yb_proofs', $clean, false); foreach ((array) wp_unslash($_POST['yb_short_label'] ?? array()) as $id => $lbl) { $id = (int) $id; if (get_post_type($id) !== 'post') continue; $lbl = sanitize_text_field($lbl); $lbl === '' ? delete_post_meta($id, '_yb_short_label') : update_post_meta($id, '_yb_short_label', $lbl); } wp_safe_redirect(admin_url('admin.php?page=yb-competences&updated=1')); exit; });