# easy-invoice/2.4.0/templates/document/single.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.4.0. 274 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.4.0/code/templates/document/single.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.4.0/raw/templates/document/single.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.0/code/templates/document/single.php#L10-L20`.

```php
<?php
/**
 * Public invoice / quote page.
 *
 * One page for both documents. The design (what the client sees inside the
 * frame) comes from templates/invoice-templates or templates/quote-templates;
 * this file is the frame around it: the action bar, the payment panel, the
 * admin's CSS editor.
 *
 * Override by copying to {theme}/easy-invoice/document/single.php.
 *
 * Variables available to overrides:
 *   $document_type  'invoice' | 'quote'
 *   $document       The model
 *   $invoice        Same object when it is an invoice, else null
 *   $quote          Same object when it is a quote, else null
 *   $formatter      Currency formatter for this document
 *   $text_settings  Button labels from Settings → Text
 *   $template_file  Absolute path of the resolved design template
 *
 * @package Easy_Invoice
 * @subpackage Templates
 */

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

global $post;

$document_type = ( $post && \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE === $post->post_type ) ? 'quote' : 'invoice';
$is_quote      = ( 'quote' === $document_type );

$document = null;
if ( $post ) {
    if ( $is_quote && \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE === $post->post_type ) {
        $document = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository()->find( $post->ID );
        if ( $document && is_callable( [ $document, 'ensureProperSlug' ] ) ) {
            $document->ensureProperSlug();
        }
    } elseif ( ! $is_quote && \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE === $post->post_type ) {
        $document = \EasyInvoice\Providers\InvoiceServiceProvider::getInvoiceRepository()->find( $post->ID );
    }
}

if ( ! $document ) {
    wp_die( $is_quote ? esc_html__( 'Quote not found.', 'easy-invoice' ) : esc_html__( 'Invoice not found.', 'easy-invoice' ) );
}

$invoice       = $is_quote ? null : $document;
$quote         = $is_quote ? $document : null;
$document_id   = (int) $document->getId();
$formatter     = $is_quote ? new \EasyInvoice\Helpers\QuoteFormatter( $quote ) : new \EasyInvoice\Helpers\InvoiceFormatter( $invoice );
$text_settings = $is_quote
    ? \EasyInvoice\Helpers\TemplateTextHelper::getQuoteTextSettings()
    : \EasyInvoice\Helpers\TemplateTextHelper::getInvoiceTextSettings();
$template_file = easy_invoice_design_template( $document_type, (string) $document->getTemplate() );
$status        = (string) $document->getStatus();
$is_admin_user = is_user_logged_in() && current_user_can( 'manage_options' );

// The emailed link carries an access token; every URL we build keeps it so
// the recipient stays authorised across print, PDF and email actions.
$token_param = $is_quote ? 'qk' : 'ik';
$token       = isset( $_GET[ $token_param ] ) ? sanitize_text_field( wp_unslash( $_GET[ $token_param ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
/** This filter is documented in includes/Controllers/InvoiceController.php */
$token       = (string) apply_filters( 'easy_invoice_presented_access_token', $token, $document_type );
$pdf_url     = add_query_arg( array_filter( [ 'auto_download_pdf' => '1', $token_param => $token ] ), get_permalink( $document_id ) );

// Invoice: may the viewer pay? Quote: may the viewer accept or decline?
// Payable while something is owed: unpaid, part-paid or overdue — a draft is
// not issued yet, a paid or cancelled invoice has nothing to collect.
$can_pay = ! $is_quote
    && in_array( $status, [ 'unpaid', 'available', 'partial', 'overdue' ], true )
    && \EasyInvoice\Services\InvoiceBalance::due( $invoice ) > 0;
// Open for a decision: authorised viewer, and the quote neither decided nor
// past its expiry date. When it is closed the reason is shown instead.
$quote_closed_reason = '';
$quote_closed_tone   = 'expired';
$can_act             = false;
if ( $is_quote && \EasyInvoice\Controllers\QuoteController::canActOnQuote( $document_id, $quote ) ) {
    $ei_open = \EasyInvoice\Controllers\QuoteController::openForDecision( $quote );
    if ( true === $ei_open ) {
        $can_act = true;
    } elseif ( is_wp_error( $ei_open ) ) {
        // The reader came here to decide; tell them what already happened
        // instead of showing a quote with no buttons and no explanation.
        $ei_qstatus = strtolower( (string) $quote->getStatus() );
        if ( 'accepted' === $ei_qstatus ) {
            $ei_when             = (string) get_post_meta( $document_id, '_easy_invoice_quote_accepted_date', true );
            $quote_closed_tone   = 'accepted';
            $quote_closed_reason = '' !== $ei_when && strtotime( $ei_when )
                /* translators: %s: date. */
                ? sprintf( __( 'You accepted this quote on %s. Thank you — nothing more to do here.', 'easy-invoice' ), date_i18n( get_option( 'date_format' ), strtotime( $ei_when ) ) )
                : __( 'This quote has been accepted. Thank you — nothing more to do here.', 'easy-invoice' );
        } elseif ( 'declined' === $ei_qstatus ) {
            $quote_closed_tone   = 'declined';
            $quote_closed_reason = __( 'This quote was declined. If that was a mistake, reply to the email it came with and we will send a fresh one.', 'easy-invoice' );
        } else {
            $quote_closed_reason = $ei_open->get_error_message();
        }
    }
}

$accept_description = '';
if ( $can_act ) {
    switch ( \EasyInvoice\Controllers\SettingsController::getQuoteAcceptAction() ) {
        case 'convert':
            $accept_description = __( 'This will convert the quote to an invoice.', 'easy-invoice' );
            break;
        case 'convert_send':
            $accept_description = __( 'This will convert the quote to an invoice and send it to the client.', 'easy-invoice' );
            break;
        case 'duplicate':
            $accept_description = __( 'This will create a new invoice while keeping this quote unchanged.', 'easy-invoice' );
            break;
        case 'duplicate_send':
            $accept_description = __( 'This will create a new invoice and send it to the client, while keeping this quote unchanged.', 'easy-invoice' );
            break;
        default:
            $accept_description = __( 'This will mark the quote as accepted.', 'easy-invoice' );
    }
}

$additional_css = (string) get_post_meta( $document_id, '_easy_invoice_additional_css', true );
?><!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo( 'charset' ); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="robots" content="noindex, nofollow">
    <title><?php echo esc_html( $document->getTitle() ?: ( $is_quote ? __( 'Quote', 'easy-invoice' ) : __( 'Invoice', 'easy-invoice' ) ) ); ?></title>
    <?php wp_head(); ?>
    <?php
    /**
     * Fires in the <head> of the public document page.
     *
     * Kept for the gateways and addons that print their assets here
     * directly; new code should enqueue on `wp_enqueue_scripts` and test
     * `DocumentPage::currentType()` instead.
     */
    do_action( 'easy_invoice_head' );
    ?>
    <?php if ( '' !== $additional_css ) : ?>
        <?php // Same treatment as core's wp_custom_css_cb(): entities are not decoded inside <style>, so tags are stripped instead of escaped. ?>
        <style id="custom-<?php echo esc_attr( $document_type ); ?>-css"><?php echo wp_strip_all_tags( $additional_css ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></style>
    <?php endif; ?>
</head>
<body <?php body_class(); ?>>

<div class="ei-document easy-invoice-<?php echo esc_attr( $document_type ); ?>-container">

    <?php if ( $can_act && '' !== $accept_description ) : ?>
        <div class="ei-document__notice accept-action-info">
            <strong><?php echo esc_html( $text_settings['accept_quote'] ); ?>:</strong>
            <?php echo esc_html( $accept_description ); ?>
        </div>
    <?php elseif ( '' !== $quote_closed_reason ) : ?>
        <div class="ei-document__notice ei-document__notice--<?php echo esc_attr( $quote_closed_tone ); ?>">
            <?php echo esc_html( $quote_closed_reason ); ?>
        </div>
    <?php endif; ?>

    <div class="ei-document__actions <?php echo esc_attr( $document_type ); ?>-actions">
        <?php if ( $can_act ) : ?>
            <button type="button" class="ei-btn ei-btn--accept accept" data-ei-action="accept"
                    data-description="<?php echo esc_attr( $accept_description ); ?>">
                <?php echo esc_html( $text_settings['accept_quote'] ); ?>
            </button>
            <button type="button" class="ei-btn ei-btn--decline decline" data-ei-action="decline"
                    data-message="<?php echo esc_attr( \EasyInvoice\Controllers\SettingsController::getDeclinedQuoteMessage() ); ?>"
                    data-reason-required="<?php echo \EasyInvoice\Controllers\SettingsController::isDeclineReasonRequired() ? '1' : '0'; ?>">
                <?php echo esc_html( $text_settings['decline_quote'] ); ?>
            </button>
        <?php endif; ?>

        <button type="button" class="ei-btn ei-btn--print" data-ei-action="print">
            <?php echo esc_html( $text_settings['print'] ); ?>
        </button>

        <a class="ei-btn ei-btn--pdf" href="<?php echo esc_url( $pdf_url ); ?>" data-ei-action="pdf">
            <?php echo esc_html( $text_settings['download_pdf'] ); ?>
        </a>

        <button type="button" class="ei-btn ei-btn--email send-email-button" data-ei-action="email">
            <?php echo esc_html( $text_settings['send_email'] ); ?>
        </button>

        <?php if ( $can_pay ) : ?>
            <button type="button" id="toggle-payment-panel" class="ei-btn ei-btn--pay pay" data-ei-action="pay" data-label="<?php echo esc_attr( $text_settings['pay_now'] ); ?>">
                <?php echo esc_html( $text_settings['pay_now'] ); ?>
            </button>
        <?php endif; ?>

        <?php
        /**
         * Fires after the built-in buttons in the action bar.
         *
         * @param object $document The invoice or quote.
         * @param string $document_type 'invoice' or 'quote'.
         */
        do_action( 'easy_invoice_document_actions', $document, $document_type );
        ?>
    </div>

    <?php if ( ! $is_quote ) : ?>
        <?php do_action( 'easy_invoice_before_invoice_content_top', $invoice ); ?>
    <?php endif; ?>

    <div class="ei-document__layout <?php echo esc_attr( $document_type ); ?>-layout">
        <div class="ei-document__content <?php echo esc_attr( $document_type ); ?>-content" id="<?php echo esc_attr( $document_type ); ?>-content">

            <?php if ( $is_admin_user ) : ?>
                <button type="button" id="additional-css-btn" class="ei-css-toggle <?php echo '' !== $additional_css ? 'has-css' : ''; ?>" title="<?php esc_attr_e( 'Additional CSS for this document', 'easy-invoice' ); ?>">
                    <span aria-hidden="true">{ }</span>
                </button>
            <?php endif; ?>

            <?php
            if ( $is_quote ) {
                do_action( 'easy_invoice_quote_view_content_top', $quote );
            } else {
                do_action( 'easy_invoice_invoice_view_content_top', $invoice );
            }

            if ( '' !== $template_file ) {
                include $template_file;
            } else {
                easy_invoice_get_template( 'document/fallback.php', [
                    'document'      => $document,
                    'document_type' => $document_type,
                    'formatter'     => $formatter,
                    'invoice'       => $invoice,
                    'quote'         => $quote,
                ] );
            }

            if ( $is_quote ) {
                do_action( 'easy_invoice_quote_content_after', $quote );
            }
            ?>
        </div>

        <?php if ( $can_pay ) : ?>
            <div id="payment-panel" class="ei-payment-panel payment-panel" hidden>
                <button type="button" class="ei-payment-panel__close" data-ei-action="close-payment" aria-label="<?php esc_attr_e( 'Close', 'easy-invoice' ); ?>">&times;</button>
                <?php easy_invoice_get_template( 'payment-section.php' ); ?>
            </div>
        <?php endif; ?>
    </div>
</div>

<?php if ( $is_admin_user ) : ?>
    <?php
    easy_invoice_get_template( 'document/css-panel.php', [
        'document_type'  => $document_type,
        'additional_css' => $additional_css,
    ] );
    ?>
<?php endif; ?>

<?php
/**
 * Fires before wp_footer() on the public document page.
 *
 * @param object $document      The invoice or quote.
 * @param string $document_type 'invoice' or 'quote'.
 */
do_action( 'easy_invoice_document_footer', $document, $document_type );

wp_footer();
?>
</body>
</html>

```
