PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
easy-invoice / templates / document / single.php

single.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.3, at templates/document/single.php

318 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Public invoice / quote page.
4 *
5 * One page for both documents. The design (what the client sees inside the
6 * frame) comes from templates/invoice-templates or templates/quote-templates;
7 * this file is the frame around it: the action bar, the payment panel, the
8 * admin's CSS editor.
9 *
10 * Override by copying to {theme}/easy-invoice/document/single.php.
11 *
12 * Variables available to overrides:
13 * $document_type 'invoice' | 'quote'
14 * $document The model
15 * $invoice Same object when it is an invoice, else null
16 * $quote Same object when it is a quote, else null
17 * $formatter Currency formatter for this document
18 * $text_settings Button labels from Settings → Text
19 * $template_file Absolute path of the resolved design template
20 *
21 * @package Easy_Invoice
22 * @subpackage Templates
23 */
24
25 if ( ! defined( 'ABSPATH' ) ) {
26 exit;
27 }
28
29 global $post;
30
31 $document_type = ( $post && \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE === $post->post_type ) ? 'quote' : 'invoice';
32 $is_quote = ( 'quote' === $document_type );
33
34 $document = null;
35 if ( $post ) {
36 if ( $is_quote && \EasyInvoice\Constants\PostTypes::EASY_INVOICE_QUOTE_POST_TYPE === $post->post_type ) {
37 $document = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository()->find( $post->ID );
38 if ( $document && is_callable( [ $document, 'ensureProperSlug' ] ) ) {
39 $document->ensureProperSlug();
40 }
41 } elseif ( ! $is_quote && \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE === $post->post_type ) {
42 $document = \EasyInvoice\Providers\InvoiceServiceProvider::getInvoiceRepository()->find( $post->ID );
43 }
44 }
45
46 if ( ! $document ) {
47 wp_die( $is_quote ? esc_html__( 'Quote not found.', 'easy-invoice' ) : esc_html__( 'Invoice not found.', 'easy-invoice' ) );
48 }
49
50 $invoice = $is_quote ? null : $document;
51 $quote = $is_quote ? $document : null;
52 $document_id = (int) $document->getId();
53 $formatter = $is_quote ? new \EasyInvoice\Helpers\QuoteFormatter( $quote ) : new \EasyInvoice\Helpers\InvoiceFormatter( $invoice );
54 $text_settings = $is_quote
55 ? \EasyInvoice\Helpers\TemplateTextHelper::getQuoteTextSettings()
56 : \EasyInvoice\Helpers\TemplateTextHelper::getInvoiceTextSettings();
57 $template_file = easy_invoice_design_template( $document_type, (string) $document->getTemplate() );
58 $status = (string) $document->getStatus();
59 $is_admin_user = is_user_logged_in() && current_user_can( 'manage_options' );
60
61 // The emailed link carries an access token; every URL we build keeps it so
62 // the recipient stays authorised across print, PDF and email actions.
63 $token_param = $is_quote ? 'qk' : 'ik';
64 $token = isset( $_GET[ $token_param ] ) ? sanitize_text_field( wp_unslash( $_GET[ $token_param ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
65 /** This filter is documented in includes/Controllers/InvoiceController.php */
66 $token = (string) apply_filters( 'easy_invoice_presented_access_token', $token, $document_type );
67 $pdf_url = add_query_arg( array_filter( [ 'auto_download_pdf' => '1', $token_param => $token ] ), get_permalink( $document_id ) );
68
69 // Invoice: may the viewer pay? Quote: may the viewer accept or decline?
70 // Payable while something is owed: unpaid, part-paid or overdue — a draft is
71 // not issued yet, a paid or cancelled invoice has nothing to collect.
72 $can_pay = ! $is_quote
73 && in_array( $status, [ 'unpaid', 'available', 'partial', 'overdue' ], true )
74 && \EasyInvoice\Services\InvoiceBalance::due( $invoice ) > 0;
75 // Open for a decision: authorised viewer, and the quote neither decided nor
76 // past its expiry date. When it is closed the reason is shown instead.
77 $quote_closed_reason = '';
78 $quote_closed_tone = 'expired';
79 $can_act = false;
80 if ( $is_quote && \EasyInvoice\Controllers\QuoteController::canActOnQuote( $document_id, $quote ) ) {
81 $ei_open = \EasyInvoice\Controllers\QuoteController::openForDecision( $quote );
82 if ( true === $ei_open ) {
83 $can_act = true;
84 } elseif ( is_wp_error( $ei_open ) ) {
85 // The reader came here to decide; tell them what already happened
86 // instead of showing a quote with no buttons and no explanation.
87 $ei_qstatus = strtolower( (string) $quote->getStatus() );
88 if ( 'accepted' === $ei_qstatus ) {
89 $ei_when = (string) get_post_meta( $document_id, '_easy_invoice_quote_accepted_date', true );
90 $quote_closed_tone = 'accepted';
91 $quote_closed_reason = '' !== $ei_when && strtotime( $ei_when )
92 /* translators: %s: date. */
93 ? 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 ) ) )
94 : __( 'This quote has been accepted. Thank you — nothing more to do here.', 'easy-invoice' );
95 } elseif ( 'declined' === $ei_qstatus ) {
96 $quote_closed_tone = 'declined';
97 $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' );
98 } else {
99 $quote_closed_reason = $ei_open->get_error_message();
100 }
101 }
102 }
103
104 $accept_description = '';
105 if ( $can_act ) {
106 switch ( \EasyInvoice\Controllers\SettingsController::getQuoteAcceptAction() ) {
107 case 'convert':
108 $accept_description = __( 'This will convert the quote to an invoice.', 'easy-invoice' );
109 break;
110 case 'convert_send':
111 $accept_description = __( 'This will convert the quote to an invoice and send it to the client.', 'easy-invoice' );
112 break;
113 case 'duplicate':
114 $accept_description = __( 'This will create a new invoice while keeping this quote unchanged.', 'easy-invoice' );
115 break;
116 case 'duplicate_send':
117 $accept_description = __( 'This will create a new invoice and send it to the client, while keeping this quote unchanged.', 'easy-invoice' );
118 break;
119 default:
120 $accept_description = __( 'This will mark the quote as accepted.', 'easy-invoice' );
121 }
122 }
123
124 $additional_css = (string) get_post_meta( $document_id, '_easy_invoice_additional_css', true );
125 ?><!DOCTYPE html>
126 <html <?php language_attributes(); ?>>
127 <head>
128 <meta charset="<?php bloginfo( 'charset' ); ?>">
129 <meta name="viewport" content="width=device-width, initial-scale=1.0">
130 <meta name="robots" content="noindex, nofollow">
131 <title><?php echo esc_html( $document->getTitle() ?: ( $is_quote ? __( 'Quote', 'easy-invoice' ) : __( 'Invoice', 'easy-invoice' ) ) ); ?></title>
132 <?php wp_head(); ?>
133 <?php
134 /**
135 * Fires in the <head> of the public document page.
136 *
137 * Kept for the gateways and addons that print their assets here
138 * directly; new code should enqueue on `wp_enqueue_scripts` and test
139 * `DocumentPage::currentType()` instead.
140 */
141 do_action( 'easy_invoice_head' );
142 ?>
143 <?php if ( '' !== $additional_css ) : ?>
144 <?php // Same treatment as core's wp_custom_css_cb(): entities are not decoded inside <style>, so tags are stripped instead of escaped. ?>
145 <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>
146 <?php endif; ?>
147 </head>
148 <body <?php body_class(); ?>>
149
150 <div class="ei-document easy-invoice-<?php echo esc_attr( $document_type ); ?>-container">
151
152 <?php if ( $can_act && '' !== $accept_description ) : ?>
153 <div class="ei-document__notice accept-action-info">
154 <strong><?php echo esc_html( $text_settings['accept_quote'] ); ?>:</strong>
155 <?php echo esc_html( $accept_description ); ?>
156 </div>
157 <?php elseif ( '' !== $quote_closed_reason ) : ?>
158 <div class="ei-document__notice ei-document__notice--<?php echo esc_attr( $quote_closed_tone ); ?>">
159 <?php echo esc_html( $quote_closed_reason ); ?>
160 </div>
161 <?php endif; ?>
162
163 <?php
164 // An offline payment the client told us about and staff have not yet
165 // confirmed: say so, so they do not pay twice or wonder where it went.
166 $ei_pending = ! $is_quote ? \EasyInvoice\Services\OfflinePayments::pendingForInvoice( (int) $document_id ) : [];
167 if ( $ei_pending ) :
168 $ei_p_fmt = new \EasyInvoice\Helpers\InvoiceFormatter( $invoice );
169 foreach ( $ei_pending as $ei_p ) :
170 $ei_p_when = (string) get_post_meta( $ei_p->ID, '_payment_date', true );
171 $ei_p_amount = (float) get_post_meta( $ei_p->ID, '_amount', true );
172 $ei_p_method = \EasyInvoice\Services\OfflinePayments::label( (string) get_post_meta( $ei_p->ID, '_payment_method', true ) );
173 ?>
174 <div class="ei-document__notice ei-document__notice--pending" data-ei-pending-payment="<?php echo (int) $ei_p->ID; ?>">
175 <strong><?php esc_html_e( 'Payment noted — awaiting confirmation.', 'easy-invoice' ); ?></strong>
176 <?php
177 printf(
178 /* translators: 1: amount, 2: payment method, 3: date. */
179 esc_html__( 'You told us on %3$s that you paid %1$s by %2$s. It will show as paid once we have confirmed it; nothing more is needed from you.', 'easy-invoice' ),
180 esc_html( $ei_p_fmt->format( $ei_p_amount ) ),
181 esc_html( $ei_p_method ),
182 esc_html( '' !== $ei_p_when && strtotime( $ei_p_when ) ? date_i18n( get_option( 'date_format' ), strtotime( $ei_p_when ) ) : date_i18n( get_option( 'date_format' ) ) )
183 );
184 ?>
185 </div>
186 <?php
187 endforeach;
188 endif;
189 ?>
190
191 <div class="ei-document__actions <?php echo esc_attr( $document_type ); ?>-actions">
192 <?php if ( $can_act ) : ?>
193 <button type="button" class="ei-btn ei-btn--accept accept" data-ei-action="accept"
194 data-description="<?php echo esc_attr( $accept_description ); ?>">
195 <?php echo esc_html( $text_settings['accept_quote'] ); ?>
196 </button>
197 <button type="button" class="ei-btn ei-btn--decline decline" data-ei-action="decline"
198 data-message="<?php echo esc_attr( \EasyInvoice\Controllers\SettingsController::getDeclinedQuoteMessage() ); ?>"
199 data-reason-required="<?php echo \EasyInvoice\Controllers\SettingsController::isDeclineReasonRequired() ? '1' : '0'; ?>">
200 <?php echo esc_html( $text_settings['decline_quote'] ); ?>
201 </button>
202 <?php endif; ?>
203
204 <button type="button" class="ei-btn ei-btn--print" data-ei-action="print">
205 <?php echo esc_html( $text_settings['print'] ); ?>
206 </button>
207
208 <a class="ei-btn ei-btn--pdf" href="<?php echo esc_url( $pdf_url ); ?>" data-ei-action="pdf">
209 <?php echo esc_html( $text_settings['download_pdf'] ); ?>
210 </a>
211
212 <?php
213 // "Send Email" is a staff action — it emails the document to the
214 // client. A client looking at their own invoice was shown it too,
215 // which reads as if they are about to email someone; they have Print
216 // and Download instead. The endpoint's own authorisation is unchanged.
217 $ei_can_send = is_user_logged_in() && ( current_user_can( 'manage_options' ) || easy_invoice_user_can( 'ei_send_invoice' ) || easy_invoice_user_can( 'ei_view_invoices' ) );
218 /**
219 * Filter whether the document page offers "Send Email".
220 *
221 * @param bool $show Default: staff only.
222 * @param object $document Invoice or quote.
223 * @param string $document_type 'invoice' or 'quote'.
224 */
225 if ( apply_filters( 'easy_invoice_show_send_email_button', $ei_can_send, $document, $document_type ) ) :
226 ?>
227 <button type="button" class="ei-btn ei-btn--email send-email-button" data-ei-action="email">
228 <?php echo esc_html( $text_settings['send_email'] ); ?>
229 </button>
230 <?php endif; ?>
231
232 <?php if ( $can_pay ) : ?>
233 <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'] ); ?>">
234 <?php echo esc_html( $text_settings['pay_now'] ); ?>
235 </button>
236 <?php endif; ?>
237
238 <?php
239 /**
240 * Fires after the built-in buttons in the action bar.
241 *
242 * @param object $document The invoice or quote.
243 * @param string $document_type 'invoice' or 'quote'.
244 */
245 do_action( 'easy_invoice_document_actions', $document, $document_type );
246 ?>
247 </div>
248
249 <?php if ( ! $is_quote ) : ?>
250 <?php do_action( 'easy_invoice_before_invoice_content_top', $invoice ); ?>
251 <?php endif; ?>
252
253 <div class="ei-document__layout <?php echo esc_attr( $document_type ); ?>-layout">
254 <div class="ei-document__content <?php echo esc_attr( $document_type ); ?>-content" id="<?php echo esc_attr( $document_type ); ?>-content">
255
256 <?php if ( $is_admin_user ) : ?>
257 <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' ); ?>">
258 <span aria-hidden="true">{ }</span>
259 </button>
260 <?php endif; ?>
261
262 <?php
263 if ( $is_quote ) {
264 do_action( 'easy_invoice_quote_view_content_top', $quote );
265 } else {
266 do_action( 'easy_invoice_invoice_view_content_top', $invoice );
267 }
268
269 if ( '' !== $template_file ) {
270 include $template_file;
271 } else {
272 easy_invoice_get_template( 'document/fallback.php', [
273 'document' => $document,
274 'document_type' => $document_type,
275 'formatter' => $formatter,
276 'invoice' => $invoice,
277 'quote' => $quote,
278 ] );
279 }
280
281 if ( $is_quote ) {
282 do_action( 'easy_invoice_quote_content_after', $quote );
283 }
284 ?>
285 </div>
286
287 <?php if ( $can_pay ) : ?>
288 <div id="payment-panel" class="ei-payment-panel payment-panel" hidden>
289 <button type="button" class="ei-payment-panel__close" data-ei-action="close-payment" aria-label="<?php esc_attr_e( 'Close', 'easy-invoice' ); ?>">&times;</button>
290 <?php easy_invoice_get_template( 'payment-section.php' ); ?>
291 </div>
292 <?php endif; ?>
293 </div>
294 </div>
295
296 <?php if ( $is_admin_user ) : ?>
297 <?php
298 easy_invoice_get_template( 'document/css-panel.php', [
299 'document_type' => $document_type,
300 'additional_css' => $additional_css,
301 ] );
302 ?>
303 <?php endif; ?>
304
305 <?php
306 /**
307 * Fires before wp_footer() on the public document page.
308 *
309 * @param object $document The invoice or quote.
310 * @param string $document_type 'invoice' or 'quote'.
311 */
312 do_action( 'easy_invoice_document_footer', $document, $document_type );
313
314 wp_footer();
315 ?>
316 </body>
317 </html>
318