invoice_controller = new InvoiceController(); $this->quote_controller = new QuoteController(); $this->client_controller = new ClientController(); $this->settings_controller = new SettingsController(); $this->report_controller = new ReportController(); $this->dashboard_controller = new DashboardController(); $this->payment_controller = new PaymentController(); // Initialize Template Builder controller if Pro is active AND // the custom_templates addon is enabled. The addon owns this // feature — see addons/CustomTemplates/addon.php in the Pro // plugin. Constructing the controller here always-on would // register its admin_menu hook regardless of addon state. if ( easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('custom_templates') && class_exists('\EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController') ) { $this->template_builder_controller = new \EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController(); } } /** * Initialize the admin class */ public function init() { // Add menu items add_action('admin_menu', array($this, 'registerMenuPages')); // Register admin assets $admin_assets = new AdminAssets(); $admin_assets->register(); // Register invoice preview page add_action('admin_init', array($this, 'registerPreviewPage')); // Ensure proper page titles to avoid WordPress core issues add_filter('admin_title', array($this, 'setAdminTitle')); // Hide admin UI add_action('admin_head', array($this, 'hideAdminUi')); // Body class for layout fixes (admin bar offset, header strip alignment) add_filter('admin_body_class', array($this, 'adminBodyClass')); // Initialize controllers $this->invoice_controller->init(); $this->quote_controller->init(); $this->client_controller->init(); $this->settings_controller->init(); $this->report_controller->init(); $this->dashboard_controller->init(); $this->payment_controller->init(); // Main content hook add_action('easy_invoice_admin_main_content', array($this, 'mainPageContent')); } /** * Register admin menu pages */ public function registerMenuPages() { // Main menu item add_menu_page( __('Easy Invoice', 'easy-invoice'), __('Easy Invoice', 'easy-invoice'), 'manage_options', PagesSlugs::DASHBOARD, array($this, 'displayMainPage'), 'dashicons-media-text', 25 ); // Dashboard submenu add_submenu_page( 'easy-invoice', __('Dashboard', 'easy-invoice'), __('Dashboard', 'easy-invoice'), 'manage_options', PagesSlugs::DASHBOARD, array($this, 'displayMainPage') ); // All invoices submenu add_submenu_page( 'easy-invoice', __('All Invoices', 'easy-invoice'), __('All Invoices', 'easy-invoice'), 'manage_options', PagesSlugs::ALL_INVOICES, array($this, 'displayMainPage') ); // Add new invoice submenu add_submenu_page( 'easy-invoice-hidden', __('Add New Invoice', 'easy-invoice'), __('Add New', 'easy-invoice'), 'manage_options', PagesSlugs::INVOICE_NEW, array($this, 'displayMainPage') ); // All quotes submenu add_submenu_page( 'easy-invoice', __('All Quotes', 'easy-invoice'), __('All Quotes', 'easy-invoice'), 'manage_options', PagesSlugs::ALL_QUOTES, array($this, 'displayMainPage') ); // Add new quote submenu add_submenu_page( 'easy-invoice-hidden', __('Add New Quote', 'easy-invoice'), __('Add New Quote', 'easy-invoice'), 'manage_options', PagesSlugs::QUOTE_NEW, array($this, 'displayMainPage') ); // Payments submenu add_submenu_page( 'easy-invoice-hidden', __('Payments', 'easy-invoice'), __('Payments', 'easy-invoice'), 'manage_options', PagesSlugs::PAYMENTS, array($this, 'displayMainPage') ); // Add new payment submenu add_submenu_page( 'easy-invoice-hidden', __('Add New Payment', 'easy-invoice'), __('Add New Payment', 'easy-invoice'), 'manage_options', PagesSlugs::PAYMENT_NEW, array($this, 'displayMainPage') ); // Clients submenu add_submenu_page( 'easy-invoice', __('All Clients', 'easy-invoice'), __('All Clients', 'easy-invoice'), 'edit_posts', PagesSlugs::CLIENTS, array($this, 'displayMainPage') ); // Item Library + Template Builder pages are reached via the // Easy Invoice in-app sidebar (templates/main-template.php), NOT // the WP admin submenu. We still register the page slugs so the // URLs resolve, but we parent them under 'easy-invoice-hidden' // so they don't render duplicate entries in the WP submenu — // the WP submenu showed the link AND the in-app sidebar showed // the same link, giving the user two visible navigation // entry-points to the same page. // // Registration is still gated on the addon being active — if // the addon is off, the slug isn't registered, so direct-URL // access returns the standard "you do not have sufficient // permissions" page. if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library')) { add_submenu_page( 'easy-invoice-hidden', __('Item Library', 'easy-invoice'), __('Item Library', 'easy-invoice'), 'manage_options', 'easy-invoice-pro-item-library', array($this, 'displayMainPage') ); } if (easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('custom_templates')) { // Template listing — reachable from the in-app sidebar. add_submenu_page( 'easy-invoice-hidden', __('Template Builder', 'easy-invoice'), __('Template Builder', 'easy-invoice'), 'manage_options', 'easy-invoice-templates', array($this, 'displayMainPage') ); // Create New Template — used by deep-links from inside the // builder UI. add_submenu_page( 'easy-invoice-hidden', __('Create New Template', 'easy-invoice'), __('Create New', 'easy-invoice'), 'manage_options', 'easy-invoice-templates-new', array($this, 'displayMainPage') ); // Template Builder canvas — full-screen builder, reached // from the listing page's Edit / New buttons. add_submenu_page( 'easy-invoice-hidden', __('Template Builder Page', 'easy-invoice'), __('Template Builder Page', 'easy-invoice'), 'manage_options', 'easy-invoice-template-builder', array($this, 'displayMainPage') ); } // Hidden submenu pages - not shown in the menu but accessible add_submenu_page( 'easy-invoice-hidden', // Use main menu as parent to avoid title issues __('Edit Client', 'easy-invoice'), __('Edit Client', 'easy-invoice'), 'manage_options', PagesSlugs::CLIENT_EDIT, array($this, 'displayMainPage') ); add_submenu_page( 'easy-invoice-hidden', // Use main menu as parent to avoid title issues __('View Client', 'easy-invoice'), __('View Client', 'easy-invoice'), 'manage_options', PagesSlugs::CLIENT_VIEW, array($this, 'displayMainPage') ); add_submenu_page( 'easy-invoice-hidden', // Use main menu as parent to avoid title issues __('Preview Invoice', 'easy-invoice'), __('Preview Invoice', 'easy-invoice'), 'manage_options', PagesSlugs::INVOICE_PREVIEW, array($this, 'displayPreviewPage') ); add_submenu_page( 'easy-invoice-hidden', // Use main menu as parent to avoid title issues __('Preview Quote', 'easy-invoice'), __('Preview Quote', 'easy-invoice'), 'manage_options', PagesSlugs::QUOTE_PREVIEW, array($this, 'displayMainPage') ); // Reports — addon-gated (Personal tier). Slug registered as a // HIDDEN submenu so the WP admin sidebar doesn't double the // in-app sidebar entry. When the reports addon is disabled, the // slug isn't registered at all, so direct-URL access falls // through to WP's "no permission" page. if (\EasyInvoice\Addons\AddonManager::shouldLoad('reports')) { add_submenu_page( 'easy-invoice-hidden', __('Reports', 'easy-invoice'), __('Reports', 'easy-invoice'), 'manage_options', PagesSlugs::REPORTS, array($this, 'displayMainPage') ); } // Settings submenu add_submenu_page( 'easy-invoice', __('Settings', 'easy-invoice'), __('Settings', 'easy-invoice'), 'manage_options', PagesSlugs::SETTINGS, array($this, 'displayMainPage') ); // Addons submenu add_submenu_page( 'easy-invoice', __('Addons', 'easy-invoice'), __('Addons', 'easy-invoice'), 'manage_options', PagesSlugs::ADDONS, array($this, 'displayMainPage') ); // License submenu add_submenu_page( 'easy-invoice', __('License', 'easy-invoice'), __('License', 'easy-invoice'), 'manage_options', PagesSlugs::LICENSE, array($this, 'displayMainPage') ); // Free vs Pro submenu - only show in free version if (!easy_invoice_has_pro()) { add_submenu_page( 'easy-invoice', __('Free vs Pro', 'easy-invoice'), __('Free vs Pro', 'easy-invoice'), 'manage_options', 'easy-invoice-free-vs-pro', array($this, 'displayMainPage') ); } // Join Community submenu add_submenu_page( 'easy-invoice-hidden', __('Join Community', 'easy-invoice'), __('Join Community', 'easy-invoice'), 'read', 'easy-invoice-join-community', array($this, 'redirectToCommunity') ); } /** * Redirect to community page */ public function redirectToCommunity() { wp_redirect(esc_url_raw('https://www.facebook.com/groups/mantrabraincommunity')); exit; } /** * Register invoice preview page. * * Placeholder kept as an extension point — the preview-page hook used to * be wired here but is now routed through the per-document preview * controller. Left in place so themes/plugins relying on a no-op * `registerPreviewPage()` call don't fatal. */ public function registerPreviewPage() { // No-op: preview rendering is handled by the per-document controller. } /** * Display main page */ public function displayMainPage() { include EASY_INVOICE_PLUGIN_DIR . 'templates/main-template.php'; } /** * Display preview page */ public function displayPreviewPage() { $this->invoice_controller->display(['page' => PagesSlugs::INVOICE_PREVIEW]); } /** * Display license page */ public function displayLicensePage() { include EASY_INVOICE_PLUGIN_DIR . 'templates/admin/license-page.php'; } /** * Display addons page */ public function displayAddonsPage() { if (class_exists('\\EasyInvoice\\Controllers\\AddonsController')) { (new \EasyInvoice\Controllers\AddonsController())->display(); } } /** * Handle main page content based on the current page * * @param string $page Current page */ public function mainPageContent($page) { switch ($page) { case PagesSlugs::DASHBOARD: $this->dashboard_controller->display(['page' => PagesSlugs::DASHBOARD]); break; case PagesSlugs::ALL_INVOICES: $this->invoice_controller->display(['page' => PagesSlugs::ALL_INVOICES]); break; case PagesSlugs::INVOICE_NEW: $this->invoice_controller->display(['page' => PagesSlugs::INVOICE_NEW]); break; case PagesSlugs::INVOICE_PREVIEW: $this->invoice_controller->display(['page' => PagesSlugs::INVOICE_PREVIEW]); break; case PagesSlugs::ALL_QUOTES: $this->quote_controller->display(['page' => PagesSlugs::ALL_QUOTES]); break; case PagesSlugs::QUOTE_NEW: $this->quote_controller->display(['page' => PagesSlugs::QUOTE_NEW]); break; case PagesSlugs::QUOTE_PREVIEW: $this->quote_controller->display(['page' => PagesSlugs::QUOTE_PREVIEW]); break; case PagesSlugs::PAYMENTS: if (isset($_GET['action'])) { $this->payment_controller->display(['page' => $_GET['action']]); } else { $this->payment_controller->display(['page' => PagesSlugs::PAYMENTS]); } break; case PagesSlugs::PAYMENT_NEW: $this->payment_controller->display(['page' => PagesSlugs::PAYMENT_NEW]); break; case PagesSlugs::CLIENTS: $this->client_controller->display(['page' => PagesSlugs::CLIENTS]); break; case PagesSlugs::CLIENT_EDIT: $this->client_controller->display(['page' => PagesSlugs::CLIENT_EDIT]); break; // Handle Pro features — gated on addon-state. The menu // registrations above are also addon-gated, so these cases // should only fire when the addon is enabled. Re-checking // shouldLoad() here is defensive in case the user hits the // page slug directly via URL. case 'easy-invoice-pro-item-library': if ( easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('item_library') && class_exists('\EasyInvoicePro\Addons\ItemLibrary\Controllers\ItemLibraryController') ) { $item_library_controller = new \EasyInvoicePro\Addons\ItemLibrary\Controllers\ItemLibraryController(); $item_library_controller->displayMainPage(); } break; case 'easy-invoice-templates': if ( easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('custom_templates') && class_exists('\EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController') ) { $template_builder_controller = new \EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController(); $template_builder_controller->displayMainPage(); } break; case 'easy-invoice-template-builder': if ( easy_invoice_has_pro() && \EasyInvoice\Addons\AddonManager::shouldLoad('custom_templates') && class_exists('\EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController') ) { $template_builder_controller = new \EasyInvoicePro\Addons\CustomTemplates\Controllers\TemplateBuilderController(); $template_builder_controller->displayMainPage(); } break; case PagesSlugs::CLIENT_VIEW: $this->client_controller->display(['page' => PagesSlugs::CLIENT_VIEW]); break; case PagesSlugs::REPORTS: // Reports is addon-gated. The submenu page slug is only // registered when shouldLoad('reports') is true, so this // case ordinarily only fires for active-addon users — // but recheck defensively in case the page is reached // via direct URL right after the addon was disabled. if (\EasyInvoice\Addons\AddonManager::shouldLoad('reports')) { $this->report_controller->display(['page' => PagesSlugs::REPORTS]); } break; case PagesSlugs::SETTINGS: $this->settings_controller->display(['page' => PagesSlugs::SETTINGS]); break; case PagesSlugs::LICENSE: $this->displayLicensePage(); break; case PagesSlugs::ADDONS: $this->displayAddonsPage(); break; case PagesSlugs::EMAIL_SETTINGS: case PagesSlugs::EMAIL_SETTINGS_GENERAL: case PagesSlugs::EMAIL_SETTINGS_INVOICE: case PagesSlugs::EMAIL_SETTINGS_QUOTE: case PagesSlugs::EMAIL_SETTINGS_PAYMENT: $this->settings_controller->display(['page' => PagesSlugs::SETTINGS, 'subsection' => $page]); break; case 'easy-invoice-migration': include EASY_INVOICE_PLUGIN_DIR . 'includes/Migration/templates/migration-page.php'; break; case 'easy-invoice-free-vs-pro': include EASY_INVOICE_PLUGIN_DIR . 'templates/admin/free-vs-pro-page.php'; break; default: // Addon pages render themselves via the // `easy_invoice_admin_main_content` action at priority 11. The // dispatcher must NOT fall through to the Dashboard for those // slugs, otherwise the Dashboard markup gets emitted first and // the addon panel renders on top of it (two pages stacked). // // We treat any slug that's either (a) prefixed with // `easy-invoice-addon-` or (b) registered in AddonRegistry as // an addon page — and just `break` to let the addon's own hook // handle the rendering. if (strpos($page, 'easy-invoice-addon-') === 0) { break; } if (class_exists('\\EasyInvoice\\Addons\\AddonRegistry')) { foreach (\EasyInvoice\Addons\AddonRegistry::all() as $_addon) { if (empty($_addon['settings_url'])) continue; $parsed = wp_parse_url($_addon['settings_url']); if (empty($parsed['query'])) continue; parse_str($parsed['query'], $q); if (($q['page'] ?? '') === $page) { return; // Addon owns this slug — bail without rendering. } } } $this->dashboard_controller->display(['page' => PagesSlugs::DASHBOARD]); break; } } /** * Set proper admin title to avoid WordPress core issues * * @param string $title The current admin title * @return string The modified admin title */ public function setAdminTitle($title) { $current_page = isset($_GET['page']) ? sanitize_text_field($_GET['page']) : ''; // Only modify titles for our plugin pages if (strpos($current_page, 'easy-invoice') === 0 || strpos($current_page, 'easy-quote') === 0) { $page_titles = [ 'easy-invoice' => __('Dashboard', 'easy-invoice'), 'easy-invoice-all' => __('All Invoices', 'easy-invoice'), 'easy-invoice-builder' => __('Add New Invoice', 'easy-invoice'), 'easy-invoice-preview' => __('Preview Invoice', 'easy-invoice'), 'easy-quote-all' => __('All Quotes', 'easy-invoice'), 'easy-invoice-quote-builder' => __('Add New Quote', 'easy-invoice'), 'easy-quote-preview' => __('Preview Quote', 'easy-invoice'), 'easy-invoice-payments' => __('Payments', 'easy-invoice'), 'easy-invoice-payment-new' => __('Add New Payment', 'easy-invoice'), 'easy-invoice-clients' => __('Clients', 'easy-invoice'), 'easy-invoice-client-edit' => __('Edit Client', 'easy-invoice'), 'easy-invoice-client-view' => __('View Client', 'easy-invoice'), 'easy-invoice-reports' => __('Reports', 'easy-invoice'), 'easy-invoice-settings' => __('Settings', 'easy-invoice'), 'easy-invoice-email-settings' => __('Email Settings', 'easy-invoice'), 'easy-invoice-migration' => __('Migration', 'easy-invoice'), 'easy-invoice-free-vs-pro' => __('Free vs Pro', 'easy-invoice'), 'easy-invoice-addons' => __('Addons', 'easy-invoice'), ]; if (isset($page_titles[$current_page])) { return $page_titles[$current_page] . ' - ' . get_bloginfo('name'); } } return $title; } /** * Body class for Easy Invoice full-width layout (used by hideAdminUi CSS). * * @param string $classes Space-separated classes. * @return string */ public function adminBodyClass($classes) { $screen = get_current_screen(); if (!$screen || !isset($screen->id)) { return $classes; } if (strpos($screen->id, 'easy-invoice') !== false || strpos($screen->id, 'easy-quote') !== false) { $classes .= ' easy-invoice-admin-body'; } return $classes; } /** * Hide the WordPress admin UI for our custom pages */ public function hideAdminUi() { $screen = get_current_screen(); if ($screen && property_exists($screen, 'id') && (strpos($screen->id, 'easy-invoice') !== false || strpos($screen->id, 'easy-quote') !== false)) { ?>