'array', 'sanitize_callback' => array( 'Velox_Settings', 'sanitize' ), ) ); } public static function menu() { add_menu_page( 'AngelForce Cache', 'AngelForce Cache', 'manage_options', self::SLUG, array( __CLASS__, 'render' ), 'dashicons-performance', 80 ); } public static function action_links( $links ) { array_unshift( $links, '' . esc_html__( 'Settings', 'angelforce-cache' ) . '' ); return $links; } public static function row_meta( $links, $file ) { if ( plugin_basename( VELOX_FILE ) === $file ) { $links[] = '' . esc_html__( 'Website', 'angelforce-cache' ) . ''; $links[] = '' . esc_html__( 'Support', 'angelforce-cache' ) . ''; } return $links; } public static function assets( $hook ) { if ( 'toplevel_page_' . self::SLUG !== $hook ) { return; } wp_enqueue_style( 'velox-admin', VELOX_URL . 'assets/admin.css', array(), VELOX_VERSION ); wp_enqueue_script( 'velox-admin', VELOX_URL . 'assets/admin.js', array(), VELOX_VERSION, true ); wp_localize_script( 'velox-admin', 'VELOX', array( 'ajax' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'velox_nonce' ), 'option' => Velox_Settings::OPTION, 'i18n' => array( 'purged' => __( 'Cache cleared.', 'angelforce-cache' ), 'cleaned' => __( 'Cleanup finished.', 'angelforce-cache' ), 'preload' => __( 'Preloading started in the background.', 'angelforce-cache' ), 'error' => __( 'Something went wrong. Please try again.', 'angelforce-cache' ), 'confirm' => __( 'This permanently deletes the selected data. Continue?', 'angelforce-cache' ), 'working' => __( 'Working…', 'angelforce-cache' ), /* translators: 1: images processed, 2: total images, 3: files converted, 4: space saved */ 'webp' => __( '%1$s of %2$s images processed, %3$s files converted, %4$s saved.', 'angelforce-cache' ), 'wzrisky' => __( 'Maximum removes unused CSS on every page. With page builders some pages can look unstyled for a moment until the full CSS has loaded. Apply anyway?', 'angelforce-cache' ), 'webpdone' => __( 'All images processed.', 'angelforce-cache' ), 'paused' => __( 'Paused. Click Start to resume.', 'angelforce-cache' ), 'saved' => __( 'Settings saved. Cache cleared.', 'angelforce-cache' ), 'thinking' => __( 'Analysing… this can take up to a minute.', 'angelforce-cache' ), 'apply' => __( 'Apply selected', 'angelforce-cache' ), 'applied' => __( 'Applied. Settings saved and cache cleared.', 'angelforce-cache' ), 'current' => __( 'current', 'angelforce-cache' ), 'warnings' => __( 'Test after applying', 'angelforce-cache' ), 'lists' => __( 'Exclusions / safelist to add', 'angelforce-cache' ), 'score' => __( 'Performance score', 'angelforce-cache' ), 'notplugin' => __( 'Needs theme, host or developer', 'angelforce-cache' ), 'nochange' => __( 'Nothing selected.', 'angelforce-cache' ), 'mobile' => __( 'Mobile', 'angelforce-cache' ), 'desktop' => __( 'Desktop', 'angelforce-cache' ), 'risk' => __( 'Risk', 'angelforce-cache' ), 'rlow' => __( 'low', 'angelforce-cache' ), 'rmedium' => __( 'medium', 'angelforce-cache' ), 'rhigh' => __( 'high', 'angelforce-cache' ), 'fixall' => __( 'Apply all (full fix)', 'angelforce-cache' ), 'fixstep' => __( 'Step by step (safe)', 'angelforce-cache' ), 'fixundo' => __( 'Undo AI changes', 'angelforce-cache' ), 'fixagain' => __( 'Run PageSpeed again', 'angelforce-cache' ), 'fixhint' => __( 'Step by step applies the safest change first. After every step the cache is cleared and your home page and latest post are checked; a step that breaks something is undone automatically.', 'angelforce-cache' ), 'stok' => __( 'applied, pages look fine', 'angelforce-cache' ), 'stskip' => __( 'already set', 'angelforce-cache' ), 'strev' => __( 'undone automatically:', 'angelforce-cache' ), 'stissue' => __( 'applied, but check:', 'angelforce-cache' ), 'stwait' => __( 'applying and checking…', 'angelforce-cache' ), /* translators: 1: changes applied, 2: changes undone, 3: changes already set */ 'fixdone' => __( 'Done: %1$s applied, %2$s undone, %3$s already set. Open your site in a browser and click through menus, sliders and forms, then run PageSpeed again to compare.', 'angelforce-cache' ), 'undone' => __( 'All AI changes were undone. Your previous settings are back.', 'angelforce-cache' ), 'before' => __( 'before', 'angelforce-cache' ), 'fixsel' => __( 'Only checked items are applied.', 'angelforce-cache' ), 'amain' => __( 'main address', 'angelforce-cache' ), 'seo' => __( 'SEO', 'angelforce-cache' ), 'a11y' => __( 'Accessibility', 'angelforce-cache' ), 'bp' => __( 'Best practices', 'angelforce-cache' ), 'wvelox' => __( 'AngelForce Cache can fix this', 'angelforce-cache' ), 'wyou' => __( 'You, in WordPress', 'angelforce-cache' ), 'wtheme' => __( 'Theme / developer', 'angelforce-cache' ), 'whost' => __( 'Hosting', 'angelforce-cache' ), 'altgo' => __( 'Write missing alt texts with AI', 'angelforce-cache' ), 'aok' => __( 'redirects to the main address', 'angelforce-cache' ), 'aother' => __( 'redirects somewhere else:', 'angelforce-cache' ), 'adup' => __( 'opens as a duplicate: switch on the redirect above', 'angelforce-cache' ), 'anodns' => __( 'not set up in DNS (fine)', 'angelforce-cache' ), 'anossl' => __( 'the SSL certificate does not cover this name: add it at your host', 'angelforce-cache' ), 'adown' => __( 'not reachable', 'angelforce-cache' ), 'aerror' => __( 'returns an error page', 'angelforce-cache' ), /* translators: %s: Cloudflare zone name */ 'cfok' => __( 'Connected to zone: %s', 'angelforce-cache' ), /* translators: 1: images written, 2: images skipped, 3: images remaining, 4: last image name */ 'alt' => __( '%1$s written, %2$s skipped, %3$s remaining. Last: “%4$s”', 'angelforce-cache' ), 'altdone' => __( 'All images have alt text now.', 'angelforce-cache' ), 'healthy' => __( 'Healthy', 'angelforce-cache' ), 'hwarn' => __( 'Check these', 'angelforce-cache' ), 'hprob' => __( 'Problem found', 'angelforce-cache' ), 'manual' => __( 'Test in your browser', 'angelforce-cache' ), 'generated' => __( 'Generated by AI from automatically extracted data. Review every item before applying; the site is never changed without your click.', 'angelforce-cache' ), ), ) ); } /* ---------- notices ---------- */ /** * On the AngelForce Cache screen the dashboard should start at the top, so notices printed by * other plugins (rating requests, upsells) are moved below the plugin's own screen instead of * above it. Nothing is removed or unhooked; they stay on every other admin screen as they are. */ public static function quiet_screen( $classes ) { $screen = get_current_screen(); if ( ! $screen || 'toplevel_page_' . self::SLUG !== $screen->id || ! apply_filters( 'velox_hide_foreign_notices', true ) ) { return $classes; } return $classes . ' vx-quiet'; } public static function notices() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( is_multisite() ) { $screen = get_current_screen(); if ( $screen && 'toplevel_page_' . self::SLUG === $screen->id ) { echo '

' . esc_html__( 'AngelForce Cache is designed for single-site installs; it has not been tested on multisite networks.', 'angelforce-cache' ) . '

'; } } // phpcs:ignore WordPress.Security.NonceVerification if ( isset( $_GET['velox_msg'] ) ) { $map = array( 'purged' => __( 'AngelForce Cache: cache cleared.', 'angelforce-cache' ), 'imported' => __( 'AngelForce Cache: settings imported.', 'angelforce-cache' ), 'reset' => __( 'AngelForce Cache: settings reset to defaults.', 'angelforce-cache' ), 'badfile' => __( 'AngelForce Cache: the import data was not valid JSON.', 'angelforce-cache' ), ); $k = sanitize_key( wp_unslash( $_GET['velox_msg'] ) ); // phpcs:ignore WordPress.Security.NonceVerification if ( isset( $map[ $k ] ) ) { printf( '

%s

', 'badfile' === $k ? 'error' : 'success', esc_html( $map[ $k ] ) ); } } $screen = get_current_screen(); if ( ! $screen || 'toplevel_page_' . self::SLUG !== $screen->id ) { return; } $redirect_error = get_transient( 'velox_redirect_error' ); if ( $redirect_error ) { delete_transient( 'velox_redirect_error' ); echo '

' . esc_html( 'AngelForce Cache: ' . $redirect_error ) . '

'; } if ( ! Velox_Cache::dropin_is_ours() && file_exists( Velox_Cache::dropin_path() ) ) { echo '

' . esc_html__( 'AngelForce Cache: another plugin already uses wp-content/advanced-cache.php. Disable the other caching plugin to enable the page cache.', 'angelforce-cache' ) . '

'; } if ( Velox_Settings::get( 'cache_enabled' ) && ! Velox_Cache::wp_cache_active() ) { echo '

' . esc_html__( "AngelForce Cache: WP_CACHE is not enabled. Add define( 'WP_CACHE', true ); to wp-config.php (the plugin could not do it automatically).", 'angelforce-cache' ) . '

'; } } /* ---------- admin bar ---------- */ public static function admin_bar( $bar ) { if ( ! current_user_can( 'manage_options' ) || ! Velox_Settings::get( 'adminbar' ) ) { return; } $bar->add_node( array( 'id' => 'velox', 'title' => ' AngelForce Cache', 'href' => admin_url( 'admin.php?page=' . self::SLUG ), ) ); $bar->add_node( array( 'id' => 'velox-purge-all', 'parent' => 'velox', 'title' => esc_html__( 'Clear all cache', 'angelforce-cache' ), 'href' => wp_nonce_url( admin_url( 'admin-post.php?action=velox_purge_all' ), 'velox_purge_all' ), ) ); if ( ! is_admin() ) { list( $ok, $why, $has_file ) = Velox_Cache::explain(); if ( $ok ) { $status = $has_file ? __( 'Visitors: served from cache', 'angelforce-cache' ) : __( 'Visitors: cacheable, not cached yet', 'angelforce-cache' ); $icon = '✓'; } else { $status = __( 'Visitors: not cached', 'angelforce-cache' ) . ' – ' . implode( ', ', $why ); $icon = '✗'; } $bar->add_node( array( 'id' => 'velox-status', 'parent' => 'velox', 'title' => '' . esc_html( $icon . ' ' . $status ) . '', 'href' => false, ) ); $here = ( is_ssl() ? 'https://' : 'http://' ) . ( isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '' ) . ( isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/' ); $bar->add_node( array( 'id' => 'velox-purge-url', 'parent' => 'velox', 'title' => esc_html__( 'Clear this page', 'angelforce-cache' ), 'href' => wp_nonce_url( admin_url( 'admin-post.php?action=velox_purge_url&url=' . rawurlencode( $here ) ), 'velox_purge_url' ), ) ); } } public static function handle_purge_all() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not allowed.', 'angelforce-cache' ) ); } check_admin_referer( 'velox_purge_all' ); Velox_Cache::purge_all(); wp_safe_redirect( add_query_arg( 'velox_msg', 'purged', wp_get_referer() ? wp_get_referer() : admin_url( 'admin.php?page=' . self::SLUG ) ) ); exit; } public static function handle_purge_url() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not allowed.', 'angelforce-cache' ) ); } check_admin_referer( 'velox_purge_url' ); $url = isset( $_GET['url'] ) ? esc_url_raw( wp_unslash( $_GET['url'] ) ) : ''; if ( $url && (string) wp_parse_url( $url, PHP_URL_HOST ) === (string) wp_parse_url( home_url(), PHP_URL_HOST ) ) { Velox_Cache::purge_url( $url ); } wp_safe_redirect( $url ? remove_query_arg( 'velox_msg', $url ) : home_url( '/' ) ); exit; } /* ---------- ajax ---------- */ private static function ajax_guard() { check_ajax_referer( 'velox_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'forbidden', 403 ); } } public static function ajax_purge() { self::ajax_guard(); Velox_Cache::purge_all(); wp_send_json_success( self::stats() ); } public static function ajax_db_counts() { self::ajax_guard(); wp_send_json_success( array( 'counts' => Velox_Database::counts() ) ); } /** Saves the settings form without a page reload; returns fresh score + stats. */ public static function ajax_save() { self::ajax_guard(); $raw = isset( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput,WordPress.Security.NonceVerification.Missing -- nonce verified in ajax_guard(); the string is parsed and every value sanitized by Velox_Settings::sanitize(). parse_str( (string) $raw, $form ); $input = isset( $form[ Velox_Settings::OPTION ] ) && is_array( $form[ Velox_Settings::OPTION ] ) ? $form[ Velox_Settings::OPTION ] : array(); update_option( Velox_Settings::OPTION, Velox_Settings::sanitize( $input ) ); Velox_Settings::flush(); list( $score ) = self::score(); wp_send_json_success( array( 'score' => $score, 'stats' => self::stats(), 'ai_ready' => Velox_AI::configured(), 'alt_ready' => Velox_AI::own_key(), 'warning' => Velox_Htaccess::$last_error, ) ); } public static function ajax_stats() { self::ajax_guard(); wp_send_json_success( self::stats() ); } public static function ajax_preload() { self::ajax_guard(); Velox_Preload::schedule(); wp_send_json_success( self::stats() ); } public static function ajax_db() { self::ajax_guard(); $item = isset( $_POST['item'] ) ? sanitize_key( wp_unslash( $_POST['item'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in the ajax guard. $valid = array_merge( array_keys( Velox_Database::labels() ), array( 'optimize', 'all' ) ); if ( ! in_array( $item, $valid, true ) ) { wp_send_json_error( 'bad item', 400 ); } Velox_Database::clean( $item ); wp_send_json_success( array( 'counts' => Velox_Database::counts() ) ); } public static function stats() { $st = Velox_Cache::stats(); $last = (int) get_option( 'velox_last_purge', 0 ); return array( 'pages' => $st['pages'], 'size' => size_format( $st['size'], 1 ), 'last' => $last ? sprintf( /* translators: %s: human time diff */ __( '%s ago', 'angelforce-cache' ), human_time_diff( $last ) ) : __( 'never', 'angelforce-cache' ), 'preload' => Velox_Preload::remaining(), ); } /* ---------- export / import / reset ---------- */ public static function handle_export() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not allowed.', 'angelforce-cache' ) ); } check_admin_referer( 'velox_export' ); nocache_headers(); header( 'Content-Type: application/json; charset=utf-8' ); header( 'Content-Disposition: attachment; filename=angelforce-cache-settings-' . gmdate( 'Y-m-d' ) . '.json' ); $export = Velox_Settings::all(); unset( $export['ai_key'], $export['psi_key'], $export['cf_token'], $export['cf_zone'] ); echo wp_json_encode( $export, JSON_PRETTY_PRINT ); exit; } public static function handle_import() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not allowed.', 'angelforce-cache' ) ); } check_admin_referer( 'velox_import' ); $raw = isset( $_POST['velox_json'] ) ? wp_unslash( $_POST['velox_json'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $data = strlen( (string) $raw ) < 65536 ? json_decode( (string) $raw, true ) : null; $to = admin_url( 'admin.php?page=' . self::SLUG ); if ( ! is_array( $data ) ) { wp_safe_redirect( add_query_arg( 'velox_msg', 'badfile', $to ) ); exit; } update_option( Velox_Settings::OPTION, Velox_Settings::sanitize( array_merge( Velox_Settings::all(), $data ) ) ); wp_safe_redirect( add_query_arg( 'velox_msg', 'imported', $to ) ); exit; } public static function handle_reset() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not allowed.', 'angelforce-cache' ) ); } check_admin_referer( 'velox_reset' ); update_option( Velox_Settings::OPTION, Velox_Settings::defaults() ); wp_safe_redirect( add_query_arg( 'velox_msg', 'reset', admin_url( 'admin.php?page=' . self::SLUG ) ) ); exit; } /* ---------- field helpers ---------- */ private static function name( $key ) { return Velox_Settings::OPTION . '[' . $key . ']'; } private static function toggle( $key, $title, $desc = '', $badge = '' ) { $on = (bool) Velox_Settings::get( $key ); ?>

= 85 ? 'poor' : ( $load >= 65 ? 'ni' : 'good' ) ); ?>
>

Workspaces.', 'angelforce-cache' ), __( 'Copy the ID of the workspace you want to use (it starts with wrkspc_) and paste it here.', 'angelforce-cache' ), __( 'Save the keys.', 'angelforce-cache' ), ), ), ); case 'psi_key': return array( array( '', array( __( 'Open console.cloud.google.com and create or select a project.', 'angelforce-cache' ), __( 'Go to APIs & Services > Library and enable "PageSpeed Insights API".', 'angelforce-cache' ), __( 'Go to APIs & Services > Credentials, click "Create credentials" and choose "API key".', 'angelforce-cache' ), __( 'Copy the key (it starts with AIza) and paste it here. It is free.', 'angelforce-cache' ), ), ), ); case 'cf_token': return array( array( '', array( __( 'Open dash.cloudflare.com, then My Profile > API Tokens and click "Create Token".', 'angelforce-cache' ), __( 'Choose "Create Custom Token" and add the permission Zone > Cache Purge > Purge.', 'angelforce-cache' ), __( 'Under Zone Resources select only your site, create the token and paste it here.', 'angelforce-cache' ), ), ), ); case 'cf_zone': return array( array( '', array( __( 'Open dash.cloudflare.com and click your site.', 'angelforce-cache' ), __( 'On the Overview page, copy "Zone ID" from the API box on the right and paste it here.', 'angelforce-cache' ), ), ), ); } return array(); } /** * What a setting does, when to use it and what to watch out for (the "?" next to it). * * @param string $key Setting key. * @return array{what?:string,when?:string,caution?:string} */ private static function feature_help( $key ) { switch ( $key ) { case 'cache_enabled': return array( 'what' => __( 'Saves each page as a ready HTML file, so visitors get it without WordPress and the database doing any work.', 'angelforce-cache' ), 'when' => __( 'Keep it on for every site; it is the main speed gain.', 'angelforce-cache' ), 'caution' => __( 'Logged-in users, carts and forms are never cached.', 'angelforce-cache' ), ); case 'static_serve': return array( 'what' => __( 'Lets Apache or LiteSpeed send cached pages directly, without starting PHP at all: the fastest way to serve a page.', 'angelforce-cache' ), 'when' => __( 'Keep it on unless your host does not allow .htaccess rules.', 'angelforce-cache' ), 'caution' => __( 'Nginx ignores .htaccess; there the drop-in serves the cache instead.', 'angelforce-cache' ), ); case 'cache_mobile': return array( 'what' => __( 'Keeps a separate cached copy for phones.', 'angelforce-cache' ), 'when' => __( 'Only if your theme sends different HTML to phones (rare; most themes are responsive).', 'angelforce-cache' ), 'caution' => __( 'Doubles the number of cached files.', 'angelforce-cache' ), ); case 'cache_lifespan': return array( 'what' => __( 'How many hours a cached page is kept before it is built again.', 'angelforce-cache' ), 'when' => __( '10 hours suits most sites; lower it if content changes often without being edited (feeds, prices from another system).', 'angelforce-cache' ), 'caution' => __( 'Safe: it does not change how your site looks or works.', 'angelforce-cache' ), ); case 'cache_exclude': return array( 'what' => __( 'Pages listed here are never cached, one path per line; * is a wildcard.', 'angelforce-cache' ), 'when' => __( 'Pages that must always be fresh: personal dashboards, live data, booking calendars.', 'angelforce-cache' ), ); case 'preload': return array( 'what' => __( 'After the cache is cleared, AngelForce Cache visits your pages in the background so the first real visitor also gets a cached page.', 'angelforce-cache' ), 'when' => __( 'Keep it on; it pauses automatically when the server is busy.', 'angelforce-cache' ), ); case 'preload_sitemap': return array( 'what' => __( 'Takes the list of pages to preload from your XML sitemap instead of only the home page and recent posts.', 'angelforce-cache' ), 'when' => __( 'Turn it on when you have an SEO plugin sitemap or many pages.', 'angelforce-cache' ), ); case 'preload_max': return array( 'what' => __( 'The most pages preloaded in one run.', 'angelforce-cache' ), 'when' => __( 'Lower it on small hosting plans, raise it for big sites on a strong server.', 'angelforce-cache' ), ); case 'html_minify': return array( 'what' => __( 'Removes spaces, line breaks and comments from the page HTML.', 'angelforce-cache' ), 'when' => __( 'Safe to keep on.', 'angelforce-cache' ), ); case 'css_minify': return array( 'what' => __( 'Makes stylesheets smaller by removing spaces and comments; files are minified once and kept.', 'angelforce-cache' ), 'when' => __( 'Safe to keep on.', 'angelforce-cache' ), ); case 'defer_js': return array( 'what' => __( 'Lets the page show before JavaScript files have downloaded; scripts still run in the right order.', 'angelforce-cache' ), 'when' => __( 'Keep it on for better First Contentful Paint.', 'angelforce-cache' ), 'caution' => __( 'If something only works after a reload, add its script to the exclusions.', 'angelforce-cache' ), ); case 'defer_exclude': return array( 'what' => __( 'Scripts listed here (file name or part of it) load the classic way.', 'angelforce-cache' ), 'when' => __( 'Only when a script breaks with defer.', 'angelforce-cache' ), ); case 'delay_js': return array( 'what' => __( 'Holds back JavaScript until the visitor scrolls, taps, clicks or types. The page shows and becomes usable much faster.', 'angelforce-cache' ), 'when' => __( 'The biggest win for PageSpeed. Payment, form, consent and slider scripts are excluded automatically.', 'angelforce-cache' ), 'caution' => __( 'Test menus, sliders and forms after turning it on.', 'angelforce-cache' ), ); case 'delay_exclude': return array( 'what' => __( 'Scripts listed here are never delayed (file name, id or a word from inline code).', 'angelforce-cache' ), 'when' => __( 'When a part of the page must work before the first interaction, such as a slider at the top.', 'angelforce-cache' ), ); case 'delay_timeout': return array( 'what' => __( 'Runs delayed scripts anyway after this many seconds, even without any interaction.', 'angelforce-cache' ), 'when' => __( 'Keep 0 for the best PageSpeed; use 8 if you need analytics from visitors who never touch the page.', 'angelforce-cache' ), 'caution' => __( 'A timeout lets PageSpeed see a late change and Speed Index gets worse.', 'angelforce-cache' ), ); case 'critical_css': return array( 'what' => __( 'Puts the CSS needed for the top of the page into the page itself and loads the full stylesheets without blocking.', 'angelforce-cache' ), 'when' => __( 'Keep it on: text and images appear before the CSS files have downloaded.', 'angelforce-cache' ), 'caution' => __( 'If the page flashes unstyled for a moment, add the stylesheet to the exclusions.', 'angelforce-cache' ), ); case 'critical_exclude': return array( 'what' => __( 'Stylesheets listed here keep loading the classic, blocking way.', 'angelforce-cache' ), 'when' => __( 'When a stylesheet must be there from the first frame.', 'angelforce-cache' ), ); case 'critical_manual': return array( 'what' => __( 'Your own critical CSS instead of the automatic one.', 'angelforce-cache' ), 'when' => __( 'Only if you generated critical CSS with another tool.', 'angelforce-cache' ), ); case 'rucss': return array( 'what' => __( 'Keeps only the CSS rules each page really uses and loads the rest after the first interaction.', 'angelforce-cache' ), 'when' => __( 'For the highest PageSpeed score on CSS-heavy themes and page builders.', 'angelforce-cache' ), 'caution' => __( 'Elements styled only after a click (menus, popups) can look wrong; add their classes to the safelist.', 'angelforce-cache' ), ); case 'rucss_exclude': return array( 'what' => __( 'Pages or stylesheets listed here keep all of their CSS.', 'angelforce-cache' ), 'when' => __( 'When one page looks wrong with unused CSS removed.', 'angelforce-cache' ), ); case 'rucss_safelist': return array( 'what' => __( 'CSS classes or words that are always kept, even if they are not in the page HTML.', 'angelforce-cache' ), 'when' => __( 'For classes that scripts add later, such as is-open or menu-active.', 'angelforce-cache' ), ); case 'rucss_timeout': return array( 'what' => __( 'Seconds to wait after the page has loaded before the full stylesheets follow.', 'angelforce-cache' ), 'when' => __( 'Keep 0: the full CSS arrives right after loading, so nothing stays unstyled.', 'angelforce-cache' ), ); case 'lazy_images': return array( 'what' => __( 'Images below the visible area load only when the visitor scrolls near them.', 'angelforce-cache' ), 'when' => __( 'Keep it on; the first images and the main image are never delayed.', 'angelforce-cache' ), ); case 'lazy_skip': return array( 'what' => __( 'This many first images on a page always load at once.', 'angelforce-cache' ), 'when' => __( 'Raise it if your header or hero has several images.', 'angelforce-cache' ), ); case 'lazy_iframes': return array( 'what' => __( 'Embedded maps, videos and widgets load only when scrolled near.', 'angelforce-cache' ), 'when' => __( 'Safe to keep on.', 'angelforce-cache' ), ); case 'lazy_youtube': return array( 'what' => __( 'Shows a lightweight preview image instead of the full YouTube player; the player loads on click.', 'angelforce-cache' ), 'when' => __( 'Saves about 500 KB of JavaScript per video.', 'angelforce-cache' ), 'caution' => __( 'The video starts with one extra click on some phones.', 'angelforce-cache' ), ); case 'lcp_priority': return array( 'what' => __( 'Tells the browser to download the page\'s main image (the Largest Contentful Paint) first, and preloads it from the page head.', 'angelforce-cache' ), 'when' => __( 'Keep it on; it directly improves LCP.', 'angelforce-cache' ), ); case 'img_dimensions': return array( 'what' => __( 'Adds the missing width and height to images, so the page does not jump while they load.', 'angelforce-cache' ), 'when' => __( 'Keep it on; it lowers Cumulative Layout Shift.', 'angelforce-cache' ), ); case 'font_swap': return array( 'what' => __( 'Shows text right away in a fallback font while Google Fonts load.', 'angelforce-cache' ), 'when' => __( 'Keep it on; text appears sooner.', 'angelforce-cache' ), ); case 'webp': return array( 'what' => __( 'Serves modern WebP copies of your JPEG and PNG images to browsers that support them; originals stay untouched.', 'angelforce-cache' ), 'when' => __( 'Keep it on; images become 25-35% smaller.', 'angelforce-cache' ), ); case 'webp_upload': return array( 'what' => __( 'Creates the WebP copy as soon as you upload an image.', 'angelforce-cache' ), 'when' => __( 'Keep it on; use the Media tab to convert existing images.', 'angelforce-cache' ), ); case 'webp_quality': return array( 'what' => __( 'Image quality of WebP copies from 1 to 100.', 'angelforce-cache' ), 'when' => __( '80 is a good balance; raise it for photography sites.', 'angelforce-cache' ), ); case 'avif': return array( 'what' => __( 'Also creates AVIF copies, even smaller than WebP, for browsers that support them.', 'angelforce-cache' ), 'when' => __( 'Turn it on when your server supports AVIF (shown in the Media tab).', 'angelforce-cache' ), 'caution' => __( 'Converting takes more server time than WebP.', 'angelforce-cache' ), ); case 'avif_quality': return array( 'what' => __( 'Image quality of AVIF copies from 1 to 100.', 'angelforce-cache' ), 'when' => __( '60 in AVIF looks about like 80 in WebP.', 'angelforce-cache' ), ); case 'gzip': return array( 'what' => __( 'Compresses pages, CSS and JavaScript before sending them, usually to a quarter of their size.', 'angelforce-cache' ), 'when' => __( 'Keep it on.', 'angelforce-cache' ), ); case 'brotli_dynamic': return array( 'what' => __( 'Lets the server compress cached pages with Brotli, about 15% smaller than gzip.', 'angelforce-cache' ), 'when' => __( 'Only if your host has mod_brotli; not needed when the PHP brotli extension prepares copies in advance.', 'angelforce-cache' ), ); case 'browser_cache': return array( 'what' => __( 'Tells browsers to keep images, fonts, CSS and JavaScript, so returning visitors load them from their own device.', 'angelforce-cache' ), 'when' => __( 'Keep it on.', 'angelforce-cache' ), ); case 'cdn_url': return array( 'what' => __( 'Loads files from wp-content and wp-includes from your CDN address.', 'angelforce-cache' ), 'when' => __( 'Only when you use a pull CDN such as BunnyCDN or KeyCDN. Cloudflare does not need it.', 'angelforce-cache' ), 'caution' => __( 'A wrong address breaks images and styles; check the site after saving.', 'angelforce-cache' ), ); case 'force_https': return array( 'what' => __( 'Sends every http:// visit to the https:// address with a permanent redirect.', 'angelforce-cache' ), 'when' => __( 'As soon as your SSL certificate works.', 'angelforce-cache' ), 'caution' => __( 'AngelForce Cache checks the address first and switches the redirect off by itself if it would loop.', 'angelforce-cache' ), ); case 'www_redirect': return array( 'what' => __( 'Sends the www or non-www form of your address to the main one.', 'angelforce-cache' ), 'when' => __( 'Always: one address avoids duplicate pages in Google.', 'angelforce-cache' ), ); case 'dns_prefetch': return array( 'what' => __( 'Lets the browser connect early to other servers the page uses, such as fonts or analytics.', 'angelforce-cache' ), 'when' => __( 'List the hosts of external files that appear at the top of the page.', 'angelforce-cache' ), ); case 'preload_fonts': return array( 'what' => __( 'Starts downloading the listed font files right away.', 'angelforce-cache' ), 'when' => __( 'Only for fonts used above the fold; each extra font costs bandwidth.', 'angelforce-cache' ), ); case 'disable_emojis': return array( 'what' => __( 'Removes the WordPress emoji script and style from every page.', 'angelforce-cache' ), 'when' => __( 'Safe: browsers show emojis themselves.', 'angelforce-cache' ), ); case 'disable_embeds': return array( 'what' => __( 'Removes the script that lets other sites embed your posts.', 'angelforce-cache' ), 'when' => __( 'Turn it on unless you embed WordPress posts from other sites.', 'angelforce-cache' ), ); case 'remove_query': return array( 'what' => __( 'Removes ?ver= from CSS and JavaScript links.', 'angelforce-cache' ), 'when' => __( 'Some proxies cache files better without it.', 'angelforce-cache' ), 'caution' => __( 'Visitors may keep old files for a while after plugin updates.', 'angelforce-cache' ), ); case 'heartbeat': return array( 'what' => __( 'Controls how often WordPress pings the server while the dashboard is open.', 'angelforce-cache' ), 'when' => __( 'Slow it down on shared hosting to save server resources.', 'angelforce-cache' ), 'caution' => __( 'Off can stop autosave and post lock warnings in the editor.', 'angelforce-cache' ), ); case 'db_schedule': return array( 'what' => __( 'Cleans the database automatically: revisions, spam, trash and expired data.', 'angelforce-cache' ), 'when' => __( 'Weekly suits most sites.', 'angelforce-cache' ), ); case 'compat_list': return array( 'what' => __( 'Built-in list of scripts and CSS classes that must never be delayed or removed: payments, checkout, captcha, consent banners, forms, sliders.', 'angelforce-cache' ), 'when' => __( 'Keep it on.', 'angelforce-cache' ), ); case 'adminbar': return array( 'what' => __( 'Adds a AngelForce Cache menu to the admin bar: clear the cache and see whether visitors get this page from the cache.', 'angelforce-cache' ), 'when' => __( 'Handy while editing.', 'angelforce-cache' ), ); case 'credit_link': return array( 'what' => __( 'Adds a small "Optimized by AngelForce" link to your footer.', 'angelforce-cache' ), 'when' => __( 'Only if you want to support the project; it is off by default.', 'angelforce-cache' ), ); case 'rum': return array( 'what' => __( 'Measures Core Web Vitals from real visitors and shows them in the Dashboard.', 'angelforce-cache' ), 'when' => __( 'Turn it on to see real-world speed, the same data Google uses.', 'angelforce-cache' ), 'caution' => __( 'Adds a 1 KB script for a sample of visitors; no personal data is stored.', 'angelforce-cache' ), ); case 'rum_sample': return array( 'what' => __( 'Percentage of visitors that are measured.', 'angelforce-cache' ), 'when' => __( '10% is enough for most sites; lower it on very busy sites.', 'angelforce-cache' ), ); case 'ui_lang': return array( 'what' => __( 'Language of the AngelForce Cache screens, independent of your WordPress language.', 'angelforce-cache' ), 'when' => __( 'Choose a language or leave it on automatic.', 'angelforce-cache' ), ); case 'ai_provider': return array( 'what' => __( 'Which AI service the assistant uses.', 'angelforce-cache' ), 'when' => __( 'On WordPress 7.0 or later choose "WordPress AI" to use the provider already connected in WordPress (no key needed here). Otherwise OpenAI or Anthropic with your own key. Alt texts currently need your own key.', 'angelforce-cache' ), ); case 'ai_model': return array( 'what' => __( 'Which AI model to use.', 'angelforce-cache' ), 'when' => __( 'Leave it empty: AngelForce Cache picks a fast, low-cost model your key can use.', 'angelforce-cache' ), ); case 'woo_stock_purge': return array( 'what' => __( 'Refreshes product, shop and category pages when an order changes stock or a sale starts or ends.', 'angelforce-cache' ), 'when' => __( 'Keep it on for every shop.', 'angelforce-cache' ), ); case 'woo_fragments': return array( 'what' => __( 'Skips WooCommerce\'s cart refresh request for visitors with an empty cart.', 'angelforce-cache' ), 'when' => __( 'Saves one server request per page view on busy shops.', 'angelforce-cache' ), 'caution' => __( 'Check that the header cart icon still updates after adding a product.', 'angelforce-cache' ), ); } return array(); } /** Renders the "?" popover for a setting: key steps when it has them, otherwise what it does. */ private static function help_tip( $key ) { $groups = self::help_steps( $key ); $info = $groups ? array() : self::feature_help( $key ); if ( ! $groups && ! $info ) { return; } $id = 'vx-help-' . $key; ?> __( 'What it does', 'angelforce-cache' ), 'when' => __( 'When to use it', 'angelforce-cache' ), 'caution' => __( 'Watch out', 'angelforce-cache' ), ); foreach ( $labels as $part => $label ) : if ( empty( $info[ $part ] ) ) { continue; } ?>

array( __( 'Page cache', 'angelforce-cache' ), 'cache' ), 'html_minify' => array( __( 'HTML minify', 'angelforce-cache' ), 'files' ), 'css_minify' => array( __( 'CSS minify', 'angelforce-cache' ), 'files' ), 'defer_js' => array( __( 'Defer JavaScript', 'angelforce-cache' ), 'files' ), 'delay_js' => array( __( 'Delay JavaScript', 'angelforce-cache' ), 'files' ), 'lazy_images' => array( __( 'Lazy load images', 'angelforce-cache' ), 'media' ), 'lazy_youtube' => array( __( 'YouTube facade', 'angelforce-cache' ), 'media' ), 'gzip' => array( __( 'GZIP compression', 'angelforce-cache' ), 'delivery' ), 'browser_cache' => array( __( 'Browser caching', 'angelforce-cache' ), 'delivery' ), 'preload' => array( __( 'Cache preload', 'angelforce-cache' ), 'cache' ), 'disable_emojis' => array( __( 'Disable emojis', 'angelforce-cache' ), 'advanced' ), 'webp' => array( __( 'WebP images', 'angelforce-cache' ), 'media' ), 'lcp_priority' => array( __( 'LCP image priority', 'angelforce-cache' ), 'media' ), 'img_dimensions' => array( __( 'Image dimensions', 'angelforce-cache' ), 'media' ), 'critical_css' => array( __( 'Critical CSS', 'angelforce-cache' ), 'files' ), ); $on = 0; foreach ( $checks as $k => $c ) { if ( Velox_Settings::get( $k ) || ( 'critical_css' === $k && Velox_Settings::get( 'rucss' ) ) ) { ++$on; } } return array( (int) round( $on / count( $checks ) * 100 ), $checks ); } /* ---------- setup wizard ---------- */ private static function wizard_card() { $done = (string) get_option( 'velox_setup_done' ); if ( '' !== $done ) { $profiles = Velox_Compat::profiles(); $name = isset( $profiles[ $done ] ) ? $profiles[ $done ]['label'] : __( 'Manual settings', 'angelforce-cache' ); ?>

· · ·

$pr ) : ?>
'LCP', 'inp' => 'INP', 'cls' => 'CLS', 'fcp' => 'FCP', 'ttfb' => 'TTFB', ); ?>

__( 'Mobile', 'angelforce-cache' ), 'desktop' => __( 'Desktop', 'angelforce-cache' ), ) as $dev => $label ) : $row = $vit[ $dev ]; ?>
$nm ) : $mm = $row['metrics'][ $k ]; ?>
$svg ) : ?>
·

=' ) ), ); ?>

AngelForce Cache

v ↗

%

    $c ) : $ok = (bool) Velox_Settings::get( $k ); ?>