PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | includes/EasyInvoice.php +150 -36 2.1.132.4.0 View file →
@@ -105,9 +105,18 @@
105 105 * @return void
106 106 */
107 107 private function defineConstants(): void {
108 108 if ( ! defined( 'EASY_INVOICE_VERSION' ) ) {
109 - define( 'EASY_INVOICE_VERSION', '1.0.0' );
109 + $fallback = '2.1.18';
110 + if ( defined( 'EASY_INVOICE_PLUGIN_FILE' ) && function_exists( 'get_file_data' ) ) {
111 + $headers = get_file_data(
112 + EASY_INVOICE_PLUGIN_FILE,
113 + array( 'version' => 'Version' ),
114 + 'plugin'
115 + );
116 + $fallback = ! empty( $headers['version'] ) ? $headers['version'] : $fallback;
117 + }
118 + define( 'EASY_INVOICE_VERSION', $fallback );
110 119 }
111 120
112 121 if ( ! defined( 'EASY_INVOICE_FILE' ) ) {
113 122 define( 'EASY_INVOICE_FILE', dirname( dirname( __FILE__ ) ) . '/easy-invoice.php' );
@@ -155,10 +164,14 @@
155 164
156 165 // Add admin action to flush rewrite rules
157 166 add_action('admin_post_flush_easy_invoice_rewrite_rules', [$this, 'handleFlushRewriteRules']);
158 167
159 - // Add admin action to fix quote slugs
160 - add_action('admin_post_fix_easy_invoice_quote_slugs', [$this, 'handleFixQuoteSlugs']);
168 + // NOTE: 'admin_post_fix_easy_invoice_quote_slugs' was registered here against
169 + // [$this, 'handleFixQuoteSlugs'] — a method that does not exist on this class
170 + // (or anywhere else in the plugin). Hitting that endpoint was an immediate
171 + // fatal. Nothing in the UI ever linked to it, so the registration is removed
172 + // rather than the method being written. Re-add both together if the
173 + // quote-slug repair tool is ever actually needed.
161 174
162 175 // Add admin action to manually register post types
163 176 add_action('admin_post_register_easy_invoice_post_types', [$this, 'handleRegisterPostTypes']);
164 177
@@ -215,8 +228,9 @@
215 228
216 229 // Register payment gateways.
217 230 $gateways = [
218 231 new Gateways\PayPalGateway(),
232 + new Gateways\ManualGateway(),
219 233 ];
220 234
221 235 foreach ( $gateways as $gateway ) {
222 236 $this->gateway_manager->registerGateway( $gateway );
@@ -295,8 +309,15 @@
295 309 'query_var' => true,
296 310 'rewrite' => [ 'slug' => 'invoice' ],
297 311 'capability_type' => 'post',
298 312 'has_archive' => false,
313 + // Keep invoices out of site search, search feeds and any archive-style
314 + // query. They are only ever reachable as an authorised single document
315 + // (see TemplateLoader::enforceDocumentAccess). Without this, `?s=INV`
316 + // and `?feed=rss2&post_type=easy_invoice` listed invoice titles and
317 + // permalinks to anonymous visitors — enough to enumerate every invoice
318 + // on the site even though the documents themselves are gated.
319 + 'exclude_from_search' => true,
299 320 'hierarchical' => false,
300 321 'menu_position' => null,
301 322 'supports' => [ 'title', 'editor', 'custom-fields' ]
302 323 ]);
@@ -329,8 +350,10 @@
329 350 'query_var' => true,
330 351 'rewrite' => [ 'slug' => 'easy-invoice-quote', 'with_front' => false ],
331 352 'capability_type' => 'post',
332 353 'has_archive' => false,
354 + // Same reasoning as the invoice post type above.
355 + 'exclude_from_search' => true,
333 356 'hierarchical' => false,
334 357 'menu_position' => null,
335 358 'supports' => [ 'title', 'editor', 'custom-fields' ]
336 359 ]);
@@ -357,10 +380,13 @@
357 380 ],
358 381 'description' => __( 'Payments for Easy Invoice plugin.', 'easy-invoice' ),
359 382 'public' => false,
360 383 'publicly_queryable' => false,
361 - 'show_ui' => true,
362 - 'show_in_menu' => 'edit.php?post_type=easy_invoice',
384 + // Payments are managed on the plugin's own Payments screen; the
385 + // stock post editor knows none of their fields and its "Add New"
386 + // left nameless auto-drafts behind.
387 + 'show_ui' => false,
388 + 'show_in_menu' => false,
363 389 'query_var' => true,
364 390 'rewrite' => [ 'slug' => 'payment' ],
365 391 'capability_type' => 'post',
366 392 'has_archive' => false,
@@ -369,14 +395,52 @@
369 395 'supports' => [ 'title', 'author', 'custom-fields' ],
370 396 'show_in_rest' => false,
371 397 ] );
372 398
399 + // Credit notes. Not publicly queryable and with no rewrite: unlike an
400 + // invoice, a credit note is never handed to a customer through a
401 + // permalink — it reaches them as a PDF attached to the correction being
402 + // explained, so there is no front-end URL to protect in the first place.
403 + register_post_type( \EasyInvoice\Constants\PostTypes::EASY_INVOICE_CREDIT_NOTE_POST_TYPE, [
404 + 'labels' => [
405 + 'name' => _x( 'Credit Notes', 'post type general name', 'easy-invoice' ),
406 + 'singular_name' => _x( 'Credit Note', 'post type singular name', 'easy-invoice' ),
407 + 'menu_name' => _x( 'Credit Notes', 'admin menu', 'easy-invoice' ),
408 + 'all_items' => __( 'All Credit Notes', 'easy-invoice' ),
409 + 'edit_item' => __( 'Edit Credit Note', 'easy-invoice' ),
410 + 'view_item' => __( 'View Credit Note', 'easy-invoice' ),
411 + 'search_items' => __( 'Search Credit Notes', 'easy-invoice' ),
412 + 'not_found' => __( 'No credit notes found.', 'easy-invoice' ),
413 + 'not_found_in_trash' => __( 'No credit notes found in Trash.', 'easy-invoice' ),
414 + ],
415 + 'description' => __( 'Credit notes issued against invoices.', 'easy-invoice' ),
416 + 'public' => false,
417 + 'publicly_queryable' => false,
418 + 'exclude_from_search'=> true,
419 + 'show_ui' => false,
420 + 'show_in_menu' => false,
421 + 'query_var' => false,
422 + 'rewrite' => false,
423 + 'capability_type' => 'post',
424 + 'has_archive' => false,
425 + 'hierarchical' => false,
426 + 'supports' => [ 'title', 'author', 'custom-fields' ],
427 + 'show_in_rest' => false,
428 + ] );
373 429
374 - // Force flush rewrite rules after post type registration
430 +
431 + // Flush rewrite rules after post type registration.
432 + //
433 + // This is throttled to once every 5 minutes (see flushRewriteRules below).
434 + // An unconditional `flush_rewrite_rules(true)` used to follow this call,
435 + // which meant every single request — this method runs on `init` priority 0 —
436 + // regenerated the whole rule set and wrote the `rewrite_rules` option. That
437 + // is one of the most expensive things a plugin can do per request, and it
438 + // made the throttle above pointless.
439 + //
440 + // Activation still flushes explicitly (see easy_invoice_activate), so new
441 + // installs and permalink changes are covered without the per-request cost.
375 442 $this->flushRewriteRules();
376 -
377 - // Force an immediate rewrite rules flush
378 - flush_rewrite_rules(true);
379 443 }
380 444
381 445 /**
382 446 * Flush rewrite rules to ensure custom post type URLs work
@@ -590,8 +654,9 @@
590 654 'public' => true,
591 655 'exclude_from_search' => false,
592 656 'show_in_admin_all_list' => true,
593 657 'show_in_admin_status_list' => true,
658 + /* translators: %s: number of items. */
594 659 'label_count' => _n_noop(
595 660 'Pending Bank Transfer <span class="count">(%s)</span>',
596 661 'Pending Bank Transfer <span class="count">(%s)</span>',
597 662 'easy-invoice'
@@ -602,8 +667,9 @@
602 667 'public' => true,
603 668 'exclude_from_search' => false,
604 669 'show_in_admin_all_list' => true,
605 670 'show_in_admin_status_list' => true,
671 + /* translators: %s: number of items. */
606 672 'label_count' => _n_noop(
607 673 'Pending Cheque <span class="count">(%s)</span>',
608 674 'Pending Cheque <span class="count">(%s)</span>',
609 675 'easy-invoice'
@@ -640,9 +706,9 @@
640 706 $redirect_url = admin_url('admin.php?page=easy-quote-all&rewrite_flushed=1');
641 707 }
642 708 }
643 709
644 - wp_redirect($redirect_url);
710 + wp_safe_redirect($redirect_url);
645 711 exit;
646 712 }
647 713
648 714 /**
@@ -670,9 +736,9 @@
670 736 $redirect_url = admin_url('admin.php?page=easy-quote-all&post_types_registered=1');
671 737 }
672 738 }
673 739
674 - wp_redirect($redirect_url);
740 + wp_safe_redirect($redirect_url);
675 741 exit;
676 742 }
677 743
678 744 /**
@@ -684,40 +750,88 @@
684 750 public function disableAdminNoticesOnEasyInvoicePages() {
685 751 // Get current page
686 752 $page = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : '';
687 753
688 - // Check if we're on an Easy Invoice page
754 + // Static list of core Easy Invoice page slugs. Addon-owned page
755 + // slugs (Item Library, Template Builder, enterprise addon pages,
756 + // export manager, etc.) are appended dynamically below from
757 + // AddonRegistry so new addons automatically suppress WP notices
758 + // on their pages without needing to edit this list.
689 759 $easy_invoice_pages = [
690 - 'easy-invoice', // Dashboard
691 - 'easy-invoice-all', // All invoices
692 - 'easy-invoice-builder', // Invoice builder
693 - 'easy-invoice-preview', // Invoice preview
694 - 'easy-quote-all', // All quotes
695 - 'easy-invoice-quote-builder', // Quote builder
696 - 'easy-quote-preview', // Quote preview
697 - 'easy-invoice-payments', // All payments
698 - 'easy-invoice-payment-new', // Add new payment
699 - 'easy-invoice-clients', // All clients
700 - 'easy-invoice-client-edit', // Edit client
701 - 'easy-invoice-client-view', // View client
702 - 'easy-invoice-reports', // Reports
703 - 'easy-invoice-settings', // Main settings
704 - 'easy-invoice-email-settings', // Email settings
760 + 'easy-invoice', // Dashboard
761 + 'easy-invoice-all', // All invoices
762 + 'easy-invoice-builder', // Invoice builder
763 + 'easy-invoice-preview', // Invoice preview
764 + 'easy-quote-all', // All quotes
765 + 'easy-invoice-quote-builder', // Quote builder
766 + 'easy-quote-preview', // Quote preview
767 + 'easy-invoice-payments', // All payments
768 + 'easy-invoice-payment-new', // Add new payment
769 + 'easy-invoice-clients', // All clients
770 + 'easy-invoice-client-edit', // Edit client
771 + 'easy-invoice-client-view', // View client
772 + 'easy-invoice-reports', // Reports
773 + 'easy-invoice-settings', // Main settings
774 + 'easy-invoice-email-settings', // Email settings
705 775 'easy-invoice-email-settings-general',
706 776 'easy-invoice-email-settings-invoice',
707 777 'easy-invoice-email-settings-quote',
708 778 'easy-invoice-email-settings-payment',
709 - 'easy-invoice-pro-settings', // Pro settings
710 - 'easy-invoice-pro-translations', // Pro translations
711 - 'easy-invoice-pro-item-library', // Item Library (Pro)
712 - 'easy-invoice-templates', // Template Builder (Pro)
713 - 'easy-invoice-migration', // Migration page
714 - 'easy-invoice-license', // License page
715 - 'easy-invoice-free-vs-pro'
779 + 'easy-invoice-pro-settings', // Pro settings
780 + 'easy-invoice-pro-translations',// Pro translations
781 + 'easy-invoice-pro-item-library',// Item Library (Pro)
782 + 'easy-invoice-templates', // Template Builder listing (Pro)
783 + 'easy-invoice-templates-new', // Template Builder "Create New" (Pro)
784 + 'easy-invoice-template-builder',// Template Builder canvas (Pro)
785 + 'easy-invoice-export', // Export Data (bulk_operations addon)
786 + 'easy-invoice-addons', // Addons grid
787 + 'easy-invoice-migration', // Migration page
788 + 'easy-invoice-license', // License page
789 + 'easy-invoice-free-vs-pro',
790 + 'easy-invoice-join-community',
791 + 'easy-invoice-import',
716 792 ];
717 793
718 - // Check if current page is an Easy Invoice page
719 - if ( in_array( $page, $easy_invoice_pages ) ) {
794 + // Dynamically include every addon's `settings_url` page slug.
795 + // This means enterprise addons (time-tracking, dunning, white-label,
796 + // team-roles, webhooks) and any future addon automatically get
797 + // notice-suppression without needing to update this array.
798 + if ( class_exists( '\\EasyInvoice\\Addons\\AddonRegistry' ) ) {
799 + foreach ( \EasyInvoice\Addons\AddonRegistry::all() as $ei_addon ) {
800 + if ( empty( $ei_addon['settings_url'] ) ) {
801 + continue;
802 + }
803 + $parts = wp_parse_url( $ei_addon['settings_url'] );
804 + if ( empty( $parts['query'] ) ) {
805 + continue;
806 + }
807 + parse_str( $parts['query'], $qs );
808 + if ( ! empty( $qs['page'] ) ) {
809 + $easy_invoice_pages[] = $qs['page'];
810 + }
811 + }
812 + $easy_invoice_pages = array_values( array_unique( $easy_invoice_pages ) );
813 + }
814 +
815 + // Any Easy Invoice screen, including ones the list above cannot know about.
816 + //
817 + // The list is built from each addon's `settings_url`, which is only an addon's
818 + // PRIMARY page. An addon that registers a second screen — Accounting Sync's
819 + // "Sync Log" is the one that exists today — was therefore left out, and WordPress
820 + // rendered its notices there. Those notices are emitted before this plugin's
821 + // markup, so they land outside the app shell's content column and are clipped by
822 + // the fixed sidebar: the page opened with truncated text across the top and the
823 + // real heading pushed far down. It read as a broken page rather than a styled one.
824 + //
825 + // Matching on the slug prefix instead covers every Easy Invoice screen that
826 + // exists now and any added later, and it lines up with how AdminAssets decides
827 + // to load the admin CSS (`strpos($hook, 'easy-invoice') !== false`) — the two
828 + // should always agree on what counts as one of our screens.
829 + $is_easy_invoice_page = in_array( $page, $easy_invoice_pages, true )
830 + || strpos( $page, 'easy-invoice' ) === 0
831 + || strpos( $page, 'easy-quote' ) === 0;
832 +
833 + if ( $is_easy_invoice_page ) {
720 834 // Don't remove our review notice - keep it for free users
721 835 // Store our notice callback temporarily
722 836 $review_notice_callback = false;
723 837 $review_notice_priority = false;