PluginProbe
JCH Optimize / trunk
JCH Optimize vtrunk
6.0.2 6.0.1 trunk 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.10.0 2.2.0 2.2.1 All 58 releases
jch-optimize / lib / src / Helper.php

Helper.php in JCH Optimize trunk, at lib/src/Helper.php

344 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * JCH Optimize - Performs several front-end optimizations for fast downloads
5 *
6 * @package jchoptimize/core
7 * @author Samuel Marshall <samuel@jch-optimize.net>
8 * @copyright Copyright (c) 2022 Samuel Marshall / JCH Optimize
9 * @license GNU/GPLv3, or later. See LICENSE file
10 *
11 * If LICENSE file missing, see <http://www.gnu.org/licenses/>.
12 */
13
14 namespace JchOptimize\Core;
15
16 use _JchOptimizeVendor\V91\Joomla\DI\Container;
17 use _JchOptimizeVendor\V91\Joomla\Filesystem\Folder;
18 use _JchOptimizeVendor\V91\Psr\Http\Message\UriInterface;
19 use Exception;
20 use FilesystemIterator;
21 use JchOptimize\Core\Html\Elements\Script;
22 use JchOptimize\Core\Html\HtmlElementInterface;
23 use JchOptimize\Core\Platform\PathsInterface;
24 use JchOptimize\Core\SystemUri\SystemUri;
25 use JchOptimize\Core\Uri\AttributeClassifier;
26 use JchOptimize\Core\Uri\Utils;
27 use RecursiveDirectoryIterator;
28 use RecursiveIteratorIterator;
29 use SplFileInfo;
30
31 use function array_map;
32 use function defined;
33 use function file_exists;
34 use function html_entity_decode;
35 use function is_object;
36 use function preg_match;
37 use function preg_replace;
38 use function rmdir;
39 use function strtolower;
40 use function unlink;
41
42 defined('_JCH_EXEC') or die('Restricted access');
43
44 /**
45 * Some helper functions
46 *
47 */
48 class Helper
49 {
50 /**
51 * Checks if file (can be external) exists
52 *
53 * @param string $sPath
54 *
55 * @return bool
56 */
57 public static function fileExists(string $sPath): bool
58 {
59 if ((str_starts_with($sPath, 'http'))) {
60 $sFileHeaders = @get_headers($sPath);
61
62 return ($sFileHeaders !== false && !str_contains($sFileHeaders[0], '404'));
63 } else {
64 return file_exists($sPath);
65 }
66 }
67
68 public static function cleanReplacement(string $string): string
69 {
70 return strtr($string, ['\\' => '\\\\', '$' => '\$']);
71 }
72
73
74 /**
75 * @return string
76 * @deprecated
77 */
78 public static function getBaseFolder(): string
79 {
80 return SystemUri::currentBasePath();
81 }
82
83 public static function strReplace(string $search, string $replace, string $subject): string
84 {
85 return (string)str_replace(self::cleanPath($search), $replace, self::cleanPath($subject));
86 }
87
88 public static function cleanPath(string $str): string
89 {
90 return str_replace(['\\\\', '\\'], '/', $str);
91 }
92
93 /**
94 * Determine if document is of XHTML doctype
95 *
96 * @param string $html
97 *
98 * @return bool
99 */
100 public static function isXhtml(string $html): bool
101 {
102 return (bool)preg_match('#^\s*+(?:<!DOCTYPE(?=[^>]+XHTML)|<\?xml.*?\?>)#i', trim($html));
103 }
104
105 /**
106 * Determines if document is of html5 doctype
107 *
108 * @param string $html
109 *
110 * @return bool True if doctype is html5
111 */
112 public static function isHtml5(string $html): bool
113 {
114 return (bool)preg_match('#^<!DOCTYPE html>#i', trim($html));
115 }
116
117 public static function getArray(mixed $value): array
118 {
119 if (is_array($value)) {
120 $array = $value;
121 } elseif (is_string($value)) {
122 $array = explode(',', trim($value));
123 } elseif (is_object($value)) {
124 $array = (array)$value;
125 } else {
126 $array = [];
127 }
128
129 if (!empty($array)) {
130 $array = array_map(function ($v) {
131 if (is_string($v)) {
132 return trim($v);
133 } elseif (is_object($v)) {
134 return (array)$v;
135 } else {
136 return $v;
137 }
138 }, $array);
139 }
140
141 return array_filter($array);
142 }
143
144 public static function validateHtml(string $html): bool
145 {
146 return (bool)preg_match(
147 '#^(?>(?><?[^<]*+)*?<html(?><?[^<]*+)*?<head(?><?[^<]*+)*?</head\s*+>)(?><?[^<]*+)*?'
148 . '<body.*</body\s*+>(?><?[^<]*+)*?</html\s*+>#is',
149 $html
150 );
151 }
152
153 /**
154 *
155 * @param string[] $needles Array of excluded values to compare against
156 * @param string $haystack The string we're testing to see if it was excluded
157 * @param string $type (css|js) No longer used
158 *
159 * @return bool
160 */
161 public static function findExcludes(array $needles, string $haystack, string $type = ''): bool
162 {
163 if (empty($needles)) {
164 return false;
165 }
166
167 foreach ($needles as $needle) {
168 //Remove all spaces from test string and excluded string
169 $needle = strtolower((string)preg_replace('#\s#', '', $needle));
170 $haystack = strtolower((string)preg_replace('#\s#', '', html_entity_decode($haystack)));
171
172 if ($needle && str_contains($haystack, $needle)) {
173 return true;
174 }
175 }
176
177 return false;
178 }
179
180 /**
181 * @param string[] $needles
182 * @param string $haystack
183 *
184 * @return bool
185 */
186 public static function findMatches(array $needles, string $haystack): bool
187 {
188 return self::findExcludes($needles, $haystack);
189 }
190
191 /**
192 * @return UriInterface[]
193 */
194 public static function extractUrlsFromSrcset(string $srcSet): array
195 {
196 $parts = explode(',', $srcSet);
197
198 return array_filter(
199 array_map(
200 function ($v) {
201 $urlDescMap = explode(' ', trim($v));
202 $urlValue = array_shift($urlDescMap);
203
204 if (AttributeClassifier::isUriCandidate($urlValue)) {
205 return Utils::uriFor($urlValue);
206 }
207
208 return null;
209 },
210 $parts
211 )
212 );
213 }
214
215 public static function isScriptDeferred(Script $script): bool
216 {
217 return $script->getType() == 'module'
218 || ($script->getSrc() instanceof UriInterface && ($script->getDefer() || $script->getAsync()));
219 }
220
221 /**
222 * Utility function to convert a rule set to a unique class. Will retain pseudo-classes or pseudo-elements
223 *
224 * @param string $selectorGroup
225 *
226 * @return string
227 */
228 public static function cssSelectorsToClass(string $selectorGroup): string
229 {
230 return '_jch-' . preg_replace(
231 [
232 '#\##',
233 '#\.#',
234 '#[^0-9a-z_:-]#i',
235 ],
236 [
237 'id_',
238 'cl_',
239 '',
240 ],
241 $selectorGroup
242 );
243 }
244
245 public static function deleteFolder(string $folder): bool
246 {
247 $it = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
248 $files = new RecursiveIteratorIterator(
249 $it,
250 RecursiveIteratorIterator::CHILD_FIRST
251 );
252 /** @var SplFileInfo $file */
253 foreach ($files as $file) {
254 if ($file->isDir()) {
255 rmdir($file->getPathname());
256 } else {
257 unlink($file->getPathname());
258 }
259 }
260
261 rmdir($folder);
262
263 return !file_exists($folder);
264 }
265
266 /**
267 * Checks if a Uri is valid
268 *
269 * @param UriInterface $uri
270 *
271 * @return bool
272 */
273 public static function uriInvalid(UriInterface $uri): bool
274 {
275 if ((string)$uri == '') {
276 return true;
277 }
278
279 if (
280 $uri->getScheme() == ''
281 && $uri->getAuthority() == ''
282 && $uri->getQuery() == ''
283 && $uri->getFragment() == ''
284 ) {
285 if ($uri->getPath() == '/' || $uri->getPath() == SystemUri::currentBasePath()) {
286 return true;
287 }
288 }
289
290 return false;
291 }
292
293 public static function isStaticFile(string $filePath): bool
294 {
295 return (bool)preg_match('#\.(?:css|js|png|jpe?g|gif|bmp|webp|svg)$#i', $filePath);
296 }
297
298 public static function createCacheFolder(Container $container): void
299 {
300 $cacheDir = $container->get(PathsInterface::class)->cacheDir();
301
302 if (!file_exists($cacheDir)) {
303 try {
304 Folder::create($cacheDir);
305 } catch (Exception $e) {
306 }
307 }
308 }
309
310 public static function appendTrailingSlash(string $path): string
311 {
312 return self::removeTrailingSlash($path) . '/';
313 }
314
315 public static function removeTrailingSlash(string $path): string
316 {
317 return rtrim($path, '/\\');
318 }
319
320 public static function prependLeadingSlash(string $path): string
321 {
322 return '/' . self::removeLeadingSlash($path);
323 }
324
325 public static function removeLeadingSlash(string $path): string
326 {
327 return ltrim($path, '/\\');
328 }
329
330 public static function getElementWidth(HtmlElementInterface $element): int
331 {
332 return (int)($element->attributeValue('width') ?: $element->attributeValue('data-width') ?: 0);
333 }
334
335 public static function removeEmptyBackgroundDeclarations($css): string
336 {
337 return preg_replace(
338 ['#background(?:-image)?\s*+:\s*+(?:!important)?\s*+(?:;\s*+|(?=(?:}|$)))#', '#,\s*+;#'],
339 ['', ';'],
340 $css
341 );
342 }
343 }
344