HEX
Server: Apache
System: Linux webm003.cluster115.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64
User: coinconse (191050)
PHP: 8.2.31
Disabled: _dyuweyrj4,_dyuweyrj4r,dl
Upload Files
File: /home/coinconse/www/wp-content/mu-plugins/serrurier-jouy-woocommerce.php
<?php
/**
 * Plugin Name: Serrurier Jouy WooCommerce Customizations
 * Description: Checkout legal consent, quote mode, custom numbering, and invoice generation.
 * Author: serrurier-jouy
 * Version: 1.1.0
 */

if (!defined('ABSPATH')) {
    exit;
}

add_action('plugins_loaded', 'serrurier_jouy_bootstrap_woocommerce_customizations', 20);

function serrurier_jouy_bootstrap_woocommerce_customizations(): void
{
    if (!class_exists('WooCommerce')) {
        return;
    }

    add_action('woocommerce_review_order_before_submit', 'serrurier_jouy_render_intervention_checkbox', 12);
    add_action('woocommerce_checkout_process', 'serrurier_jouy_validate_intervention_checkbox');
    add_action('woocommerce_checkout_create_order', 'serrurier_jouy_save_intervention_checkbox', 10, 2);
    add_action('woocommerce_admin_order_data_after_billing_address', 'serrurier_jouy_display_intervention_consent_in_admin');

    add_action('woocommerce_product_options_general_product_data', 'serrurier_jouy_add_quote_only_product_field');
    add_action('woocommerce_admin_process_product_object', 'serrurier_jouy_save_quote_only_product_field');
    add_filter('woocommerce_get_price_html', 'serrurier_jouy_maybe_replace_price_with_quote_label', 10, 2);
    add_filter('woocommerce_is_purchasable', 'serrurier_jouy_make_quote_products_not_purchasable', 10, 2);
    add_filter('woocommerce_loop_add_to_cart_link', 'serrurier_jouy_replace_loop_add_to_cart_for_quote_products', 10, 3);
    add_action('woocommerce_single_product_summary', 'serrurier_jouy_render_single_quote_button', 31);
    add_filter('woocommerce_add_to_cart_validation', 'serrurier_jouy_block_add_to_cart_for_quote_products', 10, 3);

    add_action('woocommerce_checkout_create_order', 'serrurier_jouy_attach_custom_order_number', 20, 2);
    add_action('woocommerce_new_order', 'serrurier_jouy_attach_custom_order_number_on_new_order', 20, 1);
    add_filter('woocommerce_order_number', 'serrurier_jouy_filter_order_number_display', 10, 2);

    add_action('woocommerce_order_status_completed', 'serrurier_jouy_generate_invoice_metadata', 10, 1);
    add_action('template_redirect', 'serrurier_jouy_maybe_render_invoice_page');
    add_action('woocommerce_view_order', 'serrurier_jouy_render_my_account_invoice_link', 30);
    add_action('woocommerce_email_after_order_table', 'serrurier_jouy_add_invoice_link_to_completed_email', 20, 4);
    add_filter('woocommerce_email_order_meta_fields', 'serrurier_jouy_add_invoice_number_to_email_meta', 10, 3);
}

function serrurier_jouy_render_intervention_checkbox(): void
{
    woocommerce_form_field(
        'sj_immediate_intervention_consent',
        array(
            'type' => 'checkbox',
            'class' => array('form-row terms'),
            'label_class' => array('woocommerce-form__label woocommerce-form__label-for-checkbox checkbox'),
            'input_class' => array('woocommerce-form__input woocommerce-form__input-checkbox input-checkbox'),
            'required' => true,
            'label' => wp_kses_post("J'accepte l'intervention immediate et je renonce expressement a mon droit de retractation (article L221-28 du Code de la consommation)."),
        ),
        WC()->checkout()->get_value('sj_immediate_intervention_consent')
    );
}

function serrurier_jouy_validate_intervention_checkbox(): void
{
    if (empty($_POST['sj_immediate_intervention_consent'])) {
        wc_add_notice("Vous devez accepter l'intervention immediate et la renonciation au droit de retractation.", 'error');
    }
}

function serrurier_jouy_save_intervention_checkbox(WC_Order $order, array $data): void
{
    $consent = isset($_POST['sj_immediate_intervention_consent']) ? 'yes' : 'no';
    $order->update_meta_data('_sj_immediate_intervention_consent', $consent);
}

function serrurier_jouy_display_intervention_consent_in_admin(WC_Order $order): void
{
    $consent = $order->get_meta('_sj_immediate_intervention_consent');
    $label = ('yes' === $consent) ? 'Oui' : 'Non';

    echo '<p><strong>Intervention immediate / renonciation retractation :</strong> ' . esc_html($label) . '</p>';
}

function serrurier_jouy_add_quote_only_product_field(): void
{
    woocommerce_wp_checkbox(
        array(
            'id' => '_sj_quote_only',
            'label' => 'Produit sur devis uniquement',
            'description' => 'Masque le prix et remplace l\'achat par une demande de devis.',
            'desc_tip' => true,
        )
    );
}

function serrurier_jouy_save_quote_only_product_field(WC_Product $product): void
{
    $quote_only = isset($_POST['_sj_quote_only']) ? 'yes' : 'no';
    $product->update_meta_data('_sj_quote_only', $quote_only);
}

function serrurier_jouy_is_quote_only_product($product): bool
{
    if (!$product instanceof WC_Product) {
        return false;
    }

    return 'yes' === $product->get_meta('_sj_quote_only', true);
}

function serrurier_jouy_get_quote_page_url(): string
{
    $custom_url = get_option('sj_quote_page_url', '');

    if (!empty($custom_url)) {
        return esc_url($custom_url);
    }

    return esc_url(home_url('/demande-devis/'));
}

function serrurier_jouy_build_quote_request_url(WC_Product $product): string
{
    return add_query_arg(
        array(
            'product_id' => $product->get_id(),
            'product_name' => rawurlencode($product->get_name()),
        ),
        serrurier_jouy_get_quote_page_url()
    );
}

function serrurier_jouy_maybe_replace_price_with_quote_label(string $price_html, WC_Product $product): string
{
    if (!serrurier_jouy_is_quote_only_product($product)) {
        return $price_html;
    }

    return '<span class="sj-quote-label">Sur devis</span>';
}

function serrurier_jouy_make_quote_products_not_purchasable(bool $purchasable, WC_Product $product): bool
{
    if (serrurier_jouy_is_quote_only_product($product)) {
        return false;
    }

    return $purchasable;
}

function serrurier_jouy_replace_loop_add_to_cart_for_quote_products(string $html, WC_Product $product, array $args): string
{
    if (!serrurier_jouy_is_quote_only_product($product)) {
        return $html;
    }

    $url = serrurier_jouy_build_quote_request_url($product);
    $class = isset($args['class']) ? sanitize_html_class($args['class']) : 'button';

    return '<a href="' . esc_url($url) . '" class="' . esc_attr($class) . '">Demander un devis</a>';
}

function serrurier_jouy_render_single_quote_button(): void
{
    global $product;

    if (!serrurier_jouy_is_quote_only_product($product)) {
        return;
    }

    $url = serrurier_jouy_build_quote_request_url($product);
    echo '<a href="' . esc_url($url) . '" class="button alt">Demander un devis</a>';
}

function serrurier_jouy_block_add_to_cart_for_quote_products(bool $passed, int $product_id, int $quantity): bool
{
    $product = wc_get_product($product_id);

    if (!serrurier_jouy_is_quote_only_product($product)) {
        return $passed;
    }

    wc_add_notice('Ce produit est disponible uniquement sur devis.', 'error');
    return false;
}

function serrurier_jouy_attach_custom_order_number(WC_Order $order, array $data): void
{
    if ($order->get_meta('_sj_custom_order_number')) {
        return;
    }

    $order->update_meta_data('_sj_custom_order_number', serrurier_jouy_next_sequence_number('sj_order_number_state', ''));
}

function serrurier_jouy_attach_custom_order_number_on_new_order(int $order_id): void
{
    $order = wc_get_order($order_id);

    if (!$order instanceof WC_Order) {
        return;
    }

    if ($order->get_meta('_sj_custom_order_number')) {
        return;
    }

    $order->update_meta_data('_sj_custom_order_number', serrurier_jouy_next_sequence_number('sj_order_number_state', ''));
    $order->save();
}

function serrurier_jouy_filter_order_number_display(string $order_number, WC_Order $order): string
{
    $custom_order_number = $order->get_meta('_sj_custom_order_number');

    if (!empty($custom_order_number)) {
        return (string) $custom_order_number;
    }

    return $order_number;
}

function serrurier_jouy_generate_invoice_metadata(int $order_id): void
{
    $order = wc_get_order($order_id);

    if (!$order instanceof WC_Order) {
        return;
    }

    if (!$order->get_meta('_sj_invoice_number')) {
        $invoice_number = serrurier_jouy_next_sequence_number('sj_invoice_number_state', 'FAC-');
        $order->update_meta_data('_sj_invoice_number', $invoice_number);
        $order->update_meta_data('_sj_invoice_date', current_time('mysql'));
        $order->save();
    }
}

function serrurier_jouy_next_sequence_number(string $option_name, string $prefix): string
{
    $period = wp_date('mY');
    $state = get_option($option_name, array());

    if (!is_array($state)) {
        $state = array();
    }

    $current_period = isset($state['period']) ? (string) $state['period'] : '';
    $counter = isset($state['counter']) ? (int) $state['counter'] : 0;

    if ($current_period !== $period) {
        $counter = 1;
    } else {
        $counter++;
    }

    update_option(
        $option_name,
        array(
            'period' => $period,
            'counter' => $counter,
        ),
        false
    );

    return $prefix . str_pad((string) $counter, 2, '0', STR_PAD_LEFT) . '-' . $period;
}

function serrurier_jouy_get_invoice_url(WC_Order $order): string
{
    return add_query_arg(
        array(
            'sj_invoice' => '1',
            'order_id' => $order->get_id(),
            'key' => $order->get_order_key(),
        ),
        home_url('/')
    );
}

function serrurier_jouy_maybe_render_invoice_page(): void
{
    if (!isset($_GET['sj_invoice'], $_GET['order_id'], $_GET['key'])) {
        return;
    }

    $order_id = absint($_GET['order_id']);
    $order_key = wc_clean(wp_unslash($_GET['key']));
    $order = wc_get_order($order_id);

    if (!$order instanceof WC_Order) {
        wp_die('Commande introuvable.');
    }

    $is_owner = hash_equals((string) $order->get_order_key(), (string) $order_key);
    $is_manager = current_user_can('manage_woocommerce');

    if (!$is_owner && !$is_manager) {
        wp_die('Acces refuse.');
    }

    if (!$order->get_meta('_sj_invoice_number')) {
        $order->update_meta_data('_sj_invoice_number', serrurier_jouy_next_sequence_number('sj_invoice_number_state', 'FAC-'));
        $order->update_meta_data('_sj_invoice_date', current_time('mysql'));
        $order->save();
    }

    $invoice_number = (string) $order->get_meta('_sj_invoice_number');
    $invoice_date = (string) $order->get_meta('_sj_invoice_date');

    status_header(200);
    nocache_headers();

    echo '<!doctype html>';
    echo '<html><head><meta charset="utf-8"><title>Facture ' . esc_html($invoice_number) . '</title>';
    echo '<style>body{font-family:Arial,sans-serif;color:#111;margin:30px;}h1{margin:0 0 10px;}table{width:100%;border-collapse:collapse;margin-top:20px;}th,td{border:1px solid #ddd;padding:8px;text-align:left;}th{background:#f5f5f5;} .meta{margin:12px 0;} .totals{margin-top:16px;max-width:380px;margin-left:auto;} .totals table td{border:0;border-bottom:1px solid #eee;} .print{margin-top:20px;} @media print {.print{display:none;}}</style>';
    echo '</head><body>';
    echo '<h1>Facture</h1>';
    echo '<div class="meta"><strong>Numero :</strong> ' . esc_html($invoice_number) . '<br>';
    echo '<strong>Date :</strong> ' . esc_html(wp_date('d/m/Y', strtotime($invoice_date))) . '<br>';
    echo '<strong>Commande :</strong> #' . esc_html((string) $order->get_id()) . '</div>';
    echo '<div class="meta"><strong>Client :</strong> ' . esc_html($order->get_formatted_billing_full_name()) . '<br>';
    echo esc_html($order->get_billing_email()) . '</div>';
    echo '<table><thead><tr><th>Produit</th><th>Quantite</th><th>Total</th></tr></thead><tbody>';

    foreach ($order->get_items() as $item) {
        $name = $item->get_name();
        $qty = $item->get_quantity();
        $total = $order->get_formatted_line_subtotal($item);
        echo '<tr><td>' . esc_html($name) . '</td><td>' . esc_html((string) $qty) . '</td><td>' . wp_kses_post($total) . '</td></tr>';
    }

    echo '</tbody></table>';
    echo '<div class="totals"><table>';
    echo '<tr><td><strong>Sous-total</strong></td><td>' . wp_kses_post(wc_price((float) $order->get_subtotal(), array('currency' => $order->get_currency()))) . '</td></tr>';
    echo '<tr><td><strong>Total</strong></td><td>' . wp_kses_post($order->get_formatted_order_total()) . '</td></tr>';
    echo '</table></div>';
    echo '<p class="print"><button onclick="window.print();">Imprimer / Export PDF</button></p>';
    echo '</body></html>';
    exit;
}

function serrurier_jouy_render_my_account_invoice_link(int $order_id): void
{
    $order = wc_get_order($order_id);

    if (!$order instanceof WC_Order) {
        return;
    }

    if ('completed' !== $order->get_status()) {
        return;
    }

    $invoice_url = serrurier_jouy_get_invoice_url($order);
    echo '<p><a class="button" href="' . esc_url($invoice_url) . '">Voir la facture</a></p>';
}

function serrurier_jouy_add_invoice_link_to_completed_email($order, bool $sent_to_admin, bool $plain_text, $email): void
{
    if (!$order instanceof WC_Order || $sent_to_admin) {
        return;
    }

    if ('completed' !== $order->get_status()) {
        return;
    }

    $invoice_url = serrurier_jouy_get_invoice_url($order);

    if ($plain_text) {
        echo "\nFacture: " . esc_url_raw($invoice_url) . "\n";
        return;
    }

    echo '<p><a href="' . esc_url($invoice_url) . '">Telecharger votre facture</a></p>';
}

function serrurier_jouy_add_invoice_number_to_email_meta(array $fields, bool $sent_to_admin, WC_Order $order): array
{
    $invoice_number = (string) $order->get_meta('_sj_invoice_number');

    if (!empty($invoice_number)) {
        $fields['sj_invoice_number'] = array(
            'label' => 'Numero de facture',
            'value' => $invoice_number,
        );
    }

    return $fields;
}