AppsService.php
3 days ago
CollaborationCommentService.php
3 weeks ago
CollaborationService.php
3 weeks ago
CollectionItemService.php
3 weeks ago
CollectionService.php
3 weeks ago
EditorService.php
3 weeks ago
FontService.php
3 days ago
GlobalDataService.php
3 days ago
MediaService.php
3 weeks ago
PageService.php
3 days ago
PageSettingsService.php
3 weeks ago
UtilityPageService.php
3 days ago
FontService.php
199 lines
| 1 | <?php |
| 2 | |
| 3 | namespace Kirki\App\Services; |
| 4 | |
| 5 | defined('ABSPATH') || exit; |
| 6 | |
| 7 | use Exception; |
| 8 | use Kirki\App\DTO\GoogleFontDTO; |
| 9 | use Kirki\App\Supports\Facades\GlobalData; |
| 10 | use Kirki\App\Supports\FileHandler; |
| 11 | use Kirki\Framework\Http\Response; |
| 12 | use Kirki\Framework\Supports\Facades\File; |
| 13 | use Kirki\Framework\Supports\Facades\Http; |
| 14 | |
| 15 | use function Kirki\App\get_upload_directory; |
| 16 | use function Kirki\App\get_upload_directory_url; |
| 17 | use function Kirki\Framework\clean_path; |
| 18 | |
| 19 | class FontService |
| 20 | { |
| 21 | public function download_google_font(GoogleFontDTO $payload) |
| 22 | { |
| 23 | // Extra security: keep only a-z, 0-9, and hyphens |
| 24 | $font_family_slug = preg_replace('/[^a-z0-9\-]/i', '', sanitize_title_with_dashes(basename($payload->family))); |
| 25 | |
| 26 | if (empty($font_family_slug) || strpos($font_family_slug, '..') !== false) { |
| 27 | throw new Exception(esc_html__('Not valid font family.', 'kirki'), Response::FORBIDDEN); |
| 28 | } |
| 29 | |
| 30 | $has_write_permission = File::is_writable(get_upload_directory()); |
| 31 | |
| 32 | if (!$has_write_permission) { |
| 33 | throw new Exception(esc_html__('Upload directory is not writable.', 'kirki'), Response::FORBIDDEN); |
| 34 | } |
| 35 | |
| 36 | $font_dir = clean_path(get_upload_directory() . "/kirki-fonts/{$font_family_slug}", false); |
| 37 | $css_file_path = clean_path($font_dir . "/{$font_family_slug}.css", false); |
| 38 | |
| 39 | FileHandler::verify_directory_traversal($css_file_path); |
| 40 | |
| 41 | if (File::exists($css_file_path)) { |
| 42 | throw new Exception(esc_html__('Font already downloaded.', 'kirki'), Response::FORBIDDEN); |
| 43 | } |
| 44 | |
| 45 | $font_url_parts = wp_parse_url($payload->fontUrl); |
| 46 | |
| 47 | if (!$font_url_parts) { |
| 48 | throw new Exception(esc_html__('Invalid URL.', 'kirki')); |
| 49 | } |
| 50 | |
| 51 | if (($font_url_parts['scheme'] ?? '') !== 'https') { |
| 52 | throw new Exception(esc_html__('Only HTTPS is allowed.', 'kirki')); |
| 53 | } |
| 54 | |
| 55 | if (($font_url_parts['host'] ?? '') !== 'fonts.googleapis.com') { |
| 56 | throw new Exception(esc_html__('Only Google Fonts is supported.', 'kirki')); |
| 57 | } |
| 58 | |
| 59 | try { |
| 60 | if (!File::is_directory($font_dir)) { |
| 61 | File::make_dir($font_dir); |
| 62 | } |
| 63 | |
| 64 | $response = Http::with_options([ |
| 65 | 'redirection' => 0 |
| 66 | ])->get($payload->fontUrl); |
| 67 | |
| 68 | if ($response->failed()) { |
| 69 | throw new Exception(esc_html__('Failed to fetch Google Fonts CSS.', 'kirki')); |
| 70 | } |
| 71 | |
| 72 | if (isset($payload->files['regular'])) { |
| 73 | $payload->files['400'] = $payload->files['regular']; |
| 74 | unset($payload->files['regular']); |
| 75 | } |
| 76 | |
| 77 | $formats = [ |
| 78 | 'woff2' => 'woff2', |
| 79 | 'woff' => 'woff', |
| 80 | 'ttf' => 'truetype', |
| 81 | 'otf' => 'opentype', |
| 82 | ]; |
| 83 | $local_css = ''; |
| 84 | |
| 85 | foreach ($payload->files as $weight => $url) { |
| 86 | $allowed_ext = array_keys($formats); |
| 87 | $extension = strtolower(pathinfo($url, PATHINFO_EXTENSION)); |
| 88 | |
| 89 | if (!in_array($extension, $allowed_ext, true)) { |
| 90 | /* translators: %s: file extension */ |
| 91 | throw new Exception(sprintf(esc_html__('Invalid or unsafe file extension: %s.', 'kirki'), $extension)); |
| 92 | } |
| 93 | |
| 94 | $url_parts = wp_parse_url($url); |
| 95 | |
| 96 | if (!$url_parts) { |
| 97 | throw new Exception(esc_html__('Invalid URL.', 'kirki')); |
| 98 | } |
| 99 | |
| 100 | if (($url_parts['scheme'] ?? '') !== 'https') { |
| 101 | throw new Exception(esc_html__('Only HTTPS is allowed.', 'kirki')); |
| 102 | } |
| 103 | |
| 104 | if (($url_parts['host'] ?? '') !== 'fonts.gstatic.com') { |
| 105 | throw new Exception(esc_html__('Only Google Fonts is supported.', 'kirki')); |
| 106 | } |
| 107 | |
| 108 | $file_name = "{$weight}.{$extension}"; |
| 109 | $file_path = clean_path($font_dir . '/' . $file_name, false); |
| 110 | |
| 111 | FileHandler::verify_directory_traversal($file_path); |
| 112 | |
| 113 | $font_response = Http::with_options([ |
| 114 | 'redirection' => 0 |
| 115 | ])->get($url); |
| 116 | |
| 117 | if ($font_response->failed()) { |
| 118 | throw new Exception(esc_html__('Failed to fetch font file.', 'kirki')); |
| 119 | } |
| 120 | |
| 121 | File::put($file_path, $font_response->__toString()); |
| 122 | |
| 123 | $format = $formats[$extension] ?? 'truetype'; |
| 124 | $local_css .= "@font-face { |
| 125 | font-family: '{$payload->family}'; |
| 126 | font-style: normal; |
| 127 | font-weight: {$weight}; |
| 128 | font-display: swap; |
| 129 | src: url('{$file_name}') format('{$format}'); |
| 130 | }\n"; |
| 131 | } |
| 132 | |
| 133 | if (empty($local_css)) { |
| 134 | throw new Exception(esc_html__('No usable font files were downloaded.', 'kirki')); |
| 135 | } |
| 136 | |
| 137 | File::put($css_file_path, $local_css); |
| 138 | $payload->localUrl = clean_path(get_upload_directory_url() . "/kirki-fonts/{$font_family_slug}/{$font_family_slug}.css", false); |
| 139 | |
| 140 | $this->save_google_font_into_global_custom_fonts($payload); |
| 141 | |
| 142 | return $payload; |
| 143 | |
| 144 | } catch (Exception $exception) { |
| 145 | if (File::is_directory($font_dir)) { |
| 146 | File::delete($font_dir); |
| 147 | } |
| 148 | |
| 149 | throw $exception; |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | public function remove_google_font(GoogleFontDTO $payload) { |
| 154 | $font_family_slug = sanitize_title_with_dashes(basename($payload->family)); |
| 155 | $font_dir = clean_path(get_upload_directory() . "/kirki-fonts/{$font_family_slug}", false); |
| 156 | |
| 157 | FileHandler::verify_directory_traversal($font_dir); |
| 158 | |
| 159 | if (File::is_directory($font_dir)) { |
| 160 | File::delete($font_dir); |
| 161 | } |
| 162 | |
| 163 | $payload->exclude(['localUrl']); |
| 164 | |
| 165 | $this->save_google_font_into_global_custom_fonts($payload); |
| 166 | |
| 167 | return true; |
| 168 | } |
| 169 | |
| 170 | protected function save_google_font_into_global_custom_fonts(GoogleFontDTO $font) { |
| 171 | // first get the font data |
| 172 | $custom_fonts = GlobalData::get_global_custom_fonts(); |
| 173 | |
| 174 | if (empty($custom_fonts)) { |
| 175 | $custom_fonts = []; |
| 176 | } |
| 177 | |
| 178 | if (!empty($font->family)) { |
| 179 | $custom_fonts[$font->family] = $font->to_array(); |
| 180 | |
| 181 | GlobalData::update_global_custom_fonts($custom_fonts); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | public function remove_custom_fonts_permanently_from_directory(array $fonts) |
| 186 | { |
| 187 | foreach ($fonts as $font) { |
| 188 | // Remove font from local. |
| 189 | $font_family_slug = sanitize_title_with_dashes(basename($font['family'])); |
| 190 | $font_local_dir = clean_path(get_upload_directory() . "/kirki-fonts/{$font_family_slug}", false); |
| 191 | |
| 192 | FileHandler::verify_directory_traversal($font_local_dir); |
| 193 | |
| 194 | if (File::is_directory($font_local_dir)) { |
| 195 | File::delete($font_local_dir); |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | } |