PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
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
easy-invoice / includes / Admin / EasyInvoiceAdmin.php

EasyInvoiceAdmin.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.3, at includes/Admin/EasyInvoiceAdmin.php

807 lines 30.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * EasyInvoice Admin Class
4 *
5 * @package Easy_Invoice
6 * @subpackage Admin
7 */
8
9 namespace EasyInvoice\Admin;
10
11 use EasyInvoice\Admin\AdminAssets;
12 use EasyInvoice\Constants\PagesSlugs;
13 use EasyInvoice\Controllers\InvoiceController;
14 use EasyInvoice\Controllers\QuoteController;
15 use EasyInvoice\Controllers\ClientController;
16 use EasyInvoice\Controllers\SettingsController;
17 use EasyInvoice\Controllers\ReportController;
18 use EasyInvoice\Controllers\DashboardController;
19 use EasyInvoice\Controllers\PaymentController;
20
21 /**
22 * EasyInvoiceAdmin is the main admin class that initializes all controllers
23 */
24 class EasyInvoiceAdmin {
25 /**
26 * Invoice controller instance
27 *
28 * @var InvoiceController
29 */
30 private $invoice_controller;
31
32 /**
33 * Quote controller instance
34 *
35 * @var QuoteController
36 */
37 private $quote_controller;
38
39 /**
40 * Client controller instance
41 *
42 * @var ClientController
43 */
44 private $client_controller;
45
46 /**
47 * Settings controller instance
48 *
49 * @var SettingsController
50 */
51 private $settings_controller;
52
53 /**
54 * Report controller instance
55 *
56 * @var ReportController
57 */
58 private $report_controller;
59
60 /**
61 * Dashboard controller instance
62 *
63 * @var DashboardController
64 */
65 private $dashboard_controller;
66
67 /**
68 * Payment controller instance
69 *
70 * @var PaymentController
71 */
72 private $payment_controller;
73
74 /**
75 * Template Builder controller instance
76 *
77 * @var \EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController
78 */
79 private $template_builder_controller;
80
81 /**
82 * Constructor
83 */
84 public function __construct() {
85 $this->invoice_controller = new InvoiceController();
86 $this->quote_controller = new QuoteController();
87 $this->client_controller = new ClientController();
88 $this->settings_controller = new SettingsController();
89 $this->report_controller = new ReportController();
90 $this->dashboard_controller = new DashboardController();
91 $this->payment_controller = new PaymentController();
92
93 // Initialize Template Builder controller if Pro is active AND
94 // the custom_templates addon is enabled. The addon owns this
95 // feature — see addons/CustomTemplates/addon.php in the Pro
96 // plugin. Constructing the controller here always-on would
97 // register its admin_menu hook regardless of addon state.
98 if (
99 easy_invoice_has_pro()
100 && \EasyInvoice\Addons\AddonManager::shouldLoad('custom_templates')
101 && class_exists('\EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController')
102 ) {
103 $this->template_builder_controller = new \EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController();
104 }
105 }
106
107 /**
108 * Initialize the admin class
109 */
110 public function init() {
111 // Add menu items
112 add_action('admin_menu', array($this, 'registerMenuPages'));
113 add_action('admin_menu', array($this, 'relaxMenuCapabilities'), 9999);
114
115 // Register admin assets
116 $admin_assets = new AdminAssets();
117 $admin_assets->register();
118
119 // Register invoice preview page
120 add_action('admin_init', array($this, 'registerPreviewPage'));
121
122 // Ensure proper page titles to avoid WordPress core issues
123 add_filter('admin_title', array($this, 'setAdminTitle'));
124
125 // Hide admin UI
126 add_action('admin_head', array($this, 'hideAdminUi'));
127
128 // Body class for layout fixes (admin bar offset, header strip alignment)
129 add_filter('admin_body_class', array($this, 'adminBodyClass'));
130
131 // Initialize controllers
132 $this->invoice_controller->init();
133 $this->quote_controller->init();
134 $this->client_controller->init();
135 $this->settings_controller->init();
136 $this->report_controller->init();
137 $this->dashboard_controller->init();
138 $this->payment_controller->init();
139
140 // Main content hook
141 add_action('easy_invoice_admin_main_content', array($this, 'mainPageContent'));
142 }
143
144 /**
145 * Let an addon lower the capability a menu page demands.
146 *
147 * Every page is registered with `manage_options`, which is right for a
148 * site with one administrator and wrong the moment the Team Roles addon
149 * hands a colleague an "EI Viewer" role: WordPress refused them every
150 * screen before the plugin's own capability checks were even consulted.
151 * add_submenu_page() drops a page outright when the current user lacks
152 * the capability, so the plugin's own pages pass through menuCapability()
153 * at registration; this late pass covers pages registered elsewhere.
154 */
155 public function menuCapability($slug) {
156 /** This filter is documented in relaxMenuCapabilities(). */
157 return (string) apply_filters('easy_invoice_menu_capability', 'manage_options', (string) $slug);
158 }
159
160 public function relaxMenuCapabilities() {
161 global $menu, $submenu;
162 $relax = static function ($cap, $slug) {
163 if (!is_string($slug) || 0 !== strpos($slug, 'easy-') ) {
164 return $cap;
165 }
166 /**
167 * Filter the capability required to open an Easy Invoice admin page.
168 *
169 * @param string $cap Capability registered for the page.
170 * @param string $slug Page slug.
171 */
172 return (string) apply_filters('easy_invoice_menu_capability', $cap, $slug);
173 };
174 if (is_array($menu)) {
175 foreach ($menu as $i => $item) {
176 if (isset($item[1], $item[2])) {
177 $menu[$i][1] = $relax($item[1], $item[2]);
178 }
179 }
180 }
181 if (is_array($submenu)) {
182 foreach ($submenu as $parent => $items) {
183 foreach ((array) $items as $i => $item) {
184 if (isset($item[1], $item[2])) {
185 $submenu[$parent][$i][1] = $relax($item[1], $item[2]);
186 }
187 }
188 }
189 }
190 }
191
192 /**
193 * The admin menu mark: the same document-and-tick glyph as the plugin
194 * icon, monochrome so it follows WordPress' menu colour schemes. Falls
195 * back to a dashicon if the file is ever missing from the build.
196 *
197 * @return string Data URI, or a dashicon class.
198 */
199 public static function menuIcon() {
200 static $icon = null;
201 if ($icon !== null) {
202 return $icon;
203 }
204 $file = EASY_INVOICE_PLUGIN_DIR . 'assets/images/menu-icon.svg';
205 $svg = is_readable($file) ? file_get_contents($file) : '';
206 $icon = $svg ? 'data:image/svg+xml;base64,' . base64_encode($svg) : 'dashicons-media-text';
207 return $icon;
208 }
209
210 /**
211 * Register admin menu pages
212 */
213 public function registerMenuPages() {
214 // Main menu item
215 add_menu_page(
216 __('Easy Invoice', 'easy-invoice'),
217 __('Easy Invoice', 'easy-invoice'),
218 $this->menuCapability(PagesSlugs::DASHBOARD),
219 PagesSlugs::DASHBOARD,
220 array($this, 'displayMainPage'),
221 self::menuIcon(),
222 25
223 );
224
225 // Dashboard submenu
226 add_submenu_page(
227 'easy-invoice',
228 __('Dashboard', 'easy-invoice'),
229 __('Dashboard', 'easy-invoice'),
230 $this->menuCapability(PagesSlugs::DASHBOARD),
231 PagesSlugs::DASHBOARD,
232 array($this, 'displayMainPage')
233 );
234
235 // All invoices submenu
236 add_submenu_page(
237 'easy-invoice',
238 __('All Invoices', 'easy-invoice'),
239 __('All Invoices', 'easy-invoice'),
240 $this->menuCapability(PagesSlugs::ALL_INVOICES),
241 PagesSlugs::ALL_INVOICES,
242 array($this, 'displayMainPage')
243 );
244
245 // Add new invoice submenu
246 add_submenu_page(
247 'easy-invoice-hidden',
248 __('Add New Invoice', 'easy-invoice'),
249 __('Add New', 'easy-invoice'),
250 $this->menuCapability(PagesSlugs::INVOICE_NEW),
251 PagesSlugs::INVOICE_NEW,
252 array($this, 'displayMainPage')
253 );
254
255 // All quotes submenu
256 add_submenu_page(
257 'easy-invoice',
258 __('All Quotes', 'easy-invoice'),
259 __('All Quotes', 'easy-invoice'),
260 $this->menuCapability(PagesSlugs::ALL_QUOTES),
261 PagesSlugs::ALL_QUOTES,
262 array($this, 'displayMainPage')
263 );
264
265 // Add new quote submenu
266 add_submenu_page(
267 'easy-invoice-hidden',
268 __('Add New Quote', 'easy-invoice'),
269 __('Add New Quote', 'easy-invoice'),
270 $this->menuCapability(PagesSlugs::QUOTE_NEW),
271 PagesSlugs::QUOTE_NEW,
272 array($this, 'displayMainPage')
273 );
274
275 // Payments submenu
276 add_submenu_page(
277 'easy-invoice-hidden',
278 __('Payments', 'easy-invoice'),
279 __('Payments', 'easy-invoice'),
280 $this->menuCapability(PagesSlugs::PAYMENTS),
281 PagesSlugs::PAYMENTS,
282 array($this, 'displayMainPage')
283 );
284
285 // Add new payment submenu
286 add_submenu_page(
287 'easy-invoice-hidden',
288 __('Add New Payment', 'easy-invoice'),
289 __('Add New Payment', 'easy-invoice'),
290 $this->menuCapability(PagesSlugs::PAYMENT_NEW),
291 PagesSlugs::PAYMENT_NEW,
292 array($this, 'displayMainPage')
293 );
294
295 // Clients submenu
296 add_submenu_page(
297 'easy-invoice',
298 __('All Clients', 'easy-invoice'),
299 __('All Clients', 'easy-invoice'),
300 $this->menuCapability(PagesSlugs::CLIENTS),
301 PagesSlugs::CLIENTS,
302 array($this, 'displayMainPage')
303 );
304
305 // Item Library + Template Builder pages are reached via the
306 // Easy Invoice in-app sidebar (templates/main-template.php), NOT
307 // the WP admin submenu. We still register the page slugs so the
308 // URLs resolve, but we parent them under 'easy-invoice-hidden'
309 // so they don't render duplicate entries in the WP submenu —
310 // the WP submenu showed the link AND the in-app sidebar showed
311 // the same link, giving the user two visible navigation
312 // entry-points to the same page.
313 //
314 // Registration is still gated on the addon being active — if
315 // the addon is off, the slug isn't registered, so direct-URL
316 // access returns the standard "you do not have sufficient
317 // permissions" page.
318 if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')) {
319 add_submenu_page(
320 'easy-invoice-hidden',
321 __('Item Library', 'easy-invoice'),
322 __('Item Library', 'easy-invoice'),
323 $this->menuCapability('easy-invoice-pro-item-library'),
324 'easy-invoice-pro-item-library',
325 array($this, 'displayMainPage')
326 );
327 }
328
329 if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('custom_templates')) {
330 // Template listing — reachable from the in-app sidebar.
331 add_submenu_page(
332 'easy-invoice-hidden',
333 __('Template Builder', 'easy-invoice'),
334 __('Template Builder', 'easy-invoice'),
335 $this->menuCapability('easy-invoice-templates'),
336 'easy-invoice-templates',
337 array($this, 'displayMainPage')
338 );
339
340 // Create New Template — used by deep-links from inside the
341 // builder UI.
342 add_submenu_page(
343 'easy-invoice-hidden',
344 __('Create New Template', 'easy-invoice'),
345 __('Create New', 'easy-invoice'),
346 $this->menuCapability('easy-invoice-templates-new'),
347 'easy-invoice-templates-new',
348 array($this, 'displayMainPage')
349 );
350
351 // Template Builder canvas — full-screen builder, reached
352 // from the listing page's Edit / New buttons.
353 add_submenu_page(
354 'easy-invoice-hidden',
355 __('Template Builder Page', 'easy-invoice'),
356 __('Template Builder Page', 'easy-invoice'),
357 $this->menuCapability('easy-invoice-template-builder'),
358 'easy-invoice-template-builder',
359 array($this, 'displayMainPage')
360 );
361 }
362
363 // Hidden submenu pages - not shown in the menu but accessible
364 add_submenu_page(
365 'easy-invoice-hidden', // Use main menu as parent to avoid title issues
366 __('Edit Client', 'easy-invoice'),
367 __('Edit Client', 'easy-invoice'),
368 $this->menuCapability(PagesSlugs::CLIENT_EDIT),
369 PagesSlugs::CLIENT_EDIT,
370 array($this, 'displayMainPage')
371 );
372
373 add_submenu_page(
374 'easy-invoice-hidden', // Use main menu as parent to avoid title issues
375 __('View Client', 'easy-invoice'),
376 __('View Client', 'easy-invoice'),
377 $this->menuCapability(PagesSlugs::CLIENT_VIEW),
378 PagesSlugs::CLIENT_VIEW,
379 array($this, 'displayMainPage')
380 );
381
382 add_submenu_page(
383 'easy-invoice-hidden', // Use main menu as parent to avoid title issues
384 __('Preview Invoice', 'easy-invoice'),
385 __('Preview Invoice', 'easy-invoice'),
386 $this->menuCapability(PagesSlugs::INVOICE_PREVIEW),
387 PagesSlugs::INVOICE_PREVIEW,
388 array($this, 'displayPreviewPage')
389 );
390
391 add_submenu_page(
392 'easy-invoice-hidden', // Use main menu as parent to avoid title issues
393 __('Preview Quote', 'easy-invoice'),
394 __('Preview Quote', 'easy-invoice'),
395 $this->menuCapability(PagesSlugs::QUOTE_PREVIEW),
396 PagesSlugs::QUOTE_PREVIEW,
397 array($this, 'displayMainPage')
398 );
399
400 // Reports — addon-gated (Personal tier). Slug registered as a
401 // HIDDEN submenu so the WP admin sidebar doesn't double the
402 // in-app sidebar entry. When the reports addon is disabled, the
403 // slug isn't registered at all, so direct-URL access falls
404 // through to WP's "no permission" page.
405 if (\EasyInvoice\Addons\AddonManager::shouldLoad('reports')) {
406 add_submenu_page(
407 'easy-invoice-hidden',
408 __('Reports', 'easy-invoice'),
409 __('Reports', 'easy-invoice'),
410 $this->menuCapability(PagesSlugs::REPORTS),
411 PagesSlugs::REPORTS,
412 array($this, 'displayMainPage')
413 );
414 }
415
416 // Settings submenu
417 add_submenu_page(
418 'easy-invoice',
419 __('Settings', 'easy-invoice'),
420 __('Settings', 'easy-invoice'),
421 $this->menuCapability(PagesSlugs::SETTINGS),
422 PagesSlugs::SETTINGS,
423 array($this, 'displayMainPage')
424 );
425
426 // Addons submenu
427 add_submenu_page(
428 'easy-invoice',
429 __('Addons', 'easy-invoice'),
430 __('Addons', 'easy-invoice'),
431 $this->menuCapability(PagesSlugs::ADDONS),
432 PagesSlugs::ADDONS,
433 array($this, 'displayMainPage')
434 );
435
436 // License submenu
437 add_submenu_page(
438 'easy-invoice',
439 __('License', 'easy-invoice'),
440 __('License', 'easy-invoice'),
441 $this->menuCapability(PagesSlugs::LICENSE),
442 PagesSlugs::LICENSE,
443 array($this, 'displayMainPage')
444 );
445
446 // Free vs Pro submenu - only show in free version
447 if (!easy_invoice_has_pro()) {
448 add_submenu_page(
449 'easy-invoice',
450 __('Free vs Pro', 'easy-invoice'),
451 __('Free vs Pro', 'easy-invoice'),
452 $this->menuCapability('easy-invoice-free-vs-pro'),
453 'easy-invoice-free-vs-pro',
454 array($this, 'displayMainPage')
455 );
456 }
457
458 // Join Community submenu
459 add_submenu_page(
460 'easy-invoice-hidden',
461 __('Join Community', 'easy-invoice'),
462 __('Join Community', 'easy-invoice'),
463 'read',
464 'easy-invoice-join-community',
465 array($this, 'redirectToCommunity')
466 );
467 }
468
469 /**
470 * Redirect to community page
471 */
472 public function redirectToCommunity() {
473 wp_safe_redirect(esc_url_raw('https://www.facebook.com/groups/mantrabraincommunity'));
474 exit;
475 }
476
477 /**
478 * Register invoice preview page.
479 *
480 * Placeholder kept as an extension point — the preview-page hook used to
481 * be wired here but is now routed through the per-document preview
482 * controller. Left in place so themes/plugins relying on a no-op
483 * `registerPreviewPage()` call don't fatal.
484 */
485 public function registerPreviewPage() {
486 // No-op: preview rendering is handled by the per-document controller.
487 }
488
489 /**
490 * Display main page
491 */
492 public function displayMainPage() {
493 include EASY_INVOICE_PLUGIN_DIR . 'templates/main-template.php';
494 }
495
496 /**
497 * Display preview page
498 */
499 public function displayPreviewPage() {
500 $this->invoice_controller->display(['page' => PagesSlugs::INVOICE_PREVIEW]);
501 }
502
503 /**
504 * Display license page
505 */
506 public function displayLicensePage() {
507 include EASY_INVOICE_PLUGIN_DIR . 'templates/admin/license-page.php';
508 }
509
510 /**
511 * Display addons page
512 */
513 public function displayAddonsPage() {
514 if (class_exists('\\EasyInvoice\\Controllers\\AddonsController')) {
515 (new \EasyInvoice\Controllers\AddonsController())->display();
516 }
517 }
518
519 /**
520 * Handle main page content based on the current page
521 *
522 * @param string $page Current page
523 */
524 public function mainPageContent($page) {
525 switch ($page) {
526 case PagesSlugs::DASHBOARD:
527 $this->dashboard_controller->display(['page' => PagesSlugs::DASHBOARD]);
528 break;
529
530 case PagesSlugs::ALL_INVOICES:
531 $this->invoice_controller->display(['page' => PagesSlugs::ALL_INVOICES]);
532 break;
533
534 case PagesSlugs::INVOICE_NEW:
535 $this->invoice_controller->display(['page' => PagesSlugs::INVOICE_NEW]);
536 break;
537
538 case PagesSlugs::INVOICE_PREVIEW:
539 $this->invoice_controller->display(['page' => PagesSlugs::INVOICE_PREVIEW]);
540 break;
541
542 case PagesSlugs::ALL_QUOTES:
543 $this->quote_controller->display(['page' => PagesSlugs::ALL_QUOTES]);
544 break;
545
546 case PagesSlugs::QUOTE_NEW:
547 $this->quote_controller->display(['page' => PagesSlugs::QUOTE_NEW]);
548 break;
549
550 case PagesSlugs::QUOTE_PREVIEW:
551 $this->quote_controller->display(['page' => PagesSlugs::QUOTE_PREVIEW]);
552 break;
553
554 case PagesSlugs::PAYMENTS:
555 if (isset($_GET['action'])) {
556 $this->payment_controller->display(['page' => sanitize_key(wp_unslash($_GET['action']))]); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
557 } else {
558 $this->payment_controller->display(['page' => PagesSlugs::PAYMENTS]);
559 }
560 break;
561
562 case PagesSlugs::PAYMENT_NEW:
563 $this->payment_controller->display(['page' => PagesSlugs::PAYMENT_NEW]);
564 break;
565
566 case PagesSlugs::CLIENTS:
567 $this->client_controller->display(['page' => PagesSlugs::CLIENTS]);
568 break;
569
570 case PagesSlugs::CLIENT_EDIT:
571 $this->client_controller->display(['page' => PagesSlugs::CLIENT_EDIT]);
572 break;
573
574 // Handle Pro features — gated on addon-state. The menu
575 // registrations above are also addon-gated, so these cases
576 // should only fire when the addon is enabled. Re-checking
577 // shouldLoad() here is defensive in case the user hits the
578 // page slug directly via URL.
579 case 'easy-invoice-pro-item-library':
580 if (
581 easy_invoice_has_pro()
582 && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')
583 && class_exists('\EasyInvoicePro\Addons\ItemLibrary\Controllers\ItemLibraryController')
584 ) {
585 $item_library_controller = new \EasyInvoicePro\Addons\ItemLibrary\Controllers\ItemLibraryController();
586 $item_library_controller->displayMainPage();
587 }
588 break;
589
590 case 'easy-invoice-templates':
591 if (
592 easy_invoice_has_pro()
593 && \EasyInvoice\Addons\AddonManager::shouldLoad('custom_templates')
594 && class_exists('\EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController')
595 ) {
596 $template_builder_controller = new \EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController();
597 $template_builder_controller->displayMainPage();
598 }
599 break;
600
601 case 'easy-invoice-template-builder':
602 if (
603 easy_invoice_has_pro()
604 && \EasyInvoice\Addons\AddonManager::shouldLoad('custom_templates')
605 && class_exists('\EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController')
606 ) {
607 $template_builder_controller = new \EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController();
608 $template_builder_controller->displayMainPage();
609 }
610 break;
611
612 case PagesSlugs::CLIENT_VIEW:
613 $this->client_controller->display(['page' => PagesSlugs::CLIENT_VIEW]);
614 break;
615
616 case PagesSlugs::REPORTS:
617 // Reports is addon-gated. The submenu page slug is only
618 // registered when shouldLoad('reports') is true, so this
619 // case ordinarily only fires for active-addon users —
620 // but recheck defensively in case the page is reached
621 // via direct URL right after the addon was disabled.
622 if (\EasyInvoice\Addons\AddonManager::shouldLoad('reports')) {
623 $this->report_controller->display(['page' => PagesSlugs::REPORTS]);
624 }
625 break;
626
627 case PagesSlugs::SETTINGS:
628 $this->settings_controller->display(['page' => PagesSlugs::SETTINGS]);
629 break;
630
631 case PagesSlugs::LICENSE:
632 $this->displayLicensePage();
633 break;
634
635 case PagesSlugs::ADDONS:
636 $this->displayAddonsPage();
637 break;
638
639 case PagesSlugs::EMAIL_SETTINGS:
640 case PagesSlugs::EMAIL_SETTINGS_GENERAL:
641 case PagesSlugs::EMAIL_SETTINGS_INVOICE:
642 case PagesSlugs::EMAIL_SETTINGS_QUOTE:
643 case PagesSlugs::EMAIL_SETTINGS_PAYMENT:
644 $this->settings_controller->display(['page' => PagesSlugs::SETTINGS, 'subsection' => $page]);
645 break;
646
647 case 'easy-invoice-migration':
648 include EASY_INVOICE_PLUGIN_DIR . 'includes/Migration/templates/migration-page.php';
649 break;
650
651 case 'easy-invoice-free-vs-pro':
652 include EASY_INVOICE_PLUGIN_DIR . 'templates/admin/free-vs-pro-page.php';
653 break;
654
655 default:
656 // Addon pages render themselves via the
657 // `easy_invoice_admin_main_content` action at priority 11. The
658 // dispatcher must NOT fall through to the Dashboard for those
659 // slugs, otherwise the Dashboard markup gets emitted first and
660 // the addon panel renders on top of it (two pages stacked).
661 //
662 // We treat any slug that's either (a) prefixed with
663 // `easy-invoice-addon-` or (b) registered in AddonRegistry as
664 // an addon page — and just `break` to let the addon's own hook
665 // handle the rendering.
666 if (strpos($page, 'easy-invoice-addon-') === 0) {
667 break;
668 }
669
670 /**
671 * Slugs whose page draws itself on the same hook at a later
672 * priority, and which must therefore not get the Dashboard
673 * rendered underneath them.
674 *
675 * Addon pages were special-cased by prefix above, which left
676 * anything in the free plugin that renders the same way — the
677 * credit note screen, for one — falling through to the
678 * Dashboard and stacking two pages on top of each other. A
679 * filter means the next such page registers itself instead of
680 * editing this switch and rediscovering the same bug.
681 *
682 * @param string[] $slugs Page slugs that render themselves.
683 */
684 // "Create New Template" is a deep-link slug with no screen of
685 // its own; send it to the builder rather than draw the Dashboard.
686 if ('easy-invoice-templates-new' === $page) {
687 echo '<script>window.location.replace(' . wp_json_encode(admin_url('admin.php?page=easy-invoice-template-builder&action=new')) . ');</script>';
688 break;
689 }
690 $self_rendering = (array) apply_filters('easy_invoice_self_rendering_pages', [
691 \EasyInvoice\Controllers\CreditNoteController::PAGE_SLUG,
692 ]);
693
694 if (in_array($page, $self_rendering, true)) {
695 break;
696 }
697 if (class_exists('\\EasyInvoice\\Addons\\AddonRegistry')) {
698 foreach (\EasyInvoice\Addons\AddonRegistry::all() as $_addon) {
699 if (empty($_addon['settings_url'])) continue;
700 $parsed = wp_parse_url($_addon['settings_url']);
701 if (empty($parsed['query'])) continue;
702 parse_str($parsed['query'], $q);
703 if (($q['page'] ?? '') === $page) {
704 return; // Addon owns this slug — bail without rendering.
705 }
706 }
707 }
708 $this->dashboard_controller->display(['page' => PagesSlugs::DASHBOARD]);
709 break;
710 }
711 }
712
713 /**
714 * Set proper admin title to avoid WordPress core issues
715 *
716 * @param string $title The current admin title
717 * @return string The modified admin title
718 */
719 public function setAdminTitle($title) {
720 $current_page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : '';
721
722 // Only modify titles for our plugin pages
723 if (strpos($current_page, 'easy-invoice') === 0 || strpos($current_page, 'easy-quote') === 0) {
724 $page_titles = [
725 'easy-invoice' => __('Dashboard', 'easy-invoice'),
726 'easy-invoice-all' => __('All Invoices', 'easy-invoice'),
727 'easy-invoice-builder' => __('Add New Invoice', 'easy-invoice'),
728 'easy-invoice-preview' => __('Preview Invoice', 'easy-invoice'),
729 'easy-quote-all' => __('All Quotes', 'easy-invoice'),
730 'easy-invoice-quote-builder' => __('Add New Quote', 'easy-invoice'),
731 'easy-quote-preview' => __('Preview Quote', 'easy-invoice'),
732 'easy-invoice-payments' => __('Payments', 'easy-invoice'),
733 'easy-invoice-payment-new' => __('Add New Payment', 'easy-invoice'),
734 'easy-invoice-clients' => __('Clients', 'easy-invoice'),
735 'easy-invoice-client-edit' => __('Edit Client', 'easy-invoice'),
736 'easy-invoice-client-view' => __('View Client', 'easy-invoice'),
737 'easy-invoice-reports' => __('Reports', 'easy-invoice'),
738 'easy-invoice-settings' => __('Settings', 'easy-invoice'),
739 'easy-invoice-email-settings' => __('Email Settings', 'easy-invoice'),
740 'easy-invoice-migration' => __('Migration', 'easy-invoice'),
741 'easy-invoice-free-vs-pro' => __('Free vs Pro', 'easy-invoice'),
742 'easy-invoice-addons' => __('Addons', 'easy-invoice'),
743 ];
744
745 if (isset($page_titles[$current_page])) {
746 return $page_titles[$current_page] . ' - ' . get_bloginfo('name');
747 }
748 }
749
750 return $title;
751 }
752
753 /**
754 * Body class for Easy Invoice full-width layout (used by hideAdminUi CSS).
755 *
756 * @param string $classes Space-separated classes.
757 * @return string
758 */
759 public function adminBodyClass($classes) {
760 $screen = get_current_screen();
761 if (!$screen || !isset($screen->id)) {
762 return $classes;
763 }
764 if (strpos($screen->id, 'easy-invoice') !== false || strpos($screen->id, 'easy-quote') !== false) {
765 $classes .= ' easy-invoice-admin-body';
766 }
767 return $classes;
768 }
769
770 /**
771 * Hide the WordPress admin UI for our custom pages
772 */
773 public function hideAdminUi() {
774 $screen = get_current_screen();
775 if ($screen && property_exists($screen, 'id') && (strpos($screen->id, 'easy-invoice') !== false || strpos($screen->id, 'easy-quote') !== false)) {
776 ?>
777 <style>
778 #wpadminbar,
779 #adminmenumain,
780 #wpfooter {
781 display: none !important;
782 }
783 #wpcontent {
784 margin-left: 0 !important;
785 padding: 0 !important;
786 }
787 .wrap {
788 margin: 0 !important;
789 padding: 0 !important;
790 }
791 /*
792 * Admin bar is hidden but body.admin-bar often still gets top padding,
793 * shifting #wpbody down while the fixed plugin sidebar stays at y=0 — header borders misalign.
794 */
795 body.easy-invoice-admin-body.admin-bar {
796 padding-top: 0 !important;
797 }
798 body.easy-invoice-admin-body.admin-bar #wpbody,
799 body.easy-invoice-admin-body.admin-bar #wpbody-content {
800 padding-top: 0 !important;
801 }
802 </style>
803 <?php
804 }
805 }
806 }
807