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;
}