checklist = $checklist; $this->state = $state; $this->page = $page; } /** * Register hooks. * * @since 4.10.0 */ public function hooks(): void { $menu_hook = $this->get_menu_hook(); add_action( $menu_hook, [ $this, 'register' ], 11 ); add_action( $menu_hook, [ $this, 'reorder' ], PHP_INT_MAX ); add_action( 'admin_page_access_denied', [ $this, 'maybe_redirect_dismissed' ] ); add_action( 'admin_head', [ $this, 'print_styles' ] ); } /** * Send a dismissed checklist's page back to the plugin's main page. * * `reorder()` removes the submenu entry on dismissal, so core can no longer resolve * this page's parent and fires `admin_page_access_denied` before its own `wp_die()`. * * @since 4.10.0 */ public function maybe_redirect_dismissed(): void { if ( ! wp_mail_smtp()->get_admin()->is_admin_page( 'setup-checklist' ) || ! $this->state->is_dismissed() ) { return; } wp_safe_redirect( wp_mail_smtp()->get_admin()->get_admin_page_url( self::PARENT_SLUG ) ); exit; } /** * The menu hook the item is registered on: the admin the settings it walks the * user through live in. * * @since 4.10.0 * * @return string */ private function get_menu_hook(): string { return WP::use_global_plugin_settings() ? 'network_admin_menu' : 'admin_menu'; } /** * Register the submenu item and the page route. * * @since 4.10.0 */ public function register(): void { $capability = wp_mail_smtp()->get_capability_manage_options(); if ( ! current_user_can( $capability ) ) { return; } $title = $this->state->is_dismissed() ? esc_html__( 'Setup Checklist', 'wp-mail-smtp' ) : $this->get_menu_title(); add_submenu_page( self::PARENT_SLUG, esc_html__( 'WP Mail SMTP Setup Checklist', 'wp-mail-smtp' ), $title, $capability, Page::SLUG, [ $this->page, 'output' ] ); } /** * Position the item, or hide its link when the checklist is dismissed. * * @since 4.10.0 */ public function reorder(): void { if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { return; } if ( $this->state->is_dismissed() ) { remove_submenu_page( self::PARENT_SLUG, Page::SLUG ); return; } $this->move_before_dashboard(); } /** * Output the inline menu styles on every admin screen the menu shows on. * * @since 4.10.0 */ public function print_styles(): void { if ( $this->state->is_dismissed() || ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) { return; } $item = '#adminmenu .wp-submenu a.' . self::ITEM_CLASS; $styles = '#adminmenu .wp-submenu a.' . self::PARENT_LINK_CLASS . '{display:none;}' . $item . '{box-sizing:border-box;display:flex;flex-direction:column;justify-content:center;align-items:flex-start;gap:8px;padding:12px 12px 13px;border-bottom:1px solid #3C434A;}' . $item . ' .wpms-setup-checklist-menu-label{width:100%;font-weight:500;font-size:13px;line-height:16px;}' . $item . ' .wpms-setup-checklist-menu-progress{position:relative;width:100%;height:4px;background:#1D2327;border-radius:2px;overflow:hidden;}' . $item . ' .wpms-setup-checklist-menu-progress-bar{display:block;height:4px;min-width:4px;background:#00BA37;border-radius:2px;}'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped printf( '', $styles ); } /** * Build the menu label markup: the title plus the progress bar. * * @since 4.10.0 * * @return string */ private function get_menu_title(): string { $percent = (int) $this->checklist->get_progress()['percent']; $percent = max( 0, min( 100, $percent ) ); return sprintf( '%1$s' . '' . '' . '%3$s' . '', esc_html__( 'Setup Checklist', 'wp-mail-smtp' ), $percent, esc_html( sprintf( /* translators: %d: setup checklist completion percentage. */ __( '%d%% complete', 'wp-mail-smtp' ), $percent ) ) ); } /** * Place the checklist item right after the Dashboard in the submenu. * * @since 4.10.0 */ private function move_before_dashboard(): void { global $submenu; if ( empty( $submenu[ self::PARENT_SLUG ] ) ) { return; } $items = $submenu[ self::PARENT_SLUG ]; $ours = null; foreach ( $items as $key => $item ) { if ( isset( $item[2] ) && $item[2] === Page::SLUG ) { $ours = $item; unset( $items[ $key ] ); break; } } if ( $ours === null ) { return; } // The 5th element becomes a class on the menu link. $ours[4] = empty( $ours[4] ) ? self::ITEM_CLASS : $ours[4] . ' ' . self::ITEM_CLASS; $items = array_values( $items ); $position = 0; $dashboard = null; foreach ( $items as $key => $item ) { if ( isset( $item[2] ) && $item[2] === DashboardPage::SLUG ) { $position = $key; $dashboard = $item; break; } } array_splice( $items, $position, 0, [ $ours ] ); // WordPress builds the top-level menu's link from the first submenu item and // offers no filter for it, so the Dashboard is repeated there and hidden. That // keeps the link on the Dashboard while the checklist is listed above it. if ( $dashboard !== null ) { $dashboard[4] = empty( $dashboard[4] ) ? self::PARENT_LINK_CLASS : $dashboard[4] . ' ' . self::PARENT_LINK_CLASS; array_unshift( $items, $dashboard ); } // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited $submenu[ self::PARENT_SLUG ] = $items; } }