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 +86 -12 2.3.82.4.0 View file →
@@ -164,10 +164,14 @@
164 164
165 165 // Add admin action to flush rewrite rules
166 166 add_action('admin_post_flush_easy_invoice_rewrite_rules', [$this, 'handleFlushRewriteRules']);
167 167
168 - // Add admin action to fix quote slugs
169 - 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.
170 174
171 175 // Add admin action to manually register post types
172 176 add_action('admin_post_register_easy_invoice_post_types', [$this, 'handleRegisterPostTypes']);
173 177
@@ -305,8 +309,15 @@
305 309 'query_var' => true,
306 310 'rewrite' => [ 'slug' => 'invoice' ],
307 311 'capability_type' => 'post',
308 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,
309 320 'hierarchical' => false,
310 321 'menu_position' => null,
311 322 'supports' => [ 'title', 'editor', 'custom-fields' ]
312 323 ]);
@@ -339,8 +350,10 @@
339 350 'query_var' => true,
340 351 'rewrite' => [ 'slug' => 'easy-invoice-quote', 'with_front' => false ],
341 352 'capability_type' => 'post',
342 353 'has_archive' => false,
354 + // Same reasoning as the invoice post type above.
355 + 'exclude_from_search' => true,
343 356 'hierarchical' => false,
344 357 'menu_position' => null,
345 358 'supports' => [ 'title', 'editor', 'custom-fields' ]
346 359 ]);
@@ -367,10 +380,13 @@
367 380 ],
368 381 'description' => __( 'Payments for Easy Invoice plugin.', 'easy-invoice' ),
369 382 'public' => false,
370 383 'publicly_queryable' => false,
371 - 'show_ui' => true,
372 - '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,
373 389 'query_var' => true,
374 390 'rewrite' => [ 'slug' => 'payment' ],
375 391 'capability_type' => 'post',
376 392 'has_archive' => false,
@@ -379,14 +395,52 @@
379 395 'supports' => [ 'title', 'author', 'custom-fields' ],
380 396 'show_in_rest' => false,
381 397 ] );
382 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 + ] );
383 429
384 - // 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.
385 442 $this->flushRewriteRules();
386 -
387 - // Force an immediate rewrite rules flush
388 - flush_rewrite_rules(true);
389 443 }
390 444
391 445 /**
392 446 * Flush rewrite rules to ensure custom post type URLs work
@@ -600,8 +654,9 @@
600 654 'public' => true,
601 655 'exclude_from_search' => false,
602 656 'show_in_admin_all_list' => true,
603 657 'show_in_admin_status_list' => true,
658 + /* translators: %s: number of items. */
604 659 'label_count' => _n_noop(
605 660 'Pending Bank Transfer <span class="count">(%s)</span>',
606 661 'Pending Bank Transfer <span class="count">(%s)</span>',
607 662 'easy-invoice'
@@ -612,8 +667,9 @@
612 667 'public' => true,
613 668 'exclude_from_search' => false,
614 669 'show_in_admin_all_list' => true,
615 670 'show_in_admin_status_list' => true,
671 + /* translators: %s: number of items. */
616 672 'label_count' => _n_noop(
617 673 'Pending Cheque <span class="count">(%s)</span>',
618 674 'Pending Cheque <span class="count">(%s)</span>',
619 675 'easy-invoice'
@@ -650,9 +706,9 @@
650 706 $redirect_url = admin_url('admin.php?page=easy-quote-all&rewrite_flushed=1');
651 707 }
652 708 }
653 709
654 - wp_redirect($redirect_url);
710 + wp_safe_redirect($redirect_url);
655 711 exit;
656 712 }
657 713
658 714 /**
@@ -680,9 +736,9 @@
680 736 $redirect_url = admin_url('admin.php?page=easy-quote-all&post_types_registered=1');
681 737 }
682 738 }
683 739
684 - wp_redirect($redirect_url);
740 + wp_safe_redirect($redirect_url);
685 741 exit;
686 742 }
687 743
688 744 /**
@@ -731,8 +787,9 @@
731 787 'easy-invoice-migration', // Migration page
732 788 'easy-invoice-license', // License page
733 789 'easy-invoice-free-vs-pro',
734 790 'easy-invoice-join-community',
791 + 'easy-invoice-import',
735 792 ];
736 793
737 794 // Dynamically include every addon's `settings_url` page slug.
738 795 // This means enterprise addons (time-tracking, dunning, white-label,
@@ -754,10 +811,27 @@
754 811 }
755 812 $easy_invoice_pages = array_values( array_unique( $easy_invoice_pages ) );
756 813 }
757 814
758 - // Check if current page is an Easy Invoice page
759 - if ( in_array( $page, $easy_invoice_pages ) ) {
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 ) {
760 834 // Don't remove our review notice - keep it for free users
761 835 // Store our notice callback temporarily
762 836 $review_notice_callback = false;
763 837 $review_notice_priority = false;