| @@ -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; |