PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.2
2.4.3 2.4.4 2.4.2 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 All 60 releases
← All changes | includes/EasyInvoice.php +112 -23 2.3.2 → 2.4.2 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
@@ -221,13 +225,16 @@
221 225 private function initPaymentGateways() {
222 226 if ( ! $this->gateway_manager ) {
223 227 $this->gateway_manager = new PaymentGatewayManager();
224 228
225 - // Register payment gateways.
226 - $gateways = [
227 - new Gateways\PayPalGateway(),
228 - new Gateways\ManualGateway(),
229 - ];
229 + // Register payment gateways: PayPal, then one gateway per offline
230 + // method (bank transfer, cheque, cash and any the merchant added).
231 + // The single "Manual Payment" gateway of earlier versions is gone;
232 + // setDefaultPaymentMethods() carries its enabled state over.
233 + $gateways = [ new Gateways\PayPalGateway() ];
234 + foreach ( Services\OfflinePayments::methods() as $method ) {
235 + $gateways[] = new Gateways\OfflineGateway( $method );
236 + }
230 237
231 238 foreach ( $gateways as $gateway ) {
232 239 $this->gateway_manager->registerGateway( $gateway );
233 240 }
@@ -247,15 +254,27 @@
247 254 * @access private
248 255 * @return void
249 256 */
250 257 private function setDefaultPaymentMethods() {
251 - $payment_methods = get_option( 'easy_invoice_payment_methods', [] );
258 + $payment_methods = get_option( 'easy_invoice_payment_methods', null );
259 + if ( null === $payment_methods ) {
260 + // First run: an invoice with no way to pay is a dead end, and bank
261 + // transfer needs no account with anyone. The merchant can switch it
262 + // off under Settings → Payment Methods.
263 + update_option( 'easy_invoice_payment_methods', [ 'bank_transfer' ] );
264 + return;
265 + }
266 + $payment_methods = is_array( $payment_methods ) ? $payment_methods : [];
252 267
253 - $defaults_updated = false;
254 -
255 - // Bank Transfer, Cheque, and Cash payment defaults moved to Pro plugin
256 -
257 - if ( $defaults_updated ) {
268 + // Sites that had "Manual Payment" switched on keep an offline option:
269 + // its three sub-choices are gateways of their own now.
270 + if ( in_array( 'manual', $payment_methods, true ) ) {
271 + $payment_methods = array_values( array_diff( $payment_methods, [ 'manual' ] ) );
272 + foreach ( [ 'bank_transfer', 'cheque', 'cash' ] as $offline ) {
273 + if ( ! in_array( $offline, $payment_methods, true ) ) {
274 + $payment_methods[] = $offline;
275 + }
276 + }
258 277 update_option( 'easy_invoice_payment_methods', array_unique( $payment_methods ) );
259 278 }
260 279 }
261 280
@@ -305,8 +324,15 @@
305 324 'query_var' => true,
306 325 'rewrite' => [ 'slug' => 'invoice' ],
307 326 'capability_type' => 'post',
308 327 'has_archive' => false,
328 + // Keep invoices out of site search, search feeds and any archive-style
329 + // query. They are only ever reachable as an authorised single document
330 + // (see TemplateLoader::enforceDocumentAccess). Without this, `?s=INV`
331 + // and `?feed=rss2&post_type=easy_invoice` listed invoice titles and
332 + // permalinks to anonymous visitors — enough to enumerate every invoice
333 + // on the site even though the documents themselves are gated.
334 + 'exclude_from_search' => true,
309 335 'hierarchical' => false,
310 336 'menu_position' => null,
311 337 'supports' => [ 'title', 'editor', 'custom-fields' ]
312 338 ]);
@@ -339,8 +365,10 @@
339 365 'query_var' => true,
340 366 'rewrite' => [ 'slug' => 'easy-invoice-quote', 'with_front' => false ],
341 367 'capability_type' => 'post',
342 368 'has_archive' => false,
369 + // Same reasoning as the invoice post type above.
370 + 'exclude_from_search' => true,
343 371 'hierarchical' => false,
344 372 'menu_position' => null,
345 373 'supports' => [ 'title', 'editor', 'custom-fields' ]
346 374 ]);
@@ -367,10 +395,13 @@
367 395 ],
368 396 'description' => __( 'Payments for Easy Invoice plugin.', 'easy-invoice' ),
369 397 'public' => false,
370 398 'publicly_queryable' => false,
371 - 'show_ui' => true,
372 - 'show_in_menu' => 'edit.php?post_type=easy_invoice',
399 + // Payments are managed on the plugin's own Payments screen; the
400 + // stock post editor knows none of their fields and its "Add New"
401 + // left nameless auto-drafts behind.
402 + 'show_ui' => false,
403 + 'show_in_menu' => false,
373 404 'query_var' => true,
374 405 'rewrite' => [ 'slug' => 'payment' ],
375 406 'capability_type' => 'post',
376 407 'has_archive' => false,
@@ -379,14 +410,52 @@
379 410 'supports' => [ 'title', 'author', 'custom-fields' ],
380 411 'show_in_rest' => false,
381 412 ] );
382 413
414 + // Credit notes. Not publicly queryable and with no rewrite: unlike an
415 + // invoice, a credit note is never handed to a customer through a
416 + // permalink — it reaches them as a PDF attached to the correction being
417 + // explained, so there is no front-end URL to protect in the first place.
418 + register_post_type( \EasyInvoice\Constants\PostTypes::EASY_INVOICE_CREDIT_NOTE_POST_TYPE, [
419 + 'labels' => [
420 + 'name' => _x( 'Credit Notes', 'post type general name', 'easy-invoice' ),
421 + 'singular_name' => _x( 'Credit Note', 'post type singular name', 'easy-invoice' ),
422 + 'menu_name' => _x( 'Credit Notes', 'admin menu', 'easy-invoice' ),
423 + 'all_items' => __( 'All Credit Notes', 'easy-invoice' ),
424 + 'edit_item' => __( 'Edit Credit Note', 'easy-invoice' ),
425 + 'view_item' => __( 'View Credit Note', 'easy-invoice' ),
426 + 'search_items' => __( 'Search Credit Notes', 'easy-invoice' ),
427 + 'not_found' => __( 'No credit notes found.', 'easy-invoice' ),
428 + 'not_found_in_trash' => __( 'No credit notes found in Trash.', 'easy-invoice' ),
429 + ],
430 + 'description' => __( 'Credit notes issued against invoices.', 'easy-invoice' ),
431 + 'public' => false,
432 + 'publicly_queryable' => false,
433 + 'exclude_from_search'=> true,
434 + 'show_ui' => false,
435 + 'show_in_menu' => false,
436 + 'query_var' => false,
437 + 'rewrite' => false,
438 + 'capability_type' => 'post',
439 + 'has_archive' => false,
440 + 'hierarchical' => false,
441 + 'supports' => [ 'title', 'author', 'custom-fields' ],
442 + 'show_in_rest' => false,
443 + ] );
383 444
384 - // Force flush rewrite rules after post type registration
445 +
446 + // Flush rewrite rules after post type registration.
447 + //
448 + // This is throttled to once every 5 minutes (see flushRewriteRules below).
449 + // An unconditional `flush_rewrite_rules(true)` used to follow this call,
450 + // which meant every single request — this method runs on `init` priority 0 —
451 + // regenerated the whole rule set and wrote the `rewrite_rules` option. That
452 + // is one of the most expensive things a plugin can do per request, and it
453 + // made the throttle above pointless.
454 + //
455 + // Activation still flushes explicitly (see easy_invoice_activate), so new
456 + // installs and permalink changes are covered without the per-request cost.
385 457 $this->flushRewriteRules();
386 -
387 - // Force an immediate rewrite rules flush
388 - flush_rewrite_rules(true);
389 458 }
390 459
391 460 /**
392 461 * Flush rewrite rules to ensure custom post type URLs work
@@ -600,8 +669,9 @@
600 669 'public' => true,
601 670 'exclude_from_search' => false,
602 671 'show_in_admin_all_list' => true,
603 672 'show_in_admin_status_list' => true,
673 + /* translators: %s: number of items. */
604 674 'label_count' => _n_noop(
605 675 'Pending Bank Transfer <span class="count">(%s)</span>',
606 676 'Pending Bank Transfer <span class="count">(%s)</span>',
607 677 'easy-invoice'
@@ -612,8 +682,9 @@
612 682 'public' => true,
613 683 'exclude_from_search' => false,
614 684 'show_in_admin_all_list' => true,
615 685 'show_in_admin_status_list' => true,
686 + /* translators: %s: number of items. */
616 687 'label_count' => _n_noop(
617 688 'Pending Cheque <span class="count">(%s)</span>',
618 689 'Pending Cheque <span class="count">(%s)</span>',
619 690 'easy-invoice'
@@ -650,9 +721,9 @@
650 721 $redirect_url = admin_url('admin.php?page=easy-quote-all&rewrite_flushed=1');
651 722 }
652 723 }
653 724
654 - wp_redirect($redirect_url);
725 + wp_safe_redirect($redirect_url);
655 726 exit;
656 727 }
657 728
658 729 /**
@@ -680,9 +751,9 @@
680 751 $redirect_url = admin_url('admin.php?page=easy-quote-all&post_types_registered=1');
681 752 }
682 753 }
683 754
684 - wp_redirect($redirect_url);
755 + wp_safe_redirect($redirect_url);
685 756 exit;
686 757 }
687 758
688 759 /**
@@ -731,8 +802,9 @@
731 802 'easy-invoice-migration', // Migration page
732 803 'easy-invoice-license', // License page
733 804 'easy-invoice-free-vs-pro',
734 805 'easy-invoice-join-community',
806 + 'easy-invoice-import',
735 807 ];
736 808
737 809 // Dynamically include every addon's `settings_url` page slug.
738 810 // This means enterprise addons (time-tracking, dunning, white-label,
@@ -754,10 +826,27 @@
754 826 }
755 827 $easy_invoice_pages = array_values( array_unique( $easy_invoice_pages ) );
756 828 }
757 829
758 - // Check if current page is an Easy Invoice page
759 - if ( in_array( $page, $easy_invoice_pages ) ) {
830 + // Any Easy Invoice screen, including ones the list above cannot know about.
831 + //
832 + // The list is built from each addon's `settings_url`, which is only an addon's
833 + // PRIMARY page. An addon that registers a second screen — Accounting Sync's
834 + // "Sync Log" is the one that exists today — was therefore left out, and WordPress
835 + // rendered its notices there. Those notices are emitted before this plugin's
836 + // markup, so they land outside the app shell's content column and are clipped by
837 + // the fixed sidebar: the page opened with truncated text across the top and the
838 + // real heading pushed far down. It read as a broken page rather than a styled one.
839 + //
840 + // Matching on the slug prefix instead covers every Easy Invoice screen that
841 + // exists now and any added later, and it lines up with how AdminAssets decides
842 + // to load the admin CSS (`strpos($hook, 'easy-invoice') !== false`) — the two
843 + // should always agree on what counts as one of our screens.
844 + $is_easy_invoice_page = in_array( $page, $easy_invoice_pages, true )
845 + || strpos( $page, 'easy-invoice' ) === 0
846 + || strpos( $page, 'easy-quote' ) === 0;
847 +
848 + if ( $is_easy_invoice_page ) {
760 849 // Don't remove our review notice - keep it for free users
761 850 // Store our notice callback temporarily
762 851 $review_notice_callback = false;
763 852 $review_notice_priority = false;