enqueueStyles(); // Enqueue scripts $this->enqueueScripts($hook); // Localize script data $this->localizeScripts($hook); } /** * Enqueue stylesheets */ private function enqueueStyles() { // Tailwind: the purged ~47 KB build is the default; the full 2.8 MB // dump stays on disk as the fallback. // // The purged file is the shipped v2 bundle with unused rules removed -- // NOT a v3 rebuild, which would rename utilities and change the palette. // Every rule it keeps is byte-identical to the full file, so anything // still on a page renders exactly as before. It was verified by // pixel-diffing 41 admin screens (every core page, every addon page, // three breakpoints, the drawer, a modal and toasts) against the full // file: zero differing pixels. See tools/tailwind-build/README.md. // // A site that adds markup using utilities the plugin never uses -- // a custom template, a child theme -- can go back to the full file: // update_option('easy_invoice_tailwind_purged', '0'); // or // add_filter('easy_invoice_use_purged_tailwind', '__return_false'); // // The option was previously opt-IN ('1' enabled the purged file). It is // now opt-OUT: only an explicit '0' disables it. A site that had set // '1' keeps working; a site that never set it gets the win. // // The file_exists check below means a deployment that lost the purged // file falls back to the full one rather than 404-ing the stylesheet. $purged_option = get_option('easy_invoice_tailwind_purged', '1'); $use_purged = apply_filters( 'easy_invoice_use_purged_tailwind', '0' !== (string) $purged_option && false !== $purged_option ); $purged_rel = 'assets/lib/tailwind/tailwind.purged.min.css'; $shipped_rel = 'assets/lib/tailwind/tailwind.min.css'; $tailwind_rel = ($use_purged && file_exists(EASY_INVOICE_PLUGIN_DIR . $purged_rel)) ? $purged_rel : $shipped_rel; wp_enqueue_style( 'easy-invoice-tailwind', EASY_INVOICE_PLUGIN_URL . $tailwind_rel, array(), EASY_INVOICE_VERSION ); // Colour names the views use that Tailwind v2 never had (amber, emerald, // teal …) — without these every amber notice and emerald badge was unstyled. wp_enqueue_style( 'easy-invoice-palette-ext', EASY_INVOICE_PLUGIN_URL . 'assets/css/palette-ext.css', array( 'easy-invoice-tailwind' ), EASY_INVOICE_VERSION ); $page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : ''; // Main plugin layout + header strip alignment (always on Easy Invoice admin screens). wp_enqueue_style( 'easy-invoice-main', EASY_INVOICE_PLUGIN_URL . 'assets/css/easy-invoice.css', array(), EASY_INVOICE_VERSION ); if($page === 'easy-invoice-builder') { // Main admin styles wp_enqueue_style( 'easy-invoice-preview', EASY_INVOICE_PLUGIN_URL . 'assets/css/invoice-preview.css', array(), EASY_INVOICE_VERSION ); } // Font Awesome (~87 KB) is used by the heavyweight templates // (clients, settings, reports, invoices, quotes, payments) but // NOT by the lightweight admin pages — Dashboard, Addons grid, // License, Free-vs-Pro, Join Community, and every addon-settings // sub-page (those use dashicons). Skip enqueueing FA on the // deny-listed pages to save the bandwidth and the parse cost. // Filterable so site-specific custom templates can opt back in. $fa_deny = (array) apply_filters('easy_invoice_font_awesome_skip_pages', [ 'easy-invoice-dashboard', 'easy-invoice-addons', 'easy-invoice-license', 'easy-invoice-free-vs-pro', 'easy-invoice-join-community', ]); $is_addon_subpage = ($page !== '' && strpos($page, 'easy-invoice-addon-') === 0); if (!in_array($page, $fa_deny, true) && !$is_addon_subpage) { wp_enqueue_style( 'font-awesome', EASY_INVOICE_PLUGIN_URL . 'assets/lib/font-awesome/css/all.min.css', array(), EASY_INVOICE_VERSION ); } // RTL support. // // Strategy: 'append' (not 'replace') because Tailwind utility // classes generated into easy-invoice.css remain valid LTR — only // the small set of custom directional rules (sidebar borders, // page-header negative margins, drawer offsets) need mirroring. // WordPress loads assets/css/easy-invoice-rtl.css AFTER // easy-invoice.css when is_rtl() is true; that file holds only the // directional overrides. // // 'easy-invoice-main' is the handle of the file we want to extend // (line 87 above). The previous handle 'easy-invoice-admin' did // not exist and so the call was a no-op. wp_style_add_data('easy-invoice-main', 'rtl', 'append'); } /** * Enqueue scripts * * @param string $hook The current admin page */ private function enqueueScripts($hook) { // Only load on Easy Invoice pages if (empty($hook) || strpos($hook, 'easy-invoice') === false) { return; } // Common admin scripts wp_enqueue_script('jquery'); wp_enqueue_script('jquery-ui-core'); wp_enqueue_script('jquery-ui-datepicker'); wp_enqueue_script('wp-util'); // Add WordPress core dependencies that provide the 'wp' object wp_enqueue_script('wp-api-fetch'); wp_enqueue_script('wp-i18n'); wp_enqueue_script('wp-a11y'); wp_enqueue_script('wp-hooks'); // Settings page script if (isset($_GET['page']) && $_GET['page'] === 'easy-invoice-settings') { // settings.js calls .sortable() and .disableSelection() on the // payment-gateway list, so jQuery UI Sortable must be present and // printed first. SettingsController::enqueueAdminScripts() also // registers this same handle; WordPress keeps whichever call runs // first and silently drops the other's dependency array, and this // one runs first (AdminAssets is wired up during EasyInvoice::init, // before SettingsController::init). Declaring the jQuery UI handles // here lets wp_scripts resolve the order rather than leaving it to // the sequence the two enqueue callbacks happen to fire in. // // Only core handles are listed. select2 / easy-invoice-toast are // also used by settings.js but are registered by SettingsController // later in the request; naming them here would mean settings.js is // dropped entirely if that callback ever stops running, so they are // deliberately left out. wp_enqueue_script( 'easy-invoice-settings', EASY_INVOICE_PLUGIN_URL . 'assets/js/settings.js', array('jquery', 'jquery-ui-core', 'jquery-ui-sortable', 'wp-util', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true ); // Localize the script with necessary data wp_localize_script('easy-invoice-settings', 'easyInvoiceSettings', array( 'nonce' => wp_create_nonce('easy_invoice_settings'), 'ajaxurl' => admin_url('admin-ajax.php') )); return; // Don't load other scripts on settings page } // Load dependencies wp_enqueue_script('jquery-ui-sortable'); // Main admin script wp_enqueue_script('jquery'); // Register and enqueue our scripts wp_register_script('easy-invoice-scripts', EASY_INVOICE_PLUGIN_URL . 'assets/js/easy-invoice.js', array('jquery', 'wp-api-fetch', 'wp-i18n', 'wp-a11y'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-scripts'); // Conditionally load client manager only on invoice pages (not quote pages or invoice builder) if (!(isset($_GET['page']) && ($_GET['page'] === 'easy-invoice-quote-builder' || $_GET['page'] === 'easy-invoice-builder'))) { wp_register_script('easy-invoice-client-manager', EASY_INVOICE_PLUGIN_URL . 'assets/js/client-manager.js', array('jquery', 'easy-invoice-scripts', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-client-manager'); } // Load clients.js on the clients page if (isset($_GET['page']) && $_GET['page'] === PagesSlugs::CLIENTS) { wp_register_script('easy-invoice-clients', EASY_INVOICE_PLUGIN_URL . 'assets/js/clients.js', array('jquery', 'easy-invoice-scripts', 'easy-invoice-confirmation-modal', 'easy-invoice-toast', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-clients'); } wp_register_script('easy-invoice-payment-manager', EASY_INVOICE_PLUGIN_URL . 'assets/js/payment-manager.js', array('jquery', 'easy-invoice-scripts', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-payment-manager'); // Tooltip manager - reusable across the plugin wp_register_script('easy-invoice-tooltip', EASY_INVOICE_PLUGIN_URL . 'assets/js/tooltip-manager.js', array('jquery', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-tooltip'); // Confirmation modal - reusable across the plugin wp_register_script('easy-invoice-confirmation-modal', EASY_INVOICE_PLUGIN_URL . 'assets/js/confirmation-modal.js', array('jquery', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-confirmation-modal'); // Toast notification system - global across the plugin wp_register_script('easy-invoice-toast', EASY_INVOICE_PLUGIN_URL . 'assets/js/easy-invoice-toast.js', array('jquery', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-toast'); // Bulk "Send Email" teaser — only on the invoice / quote listings. // Always injects the option (even without Pro) so users discover the // feature; when Pro is inactive, clicking Apply opens the upgrade // dialog instead of doing the work. $current_page = isset($_GET['page']) ? sanitize_key($_GET['page']) : ''; if (in_array($current_page, ['easy-invoice-all', 'easy-quote-all'], true)) { wp_register_script( 'easy-invoice-bulk-send-email-teaser', EASY_INVOICE_PLUGIN_URL . 'assets/js/bulk-send-email-teaser.js', array('jquery', 'easy-invoice-confirmation-modal', 'easy-invoice-toast'), EASY_INVOICE_VERSION, true ); wp_localize_script('easy-invoice-bulk-send-email-teaser', 'easyInvoiceBulkSendTeaser', array( 'hasPro' => function_exists('easy_invoice_has_pro') && easy_invoice_has_pro(), 'upgradeUrl' => 'https://matrixaddons.com/plugins/easy-invoice/#pricing', 'i18n' => array( // Send Email 'send_email_label' => __('Send Email (Pro)', 'easy-invoice'), 'send_email_feature_name' => __('Bulk Send Email', 'easy-invoice'), 'send_email_feature_description'=> __('Select multiple invoices or quotes and send the configured "Available" email to every selected document\'s client in a single click. Includes a per-row success / failure report so you can spot deliverability problems immediately.', 'easy-invoice'), // Export 'export_feature_name' => __('Bulk Export Selected', 'easy-invoice'), 'export_feature_description' => __('Export your selected invoices or quotes to a clean, accounting-ready CSV — perfect for QuickBooks, Xero, audit trails, or migrating to a new system.', 'easy-invoice'), // Generic 'upgrade_required' => __('This action requires Easy Invoice Pro.', 'easy-invoice'), ), )); wp_enqueue_script('easy-invoice-bulk-send-email-teaser'); } // jsPDF + html2canvas for PDF generation - available on all Easy Invoice pages. // Both are bundled in assets/js/vendors/ rather than loaded from a CDN: // WordPress.org requires every asset to ship inside the plugin, and a remote // CDN is a hard runtime dependency for a core feature. wp_register_script('html2canvas', EASY_INVOICE_PLUGIN_URL . 'assets/js/vendors/html2canvas.min.js', array(), '1.4.1', true); wp_register_script('jspdf', EASY_INVOICE_PLUGIN_URL . 'assets/js/vendors/jspdf.umd.min.js', array(), '2.5.1', true); wp_enqueue_script('html2canvas'); wp_enqueue_script('jspdf'); // Conditionally load invoice-specific scripts only on invoice pages if (isset($_GET['page']) && ($_GET['page'] === 'easy-invoice-new' || $_GET['page'] === 'easy-invoice-builder')) { // Invoice builder scripts wp_register_script('easy-invoice-builder', EASY_INVOICE_PLUGIN_URL . 'assets/js/invoice-builder.js', array('jquery', 'easy-invoice-scripts', 'easy-invoice-payment-manager', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-builder'); // Use invoice-save.js for comprehensive save functionality wp_register_script('easy-invoice-save', EASY_INVOICE_PLUGIN_URL . 'assets/js/invoice-save.js', array('jquery', 'easy-invoice-scripts', 'easy-invoice-payment-manager', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true); wp_enqueue_script('easy-invoice-save'); } // Quote builder (full-screen editor) if (isset($_GET['page']) && $_GET['page'] === 'easy-invoice-quote-builder') { wp_register_script( 'easy-quote-save', EASY_INVOICE_PLUGIN_URL . 'assets/js/quote-save.js', array('jquery', 'easy-invoice-scripts', 'easy-invoice-payment-manager', 'wp-api-fetch', 'wp-i18n'), EASY_INVOICE_VERSION, true ); wp_enqueue_script('easy-quote-save'); } if (isset($_GET['page']) && in_array($_GET['page'], array('easy-invoice-builder', 'easy-invoice-quote-builder'), true)) { wp_enqueue_script( 'easy-invoice-builder-mobile-tabs', EASY_INVOICE_PLUGIN_URL . 'assets/js/builder-mobile-tabs.js', array('jquery'), EASY_INVOICE_VERSION, true ); wp_enqueue_script( 'easy-invoice-builder-ux', EASY_INVOICE_PLUGIN_URL . 'assets/js/builder-ux.js', array('jquery', 'easy-invoice-scripts'), EASY_INVOICE_VERSION, true ); } } /** * Localize script data * * @param string $hook The current admin page */ private function localizeScripts($hook) { global $pagenow, $post; // Get currency settings $settings_controller = new \EasyInvoice\Controllers\SettingsController(); $settings = $settings_controller->getSettings(); $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD'; $currency_position = $settings['easy_invoice_currency_position'] ?? 'left'; $currency_symbol = easy_invoice_get_currency_symbol(); $current_admin_page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : ''; // Default data $script_data = array( 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('easy_invoice_nonce'), 'i18n' => array( 'confirm_delete' => __('Are you sure you want to delete this invoice?', 'easy-invoice'), 'invoice_deleted' => __('Invoice deleted successfully', 'easy-invoice'), 'client_deleted' => __('Client deleted successfully', 'easy-invoice'), 'confirm_delete_client' => __('Are you sure you want to delete this client?', 'easy-invoice'), 'edit_invoice' => __('Edit Invoice', 'easy-invoice'), 'create_invoice' => __('Create Invoice', 'easy-invoice'), 'save' => __('Save', 'easy-invoice'), 'cancel' => __('Cancel', 'easy-invoice'), 'add_item' => __('Add Item', 'easy-invoice'), 'delete_item' => __('Delete Item', 'easy-invoice'), 'error' => __('An error occurred', 'easy-invoice'), ), ); if (in_array($current_admin_page, array('easy-invoice-builder', 'easy-invoice-quote-builder'), true)) { $script_data['i18n'] = array_merge( $script_data['i18n'], array( 'saving' => __('Saving…', 'easy-invoice'), 'sending' => __('Sending…', 'easy-invoice'), 'save_invoice' => __('Save Invoice', 'easy-invoice'), 'update_invoice' => __('Update Invoice', 'easy-invoice'), 'save_quote' => __('Save Quote', 'easy-invoice'), 'update_quote' => __('Update Quote', 'easy-invoice'), 'send_invoice' => __('Send Invoice', 'easy-invoice'), 'send_quote' => __('Send Quote', 'easy-invoice'), 'save_first_invoice' => __('Please save the invoice before sending.', 'easy-invoice'), 'save_first_quote' => __('Please save the quote before sending.', 'easy-invoice'), 'email_sent_invoice' => __('Invoice email sent successfully.', 'easy-invoice'), 'email_sent_quote' => __('Quote email sent successfully.', 'easy-invoice'), 'email_error' => __('Error sending email.', 'easy-invoice'), 'network_error' => __('Error connecting to server.', 'easy-invoice'), 'tab_editor' => __('Editor', 'easy-invoice'), 'tab_preview' => __('Preview', 'easy-invoice'), 'confirm_send_invoice_title' => __('Send invoice by email?', 'easy-invoice'), 'confirm_send_invoice_message' => __('This will email the invoice to the client using your configured template.', 'easy-invoice'), 'confirm_send_quote_title' => __('Send quote by email?', 'easy-invoice'), 'confirm_send_quote_message' => __('This will email the quote to the client using your configured template.', 'easy-invoice'), 'confirm_send_confirm' => __('Send', 'easy-invoice'), 'confirm_cancel' => __('Cancel', 'easy-invoice'), 'builder_unsaved_warning' => __('You have unsaved changes. If you leave this page, your changes may be lost.', 'easy-invoice'), 'builder_title_hint' => __('The title above matches the document title field in the editor.', 'easy-invoice'), 'builder_shell_hint' => __('On smaller screens, use the tabs to switch between the editor and the live preview.', 'easy-invoice'), 'builder_shell_hint_dismiss' => __('Got it', 'easy-invoice'), 'send_invoice_confirm_browser' => __('Send this invoice by email?', 'easy-invoice'), 'send_quote_confirm_browser' => __('Send this quote by email?', 'easy-invoice'), ) ); } $script_data = array_merge( $script_data, array( 'urls' => array( 'preview' => admin_url('admin.php?action=easy_invoice_preview&invoice_id='), 'edit' => admin_url('admin.php?page=easy-invoice-new&id='), ), 'settings' => array( 'currency_symbol' => $currency_symbol, 'currency_position' => $currency_position, 'currency_code' => $currency_code, 'decimal_separator' => $settings['easy_invoice_decimal_separator'] ?? '.', 'thousand_separator' => $settings['easy_invoice_thousands_separator'] ?? ',', 'decimal_places' => $settings['easy_invoice_decimal_precision'] ?? 2, 'date_format' => get_option('date_format', 'F j, Y'), ), 'showAdjustField' => \EasyInvoice\Controllers\SettingsController::shouldShowInvoiceAdjustField(), ) ); // Localize common scripts wp_localize_script('easy-invoice-scripts', 'easyInvoice', $script_data); // Conditionally localize client manager only when it's loaded if (!(isset($_GET['page']) && ($_GET['page'] === 'easy-invoice-quote-builder' || $_GET['page'] === 'easy-invoice-builder'))) { wp_localize_script('easy-invoice-client-manager', 'easyInvoice', $script_data); } wp_localize_script('easy-invoice-payment-manager', 'easyInvoice', $script_data); wp_localize_script('easy-invoice-tooltip', 'easyInvoice', $script_data); // Conditionally localize invoice-specific scripts if (isset($_GET['page']) && ($_GET['page'] === 'easy-invoice-new' || $_GET['page'] === 'easy-invoice-builder')) { // Add invoice-specific field configuration $form_manager = new \EasyInvoice\Forms\Invoice\InvoiceFormManager(); $script_data['fieldConfig'] = $form_manager->getFieldConfigForJavaScript(); // Add additional data for the invoice edit page $invoice_id = isset($_GET['id']) ? intval($_GET['id']) : 0; $invoice_items_json = ''; if ($invoice_id > 0) { // Existing invoice - get its data $repository = \EasyInvoice\Providers\InvoiceServiceProvider::getInvoiceRepository(); $invoice = $repository->find($invoice_id); if ($invoice) { $script_data['invoice_id'] = $invoice_id; $script_data['editMode'] = true; // Get invoice data for JavaScript $invoice_data = $invoice->toArray(); $script_data['invoiceData'] = $invoice_data; // Get invoice items $items = $invoice->getItems(); $items_data = []; foreach ($items as $item) { $items_data[] = [ 'id' => $item->getId(), 'name' => $item->getName(), 'description' => $item->getDescription(), 'quantity' => $item->getQuantity(), 'price' => $item->getPrice(), 'taxable' => $item->isTaxable(), 'total' => $item->getAmount() ]; } $script_data['invoiceItems'] = $items_data; // Get client data if available if ($invoice->getClientId()) { $client_repository = \EasyInvoice\Providers\ClientServiceProvider::getClientRepository(); $client = $client_repository->find($invoice->getClientId()); if ($client) { $script_data['clientData'] = [ 'id' => $client->getId(), 'name' => $client->getBusinessClientName() ?: ($client->getFirstName() . ' ' . $client->getLastName()), 'email' => $client->getEmail(), 'phone' => $client->getExtraInfo(), 'address' => $client->getAddress(), 'company' => $client->getBusinessClientName() ]; } } } } else { // New invoice - set default items $script_data['editMode'] = false; $script_data['invoice_id'] = 0; $default_items = [ [ 'name' => '', 'description' => '', 'quantity' => 0, 'price' => 0, 'taxable' => true ] ]; $script_data['default_items'] = $default_items; } // Localize invoice-specific scripts wp_localize_script('easy-invoice-builder', 'easyInvoice', $script_data); wp_localize_script('easy-invoice-save', 'easyInvoice', $script_data); } if (isset($_GET['page']) && $_GET['page'] === 'easy-invoice-quote-builder') { // Override showAdjustField for quote pages $script_data['showAdjustField'] = \EasyInvoice\Controllers\SettingsController::shouldShowQuoteAdjustField(); wp_localize_script('easy-quote-save', 'easyInvoice', $script_data); } } }