| 1 |
<?php |
| 2 |
/** |
| 3 |
* Theme-overridable template lookup. |
| 4 |
* |
| 5 |
* @package Easy_Invoice |
| 6 |
* @subpackage Helpers |
| 7 |
*/ |
| 8 |
|
| 9 |
if ( ! defined( 'ABSPATH' ) ) { |
| 10 |
exit; |
| 11 |
} |
| 12 |
|
| 13 |
/** |
| 14 |
* Resolve a template name to a file, letting the theme override it. |
| 15 |
* |
| 16 |
* Lookup order: |
| 17 |
* 1. {child theme}/easy-invoice/{template} |
| 18 |
* 2. {parent theme}/easy-invoice/{template} |
| 19 |
* 3. {plugin}/templates/{template} |
| 20 |
* |
| 21 |
* This is the same convention WooCommerce established, so anyone who has |
| 22 |
* customised a shop knows what to do: copy the file into the theme, keep the |
| 23 |
* path, edit. Until now every template path in the plugin was hard-coded, so |
| 24 |
* the only way to change what a client saw was to edit the plugin and lose |
| 25 |
* it on the next update. |
| 26 |
* |
| 27 |
* The plugin's own screens (the admin pages under templates/admin) go |
| 28 |
* through this too, but a theme has no business overriding those; only the |
| 29 |
* public documents, the design templates and the PDFs are documented as |
| 30 |
* overridable. |
| 31 |
* |
| 32 |
* @param string $template Path relative to the plugin's templates/ directory, |
| 33 |
* e.g. 'document/single.php'. |
| 34 |
* @param array $args Context, passed to the filter for callers that want |
| 35 |
* to switch templates on it. |
| 36 |
* @return string Absolute path, or '' when nothing exists anywhere. |
| 37 |
*/ |
| 38 |
function easy_invoice_locate_template( string $template, array $args = [] ): string { |
| 39 |
$template = ltrim( str_replace( '\\', '/', $template ), '/' ); |
| 40 |
|
| 41 |
// Never let a template name climb out of the directories we search. |
| 42 |
if ( '' === $template || false !== strpos( $template, '..' ) ) { |
| 43 |
return ''; |
| 44 |
} |
| 45 |
|
| 46 |
/** |
| 47 |
* Filter the folder inside a theme that holds overrides. |
| 48 |
* |
| 49 |
* @param string $dir Folder name, default 'easy-invoice'. |
| 50 |
*/ |
| 51 |
$theme_dir = trim( (string) apply_filters( 'easy_invoice_template_directory', 'easy-invoice' ), '/' ); |
| 52 |
|
| 53 |
$candidates = []; |
| 54 |
if ( '' !== $theme_dir ) { |
| 55 |
$candidates[] = trailingslashit( get_stylesheet_directory() ) . $theme_dir . '/' . $template; |
| 56 |
if ( get_template_directory() !== get_stylesheet_directory() ) { |
| 57 |
$candidates[] = trailingslashit( get_template_directory() ) . $theme_dir . '/' . $template; |
| 58 |
} |
| 59 |
} |
| 60 |
$candidates[] = EASY_INVOICE_PLUGIN_DIR . 'templates/' . $template; |
| 61 |
|
| 62 |
$located = ''; |
| 63 |
foreach ( $candidates as $candidate ) { |
| 64 |
if ( file_exists( $candidate ) ) { |
| 65 |
$located = $candidate; |
| 66 |
break; |
| 67 |
} |
| 68 |
} |
| 69 |
|
| 70 |
/** |
| 71 |
* Filter the resolved template path. |
| 72 |
* |
| 73 |
* Lets an addon or a site ship a template somewhere other than the |
| 74 |
* theme. Return '' to declare it missing. |
| 75 |
* |
| 76 |
* @param string $located Absolute path found, or ''. |
| 77 |
* @param string $template The relative name asked for. |
| 78 |
* @param array $args Context passed by the caller. |
| 79 |
*/ |
| 80 |
$located = (string) apply_filters( 'easy_invoice_locate_template', $located, $template, $args ); |
| 81 |
|
| 82 |
return ( '' !== $located && file_exists( $located ) ) ? $located : ''; |
| 83 |
} |
| 84 |
|
| 85 |
/** |
| 86 |
* Include a template, exposing $args as variables. |
| 87 |
* |
| 88 |
* @param string $template Relative template name. |
| 89 |
* @param array $args Variables the template may read. |
| 90 |
* @return void |
| 91 |
*/ |
| 92 |
function easy_invoice_get_template( string $template, array $args = [] ): void { |
| 93 |
$located = easy_invoice_locate_template( $template, $args ); |
| 94 |
if ( '' === $located ) { |
| 95 |
return; |
| 96 |
} |
| 97 |
|
| 98 |
if ( ! empty( $args ) ) { |
| 99 |
extract( $args, EXTR_SKIP ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract -- template context, keys are ours. |
| 100 |
} |
| 101 |
|
| 102 |
include $located; |
| 103 |
} |
| 104 |
|
| 105 |
/** |
| 106 |
* Resolve a design template (the look of an invoice or quote) by name. |
| 107 |
* |
| 108 |
* Falls back through the plugin's 'default' design when the requested one |
| 109 |
* does not exist anywhere, so an invoice that references a design from an |
| 110 |
* addon that has since been switched off still renders. |
| 111 |
* |
| 112 |
* @param string $type 'invoice' or 'quote'. |
| 113 |
* @param string $name Design name, e.g. 'standard'. |
| 114 |
* @return string Absolute path to the design file. |
| 115 |
*/ |
| 116 |
function easy_invoice_design_template( string $type, string $name ): string { |
| 117 |
$folder = ( 'quote' === $type ) ? 'quote-templates' : 'invoice-templates'; |
| 118 |
$name = sanitize_file_name( '' !== $name ? $name : 'standard' ); |
| 119 |
|
| 120 |
$path = easy_invoice_locate_template( $folder . '/' . $name . '.php', [ 'type' => $type, 'design' => $name ] ); |
| 121 |
if ( '' === $path ) { |
| 122 |
$path = easy_invoice_locate_template( $folder . '/default.php', [ 'type' => $type, 'design' => 'default' ] ); |
| 123 |
} |
| 124 |
|
| 125 |
return $path; |
| 126 |
} |
| 127 |
|