Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 750
0.00% covered (danger)
0.00%
0 / 21
CRAP
n/a
0 / 0
zeroBSCRM_invoiceBuilder_isInvoiceDue
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
zeroBSCRM_replace_invoice_submit_meta_box
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_invoiceBuilderColumnCount
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_invoice_generateInvoiceHTML
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_invoice_generateInvoiceHTML_v3
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_invoice_generatePortalInvoiceHTML
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
zeroBSCRM_invoice_generatePortalInvoiceHTML_v3
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
56
zbs_invoice_generate_pdf
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
jpcrm_invoice_generate_pdf
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
zbs_invoice_html
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
zeroBSCRM_invoicing_generateStatementPDF
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
132
zeroBSCRM_invoicing_generateStatementHTML
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_invoicing_generateStatementHTML_v3
0.00% covered (danger)
0.00%
0 / 161
0.00% covered (danger)
0.00%
0 / 1
1056
zeroBSCRM_invoicing_generateInvoiceHTML
0.00% covered (danger)
0.00%
0 / 276
0.00% covered (danger)
0.00%
0 / 1
6972
zeroBSCRM_invoicing_generateInvPart_bizTable
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
110
zeroBSCRM_invoicing_generateInvPart_custTable
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
342
jpcrm_invoicing_generate_customer_custom_fields_lines
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
182
jpcrm_invoicing_generate_invoice_custom_fields_lines
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
110
zeroBSCRM_invoicing_generateInvPart_lineitems
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
zeroBSCRM_invoicing_generateInvPart_payButton
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
56
zeroBSCRM_invoicing_generateInvPart_tableHeaders
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/*
3 * Jetpack CRM
4 * https://jetpackcrm.com
5 * V1.20
6 *
7 * Copyright 2020 Automattic
8 *
9 * Date: 01/11/16
10 */
11
12defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
13
14// takes inv meta and works out if due
15// now v3.0 friendly!
16function zeroBSCRM_invoiceBuilder_isInvoiceDue( $zbsInvoice = array() ) {
17
18    global $zbs;
19
20    if ( is_array( $zbsInvoice ) ) {
21
22        // first get due
23        $due = 0;
24        if ( isset( $zbsInvoice['due_date'] ) && $zbsInvoice['due_date'] > 0 ) {
25            $due = (int) $zbsInvoice['due_date'];
26        }
27
28        // compare (could give days difference here, but not req yet.)
29        if ( $due > time() ) {
30            // not due
31            return false;
32        } else {
33            // due
34            return true;
35        }
36    }
37
38    return false;
39}
40
41/*
42======================================================
43    ZBS Invoicing - REMOVE previous submit meta box + replace with custom
44    ====================================================== */
45
46#} This adds our own save box
47function zeroBSCRM_replace_invoice_submit_meta_box() {
48
49        #} remove typical submit box:
50        remove_meta_box( 'submitdiv', 'zerobs_invoice', 'core' ); // $item represents post_type
51
52        #} Include/initialise custom submitbox
53        require_once ZEROBSCRM_INCLUDE_PATH . 'ZeroBSCRM.MetaBoxes.SubmitBoxes.php';
54}
55
56add_action( 'admin_init', 'zeroBSCRM_replace_invoice_submit_meta_box' );
57
58#} This specifies 1 column pre-save, 2 columns post-save :)
59function zeroBSCRM_invoiceBuilderColumnCount() {
60
61        global $post;
62    if ( isset( $post->post_status ) && $post->post_status != 'auto-draft' ) {
63        return 2;
64    }
65
66        return 2;
67}
68add_filter( 'get_user_option_screen_layout_zerobs_invoice', 'zeroBSCRM_invoiceBuilderColumnCount' );
69
70/*
71======================================================
72    ZBS Invoicing - HTML GENERATOR
73    ====================================================== */
74
75#} Generates the HTML of an invoice based on the template in templates/invoices/invoice-pdf.html
76#} if $return, it'll return, otherwise it'll echo + exit
77#} --------- Notes:
78#} ... there's several ways we COULD do this,
79#} ... suggest we explore this way first, then re-discuss,
80#} ... Benefits of this inc. easy to theme ;) just create variations of invoice.html
81// Note: This is primarily used to generate PDF invoice html. (dig down and see use if "zeroBSCRM_invoicing_generateInvoiceHTML($invoiceID,'pdf'")
82function zeroBSCRM_invoice_generateInvoiceHTML( $invoicePostID = -1, $return = true ) {
83
84    if ( ! empty( $invoicePostID ) ) {
85
86        global $zbs;
87        return zeroBSCRM_invoice_generateInvoiceHTML_v3( $invoicePostID, $return );
88
89    }
90
91    #} Empty inv id
92    return false;
93}
94
95// invoice html generation 3.0+
96function zeroBSCRM_invoice_generateInvoiceHTML_v3( $invoiceID = -1, $return = true ) {
97
98    global $zbs;
99
100    if ( ! empty( $invoiceID ) ) {
101
102        // Discern template and retrieve
103        $global_invoice_pdf_template = zeroBSCRM_getSetting( 'inv_pdf_template' );
104        if ( ! empty( $global_invoice_pdf_template ) ) {
105            $templatedHTML = jpcrm_retrieve_template( $global_invoice_pdf_template, false );
106        }
107
108        // fallback to default template
109        if ( ! isset( $templatedHTML ) || empty( $templatedHTML ) ) {
110
111            // template failed as setting potentially holds out of date (removed) template
112            // so use the default
113            $templatedHTML = jpcrm_retrieve_template( 'invoices/invoice-pdf.html', false );
114
115        }
116
117        #} Act
118        if ( ! empty( $templatedHTML ) ) {
119
120            // Over-ride the #MSGCONTENT# part
121            $placeholder_templating = $zbs->get_templating();
122
123            // replace the content with our new ID ... (gets our content template info and replaces ###MSG CONTENT)
124            $message_content = zeroBSCRM_mailTemplate_get( ZBSEMAIL_EMAILINVOICE );
125            $message_content = $message_content->zbsmail_body;
126            $templatedHTML   = $placeholder_templating->replace_single_placeholder( 'msg-content', $message_content, $templatedHTML );
127
128            // for v3.0 WH split out the data-retrieval from scattering amongst this func, unified here:
129            // translated the below cpt way into dal / v3.0:
130
131            // this was refactored as was duplicate code.
132            // now all wired through zeroBSCRM_invoicing_generateInvoiceHTML
133            $html = zeroBSCRM_invoicing_generateInvoiceHTML( $invoiceID, 'pdf', $templatedHTML );
134
135            // return
136            if ( ! $return ) {
137                echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
138                exit( 0 );
139            }
140        }
141
142        return $html;
143
144    }
145
146    #} Empty inv id
147    return false;
148}
149
150// this was clunky, so split into 3.0 and <3.0 versions.
151// ultimately this is much like zeroBSCRM_invoice_generateInvoiceHTML
152// ... should refactor the bits that are the same
153function zeroBSCRM_invoice_generatePortalInvoiceHTML( $invoicePostID = -1, $return = true ) {
154    global $zbs;
155
156    if ( ! empty( $invoicePostID ) ) {
157        return zeroBSCRM_invoice_generatePortalInvoiceHTML_v3( $invoicePostID, $return );
158    }
159
160    #} Empty inv id
161    return false;
162}
163
164// 3.0+
165function zeroBSCRM_invoice_generatePortalInvoiceHTML_v3( $invoiceID = -1, $return = true ) {
166
167    global $zbs;
168
169    if ( ! empty( $invoiceID ) ) {
170
171        // Discern template and retrieve
172        $global_invoice_portal_template = zeroBSCRM_getSetting( 'inv_portal_template' );
173        if ( ! empty( $global_invoice_portal_template ) ) {
174            $html = jpcrm_retrieve_template( $global_invoice_portal_template, false );
175        }
176
177        // fallback to default template
178        if ( ! isset( $html ) || empty( $html ) ) {
179
180            // template failed as setting potentially holds out of date (removed) template
181            // so use the default
182            $html = jpcrm_retrieve_template( 'invoices/portal-invoice.html', false );
183
184        }
185
186        #} Act
187        if ( ! empty( $html ) ) {
188
189            // load templating
190            $placeholder_templating = $zbs->get_templating();
191
192            // replace the content with our new ID ... (gets our content template info and replaces ###MSG CONTENT)
193            $message_content = zeroBSCRM_mailTemplate_get( ZBSEMAIL_EMAILINVOICE );
194            $message_content = $message_content->zbsmail_body;
195            $html            = $placeholder_templating->replace_single_placeholder( 'msg-content', $message_content, $html );
196
197            // this was refactored as was duplicate code.
198            // now all wired through zeroBSCRM_invoicing_generateInvoiceHTML
199            $html = zeroBSCRM_invoicing_generateInvoiceHTML( $invoiceID, 'portal', $html );
200
201            // return
202            if ( ! $return ) {
203                echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
204                exit( 0 );
205            }
206        }
207
208        return $html;
209    }
210
211    #} Empty inv id
212    return false;
213}
214
215function zbs_invoice_generate_pdf() {
216
217    // download flag
218    if ( isset( $_POST['zbs_invoicing_download_pdf'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
219
220        // THIS REALLLY needs nonces! For now (1.1.19) added this for you...
221        if ( ! zeroBSCRM_permsInvoices() ) {
222            exit( 0 );
223        }
224
225        // Check ID
226        $invoice_id = -1;
227        if ( ! empty( $_POST['zbs_invoice_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
228            $invoice_id = (int) $_POST['zbs_invoice_id']; // phpcs:ignore WordPress.Security.NonceVerification.Missing
229        }
230        if ( $invoice_id <= 0 ) {
231            exit( 0 );
232        }
233
234        // generate the PDF
235        $pdf_path = jpcrm_invoice_generate_pdf( $invoice_id );
236
237        if ( $pdf_path !== false ) {
238
239            $pdf_filename = basename( $pdf_path );
240
241            // print the pdf file to the screen for saving
242            header( 'Content-type: application/pdf' );
243            header( 'Content-Disposition: attachment; filename="' . $pdf_filename . '"' );
244            header( 'Content-Transfer-Encoding: binary' );
245            header( 'Content-Length: ' . filesize( $pdf_path ) );
246            header( 'Accept-Ranges: bytes' );
247            readfile( $pdf_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile
248
249            // delete the PDF file once it's been read (i.e. downloaded)
250            wp_delete_file( $pdf_path );
251
252        }
253
254        exit( 0 );
255    }
256}
257// This fires post ZBS init
258add_action( 'zerobscrm_post_init', 'zbs_invoice_generate_pdf' );
259
260/**
261 * Generate PDF file for an invoice
262 *
263 * @param int $invoice_id Invoice ID.
264 * @return string path to PDF file
265 */
266function jpcrm_invoice_generate_pdf( $invoice_id = -1 ) {
267
268    // brutal.
269    if ( ! zeroBSCRM_permsInvoices() ) {
270        return false;
271    }
272
273    // If user has no perms, or id not present, die
274    if ( $invoice_id <= 0 ) {
275        return false;
276    }
277
278    // Generate html
279    $html = zeroBSCRM_invoice_generateInvoiceHTML( $invoice_id );
280
281    global $zbs;
282    $invoice = $zbs->DAL->invoices->getInvoice( $invoice_id ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
283
284    // if invoice has reference number, use instead of ID
285    if ( ! empty( $invoice['id_override'] ) ) {
286        $invoice_id = $invoice['id_override'];
287    }
288
289    // normalise translated text to alphanumeric, resulting in a filename like `invoice-321.pdf`
290    $pdf_filename = sanitize_title( __( 'Invoice', 'zero-bs-crm' ) . '-' . $invoice_id ) . '.pdf';
291
292    // return PDF filename if successful, false if not
293    return jpcrm_generate_pdf( $html, $pdf_filename );
294}
295
296// LEGACY, should now be using zeroBSCRM_invoice_generateInvoiceHTML
297// still used in Client Portal Pro
298function zbs_invoice_html( $invoicePostID ) {
299
300    $html = zeroBSCRM_invoice_generateInvoiceHTML( $invoicePostID );
301
302    return $html;
303}
304
305#} this generates a PDF statement for a contact, either returning the filepath or a PDF download prompt
306function zeroBSCRM_invoicing_generateStatementPDF( $contactID = -1, $returnPDF = false ) {
307
308    if ( ! zeroBSCRM_permsInvoices() ) {
309        exit( 0 );
310    }
311
312    global $zbs;
313
314    #} Check ID
315    $contactID = (int) $contactID;
316    #} If user has no perms, or id not present, die
317    if ( ! zeroBSCRM_permsInvoices() || empty( $contactID ) || $contactID <= 0 ) {
318        die( 0 );
319    }
320
321    $html = zeroBSCRM_invoicing_generateStatementHTML( $contactID );
322
323    // build PDF
324    $dompdf = $zbs->pdf_engine();
325    $dompdf->loadHtml( $html, 'UTF-8' );
326    $dompdf->render();
327
328    // target dir
329    $upload_dir      = wp_upload_dir();
330    $zbsInvoicingDir = $upload_dir['basedir'] . '/invoices/';
331
332    if ( ! file_exists( $zbsInvoicingDir ) ) {
333        wp_mkdir_p( $zbsInvoicingDir );
334    }
335    // got it?
336    if ( ! file_exists( $zbsInvoicingDir ) ) {
337        return false;
338    }
339
340    // make a hash
341    // here we've tried to protect against someone overriding the security,
342    // but if they're inside... it's too late anyhow.
343    $hash = wp_generate_password( 14, false );
344    if ( empty( $hash ) || strlen( $hash ) < 14 ) {
345        $hash = md5( time() . 'xcsac' ); // backup
346    }
347
348    $statementFilename = $zbsInvoicingDir . $hash . '-' . __( 'statement', 'zero-bs-crm' ) . '-' . $contactID . '.pdf';
349
350    // save the pdf file on the server
351    file_put_contents( $statementFilename, $dompdf->output() );
352
353    if ( file_exists( $statementFilename ) ) {
354
355        // if return pdf, return, otherwise return filepath
356        if ( $returnPDF ) {
357
358            // print the pdf file to the screen for saving
359            header( 'Content-type: application/pdf' );
360            header( 'Content-Disposition: attachment; filename="' . __( 'statement', 'zero-bs-crm' ) . '-' . $contactID . '.pdf' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
361            header( 'Content-Transfer-Encoding: binary' );
362            header( 'Content-Length: ' . filesize( $statementFilename ) );
363            header( 'Accept-Ranges: bytes' );
364            readfile( $statementFilename );
365
366            // delete the PDF file once it's been read (i.e. downloaded)
367            unlink( $statementFilename );
368
369        } else {
370
371            return $statementFilename;
372
373        }
374    } // if file
375
376    return false;
377}
378
379/*
380======================================================
381    ZBS Invoicing - STATEMENT HTML GENERATOR
382    ====================================================== */
383
384// phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamTag
385/**
386 * Generates the HTML of an invoice based on the template in templates/invoices/statement-pdf.html
387 * if $return, it'll return, otherwise it'll echo + exit
388 **/
389function zeroBSCRM_invoicing_generateStatementHTML( $contact_id = -1, $return = true ) {
390
391    if ( ! empty( $contact_id ) && $contact_id > 0 ) {
392
393        // Discern template and retrieve
394        $global_statement_pdf_template = zeroBSCRM_getSetting( 'statement_pdf_template' );
395        if ( ! empty( $global_statement_pdf_template ) ) {
396            $templated_html = jpcrm_retrieve_template( $global_statement_pdf_template, false );
397        }
398
399        // fallback to default template
400        if ( ! isset( $templated_html ) || empty( $templated_html ) ) {
401            // template failed as setting potentially holds out of date (removed) template
402            // so use the default
403            $templated_html = jpcrm_retrieve_template( 'invoices/statement-pdf.html', false );
404        }
405
406        // Act
407        if ( ! empty( $templated_html ) ) {
408            return zeroBSCRM_invoicing_generateStatementHTML_v3( $contact_id, $return, $templated_html );
409        }
410    }
411
412    // Empty inv id
413    return false;
414}
415
416// 3.0+ (could now run off contact or company, but that's not written in yet)
417// phpcs:ignore Squiz.Commenting.FunctionComment.Missing
418function zeroBSCRM_invoicing_generateStatementHTML_v3( $contact_id = -1, $return = true, $templated_html = '' ) {
419
420    if ( $contact_id > 0 && $templated_html !== '' ) {
421
422        global $zbs;
423
424        // Globals
425        $statement_extra = zeroBSCRM_getSetting( 'statementextra' );
426        $logo_url        = zeroBSCRM_getSetting( 'invoicelogourl' );
427        $biz_name        = zeroBSCRM_getSetting( 'businessname' );
428        // invoices
429        $invoices = $zbs->DAL->invoices->getInvoices( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
430            array(
431
432                'assignedContact'  => $contact_id, // assigned to contact id (int)
433                'assignedCompany'  => false, // assigned to company id (int)
434
435                // returns
436                'withLineItems'    => true,
437                'withCustomFields' => true,
438                'withTransactions' => true, // partials
439                'withTags'         => false,
440
441                // sort
442                'sortByField'      => 'ID',
443                'sortOrder'        => 'ASC',
444
445            )
446        );
447
448        $statement_table = '';
449
450        // logo header
451        if ( ! empty( $logo_url ) ) {
452
453            $statement_table .= sprintf(
454                "<div class='header-image'><img src='%s' alt='%s'></div>",
455                esc_url( $logo_url ),
456                esc_attr( $biz_name )
457            );
458        }
459
460        // title
461        $statement_table .= sprintf(
462            '<div class="div-table" role="table" aria-label="Statement Details">
463                <div class="div-table-row-group">
464                    <div class="div-table-row">
465                        <div class="div-table-cell" colspan="3"><h2 class="zbs-statement">%s</h2></div>
466                    </div>
467                </div>',
468            esc_html__( 'STATEMENT', 'zero-bs-crm' )
469        );
470
471        // address | dates | biz deets line
472        $statement_table .= '<div class="div-table-row">';
473
474        // contact address
475        $contact_details = $zbs->DAL->contacts->getContact( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
476            $contact_id,
477            array(
478                'withCustomFields' => true,
479                'withQuotes'       => false,
480                'withInvoices'     => false,
481                'withTransactions' => false,
482                'withLogs'         => false,
483                'withLastLog'      => false,
484                'withTags'         => false,
485                'withCompanies'    => false,
486                'withOwner'        => false,
487                'withValues'       => false,
488            )
489        );
490        if ( is_array( $contact_details ) && isset( $contact_details['fname'] ) ) {
491            $invoice_customer_info_cell_html = sprintf(
492                '<b>%s %s</b>',
493                esc_html( $contact_details['fname'] ),
494                esc_html( $contact_details['lname'] )
495            );
496
497            $invoice_customer_info_cell_html .= '<div class="info-details">';
498            $details_to_add                   = array( 'addr1', 'addr2', 'city', 'county', 'postcode' );
499
500            $customer_details = array();
501            foreach ( $details_to_add as $detail_name ) {
502                if ( isset( $contact_details[ $detail_name ] ) && ! empty( $contact_details[ $detail_name ] ) ) {
503                    $customer_details[] = esc_html( $contact_details[ $detail_name ] );
504                }
505            }
506            $invoice_customer_info_cell_html .= implode( '<br>', $customer_details );
507            $invoice_customer_info_cell_html .= '</div>';
508        }
509        $statement_table .= '<div class="div-table-cell">' . $invoice_customer_info_cell_html . '</div>';
510
511        // Dates
512        $statement_table .= sprintf(
513            '<div class="div-table-cell center medium-font"><b>%s</b><br>%s</div>',
514            esc_html__( 'Statement Date', 'zero-bs-crm' ),
515            esc_html( zeroBSCRM_locale_utsToDate( time() ) )
516        );
517
518        // Biz deets
519        $business_details_to_add          = array( 'businessyourname', 'businessyouremail', 'businessyoururl', 'businessextra', 'businesstel' );
520        $invoice_business_info_cell_html  = sprintf(
521            '<b>%s</b>',
522            esc_html( $biz_name )
523        );
524        $invoice_business_info_cell_html .= '<div class="info-details">';
525        $details_to_add                   = array( 'addr1', 'addr2', 'city', 'county', 'postcode' );
526        $detail_contents                  = array();
527        foreach ( $business_details_to_add as $detail_name ) {
528            $detail_content = zeroBSCRM_getSetting( $detail_name );
529            if ( ! empty( $detail_content ) ) {
530                $detail_contents[] = esc_html( $detail_content );
531            }
532        }
533
534        // Join all non-empty details with <br> tag
535        $invoice_business_info_cell_html .= implode( '<br>', $detail_contents );
536
537        $invoice_business_info_cell_html .= '</div>';
538        $statement_table                 .= '<div class="div-table-cell right">' . $invoice_business_info_cell_html . '</div>';
539        // Close div-table and div-table-row-group
540        $statement_table .= '</div></div>';
541
542        // STATEMENT table
543        // header
544        $s_table = sprintf(
545            '<div class="div-table" role="table" aria-label="Transactions">
546                <div class="div-table-head">
547                    <div class="div-table-row">
548                        <div class="div-table-heading">%s</div>
549                        <div class="div-table-heading">%s</div>
550                        <div class="div-table-heading">%s</div>
551                        <div class="div-table-heading right">%s</div>
552                        <div class="div-table-heading right">%s</div>
553                        <div class="div-table-heading right">%s</div>
554                    </div>
555                </div>',
556            esc_html__( 'Date', 'zero-bs-crm' ),
557            esc_html( rtrim( $zbs->settings->get( 'reflabel' ), ':' ) ),
558            esc_html__( 'Due date', 'zero-bs-crm' ),
559            esc_html__( 'Amount', 'zero-bs-crm' ),
560            esc_html__( 'Payments', 'zero-bs-crm' ),
561            esc_html__( 'Balance', 'zero-bs-crm' )
562        );
563
564        // should be all of em so can do 'outstanding balance' from this
565        $balance_due = 0.00;
566
567        $s_table .= '<div class="div-table-row-group">';
568
569        // rows. (all invs for this contact)
570        if ( is_array( $invoices ) && count( $invoices ) > 0 ) {
571
572            foreach ( $invoices as $invoice ) {
573
574                // number
575                $invoice_reference = $invoice['id'];
576                if ( isset( $invoice['id_override'] ) && ! empty( $invoice['id_override'] ) ) {
577                    $invoice_reference = $invoice['id_override'];
578                }
579
580                // date
581                $invoice_date = $invoice['date_date'];
582
583                // due
584                if ( $invoice['due_date'] <= 0 ) {
585
586                    // no due date;
587                    $due_date_str = __( 'No due date', 'zero-bs-crm' );
588
589                } else {
590
591                    $due_date_str = $invoice['due_date_date'];
592
593                    // is it due?
594                    if ( zeroBSCRM_invoiceBuilder_isInvoiceDue( $invoice ) ) {
595                        $due_date_str .= ' [' . esc_html__( 'Due', 'zero-bs-crm' ) . ']';
596                    }
597                }
598
599                // partials = transactions associated in v3.0 model
600                $partials = $invoice['transactions'];
601
602                // ================= / DATA RETRIEVAL ===================================
603
604                // total etc.
605                $total    = 0.00;
606                $payments = 0.00;
607                $balance  = 0.00;
608                if ( isset( $invoice['total'] ) && $invoice['total'] > 0 ) {
609                    $total   = $invoice['total'];
610                    $balance = $total;
611                }
612
613                // 2 ways here - if marked 'paid', then assume balance
614                // ... if not, then trans allocation check
615                if ( isset( $invoice['status'] ) && $invoice['status'] === 'Paid' ) {
616
617                    // assume fully paid
618                    $balance  = 0.00;
619                    $payments = $total;
620
621                } elseif ( is_array( $partials ) ) {
622
623                    foreach ( $partials as $partial ) {
624
625                        // ignore if status_bool (non-completed status)
626                        $partial['status_bool'] = (int) $partial['status_bool'];
627                        if ( isset( $partial ) && $partial['status_bool'] == 1 && isset( $partial['total'] ) && $partial['total'] > 0 ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
628
629                            // v3.0+ has + or - partials. Account for that:
630                            if ( $partial['type_accounting'] === 'credit' ) {
631
632                                // credit note, or refund
633                                $balance = $balance + $partial['total'];
634                                // add to payments
635                                $payments += $partial['total'];
636
637                            } else {
638
639                                // assume debit
640                                $balance = $balance - $partial['total'];
641
642                                // add to payments
643                                $payments += $partial['total'];
644                            }
645                        }
646                    } // /foreach
647
648                } // if is array
649
650                // now we add any outstanding bal to the total bal for table
651                if ( $balance > 0 ) {
652                    $balance_due += $balance;
653                }
654
655                // output
656                $s_table .= sprintf(
657                    '<div class="div-table-row">
658                        <div class="div-table-cell">%s</div>
659                        <div class="div-table-cell">%s</div>
660                        <div class="div-table-cell">%s</div>
661                        <div class="div-table-cell right">%s</div>
662                        <div class="div-table-cell right">%s</div>
663                        <div class="div-table-cell right">%s</div>
664                    </div>',
665                    esc_html( $invoice_date ),
666                    esc_html( $invoice_reference ),
667                    esc_html( $due_date_str ),
668                    esc_html( zeroBSCRM_formatCurrency( $total ) ),
669                    esc_html( zeroBSCRM_formatCurrency( $payments ) ),
670                    esc_html( zeroBSCRM_formatCurrency( $balance ) )
671                );
672            }
673        } else {
674            // No invoices?
675            $s_table .= sprintf(
676                '<div class="div-table-row"><div class="div-table-cell center" colspan="6">%s</div></div>',
677                esc_html__( 'No Activity', 'zero-bs-crm' )
678            );
679        }
680        // Close `div-table-row-group`
681        $s_table .= '</div>';
682        // Close `div-table`
683        $s_table         .= '</div>';
684        $statement_table .= $s_table;
685
686        // footer
687        $statement_table .= sprintf(
688            '<div class="div-footer">
689                <div class="div-table-row">
690                    <div class="div-footer-cell right" colspan="6">%s %s</div>
691                </div>
692            </div>',
693            esc_html__( 'BALANCE DUE', 'zero-bs-crm' ),
694            esc_html( zeroBSCRM_formatCurrency( $balance_due ) )
695        );
696
697        // Extra Info
698        if ( ! empty( $statement_extra ) ) {
699            $statement_table .= sprintf(
700                '<div class="div-footer">
701                    <div class="div-table-row">
702                        <div class="div-footer-cell" colspan="6">%s</div>
703                    </div>
704                </div>',
705                nl2br( esc_html( $statement_extra ) )
706            );
707        }
708
709        // load templating
710        $placeholder_templating = $zbs->get_templating();
711
712        // main content build
713        $html = $placeholder_templating->replace_single_placeholder( 'invoice-statement-html', $statement_table, $templated_html );
714
715        // return
716        if ( ! $return ) {
717
718            echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
719            exit( 0 );
720
721        }
722
723        return $html;
724
725    } // /if anything
726
727    return false;
728}
729
730// phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamTag,Generic.Commenting.DocComment.MissingShort
731/**
732 *
733 * Generate an invoice as html (where the actual holder-html is passed)
734 * ... this is a refactor of what was being replicated 3 places
735 * ... 1) Invoice PDF Gen (zeroBSCRM_invoice_generateInvoiceHTML)
736 * ... 2) Invoice Portal Gen (zeroBSCRM_invoice_generatePortalInvoiceHTML)
737 * ... 3) Invoice email notification Gen (zeroBSCRM_invoice_generateNotificationHTML in mail-templating.php)
738 * ... now the generic element of the above are all wired through here :)
739 * Note:
740 * $template is crucial. pdf | portal | notification *currently v3.0
741 **/
742function zeroBSCRM_invoicing_generateInvoiceHTML( $invoice_id = -1, $template = 'pdf', $html = '' ) {
743
744    global $zbs;
745
746    // load templating
747    $placeholder_templating = $zbs->get_templating();
748
749    // need this.
750    if ( $invoice_id <= 0 || $html == '' ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
751        return '';
752    }
753
754    // for v3.0 WH split out the data-retrieval from scattering amongst this func, unified here:
755    // translated the below cpt way into dal / v3.0:
756
757    // ================== DATA RETRIEVAL ===================================
758
759    $invoice = $zbs->DAL->invoices->getInvoice( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
760        $invoice_id,
761        array(
762
763            // we want all this:
764            'withLineItems'    => true,
765            'withCustomFields' => true,
766            'withTransactions' => true, // gets trans associated with inv as well
767            'withAssigned'     => true, // return arrays of assigned contact and companies objects
768            'withTags'         => true,
769            'withOwner'        => true,
770            'withTotals'       => true,
771
772        )
773    );
774
775    // retrieve
776    $zbs_customer_id = -1;
777    if ( is_array( $invoice ) && isset( $invoice['contact'] ) && is_array( $invoice['contact'] ) && count( $invoice['contact'] ) > 0 ) {
778        $zbs_customer_id = $invoice['contact'][0]['id'];
779    }
780    $zbs_company_id = -1;
781    if ( is_array( $invoice ) && isset( $invoice['company'] ) && is_array( $invoice['company'] ) && count( $invoice['company'] ) > 0 ) {
782        $zbs_company_id = $invoice['company'][0]['id'];
783    }
784
785    // date
786    $inv_date_str = jpcrm_uts_to_date_str( $invoice['date'] );
787
788    // due
789    if ( $invoice['due_date'] <= 0 ) {
790
791        // no due date
792        $due_date_str = __( 'No due date', 'zero-bs-crm' );
793
794    } else {
795
796        $due_date_str = jpcrm_uts_to_date_str( $invoice['due_date'] );
797    }
798
799    // Custom fields
800    $invoice_custom_fields_html = jpcrm_invoicing_generate_invoice_custom_fields_lines( $invoice, $template );
801
802    // default status and status label
803    if ( ! isset( $invoice['status'] ) ) {
804        $invoice['status'] = 'Draft';
805    }
806    if ( ! isset( $invoice['status_label'] ) ) {
807        $invoice['status_label'] = __( 'Draft', 'zero-bs-crm' );
808    }
809
810    // status html:
811    if ( $template === 'portal' ) {
812
813        // portal version: Includes status label and amount (shown at top of portal invoice)
814        $top_status  = '<div class="zbs-portal-label">';
815        $top_status .= esc_html( $invoice['status_label'] );
816        $top_status .= '</div>';
817        // WH added quickly to get around fact this is sometimes empty, please tidy when you address currency formatting :)
818        $inv_g_total = '';
819        if ( isset( $invoice['total'] ) ) {
820            $inv_g_total = zeroBSCRM_formatCurrency( $invoice['total'] );
821        }
822        $top_status .= '<h1 class="zbs-portal-value">' . esc_html( $inv_g_total ) . '</h1>';
823        if ( $invoice['status'] === 'Paid' ) {
824            $top_status .= '<div class="zbs-invoice-paid"><i class="fa fa-check"></i>' . esc_html( $invoice['status_label'] ) . '</div>';
825        }
826    } elseif ( $template === 'pdf' ) {
827
828        // pdf status
829        if ( $invoice['status'] === 'Paid' ) {
830
831            $top_status = '<div class="jpcrm-invoice-status jpcrm-invoice-paid">' . esc_html( $invoice['status_label'] ) . '</div>';
832
833        } else {
834
835            $top_status = '<div class="jpcrm-invoice-status">' . esc_html( $invoice['status_label'] ) . '</div>';
836
837        }
838    } elseif ( $template === 'notification' ) {
839        // sent to contact via email
840        $top_status = esc_html( $invoice['status_label'] );
841    }
842
843    // inv lines
844    $invlines = $invoice['lineitems'];
845
846    // switch for Company if set...
847    if ( $zbs_company_id > 0 ) {
848
849        $inv_to = zeroBS_getCompany( $zbs_company_id );
850        if ( is_array( $inv_to ) && ( isset( $inv_to['name'] ) || isset( $inv_to['coname'] ) ) ) {
851
852            if ( isset( $inv_to['name'] ) ) {
853                $inv_to['fname'] = $inv_to['name']; // DAL3
854            }
855            if ( isset( $inv_to['coname'] ) ) {
856                $inv_to['fname'] = $inv_to['coname']; // DAL2
857            }
858            $inv_to['lname'] = '';
859
860        } else {
861
862            $inv_to = array(
863                'fname' => '',
864                'lname' => '',
865            );
866        }
867
868        // object type flag used downstream, I wonder if we should put these in at the DAL level..
869        $inv_to['objtype'] = ZBS_TYPE_COMPANY;
870    } else {
871
872        $inv_to = $zbs->DAL->contacts->getContact( $zbs_customer_id ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
873
874        if ( ! $inv_to ) {
875            $inv_to = array();
876        }
877
878        // object type flag used downstream, I wonder if we should put these in at the DAL level..
879        $inv_to['objtype'] = ZBS_TYPE_CONTACT;
880    }
881
882    // is this stored same format?
883    $zbs_invoice_hours_or_quantity = $invoice['hours_or_quantity'];
884
885    // partials = transactions associated in v3.0 model
886    $partials = $invoice['transactions'];
887
888    // ================= / DATA RETRIEVAL ===================================
889
890    // ================= CONTENT BUILDING ===================================
891
892    // Globals
893    $invsettings = $zbs->settings->getAll();
894    $css_url     = ZEROBSCRM_URL . 'css/ZeroBSCRM.admin.invoicepreview' . wp_scripts_get_suffix() . '.css';
895    $ref_label   = $zbs->settings->get( 'reflabel' );
896
897    $logo_url = '';
898    if ( isset( $invoice['logo_url'] ) ) {
899
900        if ( isset( $invoice['logo_url'] ) ) {
901            $logo_url = $invoice['logo_url'];
902        }
903    } elseif ( isset( $invsettings['invoicelogourl'] ) && $invsettings['invoicelogourl'] != '' ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
904        $logo_url = $invsettings['invoicelogourl'];
905    }
906
907    if ( $logo_url != '' && isset( $invoice['logo_url'] ) ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
908        $logo_class     = 'show';
909        $logo_url       = $invoice['logo_url'];
910        $biz_info_class = '';
911    } else {
912        $logo_class     = '';
913        $logo_url       = '';
914        $biz_info_class = 'biz-up';
915    }
916
917    // Invoice Number or Reference
918    // Reference, falling back to ID
919    $inv_id_styles  = '';
920    $inv_ref_styles = 'display:none;'; // none initially
921
922    // ID
923    $this_inv_reference = $invoice['id'];
924
925    // ID - Portal
926    if ( $template === 'portal' ) {
927        $this_inv_reference = __( 'Invoice #', 'zero-bs-crm' ) . ' ' . $this_inv_reference;
928    }
929
930    // Reference
931    if ( isset( $invoice['id_override'] ) && ! empty( $invoice['id_override'] ) ) {
932
933        // Ref
934        $this_inv_reference = $invoice['id_override'];
935
936        // Ref - Portal
937        if ( $template === 'portal' ) {
938            $this_inv_reference = $ref_label . ' ' . $this_inv_reference;
939        }
940
941        // and we don't show ID, do show ref label:
942        $inv_id_styles  = 'display:none;';
943        $inv_ref_styles = '';
944
945    }
946
947    // replacement str
948    $inv_no_str = $this_inv_reference;
949
950    // Portal
951    if ( $template === 'portal' ) {
952        $inv_no_str = '<div class="zbs-normal">' . esc_html( $this_inv_reference ) . '</div>';
953    }
954
955    // == Build biz info table.
956
957    // the business info from the settings
958    $zbs_biz_name      = zeroBSCRM_getSetting( 'businessname' );
959    $zbs_biz_yourname  = zeroBSCRM_getSetting( 'businessyourname' );
960    $zbs_biz_extra     = zeroBSCRM_getSetting( 'businessextra' );
961    $zbs_biz_youremail = zeroBSCRM_getSetting( 'businessyouremail' );
962    $zbs_biz_yoururl   = zeroBSCRM_getSetting( 'businessyoururl' );
963
964    // generate a templated biz info table
965    $biz_info_table = zeroBSCRM_invoicing_generateInvPart_bizTable(
966        array(
967            'zbs_biz_name'      => $zbs_biz_name,
968            'zbs_biz_yourname'  => $zbs_biz_yourname,
969            'zbs_biz_extra'     => $zbs_biz_extra,
970            'zbs_biz_youremail' => $zbs_biz_youremail,
971            'zbs_biz_yoururl'   => $zbs_biz_yoururl,
972            'template'          => $template,
973        )
974    );
975
976    // generate a templated customer info table
977    $invoice_customer_info_table_html = zeroBSCRM_invoicing_generateInvPart_custTable( $inv_to, $template );
978
979    // == Lineitem table > Column headers
980    // generate a templated customer info table
981    $table_headers = zeroBSCRM_invoicing_generateInvPart_tableHeaders( $zbs_invoice_hours_or_quantity );
982
983    // == Lineitem table > Line items
984    // generate a templated lineitems
985    $line_items = zeroBSCRM_invoicing_generateInvPart_lineitems( $invlines );
986
987    // == Lineitem table > Totals
988    // due to withTotals parameter on get above, we now don't need ot calc anything here, just expose
989    $totals_table = '';
990
991    $totals_table .= '<table id="invoice_totals" class="table-totals striped" style="width: 100%;;margin-left:0;"><tbody>';
992    if ( $invsettings['invtax'] != 0 || $invsettings['invpandp'] != 0 || $invsettings['invdis'] != 0 ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
993        $totals_table .= '<tr class="total-top">';
994        $totals_table .= '<td  class="bord bord-l" style="text-align:right; width: 80%; text-transform: uppercase;">' . esc_html__( 'Subtotal', 'zero-bs-crm' ) . '</td>';
995        $totals_table .= '<td class="bord row-amount" style="text-align:right;margin-left:-30px;"><span class="zbs-totals">';
996        if ( isset( $invoice['net'] ) && ! empty( $invoice['net'] ) ) {
997            $totals_table .= esc_html( zeroBSCRM_formatCurrency( $invoice['net'] ) );
998        } else {
999            $totals_table .= esc_html( zeroBSCRM_formatCurrency( 0 ) );
1000        }
1001        $totals_table .= '</span></td>';
1002        $totals_table .= '</tr>';
1003    }
1004
1005    // discount
1006    if ( isset( $invoice['discount'] ) && ! empty( $invoice['discount'] ) ) {
1007
1008        if ( $invsettings['invdis'] == 1 && isset( $invoice['totals'] ) && is_array( $invoice['totals'] ) && isset( $invoice['totals']['discount'] ) ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1009            $invoice_percent = '';
1010            if ( $invoice['discount_type'] == '%' && $invoice['discount'] != 0 ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual,Universal.Operators.StrictComparisons.LooseNotEqual
1011                $invoice_percent = (float) $invoice['discount'] . '% ';
1012            }
1013            $totals_table .= '<tr class="discount">
1014                <td class="bord bord-l" style="text-align:right; text-transform: uppercase;">' . esc_html( $invoice_percent . __( 'Discount', 'zero-bs-crm' ) ) . '</td>
1015                <td class="bord row-amount" id="zbs_discount_combi" style="text-align:right"><span class="zbs-totals">';
1016
1017            $totals_table .= '-' . esc_html( zeroBSCRM_formatCurrency( $invoice['totals']['discount'] ) );
1018
1019            $totals_table .= '</span></td>';
1020            $totals_table .= '</tr>';
1021        }
1022    }
1023
1024    // shipping
1025    if ( $invsettings['invpandp'] == 1 ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1026        $totals_table .= '<tr class="postage_and_pack">
1027            <td class="bord bord-l" style="text-align:right; text-transform: uppercase;">' . esc_html__( 'Postage and packaging', 'zero-bs-crm' ) . '</td>
1028            <td class="bord row-amount" id="pandptotal" style="text-align:right;"><span class="zbs-totals">';
1029        if ( isset( $invoice['shipping'] ) && ! empty( $invoice['shipping'] ) ) {
1030            $totals_table .= esc_html( zeroBSCRM_formatCurrency( $invoice['shipping'] ) );
1031        } else {
1032            $totals_table .= esc_html( zeroBSCRM_formatCurrency( 0 ) );
1033        }
1034        $totals_table .= '</span></td>';
1035        $totals_table .= '</tr>';
1036    }
1037
1038    // tax
1039    if ( $invsettings['invtax'] == 1 ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1040
1041        $tax_lines = false;
1042        if ( isset( $invoice['totals'] ) && is_array( $invoice['totals'] ) && isset( $invoice['totals']['taxes'] ) ) {
1043            // now calc'd in DAL
1044            $tax_lines = $invoice['totals']['taxes'];
1045        }
1046
1047        if ( isset( $tax_lines ) && is_array( $tax_lines ) && count( $tax_lines ) > 0 ) {
1048
1049            foreach ( $tax_lines as $tax ) {
1050
1051                $tax_name = __( 'Tax', 'zero-bs-crm' );
1052                if ( isset( $tax['name'] ) ) {
1053                    $tax_name = $tax['name'];
1054                }
1055
1056                $totals_table .= '<tr class="ttclass">
1057                    <td class="bord bord-l" style="text-align:right">' . esc_html( $tax_name ) . '</td>
1058                    <td class="bord bord-l row-amount zbs-tax-total-span" style="text-align:right"><span class="zbs-totals">';
1059                if ( isset( $tax['value'] ) && ! empty( $tax['value'] ) ) {
1060                    $totals_table .= esc_html( zeroBSCRM_formatCurrency( $tax['value'] ) );
1061                } else {
1062                    $totals_table .= esc_html( zeroBSCRM_formatCurrency( 0 ) );
1063                }
1064                $totals_table .= '</span></td>';
1065                $totals_table .= '</tr>';
1066            }
1067        } else {
1068
1069            // simple fallback
1070            $totals_table .= '<tr class="ttclass">
1071                <td class="bord bord-l" style="text-align:right">' . esc_html__( 'Tax', 'zero-bs-crm' ) . '</td>
1072                <td class="bord bord-l row-amount zbs-tax-total-span" style="text-align:right"><span class="zbs-totals">';
1073            if ( isset( $invoice['tax'] ) && ! empty( $invoice['tax'] ) ) {
1074                $totals_table .= esc_html( zeroBSCRM_formatCurrency( $invoice['tax'] ) );
1075            } else {
1076                $totals_table .= esc_html( zeroBSCRM_formatCurrency( 0 ) );
1077            }
1078            $totals_table .= '</span></td>';
1079            $totals_table .= '</tr>';
1080        }
1081    }
1082
1083    $totals_table .= '<tr class="zbs_grand_total" style="line-height:30px;">
1084        <td class="bord-l"  style="text-align:right; font-weight:bold;  border-radius: 0;"><span class="zbs-total">' . __( 'Total', 'zero-bs-crm' ) . '</span></td>
1085        <td class="row-amount" style="text-align:right; font-weight:bold;"><span class="zbs-total">';
1086    if ( isset( $invoice['total'] ) && ! empty( $invoice['total'] ) ) {
1087        $totals_table .= esc_html( zeroBSCRM_formatCurrency( $invoice['total'] ) );
1088    } else {
1089        $totals_table .= esc_html( zeroBSCRM_formatCurrency( 0 ) );
1090    }
1091    $totals_table .= '</span></td>';
1092    $totals_table .= '</tr>';
1093
1094    $totals_table .= '</table>';
1095
1096    // == Partials (Transactions against Invs)
1097    $partials_table = '';
1098
1099    // Check if partial payments are disabled
1100    if ( ! isset( $invsettings['invoicing_disable_partial_payments'] ) || $invsettings['invoicing_disable_partial_payments'] !== 1 ) {
1101
1102        if ( $invoice['total'] == 0 ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1103            $partials_table .= '<table id="partials" class="hide table-totals striped">';
1104        } else {
1105            $partials_table .= '<table id="partials" class="table-totals striped">';
1106        }
1107
1108        $balance = $invoice['total'];
1109
1110        if ( is_array( $partials ) && count( $partials ) > 0 ) {
1111
1112            // header
1113            $partials_table .= '<tr><td colspan="2" style="text-align:center;font-weight:bold;  border-radius: 0;"><span class="zbs-total">' . esc_html__( 'Payments', 'zero-bs-crm' ) . '</span></td></tr>';
1114
1115            foreach ( $partials as $partial ) {
1116
1117                // ignore if status_bool (non-completed status)
1118                $partial['status_bool'] = (int) $partial['status_bool'];
1119                if ( isset( $partial ) && $partial['status_bool'] == 1 ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1120
1121                    // v3.0+ has + or - partials. Account for that:
1122                    if ( $partial['type_accounting'] === 'credit' ) {
1123                        // credit note, or refund
1124                        $balance = $balance + $partial['total'];
1125                    } else {
1126                        // assume debit
1127                        $balance = $balance - $partial['total'];
1128                    }
1129
1130                    $partials_table .= '<tr id="invoice-payments" class="total-top">';
1131                    $partials_table .= '<td class="bord bord-l" style="text-align:right">' . esc_html( $partial['ref'] ) . '</td>';
1132                    $partials_table .= '<td class="bord row-amount"><span class="zbs-partial-value">';
1133                    if ( ! empty( $partial['total'] ) ) {
1134                        $partials_table .= esc_html( zeroBSCRM_formatCurrency( $partial['total'] ) );
1135                    } else {
1136                        $partials_table .= esc_html( zeroBSCRM_formatCurrency( 0 ) );
1137                    }
1138                    $partials_table .= '</span></td>';
1139                    $partials_table .= '</tr>';
1140                }
1141            }
1142        }
1143
1144        if ( $balance == $invoice['total'] ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1145            $balance_hide = 'hide';
1146        } else {
1147            $balance_hide = '';
1148        }
1149
1150        $partials_table .= '<tr class="zbs_grand_total' . $balance_hide . '">';
1151        $partials_table .= '<td class="bord bord-l" style="text-align:right; font-weight:bold;  border-radius: 0;"><span class="zbs-minitotal">' . esc_html__( 'Amount due', 'zero-bs-crm' ) . '</span></td>';
1152        $partials_table .= '<td class="bord row-amount"  style="text-align:right;font-weight:bold;"><span class="zbs-subtotal-value">' . esc_html( zeroBSCRM_formatCurrency( $balance ) ) . '</span></td>';
1153        $partials_table .= '</tr>';
1154        $partials_table .= '</table>';
1155
1156    } // / end if partials enabled
1157
1158    // generate a templated paybutton (depends on template :))
1159    $potential_pay_button = zeroBSCRM_invoicing_generateInvPart_payButton( $invoice_id, $invoice['status'], $template );
1160
1161    // == Payment terms, thanks etc. will only replace when present in template, so safe to generically check
1162    $pay_thanks = '';
1163    if ( $invoice['status'] === 'Paid' ) {
1164        $pay_thanks  = '<div class="deets"><h3>' . esc_html__( 'Thank You', 'zero-bs-crm' ) . '</h3>';
1165        $pay_thanks .= '<div>' . nl2br( esc_html( zeroBSCRM_getSetting( 'paythanks' ) ) ) . '</div>';
1166        $pay_thanks .= '</div>';
1167    }
1168    $payment_info_text = zeroBSCRM_getSetting( 'paymentinfo' );
1169
1170    $pay_details  = '<div class="deets"><h2>' . esc_html__( 'Payment Details', 'zero-bs-crm' ) . '</h2>';
1171    $pay_details .= '<div class="deets-line"><span class="deets-content">' . nl2br( esc_html( $payment_info_text ) ) . '</span></div>';
1172    $pay_details .= '<div class="deets-line"><span class="deets-title">' . esc_html__( 'Payment Reference:', 'zero-bs-crm' ) . '</span> <span>' . $inv_no_str . '</span></div>';
1173    $pay_details .= '</div>';
1174
1175    // == Template -> HTML build
1176
1177    // powered by
1178    $powered_by = zeroBSCRM_mailTemplate_poweredByHTML();
1179
1180    $view_in_portal_link   = '';
1181    $view_in_portal_button = '';
1182
1183    // got portal?
1184    if ( zeroBSCRM_isExtensionInstalled( 'portal' ) ) {
1185        $view_in_portal_link   = zeroBSCRM_portal_linkObj( $invoice_id, ZBS_TYPE_INVOICE );
1186        $view_in_portal_button = '<div style="text-align:center;margin:1em;margin-top:2em">' . zeroBSCRM_mailTemplate_emailSafeButton( $view_in_portal_link, esc_html__( 'View Invoice', 'zero-bs-crm' ) ) . '</div>';
1187    }
1188
1189    // build replacements array
1190    $replacements = array(
1191
1192        // invoice specific
1193        'invoice-title'               => __( 'Invoice', 'zero-bs-crm' ),
1194        'css'                         => $css_url,
1195        'logo-class'                  => $logo_class,
1196        'logo-url'                    => esc_url( $logo_url ),
1197        'invoice-number'              => $inv_no_str,
1198        'invoice-date'                => $inv_date_str,
1199        'invoice-id-styles'           => $inv_id_styles,
1200        'invoice-ref'                 => $inv_no_str,
1201        'invoice-ref-styles'          => $inv_ref_styles,
1202        'invoice-due-date'            => $due_date_str,
1203        'invoice-custom-fields'       => $invoice_custom_fields_html,
1204        'invoice-biz-class'           => $biz_info_class,
1205        'invoice-customer-info'       => $invoice_customer_info_table_html,
1206        'invoice-html-status'         => $top_status,
1207        'invoice-table-headers'       => $table_headers,
1208        'invoice-line-items'          => $line_items,
1209        'invoice-totals-table'        => $totals_table,
1210        'invoice-partials-table'      => $partials_table,
1211        'invoice-pay-button'          => $potential_pay_button,
1212        'pre-invoice-payment-details' => '',
1213        'invoice-payment-details'     => $pay_details,
1214        'invoice-pay-thanks'          => $pay_thanks,
1215
1216        // client portal
1217        'portal-view-button'          => $view_in_portal_button,
1218        'portal-link'                 => $view_in_portal_link,
1219
1220        // language
1221        'invoice-label-inv-number'    => __( 'Invoice number', 'zero-bs-crm' ) . ':',
1222        'invoice-label-inv-date'      => __( 'Invoice date', 'zero-bs-crm' ) . ':',
1223        'invoice-label-inv-ref'       => $zbs->settings->get( 'reflabel' ),
1224        'invoice-label-status'        => __( 'Status:', 'zero-bs-crm' ),
1225        'invoice-label-from'          => __( 'From', 'zero-bs-crm' ) . ':',
1226        'invoice-label-to'            => __( 'To', 'zero-bs-crm' ) . ':',
1227        'invoice-label-due-date'      => __( 'Due date', 'zero-bs-crm' ) . ':',
1228        'invoice-pay-terms'           => __( 'Payment terms', 'zero-bs-crm' ) . ': ' . __( 'Due', 'zero-bs-crm' ) . ' ',
1229
1230        // global
1231        'biz-info'                    => $biz_info_table,
1232        'powered-by'                  => $powered_by,
1233
1234    );
1235
1236    // Switch. If partials, put the payment deets on the left next to the partials,
1237    // rather than in it's own line:
1238    if ( ! empty( $partials_table ) ) {
1239        // partials, split to two columns
1240        $replacements['pre-invoice-payment-details'] = $pay_details;
1241        $replacements['invoice-payment-details']     = '';
1242    }
1243
1244    // replace vars
1245    $html = $placeholder_templating->replace_placeholders(
1246        array( 'invoice', 'global', 'contact', 'company' ),
1247        $html,
1248        $replacements,
1249        array(
1250            ZBS_TYPE_INVOICE   => $invoice,
1251            $inv_to['objtype'] => $inv_to,
1252        )
1253    );
1254
1255    // ================= / CONTENT BUILDING =================================
1256
1257    return $html;
1258}
1259
1260// Used to generate specific part of invoice pdf: Biz table (Pay To)
1261function zeroBSCRM_invoicing_generateInvPart_bizTable( $args = array() ) {
1262
1263    #} =========== LOAD ARGS ==============
1264    $defaultArgs = array( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
1265
1266        'zbs_biz_name'      => '',
1267        'zbs_biz_yourname'  => '',
1268        'zbs_biz_extra'     => '',
1269        'zbs_biz_youremail' => '',
1270        'zbs_biz_yoururl'   => '',
1271
1272        'template'          => 'pdf', // this'll choose between the html output variants below, e.g. pdf, portal, notification
1273
1274    );
1275    foreach ( $defaultArgs as $argK => $argV ) {
1276        $$argK = $argV;
1277        if ( is_array( $args ) && isset( $args[ $argK ] ) ) {
1278            if ( is_array( $args[ $argK ] ) ) {
1279                $newData = $$argK;
1280                if ( ! is_array( $newData ) ) {
1281                    $newData = array();
1282                } foreach ( $args[ $argK ] as $subK => $subV ) {
1283                    $newData[ $subK ] = $subV;
1284                }$$argK = $newData;
1285            } else {
1286                $$argK = $args[ $argK ]; }
1287        }
1288    }
1289    #} =========== / LOAD ARGS =============
1290
1291    $biz_info_table = '';
1292
1293    switch ( $template ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1294
1295        case 'pdf':
1296        case 'notification':
1297            $biz_info_table  = '<div class="zbs-line-info zbs-line-info-title">' . esc_html( $zbs_biz_name ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1298            $biz_info_table .= '<div class="zbs-line-info">' . esc_html( $zbs_biz_yourname ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1299            $biz_info_table .= '<div class="zbs-line-info">' . nl2br( esc_html( $zbs_biz_extra ) ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1300            $biz_info_table .= '<div class="zbs-line-info">' . esc_html( $zbs_biz_youremail ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1301            $biz_info_table .= '<div class="zbs-line-info">' . esc_html( $zbs_biz_yoururl ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1302            break;
1303
1304        case 'portal':
1305            $biz_info_table  = '<div class="pay-to">';
1306            $biz_info_table .= '<div class="zbs-portal-label">' . esc_html__( 'Pay To', 'zero-bs-crm' ) . '</div>';
1307            $biz_info_table .= '<div class="zbs-portal-biz">';
1308            $biz_info_table .= '<div class="pay-to-name">' . esc_html( $zbs_biz_name ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1309            $biz_info_table .= '<div>' . esc_html( $zbs_biz_yourname ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1310            $biz_info_table .= '<div>' . nl2br( esc_html( $zbs_biz_extra ) ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1311            $biz_info_table .= '<div>' . esc_html( $zbs_biz_youremail ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1312            $biz_info_table .= '<div>' . esc_html( $zbs_biz_yoururl ) . '</div>'; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable
1313            $biz_info_table .= '</div>';
1314            $biz_info_table .= '</div>';
1315            break;
1316
1317    }
1318
1319    return $biz_info_table;
1320}
1321/** // phpcs:ignore Squiz.Commenting.FunctionComment.MissingParamTag,Generic.Commenting.DocComment.MissingShort
1322 * Used to generate specific part of invoice pdf: (Customer table)
1323 **/
1324function zeroBSCRM_invoicing_generateInvPart_custTable( $inv_to = array(), $template = 'pdf' ) {
1325
1326    $invoice_customer_info_table_html = '<div class="customer-info-wrapped">';
1327
1328    switch ( $template ) {
1329        case 'pdf':
1330        case 'notification':
1331            if ( isset( $inv_to['fname'] ) && isset( $inv_to['fname'] ) ) {
1332                $invoice_customer_info_table_html .= '<div class="zbs-line-info zbs-line-info-title">' . esc_html( $inv_to['fname'] ) . ' ' . esc_html( $inv_to['lname'] ) . '</div>';
1333            }
1334            if ( isset( $inv_to['addr1'] ) ) {
1335                $invoice_customer_info_table_html .= '<div class="zbs-line-info">' . esc_html( $inv_to['addr1'] ) . '</div>';
1336            }
1337            if ( isset( $inv_to['addr2'] ) ) {
1338                $invoice_customer_info_table_html .= '<div class="zbs-line-info">' . esc_html( $inv_to['addr2'] ) . '</div>';
1339            }
1340            if ( isset( $inv_to['city'] ) ) {
1341                $invoice_customer_info_table_html .= '<div class="zbs-line-info">' . esc_html( $inv_to['city'] ) . '</div>';
1342            }
1343            if ( isset( $inv_to['county'] ) ) {
1344                $invoice_customer_info_table_html .= '<div class="zbs-line-info">' . esc_html( $inv_to['county'] ) . '</div>';
1345            }
1346            if ( isset( $inv_to['postcode'] ) ) {
1347                $invoice_customer_info_table_html .= '<div class="zbs-line-info">' . esc_html( $inv_to['postcode'] ) . '</div>';
1348            }
1349            if ( isset( $inv_to['country'] ) ) {
1350                $invoice_customer_info_table_html .= '<div class="zbs-line-info">' . esc_html( $inv_to['country'] ) . '</div>';
1351            }
1352
1353            // Append custom fields if specified in settings
1354            $invoice_customer_info_table_html .= jpcrm_invoicing_generate_customer_custom_fields_lines( $inv_to, $template );
1355
1356            // the abilty to add in extra info to the customer info area.
1357            $extra_cust_info = '';
1358            $extra_cust_info = apply_filters( 'zbs_invoice_customer_info_line', $extra_cust_info );
1359
1360            $invoice_customer_info_table_html .= $extra_cust_info;
1361            break;
1362
1363        case 'portal':
1364            $invoice_customer_info_table_html .= '<div class="pay-to">';
1365            $invoice_customer_info_table_html .= '<div class="zbs-portal-label">' . esc_html__( 'Invoice To', 'zero-bs-crm' ) . '</div>';
1366            $invoice_customer_info_table_html .= '<div class="zbs-portal-biz">';
1367            if ( isset( $inv_to['fname'] ) && isset( $inv_to['fname'] ) ) {
1368                $invoice_customer_info_table_html .= '<div class="pay-to-name">' . esc_html( $inv_to['fname'] ) . ' ' . esc_html( $inv_to['lname'] ) . '</div>';
1369            }
1370            if ( isset( $inv_to['addr1'] ) ) {
1371                $invoice_customer_info_table_html .= '<div>' . esc_html( $inv_to['addr1'] ) . '</div>';
1372            }
1373            if ( isset( $inv_to['addr2'] ) ) {
1374                $invoice_customer_info_table_html .= '<div>' . esc_html( $inv_to['addr2'] ) . '</div>';
1375            }
1376            if ( isset( $inv_to['city'] ) ) {
1377                $invoice_customer_info_table_html .= '<div>' . esc_html( $inv_to['city'] ) . '</div>';
1378            }
1379            if ( isset( $inv_to['postcode'] ) ) {
1380                $invoice_customer_info_table_html .= '<div>' . esc_html( $inv_to['postcode'] ) . '</div>';
1381            }
1382
1383            // Append custom fields if specified in settings
1384            $invoice_customer_info_table_html .= jpcrm_invoicing_generate_customer_custom_fields_lines( $inv_to, $template );
1385
1386            // the abilty to add in extra info to the customer info area.
1387            $extra_cust_info = apply_filters( 'zbs_invoice_customer_info_line', '' );
1388
1389            $invoice_customer_info_table_html .= $extra_cust_info;
1390            $invoice_customer_info_table_html .= '</div>';
1391            $invoice_customer_info_table_html .= '</div>';
1392            break;
1393
1394    }
1395
1396        $invoice_customer_info_table_html .= '</div>';
1397
1398        // filter the whole thing if you really want to modify it
1399        $invoice_customer_info_table_html = apply_filters( 'zbs_invoice_customer_info_table', $invoice_customer_info_table_html );
1400
1401        return $invoice_customer_info_table_html;
1402}
1403
1404/*
1405* Generates html string to output customer (contact or company) custom field lines for templating
1406*
1407* @param array $customer - a contact|company object
1408* @param string $template - 'pdf', 'notification', or 'portal'
1409*
1410* @return string HTML
1411*/
1412function jpcrm_invoicing_generate_customer_custom_fields_lines( $customer, $template ) {
1413
1414    global $zbs;
1415
1416    $customer_custom_fields_html = '';
1417
1418    // retrieve custom fields to pass through
1419    // contact or company?
1420    if ( $customer['objtype'] == ZBS_TYPE_CONTACT ) {
1421
1422        $custom_fields_to_include = zeroBSCRM_getSetting( 'contactcustomfields' );
1423
1424    } elseif ( $customer['objtype'] == ZBS_TYPE_COMPANY ) {
1425
1426        $custom_fields_to_include = zeroBSCRM_getSetting( 'companycustomfields' );
1427
1428    } else {
1429
1430        // no type? Â¯\_(ツ)_/¯
1431        return '';
1432
1433    }
1434
1435    if ( ! empty( $custom_fields_to_include ) ) {
1436
1437        // split the csv
1438        $custom_fields_to_include = array_map( 'trim', explode( ',', $custom_fields_to_include ) );
1439
1440        // retrieve fields
1441        $invoice_custom_fields = $zbs->DAL->getActiveCustomFields( array( 'objtypeid' => $customer['objtype'] ) );
1442
1443        // build custom fields string.
1444        // here we immitate what we expect the HTML to be, which will be errorsome if people modify heavily.
1445        // for now it's better than no custom fields, let's see if people have issue with this approach.
1446        foreach ( $invoice_custom_fields as $field_key => $field_info ) {
1447
1448            // where user has set the field in settings
1449            if ( in_array( $field_key, $custom_fields_to_include ) ) {
1450
1451                $custom_field_str = '';
1452
1453                if ( isset( $customer[ $field_key ] ) && $customer[ $field_key ] ) {
1454
1455                    $custom_field_str = $customer[ $field_key ];
1456
1457                    // catch formatted dates
1458                    if ( isset( $customer[ $field_key . '_cfdate' ] ) ) {
1459
1460                        $custom_field_str = $customer[ $field_key . '_cfdate' ];
1461
1462                    }
1463                }
1464
1465                // skip empties
1466                if ( empty( $custom_field_str ) ) {
1467                        continue;
1468                }
1469
1470                switch ( $template ) {
1471
1472                    case 'pdf':
1473                    case 'notification':
1474                        $customer_custom_fields_html .= '<div class="zbs-line-info">' . esc_html( $field_info[1] ) . ': ' . esc_html( $custom_field_str ) . '</div>';
1475                        break;
1476
1477                    case 'portal':
1478                        $customer_custom_fields_html .= '<div><strong>' . esc_html( $field_info[1] ) . ':</strong> ' . esc_html( $custom_field_str ) . '</div>';
1479                        break;
1480
1481                }
1482            }
1483        }
1484    }
1485
1486    return $customer_custom_fields_html;
1487}
1488
1489/*
1490* Generates html string to output invoice custom field lines for templating
1491*
1492* @param array $invoice - an invoice object
1493* @param string $template - 'pdf', 'notification', or 'portal'
1494*
1495* @return string HTML
1496*/
1497function jpcrm_invoicing_generate_invoice_custom_fields_lines( $invoice, $template ) {
1498
1499    global $zbs;
1500
1501    $invoice_custom_fields_html = '';
1502
1503    // retrieve custom fields to pass through
1504    $custom_fields_to_include = zeroBSCRM_getSetting( 'invcustomfields' );
1505    if ( ! empty( $custom_fields_to_include ) ) {
1506
1507        // split the csv
1508        $custom_fields_to_include = array_map( 'trim', explode( ',', $custom_fields_to_include ) );
1509
1510        // retrieve fields
1511        $invoice_custom_fields = $zbs->DAL->getActiveCustomFields( array( 'objtypeid' => ZBS_TYPE_INVOICE ) );
1512
1513        // build custom fields string.
1514        // here we immitate what we expect the HTML to be, which will be errorsome if people modify heavily.
1515        // for now it's better than no custom fields, let's see if people have issue with this approach.
1516        foreach ( $invoice_custom_fields as $field_key => $field_info ) {
1517
1518            // where user has set the field in settings
1519            if ( in_array( $field_key, $custom_fields_to_include ) ) {
1520
1521                $custom_field_str = '';
1522
1523                if ( $invoice[ $field_key ] ) {
1524
1525                    $custom_field_str = $invoice[ $field_key ];
1526
1527                    // catch formatted dates
1528                    if ( isset( $invoice[ $field_key . '_cfdate' ] ) ) {
1529
1530                        $custom_field_str = $invoice[ $field_key . '_cfdate' ];
1531
1532                    }
1533                }
1534
1535                // skip empties
1536                if ( empty( $custom_field_str ) ) {
1537                        continue;
1538                }
1539
1540                switch ( $template ) {
1541
1542                    case 'pdf':
1543                    case 'notification':
1544                        $invoice_custom_fields_html .= '<tr class="zbs-top-right-box-line">';
1545                        $invoice_custom_fields_html .= '    <td><label for="' . esc_attr( $field_key ) . '">' . esc_html( $field_info[1] ) . ':</label></td>';
1546                        $invoice_custom_fields_html .= '    <td style="text-align:right;">' . esc_html( $custom_field_str ) . '</td>';
1547                        $invoice_custom_fields_html .= '</tr>';
1548                        break;
1549
1550                    case 'portal':
1551                        $invoice_custom_fields_html .= '<div><strong>' . esc_html( $field_info[1] ) . ':</strong> ' . esc_html( $custom_field_str ) . '</div>';
1552                        break;
1553                }
1554            }
1555        }
1556    }
1557
1558    return $invoice_custom_fields_html;
1559}
1560
1561// Used to generate specific part of invoice pdf: (Lineitem row in inv table)
1562// phpcs:ignore Squiz.Commenting.FunctionComment.Missing
1563function zeroBSCRM_invoicing_generateInvPart_lineitems( $invlines = array() ) {
1564
1565    if ( empty( $invlines ) ) {
1566        return '';
1567    }
1568
1569    $line_item_html = '';
1570    foreach ( $invlines as $invline ) {
1571
1572        $line_item_html .= '
1573            <tr class="jpcrm-invoice-lineitem">
1574            <td class="jpcrm-invoice-lineitem-description"><span class="title">' . esc_html( $invline['title'] ) . '</span><br/><span class="subtitle">' . nl2br( esc_html( $invline['desc'] ) ) . '</span></td>
1575            <td class="jpcrm-invoice-lineitem-quantity" style="text-align:right;">' . esc_html( zeroBSCRM_format_quantity( $invline['quantity'] ) ) . '</td>
1576            <td class="jpcrm-invoice-lineitem-price" style="text-align:right;">' . esc_html( zeroBSCRM_formatCurrency( $invline['price'] ) ) . '</td>
1577            <td class="jpcrm-invoice-lineitem-amount" style="text-align:right;">' . esc_html( zeroBSCRM_formatCurrency( $invline['net'] ) ) . '</td>
1578            </tr>';
1579    }
1580
1581    return $line_item_html;
1582}
1583
1584// Used to generate specific part of invoice pdf: (pay button)
1585function zeroBSCRM_invoicing_generateInvPart_payButton( $invoice_id = -1, $status = '', $template = 'pdf' ) { // phpcs:ignore Squiz.Commenting.FunctionComment.WrongStyle
1586
1587    $potential_pay_button = '';
1588
1589    switch ( $template ) {
1590
1591        case 'pdf':
1592            $potential_pay_button = '';
1593            break;
1594
1595        case 'portal':
1596            if ( $status !== 'Paid' ) {
1597
1598                // need to add somethere here which stops the below if WooCommerce meta set
1599                // so the action below will fire in WooSync, and remove the three filters below
1600                // https://codex.wordpress.org/Function_Reference/remove_filter
1601                // and then filter itself in. EDIT the remove filter does not seem to remove them below
1602                // think they already need to be applied (i.e. this below). The below works but should
1603                // think how best to do this for further extension later?
1604
1605                // WH: This'll be the ID if woo doesn't return a button (e.g. it's a woo inv so don't show pay buttons)
1606                $potential_woo_pay_button_or_inv_id = apply_filters( 'zbs_woo_pay_invoice', $invoice_id );
1607
1608                if ( $potential_woo_pay_button_or_inv_id == $invoice_id ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1609                    $potential_pay_button = apply_filters( 'invpro_pay_online', $invoice_id );
1610                } else {
1611                    $potential_pay_button = $potential_woo_pay_button_or_inv_id;
1612                }
1613
1614                if ( $potential_pay_button == $invoice_id ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1615                    $potential_pay_button = '';
1616                }
1617            }
1618
1619            break;
1620
1621        case 'notification':
1622            $potential_pay_button = '';
1623            break;
1624
1625    }
1626
1627    return $potential_pay_button;
1628}
1629// Used to generate specific part of invoice pdf: (table headers)
1630// phpcs:ignore Squiz.Commenting.FunctionComment.Missing
1631function zeroBSCRM_invoicing_generateInvPart_tableHeaders( $zbs_invoice_hours_or_quantity = 1 ) {
1632    $table_headers  = '<tr>';
1633    $table_headers .= '<th class="jpcrm-invoice-lineitem-description">' . esc_html__( 'Description', 'zero-bs-crm' ) . '</th>';
1634
1635    if ( $zbs_invoice_hours_or_quantity == 1 ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1636        $table_headers .= '<th class="jpcrm-invoice-lineitem-quantity">' . esc_html__( 'Quantity', 'zero-bs-crm' ) . '</th>';
1637        $table_headers .= '<th class="jpcrm-invoice-lineitem-price">' . esc_html__( 'Price', 'zero-bs-crm' ) . '</th>';
1638    } else {
1639        $table_headers .= '<th class="jpcrm-invoice-lineitem-quantity">' . esc_html__( 'Hours', 'zero-bs-crm' ) . '</th>';
1640        $table_headers .= '<th class="jpcrm-invoice-lineitem-price">' . esc_html__( 'Rate', 'zero-bs-crm' ) . '</th>';
1641    }
1642    $table_headers .= '<th class="jpcrm-invoice-lineitem-amount">' . esc_html__( 'Amount', 'zero-bs-crm' ) . '</th>';
1643
1644    $table_headers .= '</tr>';
1645    return $table_headers;
1646}