Skip to content

Commit 21986d5

Browse files
authored
Updates
1 parent eff35d2 commit 21986d5

File tree

3 files changed

+209
-37
lines changed

3 files changed

+209
-37
lines changed

CHANGELOG.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
### Added
99
- Added new option to remove RSD (Really Simple Discovery) link from WordPress header
10-
- Added DNS prefetch domains: `https://s.w.org`, `https://wordpress.com`, and `https://cdnjs.cloudflare.com`
10+
- **NEW FEATURE**: Added separate DNS Prefetch option alongside Preconnect for better resource hint control
11+
- DNS-prefetch performs lighter-weight DNS-only lookups for less critical domains
12+
- Preconnect performs full connection setup (DNS + TCP + TLS) for critical domains
13+
- Clear UI explanations help users understand when to use each option
14+
- Added preconnect domains: `https://s.w.org`, `https://wordpress.com`, `https://cdnjs.cloudflare.com`, and `https://www.googletagmanager.com`
15+
- Added DNS prefetch domain: `https://adservice.google.com`
1116

1217
### Changed
13-
- Updated default DNS prefetch domains to remove deprecated Google CDN URLs (`ajax.googleapis.com` and `apis.google.com`)
18+
- **PERFORMANCE UPGRADE**: Changed from DNS-prefetch to Preconnect for better performance
19+
- Preconnect establishes full connection (DNS + TCP + TLS) vs dns-prefetch which only does DNS lookup
20+
- Adds `crossorigin` attribute for font domains (required for CORS requests)
21+
- Improves Largest Contentful Paint (LCP) and First Contentful Paint (FCP) metrics
22+
- Updated default preconnect domains to remove deprecated Google CDN URLs (`ajax.googleapis.com` and `apis.google.com`)
1423
- All optimization options are now disabled by default for better user control
15-
- Improved DNS prefetch textarea display to eliminate extra whitespace on first line
24+
- Improved preconnect textarea display to eliminate extra whitespace on first line
1625

1726
### Fixed
1827
- Fixed WordPress coding standards compliance: PHP opening and closing tags now on separate lines

readme.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,14 @@ No, the plugin has a simple interface where you can toggle features on and off.
4545

4646
= Unreleased =
4747
* **FEATURE**: Added new option to remove RSD (Really Simple Discovery) link from WordPress header
48-
* **ENHANCEMENT**: Added DNS prefetch domains for WordPress.org, WordPress.com, and Cloudflare CDN
49-
* **OPTIMIZATION**: Updated default DNS prefetch domains by removing deprecated Google CDN URLs
48+
* **FEATURE**: Added separate DNS Prefetch option for lighter-weight domain lookups
49+
* **PERFORMANCE**: Upgraded from DNS-prefetch to Preconnect for critical resources
50+
* **PERFORMANCE**: Preconnect now establishes full connections (DNS + TCP + TLS) instead of just DNS lookup
51+
* **PERFORMANCE**: Added crossorigin attribute for font domains to improve CORS request handling
52+
* **ENHANCEMENT**: Added helpful descriptions explaining when to use Preconnect vs DNS-prefetch
53+
* **ENHANCEMENT**: Added preconnect for Google Tag Manager and additional critical domains
54+
* **ENHANCEMENT**: Added DNS prefetch for Google Ad Service as lighter-weight fallback
55+
* **OPTIMIZATION**: Updated default preconnect domains by removing deprecated Google CDN URLs
5056
* **USER EXPERIENCE**: All optimization options are now disabled by default for better user control
5157
* **CODE QUALITY**: Fixed WordPress coding standards compliance for PHP tag formatting and indentation
5258

simple-wp-optimizer.php

Lines changed: 189 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ function es_optimizer_init_frontend_optimizations() {
133133
add_action( 'wp_enqueue_scripts', 'disable_classic_theme_styles', 100 );
134134
add_action( 'init', 'remove_header_items' );
135135
add_action( 'init', 'remove_recent_comments_style' );
136+
add_action( 'wp_head', 'add_preconnect', 0 );
136137
add_action( 'wp_head', 'add_dns_prefetch', 0 );
137138
add_action( 'init', 'disable_jetpack_ads' );
138139
add_action( 'init', 'disable_post_via_email' );
@@ -186,17 +187,20 @@ function es_optimizer_get_default_options() {
186187
'remove_wlw_manifest' => 0,
187188
'remove_shortlink' => 0,
188189
'remove_recent_comments_style' => 0,
189-
'enable_dns_prefetch' => 0,
190-
'dns_prefetch_domains' => implode(
190+
'enable_preconnect' => 0,
191+
'preconnect_domains' => implode(
191192
"\n",
192193
array(
193194
'https://fonts.googleapis.com',
194195
'https://fonts.gstatic.com',
195196
'https://s.w.org',
196197
'https://wordpress.com',
197198
'https://cdnjs.cloudflare.com',
199+
'https://www.googletagmanager.com',
198200
)
199201
),
202+
'enable_dns_prefetch' => 0,
203+
'dns_prefetch_domains' => 'https://adservice.google.com',
200204
'disable_jetpack_ads' => 0,
201205
'disable_post_via_email' => 0,
202206
);
@@ -392,22 +396,6 @@ function es_optimizer_render_header_options( $options ) {
392396
* @param array $options Plugin options.
393397
*/
394398
function es_optimizer_render_additional_options( $options ) {
395-
// DNS Prefetch settings.
396-
es_optimizer_render_checkbox_option(
397-
$options,
398-
'enable_dns_prefetch',
399-
esc_html__( 'Enable DNS Prefetch', 'simple-wp-optimizer' ),
400-
esc_html__( 'Add DNS prefetch for common external domains', 'simple-wp-optimizer' )
401-
);
402-
403-
// DNS Prefetch Domains textarea.
404-
es_optimizer_render_textarea_option(
405-
$options,
406-
'dns_prefetch_domains',
407-
esc_html__( 'DNS Prefetch Domains', 'simple-wp-optimizer' ),
408-
esc_html__( 'Enter one HTTPS domain per line (e.g., https://fonts.googleapis.com). Only clean domains are allowed - no file paths, query parameters, or fragments. Only secure HTTPS domains are accepted for security reasons.', 'simple-wp-optimizer' )
409-
);
410-
411399
// Jetpack Ads settings.
412400
es_optimizer_render_checkbox_option(
413401
$options,
@@ -423,6 +411,38 @@ function es_optimizer_render_additional_options( $options ) {
423411
esc_html__( 'Disable Post via Email', 'simple-wp-optimizer' ),
424412
esc_html__( 'Disable WordPress post via email functionality for security and performance', 'simple-wp-optimizer' )
425413
);
414+
415+
// Preconnect settings.
416+
es_optimizer_render_checkbox_option(
417+
$options,
418+
'enable_preconnect',
419+
esc_html__( 'Enable Preconnect', 'simple-wp-optimizer' ),
420+
esc_html__( 'Preconnect to external domains for faster resource loading', 'simple-wp-optimizer' )
421+
);
422+
423+
// Preconnect Domains textarea.
424+
es_optimizer_render_textarea_option(
425+
$options,
426+
'preconnect_domains',
427+
esc_html__( 'Preconnect Domains', 'simple-wp-optimizer' ),
428+
esc_html__( 'Use preconnect for domains that host critical, frequently used resources, like Google Fonts. This hint tells the browser to establish a connection (including DNS lookup, TCP handshake, and TLS negotiation) as soon as possible, which can save 100–500ms on the subsequent request. Enter one HTTPS domain per line (e.g., https://fonts.googleapis.com). Only clean domains are allowed - no file paths, query parameters, or fragments.', 'simple-wp-optimizer' )
429+
);
430+
431+
// DNS Prefetch settings.
432+
es_optimizer_render_checkbox_option(
433+
$options,
434+
'enable_dns_prefetch',
435+
esc_html__( 'Enable DNS Prefetch', 'simple-wp-optimizer' ),
436+
esc_html__( 'DNS prefetch for less critical external domains', 'simple-wp-optimizer' )
437+
);
438+
439+
// DNS Prefetch Domains textarea.
440+
es_optimizer_render_textarea_option(
441+
$options,
442+
'dns_prefetch_domains',
443+
esc_html__( 'DNS Prefetch Domains', 'simple-wp-optimizer' ),
444+
esc_html__( 'DNS-prefetch is a lighter-weight alternative to preconnect that performs only the DNS lookup. Use it for less critical domains or as a fallback for browsers that don\'t support preconnect. Enter one HTTPS domain per line (e.g., https://adservice.google.com). Only clean domains are allowed - no file paths, query parameters, or fragments.', 'simple-wp-optimizer' )
445+
);
426446
}
427447

428448
/**
@@ -580,6 +600,7 @@ function es_optimizer_validate_options( $input ) {
580600
'remove_wlw_manifest',
581601
'remove_shortlink',
582602
'remove_recent_comments_style',
603+
'enable_preconnect',
583604
'enable_dns_prefetch',
584605
'disable_jetpack_ads',
585606
'disable_post_via_email',
@@ -589,22 +610,27 @@ function es_optimizer_validate_options( $input ) {
589610
$valid[ $checkbox ] = isset( $input[ $checkbox ] ) ? 1 : 0;
590611
}
591612

613+
// Validate and sanitize the preconnect domains with enhanced security.
614+
if ( isset( $input['preconnect_domains'] ) ) {
615+
$valid['preconnect_domains'] = es_optimizer_validate_preconnect_domains( $input['preconnect_domains'] );
616+
}
617+
592618
// Validate and sanitize the DNS prefetch domains with enhanced security.
593619
if ( isset( $input['dns_prefetch_domains'] ) ) {
594-
$valid['dns_prefetch_domains'] = es_optimizer_validate_dns_domains( $input['dns_prefetch_domains'] );
620+
$valid['dns_prefetch_domains'] = es_optimizer_validate_dns_prefetch_domains( $input['dns_prefetch_domains'] );
595621
}
596622

597623
return $valid;
598624
}
599625

600626
/**
601-
* Validate DNS prefetch domains with enhanced security
627+
* Validate preconnect domains with enhanced security
602628
*
603629
* @since 1.4.0
604630
* @param string $domains_input Raw domain input from user.
605631
* @return string Validated and sanitized domains.
606632
*/
607-
function es_optimizer_validate_dns_domains( $domains_input ) {
633+
function es_optimizer_validate_preconnect_domains( $domains_input ) {
608634
$domains = explode( "\n", trim( $domains_input ) );
609635
$sanitized_domains = array();
610636
$rejected_domains = array();
@@ -633,7 +659,71 @@ function es_optimizer_validate_dns_domains( $domains_input ) {
633659
}
634660

635661
/**
636-
* Validate a single DNS prefetch domain
662+
* Validate DNS prefetch domains with enhanced security
663+
*
664+
* @since 1.8.0
665+
* @param string $domains_input Raw domain input from user.
666+
* @return string Validated and sanitized domains.
667+
*/
668+
function es_optimizer_validate_dns_prefetch_domains( $domains_input ) {
669+
$domains = explode( "\n", trim( $domains_input ) );
670+
$sanitized_domains = array();
671+
$rejected_domains = array();
672+
673+
foreach ( $domains as $domain ) {
674+
$domain = trim( $domain );
675+
if ( empty( $domain ) ) {
676+
continue;
677+
}
678+
679+
$validation_result = es_optimizer_validate_single_domain( $domain );
680+
681+
if ( $validation_result['valid'] ) {
682+
$sanitized_domains[] = $validation_result['domain'];
683+
} else {
684+
$rejected_domains[] = $validation_result['error'];
685+
}
686+
}
687+
688+
// Show admin notice if any domains were rejected for security reasons.
689+
if ( ! empty( $rejected_domains ) ) {
690+
es_optimizer_show_dns_prefetch_rejection_notice( $rejected_domains );
691+
}
692+
693+
return implode( "\n", $sanitized_domains );
694+
}
695+
696+
/**
697+
* Show admin notice for rejected DNS prefetch domains
698+
*
699+
* @since 1.8.0
700+
* @param array $rejected_domains Array of rejected domain strings.
701+
*/
702+
function es_optimizer_show_dns_prefetch_rejection_notice( $rejected_domains ) {
703+
// Security: Properly escape and limit the rejected domains in error messages.
704+
$escaped_domains = array_map( 'esc_html', array_slice( $rejected_domains, 0, 3 ) );
705+
$rejected_message = implode( ', ', $escaped_domains );
706+
707+
if ( count( $rejected_domains ) > 3 ) {
708+
$rejected_message .= esc_html__( '...', 'simple-wp-optimizer' );
709+
}
710+
711+
$message = sprintf(
712+
// translators: %s is the list of rejected domain names.
713+
esc_html__( 'Some DNS prefetch domains were rejected for security reasons: %s', 'simple-wp-optimizer' ),
714+
$rejected_message
715+
);
716+
717+
add_settings_error(
718+
'es_optimizer_options',
719+
'dns_prefetch_security',
720+
$message,
721+
'warning'
722+
);
723+
}
724+
725+
/**
726+
* Validate a single preconnect domain
637727
*
638728
* @since 1.4.0
639729
* @param string $domain Domain to validate.
@@ -651,7 +741,7 @@ function es_optimizer_validate_single_domain( $domain ) {
651741
// Use wp_parse_url instead of parse_url for WordPress compatibility.
652742
$parsed_url = wp_parse_url( $domain );
653743

654-
// Security: Enforce HTTPS-only domains for DNS prefetch.
744+
// Security: Enforce HTTPS-only domains for preconnect.
655745
if ( ! isset( $parsed_url['scheme'] ) || 'https' !== $parsed_url['scheme'] ) {
656746
return array(
657747
'valid' => false,
@@ -667,19 +757,19 @@ function es_optimizer_validate_single_domain( $domain ) {
667757
);
668758
}
669759

670-
// Security: DNS prefetch should only use clean domains, not file paths.
760+
// Security: Preconnect should only use clean domains, not file paths.
671761
// Reject URLs with paths, query parameters, or fragments.
672762
if ( isset( $parsed_url['path'] ) && '/' !== $parsed_url['path'] && '' !== $parsed_url['path'] ) {
673763
return array(
674764
'valid' => false,
675-
'error' => $domain . ' (file paths not allowed for DNS prefetch - use domain only)',
765+
'error' => $domain . ' (file paths not allowed for preconnect - use domain only)',
676766
);
677767
}
678768

679769
if ( isset( $parsed_url['query'] ) || isset( $parsed_url['fragment'] ) ) {
680770
return array(
681771
'valid' => false,
682-
'error' => $domain . ' (query parameters and fragments not allowed for DNS prefetch)',
772+
'error' => $domain . ' (query parameters and fragments not allowed for preconnect)',
683773
);
684774
}
685775

@@ -727,13 +817,13 @@ function es_optimizer_show_domain_rejection_notice( $rejected_domains ) {
727817

728818
$message = sprintf(
729819
// translators: %s is the list of rejected domain names.
730-
esc_html__( 'Some DNS prefetch domains were rejected for security reasons: %s', 'simple-wp-optimizer' ),
820+
esc_html__( 'Some preconnect domains were rejected for security reasons: %s', 'simple-wp-optimizer' ),
731821
$rejected_message
732822
);
733823

734824
add_settings_error(
735825
'es_optimizer_options',
736-
'dns_prefetch_security',
826+
'preconnect_security',
737827
$message,
738828
'warning'
739829
);
@@ -906,15 +996,82 @@ function remove_recent_comments_style() {
906996
}
907997

908998
/**
909-
* Add DNS prefetching for common external domains.
999+
* Add preconnect hints for common external domains.
9101000
*
911-
* DNS prefetching can reduce latency when connecting to common external services.
912-
* This is particularly helpful for sites using Google Fonts, Analytics, etc.
1001+
* Preconnect establishes early connections (DNS + TCP + TLS handshake) to third-party domains.
1002+
* This reduces latency when loading resources from external origins and improves LCP/FCP metrics.
1003+
* More effective than dns-prefetch as it completes the full connection setup.
9131004
*
9141005
* Security note: All output is properly escaped with esc_url() before output to prevent XSS.
9151006
*
9161007
* @since 1.4.1
9171008
*/
1009+
function add_preconnect() {
1010+
// Only add if not admin and not doing AJAX.
1011+
if ( is_admin() || wp_doing_ajax() ) {
1012+
return;
1013+
}
1014+
1015+
// Use static caching to avoid repeated option retrieval.
1016+
static $domains_cache = null;
1017+
static $options_checked = false;
1018+
1019+
if ( ! $options_checked ) {
1020+
$options = get_option( 'es_optimizer_options' );
1021+
$options_checked = true;
1022+
1023+
// Only proceed if the option is enabled.
1024+
if ( ! isset( $options['enable_preconnect'] ) || ! $options['enable_preconnect'] ) {
1025+
$domains_cache = array(); // Cache empty array to avoid re-checking.
1026+
return;
1027+
}
1028+
1029+
// Get and process domains from settings.
1030+
if ( isset( $options['preconnect_domains'] ) && ! empty( $options['preconnect_domains'] ) ) {
1031+
// Process domains with optimization.
1032+
$domains = explode( "\n", $options['preconnect_domains'] );
1033+
$domains = array_map( 'trim', $domains );
1034+
$domains = array_filter( $domains );
1035+
1036+
// Remove duplicates and validate domains.
1037+
$domains = array_unique( $domains );
1038+
$valid_domains = array();
1039+
1040+
foreach ( $domains as $domain ) {
1041+
// Validate URL format and ensure HTTPS.
1042+
if ( filter_var( $domain, FILTER_VALIDATE_URL ) && strpos( $domain, 'https://' ) === 0 ) {
1043+
$valid_domains[] = $domain;
1044+
}
1045+
}
1046+
1047+
$domains_cache = $valid_domains;
1048+
} else {
1049+
$domains_cache = array();
1050+
}
1051+
}
1052+
1053+
// Output the preconnect links.
1054+
if ( ! empty( $domains_cache ) ) {
1055+
foreach ( $domains_cache as $domain ) {
1056+
// Add crossorigin attribute for font domains (required for CORS requests).
1057+
$crossorigin = ( strpos( $domain, 'fonts.g' ) !== false || strpos( $domain, 'gstatic' ) !== false ) ? ' crossorigin' : '';
1058+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1059+
echo '<link rel="preconnect" href="' . esc_url( $domain ) . '"' . $crossorigin . '>' . "\n";
1060+
}
1061+
}
1062+
}
1063+
1064+
/**
1065+
* Add DNS prefetch hints for external domains.
1066+
*
1067+
* DNS-prefetch performs only the DNS lookup for third-party domains.
1068+
* This is a lighter-weight alternative to preconnect for less critical resources.
1069+
* Use for domains that may not be used immediately or as a fallback.
1070+
*
1071+
* Security note: All output is properly escaped with esc_url() before output to prevent XSS.
1072+
*
1073+
* @since 1.8.0
1074+
*/
9181075
function add_dns_prefetch() {
9191076
// Only add if not admin and not doing AJAX.
9201077
if ( is_admin() || wp_doing_ajax() ) {
@@ -959,7 +1116,7 @@ function add_dns_prefetch() {
9591116
}
9601117
}
9611118

962-
// Output the prefetch links.
1119+
// Output the DNS prefetch links.
9631120
if ( ! empty( $domains_cache ) ) {
9641121
foreach ( $domains_cache as $domain ) {
9651122
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

0 commit comments

Comments
 (0)