'; echo esc_html__( 'NuxWP Devis requires WooCommerce to be installed and active.', 'nuxwp-devis' ); echo '

'; } /** * Activation: tables, PDF storage, rewrite endpoint. */ function devisqr_activate() { require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-settings.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-database.php'; DevisQR_Database::create_tables(); DevisQR_Database::ensure_storage(); add_rewrite_endpoint( DevisQR_Settings::account_endpoint(), EP_PAGES ); flush_rewrite_rules(); update_option( 'devisqr_version', DEVISQR_VERSION ); } register_activation_hook( __FILE__, 'devisqr_activate' ); /** * Deactivation: flush rewrite rules, clear scheduled hooks. */ function devisqr_deactivate() { wp_unschedule_hook( 'devisqr_send_new_quote_emails' ); wp_unschedule_hook( 'devisqr_send_status_email' ); wp_unschedule_hook( 'devisqr_daily_maintenance' ); flush_rewrite_rules(); } register_deactivation_hook( __FILE__, 'devisqr_deactivate' ); /** * Bootstrap. */ function devisqr_init() { if ( ! class_exists( 'WooCommerce' ) ) { add_action( 'admin_notices', 'devisqr_missing_woocommerce_notice' ); return; } require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-settings.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-helpers.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-texts.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-database.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-api.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-pdf-writer.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-pdf.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-email.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-cart.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-myaccount.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-admin.php'; require_once DEVISQR_PLUGIN_DIR . 'includes/class-devisqr-buttons.php'; new DevisQR_Texts(); new DevisQR_API(); new DevisQR_Cart(); new DevisQR_Buttons(); new DevisQR_MyAccount(); new DevisQR_Admin(); // Deferred e-mail handlers (WP-Cron). add_action( 'devisqr_send_new_quote_emails', 'devisqr_handle_new_quote_emails' ); add_action( 'devisqr_send_status_email', 'devisqr_handle_status_email' ); // Direct PDF download handler. add_action( 'init', 'devisqr_handle_download' ); // Schema upgrades. add_action( 'init', 'devisqr_maybe_upgrade', 20 ); // Daily expiry sweep. add_action( 'devisqr_daily_maintenance', 'devisqr_expire_quotes' ); if ( ! wp_next_scheduled( 'devisqr_daily_maintenance' ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'devisqr_daily_maintenance' ); } } /** * Flip quoted/accepted quotes past their expiry date to "expired" (no e-mail: the date was printed on the quote). */ function devisqr_expire_quotes() { foreach ( DevisQR_Database::get_expired_candidates() as $quote ) { DevisQR_Database::update_quote( (int) $quote->id, array( 'status' => 'expired' ) ); /** This action is documented in includes/class-devisqr-api.php */ do_action( 'devisqr_quote_status_changed', (int) $quote->id, 'expired', $quote->status ); } } add_action( 'plugins_loaded', 'devisqr_init' ); /** * Deferred: new-quote e-mails (customer + admin). * * @param int $quote_id Quote ID. */ function devisqr_handle_new_quote_emails( $quote_id ) { $quote = DevisQR_Database::get_quote( $quote_id ); if ( ! $quote ) { return; } DevisQR_Email::send_new_quote_customer( $quote ); DevisQR_Email::send_new_quote_admin( $quote ); } /** * Deferred: status update e-mail. * * @param int $quote_id Quote ID. */ function devisqr_handle_status_email( $quote_id ) { $quote = DevisQR_Database::get_quote( $quote_id ); if ( ! $quote ) { return; } DevisQR_Email::send_status_update( $quote ); } /** * Serve the quote PDF to its owner / shop managers. * * URL: /?devisqr_download=ID&_wpnonce=... */ function devisqr_handle_download() { if ( ! isset( $_GET['devisqr_download'] ) || ! is_user_logged_in() ) { return; } $quote_id = absint( wp_unslash( $_GET['devisqr_download'] ) ); if ( ! $quote_id ) { return; } $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'devisqr_download_' . $quote_id ) ) { wp_die( esc_html__( 'Invalid or expired download link.', 'nuxwp-devis' ), '', array( 'response' => 403 ) ); } $quote = DevisQR_Database::get_quote( $quote_id ); if ( ! $quote ) { wp_die( esc_html__( 'Quote not found.', 'nuxwp-devis' ), '', array( 'response' => 404 ) ); } if ( ! current_user_can( 'manage_woocommerce' ) && (int) $quote->user_id !== get_current_user_id() ) { wp_die( esc_html__( 'Access denied.', 'nuxwp-devis' ), '', array( 'response' => 403 ) ); } // PDF (regenerate when missing, or when branding/company settings changed after it was built). $path = DevisQR_Database::resolve_storage_path( $quote->pdf_path, 'pdf' ); $stamp = (int) get_option( 'devisqr_branding_stamp', 0 ); if ( $path && $stamp && (int) filemtime( $path ) < $stamp && ! in_array( $quote->status, array( 'accepted', 'converted', 'expired', 'rejected' ), true ) ) { $path = false; // Stale: rebuild below (pending/quoted only; accepted and closed quotes keep the document the customer received). } if ( ! $path ) { $pdf_path = DevisQR_PDF::generate( $quote_id ); if ( ! $pdf_path ) { wp_die( esc_html__( 'The PDF could not be generated.', 'nuxwp-devis' ), '', array( 'response' => 500 ) ); } DevisQR_Database::update_quote( $quote_id, array( 'pdf_path' => $pdf_path ) ); $path = DevisQR_Database::resolve_storage_path( $pdf_path, 'pdf' ); } if ( ! $path ) { wp_die( esc_html__( 'Invalid file path.', 'nuxwp-devis' ), '', array( 'response' => 403 ) ); } /* translators: %s: quote number */ $filename = sanitize_file_name( sprintf( __( 'Quote-%s.pdf', 'nuxwp-devis' ), $quote->quote_number ) ); DevisQR_Helpers::serve_file( $path, $filename, 'application/pdf' ); } /** * Run schema / endpoint upgrades once per version. */ function devisqr_maybe_upgrade() { $installed = (string) get_option( 'devisqr_version' ); if ( $installed === DEVISQR_VERSION ) { return; } // < 1.3.0 stored text overrides with '%' pre-escaped as '%%'; they are now stored raw. if ( $installed && version_compare( $installed, '1.3.0', '<' ) ) { $settings = get_option( 'devisqr_settings', array() ); if ( is_array( $settings ) && ! empty( $settings['text_overrides'] ) && is_array( $settings['text_overrides'] ) ) { foreach ( $settings['text_overrides'] as $k => $v ) { $settings['text_overrides'][ $k ] = str_replace( '%%', '%', (string) $v ); } update_option( 'devisqr_settings', $settings ); } } DevisQR_Database::create_tables(); // dbDelta adds new columns. DevisQR_Database::ensure_storage(); add_rewrite_endpoint( DevisQR_Settings::account_endpoint(), EP_PAGES ); flush_rewrite_rules(); update_option( 'devisqr_version', DEVISQR_VERSION ); }