Logo Airess.net

Rapport de mission – Airess.net

1. Résumé exécutif

Cette mission, menée en tant que consultant WordPress externe pour Inbound Value (missionné par mon agence VirtuoseWeb.fr), s'est concentrée sur la réalisation de plusieurs développements clés pour le site Airess.net. Ces travaux incluent la création d'une section blog, la publication d'articles stratégiques, et la mise en place d'un tunnel de conversion avec une landing page et une synchronisation Odoo.

En complément de ces réalisations, un audit technique et fonctionnel a été effectué. Les principaux constats révèlent des performances perfectibles, une maintenance potentiellement complexifiée par l'utilisation de WPBakery, et l'absence de fonctionnalités comme le multilinguisme. Les recommandations s'orientent vers une optimisation technique continue, l'amélioration SEO, et une réflexion sur l'infrastructure d'hébergement pour assurer la pérennité et l'évolutivité du site.

2. Contexte & Périmètre

3. Travaux réalisés

4. Constats & Enjeux (Audit complémentaire)

5. Recommandations (Suite à l'audit)

Ces recommandations découlent de l'audit complémentaire et visent à améliorer durablement la performance, la visibilité et la maintenabilité du site Airess.net. Leur mise en œuvre dépendra des priorités et du budget du client.

6. Prochaines Étapes Suggérées

7. Annexes

Liens Utiles & Pages Créées

Rapports de Performance (PDF)

Ces rapports sont hébergés sur le site Airess.net.

Extraits de Code Documentés

JavaScript : Redirection Contact Form 7

Ce script, à placer via un plugin comme Fluent Snippet ou dans les options JS du thème, redirige l'utilisateur vers /welcome-page-01/ après la soumission réussie du formulaire Contact Form 7 avec l'ID 2505.

document.addEventListener('wpcf7mailsent', function(event) {
  // Ne s’exécute que pour le formulaire CF7 ID 2505 (à adapter si besoin)
  if (event.detail.contactFormId == 2505) { 
    // Redirection immédiate vers la page de remerciement
    window.location.href = '/welcome-page-01/'; 
    // Pour un délai de 3 secondes avant redirection, décommentez et utilisez :
    /*
    setTimeout(function() {
      window.location.href = '/welcome-page-01/';
    }, 3000);
    */
  }
}, false);

PHP : Activation Globale des Shortcodes ACF

Ce snippet, à ajouter dans le fichier functions.php de votre thème enfant ou via un plugin de snippets, garantit que les shortcodes ACF (comme [acf field="votre_champ"]) sont interprétés par WordPress.

add_action('acf/init', function(){
    // Vérifie si la fonction acf_update_setting existe
    if (function_exists('acf_update_setting')) {
        // Active le shortcode natif ACF
        acf_update_setting('enable_shortcode', true);
    }
    // Affiche un commentaire HTML pour débogage (optionnel, utile en développement)
    // echo "\n<!-- ACF shortcode activé via acf/init -->\n";
});

PHP : Shortcode ACF pour Lien Ressource Dynamique

Ce code (pour functions.php ou un plugin de snippets) crée le shortcode [lien_ressource_cliquable]. Il affiche un lien vers une URL stockée dans un champ ACF nommé url_lien_ressource du post/page courant.

// Enregistre le shortcode [lien_ressource_cliquable]
add_shortcode('lien_ressource_cliquable', function () {
    // Assurez-vous que la fonction get_field existe (ACF est actif)
    if (!function_exists('get_field')) {
        return '<!-- Erreur : ACF n\'est pas actif -->';
    }

    // Récupère l'URL du champ ACF nommé 'url_lien_ressource' pour le post courant
    $url = get_field('url_lien_ressource', get_the_ID());

    if ( ! empty($url) ) {
        // Retourne le lien HTML si l'URL est trouvée
        return sprintf(
            '<a href="%1$s" target="_blank" rel="noopener noreferrer" class="text-sky-600 hover:underline font-semibold text-lg inline-block py-2 px-4 bg-sky-500 text-white rounded-md hover:bg-sky-600 transition-colors duration-200"%gt;%2$s</a>',
            esc_url( $url ),
            esc_html( 'Votre ressource vous attend. Cliquez ici pour la télécharger.' )
        );
    }
    // Message si le champ est vide ou non défini (utile pour le débogage)
    return '<!-- ACF: Champ "url_lien_ressource" vide ou non défini pour ce contenu. -->';
});

WordPress Contenu : Utilisation du champ ACF comme bouton

Pour afficher une URL d'un champ ACF (ici, url_lien_ressource) sous forme de bouton directement dans l'éditeur de contenu WordPress (Gutenberg, Classique, ou un constructeur de page acceptant les shortcodes), utilisez :

<!-- Méthode 1: Shortcode ACF direct avec style inline -->
<a href="[acf field='url_lien_ressource']"
   style="display:inline-block; padding:0.6em 1.2em; background-color:#0073AA; color:#ffffff; text-decoration:none; border-radius:4px; font-weight:500;"
   target="_blank"
   rel="noopener noreferrer">
  Récupérer ma ressource via shortcode ACF
</a>

<!-- Méthode 2: Utilisation du shortcode PHP personnalisé [lien_ressource_cliquable] (voir snippet PHP ci-dessus) -->
<p>[lien_ressource_cliquable]</p>

La méthode 2 est plus propre si le style du bouton est défini dans le code PHP du shortcode ou via CSS.

PHP : Intégration Odoo avec Contact Form 7 (Backend)

Ce script PHP s'accroche à l'événement de soumission de Contact Form 7 (wpcf7_mail_sent). Pour un formulaire spécifique (dont le titre doit être adapté dans $form_title_cible), il récupère les données et tente de créer un lead dans Odoo via l'API JSON-RPC. Les identifiants de connexion Odoo ($odoo_url, $odoo_db, $odoo_username, $odoo_api_key) et les noms des champs CF7 ($posted_data['your-name'], etc.) DOIVENT ÊTRE CONFIGURÉS avec les valeurs réelles. Ce code est destiné à être placé dans le fichier functions.php du thème enfant ou via un plugin de gestion de snippets. Des logs d'erreur sont prévus pour le débogage.

/**
 * Hook Contact Form 7 submission to send data to Odoo CRM.
 */
add_action('wpcf7_mail_sent', function($contact_form) {
    // ✔️ Adapter le titre du formulaire CF7 ciblé
    $form_title_cible = 'NomDeVotreFormulaire'; // EXEMPLE: 'Formulaire Contact Landing Page'
    
    $form_title = $contact_form->title();  
    if ($form_title !== $form_title_cible) {
        // error_log("Formulaire non ciblé : " . $form_title . ". Cible : " . $form_title_cible); // Pour débogage
        return;
    }

    $submission = WPCF7_Submission::get_instance();
    if (!$submission) {
        error_log("Soumission CF7 non trouvée pour " . $form_title);
        return; 
    }
    $posted_data = $submission->get_posted_data();

    // 🌐 [CONFIGURATION ODOO - À ADAPTER IMPÉRATIVEMENT]
    $odoo_url      = 'https://VOTRE_INSTANCE.odoo.com'; // Remplacez par votre URL Odoo
    $odoo_db       = 'NOM_VOTRE_BDD';                   // Remplacez par le nom de votre BDD Odoo
    $odoo_username = 'UTILISATEUR_API_ODOO@example.com';// Remplacez par le login utilisateur Odoo lié à la clé API
    $odoo_api_key  = 'VOTRE_CLE_API_ODOO';              // Remplacez par votre clé API Odoo

    // ✔️ Adapter les noms des champs CF7 aux variables Odoo
    // Les noms comme 'your-name', 'your-email' sont les noms par défaut des champs CF7.
    // Vérifiez les noms exacts de vos champs dans l'éditeur CF7.
    $name      = isset($posted_data['your-name']) ? sanitize_text_field($posted_data['your-name']) : '';
    $company   = isset($posted_data['your-company']) ? sanitize_text_field($posted_data['your-company']) : ''; // Adaptez si le champ a un autre nom
    $email     = isset($posted_data['your-email']) ? sanitize_email($posted_data['your-email']) : '';
    $job_title = isset($posted_data['your-jobtitle']) ? sanitize_text_field($posted_data['your-jobtitle']) : ''; // Adaptez
    $phone     = isset($posted_data['your-tel']) ? sanitize_text_field($posted_data['your-tel']) : '';       // Adaptez

    $lead_title = $company ? "$company - $name" : $name;
    if (empty($lead_title) && !empty($email)) $lead_title = "Prospect: " . $email;
    if (empty($lead_title)) $lead_title = "Nouveau prospect via formulaire " . $form_title_cible;

    $odoo_lead_data = [
        'name'         => $lead_title,
        'contact_name' => $name,
        'partner_name' => $company,
        'email_from'   => $email,
        'phone'        => $phone,
        'function'     => $job_title,
        'description'  => "Lead généré depuis le formulaire WordPress: " . $form_title_cible . "\nSource: " . site_url() . "\n\nDonnées brutes:\n" . print_r($posted_data, true),
        // 'team_id'    => 1, // Optionnel: ID de l'équipe de vente si connue
        // 'user_id'    => ID_UTILISATEUR_COMMERCIAL_ODOO, // Optionnel
        // 'campaign_id' => ID_CAMPAGNE_ODOO, // Optionnel
        // 'medium_id'   => ID_MEDIUM_ODOO,   // Optionnel
        // 'source_id'   => ID_SOURCE_ODOO,   // Optionnel
    ];

    $endpoint = rtrim($odoo_url, '/') . '/jsonrpc';

    // 1️⃣ Authentification
    $auth_payload = ['jsonrpc' => '2.0', 'method' => 'call', 'params' => ['service' => 'common', 'method' => 'authenticate', 'args' => [$odoo_db, $odoo_username, $odoo_api_key, []]], 'id' => 'auth_' . uniqid()];
    $auth_response = wp_remote_post($endpoint, ['headers' => ['Content-Type' => 'application/json'], 'body' => json_encode($auth_payload), 'timeout' => 20, 'sslverify' => true]); // Mettre sslverify à true en production

    if (is_wp_error($auth_response)) {
        error_log("Erreur de connexion API Odoo (Auth): " . $auth_response->get_error_message());
        return;
    }
    $auth_body = wp_remote_retrieve_body($auth_response);
    $auth_result = json_decode($auth_body, true);
    $uid = $auth_result['result'] ?? 0;

    if (!$uid) {
        error_log("Échec de l'authentification API Odoo. Vérifiez URL/DB/Login/Clé. Réponse: " . $auth_body . " Payload: " . json_encode($auth_payload));
        return;
    }

    // 2️⃣ Création du Lead
    $create_payload = ['jsonrpc' => '2.0', 'method' => 'call', 'params' => ['service' => 'object', 'method' => 'execute_kw', 'args' => [$odoo_db, $uid, $odoo_api_key, 'crm.lead', 'create', [$odoo_lead_data]]], 'id' => 'create_' . uniqid()];
    $create_response = wp_remote_post($endpoint, ['headers' => ['Content-Type' => 'application/json'], 'body' => json_encode($create_payload), 'timeout' => 20, 'sslverify' => true]); // Mettre sslverify à true en production

    if (is_wp_error($create_response)) {
        error_log("Erreur API Odoo (Création Lead): " . $create_response->get_error_message());
        return;
    }
    $create_body = wp_remote_retrieve_body($create_response);
    $create_result = json_decode($create_body, true);

    if (!empty($create_result['error'])) {
        error_log("Erreur retournée par API Odoo lors de la création du lead: " . print_r($create_result['error'], true) . " Données envoyées: " . json_encode($odoo_lead_data));
        return;
    }
    
    $new_lead_id = $create_result['result'] ?? 0;
    if ($new_lead_id) {
        error_log("Lead Odoo créé avec succès. ID: " . $new_lead_id . " pour le formulaire: " . $form_title);
    } else {
        error_log("Échec de la création du lead Odoo. Réponse: " . $create_body . " Données envoyées: " . json_encode($odoo_lead_data));
    }
});

JavaScript : Synchronisation Odoo (Placeholder Client)

Ce script est un exemple de ce qui pourrait être fait côté client. La logique principale d'envoi à Odoo est gérée par le PHP ci-dessus. Ce JS client peut améliorer l'UX, par exemple en désactivant le bouton d'envoi après un clic pour éviter les soumissions multiples.

// Code JavaScript côté client pour interagir avec Fluent Snippet / Odoo.
// Ce script peut être utilisé pour des validations de formulaire avant envoi,
// l'affichage de messages de statut, ou d'autres interactions utilisateur.
// La logique principale de transmission des données à Odoo est gérée côté serveur (PHP).

console.log("Placeholder pour le script JS de synchronisation Odoo (côté client).");

// Exemple : Désactiver le bouton de soumission après un clic pour éviter les doubles envois.
/*
const odooForm = document.querySelector('#form-cf7-odoo'); // Sélecteur à adapter au formulaire CF7
if (odooForm) {
  odooForm.addEventListener('submit', function(e) {
    // Il est préférable de gérer la désactivation via l'événement 'wpcf7submit' de CF7
    // pour s'assurer que la validation CF7 a eu lieu.
  });
}

document.addEventListener( 'wpcf7submit', function( event ) {
  const formIdCible = '2505'; // ID du formulaire CF7 à cibler pour la désactivation du bouton
  if ( event.detail.contactFormId == formIdCible ) { // Adaptez l'ID si nécessaire
    const submitButton = event.target.querySelector('input[type="submit"]');
    if (submitButton) {
      submitButton.disabled = true;
      submitButton.value = 'Envoi en cours...';
    }
  }
}, false );
*/

CSS Additionnel (Extrait Représentatif)

L'ensemble du CSS additionnel (plus de 152 lignes) est inclus dans le fichier css/style.css. Il contient des styles pour WPBakery, des cartes d'articles, des listes personnalisées, etc. Voici un extrait :

/* === Style de la carte WPBakery (Post Grid) === */
.vc_grid-item-mini {
  background: #ffffff;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.05);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  height: 100%; /* Assure que les cartes ont la même hauteur dans une grille */
}

.vc_grid-item-mini .vc_gitem-zone-a { /* Zone image */
  border-top-left-radius: 8px;
  border-top-right-radius: 8px;
  overflow: hidden;
  aspect-ratio: 16 / 9; /* Maintient un ratio pour l'image */
}
.vc_grid-item-mini .vc_gitem-zone-a img {
  width: 100%;
  height: 100%;
  object-fit: cover; /* Assure que l'image couvre la zone */
}

.vc_grid-item-mini .vc_gitem-zone-c { /* Zone contenu */
  padding: 20px;
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.vc_grid-item-mini h4 { /* Titre de la carte */
  margin-bottom: 12px;
  font-weight: 600; /* semi-bold */
  line-height: 1.4;
  font-size: 1.15rem;
  color: #333;
}

.vc_gitem-post-data-source-post_excerpt p { /* Extrait */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; /* Limite à 3 lignes */
  overflow: hidden;
  margin-bottom: 16px;
  color: #555;
  font-size: 0.95rem;
  line-height: 1.6;
  flex-grow: 1; /* Permet à l'extrait de prendre l'espace disponible */
}

.vc_grid-item-mini .vc_btn3-container a { /* Bouton "Lire la suite" */
  align-self: flex-start;
  padding: 0.5em 1em;
  border-radius: 6px;
  background-color: #0ea5e9; /* sky-500 */
  color: white;
  text-decoration: none;
  font-weight: 500;
  transition: background-color 0.2s ease;
}
.vc_grid-item-mini .vc_btn3-container a:hover {
  background-color: #0284c7; /* sky-600 */
}

.vc_grid-item-mini:hover {
  transform: translateY(-5px);
  box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}

/* ... (plus de styles dans css/style.css) ... */