1487 lines
59 KiB
PHP
1487 lines
59 KiB
PHP
<?php
|
|
|
|
namespace ESI_PEPPOL\controllers;
|
|
|
|
use ESI_PEPPOL\models\PEPPOL_Main_model;
|
|
|
|
class PEPPOL_Plugin {
|
|
|
|
|
|
public function init() {
|
|
$this->load_actions();
|
|
}
|
|
|
|
/**
|
|
* Méthode appelée lors de l'activation du plugin.
|
|
*
|
|
* Elle délègue au modèle principal la création de la table
|
|
* `esi_peppol_invoices` et détecte automatiquement les champs TVA.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function activate(): void {
|
|
// Création / mise à jour de la table principale des factures
|
|
PEPPOL_Main_model::create_table();
|
|
|
|
// Détection automatique des champs TVA (billing -> shipping)
|
|
self::auto_detect_vat_field_on_install();
|
|
}
|
|
|
|
public function desactivate() {
|
|
|
|
}
|
|
|
|
public static function register_routes() {
|
|
|
|
}
|
|
|
|
public function load_actions() {
|
|
// Enregistrement des hooks WooCommerce
|
|
if (class_exists(\ESI_PEPPOL\controllers\PEPPOL_Woocommerce_controller::class)) {
|
|
\ESI_PEPPOL\controllers\PEPPOL_Woocommerce_controller::register_hooks();
|
|
}
|
|
|
|
// Notice globale pour le numéro de TVA
|
|
\add_action('admin_notices', [self::class, 'maybe_show_vat_notice']);
|
|
|
|
// Ajouter une colonne "Statut Peppol" dans le listing des commandes WooCommerce
|
|
if (class_exists(\ESI_PEPPOL\controllers\PEPPOL_Woocommerce_controller::class)) {
|
|
\add_filter('manage_edit-shop_order_columns', [\ESI_PEPPOL\controllers\PEPPOL_Woocommerce_controller::class, 'add_peppol_status_column'], 20);
|
|
\add_filter('manage_woocommerce_page_wc-orders_columns', [\ESI_PEPPOL\controllers\PEPPOL_Woocommerce_controller::class, 'add_peppol_status_column'], 20);
|
|
\add_action('manage_shop_order_posts_custom_column', [\ESI_PEPPOL\controllers\PEPPOL_Woocommerce_controller::class, 'show_peppol_status_column'], 10, 2);
|
|
\add_action('manage_woocommerce_page_wc-orders_custom_column', [\ESI_PEPPOL\controllers\PEPPOL_Woocommerce_controller::class, 'show_peppol_status_column'], 10, 2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enregistre le menu et les sous-menus dans l'admin WordPress.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function add_admin_menu(): void {
|
|
// Page principale du plugin
|
|
add_menu_page(
|
|
__('ESI Peppol', 'esi_peppol'),
|
|
__('ESI Peppol', 'esi_peppol'),
|
|
'manage_options',
|
|
'esi-peppol',
|
|
[self::class, 'render_dashboard_page'],
|
|
'dashicons-migrate',
|
|
56
|
|
);
|
|
|
|
// Sous-menus (cibles des boutons de la page d'accueil)
|
|
add_submenu_page(
|
|
'esi-peppol',
|
|
__('Configuration', 'esi_peppol'),
|
|
__('Configuration', 'esi_peppol'),
|
|
'manage_options',
|
|
'esi-peppol-settings',
|
|
[self::class, 'render_settings_page']
|
|
);
|
|
|
|
add_submenu_page(
|
|
'esi-peppol',
|
|
__('Journal', 'esi_peppol'),
|
|
__('Journal', 'esi_peppol'),
|
|
'manage_options',
|
|
'esi-peppol-logs',
|
|
[self::class, 'render_logs_page']
|
|
);
|
|
}
|
|
|
|
public static function load_filters() {
|
|
|
|
}
|
|
|
|
public function load_shortcodes() {
|
|
|
|
}
|
|
|
|
public function load_frontend_assets() {
|
|
self::enqueue_admin_assets();
|
|
self::enqueue_front_assets();
|
|
}
|
|
|
|
public function load_admin_assets() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Page d'accueil du plugin (dashboard interne ESI Peppol).
|
|
*
|
|
* Contient un texte explicatif et deux boutons vers les sous-menus.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function render_dashboard_page(): void {
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die(esc_html__('Vous n\'avez pas les permissions nécessaires pour accéder à cette page.', 'esi_peppol'));
|
|
}
|
|
|
|
$settings_url = admin_url('admin.php?page=esi-peppol-settings');
|
|
$logs_url = admin_url('admin.php?page=esi-peppol-logs');
|
|
|
|
$template = ESI_PEPPOL_DIR . 'templates/admin/dashboard.php';
|
|
|
|
if (file_exists($template)) {
|
|
/** @noinspection PhpIncludeInspection */
|
|
include $template;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Page de configuration (stub pour l'instant).
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function render_settings_page(): void {
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die(esc_html__('Vous n\'avez pas les permissions nécessaires pour accéder à cette page.', 'esi_peppol'));
|
|
}
|
|
|
|
$notice = null;
|
|
$error = null;
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = isset($_POST['esi_peppol_action'])
|
|
? sanitize_text_field(wp_unslash($_POST['esi_peppol_action']))
|
|
: '';
|
|
|
|
if (!isset($_POST['esi_peppol_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['esi_peppol_nonce'])), 'esi_peppol_save_settings')) {
|
|
$error = __('Sécurité : nonce invalide.', 'esi_peppol');
|
|
} else {
|
|
$api_key = isset($_POST['esi_peppol_api_key']) ? sanitize_text_field(wp_unslash($_POST['esi_peppol_api_key'])) : '';
|
|
$password = isset($_POST['esi_peppol_password']) ? sanitize_text_field(wp_unslash($_POST['esi_peppol_password'])) : '';
|
|
$email = isset($_POST['esi_peppol_email']) ? sanitize_email(wp_unslash($_POST['esi_peppol_email'])) : '';
|
|
$logo_email_id = isset($_POST['esi_peppol_logo_email_id']) ? absint(wp_unslash($_POST['esi_peppol_logo_email_id'])) : 0;
|
|
|
|
// Sauvegarde systématique des options
|
|
update_option('esi_peppol_api_key', $api_key);
|
|
update_option('esi_peppol_password', $password);
|
|
update_option('esi_peppol_email', $email);
|
|
update_option('esi_peppol_logo_email_id', $logo_email_id);
|
|
|
|
if ($action === 'save') {
|
|
$notice = __('Paramètres enregistrés avec succès.', 'esi_peppol');
|
|
} elseif ($action === 'test') {
|
|
// Ici on pourrait appeler un endpoint de "ping" de l\'API ESIPeppol.
|
|
// Pour l\'instant : simple message de succès basé sur la présence des credentials.
|
|
if ($api_key && $password) {
|
|
$notice = __('Test de connexion simulé : les identifiants semblent corrects (vérification API réelle à implémenter).', 'esi_peppol');
|
|
} else {
|
|
$error = __('Veuillez renseigner l\'API Key et le Password avant de tester la connexion.', 'esi_peppol');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$template = ESI_PEPPOL_DIR . 'templates/admin/settings.php';
|
|
|
|
if (file_exists($template)) {
|
|
/** @noinspection PhpIncludeInspection */
|
|
include $template;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Page de journal / logs (stub pour l'instant).
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function render_logs_page(): void {
|
|
if (!current_user_can('manage_options')) {
|
|
wp_die(esc_html__('Vous n\'avez pas les permissions nécessaires pour accéder à cette page.', 'esi_peppol'));
|
|
}
|
|
|
|
// Vérifier si on affiche la page de détail
|
|
$detail_id = isset($_GET['detail']) ? (int) $_GET['detail'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
|
|
if ($detail_id > 0) {
|
|
// Afficher la page de détail
|
|
$row = PEPPOL_Main_model::get_by_id($detail_id);
|
|
|
|
if (!$row) {
|
|
wp_die(esc_html__('Aucun enregistrement Peppol trouvé pour cet ID.', 'esi_peppol'));
|
|
}
|
|
|
|
// Préparer les données pour l'affichage (même logique que ajax_get_log_details)
|
|
$data_sent = $row->data_sent ?? null;
|
|
$response_data = $row->response_data ?? null;
|
|
|
|
// Formater les données JSON si possible
|
|
$data_sent_formatted = '';
|
|
if ($data_sent) {
|
|
if (is_array($data_sent) || is_object($data_sent)) {
|
|
$data_sent_formatted = wp_json_encode($data_sent, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
} else {
|
|
$data_sent_formatted = (string) $data_sent;
|
|
}
|
|
}
|
|
|
|
$response_data_formatted = '';
|
|
if ($response_data) {
|
|
if (is_array($response_data) || is_object($response_data)) {
|
|
$response_data_formatted = wp_json_encode($response_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
} else {
|
|
$response_data_formatted = (string) $response_data;
|
|
}
|
|
}
|
|
|
|
// Extraire les informations financières depuis data_sent
|
|
$invoice_totals = null;
|
|
$vat_totals = null;
|
|
$customer_data = null;
|
|
|
|
if ($data_sent && (is_array($data_sent) || is_object($data_sent))) {
|
|
// Convertir en tableau si c'est un objet
|
|
$data_array = is_object($data_sent) ? (array) $data_sent : $data_sent;
|
|
|
|
// Extraire invoice_totals
|
|
if (isset($data_array['invoice_totals']) && is_array($data_array['invoice_totals'])) {
|
|
$invoice_totals = $data_array['invoice_totals'];
|
|
}
|
|
|
|
// Extraire vat_totals
|
|
if (isset($data_array['vat_totals']) && is_array($data_array['vat_totals'])) {
|
|
$vat_totals = $data_array['vat_totals'];
|
|
}
|
|
|
|
// Extraire les données client (buyer)
|
|
if (isset($data_array['parties']['buyer']) && is_array($data_array['parties']['buyer'])) {
|
|
$customer_data = $data_array['parties']['buyer'];
|
|
}
|
|
}
|
|
|
|
// Récupérer les informations de la commande si disponible
|
|
$order_info = null;
|
|
$order_totals = null;
|
|
if (!empty($row->id_order)) {
|
|
$order = wc_get_order($row->id_order);
|
|
if ($order) {
|
|
$order_info = [
|
|
'id' => $order->get_id(),
|
|
'number' => $order->get_order_number(),
|
|
'status' => $order->get_status(),
|
|
'total' => $order->get_total(),
|
|
'billing_name' => trim($order->get_billing_first_name() . ' ' . $order->get_billing_last_name()),
|
|
'billing_email' => $order->get_billing_email(),
|
|
'billing_phone' => $order->get_billing_phone(),
|
|
'billing_company' => $order->get_billing_company(),
|
|
'billing_address_1' => $order->get_billing_address_1(),
|
|
'billing_address_2' => $order->get_billing_address_2(),
|
|
'billing_city' => $order->get_billing_city(),
|
|
'billing_postcode' => $order->get_billing_postcode(),
|
|
'billing_country' => $order->get_billing_country(),
|
|
'billing_vat_number' => $order->get_meta('_billing_vat_number') ?: '',
|
|
'edit_link' => get_edit_post_link($row->id_order),
|
|
];
|
|
|
|
// Calculer les totaux depuis la commande si invoice_totals n'est pas disponible
|
|
if (!$invoice_totals) {
|
|
$order_totals = [
|
|
'total_amount_excluding_vat' => $order->get_total() - $order->get_total_tax(),
|
|
'total_vat_amount' => $order->get_total_tax(),
|
|
'total_amount_including_vat' => $order->get_total(),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extraire le message d'erreur depuis response_data
|
|
$error_message_to_display = $row->message ?? '';
|
|
|
|
if (!empty($response_data) && (is_array($response_data) || is_object($response_data))) {
|
|
// Convertir en tableau si c'est un objet
|
|
$error_data = is_object($response_data) ? (array) $response_data : $response_data;
|
|
|
|
if (is_array($error_data)) {
|
|
// Structure avec error.message
|
|
if (isset($error_data['error']['message'])) {
|
|
$error_message_to_display = (string) $error_data['error']['message'];
|
|
}
|
|
// Structure avec details.validation_error
|
|
elseif (isset($error_data['details']['validation_error'])) {
|
|
$error_message_to_display = (string) $error_data['details']['validation_error'];
|
|
}
|
|
// Structure avec validation_error directement
|
|
elseif (isset($error_data['validation_error'])) {
|
|
$error_message_to_display = (string) $error_data['validation_error'];
|
|
}
|
|
// Structure avec message directement
|
|
elseif (isset($error_data['message'])) {
|
|
$error_message_to_display = (string) $error_data['message'];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Préparer les données pour le template
|
|
$log_data = [
|
|
'id' => $row->id,
|
|
'id_order' => $row->id_order ?? 0,
|
|
'order_info' => $order_info,
|
|
'document_id' => $row->document_id ?? '',
|
|
'peppol_document_id' => $row->peppol_document_id ?? '',
|
|
'status' => $row->status ?? '',
|
|
'success' => !empty($row->success),
|
|
'message' => $error_message_to_display,
|
|
'http_code' => $row->http_code ?? null,
|
|
'date_add' => $row->date_add ?? '',
|
|
'date_update' => $row->date_update ?? '',
|
|
'invoice_totals' => $invoice_totals ?? $order_totals,
|
|
'vat_totals' => $vat_totals,
|
|
'customer_data' => $customer_data,
|
|
'data_sent' => $data_sent_formatted,
|
|
'response_data' => $response_data_formatted,
|
|
];
|
|
|
|
$template = ESI_PEPPOL_DIR . 'templates/admin/logs-detail.php';
|
|
|
|
if (file_exists($template)) {
|
|
/** @noinspection PhpIncludeInspection */
|
|
include $template;
|
|
} else {
|
|
wp_die(esc_html__('Template de détail introuvable.', 'esi_peppol'));
|
|
}
|
|
} else {
|
|
// Afficher la liste des logs
|
|
// Récupération des dernières lignes de la table custom
|
|
$rows = PEPPOL_Main_model::get_recent(50);
|
|
|
|
$template = ESI_PEPPOL_DIR . 'templates/admin/logs.php';
|
|
|
|
if (file_exists($template)) {
|
|
/** @noinspection PhpIncludeInspection */
|
|
include $template;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function enqueue_admin_assets() {
|
|
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
|
|
|
|
// Ne charger les assets que sur les pages du plugin ou la page des commandes WooCommerce
|
|
$is_peppol_page = isset($_GET['page']) && strpos((string) $_GET['page'], 'esi-peppol') === 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
$is_orders_page = $screen && (
|
|
strpos((string) $screen->id, 'shop_order') !== false ||
|
|
strpos((string) $screen->id, 'woocommerce_page_wc-orders') !== false ||
|
|
$screen->id === 'edit-shop_order'
|
|
);
|
|
|
|
if (!$is_peppol_page && !$is_orders_page && $screen && strpos((string) $screen->base, 'esi-peppol') === false) {
|
|
return;
|
|
}
|
|
|
|
$suffix = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min';
|
|
|
|
// Styles admin généraux
|
|
wp_enqueue_style(
|
|
'esi-peppol-admin',
|
|
ESI_PEPPOL_URL . 'assets/css/admin.css',
|
|
[],
|
|
ESI_PEPPOL_VERSION
|
|
);
|
|
|
|
// DataTables (utilisé pour le tableau du journal des échanges)
|
|
// Utiliser le CDN DataTables pour une meilleure compatibilité avec WordPress
|
|
wp_enqueue_style(
|
|
'esi-peppol-datatables',
|
|
'https://cdn.datatables.net/1.13.8/css/jquery.dataTables.min.css',
|
|
['esi-peppol-admin'],
|
|
'1.13.8'
|
|
);
|
|
|
|
// S'assurer que jQuery est chargé en premier
|
|
wp_enqueue_script('jquery');
|
|
|
|
// Utiliser le CDN DataTables au lieu du fichier local pour éviter les problèmes de compatibilité
|
|
wp_enqueue_script(
|
|
'esi-peppol-datatables',
|
|
'https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js',
|
|
['jquery'],
|
|
'1.13.8',
|
|
true
|
|
);
|
|
|
|
// Enqueue wp.media pour la gestion de la médiathèque (logo email)
|
|
wp_enqueue_media();
|
|
|
|
// Script admin principal, dépendant de DataTables
|
|
wp_enqueue_script(
|
|
'esi-peppol-admin',
|
|
ESI_PEPPOL_URL . 'assets/js/admin.js',
|
|
['jquery', 'esi-peppol-datatables'],
|
|
ESI_PEPPOL_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_localize_script(
|
|
'esi-peppol-admin',
|
|
'esiPeppolAdmin',
|
|
[
|
|
'ajax_url' => admin_url('admin-ajax.php'),
|
|
'nonce' => wp_create_nonce('esi_peppol_test_connection'),
|
|
'logs_nonce_resend' => wp_create_nonce('esi_peppol_resend_invoice'),
|
|
'logs_nonce_status' => wp_create_nonce('esi_peppol_check_invoice_status'),
|
|
'logs_nonce_details' => wp_create_nonce('esi_peppol_get_log_details'),
|
|
'vat_detect_nonce' => wp_create_nonce('esi_peppol_detect_vat_fields'),
|
|
'vat_save_nonce' => wp_create_nonce('esi_peppol_save_vat_field'),
|
|
'i18n_missing' => __('Veuillez renseigner l\'API Key et le Password avant de tester la connexion.', 'esi_peppol'),
|
|
'i18n_success' => __('Connexion réussie à l\'API ESIPeppol.', 'esi_peppol'),
|
|
'i18n_error' => __('La connexion a échoué. Veuillez vérifier vos identifiants.', 'esi_peppol'),
|
|
'i18n_network' => __('Erreur de communication avec le serveur WordPress.', 'esi_peppol'),
|
|
'i18n_logs_ok_title' => __('OK', 'esi_peppol'),
|
|
'i18n_logs_ko_title' => __('Pas OK', 'esi_peppol'),
|
|
'i18n_logs_resend_ok' => __('Document renvoyé avec succès à l\'API ESIPeppol.', 'esi_peppol'),
|
|
'i18n_logs_resend_ko' => __('L\'envoi du document a échoué.', 'esi_peppol'),
|
|
'i18n_logs_status_lbl' => __('Statut actuel du document', 'esi_peppol'),
|
|
'i18n_logs_detail_lbl' => __('Détail retour API', 'esi_peppol'),
|
|
'i18n_logs_general_info' => __('Informations générales', 'esi_peppol'),
|
|
'i18n_logs_totals' => __('Totaux', 'esi_peppol'),
|
|
'i18n_logs_vat_details' => __('Détails TVA', 'esi_peppol'),
|
|
'i18n_logs_customer_data' => __('Données client', 'esi_peppol'),
|
|
'i18n_logs_data_sent' => __('Données envoyées', 'esi_peppol'),
|
|
'i18n_logs_response_data' => __('Données de réponse', 'esi_peppol'),
|
|
'i18n_logs_error' => __('Erreur lors du chargement des détails.', 'esi_peppol'),
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handler AJAX pour le test de connexion à l'API ESIPeppol.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function ajax_test_connection(): void {
|
|
if (!check_ajax_referer('esi_peppol_test_connection', 'nonce', false)) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Sécurité : nonce invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Vous n\'avez pas les permissions nécessaires pour effectuer ce test.', 'esi_peppol'),
|
|
],
|
|
403
|
|
);
|
|
}
|
|
|
|
$api_key = isset($_POST['api_key']) ? sanitize_text_field(wp_unslash($_POST['api_key'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
$password = isset($_POST['password']) ? sanitize_text_field(wp_unslash($_POST['password'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
|
|
if ($api_key === '' || $password === '') {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Veuillez renseigner l\'API Key et le Password avant de tester la connexion.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
$result = \ESI_PEPPOL\controllers\PEPPOL_peppol_controller::status_with_credentials($api_key, $password);
|
|
|
|
if (!empty($result['success'])) {
|
|
$company = '';
|
|
if (is_array($result['data'] ?? null) && isset($result['data']['company'])) {
|
|
$company = (string) $result['data']['company'];
|
|
}
|
|
|
|
$message = $company
|
|
? sprintf(__('Connexion réussie. Entreprise: %s', 'esi_peppol'), $company)
|
|
: __('Connexion réussie à l\'API ESIPeppol.', 'esi_peppol');
|
|
|
|
wp_send_json_success(
|
|
[
|
|
'message' => $message,
|
|
'http_code' => $result['http_code'] ?? 200,
|
|
]
|
|
);
|
|
}
|
|
|
|
$error_message = $result['message'] ?? '';
|
|
if ($error_message === '' && is_array($result['data'] ?? null) && isset($result['data']['message'])) {
|
|
$error_message = (string) $result['data']['message'];
|
|
}
|
|
|
|
if ($error_message === '') {
|
|
$error_message = __('La connexion a échoué. Veuillez vérifier vos identifiants.', 'esi_peppol');
|
|
}
|
|
|
|
wp_send_json_error(
|
|
[
|
|
'message' => $error_message,
|
|
'http_code' => $result['http_code'] ?? 0,
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handler AJAX pour renvoyer un document/facture vers l'API ESIPeppol
|
|
* à partir d'un ID de commande WooCommerce.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function ajax_resend_invoice(): void {
|
|
if (!check_ajax_referer('esi_peppol_resend_invoice', 'nonce', false)) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Sécurité : nonce invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
if (!current_user_can('manage_woocommerce') && !current_user_can('manage_options')) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Vous n\'avez pas les permissions nécessaires pour effectuer cette action.', 'esi_peppol'),
|
|
],
|
|
403
|
|
);
|
|
}
|
|
|
|
$order_id = isset($_POST['order_id']) ? (int) $_POST['order_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
|
|
if ($order_id <= 0) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Identifiant de commande invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
$order = wc_get_order($order_id);
|
|
|
|
if (!$order instanceof \WC_Order) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Commande introuvable.', 'esi_peppol'),
|
|
],
|
|
404
|
|
);
|
|
}
|
|
|
|
// Reconstruire le payload et renvoyer le document via l'API ESIPeppol
|
|
$payload = \ESI_PEPPOL\controllers\PEPPOL_peppol_controller::build_payload_from_order($order);
|
|
$result = \ESI_PEPPOL\controllers\PEPPOL_peppol_controller::upload_json($payload, $order_id);
|
|
|
|
// Mettre à jour quelques métadonnées sur la commande pour le suivi
|
|
$order->update_meta_data('_esi_peppol_last_manual_action', 'resend');
|
|
$order->update_meta_data('_esi_peppol_last_peppol_status', !empty($result['success']) ? 'success' : 'error');
|
|
$order->update_meta_data('_esi_peppol_last_http_code', $result['http_code'] ?? 0);
|
|
$order->update_meta_data('_esi_peppol_last_message', $result['message'] ?? '');
|
|
$order->save();
|
|
|
|
$detail_message = '';
|
|
if (is_array($result['data'] ?? null) && isset($result['data']['message'])) {
|
|
$detail_message = (string) $result['data']['message'];
|
|
}
|
|
|
|
if ($detail_message === '' && isset($result['message'])) {
|
|
$detail_message = (string) $result['message'];
|
|
}
|
|
|
|
$payload_response = [
|
|
'success' => !empty($result['success']),
|
|
'message' => !empty($result['success'])
|
|
? __('Document renvoyé avec succès à l\'API ESIPeppol.', 'esi_peppol')
|
|
: __('L\'envoi du document a échoué.', 'esi_peppol'),
|
|
'detail' => $detail_message,
|
|
'http_code' => $result['http_code'] ?? 0,
|
|
];
|
|
|
|
if (!empty($result['success'])) {
|
|
wp_send_json_success($payload_response);
|
|
}
|
|
|
|
wp_send_json_error($payload_response, $result['http_code'] ?? 400);
|
|
}
|
|
|
|
/**
|
|
* Handler AJAX pour consulter le statut courant d'un document Peppol
|
|
* à partir de l'ID de commande (lecture depuis la table custom).
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function ajax_check_invoice_status(): void {
|
|
if (!check_ajax_referer('esi_peppol_check_invoice_status', 'nonce', false)) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Sécurité : nonce invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
if (!current_user_can('manage_woocommerce') && !current_user_can('manage_options')) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Vous n\'avez pas les permissions nécessaires pour effectuer cette action.', 'esi_peppol'),
|
|
],
|
|
403
|
|
);
|
|
}
|
|
|
|
$order_id = isset($_POST['order_id']) ? (int) $_POST['order_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
|
|
if ($order_id <= 0) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Identifiant de commande invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
$row = \ESI_PEPPOL\models\PEPPOL_Main_model::get_by_order_id($order_id);
|
|
|
|
if (!$row) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Aucun enregistrement Peppol trouvé pour cette commande.', 'esi_peppol'),
|
|
],
|
|
404
|
|
);
|
|
}
|
|
|
|
$detail_message = '';
|
|
|
|
if (isset($row->response_data)) {
|
|
$data = $row->response_data;
|
|
|
|
if (is_array($data) && isset($data['message'])) {
|
|
$detail_message = (string) $data['message'];
|
|
} elseif (is_string($data) && $data !== '') {
|
|
$detail_message = $data;
|
|
}
|
|
}
|
|
|
|
if ($detail_message === '' && isset($row->message)) {
|
|
$detail_message = (string) $row->message;
|
|
}
|
|
|
|
$payload_response = [
|
|
'success' => !empty($row->success),
|
|
'status' => isset($row->status) ? (string) $row->status : '',
|
|
'message' => sprintf(
|
|
/* translators: %s: statut Peppol actuel */
|
|
__('Statut actuel du document : %s', 'esi_peppol'),
|
|
isset($row->status) ? (string) $row->status : __('inconnu', 'esi_peppol')
|
|
),
|
|
'detail' => $detail_message,
|
|
'http_code' => isset($row->http_code) ? (int) $row->http_code : 0,
|
|
];
|
|
|
|
if (!empty($row->success)) {
|
|
wp_send_json_success($payload_response);
|
|
}
|
|
|
|
// On renvoie tout de même une réponse structurée même en cas d'échec métier
|
|
wp_send_json_error($payload_response, 200);
|
|
}
|
|
|
|
/**
|
|
* Détecte automatiquement les champs TVA lors de l'installation.
|
|
* Limite la recherche aux champs billing et shipping dans cet ordre.
|
|
* Utilise la fonction helper PEPPOL_Woo_Helper::esi_detect_vat_fields_billing_shipping().
|
|
*
|
|
* @return string|null La clé du champ TVA détecté, ou null si aucun trouvé
|
|
*/
|
|
public static function auto_detect_vat_field_on_install(): ?string {
|
|
// Ne pas détecter si un champ est déjà sauvegardé
|
|
$existing_field = get_option('esi_peppol_vat_field_key', '');
|
|
if ($existing_field !== '') {
|
|
return $existing_field;
|
|
}
|
|
|
|
// Vérifier si WooCommerce est disponible
|
|
if (!function_exists('wc_get_orders')) {
|
|
// Marquer que la détection a été tentée (même si WooCommerce n'est pas disponible)
|
|
update_option('esi_peppol_vat_field_auto_detected', true);
|
|
return null;
|
|
}
|
|
|
|
// Récupérer les dernières commandes pour scanner les champs TVA
|
|
$orders = wc_get_orders([
|
|
'limit' => 50,
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
'status' => ['completed', 'processing', 'on-hold'],
|
|
]);
|
|
|
|
$vat_fields_found = [];
|
|
|
|
// Utiliser la fonction helper pour détecter les champs TVA dans chaque commande
|
|
foreach ($orders as $order) {
|
|
if (!$order instanceof \WC_Order) {
|
|
continue;
|
|
}
|
|
|
|
$fields = \ESI_PEPPOL\helpers\PEPPOL_Woo_Helper::esi_detect_vat_fields_billing_shipping($order);
|
|
|
|
// Compter les occurrences de chaque champ
|
|
foreach ($fields as $field) {
|
|
$key = $field['key'];
|
|
if (!isset($vat_fields_found[$key])) {
|
|
$vat_fields_found[$key] = [
|
|
'key' => $key,
|
|
'count' => 0,
|
|
'group' => $field['group'],
|
|
];
|
|
}
|
|
$vat_fields_found[$key]['count']++;
|
|
}
|
|
}
|
|
|
|
// Si aucun champ trouvé, marquer que la détection a été effectuée et retourner null
|
|
if (empty($vat_fields_found)) {
|
|
update_option('esi_peppol_vat_field_auto_detected', true);
|
|
return null;
|
|
}
|
|
|
|
// Trier : d'abord par groupe (billing avant shipping), puis par nombre d'occurrences
|
|
usort($vat_fields_found, function ($a, $b) {
|
|
// Priorité au groupe billing
|
|
if ($a['group'] === 'billing' && $b['group'] !== 'billing') {
|
|
return -1;
|
|
}
|
|
if ($a['group'] !== 'billing' && $b['group'] === 'billing') {
|
|
return 1;
|
|
}
|
|
// Si même groupe, trier par nombre d'occurrences
|
|
return $b['count'] - $a['count'];
|
|
});
|
|
|
|
// Prendre le premier champ trouvé (priorité billing)
|
|
$selected_field = reset($vat_fields_found);
|
|
$vat_field_key = $selected_field['key'];
|
|
|
|
// Sauvegarder le champ détecté
|
|
update_option('esi_peppol_vat_field_key', $vat_field_key);
|
|
|
|
// Marquer que la détection a été effectuée à l'installation
|
|
update_option('esi_peppol_vat_field_auto_detected', true);
|
|
|
|
return $vat_field_key;
|
|
}
|
|
|
|
/**
|
|
* Handler AJAX pour détecter les champs TVA dans les commandes WooCommerce.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function ajax_detect_vat_fields(): void {
|
|
if (!check_ajax_referer('esi_peppol_detect_vat_fields', 'nonce', false)) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Sécurité : nonce invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Vous n\'avez pas les permissions nécessaires pour effectuer cette action.', 'esi_peppol'),
|
|
],
|
|
403
|
|
);
|
|
}
|
|
|
|
// Récupérer les dernières commandes pour scanner les champs TVA
|
|
$orders = wc_get_orders([
|
|
'limit' => 50,
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
'status' => ['completed', 'processing', 'on-hold'],
|
|
]);
|
|
|
|
$vat_fields_found = [];
|
|
|
|
foreach ($orders as $order) {
|
|
if (!$order instanceof \WC_Order) {
|
|
continue;
|
|
}
|
|
|
|
// Utiliser la fonction helper pour scanner tous les champs TVA possibles
|
|
$vat_candidates = [];
|
|
|
|
// Scanner les meta keys contenant vat/tva
|
|
foreach ($order->get_meta_data() as $meta) {
|
|
$k = (string) $meta->key;
|
|
if (preg_match('/\b(vat|tva)\b/i', $k) || preg_match('/(vat|tva)/i', $k)) {
|
|
$v = $meta->value;
|
|
if (is_string($v)) {
|
|
$v = trim($v);
|
|
}
|
|
if (!empty($v)) {
|
|
if (!isset($vat_candidates[$k])) {
|
|
$vat_candidates[$k] = [
|
|
'key' => $k,
|
|
'value' => $v,
|
|
'count' => 0,
|
|
];
|
|
}
|
|
$vat_candidates[$k]['count']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Scanner les champs de checkout
|
|
if (function_exists('WC') && WC()->checkout()) {
|
|
$fields = WC()->checkout()->get_checkout_fields();
|
|
$patterns = ['vat', 'tva', 'btw', 'ust', 'mwst', 'piva', 'moms', 'mva'];
|
|
|
|
foreach ($fields as $group_fields) {
|
|
foreach ($group_fields as $key => $def) {
|
|
$k = strtolower($key);
|
|
$found = false;
|
|
|
|
foreach ($patterns as $p) {
|
|
if (strpos($k, $p) !== false) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$found) {
|
|
$label = strtolower((string) ($def['label'] ?? ''));
|
|
foreach ($patterns as $p) {
|
|
if (preg_match('/\b' . $p . '\b/i', $label)) {
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($found) {
|
|
foreach ([$key, '_' . $key] as $meta_key) {
|
|
$val = $order->get_meta($meta_key, true);
|
|
if (!empty($val)) {
|
|
if (!isset($vat_candidates[$meta_key])) {
|
|
$vat_candidates[$meta_key] = [
|
|
'key' => $meta_key,
|
|
'value' => trim((string) $val),
|
|
'count' => 0,
|
|
];
|
|
}
|
|
$vat_candidates[$meta_key]['count']++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ajouter les candidats trouvés
|
|
foreach ($vat_candidates as $candidate) {
|
|
$key = $candidate['key'];
|
|
if (!isset($vat_fields_found[$key])) {
|
|
$vat_fields_found[$key] = [
|
|
'key' => $key,
|
|
'count' => 0,
|
|
'sample_value' => $candidate['value'],
|
|
];
|
|
}
|
|
$vat_fields_found[$key]['count'] += $candidate['count'];
|
|
}
|
|
}
|
|
|
|
// Trier par nombre d'occurrences (décroissant)
|
|
usort($vat_fields_found, function ($a, $b) {
|
|
return $b['count'] - $a['count'];
|
|
});
|
|
|
|
wp_send_json_success([
|
|
'fields' => array_values($vat_fields_found),
|
|
'count' => count($vat_fields_found),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Handler AJAX pour sauvegarder le champ TVA sélectionné.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function ajax_save_vat_field(): void {
|
|
if (!check_ajax_referer('esi_peppol_save_vat_field', 'nonce', false)) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Sécurité : nonce invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Vous n\'avez pas les permissions nécessaires pour effectuer cette action.', 'esi_peppol'),
|
|
],
|
|
403
|
|
);
|
|
}
|
|
|
|
$vat_field_key = isset($_POST['vat_field_key']) ? sanitize_text_field(wp_unslash($_POST['vat_field_key'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
|
|
if ($vat_field_key === '') {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Clé de champ TVA invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
// Sauvegarder le champ sélectionné
|
|
update_option('esi_peppol_vat_field_key', $vat_field_key);
|
|
|
|
wp_send_json_success([
|
|
'message' => __('Champ TVA enregistré avec succès.', 'esi_peppol'),
|
|
'vat_field_key' => $vat_field_key,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Handler AJAX pour récupérer les détails complets d'un log Peppol
|
|
* à partir de l'ID du log.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function ajax_get_log_details(): void {
|
|
if (!check_ajax_referer('esi_peppol_get_log_details', 'nonce', false)) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Sécurité : nonce invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
if (!current_user_can('manage_woocommerce') && !current_user_can('manage_options')) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Vous n\'avez pas les permissions nécessaires pour effectuer cette action.', 'esi_peppol'),
|
|
],
|
|
403
|
|
);
|
|
}
|
|
|
|
$log_id = isset($_POST['log_id']) ? (int) $_POST['log_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
|
|
|
|
if ($log_id <= 0) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Identifiant de log invalide.', 'esi_peppol'),
|
|
],
|
|
400
|
|
);
|
|
}
|
|
|
|
$row = \ESI_PEPPOL\models\PEPPOL_Main_model::get_by_id($log_id);
|
|
|
|
if (!$row) {
|
|
wp_send_json_error(
|
|
[
|
|
'message' => __('Aucun enregistrement Peppol trouvé pour cet ID.', 'esi_peppol'),
|
|
],
|
|
404
|
|
);
|
|
}
|
|
|
|
// Préparer les données pour l'affichage
|
|
$data_sent = $row->data_sent ?? null;
|
|
$response_data = $row->response_data ?? null;
|
|
|
|
// Formater les données JSON si possible
|
|
$data_sent_formatted = '';
|
|
if ($data_sent) {
|
|
if (is_array($data_sent) || is_object($data_sent)) {
|
|
$data_sent_formatted = wp_json_encode($data_sent, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
} else {
|
|
$data_sent_formatted = (string) $data_sent;
|
|
}
|
|
}
|
|
|
|
$response_data_formatted = '';
|
|
if ($response_data) {
|
|
if (is_array($response_data) || is_object($response_data)) {
|
|
$response_data_formatted = wp_json_encode($response_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
} else {
|
|
$response_data_formatted = (string) $response_data;
|
|
}
|
|
}
|
|
|
|
// Extraire les informations financières depuis data_sent
|
|
$invoice_totals = null;
|
|
$vat_totals = null;
|
|
$customer_data = null;
|
|
|
|
if ($data_sent && (is_array($data_sent) || is_object($data_sent))) {
|
|
// Convertir en tableau si c'est un objet
|
|
$data_array = is_object($data_sent) ? (array) $data_sent : $data_sent;
|
|
|
|
// Extraire invoice_totals
|
|
if (isset($data_array['invoice_totals']) && is_array($data_array['invoice_totals'])) {
|
|
$invoice_totals = $data_array['invoice_totals'];
|
|
}
|
|
|
|
// Extraire vat_totals
|
|
if (isset($data_array['vat_totals']) && is_array($data_array['vat_totals'])) {
|
|
$vat_totals = $data_array['vat_totals'];
|
|
}
|
|
|
|
// Extraire les données client (buyer)
|
|
if (isset($data_array['parties']['buyer']) && is_array($data_array['parties']['buyer'])) {
|
|
$customer_data = $data_array['parties']['buyer'];
|
|
}
|
|
}
|
|
|
|
// Récupérer les informations de la commande si disponible
|
|
$order_info = null;
|
|
$order_totals = null;
|
|
if (!empty($row->id_order)) {
|
|
$order = wc_get_order($row->id_order);
|
|
if ($order) {
|
|
$order_info = [
|
|
'id' => $order->get_id(),
|
|
'number' => $order->get_order_number(),
|
|
'status' => $order->get_status(),
|
|
'total' => $order->get_total(),
|
|
'billing_name' => trim($order->get_billing_first_name() . ' ' . $order->get_billing_last_name()),
|
|
'billing_email' => $order->get_billing_email(),
|
|
'billing_phone' => $order->get_billing_phone(),
|
|
'billing_company' => $order->get_billing_company(),
|
|
'billing_address_1' => $order->get_billing_address_1(),
|
|
'billing_address_2' => $order->get_billing_address_2(),
|
|
'billing_city' => $order->get_billing_city(),
|
|
'billing_postcode' => $order->get_billing_postcode(),
|
|
'billing_country' => $order->get_billing_country(),
|
|
'billing_vat_number' => $order->get_meta('_billing_vat_number') ?: '',
|
|
'edit_link' => get_edit_post_link($row->id_order),
|
|
];
|
|
|
|
// Calculer les totaux depuis la commande si invoice_totals n'est pas disponible
|
|
if (!$invoice_totals) {
|
|
$order_totals = [
|
|
'total_amount_excluding_vat' => $order->get_total() - $order->get_total_tax(),
|
|
'total_vat_amount' => $order->get_total_tax(),
|
|
'total_amount_including_vat' => $order->get_total(),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extraire le message d'erreur depuis response_data de la même manière que le template email
|
|
// pour garantir la cohérence entre la modal et l'email
|
|
$error_message_to_display = $row->message ?? '';
|
|
|
|
if (!empty($response_data) && (is_array($response_data) || is_object($response_data))) {
|
|
// Convertir en tableau si c'est un objet
|
|
$error_data = is_object($response_data) ? (array) $response_data : $response_data;
|
|
|
|
if (is_array($error_data)) {
|
|
// Structure avec error.message
|
|
if (isset($error_data['error']['message'])) {
|
|
$error_message_to_display = (string) $error_data['error']['message'];
|
|
}
|
|
// Structure avec details.validation_error
|
|
elseif (isset($error_data['details']['validation_error'])) {
|
|
$error_message_to_display = (string) $error_data['details']['validation_error'];
|
|
}
|
|
// Structure avec validation_error directement
|
|
elseif (isset($error_data['validation_error'])) {
|
|
$error_message_to_display = (string) $error_data['validation_error'];
|
|
}
|
|
// Structure avec message directement
|
|
elseif (isset($error_data['message'])) {
|
|
$error_message_to_display = (string) $error_data['message'];
|
|
}
|
|
}
|
|
}
|
|
|
|
$payload_response = [
|
|
'id' => $row->id,
|
|
'id_order' => $row->id_order ?? 0,
|
|
'order_info' => $order_info,
|
|
'document_id' => $row->document_id ?? '',
|
|
'peppol_document_id' => $row->peppol_document_id ?? '',
|
|
'status' => $row->status ?? '',
|
|
'success' => !empty($row->success),
|
|
'message' => $error_message_to_display,
|
|
'http_code' => $row->http_code ?? null,
|
|
'date_add' => $row->date_add ?? '',
|
|
'date_update' => $row->date_update ?? '',
|
|
'invoice_totals' => $invoice_totals ?? $order_totals,
|
|
'vat_totals' => $vat_totals,
|
|
'customer_data' => $customer_data,
|
|
'data_sent' => $data_sent_formatted,
|
|
'response_data' => $response_data_formatted,
|
|
];
|
|
|
|
wp_send_json_success($payload_response);
|
|
}
|
|
|
|
public static function enqueue_front_assets() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Ajoute un champ "Numéro de TVA de la boutique" dans les réglages
|
|
* WooCommerce > Général, juste après le code postal du magasin.
|
|
*
|
|
* @param array $settings
|
|
* @return array
|
|
*/
|
|
public static function filter_woocommerce_general_settings(array $settings): array {
|
|
$new_settings = [];
|
|
|
|
foreach ($settings as $setting) {
|
|
$new_settings[] = $setting;
|
|
|
|
if (isset($setting['id']) && $setting['id'] === 'woocommerce_store_postcode') {
|
|
$new_settings[] = [
|
|
'title' => __('Numéro de TVA de la boutique', 'esi_peppol'),
|
|
'desc' => __('Utilisé pour les factures Peppol (champ vendeur).', 'esi_peppol'),
|
|
'id' => 'woocommerce_store_vat_number',
|
|
'type' => 'text',
|
|
'default' => '',
|
|
'desc_tip' => true,
|
|
];
|
|
}
|
|
}
|
|
return $new_settings;
|
|
}
|
|
|
|
/**
|
|
* Retourne un message d'avertissement à propos du numéro de TVA
|
|
* de la boutique si celui-ci n'est pas encore renseigné dans
|
|
* les réglages WooCommerce, ou si aucun champ TVA client n'a été détecté.
|
|
*
|
|
* - Message d'invitation initial : aucune configuration ESI Peppol
|
|
* n'a encore été saisie.
|
|
* - Message de rappel : les identifiants ESI Peppol sont déjà
|
|
* enregistrés mais la TVA reste vide.
|
|
* - Message si aucun champ TVA client détecté : proposer la détection dans settings.
|
|
*
|
|
* @return string|null
|
|
*/
|
|
/**
|
|
* Récupère toutes les notices TVA à afficher
|
|
*
|
|
* @return array Tableau de notices, chaque notice contient 'message' et 'type' (info, warning, etc.)
|
|
*/
|
|
public static function get_vat_notice_message(): array {
|
|
$notices = [];
|
|
|
|
// Vérifier d'abord si un champ TVA client a été détecté
|
|
$vat_field_key = (string) get_option('esi_peppol_vat_field_key', '');
|
|
$auto_detected = (bool) get_option('esi_peppol_vat_field_auto_detected', false);
|
|
|
|
// Si aucun champ TVA détecté, proposer la détection dans settings
|
|
if ($vat_field_key === '' && $auto_detected) {
|
|
$settings_url = admin_url('admin.php?page=esi-peppol-settings');
|
|
$notices[] = [
|
|
'message' => sprintf(
|
|
/* translators: %s: URL vers la page de réglages */
|
|
__('Aucun champ TVA client n\'a été détecté automatiquement. Vous pouvez utiliser l\'outil de détection dans <a href="%s">ESI Peppol > Configuration</a> pour rechercher manuellement les champs TVA dans vos commandes.', 'esi_peppol'),
|
|
esc_url($settings_url)
|
|
),
|
|
'type' => 'warning'
|
|
];
|
|
}
|
|
|
|
$vat_number = (string) get_option('woocommerce_store_vat_number', '');
|
|
|
|
// Si le numéro de TVA est renseigné, on ne vérifie pas les autres notices liées au TVA
|
|
if ($vat_number === '') {
|
|
$api_key = (string) get_option('esi_peppol_api_key', '');
|
|
$password = (string) get_option('esi_peppol_password', '');
|
|
|
|
if ($api_key === '' && $password === '') {
|
|
// Invitation initiale
|
|
$notices[] = [
|
|
'message' => __(
|
|
'Pour finaliser la mise en place de Peppol, pensez à renseigner le numéro de TVA de la boutique dans WooCommerce > Réglages > Général.',
|
|
'esi_peppol'
|
|
),
|
|
'type' => 'info'
|
|
];
|
|
} else {
|
|
// Rappel après configuration du connecteur
|
|
$notices[] = [
|
|
'message' => __(
|
|
'Votre connecteur ESI Peppol est configuré, mais le numéro de TVA de la boutique n\'est pas encore renseigné dans WooCommerce > Réglages > Général. Sans ce numéro, les factures Peppol risquent d\'être incomplètes.',
|
|
'esi_peppol'
|
|
),
|
|
'type' => 'warning'
|
|
];
|
|
}
|
|
}
|
|
|
|
return $notices;
|
|
}
|
|
|
|
/**
|
|
* Affiche un message d'avertissement dans l'admin WordPress
|
|
* si le numéro de TVA de la boutique n'est pas renseigné ou
|
|
* si aucun champ TVA client n'a été détecté.
|
|
*
|
|
* Hooké sur admin_notices.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function maybe_show_vat_notice(): void {
|
|
if (!\current_user_can('manage_options')) {
|
|
return;
|
|
}
|
|
|
|
$vat_notices = self::get_vat_notice_message();
|
|
|
|
if (empty($vat_notices)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($vat_notices as $notice) {
|
|
$notice_class = 'notice-' . $notice['type'];
|
|
echo '<div class="notice ' . \esc_attr($notice_class) . '"><p>' .
|
|
\wp_kses_post($notice['message']) .
|
|
'</p></div>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vérifie l'état de configuration des settings ESI Peppol
|
|
*
|
|
* @return array Tableau avec l'état de chaque étape du setup
|
|
*/
|
|
public static function check_settings_status(): array {
|
|
$status = [
|
|
'vat_number' => [
|
|
'completed' => false,
|
|
'value' => '',
|
|
'label' => __('Numéro de TVA de la boutique', 'esi_peppol'),
|
|
'description' => __('Le numéro de TVA de votre boutique doit être renseigné dans WooCommerce > Réglages > Général', 'esi_peppol'),
|
|
'action_url' => admin_url('admin.php?page=wc-settings&tab=general'),
|
|
'action_label' => __('Configurer dans WooCommerce', 'esi_peppol'),
|
|
],
|
|
'vat_field' => [
|
|
'completed' => false,
|
|
'value' => '',
|
|
'label' => __('Champ TVA client', 'esi_peppol'),
|
|
'description' => __('Le champ utilisé pour récupérer le numéro de TVA du client dans les commandes', 'esi_peppol'),
|
|
'action_url' => admin_url('admin.php?page=esi-peppol-settings'),
|
|
'action_label' => __('Détecter le champ TVA', 'esi_peppol'),
|
|
],
|
|
'api_credentials' => [
|
|
'completed' => false,
|
|
'value' => '',
|
|
'label' => __('Identifiants API', 'esi_peppol'),
|
|
'description' => __('Votre clé API et mot de passe pour vous connecter à la plateforme ESI Peppol', 'esi_peppol'),
|
|
'action_url' => admin_url('admin.php?page=esi-peppol-settings'),
|
|
'action_label' => __('Configurer les identifiants', 'esi_peppol'),
|
|
],
|
|
'webhook' => [
|
|
'completed' => true, // Toujours disponible
|
|
'value' => '',
|
|
'label' => __('URL Webhook', 'esi_peppol'),
|
|
'description' => __('URL à configurer dans votre profil d\'entreprise sur la plateforme', 'esi_peppol'),
|
|
'action_url' => admin_url('admin.php?page=esi-peppol-settings'),
|
|
'action_label' => __('Voir l\'URL webhook', 'esi_peppol'),
|
|
],
|
|
'email' => [
|
|
'completed' => false,
|
|
'optional' => true,
|
|
'value' => '',
|
|
'label' => __('Email de notification', 'esi_peppol'),
|
|
'description' => __('Adresse email pour recevoir les notifications en cas d\'erreur d\'envoi des factures', 'esi_peppol'),
|
|
'action_url' => admin_url('admin.php?page=esi-peppol-settings'),
|
|
'action_label' => __('Configurer l\'email', 'esi_peppol'),
|
|
],
|
|
];
|
|
|
|
// Vérifier le numéro de TVA de la boutique
|
|
$vat_number = (string) get_option('woocommerce_store_vat_number', '');
|
|
if ($vat_number !== '') {
|
|
$status['vat_number']['completed'] = true;
|
|
$status['vat_number']['value'] = $vat_number;
|
|
}
|
|
|
|
// Vérifier le champ TVA client
|
|
$vat_field_key = (string) get_option('esi_peppol_vat_field_key', '');
|
|
if ($vat_field_key !== '') {
|
|
$status['vat_field']['completed'] = true;
|
|
$status['vat_field']['value'] = $vat_field_key;
|
|
}
|
|
|
|
// Vérifier les identifiants API
|
|
$api_key = (string) get_option('esi_peppol_api_key', '');
|
|
$password = (string) get_option('esi_peppol_password', '');
|
|
if ($api_key !== '' && $password !== '') {
|
|
$status['api_credentials']['completed'] = true;
|
|
$status['api_credentials']['value'] = substr($api_key, 0, 8) . '...'; // Masquer partiellement
|
|
}
|
|
|
|
// Récupérer l'URL webhook
|
|
if (class_exists(\ESI_PEPPOL\controllers\PEPPOL_Webhook_controller::class)) {
|
|
$webhook_url = \ESI_PEPPOL\controllers\PEPPOL_Webhook_controller::get_webhook_url();
|
|
$status['webhook']['value'] = $webhook_url;
|
|
}
|
|
|
|
// Vérifier l'email
|
|
$email = (string) get_option('esi_peppol_email', '');
|
|
if ($email !== '') {
|
|
$status['email']['completed'] = true;
|
|
$status['email']['value'] = $email;
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
public static function write_log($message, $level = 'INFO') {
|
|
$message = self::format_message_for_log($message);
|
|
$log_entry = sprintf("[%s] [%s] %s\n", date('Y-m-d H:i:s'), $level, $message);
|
|
error_log($log_entry);
|
|
}
|
|
|
|
/**
|
|
* Écrit un message dans un fichier debug.txt sous le dossier uploads
|
|
* Chemin: wp-content/uploads/crvi-debug.txt
|
|
*/
|
|
public static function write_debug_file($message, $level = 'INFO') {
|
|
$message = self::format_message_for_log($message);
|
|
$log_entry = sprintf("[%s] [%s] %s\n", date('Y-m-d H:i:s'), $level, $message);
|
|
|
|
$upload_dir = wp_upload_dir();
|
|
$file_path = trailingslashit($upload_dir['basedir']) . 'crvi-debug.txt';
|
|
|
|
// Tente d'écrire le log. En cas d'échec, fallback sur error_log
|
|
@file_put_contents($file_path, $log_entry, FILE_APPEND);
|
|
|
|
// Écrire également dans le log PHP (wp-content/debug.log si WP_DEBUG_LOG actif, sinon error_log du serveur)
|
|
error_log($log_entry);
|
|
}
|
|
|
|
private static function format_message_for_log($message) {
|
|
if (is_array($message)) {
|
|
return print_r($message, true);
|
|
} elseif (is_object($message)) {
|
|
return print_r($message, true);
|
|
} elseif (is_bool($message)) {
|
|
return $message ? 'TRUE' : 'FALSE';
|
|
} elseif (is_numeric($message)) {
|
|
return (string) $message;
|
|
} elseif (is_null($message)) {
|
|
return 'NULL';
|
|
} else {
|
|
return var_export($message, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Envoie un email de notification en cas d'erreur API Peppol.
|
|
*
|
|
* @param int $order_id ID de la commande WooCommerce.
|
|
* @param array $api_result Résultat de l'appel API (success, http_code, message, data).
|
|
* @param array $payload Payload envoyé à l'API (optionnel, pour debug).
|
|
*
|
|
* @return bool True si l'email a été envoyé avec succès, false sinon.
|
|
*/
|
|
public static function send_api_error_email(int $order_id, array $api_result, array $payload = []): bool {
|
|
// Vérifier si l'email est configuré
|
|
$email = \get_option('esi_peppol_email', '');
|
|
if (empty($email) || !\is_email($email)) {
|
|
return false;
|
|
}
|
|
|
|
// Récupérer la commande
|
|
$order = \wc_get_order($order_id);
|
|
if (!$order instanceof \WC_Order) {
|
|
return false;
|
|
}
|
|
|
|
// Préparer les variables pour le template
|
|
$order_number = $order->get_order_number();
|
|
$http_code = isset($api_result['http_code']) ? (int) $api_result['http_code'] : 0;
|
|
$error_message = isset($api_result['message']) && $api_result['message'] !== ''
|
|
? (string) $api_result['message']
|
|
: \__('Erreur inconnue lors de l\'envoi vers l\'API Peppol.', 'esi_peppol');
|
|
|
|
// Extraire les données d'erreur supplémentaires
|
|
$error_data = [];
|
|
if (isset($api_result['data']) && is_array($api_result['data'])) {
|
|
$error_data = $api_result['data'];
|
|
}
|
|
// Si la structure error existe directement dans api_result, l'inclure aussi
|
|
if (isset($api_result['error']) && is_array($api_result['error'])) {
|
|
$error_data['error'] = $api_result['error'];
|
|
// Si le message d'erreur n'est pas défini, utiliser celui de error.message
|
|
if (empty($error_message) && isset($api_result['error']['message'])) {
|
|
$error_message = (string) $api_result['error']['message'];
|
|
}
|
|
}
|
|
|
|
// Récupérer le template email
|
|
$template_path = ESI_PEPPOL_DIR . 'templates/email/api-error.php';
|
|
if (!file_exists($template_path)) {
|
|
return false;
|
|
}
|
|
|
|
// Capturer le contenu du template
|
|
ob_start();
|
|
include $template_path;
|
|
$email_body = ob_get_clean();
|
|
|
|
// Préparer les en-têtes de l'email
|
|
$site_name = \get_bloginfo('name');
|
|
$headers = [
|
|
'Content-Type: text/html; charset=UTF-8',
|
|
'From: ' . $site_name . ' <' . \get_option('admin_email') . '>',
|
|
];
|
|
|
|
// Sujet de l'email
|
|
$subject = \sprintf(
|
|
/* translators: %1$s: site name, %2$s: order number */
|
|
\__('[%1$s] Erreur API Peppol - Commande %2$s', 'esi_peppol'),
|
|
$site_name,
|
|
$order_number
|
|
);
|
|
|
|
// Envoyer l'email
|
|
$sent = \wp_mail($email, $subject, $email_body, $headers);
|
|
|
|
// Log de l'envoi
|
|
if ($sent) {
|
|
self::write_debug_file(
|
|
[
|
|
'event' => 'api_error_email_sent',
|
|
'order_id' => $order_id,
|
|
'email' => $email,
|
|
'http_code' => $http_code,
|
|
'error_message' => $error_message,
|
|
],
|
|
'INFO'
|
|
);
|
|
} else {
|
|
self::write_debug_file(
|
|
[
|
|
'event' => 'api_error_email_failed',
|
|
'order_id' => $order_id,
|
|
'email' => $email,
|
|
'error' => 'wp_mail returned false',
|
|
],
|
|
'ERROR'
|
|
);
|
|
}
|
|
|
|
return $sent;
|
|
}
|
|
|
|
} |