PluginProbe
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant / 2.0.1
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant v2.0.1
2.0.4 2.0.3 2.0.2 2.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2 1.3 2.0.0
easyfonts / src / Detect / Providers.php

Providers.php in EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant 2.0.1, at src/Detect/Providers.php

173 lines 4.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Font-provider URL recognition.
4 *
5 * @package EasyFonts
6 */
7
8 namespace EasyFonts\Detect;
9
10 defined( 'ABSPATH' ) || exit;
11
12 /**
13 * Knows which URLs are font-provider stylesheets and which provider they are.
14 */
15 class Providers {
16
17 /**
18 * Hostnames that serve font CSS (incl. GDPR proxies).
19 *
20 * @return string[]
21 */
22 public static function css_hosts(): array {
23 return apply_filters(
24 'easyfonts_css_hosts',
25 array(
26 'fonts.googleapis.com',
27 'fonts.bunny.net',
28 'fonts-api.wp.com',
29 )
30 );
31 }
32
33 /**
34 * Hostnames + domains used for resource hints.
35 *
36 * @return string[]
37 */
38 public static function hint_hosts(): array {
39 return array(
40 'fonts.googleapis.com',
41 'fonts.gstatic.com',
42 'fonts.bunny.net',
43 'fonts-api.wp.com',
44 );
45 }
46
47 /**
48 * Is this a provider stylesheet URL (css / css2 / icon)?
49 *
50 * @param string $url URL.
51 * @return bool
52 */
53 public static function is_css_url( string $url ): bool {
54 foreach ( self::css_hosts() as $host ) {
55 if ( false !== strpos( $url, $host . '/css' ) || false !== strpos( $url, $host . '/icon' ) ) {
56 return true;
57 }
58 }
59
60 return false;
61 }
62
63 /**
64 * Provider id for a URL.
65 *
66 * @param string $url URL.
67 * @return string
68 */
69 public static function id_for( string $url ): string {
70 if ( false !== strpos( $url, 'bunny.net' ) ) {
71 return 'bunny';
72 }
73
74 if ( false !== strpos( $url, 'wp.com' ) ) {
75 return 'wp';
76 }
77
78 return 'google';
79 }
80
81 /**
82 * Normalize a URL to an absolute, fetchable form.
83 *
84 * Handles the cases that broke detection: protocol-relative `//host/...`
85 * (the main bug — wp_remote_get can't fetch these) and root-relative
86 * `/path`. HTML-decodes `&amp;` etc. so query strings survive.
87 *
88 * @param string $url Raw URL (possibly `//…`, `/…`, or entity-encoded).
89 * @param string $base_url Optional base for resolving truly relative paths.
90 * @return string
91 */
92 public static function normalize_url( string $url, string $base_url = '' ): string {
93 $url = trim( html_entity_decode( $url, ENT_QUOTES, 'UTF-8' ) );
94
95 if ( '' === $url || 0 === strpos( $url, 'data:' ) ) {
96 return $url;
97 }
98
99 // Protocol-relative → add the current scheme.
100 if ( 0 === strpos( $url, '//' ) ) {
101 return ( is_ssl() ? 'https:' : 'http:' ) . $url;
102 }
103
104 // Already absolute.
105 if ( preg_match( '#^https?://#i', $url ) ) {
106 return $url;
107 }
108
109 // Root-relative → site root.
110 if ( 0 === strpos( $url, '/' ) ) {
111 return home_url( $url );
112 }
113
114 // Truly relative → resolve against the base directory.
115 if ( '' !== $base_url ) {
116 return self::resolve_relative( $url, $base_url );
117 }
118
119 return $url;
120 }
121
122 /**
123 * Does this URL point at a known font-provider host (any path)?
124 *
125 * Broader than is_css_url(): also matches gstatic font binaries and any
126 * provider host, so we can recognise remote @font-face src values.
127 *
128 * @param string $url URL.
129 * @return bool
130 */
131 public static function is_provider_url( string $url ): bool {
132 foreach ( self::hint_hosts() as $host ) {
133 if ( false !== strpos( $url, $host ) ) {
134 return true;
135 }
136 }
137
138 return false;
139 }
140
141 /**
142 * Resolve a relative URL against a base URL's directory.
143 *
144 * @param string $relative Relative path.
145 * @param string $base_url Absolute base URL.
146 * @return string
147 */
148 private static function resolve_relative( string $relative, string $base_url ): string {
149 $parts = wp_parse_url( $base_url );
150
151 if ( empty( $parts['scheme'] ) || empty( $parts['host'] ) ) {
152 return $relative;
153 }
154
155 $origin = $parts['scheme'] . '://' . $parts['host'] . ( isset( $parts['port'] ) ? ':' . $parts['port'] : '' );
156 $dir = isset( $parts['path'] ) ? preg_replace( '#/[^/]*$#', '/', $parts['path'] ) : '/';
157
158 // Collapse ../ segments.
159 $path = $dir . $relative;
160 $segments = array();
161
162 foreach ( explode( '/', $path ) as $segment ) {
163 if ( '..' === $segment ) {
164 array_pop( $segments );
165 } elseif ( '.' !== $segment && '' !== $segment ) {
166 $segments[] = $segment;
167 }
168 }
169
170 return $origin . '/' . implode( '/', $segments );
171 }
172 }
173