# easy-invoice/2.4.3/includes/Services/DocumentPage.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.4.3. 351 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.4.3/code/includes/Services/DocumentPage.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.4.3/raw/includes/Services/DocumentPage.php
- Modified: 2026-09-15T12:31:20+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.4.3/code/includes/Services/DocumentPage.php#L10-L20`.

```php
<?php
/**
 * Assets for the public invoice / quote page.
 *
 * @package Easy_Invoice
 * @subpackage Services
 */

namespace EasyInvoice\Services;

use EasyInvoice\Constants\PostTypes;

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

/**
 * Puts the public document page on WordPress's asset pipeline.
 *
 * Why this exists
 * ---------------
 * The public invoice and quote pages used to be self-contained HTML documents:
 * their own <!DOCTYPE>, hand-written <script src> tags, six hundred lines of
 * inline CSS and JavaScript each, and no wp_head() or wp_footer(). Two things
 * followed. Anything the plugin *enqueued* for those pages never printed --
 * the bank-transfer form's submit script was enqueued on every invoice page
 * and loaded on none of them -- and anything a site added through the normal
 * hooks (analytics, consent banners, a caching plugin's markers) was absent.
 *
 * The page now calls wp_head() and wp_footer() like any other, with its own
 * stylesheet and script enqueued here.
 *
 * Isolation
 * ---------
 * A theme's stylesheet is written for the theme's pages, not for a document a
 * client is about to print, so by default every style and script that is not
 * ours (or a dependency of ours) is dequeued on these pages. A theme that
 * wants in -- to supply fonts, say -- returns false from
 * `easy_invoice_document_isolate_assets`, or whitelists handles through
 * `easy_invoice_document_allowed_handles`.
 */
class DocumentPage {

    /** Stylesheet handle. */
    const STYLE = 'easy-invoice-document';

    /** Script handle. */
    const SCRIPT = 'easy-invoice-document';

    /**
     * Wire it up.
     *
     * @return void
     */
    public static function init(): void {
        add_action( 'wp_enqueue_scripts', [ __CLASS__, 'enqueue' ], 20 );
        add_action( 'wp_enqueue_scripts', [ __CLASS__, 'isolate' ], PHP_INT_MAX );
        add_filter( 'body_class', [ __CLASS__, 'bodyClass' ] );
        add_filter( 'show_admin_bar', [ __CLASS__, 'hideAdminBar' ] );
    }

    /**
     * Is the current request a public invoice or quote?
     *
     * @return string 'invoice', 'quote' or ''.
     */
    public static function currentType(): string {
        if ( is_admin() || ! is_singular() ) {
            return '';
        }
        if ( is_singular( PostTypes::EASY_INVOICE_POST_TYPE ) ) {
            return 'invoice';
        }
        if ( is_singular( PostTypes::EASY_INVOICE_QUOTE_POST_TYPE ) ) {
            return 'quote';
        }
        return '';
    }

    /**
     * Cache-busting version for one of the page's own files: the plugin
     * version plus the file's modification time, so a changed file is never
     * served stale from a browser or CDN that keys on the query string.
     *
     * @param string $relative Path inside the plugin.
     * @return string
     */
    private static function assetVersion( string $relative ): string {
        $path  = EASY_INVOICE_PLUGIN_DIR . $relative;
        $mtime = file_exists( $path ) ? (int) filemtime( $path ) : 0;
        return EASY_INVOICE_VERSION . ( $mtime ? '.' . $mtime : '' );
    }

    /**
     * Register and enqueue the page's own assets.
     *
     * @return void
     */
    public static function enqueue(): void {
        $type = self::currentType();
        if ( '' === $type ) {
            return;
        }

        $document_id = get_queried_object_id();

        wp_register_style( self::STYLE, EASY_INVOICE_PLUGIN_URL . 'assets/css/document.css', [], self::assetVersion( 'assets/css/document.css' ) );
        wp_enqueue_style( self::STYLE );

        // Shared UI pieces the admin already ships.
        wp_register_script( 'easy-invoice-toast', EASY_INVOICE_PLUGIN_URL . 'assets/js/easy-invoice-toast.js', [], EASY_INVOICE_VERSION, true );

        // In-browser PDF: html2canvas + jsPDF capture the page as drawn. The
        // default for the Download button, and the fallback whenever the
        // server cannot render.
        wp_register_script( 'html2canvas', EASY_INVOICE_PLUGIN_URL . 'assets/js/vendors/html2canvas.min.js', [], '1.4.1', true );
        wp_register_script( 'jspdf', EASY_INVOICE_PLUGIN_URL . 'assets/js/vendors/jspdf.umd.min.js', [], '2.5.1', true );
        wp_register_script( 'easy-invoice-document-pdf', EASY_INVOICE_PLUGIN_URL . 'assets/js/document-pdf.js', [ 'html2canvas', 'jspdf' ], self::assetVersion( 'assets/js/document-pdf.js' ), true );

        wp_register_script(
            self::SCRIPT,
            EASY_INVOICE_PLUGIN_URL . 'assets/js/document.js',
            [ 'jquery', 'easy-invoice-toast', 'easy-invoice-document-pdf' ],
            self::assetVersion( 'assets/js/document.js' ),
            true
        );

        $token_param = ( 'quote' === $type ) ? 'qk' : 'ik';
        $token       = isset( $_GET[ $token_param ] ) ? sanitize_text_field( wp_unslash( $_GET[ $token_param ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- access token, verified server-side by the endpoints.
        /** This filter is documented in includes/Controllers/InvoiceController.php */
        $token = (string) apply_filters( 'easy_invoice_presented_access_token', $token, $type );

        $config = [
            'type'        => $type,
            'id'          => $document_id,
            'number'      => (string) get_post_meta( $document_id, 'quote' === $type ? '_easy_invoice_quote_number' : '_easy_invoice_number', true ),
            'ajaxUrl'     => admin_url( 'admin-ajax.php' ),
            'accessToken' => $token,
            'nonces'      => [
                'sendEmail'     => wp_create_nonce( 'quote' === $type ? 'easy_invoice_send_quote_email' : 'easy_invoice_send_invoice_email' ),
                'additionalCss' => wp_create_nonce( 'save_additional_css_nonce' ),
                'quoteAction'   => ( 'quote' === $type ) ? wp_create_nonce( 'easy_invoice_quote_action_' . $document_id ) : '',
            ],
            'autoDownload' => isset( $_GET['auto_download_pdf'] ) && '1' === (string) $_GET['auto_download_pdf'], // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            'pdfMethod'    => class_exists( '\\EasyInvoice\\Services\\PdfDownload' ) ? PdfDownload::downloadMethod() : 'browser',
            'openPayment'  => isset( $_GET['pay'] ) && '1' === (string) $_GET['pay'], // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            'i18n'         => [
                'sendTitle'        => __( 'Send by email', 'easy-invoice' ),
                // Staff send the document to the client; the client sends
                // themselves a copy — the same endpoint, different question.
                'sendMessage'      => self::sendPrompt( $type, $document_id ),
                'send'             => __( 'Send', 'easy-invoice' ),
                'sending'          => __( 'Sending…', 'easy-invoice' ),
                'sent'             => __( 'Sent.', 'easy-invoice' ),
                'sendFailed'       => __( 'The email could not be sent.', 'easy-invoice' ),
                'cancel'           => __( 'Cancel', 'easy-invoice' ),
                'close'            => __( 'Close', 'easy-invoice' ),
                'networkError'     => __( 'Network error. Please try again.', 'easy-invoice' ),
                'pdfUnavailable'   => __( 'PDF generator not available.', 'easy-invoice' ),
                'hidePayment'      => __( 'Hide payment', 'easy-invoice' ),
                'acceptTitle'      => __( 'Accept this quote?', 'easy-invoice' ),
                'accept'           => __( 'Accept', 'easy-invoice' ),
                'accepting'        => __( 'Accepting…', 'easy-invoice' ),
                'accepted'         => __( 'Quote accepted.', 'easy-invoice' ),
                'acceptedRedirect' => __( 'Quote accepted. Taking you to the invoice…', 'easy-invoice' ),
                'declineTitle'     => __( 'Decline this quote?', 'easy-invoice' ),
                'declineMessage'   => __( 'Let us know why, so we can put a better proposal together.', 'easy-invoice' ),
                'declineReason'    => __( 'Reason (optional)', 'easy-invoice' ),
                'decline'          => __( 'Decline', 'easy-invoice' ),
                'declining'        => __( 'Declining…', 'easy-invoice' ),
                'declined'         => __( 'Quote declined.', 'easy-invoice' ),
                'actionFailed'     => __( 'That did not work. Please try again.', 'easy-invoice' ),
                'cssSaved'         => __( 'CSS saved.', 'easy-invoice' ),
                'cssCleared'       => __( 'CSS cleared.', 'easy-invoice' ),
                'cssFailed'        => __( 'Could not save the CSS.', 'easy-invoice' ),
            ],
        ];

        /**
         * Filter the configuration handed to the document page script.
         *
         * @param array  $config      Settings and strings.
         * @param string $type        'invoice' or 'quote'.
         * @param int    $document_id Document post id.
         */
        $config = apply_filters( 'easy_invoice_document_script_config', $config, $type, $document_id );

        wp_localize_script( self::SCRIPT, 'easyInvoiceDocument', $config );
        wp_enqueue_script( self::SCRIPT );

        if ( 'invoice' === $type ) {
            self::localizePaymentVars( $document_id );
        }
    }

    /**
     * Re-localise the payment script with this invoice's currency and token.
     *
     * PaymentController localises `easy_invoice_vars` with the *site*
     * currency on every front-end page. The bare template used to redefine
     * the object inline to win that race; now the later localisation wins the
     * same way, through the pipeline.
     *
     * @param int $invoice_id Invoice id.
     * @return void
     */
    /**
     * The question behind the "Send Email" button.
     *
     * @param string $type        'invoice' or 'quote'.
     * @param int    $document_id Post id.
     * @return string
     */
    private static function sendPrompt( string $type, int $document_id ): string {
        $cap   = 'ei_send_invoice'; // the staff "send documents" capability; there is no separate one for quotes
        $document = 'quote' === $type
            ? \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository()->find( $document_id )
            : \EasyInvoice\Providers\InvoiceServiceProvider::getInvoiceRepository()->find( $document_id );
        $staff = function_exists( 'easy_invoice_user_can' ) && easy_invoice_user_can( $cap );
        if ( $staff ) {
            return 'quote' === $type
                ? __( 'Email this quote to the client now?', 'easy-invoice' )
                : __( 'Email this invoice to the client now?', 'easy-invoice' );
        }
        $email = is_callable( [ $document, 'getCustomerEmail' ] ) ? (string) $document->getCustomerEmail() : '';
        if ( '' !== $email ) {
            /* translators: 1: document kind, 2: email address. */
            return sprintf( __( 'Send a copy of this %1$s to %2$s?', 'easy-invoice' ), 'quote' === $type ? __( 'quote', 'easy-invoice' ) : __( 'invoice', 'easy-invoice' ), $email );
        }
        return 'quote' === $type
            ? __( 'Send yourself a copy of this quote by email?', 'easy-invoice' )
            : __( 'Send yourself a copy of this invoice by email?', 'easy-invoice' );
    }

    private static function localizePaymentVars( int $invoice_id ): void {
        if ( ! wp_script_is( 'easy-invoice-payment', 'registered' ) ) {
            return;
        }

        $invoice  = new \EasyInvoice\Models\Invoice( get_post( $invoice_id ) );
        $currency = $invoice->getCurrencyCode();
        if ( empty( $currency ) || 'global' === $currency ) {
            $currency = get_option( 'easy_invoice_currency_code', 'USD' );
        }

        wp_localize_script( 'easy-invoice-payment', 'easy_invoice_vars', [
            'ajax_url'        => admin_url( 'admin-ajax.php' ),
            'nonce'           => wp_create_nonce( 'easy_invoice_payment' ),
            /** This filter is documented in includes/Controllers/InvoiceController.php */
            'access_token'    => (string) apply_filters( 'easy_invoice_presented_access_token', isset( $_GET['ik'] ) ? sanitize_text_field( wp_unslash( $_GET['ik'] ) ) : '', 'invoice' ), // phpcs:ignore WordPress.Security.NonceVerification.Recommended
            'currency_symbol' => \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol( $currency ),
            'currency_code'   => $currency,
        ] );
    }

    /**
     * Drop every style and script that is not ours.
     *
     * @return void
     */
    public static function isolate(): void {
        $type = self::currentType();
        if ( '' === $type ) {
            return;
        }

        /**
         * Filter whether the theme's assets are kept off the document page.
         *
         * @param bool   $isolate Default true.
         * @param string $type    'invoice' or 'quote'.
         */
        if ( ! apply_filters( 'easy_invoice_document_isolate_assets', true, $type ) ) {
            return;
        }

        $allowed = [
            'jquery', 'jquery-core', 'jquery-migrate',
            'wp-i18n', 'wp-hooks', 'wp-polyfill', 'wp-api-fetch', 'wp-url',
            'admin-bar', 'dashicons',
        ];

        /**
         * Filter the handles allowed to load alongside the plugin's own.
         *
         * @param array  $allowed Handles.
         * @param string $type    'invoice' or 'quote'.
         */
        $allowed = (array) apply_filters( 'easy_invoice_document_allowed_handles', $allowed, $type );

        foreach ( [ wp_styles(), wp_scripts() ] as $collection ) {
            // What we keep, plus everything it depends on -- a gateway's SDK
            // (js.stripe.com, Square's web SDK) is a dependency of our own
            // handler and must survive.
            $keep = [];
            foreach ( (array) $collection->queue as $handle ) {
                if ( in_array( $handle, $allowed, true ) || self::isOurs( $handle ) ) {
                    $keep[] = $handle;
                }
            }
            $collection->all_deps( $keep );
            $keep = array_unique( array_merge( $keep, (array) $collection->to_do ) );
            $collection->to_do = [];

            foreach ( (array) $collection->queue as $handle ) {
                if ( ! in_array( $handle, $keep, true ) ) {
                    $collection->dequeue( $handle );
                }
            }
        }
    }

    /**
     * Does a handle belong to Easy Invoice or one of its addons?
     *
     * @param string $handle Script or style handle.
     * @return bool
     */
    private static function isOurs( string $handle ): bool {
        return 0 === strpos( $handle, 'easy-invoice' )
            || 0 === strpos( $handle, 'easy_invoice' )
            || in_array( $handle, [ 'html2canvas', 'jspdf', 'stripe-js' ], true );
    }

    /**
     * Mark the body so CSS can tell the two documents apart.
     *
     * @param array $classes Body classes.
     * @return array
     */
    public static function bodyClass( $classes ): array {
        $classes = is_array( $classes ) ? $classes : [];
        $type    = self::currentType();
        if ( '' !== $type ) {
            $classes[] = 'easy-invoice-document';
            $classes[] = 'easy-invoice-document--' . $type;
        }
        return $classes;
    }

    /**
     * No admin bar on a document a client is reading.
     *
     * @param bool $show Whether to show it.
     * @return bool
     */
    public static function hideAdminBar( $show ) {
        return '' === self::currentType() ? $show : false;
    }
}

```
