0 ? ' ' . number_format_i18n( $counts['problems'] ) . '' : ''; $dashboard_hook = add_menu_page( __( '404Zen — Link Health', '404zen-broken-link-fixer' ), __( '404Zen', '404zen-broken-link-fixer' ) . $badge, self::CAPABILITY, 'lgbf-404zen', array( __CLASS__, 'render_dashboard' ), 'dashicons-editor-unlink', 80 ); add_submenu_page( 'lgbf-404zen', __( 'Dashboard', '404zen-broken-link-fixer' ), __( 'Dashboard', '404zen-broken-link-fixer' ), self::CAPABILITY, 'lgbf-404zen', array( __CLASS__, 'render_dashboard' ) ); $settings_hook = add_submenu_page( 'lgbf-404zen', __( '404Zen Settings', '404zen-broken-link-fixer' ), __( 'Settings', '404zen-broken-link-fixer' ), self::CAPABILITY, 'lgbf-404zen-settings', array( __CLASS__, 'render_settings' ) ); $pricing_hook = add_submenu_page( 'lgbf-404zen', __( '404Zen Pro', '404zen-broken-link-fixer' ), __( 'Upgrade', '404zen-broken-link-fixer' ), self::CAPABILITY, 'lgbf-404zen-upgrade', array( __CLASS__, 'render_pricing' ) ); $contact_hook = add_submenu_page( 'lgbf-404zen', __( 'Contact', '404zen-broken-link-fixer' ), __( 'Contact', '404zen-broken-link-fixer' ), self::CAPABILITY, '404zen-support', array( __CLASS__, 'render_contact' ) ); add_action( "load-{$contact_hook}", array( __CLASS__, 'prepare_zen_page' ) ); add_action( "load-{$dashboard_hook}", array( __CLASS__, 'add_screen_options' ) ); foreach ( array( $dashboard_hook, $settings_hook, $pricing_hook ) as $hook ) { add_action( "load-{$hook}", array( __CLASS__, 'prepare_zen_page' ) ); } } /** * Full-bleed treatment: our pages own their canvas. Third-party admin * notices are removed on 404Zen screens only (404Zen itself never * adds admin-wide notices anywhere). */ public static function prepare_zen_page() { remove_all_actions( 'admin_notices' ); remove_all_actions( 'all_admin_notices' ); add_filter( 'admin_body_class', static function ( $classes ) { return $classes . ' lgbf-zen'; } ); } /** * Screen options (rows per page). */ public static function add_screen_options() { add_screen_option( 'per_page', array( 'label' => __( 'Links per page', '404zen-broken-link-fixer' ), 'default' => 25, 'option' => 'lgbf_links_per_page', ) ); } /** * Persists the per-page screen option. * * @param mixed $status Default. * @param string $option Option name. * @param mixed $value Submitted value. * @return mixed */ public static function save_screen_option( $status, $option, $value ) { if ( 'lgbf_links_per_page' === $option ) { return max( 1, min( 200, absint( $value ) ) ); } return $status; } /** * Registers the settings option. */ public static function register_settings() { register_setting( 'lgbf_settings_group', LGBF_Settings::OPTION_KEY, array( 'type' => 'array', 'sanitize_callback' => array( 'LGBF_Settings', 'sanitize' ), 'default' => LGBF_Settings::defaults(), ) ); } /** * Enqueues admin assets on 404Zen pages only. * * @param string $hook_suffix Current admin page. */ public static function enqueue_assets( $hook_suffix ) { if ( false === strpos( (string) $hook_suffix, 'lgbf-404zen' ) ) { return; } wp_enqueue_style( 'zen-admin', LGBF_PLUGIN_URL . 'assets/css/zen-admin.css', array(), LGBF_VERSION ); wp_enqueue_style( 'lgbf-admin', LGBF_PLUGIN_URL . 'assets/css/admin.css', array( 'zen-admin' ), LGBF_VERSION ); wp_enqueue_script( 'lgbf-admin', LGBF_PLUGIN_URL . 'assets/js/admin.js', array( 'jquery' ), LGBF_VERSION, true ); wp_localize_script( 'lgbf-admin', 'lgbfAdmin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'lgbf_admin_nonce' ), 'i18n' => array( 'confirmUnlink' => __( 'Remove this link but keep its text?', '404zen-broken-link-fixer' ), 'dismissed' => __( 'Link dismissed — it will be ignored in future scans.', '404zen-broken-link-fixer' ), 'undo' => __( 'Undo', '404zen-broken-link-fixer' ), 'working' => __( 'Working…', '404zen-broken-link-fixer' ), 'error' => __( 'Something went wrong. Please try again.', '404zen-broken-link-fixer' ), ), ) ); } /** * "Scan now" button handler. */ public static function handle_scan_now() { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You are not allowed to do that.', '404zen-broken-link-fixer' ) ); } check_admin_referer( 'lgbf_scan_now' ); LGBF_Scanner::request_immediate_scan(); spawn_cron(); wp_safe_redirect( add_query_arg( 'lgbf_notice', 'scan_started', admin_url( 'admin.php?page=lgbf-404zen' ) ) ); exit; } /** * Processes list-table bulk actions before the page renders. */ public static function handle_bulk_actions() { if ( ! current_user_can( self::CAPABILITY ) ) { return; } $action = ''; foreach ( array( 'action', 'action2' ) as $key ) { if ( isset( $_REQUEST[ $key ] ) && '-1' !== $_REQUEST[ $key ] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = sanitize_key( wp_unslash( $_REQUEST[ $key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended break; } } if ( ! $action || ! in_array( $action, array( 'recheck', 'dismiss', 'unlink' ), true ) ) { return; } check_admin_referer( 'bulk-lgbf_links' ); $ids = isset( $_REQUEST['link_ids'] ) ? array_map( 'absint', (array) $_REQUEST['link_ids'] ) : array(); $ids = array_slice( array_filter( $ids ), 0, 100 ); if ( empty( $ids ) ) { return; } $done = 0; foreach ( $ids as $id ) { switch ( $action ) { case 'recheck': $result = LGBF_Fixer::recheck( $id ); break; case 'dismiss': $result = LGBF_Fixer::dismiss( $id ); break; case 'unlink': $result = LGBF_Fixer::unlink( $id ); break; default: $result = null; } if ( true === $result || is_array( $result ) ) { $done++; } } wp_safe_redirect( add_query_arg( array( 'page' => 'lgbf-404zen', 'lgbf_notice' => 'bulk_done', 'lgbf_count' => $done, ), admin_url( 'admin.php' ) ) ); exit; } /** * The Zen Plugins top bar (lockup, tabs, docs, Go Pro). * * @param string $active dashboard|settings. */ private static function render_topbar( $active ) { $docs_base = apply_filters( 'lgbf_docs_base', 'https://404zen.com/docs/' ); ?>
404Zen
prepare_items(); $notice = isset( $_GET['lgbf_notice'] ) ? sanitize_key( wp_unslash( $_GET['lgbf_notice'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>

views(); $table->search_box( __( 'Search URLs', '404zen-broken-link-fixer' ), 'lgbf-search' ); $table->display(); ?>
$prev, 'to' => $grade ), false ); } if ( $prev !== $grade ) { update_option( 'lgbf_last_grade', $grade, false ); } $stored = get_option( 'lgbf_grade_improvement' ); if ( is_array( $stored ) && isset( $stored['to'] ) && $stored['to'] === $grade && ! in_array( 'review', self::hidden_tasks(), true ) ) { $improvement = $stored; } } ?>

⏱

isset( $breakdown['broken']['link'] ) ? $breakdown['broken']['link'] : 0, 'label' => __( 'Broken links', '404zen-broken-link-fixer' ), 'sev' => 'red', 'view' => add_query_arg( array( 'link_status' => 'broken', 'link_type' => 'link' ), $base ), ), array( 'count' => isset( $breakdown['broken']['image'] ) ? $breakdown['broken']['image'] : 0, 'label' => __( 'Broken images', '404zen-broken-link-fixer' ), 'sev' => 'red', 'view' => add_query_arg( array( 'link_status' => 'broken', 'link_type' => 'image' ), $base ), ), array( 'count' => $counts['timeout'], 'label' => __( 'Timeouts', '404zen-broken-link-fixer' ), 'sev' => 'red', 'view' => add_query_arg( 'link_status', 'timeout', $base ), ), array( 'count' => $counts['redirect'], 'label' => __( 'Redirect chains', '404zen-broken-link-fixer' ), 'sev' => 'amber', 'view' => add_query_arg( 'link_status', 'redirect', $base ), ), ); $hidden = self::hidden_tasks(); $tasks = array(); if ( false && ! LGBF_Settings::get( 'digest_enabled' ) && ! in_array( 'digest', $hidden, true ) ) { // folded into the checklist card $tasks[] = array( 'key' => 'digest', 'label' => __( 'Enable the weekly email digest so problems reach your inbox', '404zen-broken-link-fixer' ), 'url' => admin_url( 'admin.php?page=lgbf-404zen-settings' ), 'cta' => __( 'Enable', '404zen-broken-link-fixer' ), ); } if ( false && ! lgbf_can_use_premium() && ! in_array( 'schedule', $hidden, true ) ) { // folded into the checklist card $tasks[] = array( 'key' => 'schedule', 'label' => __( 'Set a custom scan schedule — hourly to weekly (Pro)', '404zen-broken-link-fixer' ), 'url' => self::upgrade_url(), 'cta' => __( 'Go Pro', '404zen-broken-link-fixer' ), ); } $has_issues = false; foreach ( $groups as $group ) { if ( $group['count'] > 0 ) { $has_issues = true; break; } } ?>

$scanned, 'label' => __( 'First scan completed', '404zen-broken-link-fixer' ), 'sev' => 'task' ), array( 'done' => $scanned && 0 === $problems, 'label' => __( 'Fix all broken links', '404zen-broken-link-fixer' ), 'sev' => 'red', 'count' => $problems, 'url' => add_query_arg( 'link_status', 'broken', $base ), 'cta' => __( 'View', '404zen-broken-link-fixer' ) ), array( 'done' => $scanned && 0 === $counts['redirect'], 'label' => __( 'Resolve redirect chains', '404zen-broken-link-fixer' ), 'sev' => 'amber', 'count' => $counts['redirect'], 'url' => add_query_arg( 'link_status', 'redirect', $base ), 'cta' => __( 'View', '404zen-broken-link-fixer' ) ), array( 'done' => (bool) LGBF_Settings::get( 'digest_enabled' ), 'label' => __( 'Enable the weekly digest', '404zen-broken-link-fixer' ), 'sev' => 'task', 'url' => admin_url( 'admin.php?page=lgbf-404zen-settings' ), 'cta' => __( 'Enable', '404zen-broken-link-fixer' ) ), array( 'done' => $is_https, 'label' => $is_https ? __( 'Your site is served over HTTPS', '404zen-broken-link-fixer' ) : __( 'Serve your site over HTTPS', '404zen-broken-link-fixer' ), 'sev' => 'red', 'key' => 'https' ), array( 'done' => lgbf_can_use_premium(), 'label' => __( 'Set a scan schedule (Pro)', '404zen-broken-link-fixer' ), 'sev' => 'task', 'key' => 'schedule', 'url' => self::upgrade_url(), 'cta' => __( 'Go Pro', '404zen-broken-link-fixer' ), 'dismiss' => true ), array( 'done' => LGBF_Optin::has_optin(), 'label' => __( 'Subscribe to link-health updates', '404zen-broken-link-fixer' ), 'sev' => 'task', 'key' => 'newsletter', 'url' => '#', 'cta' => '', 'dismiss' => true ), ); $rows = array(); foreach ( $items as $item ) { if ( ! empty( $item['key'] ) && ! empty( $item['dismiss'] ) && in_array( $item['key'], $hidden, true ) ) { continue; // dismissed → out of the denominator entirely. } $rows[] = $item; } $total = count( $rows ); $done = count( array_filter( $rows, static function ( $r ) { return $r['done']; } ) ); $pct = $total ? round( 100 * $done / $total ) : 100; ?>

0 ) : ?>

' . esc_html__( 'Active', '404zen-broken-link-fixer' ) . ''; } elseif ( file_exists( WP_PLUGIN_DIR . '/' . $sslzen_file ) ) { $activate_url = wp_nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=' . $sslzen_file ), 'activate-plugin_' . $sslzen_file ); $state = '' . esc_html__( 'Activate', '404zen-broken-link-fixer' ) . ''; } else { $install_url = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=ssl-zen' ), 'install-plugin_ssl-zen' ); $state = '' . esc_html__( 'Install', '404zen-broken-link-fixer' ) . ''; } ?>

4.9

__( 'Why broken links hurt SEO', '404zen-broken-link-fixer' ), '#redirects' => __( 'How redirects leak link equity', '404zen-broken-link-fixer' ), '#grade' => __( 'Reading your Link Health grade', '404zen-broken-link-fixer' ), '#shared-hosting' => __( 'Tuning scans for shared hosting', '404zen-broken-link-fixer' ), ); ?>

true ), 'objects' ); unset( $post_types['attachment'] ); ?>

support@zenplugins.org

72416, 'name' => __( '1 Site', '404zen-broken-link-fixer' ), 'price' => 49, 'lifetime' => 129, 'popular' => false, 'features' => array( __( 'Scheduled scans — hourly to weekly', '404zen-broken-link-fixer' ), __( 'One-click auto-redirects for 404s', '404zen-broken-link-fixer' ), __( 'CSV export', '404zen-broken-link-fixer' ) ) ), array( 'pricing_id' => 72422, 'name' => __( '5 Sites', '404zen-broken-link-fixer' ), 'price' => 99, 'lifetime' => 249, 'popular' => true, 'features' => array( __( 'Everything in 1 Site', '404zen-broken-link-fixer' ), __( 'Use on up to 5 sites', '404zen-broken-link-fixer' ), __( 'Priority email support', '404zen-broken-link-fixer' ) ) ), array( 'pricing_id' => 72435, 'name' => __( '25 Sites', '404zen-broken-link-fixer' ), 'price' => 199, 'lifetime' => 499, 'popular' => false, 'features' => array( __( 'Everything in 5 Sites', '404zen-broken-link-fixer' ), __( 'Use on up to 25 sites', '404zen-broken-link-fixer' ), __( 'Multisite support', '404zen-broken-link-fixer' ) ) ), ); ?>

0 ) { /* translators: 1: grade letter, 2: number of broken links. */ echo esc_html( sprintf( _n( 'Your site: grade %1$s — %2$d broken link is leaking visitors and rankings right now.', 'Your site: grade %1$s — %2$d broken links are leaking visitors and rankings right now.', $problems, '404zen-broken-link-fixer' ), $grade, $problems ) ); } else { /* translators: %s: grade letter. */ echo esc_html( sprintf( __( 'Your links are clean today — grade %s. Pro keeps them that way, automatically.', '404zen-broken-link-fixer' ), $grade ) ); } ?>

0 ) { esc_html_e( 'Pro repairs them in one click and auto-redirects every dead URL — so you never lose another visitor.', '404zen-broken-link-fixer' ); } else { esc_html_e( 'Scheduled scans catch new breaks and auto-redirect them before a visitor ever hits a dead end.', '404zen-broken-link-fixer' ); } ?>

⚡

$

🛡

'; } ?>
404Zen
' . esc_html( $r[0] ) . '✓' . esc_html( $r[1] ) . '