Async.php
2 months ago
Downloader.php
5 months ago
Embed.php
2 months ago
Fonts.php
5 months ago
Google.php
2 months ago
Downloader.php
291 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Manage fonts downloading. |
| 4 | * |
| 5 | * @package Kirki |
| 6 | * @category Core |
| 7 | * @author Themeum |
| 8 | * @copyright Copyright (c) 2023, Themeum |
| 9 | * @license https://opensource.org/licenses/MIT |
| 10 | * @since 3.1.0 |
| 11 | */ |
| 12 | |
| 13 | namespace Kirki\Module\Webfonts; |
| 14 | |
| 15 | /** |
| 16 | * Fonts-downloading manager. |
| 17 | * |
| 18 | * @since 3.1.0 |
| 19 | */ |
| 20 | class Downloader { |
| 21 | |
| 22 | /** |
| 23 | * Get styles from URL. |
| 24 | * |
| 25 | * @access public |
| 26 | * @since 3.1.0 |
| 27 | * @param string $url The URL. |
| 28 | * @return string |
| 29 | */ |
| 30 | public function get_styles( $url ) { |
| 31 | $css = $this->get_cached_url_contents( $url ); |
| 32 | return $this->get_local_font_styles( $css ); |
| 33 | } |
| 34 | |
| 35 | /** |
| 36 | * Get styles with fonts downloaded locally. |
| 37 | * |
| 38 | * @access protected |
| 39 | * @since 3.1.0 |
| 40 | * @param string $css The styles. |
| 41 | * @return string |
| 42 | */ |
| 43 | protected function get_local_font_styles( $css ) { |
| 44 | $files = $this->get_local_files_from_css( $css ); |
| 45 | |
| 46 | // Convert paths to URLs. |
| 47 | foreach ( $files as $remote => $local ) { |
| 48 | $files[ $remote ] = str_replace( WP_CONTENT_DIR, content_url(), $local ); |
| 49 | } |
| 50 | |
| 51 | return str_replace( |
| 52 | array_keys( $files ), |
| 53 | array_values( $files ), |
| 54 | $css |
| 55 | ); |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * Download files mentioned in our CSS locally. |
| 60 | * |
| 61 | * @access protected |
| 62 | * @since 3.1.0 |
| 63 | * @param string $css The CSS we want to parse. |
| 64 | * @return array Returns an array of remote URLs and their local counterparts. |
| 65 | */ |
| 66 | protected function get_local_files_from_css( $css ) { |
| 67 | $font_files = $this->get_files_from_css( $css ); |
| 68 | $stored = get_option( 'kirki_downloaded_font_files', array() ); |
| 69 | $change = false; // If in the end this is true, we need to update the cache option. |
| 70 | |
| 71 | // If the fonts folder don't exist, create it. |
| 72 | if ( ! file_exists( WP_CONTENT_DIR . '/fonts' ) ) { |
| 73 | $this->get_filesystem()->mkdir( WP_CONTENT_DIR . '/fonts', FS_CHMOD_DIR ); |
| 74 | } |
| 75 | |
| 76 | foreach ( $font_files as $font_family => $files ) { |
| 77 | |
| 78 | // The folder path for this font-family. |
| 79 | $folder_path = WP_CONTENT_DIR . '/fonts/' . $font_family; |
| 80 | |
| 81 | // If the folder doesn't exist, create it. |
| 82 | if ( ! file_exists( $folder_path ) ) { |
| 83 | $this->get_filesystem()->mkdir( $folder_path, FS_CHMOD_DIR ); |
| 84 | } |
| 85 | |
| 86 | foreach ( $files as $url ) { |
| 87 | |
| 88 | // Get the filename. |
| 89 | $filename = basename( wp_parse_url( $url, PHP_URL_PATH ) ); |
| 90 | $font_path = $folder_path . '/' . $filename; |
| 91 | |
| 92 | if ( file_exists( $font_path ) ) { |
| 93 | |
| 94 | // Skip if already cached. |
| 95 | if ( isset( $stored[ $url ] ) ) { |
| 96 | continue; |
| 97 | } |
| 98 | |
| 99 | $stored[ $url ] = $font_path; |
| 100 | $change = true; |
| 101 | } |
| 102 | |
| 103 | if ( ! function_exists( 'download_url' ) ) { |
| 104 | require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' ); |
| 105 | } |
| 106 | |
| 107 | // Download file to temporary location. |
| 108 | $tmp_path = download_url( $url ); |
| 109 | |
| 110 | // Make sure there were no errors. |
| 111 | if ( is_wp_error( $tmp_path ) ) { |
| 112 | continue; |
| 113 | } |
| 114 | |
| 115 | // Move temp file to final destination. |
| 116 | $success = $this->get_filesystem()->move( $tmp_path, $font_path, true ); |
| 117 | if ( $success ) { |
| 118 | $stored[ $url ] = $font_path; |
| 119 | $change = true; |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | if ( $change ) { |
| 125 | update_option( 'kirki_downloaded_font_files', $stored ); |
| 126 | } |
| 127 | |
| 128 | return $stored; |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * Get cached url contents. |
| 133 | * If a cache doesn't already exist, get the URL contents from remote |
| 134 | * and cache the result. |
| 135 | * |
| 136 | * @access public |
| 137 | * @since 3.1.0 |
| 138 | * @param string $url The URL we want to get the contents from. |
| 139 | * @param string $user_agent The user-agent to use for our request. |
| 140 | * @return string Returns the remote URL contents. |
| 141 | */ |
| 142 | public function get_cached_url_contents( $url = '', $user_agent = null ) { |
| 143 | |
| 144 | // Try to retrieved cached response from the gfonts API. |
| 145 | $contents = false; |
| 146 | $cached_responses = get_transient( 'kirki_remote_url_contents' ); |
| 147 | $cached_responses = ( $cached_responses && is_array( $cached_responses ) ) ? $cached_responses : array(); |
| 148 | if ( isset( $cached_responses[ md5( $url . $user_agent ) ] ) ) { |
| 149 | return $cached_responses[ md5( $url . $user_agent ) ]; |
| 150 | } |
| 151 | |
| 152 | // Get the contents from remote. |
| 153 | $contents = $this->get_url_contents( $url, $user_agent ); |
| 154 | |
| 155 | // If we got the contents successfully, store them in a transient. |
| 156 | // We're using a transient and not an option because fonts get updated |
| 157 | // so we want to be able to get the latest version weekly. |
| 158 | if ( $contents ) { |
| 159 | $cached_responses[ md5( $url . $user_agent ) ] = $contents; |
| 160 | set_transient( 'kirki_remote_url_contents', $cached_responses, WEEK_IN_SECONDS ); |
| 161 | } |
| 162 | |
| 163 | return $contents; |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Get remote file contents. |
| 168 | * |
| 169 | * @access public |
| 170 | * @since 3.1.0 |
| 171 | * @param string $url The URL we want to get the contents from. |
| 172 | * @param string $user_agent The user-agent to use for our request. |
| 173 | * @return string Returns the remote URL contents. |
| 174 | */ |
| 175 | public function get_url_contents( $url = '', $user_agent = null ) { |
| 176 | |
| 177 | if ( ! $user_agent ) { |
| 178 | |
| 179 | /** |
| 180 | * The user-agent we want to use. |
| 181 | * |
| 182 | * For woff2 format, use'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0'. |
| 183 | * The default user-agent is the only one compatible with woff (not woff2) |
| 184 | * which also supports unicode ranges. |
| 185 | */ |
| 186 | $user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0'; |
| 187 | |
| 188 | } |
| 189 | |
| 190 | // Get the response. |
| 191 | $response = wp_remote_get( $url, array( 'user-agent' => $user_agent ) ); |
| 192 | |
| 193 | // Early exit if there was an error. |
| 194 | if ( is_wp_error( $response ) ) { |
| 195 | return; |
| 196 | } |
| 197 | |
| 198 | // Get the CSS from our response. |
| 199 | $contents = wp_remote_retrieve_body( $response ); |
| 200 | |
| 201 | // Early exit if there was an error. |
| 202 | if ( is_wp_error( $contents ) ) { |
| 203 | return; |
| 204 | } |
| 205 | |
| 206 | return $contents; |
| 207 | } |
| 208 | |
| 209 | /** |
| 210 | * Get font files from the CSS. |
| 211 | * |
| 212 | * @access public |
| 213 | * @since 3.1.0 |
| 214 | * @param string $css The CSS we want to parse. |
| 215 | * @return array Returns an array of font-families and the font-files used. |
| 216 | */ |
| 217 | public function get_files_from_css( $css ) { |
| 218 | |
| 219 | $font_faces = explode( '@font-face', $css ); |
| 220 | |
| 221 | $result = array(); |
| 222 | |
| 223 | // Loop all our font-face declarations. |
| 224 | foreach ( $font_faces as $font_face ) { |
| 225 | |
| 226 | // Make sure we only process styles inside this declaration. |
| 227 | $style = explode( '}', $font_face )[0]; |
| 228 | |
| 229 | // Sanity check. |
| 230 | if ( false === strpos( $style, 'font-family' ) ) { |
| 231 | continue; |
| 232 | } |
| 233 | |
| 234 | // Get an array of our font-families. |
| 235 | preg_match_all( '/font-family.*?\;/', $style, $matched_font_families ); |
| 236 | |
| 237 | // Get an array of our font-files. |
| 238 | preg_match_all( '/url\(.*?\)/i', $style, $matched_font_files ); |
| 239 | |
| 240 | // Get the font-family name. |
| 241 | $font_family = 'unknown'; |
| 242 | if ( isset( $matched_font_families[0] ) && isset( $matched_font_families[0][0] ) ) { |
| 243 | $font_family = rtrim( ltrim( $matched_font_families[0][0], 'font-family:' ), ';' ); |
| 244 | $font_family = trim( str_replace( array( "'", ';' ), '', $font_family ) ); |
| 245 | $font_family = sanitize_key( strtolower( str_replace( ' ', '-', $font_family ) ) ); |
| 246 | } |
| 247 | |
| 248 | // Make sure the font-family is set in our array. |
| 249 | if ( ! isset( $result[ $font_family ] ) ) { |
| 250 | $result[ $font_family ] = array(); |
| 251 | } |
| 252 | |
| 253 | // Get files for this font-family and add them to the array. |
| 254 | |
| 255 | foreach ( $matched_font_files as $match ) { |
| 256 | |
| 257 | // Sanity check. |
| 258 | if ( ! isset( $match[0] ) ) { |
| 259 | continue; |
| 260 | } |
| 261 | |
| 262 | // Add the file URL. |
| 263 | $result[ $font_family ][] = rtrim( ltrim( $match[0], 'url(' ), ')' ); |
| 264 | } |
| 265 | |
| 266 | // Make sure we have unique items. |
| 267 | // We're using array_flip here instead of array_unique for improved performance. |
| 268 | $result[ $font_family ] = array_flip( array_flip( $result[ $font_family ] ) ); |
| 269 | } |
| 270 | return $result; |
| 271 | } |
| 272 | |
| 273 | /** |
| 274 | * Get the filesystem. |
| 275 | * |
| 276 | * @access protected |
| 277 | * @since 3.1.0 |
| 278 | * @return WP_Filesystem |
| 279 | */ |
| 280 | protected function get_filesystem() { |
| 281 | global $wp_filesystem; |
| 282 | if ( ! $wp_filesystem ) { |
| 283 | if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 284 | require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' ); |
| 285 | } |
| 286 | WP_Filesystem(); |
| 287 | } |
| 288 | return $wp_filesystem; |
| 289 | } |
| 290 | } |
| 291 |