modules = $modules; $this->assets = new Assets( $this ); } /** * Register hooks. * * @return void */ public function register() { add_action( 'admin_menu', array( $this, 'add_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this->assets, 'enqueue' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'menu_badge_style' ) ); add_action( 'wp_ajax_noorsmtp_render_tab', array( $this, 'ajax_render_tab' ) ); add_filter( 'plugin_action_links_' . NOORSMTP_PLUGIN_BASENAME, array( $this, 'action_links' ) ); } /** * Register the top-level admin menu and its submenus. * * @return void */ public function add_menu() { $slug = noorsmtp_menu_slug(); $capability = noorsmtp_manage_capability(); if ( ! noorsmtp_menu_registered() ) { add_menu_page( __( 'NoorSMTP', 'noorsmtp' ), __( 'NoorSMTP', 'noorsmtp' ), $capability, $slug, array( $this, 'render_app' ), 'dashicons-email-alt', 80 ); } $this->add_safe_submenu_page( $slug, __( 'Settings', 'noorsmtp' ), __( 'Settings', 'noorsmtp' ), $capability, $slug, array( $this, 'render_app' ) ); $this->add_safe_submenu_page( $slug, __( 'Test Email', 'noorsmtp' ), __( 'Test Email', 'noorsmtp' ), $capability, 'noorsmtp-test', array( $this, 'render_app' ) ); $this->add_safe_submenu_page( $slug, __( 'Email Log', 'noorsmtp' ), __( 'Email Log', 'noorsmtp' ), $capability, 'noorsmtp-log', array( $this, 'render_app' ) ); /** * Fires after the core submenu pages are registered so extensions * (e.g. NoorSMTP Pro) can add their own pages under the same menu. * * @since 1.2.0 * @param string $slug Top-level menu slug. */ do_action( 'noorsmtp_register_submenus', $slug ); if ( ! noorsmtp_is_pro() || ! noorsmtp_license_active() ) { $this->add_safe_submenu_page( $slug, __( 'Upgrade to Pro', 'noorsmtp' ), '★ ' . __( 'Upgrade to Pro', 'noorsmtp' ), $capability, 'noorsmtp-upgrade', array( $this, 'render_app' ) ); } } /** * Safely add a submenu page without risking duplicates. * * @param string $parent_slug Parent slug. * @param string $page_title Page title. * @param string $menu_title Menu title. * @param string $capability Capability. * @param string $menu_slug Menu slug. * @param callable $callback Optional callback. * @param int $position Optional position. * @return string|false */ public function add_safe_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $callback = null, $position = null ) { global $submenu; if ( isset( $submenu[ $parent_slug ] ) && is_array( $submenu[ $parent_slug ] ) ) { foreach ( $submenu[ $parent_slug ] as $item ) { if ( isset( $item[2] ) && $item[2] === $menu_slug ) { return false; } } } return add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $callback, $position ); } /** * Colour the "Upgrade Pro ★" submenu label without raw inline script/style tags in HTML. * * @return void */ public function menu_badge_style() { $css = '#adminmenu .toplevel_page_noorsmtp ul.wp-submenu a[href*="page=noorsmtp-upgrade"] {' . 'color: #10b981 !important;' . 'font-weight: 600 !important;' . '}' . '#adminmenu .toplevel_page_noorsmtp ul.wp-submenu a[href*="page=noorsmtp-upgrade"]:hover {' . 'color: #059669 !important;' . '}'; wp_add_inline_style( 'common', $css ); } /** * Plugin action links on plugins.php. * * @param array $links Existing action links. * @return array */ public function action_links( $links ) { $extra = array( '' . esc_html__( 'Settings', 'noorsmtp' ) . '', ); array_unshift( $links, ...$extra ); return $links; } /** * Current screen ID for the active request. * * @return string */ public function current_screen_id() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : self::MENU_SLUG; return $page ? $page : self::MENU_SLUG; } /** * Whether the current admin request is a NoorSMTP screen. * * @return bool */ public function is_noorsmtp_screen() { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; return $screen && false !== strpos( (string) $screen->id, 'noorsmtp' ); } /** * Navigation groups for the sidebar. * * @return array */ public function nav_groups() { global $submenu; $slug = noorsmtp_menu_slug(); $items = isset( $submenu[ $slug ] ) && is_array( $submenu[ $slug ] ) ? $submenu[ $slug ] : array(); $groups = array( 'config' => array( 'label' => __( 'Configuration', 'noorsmtp' ), 'items' => array(), ), 'tools' => array( 'label' => __( 'Tools & Logs', 'noorsmtp' ), 'items' => array(), ), 'pro' => array( 'label' => __( 'Pro Features', 'noorsmtp' ), 'items' => array(), ), ); $icons = array( self::MENU_SLUG => 'dashicons-admin-generic', 'noorsmtp-test' => 'dashicons-email', 'noorsmtp-log' => 'dashicons-list-view', 'noorsmtp-pro-features' => 'dashicons-star-filled', 'noorsmtp-routing' => 'dashicons-randomize', 'noorsmtp-analytics' => 'dashicons-chart-bar', 'noorsmtp-dns' => 'dashicons-shield', 'noorsmtp-suppression' => 'dashicons-shield-alt', 'noorsmtp-license' => 'dashicons-admin-network', 'noorsmtp-upgrade' => 'dashicons-star-empty', ); foreach ( $items as $item ) { $item_slug = $item[2]; $item_title = wp_strip_all_tags( $item[0] ); $item_icon = isset( $icons[ $item_slug ] ) ? $icons[ $item_slug ] : 'dashicons-admin-generic'; $entry = array( 'slug' => $item_slug, 'title' => $item_title, 'menu_html' => $item[0], 'icon' => $item_icon, ); if ( self::MENU_SLUG === $item_slug ) { $groups['config']['items'][] = $entry; } elseif ( in_array( $item_slug, array( 'noorsmtp-test', 'noorsmtp-log' ), true ) ) { $groups['tools']['items'][] = $entry; } elseif ( in_array( $item_slug, array( 'noorsmtp-pro-features', 'noorsmtp-routing', 'noorsmtp-analytics', 'noorsmtp-dns', 'noorsmtp-suppression', 'noorsmtp-license' ), true ) ) { $groups['pro']['items'][] = $entry; } } return array_filter( $groups, static function ( $group ) { return ! empty( $group['items'] ); } ); } /** * Render the unified application shell. * * @return void */ public function render_app() { if ( ! noorsmtp_user_can_manage() ) { wp_die( esc_html__( 'You do not have permission to access NoorSMTP.', 'noorsmtp' ), 403 ); } $current = $this->current_screen_id(); $groups = $this->nav_groups(); $body = $this->render_screen_body( $current ); include NOORSMTP_PATH . 'src/Admin/views/page-header.php'; // Body is escaped within its respective view templates. echo $body; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped include NOORSMTP_PATH . 'src/Admin/views/page-footer.php'; } /** * Render the inner body for a screen. * * @param string $screen_slug Screen slug. * @return string */ public function render_screen_body( $screen_slug ) { $settings = $this->get_settings(); $modules = $this->modules->all(); ob_start(); switch ( $screen_slug ) { case 'noorsmtp-test': include NOORSMTP_PATH . 'src/Admin/views/test-page.php'; break; case 'noorsmtp-log': include NOORSMTP_PATH . 'src/Admin/views/log-page.php'; break; case 'noorsmtp-upgrade': include NOORSMTP_PATH . 'src/Admin/views/upgrade-panel.php'; break; case self::MENU_SLUG: default: /** * Allow custom screen rendering (e.g. Pro Features / Routing / Analytics / License from Pro). */ $custom_rendered = apply_filters( 'noorsmtp_render_screen_body', false, $screen_slug, $settings ); if ( ! $custom_rendered ) { include NOORSMTP_PATH . 'src/Admin/views/settings-page.php'; } break; } return (string) ob_get_clean(); } /** * AJAX handler for fast tab switching. * * @return void */ public function ajax_render_tab() { check_ajax_referer( 'noorsmtp_admin', 'nonce' ); if ( ! noorsmtp_user_can_manage() ) { wp_send_json_error( array( 'message' => __( 'You do not have permission to do this.', 'noorsmtp' ) ), 403 ); } $slug = isset( $_POST['slug'] ) ? sanitize_key( wp_unslash( $_POST['slug'] ) ) : self::MENU_SLUG; $html = $this->render_screen_body( $slug ); wp_send_json_success( array( 'slug' => $slug, 'html' => $html, ) ); } /** * Get the current settings, merged with defaults. * * @return array */ public function get_settings() { $defaults = array( 'mailer' => '', 'from_email' => get_option( 'admin_email' ), 'from_name' => get_bloginfo( 'name' ), 'force_from' => false, 'logging_enabled' => true, 'delete_data_on_uninstall' => false, ); foreach ( $this->modules->all() as $module ) { foreach ( $module->settings_fields() as $key => $field ) { $defaults[ $key ] = isset( $field['default'] ) ? $field['default'] : ''; } } $stored = get_option( 'noorsmtp_settings', array() ); return wp_parse_args( $stored, $defaults ); } /** * Module manager accessor. * * @return ModuleManager */ public function modules() { return $this->modules; } /** * Backwards compatibility render header helper. * * @param string $current_slug Slug. * @return void */ public static function render_header( $current_slug = 'noorsmtp' ) { // Handled centrally in render_app(). unset( $current_slug ); } /** * Backwards compatibility render footer helper. * * @param string $current_slug Slug. * @return void */ public static function render_footer( $current_slug = 'noorsmtp' ) { // Handled centrally in render_app(). unset( $current_slug ); } }