| @@ -1,5 +1,9 @@ | ||
| 1 | - <div class="bg-white shadow rounded-lg" style="padding: 10px;"> | |
| 1 | +<?php | |
| 2 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 3 | + exit; | |
| 4 | +} | |
| 5 | +?> <div class="bg-white shadow rounded-lg" style="padding: 10px;"> | |
| 2 | 6 | |
| 3 | 7 | <div class="px-6 py-5 border-b border-gray-200" style="margin-bottom: 10px;"> |
| 4 | 8 | <div class="flex justify-between items-center"> |
| 5 | 9 | <div class="flex items-center"> |
| @@ -6,14 +10,14 @@ | ||
| 6 | 10 | <div class="flex-shrink-0 bg-indigo-100 rounded-lg p-3"> |
| 7 | 11 | <i class="fas fa-eye text-indigo-600 text-xl"></i> |
| 8 | 12 | </div> |
| 9 | 13 | <div class="ml-4"> |
| 10 | - <h2 class="text-lg font-medium text-gray-900">Invoice Preview</h2> | |
| 11 | - <p class="mt-1 text-sm text-gray-500">Live preview of your invoice</p> | |
| 14 | + <h2 class="text-lg font-medium text-gray-900"><?php esc_html_e('Invoice Preview', 'easy-invoice'); ?></h2> | |
| 15 | + <p class="mt-1 text-sm text-gray-500"><?php esc_html_e('Live preview of your invoice', 'easy-invoice'); ?></p> | |
| 12 | 16 | </div> |
| 13 | 17 | </div> |
| 14 | 18 | <?php if($invoice_id > 0 ){ ?> |
| 15 | - <a href="<?php echo get_permalink($invoice_id); ?>" target="_blank" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> | |
| 19 | + <a href="<?php echo esc_url(get_permalink($invoice_id)); ?>" target="_blank" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> | |
| 16 | 20 | <i class="fas fa-external-link-alt mr-2"></i> |
| 17 | 21 | Full Preview |
| 18 | 22 | </a> |
| 19 | 23 | <?php } ?> |
| @@ -48,9 +52,9 @@ | ||
| 48 | 52 | $canvasDesignCSS = \EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController::generateCanvasCSS($templateData); |
| 49 | 53 | |
| 50 | 54 | echo "#canvas { position: relative; width: " . floatval($canvasWidth) . "px; height: " . floatval($canvasHeight) . "px; margin: 0; overflow: visible; "; |
| 51 | 55 | if ($canvasDesignCSS) { |
| 52 | - echo str_replace('!important;', ';', $canvasDesignCSS); | |
| 56 | + echo wp_strip_all_tags(str_replace('!important;', ';', $canvasDesignCSS)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CSS inside a <style> element; tags stripped, entities are not decoded there. | |
| 53 | 57 | } |
| 54 | 58 | echo " }\n"; |
| 55 | 59 | |
| 56 | 60 | // Element styles |
| @@ -57,9 +61,9 @@ | ||
| 57 | 61 | foreach ($templateData['elements'] as $element) { |
| 58 | 62 | $elementId = $element['id'] ?? ''; |
| 59 | 63 | if (!$elementId) continue; |
| 60 | 64 | |
| 61 | - echo "#{$elementId} { position: absolute; "; | |
| 65 | + echo '#' . esc_attr($elementId) . ' { position: absolute; '; | |
| 62 | 66 | if (isset($element['position']['left'])) echo "left: " . floatval($element['position']['left']) . "px; "; |
| 63 | 67 | if (isset($element['position']['top'])) echo "top: " . floatval($element['position']['top']) . "px; "; |
| 64 | 68 | if (isset($element['dimensions']['width'])) echo "width: " . floatval($element['dimensions']['width']) . "px; "; |
| 65 | 69 | if (isset($element['dimensions']['height'])) echo "min-height: " . floatval($element['dimensions']['height']) . "px; "; |
| @@ -78,12 +82,18 @@ | ||
| 78 | 82 | } |
| 79 | 83 | } |
| 80 | 84 | |
| 81 | 85 | // Dynamically load the selected template |
| 82 | - $template_file = EASY_INVOICE_PLUGIN_DIR . 'templates/invoice-templates/' . $current_template . '.php'; | |
| 83 | - | |
| 84 | - $template_file = file_exists($template_file) ? $template_file : EASY_INVOICE_PLUGIN_DIR . 'templates/invoice-templates/default.php'; | |
| 85 | - if (file_exists($template_file)) { | |
| 86 | + $template_file = easy_invoice_design_template( 'invoice', (string) $current_template ); | |
| 87 | + if ( ! $invoice instanceof \EasyInvoice\Models\Invoice ) { | |
| 88 | + // Nothing to render yet. The designs read the model | |
| 89 | + // directly, so including one here would be fatal. | |
| 90 | + ?> | |
| 91 | + <div class="ei-preview-empty" style="text-align:center;padding:48px 24px;color:#64748b;"> | |
| 92 | + <p style="margin:0;font-size:14px;"><?php esc_html_e( 'Your invoice will appear here as you fill in the form.', 'easy-invoice' ); ?></p> | |
| 93 | + </div> | |
| 94 | + <?php | |
| 95 | + } elseif (file_exists($template_file)) { | |
| 86 | 96 | include_once $template_file; |
| 87 | 97 | } else { |
| 88 | 98 | // Show template not found message |
| 89 | 99 | ?> |
| @@ -90,11 +100,11 @@ | ||
| 90 | 100 | <div class="template-not-found"> |
| 91 | 101 | <div class="template-not-found-icon"> |
| 92 | 102 | <i class="fas fa-exclamation-circle"></i> |
| 93 | 103 | </div> |
| 94 | - <h3>Template Not Found</h3> | |
| 104 | + <h3><?php esc_html_e('Template Not Found', 'easy-invoice'); ?></h3> | |
| 95 | 105 | <p>The selected template "<?php echo esc_html($current_template); ?>" does not exist.</p> |
| 96 | - <p>Please select a different template or contact support if you believe this is an error.</p> | |
| 106 | + <p><?php esc_html_e('Please select a different template or contact support if you believe this is an error.', 'easy-invoice'); ?></p> | |
| 97 | 107 | </div> |
| 98 | 108 | <style> |
| 99 | 109 | .template-not-found { |
| 100 | 110 | text-align: center; |
| @@ -153,10 +163,10 @@ | ||
| 153 | 163 | <script> |
| 154 | 164 | // Localize easyInvoice object for this page |
| 155 | 165 | window.easyInvoice = window.easyInvoice || {}; |
| 156 | 166 | window.easyInvoice.isPro = <?php echo easy_invoice_has_pro() ? 'true' : 'false'; ?>; |
| 157 | -window.easyInvoice.ajaxUrl = '<?php echo admin_url('admin-ajax.php'); ?>'; | |
| 158 | -window.easyInvoice.nonce = '<?php echo wp_create_nonce('easy_invoice_nonce'); ?>'; | |
| 167 | +window.easyInvoice.ajaxUrl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>'; | |
| 168 | +window.easyInvoice.nonce = '<?php echo esc_attr(wp_create_nonce('easy_invoice_nonce')); ?>'; | |
| 159 | 169 | |
| 160 | 170 | jQuery(document).ready(function($) { |
| 161 | 171 | // Function to update the live preview |
| 162 | 172 | function updateLivePreview(templateId) { |
| @@ -169,8 +179,9 @@ | ||
| 169 | 179 | type: 'POST', |
| 170 | 180 | data: { |
| 171 | 181 | action: 'easy_invoice_load_template', |
| 172 | 182 | template: templateId, |
| 183 | + form_data: jQuery('#invoice-form').length ? jQuery('#invoice-form').serialize() : '', | |
| 173 | 184 | invoice_id: '<?php echo esc_js($invoice_id); ?>', |
| 174 | 185 | nonce: window.easyInvoice.nonce |
| 175 | 186 | }, |
| 176 | 187 | success: function(response) { |
| @@ -183,11 +194,11 @@ | ||
| 183 | 194 | <div class="template-not-found"> |
| 184 | 195 | <div class="template-not-found-icon"> |
| 185 | 196 | <i class="fas fa-exclamation-circle"></i> |
| 186 | 197 | </div> |
| 187 | - <h3>Template Not Found</h3> | |
| 198 | + <h3><?php esc_html_e('Template Not Found', 'easy-invoice'); ?></h3> | |
| 188 | 199 | <p>The selected template "${templateId}" does not exist.</p> |
| 189 | - <p>Please select a different template or contact support if you believe this is an error.</p> | |
| 200 | + <p><?php esc_html_e('Please select a different template or contact support if you believe this is an error.', 'easy-invoice'); ?></p> | |
| 190 | 201 | </div> |
| 191 | 202 | `); |
| 192 | 203 | } |
| 193 | 204 | }, |
| @@ -197,10 +208,10 @@ | ||
| 197 | 208 | <div class="template-not-found"> |
| 198 | 209 | <div class="template-not-found-icon"> |
| 199 | 210 | <i class="fas fa-exclamation-circle"></i> |
| 200 | 211 | </div> |
| 201 | - <h3>Error Loading Template</h3> | |
| 202 | - <p>There was an error loading the template. Please try again.</p> | |
| 212 | + <h3><?php esc_html_e('Error Loading Template', 'easy-invoice'); ?></h3> | |
| 213 | + <p><?php esc_html_e('There was an error loading the template. Please try again.', 'easy-invoice'); ?></p> | |
| 203 | 214 | </div> |
| 204 | 215 | `); |
| 205 | 216 | }, |
| 206 | 217 | complete: function() { |
| @@ -219,11 +230,11 @@ | ||
| 219 | 230 | const isFreeVersion = !window.easyInvoice || !window.easyInvoice.isPro; |
| 220 | 231 | if (isPremium && isFreeVersion) { |
| 221 | 232 | // Show premium upgrade modal instead of selecting the template |
| 222 | 233 | if (typeof EasyInvoiceConfirmation !== 'undefined' && EasyInvoiceConfirmation.showFeatureUpgrade) { |
| 223 | - EasyInvoiceConfirmation.showFeatureUpgrade('<?php _e('Premium Templates', 'easy-invoice'); ?>', '<?php _e('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice'); ?>'); | |
| 234 | + EasyInvoiceConfirmation.showFeatureUpgrade('<?php echo esc_js(__('Premium Templates', 'easy-invoice')); ?>', '<?php echo esc_js(__('Access all premium templates including Modern, Professional, Classic, and more to give your invoices a unique and professional look.', 'easy-invoice')); ?>'); | |
| 224 | 235 | } else { |
| 225 | - alert('<?php _e('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice'); ?>'); | |
| 236 | + alert('<?php echo esc_js(__('This is a premium feature. Please upgrade to Pro to access all templates.', 'easy-invoice')); ?>'); | |
| 226 | 237 | } |
| 227 | 238 | return; |
| 228 | 239 | } |
| 229 | 240 | |
| @@ -236,6 +247,45 @@ | ||
| 236 | 247 | updateLivePreview(templateId); |
| 237 | 248 | }); |
| 238 | 249 | |
| 239 | 250 | |
| 251 | + // Live means live: re-render as the builder changes, not only when the | |
| 252 | + // template changes. Debounced so typing a description does not fire a | |
| 253 | + // request per keystroke. | |
| 254 | + (function () { | |
| 255 | + var $form = $('#invoice-form'); if (!$form.length) { return; } | |
| 256 | + var timer = null; | |
| 257 | + var current = function () { return $form.find('input[name="invoice_template"]').val() || $('.template-card.selected, .template-card.active').first().data('template-id') || 'standard'; }; | |
| 258 | + var schedule = function () { clearTimeout(timer); timer = setTimeout(function () { updateLivePreview(current()); }, 700); }; | |
| 259 | + $form.on('input change', 'input, textarea, select', schedule); | |
| 260 | + $(document).on('click', '.add-item-button, .remove-item, .client-option, #remove-selected-client, .template-card', schedule); | |
| 261 | + $(document).on('easy_invoice_items_changed easy_invoice_client_changed', schedule); | |
| 262 | + })(); | |
| 263 | + | |
| 240 | 264 | }); |
| 265 | +</script> | |
| 266 | + | |
| 267 | +<script> | |
| 268 | +// Scale the A4 design to the preview pane instead of letting it reflow: the | |
| 269 | +// preview then shows the sheet the client will see, only smaller. | |
| 270 | +(function () { | |
| 271 | + var box = document.getElementById('invoice-container'); | |
| 272 | + if (!box || !window.ResizeObserver) { return; } | |
| 273 | + var SHEET = 794; | |
| 274 | + function fit() { | |
| 275 | + var sheet = box.querySelector('.template, [class*="template-"]'); | |
| 276 | + if (!sheet) { box.style.height = ''; return; } | |
| 277 | + var avail = box.clientWidth; | |
| 278 | + if (avail <= 0) { return; } | |
| 279 | + var k = Math.min(1, avail / SHEET); | |
| 280 | + sheet.style.width = SHEET + 'px'; | |
| 281 | + sheet.style.maxWidth = 'none'; | |
| 282 | + sheet.style.transformOrigin = 'top left'; | |
| 283 | + sheet.style.transform = k < 1 ? 'scale(' + k + ')' : ''; | |
| 284 | + box.style.overflow = 'hidden'; | |
| 285 | + box.style.height = k < 1 ? Math.ceil(sheet.offsetHeight * k) + 'px' : ''; | |
| 286 | + } | |
| 287 | + new ResizeObserver(fit).observe(box); | |
| 288 | + new MutationObserver(fit).observe(box, { childList: true, subtree: true }); | |
| 289 | + fit(); | |
| 290 | +})(); | |
| 241 | 291 | </script> |