/* ==================================================================
* CHAMPS STRUCTURÉS NATIFS — Services & Projets (sans plugin)
* Stockage : post_meta tableaux sérialisés (_nv_service_data /
* _nv_projet_data). Rendu : templates/single-service.php &
* single-projet.php (rendu serveur centralisé).
* Appended: blocs metaboxes.
* ================================================================== */
/* ---------- Accesseurs ---------- */
function nv_get_service_data($post_id) {
$d = get_post_meta($post_id, '_nv_service_data', true);
return is_array($d) ? $d : [];
}
function nv_get_projet_data($post_id) {
$d = get_post_meta($post_id, '_nv_projet_data', true);
return is_array($d) ? $d : [];
}
/* ---------- Assets admin ---------- */
add_action('admin_enqueue_scripts', function ($hook) {
if (!in_array($hook, ['post.php', 'post-new.php'], true)) { return; }
if (!in_array(get_post_type(), ['service', 'projet'], true)) { return; }
wp_enqueue_media();
wp_enqueue_script('nv-admin-fields', get_theme_file_uri('assets/admin-fields.js'), [], NEOVENTIS_VERSION, true);
});
/* ---------- Boîtes méta ---------- */
add_action('add_meta_boxes', function () {
add_meta_box('nv-service-sections', 'Sections de la page service', 'nv_metabox_service_cb', 'service', 'normal', 'high');
add_meta_box('nv-projet-fiche', 'Fiche technique du projet', 'nv_metabox_projet_cb', 'projet', 'normal', 'high');
});
add_action('admin_head', function () {
if (!in_array(get_post_type(), ['service', 'projet'], true)) { return; }
echo '';
});
/* Rangée simple : un champ texte */
function _nv_row_simple($name_tpl, $value = '', $placeholder = '') {
printf(
'
✕
',
esc_attr($name_tpl),
esc_attr($value),
esc_attr($placeholder)
);
}
/* Rangée double : titre + texte ($idx = __I__ pour le gabarit JS) */
function _nv_row_double($base, $t = '', $d = '', $idx = '__I__') {
printf(
'',
esc_attr($base),
esc_attr($t),
esc_attr($d),
'',
esc_attr($idx)
);
}
/* ---------- Metabox Service ---------- */
function nv_metabox_service_cb($post) {
wp_nonce_field('nv_save_meta', 'nv_meta_nonce');
$d = nv_get_service_data($post->ID);
$cas = isset($d['cas_usage']) ? (array) $d['cas_usage'] : [];
$av = isset($d['avantages']) ? (array) $d['avantages'] : [];
$dem = isset($d['demarche']) ? (array) $d['demarche'] : [];
$comp = isset($d['comp']) ? array_map('intval', (array) $d['comp']) : [];
$others = get_posts([
'post_type' => 'service',
'numberposts' => -1,
'orderby' => 'menu_order',
'order' => 'ASC',
'post_status' => 'publish',
'exclude' => $post->ID,
]);
echo 'Le corps principal (« Notre approche ») reste l’éditeur Gutenberg ci-dessus. Le sous-titre du bandeau est le champ Extrait . Les sections ci-dessous sont générées automatiquement par le thème.
';
/* Cas d'usage */
echo 'Cas d’usage (liste) ';
foreach ($cas as $v) { _nv_row_simple('nv_sd[cas_usage][]', $v); }
echo '
'; _nv_row_simple('nv_sd[cas_usage][]'); echo ' ';
echo '
+ Ajouter un cas d’usage
';
/* Avantages */
echo 'Avantages de la prestation (cartes numérotées) ';
foreach ($av as $v) { _nv_row_simple('nv_sd[avantages][]', $v); }
echo '
'; _nv_row_simple('nv_sd[avantages][]'); echo ' ';
echo '
+ Ajouter un avantage
Laisser vide masque toute la section.
';
/* Démarche */
echo 'Notre démarche (étapes numérotées) ';
foreach ($dem as $i => $r) { _nv_row_double('nv_sd[demarche]', isset($r['t']) ? $r['t'] : '', isset($r['d']) ? $r['d'] : '', (string) $i); }
echo '
'; _nv_row_double('nv_sd[demarche]'); echo ' ';
echo '
+ Ajouter une étape
Laisser vide masque toute la section.
';
/* Services complémentaires */
echo 'Services complémentaires (liens) ';
if ($others) {
echo '
';
foreach ($others as $o) {
printf(
' %s ',
(int) $o->ID,
in_array((int) $o->ID, $comp, true) ? ' checked' : '',
esc_html($o->post_title)
);
}
echo '
';
} else {
echo '
Aucun autre service publié.
';
}
echo '
';
}
/* ---------- Metabox Projet ---------- */
function nv_metabox_projet_cb($post) {
wp_nonce_field('nv_save_meta', 'nv_meta_nonce');
$d = nv_get_projet_data($post->ID);
$champs = isset($d['champs']) ? (array) $d['champs'] : [];
$badges = isset($d['badges']) ? (array) $d['badges'] : [];
$logo = isset($d['logo_id']) ? absint($d['logo_id']) : 0;
echo 'La fiche technique alimente l’encadré latéral de la page projet. Le résumé du bandeau est le champ Extrait .
';
/* Champs simples */
echo 'Champs (libellé → valeur) ';
foreach ($champs as $i => $r) {
printf(
'
',
(int) $i,
esc_attr(isset($r['l']) ? $r['l'] : ''),
esc_attr(isset($r['v']) ? $r['v'] : '')
);
}
echo '
';
echo '
+ Ajouter un champ
';
/* Listes de badges */
echo 'Listes de badges (séparer par des virgules) ';
foreach ($badges as $i => $r) {
printf(
'
',
(int) $i,
esc_attr(isset($r['l']) ? $r['l'] : ''),
esc_attr(implode(', ', isset($r['tags']) ? (array) $r['tags'] : []))
);
}
echo '
';
echo '
+ Ajouter une liste
';
/* Logo client */
$src = $logo ? wp_get_attachment_image_url($logo, 'thumbnail') : '';
echo '';
}
/* ---------- Sauvegarde Service ---------- */
add_action('save_post_service', function ($post_id) {
if (!isset($_POST['nv_meta_nonce']) || !wp_verify_nonce(sanitize_key($_POST['nv_meta_nonce']), 'nv_save_meta')) { return; }
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; }
if (!current_user_can('edit_post', $post_id)) { return; }
$in = isset($_POST['nv_sd']) ? (array) wp_unslash($_POST['nv_sd']) : [];
$dem = [];
foreach ((array) (isset($in['demarche']) ? $in['demarche'] : []) as $row) {
$row = (array) $row;
$t = sanitize_text_field(isset($row['t']) ? $row['t'] : '');
$x = sanitize_text_field(isset($row['d']) ? $row['d'] : '');
if ('' !== $t || '' !== $x) { $dem[] = ['t' => $t, 'd' => $x]; }
}
$data = [
'cas_usage' => array_values(array_filter(array_map('sanitize_text_field', (array) (isset($in['cas_usage']) ? $in['cas_usage'] : [])), 'strlen')),
'avantages' => array_values(array_filter(array_map('sanitize_text_field', (array) (isset($in['avantages']) ? $in['avantages'] : [])), 'strlen')),
'demarche' => $dem,
'comp' => array_values(array_filter(array_map('absint', (array) (isset($in['comp']) ? $in['comp'] : [])))),
];
update_post_meta($post_id, '_nv_service_data', $data);
}, 10);
/* ---------- Sauvegarde Projet ---------- */
add_action('save_post_projet', function ($post_id) {
if (!isset($_POST['nv_meta_nonce']) || !wp_verify_nonce(sanitize_key($_POST['nv_meta_nonce']), 'nv_save_meta')) { return; }
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; }
if (!current_user_can('edit_post', $post_id)) { return; }
$in = isset($_POST['nv_pd']) ? (array) wp_unslash($_POST['nv_pd']) : [];
$champs = [];
foreach ((array) (isset($in['champs']) ? $in['champs'] : []) as $row) {
$row = (array) $row;
$l = sanitize_text_field(isset($row['l']) ? $row['l'] : '');
$v = sanitize_text_field(isset($row['v']) ? $row['v'] : '');
if ('' !== $l && '' !== $v) { $champs[] = ['l' => $l, 'v' => $v]; }
}
$badges = [];
foreach ((array) (isset($in['badges']) ? $in['badges'] : []) as $row) {
$row = (array) $row;
$l = sanitize_text_field(isset($row['l']) ? $row['l'] : '');
$tags = array_map('sanitize_text_field', array_values(array_filter(array_map('trim', explode(',', (string) (isset($row['tags']) ? $row['tags'] : ''))), 'strlen')));
if ('' !== $l && $tags) { $badges[] = ['l' => $l, 'tags' => $tags]; }
}
$data = [
'champs' => $champs,
'badges' => $badges,
'logo_id' => absint(isset($in['logo_id']) ? $in['logo_id'] : 0),
];
update_post_meta($post_id, '_nv_projet_data', $data);
}, 10);
https://dev.neoventis.fr/post-sitemap.xml
2026-06-03T08:06:29+00:00
https://dev.neoventis.fr/page-sitemap.xml
2026-08-24T07:27:21+00:00
https://dev.neoventis.fr/projet-sitemap.xml
2026-08-22T17:37:24+00:00
https://dev.neoventis.fr/service-sitemap.xml
2026-08-24T11:43:16+00:00
https://dev.neoventis.fr/category-sitemap.xml
2026-06-03T08:06:29+00:00
https://dev.neoventis.fr/service_univers-sitemap.xml
2026-08-24T11:43:16+00:00