├── .gitignore ├── uninstall.sql ├── vendor ├── matthiasmullie │ ├── minify │ │ ├── data │ │ │ └── js │ │ │ │ ├── keywords_after.txt │ │ │ │ ├── keywords_before.txt │ │ │ │ ├── operators_after.txt │ │ │ │ ├── operators_before.txt │ │ │ │ ├── operators.txt │ │ │ │ └── keywords_reserved.txt │ │ ├── ruleset.xml │ │ ├── src │ │ │ ├── Exception.php │ │ │ ├── Exceptions │ │ │ │ ├── IOException.php │ │ │ │ ├── FileImportException.php │ │ │ │ └── BasicException.php │ │ │ ├── Minify.php │ │ │ ├── CSS.php │ │ │ └── JS.php │ │ ├── LICENSE │ │ ├── bin │ │ │ ├── minifyjs │ │ │ └── minifycss │ │ └── composer.json │ └── path-converter │ │ ├── src │ │ ├── NoConverter.php │ │ ├── ConverterInterface.php │ │ └── Converter.php │ │ ├── composer.json │ │ └── LICENSE ├── composer │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── platform_check.php │ ├── LICENSE │ ├── autoload_real.php │ ├── autoload_classmap.php │ ├── installed.php │ ├── autoload_static.php │ ├── installed.json │ ├── InstalledVersions.php │ └── ClassLoader.php ├── autoload.php └── bin │ ├── minifyjs │ └── minifycss ├── composer.json ├── install.sql ├── CHANGELOG.md ├── .github └── workflows │ └── publish-to-redaxo-org.yml ├── package.yml ├── pages ├── index.php ├── config.php └── sets.php ├── update.php ├── LICENSE ├── README.md ├── lang ├── sv_se.lang ├── en_gb.lang ├── de_de.lang ├── pt_br.lang └── es_es.lang ├── composer.lock ├── lib └── class.minify.php └── boot.php /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /uninstall.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `%TABLE_PREFIX%minify_sets`; -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/data/js/keywords_after.txt: -------------------------------------------------------------------------------- 1 | in 2 | public 3 | extends 4 | private 5 | protected 6 | implements 7 | instanceof -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "matthiasmullie/minify": "^1.3" 4 | }, 5 | "config": { 6 | "optimize-autoloader": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src 6 | ./tests 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/data/js/keywords_before.txt: -------------------------------------------------------------------------------- 1 | do 2 | in 3 | let 4 | new 5 | var 6 | case 7 | else 8 | enum 9 | void 10 | with 11 | class 12 | const 13 | yield 14 | delete 15 | export 16 | import 17 | public 18 | static 19 | typeof 20 | extends 21 | package 22 | private 23 | function 24 | protected 25 | implements 26 | instanceof -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/data/js/operators_after.txt: -------------------------------------------------------------------------------- 1 | + 2 | - 3 | * 4 | / 5 | % 6 | = 7 | += 8 | -= 9 | *= 10 | /= 11 | %= 12 | <<= 13 | >>= 14 | >>>= 15 | &= 16 | ^= 17 | |= 18 | & 19 | | 20 | ^ 21 | << 22 | >> 23 | >>> 24 | == 25 | === 26 | != 27 | !== 28 | > 29 | < 30 | >= 31 | <= 32 | && 33 | || 34 | . 35 | [ 36 | ] 37 | ? 38 | : 39 | , 40 | ; 41 | ( 42 | ) 43 | } -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/data/js/operators_before.txt: -------------------------------------------------------------------------------- 1 | + 2 | - 3 | * 4 | / 5 | % 6 | = 7 | += 8 | -= 9 | *= 10 | /= 11 | %= 12 | <<= 13 | >>= 14 | >>>= 15 | &= 16 | ^= 17 | |= 18 | & 19 | | 20 | ^ 21 | ~ 22 | << 23 | >> 24 | >>> 25 | == 26 | === 27 | != 28 | !== 29 | > 30 | < 31 | >= 32 | <= 33 | && 34 | || 35 | ! 36 | . 37 | [ 38 | ? 39 | : 40 | , 41 | ; 42 | ( 43 | { 44 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/data/js/operators.txt: -------------------------------------------------------------------------------- 1 | + 2 | - 3 | * 4 | / 5 | % 6 | = 7 | += 8 | -= 9 | *= 10 | /= 11 | %= 12 | <<= 13 | >>= 14 | >>>= 15 | &= 16 | ^= 17 | |= 18 | & 19 | | 20 | ^ 21 | ~ 22 | << 23 | >> 24 | >>> 25 | == 26 | === 27 | != 28 | !== 29 | > 30 | < 31 | >= 32 | <= 33 | && 34 | || 35 | ! 36 | . 37 | [ 38 | ] 39 | ? 40 | : 41 | , 42 | ; 43 | ( 44 | ) 45 | { 46 | } -------------------------------------------------------------------------------- /vendor/composer/autoload_psr4.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/matthiasmullie/path-converter/src'), 10 | 'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'), 11 | ); 12 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/src/Exception.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | 11 | namespace MatthiasMullie\Minify; 12 | 13 | /** 14 | * Base Exception Class. 15 | * 16 | * @deprecated Use Exceptions\BasicException instead 17 | * 18 | * @author Matthias Mullie 19 | */ 20 | abstract class Exception extends \Exception 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /install.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%minify_sets` ( 2 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 3 | `name` varchar(30) NOT NULL DEFAULT '', 4 | `type` varchar(30) NOT NULL DEFAULT '', 5 | `minimize` enum('no','yes') NOT NULL DEFAULT 'no', 6 | `ignore_browsercache` enum('no','yes') NOT NULL DEFAULT 'no', 7 | `attributes` text NOT NULL DEFAULT '', 8 | `output` varchar(30) NOT NULL DEFAULT '', 9 | `assets` text NOT NULL DEFAULT '', 10 | PRIMARY KEY (`id`) 11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/src/Exceptions/IOException.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 10 | * @license MIT License 11 | */ 12 | 13 | namespace MatthiasMullie\Minify\Exceptions; 14 | 15 | /** 16 | * IO Exception Class. 17 | * 18 | * @author Matthias Mullie 19 | */ 20 | class IOException extends BasicException 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/src/Exceptions/FileImportException.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 10 | * @license MIT License 11 | */ 12 | 13 | namespace MatthiasMullie\Minify\Exceptions; 14 | 15 | /** 16 | * File Import Exception Class. 17 | * 18 | * @author Matthias Mullie 19 | */ 20 | class FileImportException extends BasicException 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/path-converter/src/NoConverter.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved 12 | * @license MIT License 13 | */ 14 | class NoConverter implements ConverterInterface 15 | { 16 | /** 17 | * {@inheritdoc} 18 | */ 19 | public function convert($path) 20 | { 21 | return $path; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/src/Exceptions/BasicException.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 10 | * @license MIT License 11 | */ 12 | 13 | namespace MatthiasMullie\Minify\Exceptions; 14 | 15 | use MatthiasMullie\Minify\Exception; 16 | 17 | /** 18 | * Basic Exception Class. 19 | * 20 | * @author Matthias Mullie 21 | */ 22 | abstract class BasicException extends Exception 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 2.2 // 12.05.2020 4 | 5 | ### Vendor Update 6 | 7 | ### Neue Features 8 | * Minify Cache wird nun auch gelöscht bei Aufruf des Extension Point `CACHE_DELETED` 9 | * Umbau der Cache Clear Funktion 10 | * Einzelne gecachte Dateien können nun in der Übersicht gelöscht werden 11 | * Beim Löschen des Caches werden nun weiterhin die angelegten Sets gezeigt 12 | 13 | ### Bugfixes 14 | * Kleine Stylinganpassungen ([#53](https://github.com/FriendsOfREDAXO/minify/issues/53) @alexplusde) 15 | * Nur Minify Dateien werden gelöscht ([#48](https://github.com/FriendsOfREDAXO/minify/issues/48) @alexplusde ) 16 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-redaxo-org.yml: -------------------------------------------------------------------------------- 1 | # Instructions: https://github.com/FriendsOfREDAXO/installer-action/ 2 | 3 | name: Publish to REDAXO.org 4 | on: 5 | release: 6 | types: 7 | - published 8 | 9 | jobs: 10 | redaxo_publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: FriendsOfREDAXO/installer-action@v1 15 | with: 16 | myredaxo-username: ${{ secrets.MYREDAXO_USERNAME }} 17 | myredaxo-api-key: ${{ secrets.MYREDAXO_API_KEY }} 18 | description: ${{ github.event.release.body }} 19 | version: ${{ github.event.release.tag_name }} 20 | 21 | -------------------------------------------------------------------------------- /package.yml: -------------------------------------------------------------------------------- 1 | package: minify 2 | version: '2.3.0' 3 | author: Friends Of REDAXO 4 | supportpage: github.com/FriendsOfREDAXO/minify 5 | 6 | page: 7 | title: 'translate:title' 8 | perm: minify[] 9 | pjax: true 10 | subpages: 11 | config: { title: 'translate:config', perm: minify[config] } 12 | sets: { title: 'translate:sets', perm: minify[sets] } 13 | clear_cache: { title: 'translate:minify_sets_function_clear_cache', itemclass: 'pull-right', linkclass: 'btn btn-delete', href: { page: minify/sets, cache: clear_cache } } 14 | 15 | requires: 16 | redaxo: '>=5.1.0' 17 | php: 18 | version: '>= 5.3' 19 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/path-converter/src/ConverterInterface.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved 12 | * @license MIT License 13 | */ 14 | interface ConverterInterface 15 | { 16 | /** 17 | * Convert file paths. 18 | * 19 | * @param string $path The path to be converted 20 | * 21 | * @return string The new path 22 | */ 23 | public function convert($path); 24 | } 25 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/data/js/keywords_reserved.txt: -------------------------------------------------------------------------------- 1 | do 2 | if 3 | in 4 | for 5 | let 6 | new 7 | try 8 | var 9 | case 10 | else 11 | enum 12 | eval 13 | null 14 | this 15 | true 16 | void 17 | with 18 | break 19 | catch 20 | class 21 | const 22 | false 23 | super 24 | throw 25 | while 26 | yield 27 | delete 28 | export 29 | import 30 | public 31 | return 32 | static 33 | switch 34 | typeof 35 | default 36 | extends 37 | finally 38 | package 39 | private 40 | continue 41 | debugger 42 | function 43 | arguments 44 | interface 45 | protected 46 | implements 47 | instanceof 48 | abstract 49 | boolean 50 | byte 51 | char 52 | double 53 | final 54 | float 55 | goto 56 | int 57 | long 58 | native 59 | short 60 | synchronized 61 | throws 62 | transient 63 | volatile -------------------------------------------------------------------------------- /pages/index.php: -------------------------------------------------------------------------------- 1 | i18n('title')); // $this->i18n('title') ist eine Kurzform für rex_i18n::msg('dummy_title') 9 | 10 | // Die Subpages werden nicht mehr über den "subpage"-Parameter gesteuert, sondern mit über "page" (getrennt mit einem Slash, z. B. page=dummy/config) 11 | // Die einzelnen Teile des page-Pfades können mit der folgenden Funktion ausgelesen werden. 12 | $subpage = rex_be_controller::getCurrentPagePart(2); 13 | 14 | // Subpages können über diese Methode eingebunden werden. So ist sichergestellt, dass auch Subpages funktionieren, 15 | // die von anderen Addons/Plugins hinzugefügt wurden 16 | rex_be_controller::includeCurrentPageSubPath(); 17 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/path-converter/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matthiasmullie/path-converter", 3 | "type": "library", 4 | "description": "Relative path converter", 5 | "keywords": ["relative", "path", "converter", "paths"], 6 | "homepage": "http://github.com/matthiasmullie/path-converter", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Matthias Mullie", 11 | "homepage": "http://www.mullie.eu", 12 | "email": "pathconverter@mullie.eu", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.3.0", 18 | "ext-pcre": "*" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~4.8" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "MatthiasMullie\\PathConverter\\": "src/" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | ensureColumn(new rex_sql_column('media', 'text')) 5 | ->ensureColumn(new rex_sql_column('attributes', 'text')) 6 | ->ensureColumn(new rex_sql_column('output', 'varchar(30)')) 7 | ->ensureColumn(new rex_sql_column('minimize', 'ENUM(\'no\',\'yes\')')) 8 | ->ensureColumn(new rex_sql_column('ignore_browsercache', 'ENUM(\'no\',\'yes\')')) 9 | ->alter(); 10 | 11 | $sql = rex_sql::factory(); 12 | $sql->setQuery('UPDATE `' . rex::getTable('minify_sets') . '` SET attributes = CONCAT("media=\"",media,"\"") WHERE `media` != ""'); 13 | unset($sql); 14 | 15 | rex_sql_table::get(rex::getTable('minify_sets')) 16 | ->removeColumn('media') 17 | ->alter(); 18 | 19 | if (!$this->hasConfig('pathcss')) { 20 | $this->setConfig(['pathcss' => '/assets/addons/minify/cache']); 21 | } 22 | 23 | if (!$this->hasConfig('pathjs')) { 24 | $this->setConfig(['pathjs' => '/assets/addons/minify/cache']); 25 | } 26 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 50300)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 5.3.0". You are running ' . PHP_VERSION . '.'; 9 | } 10 | 11 | if ($issues) { 12 | if (!headers_sent()) { 13 | header('HTTP/1.1 500 Internal Server Error'); 14 | } 15 | if (!ini_get('display_errors')) { 16 | if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { 17 | fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); 18 | } elseif (!headers_sent()) { 19 | echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; 20 | } 21 | } 22 | trigger_error( 23 | 'Composer detected issues in your platform: ' . implode(' ', $issues), 24 | E_USER_ERROR 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Matthias Mullie 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Friends Of REDAXO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/path-converter/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Matthias Mullie 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minify 2 | 3 | Minifies HTML, combines/minfies CSS and JS files. 4 | 5 | ![Screenshot](https://raw.githubusercontent.com/FriendsOfREDAXO/minify/assets/minify_01.png) 6 | 7 | Dieses Addon ermöglicht das minimieren und bündeln von CSS und JS Dateien. 8 | 9 | Dazu kann man unter dem Punkt 'Minify' beliebig viele Sets anlegen. Wichtig ist, dass der Name eines Sets pro Typ (CSS/JS) nur einmal vorkommen kann. In das Feld 'Assets' kommen zeilengetrennt die Pfade zu den einzelnen Dateien. Wenn eine Datei mit '.scss' endet, wird sie automatisch kompiliert. Die Pfade müssen Redaxo-Root relativ sein. 10 | 11 | Anschliessend wird ein Snippet à la "REX_MINIFY[type=css set=default]" generiert, welches im Template an beliebiger Stelle platziert werden kann. Das Snippet ist jeweils in der Set-Übersicht zu finden und kann von da kopiert werden. Das Snippet wird im Frontend automatisch durch einen entsprechenden HTML-Tag ersetzt. 12 | 13 | ## Tricks 14 | 15 | ### Sets via PHP definieren 16 | 17 | Beispiel von @cukabeka 18 | 19 | ```php 20 | $minify = new minify(); 21 | foreach ((glob(rex_path::assets()."/css/*.css")) as $css) { 22 | $minify->addFile("/".str_replace(rex_path::base(),"",$css), $set = "bla"); 23 | }; 24 | 25 | echo $minify->minify($type = 'css', $set = "bla"); 26 | ``` 27 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | return $loader; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | 'MatthiasMullie\\Minify\\CSS' => $vendorDir . '/matthiasmullie/minify/src/CSS.php', 11 | 'MatthiasMullie\\Minify\\Exception' => $vendorDir . '/matthiasmullie/minify/src/Exception.php', 12 | 'MatthiasMullie\\Minify\\Exceptions\\BasicException' => $vendorDir . '/matthiasmullie/minify/src/Exceptions/BasicException.php', 13 | 'MatthiasMullie\\Minify\\Exceptions\\FileImportException' => $vendorDir . '/matthiasmullie/minify/src/Exceptions/FileImportException.php', 14 | 'MatthiasMullie\\Minify\\Exceptions\\IOException' => $vendorDir . '/matthiasmullie/minify/src/Exceptions/IOException.php', 15 | 'MatthiasMullie\\Minify\\JS' => $vendorDir . '/matthiasmullie/minify/src/JS.php', 16 | 'MatthiasMullie\\Minify\\Minify' => $vendorDir . '/matthiasmullie/minify/src/Minify.php', 17 | 'MatthiasMullie\\PathConverter\\Converter' => $vendorDir . '/matthiasmullie/path-converter/src/Converter.php', 18 | 'MatthiasMullie\\PathConverter\\ConverterInterface' => $vendorDir . '/matthiasmullie/path-converter/src/ConverterInterface.php', 19 | 'MatthiasMullie\\PathConverter\\NoConverter' => $vendorDir . '/matthiasmullie/path-converter/src/NoConverter.php', 20 | ); 21 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/bin/minifyjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | minify(); 42 | } catch (Exception $e) { 43 | fwrite(STDERR, $e->getMessage(), PHP_EOL); 44 | exit(1); 45 | } 46 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/bin/minifycss: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | minify(); 42 | } catch (Exception $e) { 43 | fwrite(STDERR, $e->getMessage(), PHP_EOL); 44 | exit(1); 45 | } 46 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matthiasmullie/minify", 3 | "type": "library", 4 | "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", 5 | "keywords": ["minify", "minifier", "css", "js", "javascript"], 6 | "homepage": "https://github.com/matthiasmullie/minify", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Matthias Mullie", 11 | "homepage": "https://www.mullie.eu", 12 | "email": "minify@mullie.eu", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.3.0", 18 | "ext-pcre": "*", 19 | "matthiasmullie/path-converter": "~1.1" 20 | }, 21 | "require-dev": { 22 | "friendsofphp/php-cs-fixer": ">=2.0", 23 | "matthiasmullie/scrapbook": ">=1.3", 24 | "phpunit/phpunit": ">=4.8", 25 | "squizlabs/php_codesniffer": ">=3.0" 26 | }, 27 | "suggest": { 28 | "psr/cache-implementation": "Cache implementation to use with Minify::cache" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "MatthiasMullie\\Minify\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "MatthiasMullie\\Minify\\Tests\\": "tests/" 38 | } 39 | }, 40 | "bin": [ 41 | "bin/minifycss", 42 | "bin/minifyjs" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => '__root__', 4 | 'pretty_version' => 'dev-main', 5 | 'version' => 'dev-main', 6 | 'reference' => '4754540253f66bfe306e7b087ad9615aae949f13', 7 | 'type' => 'library', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | '__root__' => array( 14 | 'pretty_version' => 'dev-main', 15 | 'version' => 'dev-main', 16 | 'reference' => '4754540253f66bfe306e7b087ad9615aae949f13', 17 | 'type' => 'library', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | 'matthiasmullie/minify' => array( 23 | 'pretty_version' => '1.3.73', 24 | 'version' => '1.3.73.0', 25 | 'reference' => 'cb7a9297b4ab070909cefade30ee95054d4ae87a', 26 | 'type' => 'library', 27 | 'install_path' => __DIR__ . '/../matthiasmullie/minify', 28 | 'aliases' => array(), 29 | 'dev_requirement' => false, 30 | ), 31 | 'matthiasmullie/path-converter' => array( 32 | 'pretty_version' => '1.1.3', 33 | 'version' => '1.1.3.0', 34 | 'reference' => 'e7d13b2c7e2f2268e1424aaed02085518afa02d9', 35 | 'type' => 'library', 36 | 'install_path' => __DIR__ . '/../matthiasmullie/path-converter', 37 | 'aliases' => array(), 38 | 'dev_requirement' => false, 39 | ), 40 | ), 41 | ); 42 | -------------------------------------------------------------------------------- /lang/sv_se.lang: -------------------------------------------------------------------------------- 1 | minify_title = Minify 2 | 3 | #subpage config 4 | minify_config = Konfiguration 5 | minify_config_minifyhtml = Minimize HTML? 6 | minify_config_pathcss = Path for generated CSS files 7 | minify_config_pathcss_placeholder = /assets/cache/css 8 | minify_config_pathjs = Path for generated JS files 9 | minify_config_pathjs_placeholder = /assets/cache/js 10 | minify_config_hint = relative to document root, without trailing slashes 11 | minify_config_templates = Utesluta mallar 12 | minify_config_action_save = Spara inställningar 13 | minify_config_saved = Inställningar sparades 14 | 15 | #subpage sets 16 | minify_sets = Sets 17 | minify_sets_norowsmessage = Inga sets hittades 18 | 19 | minify_sets_column_name = Namn 20 | minify_sets_column_type = Typ 21 | minify_sets_column_snippet = Snippet 22 | minify_sets_column_cache = Cache 23 | 24 | minify_sets_formcaption_add = Tillfoga set 25 | minify_sets_formcaption_edit = Redigera set 26 | 27 | minify_sets_label_name = Namn 28 | minify_sets_label_type = Typ 29 | minify_sets_label_minimize = Minimera? 30 | minify_sets_label_ignore_browsercache = Ignorera browsercachen? 31 | minify_sets_label_output = Output 32 | minify_sets_label_attributes = Attribut 33 | minify_sets_label_attributes_note = One entry per line
example: media="print" 34 | minify_sets_label_assets = Assets 35 | minify_sets_label_assets_note = One entry per line; starts with a "/".
example: /assets/css/styles.css 36 | 37 | minify_sets_function_clear_cache = Ta bort cache 38 | minify_sets_function_clear_cache_success = Cache har tagits bort! 39 | minify_sets_function_clear_cache_file_success = Cachefilen har tagits bort! 40 | minify_sets_function_clear_cache_file_error = Cachefilen finns inte! 41 | 42 | #permissions 43 | minify_perm_general_minify[] = Rättigheter för Minify-Addon 44 | minify_perm_general_minify[config] = Permissions for the page 'Configuration' of the Minify-Addon 45 | minify_perm_general_minify[sets] = Rättigheter för sidan 'Sets' från Minify-Addon -------------------------------------------------------------------------------- /lang/en_gb.lang: -------------------------------------------------------------------------------- 1 | minify_title = Minify 2 | 3 | #subpage config 4 | minify_config = Configuration 5 | minify_config_minifyhtml = Minimize HTML? 6 | minify_config_pathcss = Path for generated CSS files 7 | minify_config_pathcss_placeholder = /assets/cache/css 8 | minify_config_pathjs = Path for generated JS files 9 | minify_config_pathjs_placeholder = /assets/cache/js 10 | minify_config_hint = relative to document root, without trailing slashes 11 | minify_config_templates = Whitelist templates 12 | minify_config_action_save = Save settings 13 | minify_config_saved = Settings successfully saved! 14 | 15 | #subpage sets 16 | minify_sets = Sets 17 | minify_sets_norowsmessage = No sets were found 18 | 19 | minify_sets_column_name = Name 20 | minify_sets_column_type = Type 21 | minify_sets_column_snippet = Snippet 22 | minify_sets_column_cache = Cache 23 | 24 | minify_sets_formcaption_add = Add set 25 | minify_sets_formcaption_edit = Edit set 26 | 27 | minify_sets_label_name = Name 28 | minify_sets_label_type = Type 29 | minify_sets_label_minimize = Minimize? 30 | minify_sets_label_ignore_browsercache = Ignore browser cache? 31 | minify_sets_label_output = Output 32 | minify_sets_label_attributes = Attributes 33 | minify_sets_label_attributes_note = One entry per line
example: media="print" 34 | minify_sets_label_assets = Assets 35 | minify_sets_label_assets_note = One entry per line; starts with a "/".
example: /assets/css/styles.css 36 | 37 | minify_sets_function_clear_cache = Clear cache 38 | minify_sets_function_clear_cache_success = Cache successfully cleared! 39 | minify_sets_function_clear_cache_file_success = Cache file successfully cleared! 40 | minify_sets_function_clear_cache_file_error = Cache file does not exist! 41 | 42 | #permissions 43 | minify_perm_general_minify[] = Permissions for the Minify-Addon 44 | minify_perm_general_minify[config] = Permissions for the page 'Configuration' of the Minify-Addon 45 | minify_perm_general_minify[sets] = Permissions for the page 'Sets' of the Minify-Addon -------------------------------------------------------------------------------- /lang/de_de.lang: -------------------------------------------------------------------------------- 1 | minify_title = Minify 2 | 3 | #subpage config 4 | minify_config = Konfiguration 5 | minify_config_minifyhtml = HTML minimizen? 6 | minify_config_pathcss = Pfad für generierte CSS-Dateien 7 | minify_config_pathcss_placeholder = /assets/cache/css 8 | minify_config_pathjs = Pfad für generierte JS-Dateien 9 | minify_config_pathjs_placeholder = /assets/cache/js 10 | minify_config_hint = DocumentRoot-relativ, ohne Trailingslash 11 | minify_config_templates = Templates ausschließen 12 | minify_config_action_save = Einstellungen speichern 13 | minify_config_saved = Einstellungen wurden gespeichert! 14 | 15 | #subpage sets 16 | minify_sets = Sets 17 | minify_sets_norowsmessage = Es wurden keine Sets gefunden 18 | 19 | minify_sets_column_name = Name 20 | minify_sets_column_type = Typ 21 | minify_sets_column_snippet = Snippet 22 | minify_sets_column_cache = Cache 23 | 24 | minify_sets_formcaption_add = Set hinzufügen 25 | minify_sets_formcaption_edit = Set editieren 26 | 27 | minify_sets_label_name = Name 28 | minify_sets_label_type = Typ 29 | minify_sets_label_minimize = Minimieren? 30 | minify_sets_label_ignore_browsercache = Browsercache umgehen? 31 | minify_sets_label_output = Ausgabe 32 | minify_sets_label_attributes = Attribute 33 | minify_sets_label_attributes_note = Zeilengetrennt
Beispiel: media="print" 34 | minify_sets_label_assets = Assets 35 | minify_sets_label_assets_note = Zeilengetrennt; Beginnt mit einem /.
Beispiel: /assets/css/styles.css 36 | 37 | minify_sets_function_clear_cache = Cache löschen 38 | minify_sets_function_clear_cache_success = Cache erfolgreich gelöscht! 39 | minify_sets_function_clear_cache_file_success = Cache Datei erfolgreich gelöscht! 40 | minify_sets_function_clear_cache_file_error = Cache Datei existiert nicht! 41 | 42 | #permissions 43 | minify_perm_general_minify[] = Rechte für Minify-Addon 44 | minify_perm_general_minify[config] = Rechte für die Seite 'Konfiguration' des Minify-Addons 45 | minify_perm_general_minify[sets] = Rechte für die Seite 'Sets' des Minify-Addons -------------------------------------------------------------------------------- /lang/pt_br.lang: -------------------------------------------------------------------------------- 1 | minify_title = Reduzir 2 | 3 | #subpage config 4 | minify_config = Configuração 5 | minify_config_minifyhtml = Reduzir HTML? 6 | minify_config_pathcss = Caminho para arquivos CSS gerados 7 | minify_config_pathcss_placeholder = /assets/cache/css 8 | minify_config_pathjs = Caminho para arquivos JS gerados 9 | minify_config_pathjs_placeholder = /assets/cache/js 10 | minify_config_hint = em relação à raiz do documento, sem barras finais 11 | minify_config_templates = Templates ausschließen 12 | minify_config_action_save = Salvar configurações 13 | minify_config_saved = Configurações salvas com sucesso! 14 | 15 | #subpage sets 16 | minify_sets = Sets 17 | minify_sets_norowsmessage = Nenhum set foi encontrado! 18 | 19 | minify_sets_column_name = Nome 20 | minify_sets_column_type = Tipo 21 | minify_sets_column_snippet = Snippet 22 | minify_sets_column_cache = Cache 23 | 24 | minify_sets_formcaption_add = Adicionar set 25 | minify_sets_formcaption_edit = Editar set 26 | 27 | minify_sets_label_name = Nome 28 | minify_sets_label_type = Tipo 29 | minify_sets_label_minimize = Minimizar? 30 | minify_sets_label_ignore_browsercache = Ignorar cachê do navegador? 31 | minify_sets_label_output = Saída 32 | minify_sets_label_attributes = Atributos 33 | minify_sets_label_attributes_note = Uma entrada por linha
example: media="print" 34 | minify_sets_label_assets = Ativos 35 | minify_sets_label_assets_note = Uma entrada por linha; Começa com um "/".
example: /ativos/css/styles.css 36 | 37 | minify_sets_function_clear_cache = Limpar cache 38 | minify_sets_function_clear_cache_success = Cache limpo com sucesso! 39 | minify_sets_function_clear_cache_file_success = Arquivo de cache limpo com sucesso! 40 | minify_sets_function_clear_cache_file_error =O arquivo cache não existe! 41 | 42 | #permissions 43 | minify_perm_general_minify[] = Permissões para o Minify-Addon 44 | minify_perm_general_minify[config] = Permissions for the page 'Configuration' of the Minify-Addon 45 | minify_perm_general_minify[sets] = Permissions for the page 'Sets' of the Minify-Addon 46 | -------------------------------------------------------------------------------- /lang/es_es.lang: -------------------------------------------------------------------------------- 1 | minify_title = Minificar 2 | 3 | #subpage config 4 | minify_config = Ajustes 5 | minify_config_minifyhtml = Minimizar el HTML? 6 | minify_config_pathcss = Ruta para archivos CSS generados 7 | minify_config_pathcss_placeholder = /assets/cache/css 8 | minify_config_pathjs = Ruta para archivos JS generados 9 | minify_config_pathjs_placeholder = /assets/cache/js 10 | minify_config_hint = relativo a la raíz del documento, sin barras inclinadas 11 | minify_config_templates = Excluir plantillas 12 | minify_config_action_save = Guardar ajustes 13 | minify_config_saved = Ajustes guardados exitosamente! 14 | 15 | #subpage sets 16 | minify_sets = Conjuntos 17 | minify_sets_norowsmessage = No se encontraron conjuntos 18 | 19 | minify_sets_column_name = Nombre 20 | minify_sets_column_type = Tipo 21 | minify_sets_column_snippet = Retazo 22 | minify_sets_column_cache = Cache 23 | 24 | minify_sets_formcaption_add = Añadir conjunto 25 | minify_sets_formcaption_edit = Editar conjunto 26 | 27 | minify_sets_label_name = Nombre 28 | minify_sets_label_type = Tipo 29 | minify_sets_label_minimize = Minimizar? 30 | minify_sets_label_ignore_browsercache = Ignorar el caché del navegador? 31 | minify_sets_label_output = Salida 32 | minify_sets_label_attributes = Atributos 33 | minify_sets_label_attributes_note = Una entrada por línea
ejemplo: media="print" 34 | minify_sets_label_assets = Bienes 35 | minify_sets_label_assets_note = Una entrada por línea; comienza con un "/".
ejemplo: /assets/css/styles.css 36 | 37 | minify_sets_function_clear_cache = Limpiar cache 38 | minify_sets_function_clear_cache_success = ¡Caché borrado exitosamente! 39 | minify_sets_function_clear_cache_file_success = Archivo de caché borrado con éxito! 40 | minify_sets_function_clear_cache_file_error = El archivo de caché no existe! 41 | 42 | #permissions 43 | minify_perm_general_minify[] = Permisos para Minify-Addon 44 | minify_perm_general_minify[config] = Permisos para la página 'Configuración' de Minify-Addon 45 | minify_perm_general_minify[sets] = Permisos para la página 'Conjuntos' del complemento Minificar 46 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'MatthiasMullie\\PathConverter\\' => 29, 13 | 'MatthiasMullie\\Minify\\' => 22, 14 | ), 15 | ); 16 | 17 | public static $prefixDirsPsr4 = array ( 18 | 'MatthiasMullie\\PathConverter\\' => 19 | array ( 20 | 0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src', 21 | ), 22 | 'MatthiasMullie\\Minify\\' => 23 | array ( 24 | 0 => __DIR__ . '/..' . '/matthiasmullie/minify/src', 25 | ), 26 | ); 27 | 28 | public static $classMap = array ( 29 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 30 | 'MatthiasMullie\\Minify\\CSS' => __DIR__ . '/..' . '/matthiasmullie/minify/src/CSS.php', 31 | 'MatthiasMullie\\Minify\\Exception' => __DIR__ . '/..' . '/matthiasmullie/minify/src/Exception.php', 32 | 'MatthiasMullie\\Minify\\Exceptions\\BasicException' => __DIR__ . '/..' . '/matthiasmullie/minify/src/Exceptions/BasicException.php', 33 | 'MatthiasMullie\\Minify\\Exceptions\\FileImportException' => __DIR__ . '/..' . '/matthiasmullie/minify/src/Exceptions/FileImportException.php', 34 | 'MatthiasMullie\\Minify\\Exceptions\\IOException' => __DIR__ . '/..' . '/matthiasmullie/minify/src/Exceptions/IOException.php', 35 | 'MatthiasMullie\\Minify\\JS' => __DIR__ . '/..' . '/matthiasmullie/minify/src/JS.php', 36 | 'MatthiasMullie\\Minify\\Minify' => __DIR__ . '/..' . '/matthiasmullie/minify/src/Minify.php', 37 | 'MatthiasMullie\\PathConverter\\Converter' => __DIR__ . '/..' . '/matthiasmullie/path-converter/src/Converter.php', 38 | 'MatthiasMullie\\PathConverter\\ConverterInterface' => __DIR__ . '/..' . '/matthiasmullie/path-converter/src/ConverterInterface.php', 39 | 'MatthiasMullie\\PathConverter\\NoConverter' => __DIR__ . '/..' . '/matthiasmullie/path-converter/src/NoConverter.php', 40 | ); 41 | 42 | public static function getInitializer(ClassLoader $loader) 43 | { 44 | return \Closure::bind(function () use ($loader) { 45 | $loader->prefixLengthsPsr4 = ComposerStaticInit790d704986df489d1de6a178d2bb1edc::$prefixLengthsPsr4; 46 | $loader->prefixDirsPsr4 = ComposerStaticInit790d704986df489d1de6a178d2bb1edc::$prefixDirsPsr4; 47 | $loader->classMap = ComposerStaticInit790d704986df489d1de6a178d2bb1edc::$classMap; 48 | 49 | }, null, ClassLoader::class); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /vendor/bin/minifyjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | realpath = realpath($opened_path) ?: $opened_path; 34 | $opened_path = $this->realpath; 35 | $this->handle = fopen($this->realpath, $mode); 36 | $this->position = 0; 37 | 38 | return (bool) $this->handle; 39 | } 40 | 41 | public function stream_read($count) 42 | { 43 | $data = fread($this->handle, $count); 44 | 45 | if ($this->position === 0) { 46 | $data = preg_replace('{^#!.*\r?\n}', '', $data); 47 | } 48 | 49 | $this->position += strlen($data); 50 | 51 | return $data; 52 | } 53 | 54 | public function stream_cast($castAs) 55 | { 56 | return $this->handle; 57 | } 58 | 59 | public function stream_close() 60 | { 61 | fclose($this->handle); 62 | } 63 | 64 | public function stream_lock($operation) 65 | { 66 | return $operation ? flock($this->handle, $operation) : true; 67 | } 68 | 69 | public function stream_seek($offset, $whence) 70 | { 71 | if (0 === fseek($this->handle, $offset, $whence)) { 72 | $this->position = ftell($this->handle); 73 | return true; 74 | } 75 | 76 | return false; 77 | } 78 | 79 | public function stream_tell() 80 | { 81 | return $this->position; 82 | } 83 | 84 | public function stream_eof() 85 | { 86 | return feof($this->handle); 87 | } 88 | 89 | public function stream_stat() 90 | { 91 | return array(); 92 | } 93 | 94 | public function stream_set_option($option, $arg1, $arg2) 95 | { 96 | return true; 97 | } 98 | 99 | public function url_stat($path, $flags) 100 | { 101 | $path = substr($path, 17); 102 | if (file_exists($path)) { 103 | return stat($path); 104 | } 105 | 106 | return false; 107 | } 108 | } 109 | } 110 | 111 | if ( 112 | (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) 113 | || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) 114 | ) { 115 | return include("phpvfscomposer://" . __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifyjs'); 116 | } 117 | } 118 | 119 | return include __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifyjs'; 120 | -------------------------------------------------------------------------------- /vendor/bin/minifycss: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | realpath = realpath($opened_path) ?: $opened_path; 34 | $opened_path = $this->realpath; 35 | $this->handle = fopen($this->realpath, $mode); 36 | $this->position = 0; 37 | 38 | return (bool) $this->handle; 39 | } 40 | 41 | public function stream_read($count) 42 | { 43 | $data = fread($this->handle, $count); 44 | 45 | if ($this->position === 0) { 46 | $data = preg_replace('{^#!.*\r?\n}', '', $data); 47 | } 48 | 49 | $this->position += strlen($data); 50 | 51 | return $data; 52 | } 53 | 54 | public function stream_cast($castAs) 55 | { 56 | return $this->handle; 57 | } 58 | 59 | public function stream_close() 60 | { 61 | fclose($this->handle); 62 | } 63 | 64 | public function stream_lock($operation) 65 | { 66 | return $operation ? flock($this->handle, $operation) : true; 67 | } 68 | 69 | public function stream_seek($offset, $whence) 70 | { 71 | if (0 === fseek($this->handle, $offset, $whence)) { 72 | $this->position = ftell($this->handle); 73 | return true; 74 | } 75 | 76 | return false; 77 | } 78 | 79 | public function stream_tell() 80 | { 81 | return $this->position; 82 | } 83 | 84 | public function stream_eof() 85 | { 86 | return feof($this->handle); 87 | } 88 | 89 | public function stream_stat() 90 | { 91 | return array(); 92 | } 93 | 94 | public function stream_set_option($option, $arg1, $arg2) 95 | { 96 | return true; 97 | } 98 | 99 | public function url_stat($path, $flags) 100 | { 101 | $path = substr($path, 17); 102 | if (file_exists($path)) { 103 | return stat($path); 104 | } 105 | 106 | return false; 107 | } 108 | } 109 | } 110 | 111 | if ( 112 | (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true)) 113 | || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) 114 | ) { 115 | return include("phpvfscomposer://" . __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifycss'); 116 | } 117 | } 118 | 119 | return include __DIR__ . '/..'.'/matthiasmullie/minify/bin/minifycss'; 120 | -------------------------------------------------------------------------------- /pages/config.php: -------------------------------------------------------------------------------- 1 | setConfig(rex_post('config', [ 7 | ['debugmode', 'bool'], 8 | ['minifyhtml', 'bool'], 9 | ['pathcss', 'string'], 10 | ['pathjs', 'string'], 11 | ['templates', 'array[int]'], 12 | ])); 13 | 14 | $post = rex_post('config'); 15 | try { 16 | rex_dir::create(rex_path::base(substr($post['pathcss'], 1))); 17 | rex_dir::create(rex_path::base(substr($post['pathjs'], 1))); 18 | $content .= rex_view::info($this->i18n('config_saved')); 19 | } catch (rex_exception $e) { 20 | $content .= rex_view::warning($this->i18n('config_error')); 21 | } 22 | } 23 | 24 | $content .= '
'; 25 | $content .= '
'; 26 | $content .= '
'; 27 | 28 | $formElements = []; 29 | 30 | //Start - minify_html 31 | $n = []; 32 | $n['label'] = ''; 33 | $n['field'] = 'getConfig('minifyhtml') ? ' checked="checked"' : '') . '>'; 34 | $formElements[] = $n; 35 | //End - minify_html 36 | 37 | //Start - path_css 38 | $n = []; 39 | $n['label'] = ''; 40 | $n['field'] = ''; 41 | $n['note'] = rex_i18n::rawMsg('minify_config_hint'); 42 | $formElements[] = $n; 43 | //End - path_css 44 | 45 | //Start - path_js 46 | $n = []; 47 | $n['label'] = ''; 48 | $n['field'] = ''; 49 | $n['note'] = rex_i18n::rawMsg('minify_config_hint'); 50 | $formElements[] = $n; 51 | //End - path_js 52 | 53 | //Start - templates 54 | $n = []; 55 | $n['label'] = ''; 56 | $select = new rex_select(); 57 | $select->setId('minify-config-templates'); 58 | $select->setMultiple(); 59 | $select->setAttribute('class', 'form-control selectpicker'); 60 | $select->setAttribute('data-live-search', 'true'); 61 | $select->setName('config[templates][]'); 62 | $select->addSqlOptions('SELECT `name`, `id` FROM `' . rex::getTable('template') . '` ORDER BY `name` ASC'); 63 | $select->setSelected($this->getConfig('templates')); 64 | $n['field'] = $select->get(); 65 | $formElements[] = $n; 66 | //End - templates 67 | 68 | $fragment = new rex_fragment(); 69 | $fragment->setVar('elements', $formElements, false); 70 | $content .= $fragment->parse('core/form/form.php'); 71 | 72 | $content .= '
'; 73 | 74 | $content .= '
'; 75 | 76 | $formElements = []; 77 | 78 | $n = []; 79 | $n['field'] = 'i18n('config_action_save'), 'save') . '>'; 80 | $formElements[] = $n; 81 | 82 | $fragment = new rex_fragment(); 83 | $fragment->setVar('elements', $formElements, false); 84 | $content .= $fragment->parse('core/form/submit.php'); 85 | 86 | $content .= '
'; 87 | $content .= '
'; 88 | $content .= '
'; 89 | 90 | $fragment = new rex_fragment(); 91 | $fragment->setVar('class', 'edit'); 92 | $fragment->setVar('title', $this->i18n('config')); 93 | $fragment->setVar('body', $content, false); 94 | echo $fragment->parse('core/page/section.php'); 95 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "name": "matthiasmullie/minify", 5 | "version": "1.3.73", 6 | "version_normalized": "1.3.73.0", 7 | "source": { 8 | "type": "git", 9 | "url": "https://github.com/matthiasmullie/minify.git", 10 | "reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a" 11 | }, 12 | "dist": { 13 | "type": "zip", 14 | "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/cb7a9297b4ab070909cefade30ee95054d4ae87a", 15 | "reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a", 16 | "shasum": "" 17 | }, 18 | "require": { 19 | "ext-pcre": "*", 20 | "matthiasmullie/path-converter": "~1.1", 21 | "php": ">=5.3.0" 22 | }, 23 | "require-dev": { 24 | "friendsofphp/php-cs-fixer": ">=2.0", 25 | "matthiasmullie/scrapbook": ">=1.3", 26 | "phpunit/phpunit": ">=4.8", 27 | "squizlabs/php_codesniffer": ">=3.0" 28 | }, 29 | "suggest": { 30 | "psr/cache-implementation": "Cache implementation to use with Minify::cache" 31 | }, 32 | "time": "2024-03-15T10:27:10+00:00", 33 | "bin": [ 34 | "bin/minifycss", 35 | "bin/minifyjs" 36 | ], 37 | "type": "library", 38 | "installation-source": "dist", 39 | "autoload": { 40 | "psr-4": { 41 | "MatthiasMullie\\Minify\\": "src/" 42 | } 43 | }, 44 | "notification-url": "https://packagist.org/downloads/", 45 | "license": [ 46 | "MIT" 47 | ], 48 | "authors": [ 49 | { 50 | "name": "Matthias Mullie", 51 | "email": "minify@mullie.eu", 52 | "homepage": "https://www.mullie.eu", 53 | "role": "Developer" 54 | } 55 | ], 56 | "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", 57 | "homepage": "https://github.com/matthiasmullie/minify", 58 | "keywords": [ 59 | "JS", 60 | "css", 61 | "javascript", 62 | "minifier", 63 | "minify" 64 | ], 65 | "support": { 66 | "issues": "https://github.com/matthiasmullie/minify/issues", 67 | "source": "https://github.com/matthiasmullie/minify/tree/1.3.73" 68 | }, 69 | "funding": [ 70 | { 71 | "url": "https://github.com/matthiasmullie", 72 | "type": "github" 73 | } 74 | ], 75 | "install-path": "../matthiasmullie/minify" 76 | }, 77 | { 78 | "name": "matthiasmullie/path-converter", 79 | "version": "1.1.3", 80 | "version_normalized": "1.1.3.0", 81 | "source": { 82 | "type": "git", 83 | "url": "https://github.com/matthiasmullie/path-converter.git", 84 | "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9" 85 | }, 86 | "dist": { 87 | "type": "zip", 88 | "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9", 89 | "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9", 90 | "shasum": "" 91 | }, 92 | "require": { 93 | "ext-pcre": "*", 94 | "php": ">=5.3.0" 95 | }, 96 | "require-dev": { 97 | "phpunit/phpunit": "~4.8" 98 | }, 99 | "time": "2019-02-05T23:41:09+00:00", 100 | "type": "library", 101 | "installation-source": "dist", 102 | "autoload": { 103 | "psr-4": { 104 | "MatthiasMullie\\PathConverter\\": "src/" 105 | } 106 | }, 107 | "notification-url": "https://packagist.org/downloads/", 108 | "license": [ 109 | "MIT" 110 | ], 111 | "authors": [ 112 | { 113 | "name": "Matthias Mullie", 114 | "email": "pathconverter@mullie.eu", 115 | "homepage": "http://www.mullie.eu", 116 | "role": "Developer" 117 | } 118 | ], 119 | "description": "Relative path converter", 120 | "homepage": "http://github.com/matthiasmullie/path-converter", 121 | "keywords": [ 122 | "converter", 123 | "path", 124 | "paths", 125 | "relative" 126 | ], 127 | "install-path": "../matthiasmullie/path-converter" 128 | } 129 | ], 130 | "dev": true, 131 | "dev-package-names": [] 132 | } 133 | -------------------------------------------------------------------------------- /pages/sets.php: -------------------------------------------------------------------------------- 1 | addTableAttribute('class', 'table-striped'); 18 | $list->setNoRowsMessage($this->i18n('sets_norowsmessage')); 19 | 20 | // icon column 21 | $thIcon = ''; 22 | $tdIcon = ''; 23 | $list->addColumn($thIcon, $tdIcon, 0, ['###VALUE###', '###VALUE###']); 24 | $list->setColumnParams($thIcon, ['func' => 'edit', 'id' => '###id###']); 25 | 26 | $list->setColumnLabel('name', $this->i18n('sets_column_name')); 27 | $list->setColumnLabel('type', $this->i18n('sets_column_type')); 28 | $list->setColumnLabel('snippet', $this->i18n('sets_column_snippet')); 29 | 30 | $tdIcon = ''; 31 | $list->addColumn($this->i18n('sets_column_cache'), $tdIcon, 5, ['###VALUE###', '###VALUE###']); 32 | $list->setColumnParams($this->i18n('sets_column_cache'), ['func' => '', 'cache' => 'delete_cache_file', 'name' => '###name###', 'type' => '###type###']); 33 | 34 | $list->setColumnParams('name', ['id' => '###id###', 'func' => 'edit']); 35 | 36 | $list->removeColumn('id'); 37 | 38 | $content = $list->get(); 39 | 40 | $fragment = new rex_fragment(); 41 | $fragment->setVar('class', 'edit'); 42 | $fragment->setVar('title', $this->i18n('sets')); 43 | $fragment->setVar('content', $content, false); 44 | echo $fragment->parse('core/page/section.php'); 45 | } elseif ('add' == $func || 'edit' == $func) { 46 | $id = rex_request('id', 'int'); 47 | 48 | if ('edit' == $func) { 49 | $formLabel = $this->i18n('sets_formcaption_edit'); 50 | } elseif ('add' == $func) { 51 | $formLabel = $this->i18n('sets_formcaption_add'); 52 | } 53 | 54 | $form = rex_form::factory(rex::getTablePrefix() . 'minify_sets', '', 'id=' . $id); 55 | 56 | //Start - add name-field 57 | $field = $form->addTextField('name'); 58 | $field->setLabel($this->i18n('sets_label_name')); 59 | //End - add name-field 60 | 61 | //Start - add type-field 62 | $field = $form->addSelectField('type'); 63 | $field->setLabel($this->i18n('sets_label_type')); 64 | $field->setAttribute('class', 'form-control selectpicker'); 65 | 66 | $select = $field->getSelect(); 67 | $select->addOptions([ 68 | 'css' => 'CSS', 69 | 'js' => 'JS', 70 | ]); 71 | //End - add type-field 72 | 73 | //Start - add minimize-field 74 | $field = $form->addSelectField('minimize'); 75 | $field->setLabel($this->i18n('sets_label_minimize')); 76 | $field->setAttribute('class', 'form-control selectpicker'); 77 | 78 | $select = $field->getSelect(); 79 | $select->addOptions([ 80 | 'no' => 'Nein', 81 | 'yes' => 'Ja', 82 | ]); 83 | //End - add minimize-field 84 | 85 | //Start - add ignore_browsercache-field 86 | $field = $form->addSelectField('ignore_browsercache'); 87 | $field->setLabel($this->i18n('sets_label_ignore_browsercache')); 88 | $field->setAttribute('class', 'form-control selectpicker'); 89 | 90 | $select = $field->getSelect(); 91 | $select->addOptions([ 92 | 'no' => 'Nein', 93 | 'yes' => 'Ja', 94 | ]); 95 | //End - add ignore_browsercache-field 96 | 97 | //Start - add attributes-field 98 | $field = $form->addTextAreaField('attributes'); 99 | $field->setLabel($this->i18n('sets_label_attributes')); 100 | $field = $form->addRawField('
 

' . $this->i18n('sets_label_attributes_note') . '

'); 101 | //End - add attributes-field 102 | 103 | //Start - add output-field 104 | $field = $form->addSelectField('output'); 105 | $field->setLabel($this->i18n('sets_label_output')); 106 | $field->setAttribute('class', 'form-control selectpicker'); 107 | 108 | $select = $field->getSelect(); 109 | $select->addOptions([ 110 | 'file' => 'Datei', 111 | 'inline' => 'Inline', 112 | ]); 113 | //End - add output-field 114 | 115 | //Start - add assets-field 116 | $field = $form->addTextAreaField('assets'); 117 | $field->setLabel($this->i18n('sets_label_assets')); 118 | $field = $form->addRawField('
 

' . $this->i18n('sets_label_assets_note') . '

'); 119 | //End - add assets-field 120 | 121 | if ('edit' == $func) { 122 | $form->addParam('id', $id); 123 | } 124 | 125 | $content = $form->get(); 126 | 127 | $fragment = new rex_fragment(); 128 | $fragment->setVar('class', 'edit', false); 129 | $fragment->setVar('title', $formLabel, false); 130 | $fragment->setVar('body', $content, false); 131 | $content = $fragment->parse('core/page/section.php'); 132 | 133 | echo $content; 134 | } 135 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "e7806260d775937d439159cde5165225", 8 | "packages": [ 9 | { 10 | "name": "matthiasmullie/minify", 11 | "version": "1.3.73", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/matthiasmullie/minify.git", 15 | "reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/cb7a9297b4ab070909cefade30ee95054d4ae87a", 20 | "reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-pcre": "*", 25 | "matthiasmullie/path-converter": "~1.1", 26 | "php": ">=5.3.0" 27 | }, 28 | "require-dev": { 29 | "friendsofphp/php-cs-fixer": ">=2.0", 30 | "matthiasmullie/scrapbook": ">=1.3", 31 | "phpunit/phpunit": ">=4.8", 32 | "squizlabs/php_codesniffer": ">=3.0" 33 | }, 34 | "suggest": { 35 | "psr/cache-implementation": "Cache implementation to use with Minify::cache" 36 | }, 37 | "bin": [ 38 | "bin/minifycss", 39 | "bin/minifyjs" 40 | ], 41 | "type": "library", 42 | "autoload": { 43 | "psr-4": { 44 | "MatthiasMullie\\Minify\\": "src/" 45 | } 46 | }, 47 | "notification-url": "https://packagist.org/downloads/", 48 | "license": [ 49 | "MIT" 50 | ], 51 | "authors": [ 52 | { 53 | "name": "Matthias Mullie", 54 | "email": "minify@mullie.eu", 55 | "homepage": "https://www.mullie.eu", 56 | "role": "Developer" 57 | } 58 | ], 59 | "description": "CSS & JavaScript minifier, in PHP. Removes whitespace, strips comments, combines files (incl. @import statements and small assets in CSS files), and optimizes/shortens a few common programming patterns.", 60 | "homepage": "https://github.com/matthiasmullie/minify", 61 | "keywords": [ 62 | "JS", 63 | "css", 64 | "javascript", 65 | "minifier", 66 | "minify" 67 | ], 68 | "support": { 69 | "issues": "https://github.com/matthiasmullie/minify/issues", 70 | "source": "https://github.com/matthiasmullie/minify/tree/1.3.73" 71 | }, 72 | "funding": [ 73 | { 74 | "url": "https://github.com/matthiasmullie", 75 | "type": "github" 76 | } 77 | ], 78 | "time": "2024-03-15T10:27:10+00:00" 79 | }, 80 | { 81 | "name": "matthiasmullie/path-converter", 82 | "version": "1.1.3", 83 | "source": { 84 | "type": "git", 85 | "url": "https://github.com/matthiasmullie/path-converter.git", 86 | "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9" 87 | }, 88 | "dist": { 89 | "type": "zip", 90 | "url": "https://api.github.com/repos/matthiasmullie/path-converter/zipball/e7d13b2c7e2f2268e1424aaed02085518afa02d9", 91 | "reference": "e7d13b2c7e2f2268e1424aaed02085518afa02d9", 92 | "shasum": "" 93 | }, 94 | "require": { 95 | "ext-pcre": "*", 96 | "php": ">=5.3.0" 97 | }, 98 | "require-dev": { 99 | "phpunit/phpunit": "~4.8" 100 | }, 101 | "type": "library", 102 | "autoload": { 103 | "psr-4": { 104 | "MatthiasMullie\\PathConverter\\": "src/" 105 | } 106 | }, 107 | "notification-url": "https://packagist.org/downloads/", 108 | "license": [ 109 | "MIT" 110 | ], 111 | "authors": [ 112 | { 113 | "name": "Matthias Mullie", 114 | "email": "pathconverter@mullie.eu", 115 | "homepage": "http://www.mullie.eu", 116 | "role": "Developer" 117 | } 118 | ], 119 | "description": "Relative path converter", 120 | "homepage": "http://github.com/matthiasmullie/path-converter", 121 | "keywords": [ 122 | "converter", 123 | "path", 124 | "paths", 125 | "relative" 126 | ], 127 | "support": { 128 | "issues": "https://github.com/matthiasmullie/path-converter/issues", 129 | "source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3" 130 | }, 131 | "time": "2019-02-05T23:41:09+00:00" 132 | } 133 | ], 134 | "packages-dev": [], 135 | "aliases": [], 136 | "minimum-stability": "stable", 137 | "stability-flags": {}, 138 | "prefer-stable": false, 139 | "prefer-lowest": false, 140 | "platform": {}, 141 | "platform-dev": {}, 142 | "plugin-api-version": "2.6.0" 143 | } 144 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/path-converter/src/Converter.php: -------------------------------------------------------------------------------- 1 | 16 | * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved 17 | * @license MIT License 18 | */ 19 | class Converter implements ConverterInterface 20 | { 21 | /** 22 | * @var string 23 | */ 24 | protected $from; 25 | 26 | /** 27 | * @var string 28 | */ 29 | protected $to; 30 | 31 | /** 32 | * @param string $from The original base path (directory, not file!) 33 | * @param string $to The new base path (directory, not file!) 34 | * @param string $root Root directory (defaults to `getcwd`) 35 | */ 36 | public function __construct($from, $to, $root = '') 37 | { 38 | $shared = $this->shared($from, $to); 39 | if ($shared === '') { 40 | // when both paths have nothing in common, one of them is probably 41 | // absolute while the other is relative 42 | $root = $root ?: getcwd(); 43 | $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from); 44 | $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to); 45 | 46 | // or traveling the tree via `..` 47 | // attempt to resolve path, or assume it's fine if it doesn't exist 48 | $from = @realpath($from) ?: $from; 49 | $to = @realpath($to) ?: $to; 50 | } 51 | 52 | $from = $this->dirname($from); 53 | $to = $this->dirname($to); 54 | 55 | $from = $this->normalize($from); 56 | $to = $this->normalize($to); 57 | 58 | $this->from = $from; 59 | $this->to = $to; 60 | } 61 | 62 | /** 63 | * Normalize path. 64 | * 65 | * @param string $path 66 | * 67 | * @return string 68 | */ 69 | protected function normalize($path) 70 | { 71 | // deal with different operating systems' directory structure 72 | $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/'); 73 | 74 | // remove leading current directory. 75 | if (substr($path, 0, 2) === './') { 76 | $path = substr($path, 2); 77 | } 78 | 79 | // remove references to current directory in the path. 80 | $path = str_replace('/./', '/', $path); 81 | 82 | /* 83 | * Example: 84 | * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif 85 | * to 86 | * /home/forkcms/frontend/core/layout/images/img.gif 87 | */ 88 | do { 89 | $path = preg_replace('/[^\/]+(? $chunk) { 122 | if (isset($path2[$i]) && $path1[$i] == $path2[$i]) { 123 | $shared[] = $chunk; 124 | } else { 125 | break; 126 | } 127 | } 128 | 129 | return implode('/', $shared); 130 | } 131 | 132 | /** 133 | * Convert paths relative from 1 file to another. 134 | * 135 | * E.g. 136 | * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css 137 | * should become: 138 | * ../../core/layout/images/img.gif relative to 139 | * /home/forkcms/frontend/cache/minified_css 140 | * 141 | * @param string $path The relative path that needs to be converted 142 | * 143 | * @return string The new relative path 144 | */ 145 | public function convert($path) 146 | { 147 | // quit early if conversion makes no sense 148 | if ($this->from === $this->to) { 149 | return $path; 150 | } 151 | 152 | $path = $this->normalize($path); 153 | // if we're not dealing with a relative path, just return absolute 154 | if (strpos($path, '/') === 0) { 155 | return $path; 156 | } 157 | 158 | // normalize paths 159 | $path = $this->normalize($this->from.'/'.$path); 160 | 161 | // strip shared ancestor paths 162 | $shared = $this->shared($path, $this->to); 163 | $path = mb_substr($path, mb_strlen($shared)); 164 | $to = mb_substr($this->to, mb_strlen($shared)); 165 | 166 | // add .. for every directory that needs to be traversed to new path 167 | $to = str_repeat('../', count(array_filter(explode('/', $to)))); 168 | 169 | return $to.ltrim($path, '/'); 170 | } 171 | 172 | /** 173 | * Attempt to get the directory name from a path. 174 | * 175 | * @param string $path 176 | * 177 | * @return string 178 | */ 179 | protected function dirname($path) 180 | { 181 | if (@is_file($path)) { 182 | return dirname($path); 183 | } 184 | 185 | if (@is_dir($path)) { 186 | return rtrim($path, '/'); 187 | } 188 | 189 | // no known file/dir, start making assumptions 190 | 191 | // ends in / = dir 192 | if (mb_substr($path, -1) === '/') { 193 | return rtrim($path, '/'); 194 | } 195 | 196 | // has a dot in the name, likely a file 197 | if (preg_match('/.*\..*$/', basename($path)) !== 0) { 198 | return dirname($path); 199 | } 200 | 201 | // you're on your own here! 202 | return $path; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /lib/class.minify.php: -------------------------------------------------------------------------------- 1 | addon = rex_addon::get(__CLASS__); 11 | } 12 | 13 | public function addFile($file, $set = 'default') 14 | { 15 | $this->files[$set][] = $file; 16 | } 17 | 18 | public static function isSCSS($file) 19 | { 20 | $pos = strrpos($file, '.'); 21 | if (false !== $pos) { 22 | return 'scss' == substr($file, $pos + 1); 23 | } 24 | 25 | return false; 26 | } 27 | 28 | public static function compileFile($file, $type = 'scss') 29 | { 30 | $compiledFilename = rex_path::addonAssets(__CLASS__, 'cache' . '/compiled.scss.' . str_replace('.scss', '.css', basename($file))); 31 | 32 | $compiler = new rex_scss_compiler(); 33 | $compiler->setScssFile(self::absolutePath($file)); 34 | $compiler->setCssFile($compiledFilename); 35 | $compiler->compile(); 36 | 37 | return $compiledFilename; 38 | } 39 | 40 | public static function absolutePath($file) 41 | { 42 | return rex_path::base($file); 43 | } 44 | 45 | public static function relativePath($file) 46 | { 47 | return substr($file, strlen(rex_path::base('')) - 1); 48 | } 49 | 50 | public function minify($type, $set = 'default', $output = 'file') 51 | { 52 | if (!in_array($type, ['css', 'js'])) { 53 | die('Minifyerror: Unknown type "' . $type . '"'); 54 | } 55 | 56 | $minify = false; 57 | $oldCache = []; 58 | $newCache = []; 59 | 60 | if (file_exists(rex_path::addonCache(__CLASS__, $type . '_' . rex_string::normalize($set) . '.json'))) { 61 | $string = rex_file::get(rex_path::addonCache(__CLASS__, $type . '_' . rex_string::normalize($set) . '.json', '')); 62 | $oldCache = json_decode($string, true); 63 | } 64 | 65 | if (!empty($this->files[$set])) { 66 | foreach ($this->files[$set] as $file) { 67 | if (!file_exists(trim(rex_path::base(substr($file, 1))))) { 68 | die('Minifyerror: File "' . $file . '" does not exists'); 69 | } 70 | 71 | //Start - get timestamp of the file 72 | $newCache[$file] = filemtime(trim(rex_path::base(substr($file, 1)))); 73 | //End - get timestamp of the file 74 | 75 | if (empty($oldCache[$file])) { 76 | $minify = true; 77 | } else { 78 | if ($newCache[$file] > $oldCache[$file]) { 79 | $minify = true; 80 | } 81 | } 82 | } 83 | 84 | //Start - save path into cachefile 85 | if (!$minify) { 86 | $path = $oldCache['path']; 87 | } 88 | //End - save path into cachefile 89 | 90 | if ($minify) { 91 | switch ($type) { 92 | case 'css': 93 | $path = rex_path::base(substr($this->addon->getConfig('pathcss'), 1) . '/bundled.' . rex_string::normalize($set) . '.' . $type); 94 | $minifier = new MatthiasMullie\Minify\CSS(); 95 | 96 | break; 97 | case 'js': 98 | $path = rex_path::base(substr($this->addon->getConfig('pathjs'), 1) . '/bundled.' . rex_string::normalize($set) . '.' . $type); 99 | $minifier = new MatthiasMullie\Minify\JS(); 100 | 101 | break; 102 | } 103 | 104 | $newCache['path'] = $path; 105 | 106 | if (!rex_file::put(rex_path::addonCache(__CLASS__, $type . '_' . rex_string::normalize($set) . '.json'), json_encode($newCache))) { 107 | echo 'Cachefile für ' . $type . ' konnte nicht geschrieben werden!'; 108 | } 109 | 110 | foreach ($this->files[$set] as $file) { 111 | $file = trim($file); 112 | 113 | if (self::isSCSS($file)) { 114 | $compiledFilename = self::compileFile($file, 'scss'); 115 | $minifier->add($compiledFilename); 116 | } else { 117 | $minifier->add(rex_path::base(substr($file, 1))); 118 | } 119 | } 120 | 121 | $minifier->minify($path); 122 | } 123 | 124 | switch ($output) { 125 | case 'file': 126 | return self::relativePath($path); 127 | 128 | break; 129 | case 'inline': 130 | return rex_file::get($path); 131 | 132 | break; 133 | } 134 | } 135 | 136 | return false; 137 | } 138 | 139 | public static function clearCacheFiles() 140 | { 141 | $class = new self(); 142 | $addon = $class->addon; 143 | 144 | $table = rex::getTable('minify_sets'); 145 | $sql = rex_sql::factory(); 146 | $sql->setDebug(false); 147 | $sql->setTable($table); 148 | $sets = $sql->select('`name`, `type`')->getArray(); 149 | 150 | //Start - delete minify files 151 | foreach ($sets as $set) { 152 | self::deleteSetFile($set['name'], $set['type'], 'clear_cache'); 153 | } 154 | 155 | echo rex_view::success($addon->i18n('minify_sets_function_clear_cache_success')); 156 | } 157 | 158 | public static function deleteSetFile(string $set_name = '', string $set_type = '', string $func = '') 159 | { 160 | if (!empty($set_name) && !empty($set_type)) { 161 | $class = new self(); 162 | $addon = $class->addon; 163 | 164 | $table = rex::getTable('minify_sets'); 165 | $sql = rex_sql::factory(); 166 | $sql->setDebug(false); 167 | $sql->setTable($table); 168 | $sql->setWhere([ 169 | 'name' => $set_name, 170 | 'type' => $set_type, 171 | ]); 172 | $set = $sql->select('`name`, `type`'); 173 | 174 | $set_name = $sql->getValue('name'); 175 | $set_type = $sql->getValue('type'); 176 | 177 | if ('css' == $set_type) { 178 | $path = substr(rex_path::base(), 0, -1) . $addon->getConfig('pathcss') . '/'; 179 | } elseif ('js' == $set_type) { 180 | $path = substr(rex_path::base(), 0, -1) . $addon->getConfig('pathjs') . '/'; 181 | } 182 | 183 | $file = $path . 'bundled.' . $set_name . '.' . $set_type; 184 | if (file_exists($file)) { 185 | unlink($file); 186 | if (empty($func)) { 187 | echo rex_view::success($addon->i18n('minify_sets_function_clear_cache_file_success')); 188 | } 189 | } else { 190 | if (empty($func)) { 191 | echo rex_view::error($addon->i18n('minify_sets_function_clear_cache_file_error')); 192 | } 193 | } 194 | 195 | //Start - delete all addon cache files 196 | $path = $addon->getCachePath(''); 197 | foreach (glob($path . '*.json') as $filename) { 198 | unlink($filename); 199 | } 200 | // End - delete all cache files 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /boot.php: -------------------------------------------------------------------------------- 1 | getSubject(); 18 | 19 | $whitelistTemplates = rex_addon::get('minify')->getConfig('templates', []); 20 | if (null !== rex_article::getCurrent() && !in_array(rex_article::getCurrent()->getTemplateId(), $whitelistTemplates)) { 21 | preg_match_all("/REX_MINIFY\[type=(.*?)\ set=(.*?)\]/", $content, $matches, PREG_SET_ORDER); 22 | 23 | foreach ($matches as $match) { 24 | //Start - get set by name and type 25 | $sql = rex_sql::factory(); 26 | $sets = $sql->getArray('SELECT `minimize`, `ignore_browsercache`, `assets`, `attributes`, `output` FROM `' . rex::getTable('minify_sets') . '` WHERE `type` = ? AND `name` = ?', [$match[1], $match[2]]); 27 | unset($sql); 28 | //End - get set by name and type 29 | 30 | if (!empty($sets)) { 31 | $assets = explode(PHP_EOL, trim($sets[0]['assets'])); 32 | 33 | if ('no' == $sets[0]['minimize']) { 34 | $assetsContent = ''; 35 | foreach ($assets as $asset) { 36 | switch ($match[1]) { 37 | case 'css': 38 | if (minify::isSCSS($asset)) { 39 | $asset = minify::compileFile($asset, 'scss'); 40 | } else { 41 | $asset = trim(rex_path::base(substr($asset, 1))); 42 | } 43 | 44 | switch ($sets[0]['output']) { 45 | case 'inline': 46 | $assetsContent = ''; 47 | 48 | break; 49 | default: 50 | // Parse attributes to check for custom rel attribute 51 | $attributes = !empty($sets[0]['attributes']) ? explode(PHP_EOL, $sets[0]['attributes']) : []; 52 | $hasRelAttribute = false; 53 | $attributesStr = ''; 54 | 55 | // Efficiently check for rel attribute and build attributes string 56 | if (!empty($attributes)) { 57 | foreach ($attributes as $attr) { 58 | if (preg_match('/^rel\s*=/', $attr)) { 59 | $hasRelAttribute = true; 60 | } 61 | $attributesStr .= ' ' . $attr; 62 | } 63 | } 64 | 65 | // Add rel="stylesheet" only if no custom rel is provided 66 | $assetsContent .= ''; 70 | break; 71 | } 72 | 73 | break; 74 | case 'js': 75 | $asset = trim(rex_path::base(substr($asset, 1))); 76 | 77 | switch ($sets[0]['output']) { 78 | case 'inline': 79 | $assetsContent .= ''; 80 | 81 | break; 82 | default: 83 | $assetsContent .= ''; 84 | 85 | break; 86 | } 87 | 88 | break; 89 | } 90 | } 91 | 92 | $content = str_replace($match[0], $assetsContent, $content); 93 | } else { 94 | $minify = new minify(); 95 | foreach ($assets as $asset) { 96 | $minify->addFile($asset, $match[2]); 97 | } 98 | 99 | $data = $minify->minify($match[1], $match[2], $sets[0]['output']); 100 | 101 | switch ($match[1]) { 102 | case 'css': 103 | switch ($sets[0]['output']) { 104 | case 'inline': 105 | $content = str_replace($match[0], '', $content); 106 | 107 | break; 108 | default: 109 | // Parse attributes to check for custom rel attribute for minimized files 110 | $attributes = !empty($sets[0]['attributes']) ? explode(PHP_EOL, $sets[0]['attributes']) : []; 111 | $hasRelAttribute = false; 112 | $attributesStr = ''; 113 | 114 | // Efficiently check for rel attribute and build attributes string 115 | if (!empty($attributes)) { 116 | foreach ($attributes as $attr) { 117 | if (preg_match('/^rel\s*=/', $attr)) { 118 | $hasRelAttribute = true; 119 | } 120 | $attributesStr .= ' ' . $attr; 121 | } 122 | } 123 | 124 | // Add rel="stylesheet" only if no custom rel is provided 125 | $content = str_replace($match[0], 126 | '', 130 | $content); 131 | break; 132 | } 133 | 134 | break; 135 | case 'js': 136 | switch ($sets[0]['output']) { 137 | case 'inline': 138 | $content = str_replace($match[0], '', $content); 139 | 140 | break; 141 | default: 142 | $content = str_replace($match[0], '', $content); 143 | 144 | break; 145 | } 146 | 147 | break; 148 | } 149 | } 150 | } else { 151 | $content = str_replace($match[0], '', $content); 152 | } 153 | } 154 | 155 | //Start - minify html 156 | if ($this->getConfig('minifyhtml')) { 157 | if (rex_addon::get('search_it')->isAvailable()) { 158 | $pattern = '//is'; 159 | } else { 160 | $pattern = '//is'; 161 | } 162 | $content = preg_replace([$pattern, '/[[:blank:]]+/'], ['', ' '], str_replace(["\n", "\r", "\t"], '', $content)); 163 | } 164 | //End - minify html 165 | } 166 | 167 | //Start - set old php.ini-settings 168 | ini_set('pcre.backtrack_limit', $currentBacktrackLimit); 169 | ini_set('pcre.recursion_limit', $currentRecursionLimit); 170 | //End - set old php.ini-settings 171 | 172 | $ep->setSubject($content); 173 | }); 174 | } 175 | -------------------------------------------------------------------------------- /vendor/composer/InstalledVersions.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer; 14 | 15 | use Composer\Autoload\ClassLoader; 16 | use Composer\Semver\VersionParser; 17 | 18 | /** 19 | * This class is copied in every Composer installed project and available to all 20 | * 21 | * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22 | * 23 | * To require its presence, you can require `composer-runtime-api ^2.0` 24 | * 25 | * @final 26 | */ 27 | class InstalledVersions 28 | { 29 | /** 30 | * @var mixed[]|null 31 | * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null 32 | */ 33 | private static $installed; 34 | 35 | /** 36 | * @var bool|null 37 | */ 38 | private static $canGetVendors; 39 | 40 | /** 41 | * @var array[] 42 | * @psalm-var array}> 43 | */ 44 | private static $installedByVendor = array(); 45 | 46 | /** 47 | * Returns a list of all package names which are present, either by being installed, replaced or provided 48 | * 49 | * @return string[] 50 | * @psalm-return list 51 | */ 52 | public static function getInstalledPackages() 53 | { 54 | $packages = array(); 55 | foreach (self::getInstalled() as $installed) { 56 | $packages[] = array_keys($installed['versions']); 57 | } 58 | 59 | if (1 === \count($packages)) { 60 | return $packages[0]; 61 | } 62 | 63 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 64 | } 65 | 66 | /** 67 | * Returns a list of all package names with a specific type e.g. 'library' 68 | * 69 | * @param string $type 70 | * @return string[] 71 | * @psalm-return list 72 | */ 73 | public static function getInstalledPackagesByType($type) 74 | { 75 | $packagesByType = array(); 76 | 77 | foreach (self::getInstalled() as $installed) { 78 | foreach ($installed['versions'] as $name => $package) { 79 | if (isset($package['type']) && $package['type'] === $type) { 80 | $packagesByType[] = $name; 81 | } 82 | } 83 | } 84 | 85 | return $packagesByType; 86 | } 87 | 88 | /** 89 | * Checks whether the given package is installed 90 | * 91 | * This also returns true if the package name is provided or replaced by another package 92 | * 93 | * @param string $packageName 94 | * @param bool $includeDevRequirements 95 | * @return bool 96 | */ 97 | public static function isInstalled($packageName, $includeDevRequirements = true) 98 | { 99 | foreach (self::getInstalled() as $installed) { 100 | if (isset($installed['versions'][$packageName])) { 101 | return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * Checks whether the given package satisfies a version constraint 110 | * 111 | * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 112 | * 113 | * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 114 | * 115 | * @param VersionParser $parser Install composer/semver to have access to this class and functionality 116 | * @param string $packageName 117 | * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package 118 | * @return bool 119 | */ 120 | public static function satisfies(VersionParser $parser, $packageName, $constraint) 121 | { 122 | $constraint = $parser->parseConstraints((string) $constraint); 123 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 124 | 125 | return $provided->matches($constraint); 126 | } 127 | 128 | /** 129 | * Returns a version constraint representing all the range(s) which are installed for a given package 130 | * 131 | * It is easier to use this via isInstalled() with the $constraint argument if you need to check 132 | * whether a given version of a package is installed, and not just whether it exists 133 | * 134 | * @param string $packageName 135 | * @return string Version constraint usable with composer/semver 136 | */ 137 | public static function getVersionRanges($packageName) 138 | { 139 | foreach (self::getInstalled() as $installed) { 140 | if (!isset($installed['versions'][$packageName])) { 141 | continue; 142 | } 143 | 144 | $ranges = array(); 145 | if (isset($installed['versions'][$packageName]['pretty_version'])) { 146 | $ranges[] = $installed['versions'][$packageName]['pretty_version']; 147 | } 148 | if (array_key_exists('aliases', $installed['versions'][$packageName])) { 149 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 150 | } 151 | if (array_key_exists('replaced', $installed['versions'][$packageName])) { 152 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 153 | } 154 | if (array_key_exists('provided', $installed['versions'][$packageName])) { 155 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 156 | } 157 | 158 | return implode(' || ', $ranges); 159 | } 160 | 161 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 162 | } 163 | 164 | /** 165 | * @param string $packageName 166 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 167 | */ 168 | public static function getVersion($packageName) 169 | { 170 | foreach (self::getInstalled() as $installed) { 171 | if (!isset($installed['versions'][$packageName])) { 172 | continue; 173 | } 174 | 175 | if (!isset($installed['versions'][$packageName]['version'])) { 176 | return null; 177 | } 178 | 179 | return $installed['versions'][$packageName]['version']; 180 | } 181 | 182 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 183 | } 184 | 185 | /** 186 | * @param string $packageName 187 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 188 | */ 189 | public static function getPrettyVersion($packageName) 190 | { 191 | foreach (self::getInstalled() as $installed) { 192 | if (!isset($installed['versions'][$packageName])) { 193 | continue; 194 | } 195 | 196 | if (!isset($installed['versions'][$packageName]['pretty_version'])) { 197 | return null; 198 | } 199 | 200 | return $installed['versions'][$packageName]['pretty_version']; 201 | } 202 | 203 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 204 | } 205 | 206 | /** 207 | * @param string $packageName 208 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 209 | */ 210 | public static function getReference($packageName) 211 | { 212 | foreach (self::getInstalled() as $installed) { 213 | if (!isset($installed['versions'][$packageName])) { 214 | continue; 215 | } 216 | 217 | if (!isset($installed['versions'][$packageName]['reference'])) { 218 | return null; 219 | } 220 | 221 | return $installed['versions'][$packageName]['reference']; 222 | } 223 | 224 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 225 | } 226 | 227 | /** 228 | * @param string $packageName 229 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. 230 | */ 231 | public static function getInstallPath($packageName) 232 | { 233 | foreach (self::getInstalled() as $installed) { 234 | if (!isset($installed['versions'][$packageName])) { 235 | continue; 236 | } 237 | 238 | return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 239 | } 240 | 241 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 242 | } 243 | 244 | /** 245 | * @return array 246 | * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} 247 | */ 248 | public static function getRootPackage() 249 | { 250 | $installed = self::getInstalled(); 251 | 252 | return $installed[0]['root']; 253 | } 254 | 255 | /** 256 | * Returns the raw installed.php data for custom implementations 257 | * 258 | * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. 259 | * @return array[] 260 | * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} 261 | */ 262 | public static function getRawData() 263 | { 264 | @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); 265 | 266 | if (null === self::$installed) { 267 | // only require the installed.php file if this file is loaded from its dumped location, 268 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 269 | if (substr(__DIR__, -8, 1) !== 'C') { 270 | self::$installed = include __DIR__ . '/installed.php'; 271 | } else { 272 | self::$installed = array(); 273 | } 274 | } 275 | 276 | return self::$installed; 277 | } 278 | 279 | /** 280 | * Returns the raw data of all installed.php which are currently loaded for custom implementations 281 | * 282 | * @return array[] 283 | * @psalm-return list}> 284 | */ 285 | public static function getAllRawData() 286 | { 287 | return self::getInstalled(); 288 | } 289 | 290 | /** 291 | * Lets you reload the static array from another file 292 | * 293 | * This is only useful for complex integrations in which a project needs to use 294 | * this class but then also needs to execute another project's autoloader in process, 295 | * and wants to ensure both projects have access to their version of installed.php. 296 | * 297 | * A typical case would be PHPUnit, where it would need to make sure it reads all 298 | * the data it needs from this class, then call reload() with 299 | * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 300 | * the project in which it runs can then also use this class safely, without 301 | * interference between PHPUnit's dependencies and the project's dependencies. 302 | * 303 | * @param array[] $data A vendor/composer/installed.php data set 304 | * @return void 305 | * 306 | * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data 307 | */ 308 | public static function reload($data) 309 | { 310 | self::$installed = $data; 311 | self::$installedByVendor = array(); 312 | } 313 | 314 | /** 315 | * @return array[] 316 | * @psalm-return list}> 317 | */ 318 | private static function getInstalled() 319 | { 320 | if (null === self::$canGetVendors) { 321 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 322 | } 323 | 324 | $installed = array(); 325 | 326 | if (self::$canGetVendors) { 327 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 328 | if (isset(self::$installedByVendor[$vendorDir])) { 329 | $installed[] = self::$installedByVendor[$vendorDir]; 330 | } elseif (is_file($vendorDir.'/composer/installed.php')) { 331 | /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ 332 | $required = require $vendorDir.'/composer/installed.php'; 333 | $installed[] = self::$installedByVendor[$vendorDir] = $required; 334 | if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 335 | self::$installed = $installed[count($installed) - 1]; 336 | } 337 | } 338 | } 339 | } 340 | 341 | if (null === self::$installed) { 342 | // only require the installed.php file if this file is loaded from its dumped location, 343 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 344 | if (substr(__DIR__, -8, 1) !== 'C') { 345 | /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ 346 | $required = require __DIR__ . '/installed.php'; 347 | self::$installed = $required; 348 | } else { 349 | self::$installed = array(); 350 | } 351 | } 352 | 353 | if (self::$installed !== array()) { 354 | $installed[] = self::$installed; 355 | } 356 | 357 | return $installed; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see https://www.php-fig.org/psr/psr-0/ 41 | * @see https://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | /** @var \Closure(string):void */ 46 | private static $includeFile; 47 | 48 | /** @var string|null */ 49 | private $vendorDir; 50 | 51 | // PSR-4 52 | /** 53 | * @var array> 54 | */ 55 | private $prefixLengthsPsr4 = array(); 56 | /** 57 | * @var array> 58 | */ 59 | private $prefixDirsPsr4 = array(); 60 | /** 61 | * @var list 62 | */ 63 | private $fallbackDirsPsr4 = array(); 64 | 65 | // PSR-0 66 | /** 67 | * List of PSR-0 prefixes 68 | * 69 | * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) 70 | * 71 | * @var array>> 72 | */ 73 | private $prefixesPsr0 = array(); 74 | /** 75 | * @var list 76 | */ 77 | private $fallbackDirsPsr0 = array(); 78 | 79 | /** @var bool */ 80 | private $useIncludePath = false; 81 | 82 | /** 83 | * @var array 84 | */ 85 | private $classMap = array(); 86 | 87 | /** @var bool */ 88 | private $classMapAuthoritative = false; 89 | 90 | /** 91 | * @var array 92 | */ 93 | private $missingClasses = array(); 94 | 95 | /** @var string|null */ 96 | private $apcuPrefix; 97 | 98 | /** 99 | * @var array 100 | */ 101 | private static $registeredLoaders = array(); 102 | 103 | /** 104 | * @param string|null $vendorDir 105 | */ 106 | public function __construct($vendorDir = null) 107 | { 108 | $this->vendorDir = $vendorDir; 109 | self::initializeIncludeClosure(); 110 | } 111 | 112 | /** 113 | * @return array> 114 | */ 115 | public function getPrefixes() 116 | { 117 | if (!empty($this->prefixesPsr0)) { 118 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 119 | } 120 | 121 | return array(); 122 | } 123 | 124 | /** 125 | * @return array> 126 | */ 127 | public function getPrefixesPsr4() 128 | { 129 | return $this->prefixDirsPsr4; 130 | } 131 | 132 | /** 133 | * @return list 134 | */ 135 | public function getFallbackDirs() 136 | { 137 | return $this->fallbackDirsPsr0; 138 | } 139 | 140 | /** 141 | * @return list 142 | */ 143 | public function getFallbackDirsPsr4() 144 | { 145 | return $this->fallbackDirsPsr4; 146 | } 147 | 148 | /** 149 | * @return array Array of classname => path 150 | */ 151 | public function getClassMap() 152 | { 153 | return $this->classMap; 154 | } 155 | 156 | /** 157 | * @param array $classMap Class to filename map 158 | * 159 | * @return void 160 | */ 161 | public function addClassMap(array $classMap) 162 | { 163 | if ($this->classMap) { 164 | $this->classMap = array_merge($this->classMap, $classMap); 165 | } else { 166 | $this->classMap = $classMap; 167 | } 168 | } 169 | 170 | /** 171 | * Registers a set of PSR-0 directories for a given prefix, either 172 | * appending or prepending to the ones previously set for this prefix. 173 | * 174 | * @param string $prefix The prefix 175 | * @param list|string $paths The PSR-0 root directories 176 | * @param bool $prepend Whether to prepend the directories 177 | * 178 | * @return void 179 | */ 180 | public function add($prefix, $paths, $prepend = false) 181 | { 182 | $paths = (array) $paths; 183 | if (!$prefix) { 184 | if ($prepend) { 185 | $this->fallbackDirsPsr0 = array_merge( 186 | $paths, 187 | $this->fallbackDirsPsr0 188 | ); 189 | } else { 190 | $this->fallbackDirsPsr0 = array_merge( 191 | $this->fallbackDirsPsr0, 192 | $paths 193 | ); 194 | } 195 | 196 | return; 197 | } 198 | 199 | $first = $prefix[0]; 200 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 201 | $this->prefixesPsr0[$first][$prefix] = $paths; 202 | 203 | return; 204 | } 205 | if ($prepend) { 206 | $this->prefixesPsr0[$first][$prefix] = array_merge( 207 | $paths, 208 | $this->prefixesPsr0[$first][$prefix] 209 | ); 210 | } else { 211 | $this->prefixesPsr0[$first][$prefix] = array_merge( 212 | $this->prefixesPsr0[$first][$prefix], 213 | $paths 214 | ); 215 | } 216 | } 217 | 218 | /** 219 | * Registers a set of PSR-4 directories for a given namespace, either 220 | * appending or prepending to the ones previously set for this namespace. 221 | * 222 | * @param string $prefix The prefix/namespace, with trailing '\\' 223 | * @param list|string $paths The PSR-4 base directories 224 | * @param bool $prepend Whether to prepend the directories 225 | * 226 | * @throws \InvalidArgumentException 227 | * 228 | * @return void 229 | */ 230 | public function addPsr4($prefix, $paths, $prepend = false) 231 | { 232 | $paths = (array) $paths; 233 | if (!$prefix) { 234 | // Register directories for the root namespace. 235 | if ($prepend) { 236 | $this->fallbackDirsPsr4 = array_merge( 237 | $paths, 238 | $this->fallbackDirsPsr4 239 | ); 240 | } else { 241 | $this->fallbackDirsPsr4 = array_merge( 242 | $this->fallbackDirsPsr4, 243 | $paths 244 | ); 245 | } 246 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 247 | // Register directories for a new namespace. 248 | $length = strlen($prefix); 249 | if ('\\' !== $prefix[$length - 1]) { 250 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 251 | } 252 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 253 | $this->prefixDirsPsr4[$prefix] = $paths; 254 | } elseif ($prepend) { 255 | // Prepend directories for an already registered namespace. 256 | $this->prefixDirsPsr4[$prefix] = array_merge( 257 | $paths, 258 | $this->prefixDirsPsr4[$prefix] 259 | ); 260 | } else { 261 | // Append directories for an already registered namespace. 262 | $this->prefixDirsPsr4[$prefix] = array_merge( 263 | $this->prefixDirsPsr4[$prefix], 264 | $paths 265 | ); 266 | } 267 | } 268 | 269 | /** 270 | * Registers a set of PSR-0 directories for a given prefix, 271 | * replacing any others previously set for this prefix. 272 | * 273 | * @param string $prefix The prefix 274 | * @param list|string $paths The PSR-0 base directories 275 | * 276 | * @return void 277 | */ 278 | public function set($prefix, $paths) 279 | { 280 | if (!$prefix) { 281 | $this->fallbackDirsPsr0 = (array) $paths; 282 | } else { 283 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 284 | } 285 | } 286 | 287 | /** 288 | * Registers a set of PSR-4 directories for a given namespace, 289 | * replacing any others previously set for this namespace. 290 | * 291 | * @param string $prefix The prefix/namespace, with trailing '\\' 292 | * @param list|string $paths The PSR-4 base directories 293 | * 294 | * @throws \InvalidArgumentException 295 | * 296 | * @return void 297 | */ 298 | public function setPsr4($prefix, $paths) 299 | { 300 | if (!$prefix) { 301 | $this->fallbackDirsPsr4 = (array) $paths; 302 | } else { 303 | $length = strlen($prefix); 304 | if ('\\' !== $prefix[$length - 1]) { 305 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 306 | } 307 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 308 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 309 | } 310 | } 311 | 312 | /** 313 | * Turns on searching the include path for class files. 314 | * 315 | * @param bool $useIncludePath 316 | * 317 | * @return void 318 | */ 319 | public function setUseIncludePath($useIncludePath) 320 | { 321 | $this->useIncludePath = $useIncludePath; 322 | } 323 | 324 | /** 325 | * Can be used to check if the autoloader uses the include path to check 326 | * for classes. 327 | * 328 | * @return bool 329 | */ 330 | public function getUseIncludePath() 331 | { 332 | return $this->useIncludePath; 333 | } 334 | 335 | /** 336 | * Turns off searching the prefix and fallback directories for classes 337 | * that have not been registered with the class map. 338 | * 339 | * @param bool $classMapAuthoritative 340 | * 341 | * @return void 342 | */ 343 | public function setClassMapAuthoritative($classMapAuthoritative) 344 | { 345 | $this->classMapAuthoritative = $classMapAuthoritative; 346 | } 347 | 348 | /** 349 | * Should class lookup fail if not found in the current class map? 350 | * 351 | * @return bool 352 | */ 353 | public function isClassMapAuthoritative() 354 | { 355 | return $this->classMapAuthoritative; 356 | } 357 | 358 | /** 359 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 360 | * 361 | * @param string|null $apcuPrefix 362 | * 363 | * @return void 364 | */ 365 | public function setApcuPrefix($apcuPrefix) 366 | { 367 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 368 | } 369 | 370 | /** 371 | * The APCu prefix in use, or null if APCu caching is not enabled. 372 | * 373 | * @return string|null 374 | */ 375 | public function getApcuPrefix() 376 | { 377 | return $this->apcuPrefix; 378 | } 379 | 380 | /** 381 | * Registers this instance as an autoloader. 382 | * 383 | * @param bool $prepend Whether to prepend the autoloader or not 384 | * 385 | * @return void 386 | */ 387 | public function register($prepend = false) 388 | { 389 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 390 | 391 | if (null === $this->vendorDir) { 392 | return; 393 | } 394 | 395 | if ($prepend) { 396 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 397 | } else { 398 | unset(self::$registeredLoaders[$this->vendorDir]); 399 | self::$registeredLoaders[$this->vendorDir] = $this; 400 | } 401 | } 402 | 403 | /** 404 | * Unregisters this instance as an autoloader. 405 | * 406 | * @return void 407 | */ 408 | public function unregister() 409 | { 410 | spl_autoload_unregister(array($this, 'loadClass')); 411 | 412 | if (null !== $this->vendorDir) { 413 | unset(self::$registeredLoaders[$this->vendorDir]); 414 | } 415 | } 416 | 417 | /** 418 | * Loads the given class or interface. 419 | * 420 | * @param string $class The name of the class 421 | * @return true|null True if loaded, null otherwise 422 | */ 423 | public function loadClass($class) 424 | { 425 | if ($file = $this->findFile($class)) { 426 | $includeFile = self::$includeFile; 427 | $includeFile($file); 428 | 429 | return true; 430 | } 431 | 432 | return null; 433 | } 434 | 435 | /** 436 | * Finds the path to the file where the class is defined. 437 | * 438 | * @param string $class The name of the class 439 | * 440 | * @return string|false The path if found, false otherwise 441 | */ 442 | public function findFile($class) 443 | { 444 | // class map lookup 445 | if (isset($this->classMap[$class])) { 446 | return $this->classMap[$class]; 447 | } 448 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 449 | return false; 450 | } 451 | if (null !== $this->apcuPrefix) { 452 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 453 | if ($hit) { 454 | return $file; 455 | } 456 | } 457 | 458 | $file = $this->findFileWithExtension($class, '.php'); 459 | 460 | // Search for Hack files if we are running on HHVM 461 | if (false === $file && defined('HHVM_VERSION')) { 462 | $file = $this->findFileWithExtension($class, '.hh'); 463 | } 464 | 465 | if (null !== $this->apcuPrefix) { 466 | apcu_add($this->apcuPrefix.$class, $file); 467 | } 468 | 469 | if (false === $file) { 470 | // Remember that this class does not exist. 471 | $this->missingClasses[$class] = true; 472 | } 473 | 474 | return $file; 475 | } 476 | 477 | /** 478 | * Returns the currently registered loaders keyed by their corresponding vendor directories. 479 | * 480 | * @return array 481 | */ 482 | public static function getRegisteredLoaders() 483 | { 484 | return self::$registeredLoaders; 485 | } 486 | 487 | /** 488 | * @param string $class 489 | * @param string $ext 490 | * @return string|false 491 | */ 492 | private function findFileWithExtension($class, $ext) 493 | { 494 | // PSR-4 lookup 495 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 496 | 497 | $first = $class[0]; 498 | if (isset($this->prefixLengthsPsr4[$first])) { 499 | $subPath = $class; 500 | while (false !== $lastPos = strrpos($subPath, '\\')) { 501 | $subPath = substr($subPath, 0, $lastPos); 502 | $search = $subPath . '\\'; 503 | if (isset($this->prefixDirsPsr4[$search])) { 504 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 505 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 506 | if (file_exists($file = $dir . $pathEnd)) { 507 | return $file; 508 | } 509 | } 510 | } 511 | } 512 | } 513 | 514 | // PSR-4 fallback dirs 515 | foreach ($this->fallbackDirsPsr4 as $dir) { 516 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 517 | return $file; 518 | } 519 | } 520 | 521 | // PSR-0 lookup 522 | if (false !== $pos = strrpos($class, '\\')) { 523 | // namespaced class name 524 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 525 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 526 | } else { 527 | // PEAR-like class name 528 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 529 | } 530 | 531 | if (isset($this->prefixesPsr0[$first])) { 532 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 533 | if (0 === strpos($class, $prefix)) { 534 | foreach ($dirs as $dir) { 535 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 536 | return $file; 537 | } 538 | } 539 | } 540 | } 541 | } 542 | 543 | // PSR-0 fallback dirs 544 | foreach ($this->fallbackDirsPsr0 as $dir) { 545 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 546 | return $file; 547 | } 548 | } 549 | 550 | // PSR-0 include paths. 551 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 552 | return $file; 553 | } 554 | 555 | return false; 556 | } 557 | 558 | /** 559 | * @return void 560 | */ 561 | private static function initializeIncludeClosure() 562 | { 563 | if (self::$includeFile !== null) { 564 | return; 565 | } 566 | 567 | /** 568 | * Scope isolated include. 569 | * 570 | * Prevents access to $this/self from included files. 571 | * 572 | * @param string $file 573 | * @return void 574 | */ 575 | self::$includeFile = \Closure::bind(static function($file) { 576 | include $file; 577 | }, null, null); 578 | } 579 | } 580 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/src/Minify.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 10 | * @license MIT License 11 | */ 12 | 13 | namespace MatthiasMullie\Minify; 14 | 15 | use MatthiasMullie\Minify\Exceptions\IOException; 16 | use Psr\Cache\CacheItemInterface; 17 | 18 | /** 19 | * Abstract minifier class. 20 | * 21 | * Please report bugs on https://github.com/matthiasmullie/minify/issues 22 | * 23 | * @author Matthias Mullie 24 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 25 | * @license MIT License 26 | */ 27 | abstract class Minify 28 | { 29 | /** 30 | * The data to be minified. 31 | * 32 | * @var string[] 33 | */ 34 | protected $data = array(); 35 | 36 | /** 37 | * Array of patterns to match. 38 | * 39 | * @var string[] 40 | */ 41 | protected $patterns = array(); 42 | 43 | /** 44 | * This array will hold content of strings and regular expressions that have 45 | * been extracted from the JS source code, so we can reliably match "code", 46 | * without having to worry about potential "code-like" characters inside. 47 | * 48 | * @internal 49 | * 50 | * @var string[] 51 | */ 52 | public $extracted = array(); 53 | 54 | /** 55 | * Init the minify class - optionally, code may be passed along already. 56 | */ 57 | public function __construct(/* $data = null, ... */) 58 | { 59 | // it's possible to add the source through the constructor as well ;) 60 | if (func_num_args()) { 61 | call_user_func_array(array($this, 'add'), func_get_args()); 62 | } 63 | } 64 | 65 | /** 66 | * Add a file or straight-up code to be minified. 67 | * 68 | * @param string|string[] $data 69 | * 70 | * @return static 71 | */ 72 | public function add($data /* $data = null, ... */) 73 | { 74 | // bogus "usage" of parameter $data: scrutinizer warns this variable is 75 | // not used (we're using func_get_args instead to support overloading), 76 | // but it still needs to be defined because it makes no sense to have 77 | // this function without argument :) 78 | $args = array($data) + func_get_args(); 79 | 80 | // this method can be overloaded 81 | foreach ($args as $data) { 82 | if (is_array($data)) { 83 | call_user_func_array(array($this, 'add'), $data); 84 | continue; 85 | } 86 | 87 | // redefine var 88 | $data = (string) $data; 89 | 90 | // load data 91 | $value = $this->load($data); 92 | $key = ($data != $value) ? $data : count($this->data); 93 | 94 | // replace CR linefeeds etc. 95 | // @see https://github.com/matthiasmullie/minify/pull/139 96 | $value = str_replace(array("\r\n", "\r"), "\n", $value); 97 | 98 | // store data 99 | $this->data[$key] = $value; 100 | } 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * Add a file to be minified. 107 | * 108 | * @param string|string[] $data 109 | * 110 | * @return static 111 | * 112 | * @throws IOException 113 | */ 114 | public function addFile($data /* $data = null, ... */) 115 | { 116 | // bogus "usage" of parameter $data: scrutinizer warns this variable is 117 | // not used (we're using func_get_args instead to support overloading), 118 | // but it still needs to be defined because it makes no sense to have 119 | // this function without argument :) 120 | $args = array($data) + func_get_args(); 121 | 122 | // this method can be overloaded 123 | foreach ($args as $path) { 124 | if (is_array($path)) { 125 | call_user_func_array(array($this, 'addFile'), $path); 126 | continue; 127 | } 128 | 129 | // redefine var 130 | $path = (string) $path; 131 | 132 | // check if we can read the file 133 | if (!$this->canImportFile($path)) { 134 | throw new IOException('The file "' . $path . '" could not be opened for reading. Check if PHP has enough permissions.'); 135 | } 136 | 137 | $this->add($path); 138 | } 139 | 140 | return $this; 141 | } 142 | 143 | /** 144 | * Minify the data & (optionally) saves it to a file. 145 | * 146 | * @param string[optional] $path Path to write the data to 147 | * 148 | * @return string The minified data 149 | */ 150 | public function minify($path = null) 151 | { 152 | $content = $this->execute($path); 153 | 154 | // save to path 155 | if ($path !== null) { 156 | $this->save($content, $path); 157 | } 158 | 159 | return $content; 160 | } 161 | 162 | /** 163 | * Minify & gzip the data & (optionally) saves it to a file. 164 | * 165 | * @param string[optional] $path Path to write the data to 166 | * @param int[optional] $level Compression level, from 0 to 9 167 | * 168 | * @return string The minified & gzipped data 169 | */ 170 | public function gzip($path = null, $level = 9) 171 | { 172 | $content = $this->execute($path); 173 | $content = gzencode($content, $level, FORCE_GZIP); 174 | 175 | // save to path 176 | if ($path !== null) { 177 | $this->save($content, $path); 178 | } 179 | 180 | return $content; 181 | } 182 | 183 | /** 184 | * Minify the data & write it to a CacheItemInterface object. 185 | * 186 | * @param CacheItemInterface $item Cache item to write the data to 187 | * 188 | * @return CacheItemInterface Cache item with the minifier data 189 | */ 190 | public function cache(CacheItemInterface $item) 191 | { 192 | $content = $this->execute(); 193 | $item->set($content); 194 | 195 | return $item; 196 | } 197 | 198 | /** 199 | * Minify the data. 200 | * 201 | * @param string[optional] $path Path to write the data to 202 | * 203 | * @return string The minified data 204 | */ 205 | abstract public function execute($path = null); 206 | 207 | /** 208 | * Load data. 209 | * 210 | * @param string $data Either a path to a file or the content itself 211 | * 212 | * @return string 213 | */ 214 | protected function load($data) 215 | { 216 | // check if the data is a file 217 | if ($this->canImportFile($data)) { 218 | $data = file_get_contents($data); 219 | 220 | // strip BOM, if any 221 | if (substr($data, 0, 3) == "\xef\xbb\xbf") { 222 | $data = substr($data, 3); 223 | } 224 | } 225 | 226 | return $data; 227 | } 228 | 229 | /** 230 | * Save to file. 231 | * 232 | * @param string $content The minified data 233 | * @param string $path The path to save the minified data to 234 | * 235 | * @throws IOException 236 | */ 237 | protected function save($content, $path) 238 | { 239 | $handler = $this->openFileForWriting($path); 240 | 241 | $this->writeToFile($handler, $content); 242 | 243 | @fclose($handler); 244 | } 245 | 246 | /** 247 | * Register a pattern to execute against the source content. 248 | * 249 | * If $replacement is a string, it must be plain text. Placeholders like $1 or \2 don't work. 250 | * If you need that functionality, use a callback instead. 251 | * 252 | * @param string $pattern PCRE pattern 253 | * @param string|callable $replacement Replacement value for matched pattern 254 | */ 255 | protected function registerPattern($pattern, $replacement = '') 256 | { 257 | // study the pattern, we'll execute it more than once 258 | $pattern .= 'S'; 259 | 260 | $this->patterns[] = array($pattern, $replacement); 261 | } 262 | 263 | /** 264 | * Both JS and CSS use the same form of multi-line comment, so putting the common code here. 265 | */ 266 | protected function stripMultilineComments() 267 | { 268 | // First extract comments we want to keep, so they can be restored later 269 | // PHP only supports $this inside anonymous functions since 5.4 270 | $minifier = $this; 271 | $callback = function ($match) use ($minifier) { 272 | $count = count($minifier->extracted); 273 | $placeholder = '/*' . $count . '*/'; 274 | $minifier->extracted[$placeholder] = $match[0]; 275 | 276 | return $placeholder; 277 | }; 278 | $this->registerPattern('/ 279 | # optional newline 280 | \n? 281 | 282 | # start comment 283 | \/\* 284 | 285 | # comment content 286 | (?: 287 | # either starts with an ! 288 | ! 289 | | 290 | # or, after some number of characters which do not end the comment 291 | (?:(?!\*\/).)*? 292 | 293 | # there is either a @license or @preserve tag 294 | @(?:license|preserve) 295 | ) 296 | 297 | # then match to the end of the comment 298 | .*?\*\/\n? 299 | 300 | /ixs', $callback); 301 | 302 | // Then strip all other comments 303 | $this->registerPattern('/\/\*.*?\*\//s', ''); 304 | } 305 | 306 | /** 307 | * We can't "just" run some regular expressions against JavaScript: it's a 308 | * complex language. E.g. having an occurrence of // xyz would be a comment, 309 | * unless it's used within a string. Of you could have something that looks 310 | * like a 'string', but inside a comment. 311 | * The only way to accurately replace these pieces is to traverse the JS one 312 | * character at a time and try to find whatever starts first. 313 | * 314 | * @param string $content The content to replace patterns in 315 | * 316 | * @return string The (manipulated) content 317 | */ 318 | protected function replace($content) 319 | { 320 | $contentLength = strlen($content); 321 | $output = ''; 322 | $processedOffset = 0; 323 | $positions = array_fill(0, count($this->patterns), -1); 324 | $matches = array(); 325 | 326 | while ($processedOffset < $contentLength) { 327 | // find first match for all patterns 328 | foreach ($this->patterns as $i => $pattern) { 329 | list($pattern, $replacement) = $pattern; 330 | 331 | // we can safely ignore patterns for positions we've unset earlier, 332 | // because we know these won't show up anymore 333 | if (array_key_exists($i, $positions) == false) { 334 | continue; 335 | } 336 | 337 | // no need to re-run matches that are still in the part of the 338 | // content that hasn't been processed 339 | if ($positions[$i] >= $processedOffset) { 340 | continue; 341 | } 342 | 343 | $match = null; 344 | if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE, $processedOffset)) { 345 | $matches[$i] = $match; 346 | 347 | // we'll store the match position as well; that way, we 348 | // don't have to redo all preg_matches after changing only 349 | // the first (we'll still know where those others are) 350 | $positions[$i] = $match[0][1]; 351 | } else { 352 | // if the pattern couldn't be matched, there's no point in 353 | // executing it again in later runs on this same content; 354 | // ignore this one until we reach end of content 355 | unset($matches[$i], $positions[$i]); 356 | } 357 | } 358 | 359 | // no more matches to find: everything's been processed, break out 360 | if (!$matches) { 361 | // output the remaining content 362 | $output .= substr($content, $processedOffset); 363 | break; 364 | } 365 | 366 | // see which of the patterns actually found the first thing (we'll 367 | // only want to execute that one, since we're unsure if what the 368 | // other found was not inside what the first found) 369 | $matchOffset = min($positions); 370 | $firstPattern = array_search($matchOffset, $positions); 371 | $match = $matches[$firstPattern]; 372 | 373 | // execute the pattern that matches earliest in the content string 374 | list(, $replacement) = $this->patterns[$firstPattern]; 375 | 376 | // add the part of the input between $processedOffset and the first match; 377 | // that content wasn't matched by anything 378 | $output .= substr($content, $processedOffset, $matchOffset - $processedOffset); 379 | // add the replacement for the match 380 | $output .= $this->executeReplacement($replacement, $match); 381 | // advance $processedOffset past the match 382 | $processedOffset = $matchOffset + strlen($match[0][0]); 383 | } 384 | 385 | return $output; 386 | } 387 | 388 | /** 389 | * If $replacement is a callback, execute it, passing in the match data. 390 | * If it's a string, just pass it through. 391 | * 392 | * @param string|callable $replacement Replacement value 393 | * @param array $match Match data, in PREG_OFFSET_CAPTURE form 394 | * 395 | * @return string 396 | */ 397 | protected function executeReplacement($replacement, $match) 398 | { 399 | if (!is_callable($replacement)) { 400 | return $replacement; 401 | } 402 | // convert $match from the PREG_OFFSET_CAPTURE form to the form the callback expects 403 | foreach ($match as &$matchItem) { 404 | $matchItem = $matchItem[0]; 405 | } 406 | 407 | return $replacement($match); 408 | } 409 | 410 | /** 411 | * Strings are a pattern we need to match, in order to ignore potential 412 | * code-like content inside them, but we just want all of the string 413 | * content to remain untouched. 414 | * 415 | * This method will replace all string content with simple STRING# 416 | * placeholder text, so we've rid all strings from characters that may be 417 | * misinterpreted. Original string content will be saved in $this->extracted 418 | * and after doing all other minifying, we can restore the original content 419 | * via restoreStrings(). 420 | * 421 | * @param string[optional] $chars 422 | * @param string[optional] $placeholderPrefix 423 | */ 424 | protected function extractStrings($chars = '\'"', $placeholderPrefix = '') 425 | { 426 | // PHP only supports $this inside anonymous functions since 5.4 427 | $minifier = $this; 428 | $callback = function ($match) use ($minifier, $placeholderPrefix) { 429 | // check the second index here, because the first always contains a quote 430 | if ($match[2] === '') { 431 | /* 432 | * Empty strings need no placeholder; they can't be confused for 433 | * anything else anyway. 434 | * But we still needed to match them, for the extraction routine 435 | * to skip over this particular string. 436 | */ 437 | return $match[0]; 438 | } 439 | 440 | $count = count($minifier->extracted); 441 | $placeholder = $match[1] . $placeholderPrefix . $count . $match[1]; 442 | $minifier->extracted[$placeholder] = $match[1] . $match[2] . $match[1]; 443 | 444 | return $placeholder; 445 | }; 446 | 447 | /* 448 | * The \\ messiness explained: 449 | * * Don't count ' or " as end-of-string if it's escaped (has backslash 450 | * in front of it) 451 | * * Unless... that backslash itself is escaped (another leading slash), 452 | * in which case it's no longer escaping the ' or " 453 | * * So there can be either no backslash, or an even number 454 | * * multiply all of that times 4, to account for the escaping that has 455 | * to be done to pass the backslash into the PHP string without it being 456 | * considered as escape-char (times 2) and to get it in the regex, 457 | * escaped (times 2) 458 | */ 459 | $this->registerPattern('/([' . $chars . '])(.*?(?extracted. 466 | * 467 | * @param string $content 468 | * 469 | * @return string 470 | */ 471 | protected function restoreExtractedData($content) 472 | { 473 | if (!$this->extracted) { 474 | // nothing was extracted, nothing to restore 475 | return $content; 476 | } 477 | 478 | $content = strtr($content, $this->extracted); 479 | 480 | $this->extracted = array(); 481 | 482 | return $content; 483 | } 484 | 485 | /** 486 | * Check if the path is a regular file and can be read. 487 | * 488 | * @param string $path 489 | * 490 | * @return bool 491 | */ 492 | protected function canImportFile($path) 493 | { 494 | $parsed = parse_url($path); 495 | if ( 496 | // file is elsewhere 497 | isset($parsed['host']) 498 | // file responds to queries (may change, or need to bypass cache) 499 | || isset($parsed['query']) 500 | ) { 501 | return false; 502 | } 503 | 504 | try { 505 | return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path); 506 | } 507 | // catch openbasedir exceptions which are not caught by @ on is_file() 508 | catch (\Exception $e) { 509 | return false; 510 | } 511 | } 512 | 513 | /** 514 | * Attempts to open file specified by $path for writing. 515 | * 516 | * @param string $path The path to the file 517 | * 518 | * @return resource Specifier for the target file 519 | * 520 | * @throws IOException 521 | */ 522 | protected function openFileForWriting($path) 523 | { 524 | if ($path === '' || ($handler = @fopen($path, 'w')) === false) { 525 | throw new IOException('The file "' . $path . '" could not be opened for writing. Check if PHP has enough permissions.'); 526 | } 527 | 528 | return $handler; 529 | } 530 | 531 | /** 532 | * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions. 533 | * 534 | * @param resource $handler The resource to write to 535 | * @param string $content The content to write 536 | * @param string $path The path to the file (for exception printing only) 537 | * 538 | * @throws IOException 539 | */ 540 | protected function writeToFile($handler, $content, $path = '') 541 | { 542 | if ( 543 | !is_resource($handler) 544 | || ($result = @fwrite($handler, $content)) === false 545 | || ($result < strlen($content)) 546 | ) { 547 | throw new IOException('The file "' . $path . '" could not be written to. Check your disk space and file permissions.'); 548 | } 549 | } 550 | 551 | protected static function str_replace_first($search, $replace, $subject) 552 | { 553 | $pos = strpos($subject, $search); 554 | if ($pos !== false) { 555 | return substr_replace($subject, $replace, $pos, strlen($search)); 556 | } 557 | 558 | return $subject; 559 | } 560 | } 561 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/src/CSS.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 10 | * @license MIT License 11 | */ 12 | 13 | namespace MatthiasMullie\Minify; 14 | 15 | use MatthiasMullie\Minify\Exceptions\FileImportException; 16 | use MatthiasMullie\PathConverter\Converter; 17 | use MatthiasMullie\PathConverter\ConverterInterface; 18 | 19 | /** 20 | * CSS minifier. 21 | * 22 | * Please report bugs on https://github.com/matthiasmullie/minify/issues 23 | * 24 | * @author Matthias Mullie 25 | * @author Tijs Verkoyen 26 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 27 | * @license MIT License 28 | */ 29 | class CSS extends Minify 30 | { 31 | /** 32 | * @var int maximum inport size in kB 33 | */ 34 | protected $maxImportSize = 5; 35 | 36 | /** 37 | * @var string[] valid import extensions 38 | */ 39 | protected $importExtensions = array( 40 | 'gif' => 'data:image/gif', 41 | 'png' => 'data:image/png', 42 | 'jpe' => 'data:image/jpeg', 43 | 'jpg' => 'data:image/jpeg', 44 | 'jpeg' => 'data:image/jpeg', 45 | 'svg' => 'data:image/svg+xml', 46 | 'woff' => 'data:application/x-font-woff', 47 | 'woff2' => 'data:application/x-font-woff2', 48 | 'avif' => 'data:image/avif', 49 | 'apng' => 'data:image/apng', 50 | 'webp' => 'data:image/webp', 51 | 'tif' => 'image/tiff', 52 | 'tiff' => 'image/tiff', 53 | 'xbm' => 'image/x-xbitmap', 54 | ); 55 | 56 | /** 57 | * Set the maximum size if files to be imported. 58 | * 59 | * Files larger than this size (in kB) will not be imported into the CSS. 60 | * Importing files into the CSS as data-uri will save you some connections, 61 | * but we should only import relatively small decorative images so that our 62 | * CSS file doesn't get too bulky. 63 | * 64 | * @param int $size Size in kB 65 | */ 66 | public function setMaxImportSize($size) 67 | { 68 | $this->maxImportSize = $size; 69 | } 70 | 71 | /** 72 | * Set the type of extensions to be imported into the CSS (to save network 73 | * connections). 74 | * Keys of the array should be the file extensions & respective values 75 | * should be the data type. 76 | * 77 | * @param string[] $extensions Array of file extensions 78 | */ 79 | public function setImportExtensions(array $extensions) 80 | { 81 | $this->importExtensions = $extensions; 82 | } 83 | 84 | /** 85 | * Move any import statements to the top. 86 | * 87 | * @param string $content Nearly finished CSS content 88 | * 89 | * @return string 90 | */ 91 | protected function moveImportsToTop($content) 92 | { 93 | if (preg_match_all('/(;?)(@import (?url\()?(?P["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) { 94 | // remove from content 95 | foreach ($matches[0] as $import) { 96 | $content = str_replace($import, '', $content); 97 | } 98 | 99 | // add to top 100 | $content = implode(';', $matches[2]) . ';' . trim($content, ';'); 101 | } 102 | 103 | return $content; 104 | } 105 | 106 | /** 107 | * Combine CSS from import statements. 108 | * 109 | * \@import's will be loaded and their content merged into the original file, 110 | * to save HTTP requests. 111 | * 112 | * @param string $source The file to combine imports for 113 | * @param string $content The CSS content to combine imports for 114 | * @param string[] $parents Parent paths, for circular reference checks 115 | * 116 | * @return string 117 | * 118 | * @throws FileImportException 119 | */ 120 | protected function combineImports($source, $content, $parents) 121 | { 122 | $importRegexes = array( 123 | // @import url(xxx) 124 | '/ 125 | # import statement 126 | @import 127 | 128 | # whitespace 129 | \s+ 130 | 131 | # open url() 132 | url\( 133 | 134 | # (optional) open path enclosure 135 | (?P["\']?) 136 | 137 | # fetch path 138 | (?P.+?) 139 | 140 | # (optional) close path enclosure 141 | (?P=quotes) 142 | 143 | # close url() 144 | \) 145 | 146 | # (optional) trailing whitespace 147 | \s* 148 | 149 | # (optional) media statement(s) 150 | (?P[^;]*) 151 | 152 | # (optional) trailing whitespace 153 | \s* 154 | 155 | # (optional) closing semi-colon 156 | ;? 157 | 158 | /ix', 159 | 160 | // @import 'xxx' 161 | '/ 162 | 163 | # import statement 164 | @import 165 | 166 | # whitespace 167 | \s+ 168 | 169 | # open path enclosure 170 | (?P["\']) 171 | 172 | # fetch path 173 | (?P.+?) 174 | 175 | # close path enclosure 176 | (?P=quotes) 177 | 178 | # (optional) trailing whitespace 179 | \s* 180 | 181 | # (optional) media statement(s) 182 | (?P[^;]*) 183 | 184 | # (optional) trailing whitespace 185 | \s* 186 | 187 | # (optional) closing semi-colon 188 | ;? 189 | 190 | /ix', 191 | ); 192 | 193 | // find all relative imports in css 194 | $matches = array(); 195 | foreach ($importRegexes as $importRegex) { 196 | if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) { 197 | $matches = array_merge($matches, $regexMatches); 198 | } 199 | } 200 | 201 | $search = array(); 202 | $replace = array(); 203 | 204 | // loop the matches 205 | foreach ($matches as $match) { 206 | // get the path for the file that will be imported 207 | $importPath = dirname($source) . '/' . $match['path']; 208 | 209 | // only replace the import with the content if we can grab the 210 | // content of the file 211 | if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) { 212 | continue; 213 | } 214 | 215 | // check if current file was not imported previously in the same 216 | // import chain. 217 | if (in_array($importPath, $parents)) { 218 | throw new FileImportException('Failed to import file "' . $importPath . '": circular reference detected.'); 219 | } 220 | 221 | // grab referenced file & minify it (which may include importing 222 | // yet other @import statements recursively) 223 | $minifier = new self($importPath); 224 | $minifier->setMaxImportSize($this->maxImportSize); 225 | $minifier->setImportExtensions($this->importExtensions); 226 | $importContent = $minifier->execute($source, $parents); 227 | 228 | // check if this is only valid for certain media 229 | if (!empty($match['media'])) { 230 | $importContent = '@media ' . $match['media'] . '{' . $importContent . '}'; 231 | } 232 | 233 | // add to replacement array 234 | $search[] = $match[0]; 235 | $replace[] = $importContent; 236 | } 237 | 238 | // replace the import statements 239 | return str_replace($search, $replace, $content); 240 | } 241 | 242 | /** 243 | * Import files into the CSS, base64-ized. 244 | * 245 | * @url(image.jpg) images will be loaded and their content merged into the 246 | * original file, to save HTTP requests. 247 | * 248 | * @param string $source The file to import files for 249 | * @param string $content The CSS content to import files for 250 | * 251 | * @return string 252 | */ 253 | protected function importFiles($source, $content) 254 | { 255 | $regex = '/url\((["\']?)(.+?)\\1\)/i'; 256 | if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { 257 | $search = array(); 258 | $replace = array(); 259 | 260 | // loop the matches 261 | foreach ($matches as $match) { 262 | $extension = substr(strrchr($match[2], '.'), 1); 263 | if ($extension && !array_key_exists($extension, $this->importExtensions)) { 264 | continue; 265 | } 266 | 267 | // get the path for the file that will be imported 268 | $path = $match[2]; 269 | $path = dirname($source) . '/' . $path; 270 | 271 | // only replace the import with the content if we're able to get 272 | // the content of the file, and it's relatively small 273 | if ($this->canImportFile($path) && $this->canImportBySize($path)) { 274 | // grab content && base64-ize 275 | $importContent = $this->load($path); 276 | $importContent = base64_encode($importContent); 277 | 278 | // build replacement 279 | $search[] = $match[0]; 280 | $replace[] = 'url(' . $this->importExtensions[$extension] . ';base64,' . $importContent . ')'; 281 | } 282 | } 283 | 284 | // replace the import statements 285 | $content = str_replace($search, $replace, $content); 286 | } 287 | 288 | return $content; 289 | } 290 | 291 | /** 292 | * Minify the data. 293 | * Perform CSS optimizations. 294 | * 295 | * @param string[optional] $path Path to write the data to 296 | * @param string[] $parents Parent paths, for circular reference checks 297 | * 298 | * @return string The minified data 299 | */ 300 | public function execute($path = null, $parents = array()) 301 | { 302 | $content = ''; 303 | 304 | // loop CSS data (raw data and files) 305 | foreach ($this->data as $source => $css) { 306 | /* 307 | * Let's first take out strings & comments, since we can't just 308 | * remove whitespace anywhere. If whitespace occurs inside a string, 309 | * we should leave it alone. E.g.: 310 | * p { content: "a test" } 311 | */ 312 | $this->extractStrings(); 313 | $this->stripComments(); 314 | $this->extractMath(); 315 | $this->extractCustomProperties(); 316 | $css = $this->replace($css); 317 | 318 | $css = $this->stripWhitespace($css); 319 | $css = $this->convertLegacyColors($css); 320 | $css = $this->cleanupModernColors($css); 321 | $css = $this->shortenHEXColors($css); 322 | $css = $this->shortenZeroes($css); 323 | $css = $this->shortenFontWeights($css); 324 | $css = $this->stripEmptyTags($css); 325 | 326 | // restore the string we've extracted earlier 327 | $css = $this->restoreExtractedData($css); 328 | 329 | $source = is_int($source) ? '' : $source; 330 | $parents = $source ? array_merge($parents, array($source)) : $parents; 331 | $css = $this->combineImports($source, $css, $parents); 332 | $css = $this->importFiles($source, $css); 333 | 334 | /* 335 | * If we'll save to a new path, we'll have to fix the relative paths 336 | * to be relative no longer to the source file, but to the new path. 337 | * If we don't write to a file, fall back to same path so no 338 | * conversion happens (because we still want it to go through most 339 | * of the move code, which also addresses url() & @import syntax...) 340 | */ 341 | $converter = $this->getPathConverter($source, $path ?: $source); 342 | $css = $this->move($converter, $css); 343 | 344 | // combine css 345 | $content .= $css; 346 | } 347 | 348 | $content = $this->moveImportsToTop($content); 349 | 350 | return $content; 351 | } 352 | 353 | /** 354 | * Moving a css file should update all relative urls. 355 | * Relative references (e.g. ../images/image.gif) in a certain css file, 356 | * will have to be updated when a file is being saved at another location 357 | * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper). 358 | * 359 | * @param ConverterInterface $converter Relative path converter 360 | * @param string $content The CSS content to update relative urls for 361 | * 362 | * @return string 363 | */ 364 | protected function move(ConverterInterface $converter, $content) 365 | { 366 | /* 367 | * Relative path references will usually be enclosed by url(). @import 368 | * is an exception, where url() is not necessary around the path (but is 369 | * allowed). 370 | * This *could* be 1 regular expression, where both regular expressions 371 | * in this array are on different sides of a |. But we're using named 372 | * patterns in both regexes, the same name on both regexes. This is only 373 | * possible with a (?J) modifier, but that only works after a fairly 374 | * recent PCRE version. That's why I'm doing 2 separate regular 375 | * expressions & combining the matches after executing of both. 376 | */ 377 | $relativeRegexes = array( 378 | // url(xxx) 379 | '/ 380 | # open url() 381 | url\( 382 | 383 | \s* 384 | 385 | # open path enclosure 386 | (?P["\'])? 387 | 388 | # fetch path 389 | (?P.+?) 390 | 391 | # close path enclosure 392 | (?(quotes)(?P=quotes)) 393 | 394 | \s* 395 | 396 | # close url() 397 | \) 398 | 399 | /ix', 400 | 401 | // @import "xxx" 402 | '/ 403 | # import statement 404 | @import 405 | 406 | # whitespace 407 | \s+ 408 | 409 | # we don\'t have to check for @import url(), because the 410 | # condition above will already catch these 411 | 412 | # open path enclosure 413 | (?P["\']) 414 | 415 | # fetch path 416 | (?P.+?) 417 | 418 | # close path enclosure 419 | (?P=quotes) 420 | 421 | /ix', 422 | ); 423 | 424 | // find all relative urls in css 425 | $matches = array(); 426 | foreach ($relativeRegexes as $relativeRegex) { 427 | if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) { 428 | $matches = array_merge($matches, $regexMatches); 429 | } 430 | } 431 | 432 | $search = array(); 433 | $replace = array(); 434 | 435 | // loop all urls 436 | foreach ($matches as $match) { 437 | // determine if it's a url() or an @import match 438 | $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url'); 439 | 440 | $url = $match['path']; 441 | if ($this->canImportByPath($url)) { 442 | // attempting to interpret GET-params makes no sense, so let's discard them for awhile 443 | $params = strrchr($url, '?'); 444 | $url = $params ? substr($url, 0, -strlen($params)) : $url; 445 | 446 | // fix relative url 447 | $url = $converter->convert($url); 448 | 449 | // now that the path has been converted, re-apply GET-params 450 | $url .= $params; 451 | } 452 | 453 | /* 454 | * Urls with control characters above 0x7e should be quoted. 455 | * According to Mozilla's parser, whitespace is only allowed at the 456 | * end of unquoted urls. 457 | * Urls with `)` (as could happen with data: uris) should also be 458 | * quoted to avoid being confused for the url() closing parentheses. 459 | * And urls with a # have also been reported to cause issues. 460 | * Urls with quotes inside should also remain escaped. 461 | * 462 | * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation 463 | * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378 464 | * @see https://github.com/matthiasmullie/minify/issues/193 465 | */ 466 | $url = trim($url); 467 | if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) { 468 | $url = $match['quotes'] . $url . $match['quotes']; 469 | } 470 | 471 | // build replacement 472 | $search[] = $match[0]; 473 | if ($type === 'url') { 474 | $replace[] = 'url(' . $url . ')'; 475 | } elseif ($type === 'import') { 476 | $replace[] = '@import "' . $url . '"'; 477 | } 478 | } 479 | 480 | // replace urls 481 | return str_replace($search, $replace, $content); 482 | } 483 | 484 | /** 485 | * Shorthand HEX color codes. 486 | * #FF0000FF -> #f00 -> red 487 | * #FF00FF00 -> transparent. 488 | * 489 | * @param string $content The CSS content to shorten the HEX color codes for 490 | * 491 | * @return string 492 | */ 493 | protected function shortenHexColors($content) 494 | { 495 | // shorten repeating patterns within HEX .. 496 | $content = preg_replace('/(?<=[: ])#([0-9a-f])\\1([0-9a-f])\\2([0-9a-f])\\3(?:([0-9a-f])\\4)?(?=[; }])/i', '#$1$2$3$4', $content); 497 | 498 | // remove alpha channel if it's pointless .. 499 | $content = preg_replace('/(?<=[: ])#([0-9a-f]{6})ff(?=[; }])/i', '#$1', $content); 500 | $content = preg_replace('/(?<=[: ])#([0-9a-f]{3})f(?=[; }])/i', '#$1', $content); 501 | 502 | // replace `transparent` with shortcut .. 503 | $content = preg_replace('/(?<=[: ])#[0-9a-f]{6}00(?=[; }])/i', '#fff0', $content); 504 | 505 | $colors = array( 506 | // make these more readable 507 | '#00f' => 'blue', 508 | '#dc143c' => 'crimson', 509 | '#0ff' => 'cyan', 510 | '#8b0000' => 'darkred', 511 | '#696969' => 'dimgray', 512 | '#ff69b4' => 'hotpink', 513 | '#0f0' => 'lime', 514 | '#fdf5e6' => 'oldlace', 515 | '#87ceeb' => 'skyblue', 516 | '#d8bfd8' => 'thistle', 517 | // we can shorten some even more by replacing them with their color name 518 | '#f0ffff' => 'azure', 519 | '#f5f5dc' => 'beige', 520 | '#ffe4c4' => 'bisque', 521 | '#a52a2a' => 'brown', 522 | '#ff7f50' => 'coral', 523 | '#ffd700' => 'gold', 524 | '#808080' => 'gray', 525 | '#008000' => 'green', 526 | '#4b0082' => 'indigo', 527 | '#fffff0' => 'ivory', 528 | '#f0e68c' => 'khaki', 529 | '#faf0e6' => 'linen', 530 | '#800000' => 'maroon', 531 | '#000080' => 'navy', 532 | '#808000' => 'olive', 533 | '#ffa500' => 'orange', 534 | '#da70d6' => 'orchid', 535 | '#cd853f' => 'peru', 536 | '#ffc0cb' => 'pink', 537 | '#dda0dd' => 'plum', 538 | '#800080' => 'purple', 539 | '#f00' => 'red', 540 | '#fa8072' => 'salmon', 541 | '#a0522d' => 'sienna', 542 | '#c0c0c0' => 'silver', 543 | '#fffafa' => 'snow', 544 | '#d2b48c' => 'tan', 545 | '#008080' => 'teal', 546 | '#ff6347' => 'tomato', 547 | '#ee82ee' => 'violet', 548 | '#f5deb3' => 'wheat', 549 | // or the other way around 550 | 'black' => '#000', 551 | 'fuchsia' => '#f0f', 552 | 'magenta' => '#f0f', 553 | 'white' => '#fff', 554 | 'yellow' => '#ff0', 555 | // and also `transparent` 556 | 'transparent' => '#fff0', 557 | ); 558 | 559 | return preg_replace_callback( 560 | '/(?<=[: ])(' . implode('|', array_keys($colors)) . ')(?=[; }])/i', 561 | function ($match) use ($colors) { 562 | return $colors[strtolower($match[0])]; 563 | }, 564 | $content 565 | ); 566 | } 567 | 568 | /** 569 | * Convert RGB|HSL color codes. 570 | * rgb(255,0,0,.5) -> rgb(255 0 0 / .5). 571 | * rgb(255,0,0) -> #f00. 572 | * 573 | * @param string $content The CSS content to shorten the RGB color codes for 574 | * 575 | * @return string 576 | */ 577 | protected function convertLegacyColors($content) 578 | { 579 | /* 580 | https://drafts.csswg.org/css-color/#color-syntax-legacy 581 | https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb 582 | https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl 583 | */ 584 | 585 | // convert legacy color syntax 586 | $content = preg_replace('/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content); 587 | $content = preg_replace('/(rgb)a?\(\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*,\s*([0-9]{1,3}%?)\s*\)/i', '$1($2 $3 $4)', $content); 588 | $content = preg_replace('/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*,\s*([0,1]?(?:\.[0-9]*)?)\s*\)/i', '$1($2 $3 $4 / $5)', $content); 589 | $content = preg_replace('/(hsl)a?\(\s*([0-9]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9]{1,3}%)\s*,\s*([0-9]{1,3}%)\s*\)/i', '$1($2 $3 $4)', $content); 590 | 591 | // convert `rgb` to `hex` 592 | $dec = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])'; 593 | return preg_replace_callback( 594 | "/rgb\($dec $dec $dec\)/i", 595 | function ($match) { 596 | return sprintf('#%02x%02x%02x', $match[1], $match[2], $match[3]); 597 | }, 598 | $content 599 | ); 600 | } 601 | 602 | /** 603 | * Cleanup RGB|HSL|HWB|LCH|LAB 604 | * rgb(255 0 0 / 1) -> rgb(255 0 0). 605 | * rgb(255 0 0 / 0) -> transparent. 606 | * 607 | * @param string $content The CSS content to cleanup HSL|HWB|LCH|LAB 608 | * 609 | * @return string 610 | */ 611 | protected function cleanupModernColors($content) 612 | { 613 | /* 614 | https://drafts.csswg.org/css-color/#color-syntax-modern 615 | https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb 616 | https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch 617 | https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab 618 | https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch 619 | https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab 620 | */ 621 | $tag = '(rgb|hsl|hwb|(?:(?:ok)?(?:lch|lab)))'; 622 | 623 | // remove alpha channel if it's pointless .. 624 | $content = preg_replace('/' . $tag . '\(\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+\/\s+1(?:(?:\.\d?)*|00%)?\s*\)/i', '$1($2 $3 $4)', $content); 625 | 626 | // replace `transparent` with shortcut .. 627 | $content = preg_replace('/' . $tag . '\(\s*[^\s]+\s+[^\s]+\s+[^\s]+\s+\/\s+0(?:[\.0%]*)?\s*\)/i', '#fff0', $content); 628 | 629 | return $content; 630 | } 631 | 632 | /** 633 | * Shorten CSS font weights. 634 | * 635 | * @param string $content The CSS content to shorten the font weights for 636 | * 637 | * @return string 638 | */ 639 | protected function shortenFontWeights($content) 640 | { 641 | $weights = array( 642 | 'normal' => 400, 643 | 'bold' => 700, 644 | ); 645 | 646 | $callback = function ($match) use ($weights) { 647 | return $match[1] . $weights[$match[2]]; 648 | }; 649 | 650 | return preg_replace_callback('/(font-weight\s*:\s*)(' . implode('|', array_keys($weights)) . ')(?=[;}])/', $callback, $content); 651 | } 652 | 653 | /** 654 | * Shorthand 0 values to plain 0, instead of e.g. -0em. 655 | * 656 | * @param string $content The CSS content to shorten the zero values for 657 | * 658 | * @return string 659 | */ 660 | protected function shortenZeroes($content) 661 | { 662 | // we don't want to strip units in `calc()` expressions: 663 | // `5px - 0px` is valid, but `5px - 0` is not 664 | // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but 665 | // `10 * 0` is invalid 666 | // we've extracted calcs earlier, so we don't need to worry about this 667 | 668 | // reusable bits of code throughout these regexes: 669 | // before & after are used to make sure we don't match lose unintended 670 | // 0-like values (e.g. in #000, or in http://url/1.0) 671 | // units can be stripped from 0 values, or used to recognize non 0 672 | // values (where wa may be able to strip a .0 suffix) 673 | $before = '(?<=[:(, ])'; 674 | $after = '(?=[ ,);}])'; 675 | $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)'; 676 | 677 | // strip units after zeroes (0px -> 0) 678 | // NOTE: it should be safe to remove all units for a 0 value, but in 679 | // practice, Webkit (especially Safari) seems to stumble over at least 680 | // 0%, potentially other units as well. Only stripping 'px' for now. 681 | // @see https://github.com/matthiasmullie/minify/issues/60 682 | $content = preg_replace('/' . $before . '(-?0*(\.0+)?)(?<=0)px' . $after . '/', '\\1', $content); 683 | 684 | // strip 0-digits (.0 -> 0) 685 | $content = preg_replace('/' . $before . '\.0+' . $units . '?' . $after . '/', '0\\1', $content); 686 | // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px 687 | $content = preg_replace('/' . $before . '(-?[0-9]+\.[0-9]+)0+' . $units . '?' . $after . '/', '\\1\\2', $content); 688 | // strip trailing 0: 50.00 -> 50, 50.00px -> 50px 689 | $content = preg_replace('/' . $before . '(-?[0-9]+)\.0+' . $units . '?' . $after . '/', '\\1\\2', $content); 690 | // strip leading 0: 0.1 -> .1, 01.1 -> 1.1 691 | $content = preg_replace('/' . $before . '(-?)0+([0-9]*\.[0-9]+)' . $units . '?' . $after . '/', '\\1\\2\\3', $content); 692 | 693 | // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0) 694 | $content = preg_replace('/' . $before . '-?0+' . $units . '?' . $after . '/', '0\\1', $content); 695 | 696 | // IE doesn't seem to understand a unitless flex-basis value (correct - 697 | // it goes against the spec), so let's add it in again (make it `%`, 698 | // which is only 1 char: 0%, 0px, 0 anything, it's all just the same) 699 | // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex 700 | $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content); 701 | $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content); 702 | 703 | return $content; 704 | } 705 | 706 | /** 707 | * Strip empty tags from source code. 708 | * 709 | * @param string $content 710 | * 711 | * @return string 712 | */ 713 | protected function stripEmptyTags($content) 714 | { 715 | $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content); 716 | $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content); 717 | 718 | return $content; 719 | } 720 | 721 | /** 722 | * Strip comments from source code. 723 | */ 724 | protected function stripComments() 725 | { 726 | $this->stripMultilineComments(); 727 | } 728 | 729 | /** 730 | * Strip whitespace. 731 | * 732 | * @param string $content The CSS content to strip the whitespace for 733 | * 734 | * @return string 735 | */ 736 | protected function stripWhitespace($content) 737 | { 738 | // remove leading & trailing whitespace 739 | $content = preg_replace('/^\s*/m', '', $content); 740 | $content = preg_replace('/\s*$/m', '', $content); 741 | 742 | // replace newlines with a single space 743 | $content = preg_replace('/\s+/', ' ', $content); 744 | 745 | // remove whitespace around meta characters 746 | // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex 747 | $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content); 748 | $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content); 749 | $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content); 750 | $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content); 751 | 752 | // whitespace around + and - can only be stripped inside some pseudo- 753 | // classes, like `:nth-child(3+2n)` 754 | // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or 755 | // selectors like `div.weird- p` 756 | $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type'); 757 | $content = preg_replace('/:(' . implode('|', $pseudos) . ')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content); 758 | 759 | // remove semicolon/whitespace followed by closing bracket 760 | $content = str_replace(';}', '}', $content); 761 | 762 | return trim($content); 763 | } 764 | 765 | /** 766 | * Replace all occurrences of functions that may contain math, where 767 | * whitespace around operators needs to be preserved (e.g. calc, clamp). 768 | */ 769 | protected function extractMath() 770 | { 771 | $functions = array('calc', 'clamp', 'min', 'max'); 772 | $pattern = '/\b(' . implode('|', $functions) . ')(\(.+?)(?=$|;|})/m'; 773 | 774 | // PHP only supports $this inside anonymous functions since 5.4 775 | $minifier = $this; 776 | $callback = function ($match) use ($minifier, $pattern, &$callback) { 777 | $function = $match[1]; 778 | $length = strlen($match[2]); 779 | $expr = ''; 780 | $opened = 0; 781 | 782 | // the regular expression for extracting math has 1 significant problem: 783 | // it can't determine the correct closing parenthesis... 784 | // instead, it'll match a larger portion of code to where it's certain that 785 | // the calc() musts have ended, and we'll figure out which is the correct 786 | // closing parenthesis here, by counting how many have opened 787 | for ($i = 0; $i < $length; ++$i) { 788 | $char = $match[2][$i]; 789 | $expr .= $char; 790 | if ($char === '(') { 791 | ++$opened; 792 | } elseif ($char === ')' && --$opened === 0) { 793 | break; 794 | } 795 | } 796 | 797 | // now that we've figured out where the calc() starts and ends, extract it 798 | $count = count($minifier->extracted); 799 | $placeholder = 'math(' . $count . ')'; 800 | $minifier->extracted[$placeholder] = $function . '(' . trim(substr($expr, 1, -1)) . ')'; 801 | 802 | // and since we've captured more code than required, we may have some leftover 803 | // calc() in here too - go recursive on the remaining but of code to go figure 804 | // that out and extract what is needed 805 | $rest = $minifier->str_replace_first($function . $expr, '', $match[0]); 806 | $rest = preg_replace_callback($pattern, $callback, $rest); 807 | 808 | return $placeholder . $rest; 809 | }; 810 | 811 | $this->registerPattern($pattern, $callback); 812 | } 813 | 814 | /** 815 | * Replace custom properties, whose values may be used in scenarios where 816 | * we wouldn't want them to be minified (e.g. inside calc). 817 | */ 818 | protected function extractCustomProperties() 819 | { 820 | // PHP only supports $this inside anonymous functions since 5.4 821 | $minifier = $this; 822 | $this->registerPattern( 823 | '/(?<=^|[;}{])\s*(--[^:;{}"\'\s]+)\s*:([^;{}]+)/m', 824 | function ($match) use ($minifier) { 825 | $placeholder = '--custom-' . count($minifier->extracted) . ':0'; 826 | $minifier->extracted[$placeholder] = $match[1] . ':' . trim($match[2]); 827 | 828 | return $placeholder; 829 | } 830 | ); 831 | } 832 | 833 | /** 834 | * Check if file is small enough to be imported. 835 | * 836 | * @param string $path The path to the file 837 | * 838 | * @return bool 839 | */ 840 | protected function canImportBySize($path) 841 | { 842 | return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024; 843 | } 844 | 845 | /** 846 | * Check if file a file can be imported, going by the path. 847 | * 848 | * @param string $path 849 | * 850 | * @return bool 851 | */ 852 | protected function canImportByPath($path) 853 | { 854 | return preg_match('/^(data:|https?:|\\/)/', $path) === 0; 855 | } 856 | 857 | /** 858 | * Return a converter to update relative paths to be relative to the new 859 | * destination. 860 | * 861 | * @param string $source 862 | * @param string $target 863 | * 864 | * @return ConverterInterface 865 | */ 866 | protected function getPathConverter($source, $target) 867 | { 868 | return new Converter($source, $target); 869 | } 870 | } 871 | -------------------------------------------------------------------------------- /vendor/matthiasmullie/minify/src/JS.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 10 | * @license MIT License 11 | */ 12 | 13 | namespace MatthiasMullie\Minify; 14 | 15 | /** 16 | * JavaScript Minifier Class. 17 | * 18 | * Please report bugs on https://github.com/matthiasmullie/minify/issues 19 | * 20 | * @author Matthias Mullie 21 | * @author Tijs Verkoyen 22 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved 23 | * @license MIT License 24 | */ 25 | class JS extends Minify 26 | { 27 | /** 28 | * Var-matching regex based on http://stackoverflow.com/a/9337047/802993. 29 | * 30 | * Note that regular expressions using that bit must have the PCRE_UTF8 31 | * pattern modifier (/u) set. 32 | * 33 | * @internal 34 | * 35 | * @var string 36 | */ 37 | const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b'; 38 | 39 | /** 40 | * Full list of JavaScript reserved words. 41 | * Will be loaded from /data/js/keywords_reserved.txt. 42 | * 43 | * @see https://mathiasbynens.be/notes/reserved-keywords 44 | * 45 | * @var string[] 46 | */ 47 | protected $keywordsReserved = array(); 48 | 49 | /** 50 | * List of JavaScript reserved words that accept a 51 | * after them. Some end of lines are not the end of a statement, like with 52 | * these keywords. 53 | * 54 | * E.g.: we shouldn't insert a ; after this else 55 | * else 56 | * console.log('this is quite fine') 57 | * 58 | * Will be loaded from /data/js/keywords_before.txt 59 | * 60 | * @var string[] 61 | */ 62 | protected $keywordsBefore = array(); 63 | 64 | /** 65 | * List of JavaScript reserved words that accept a 66 | * before them. Some end of lines are not the end of a statement, like when 67 | * continued by one of these keywords on the newline. 68 | * 69 | * E.g.: we shouldn't insert a ; before this instanceof 70 | * variable 71 | * instanceof String 72 | * 73 | * Will be loaded from /data/js/keywords_after.txt 74 | * 75 | * @var string[] 76 | */ 77 | protected $keywordsAfter = array(); 78 | 79 | /** 80 | * List of all JavaScript operators. 81 | * 82 | * Will be loaded from /data/js/operators.txt 83 | * 84 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators 85 | * 86 | * @var string[] 87 | */ 88 | protected $operators = array(); 89 | 90 | /** 91 | * List of JavaScript operators that accept a after 92 | * them. Some end of lines are not the end of a statement, like with these 93 | * operators. 94 | * 95 | * Note: Most operators are fine, we've only removed ++ and --. 96 | * ++ & -- have to be joined with the value they're in-/decrementing. 97 | * 98 | * Will be loaded from /data/js/operators_before.txt 99 | * 100 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators 101 | * 102 | * @var string[] 103 | */ 104 | protected $operatorsBefore = array(); 105 | 106 | /** 107 | * List of JavaScript operators that accept a before 108 | * them. Some end of lines are not the end of a statement, like when 109 | * continued by one of these operators on the newline. 110 | * 111 | * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~. 112 | * There can't be a newline separating ! or ~ and whatever it is negating. 113 | * ++ & -- have to be joined with the value they're in-/decrementing. 114 | * ) & ] are "special" in that they have lots or usecases. () for example 115 | * is used for function calls, for grouping, in if () and for (), ... 116 | * 117 | * Will be loaded from /data/js/operators_after.txt 118 | * 119 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators 120 | * 121 | * @var string[] 122 | */ 123 | protected $operatorsAfter = array(); 124 | 125 | public function __construct() 126 | { 127 | call_user_func_array(array('\\MatthiasMullie\Minify\\Minify', '__construct'), func_get_args()); 128 | 129 | $dataDir = __DIR__ . '/../data/js/'; 130 | $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES; 131 | $this->keywordsReserved = file($dataDir . 'keywords_reserved.txt', $options); 132 | $this->keywordsBefore = file($dataDir . 'keywords_before.txt', $options); 133 | $this->keywordsAfter = file($dataDir . 'keywords_after.txt', $options); 134 | $this->operators = file($dataDir . 'operators.txt', $options); 135 | $this->operatorsBefore = file($dataDir . 'operators_before.txt', $options); 136 | $this->operatorsAfter = file($dataDir . 'operators_after.txt', $options); 137 | } 138 | 139 | /** 140 | * Minify the data. 141 | * Perform JS optimizations. 142 | * 143 | * @param string[optional] $path Path to write the data to 144 | * 145 | * @return string The minified data 146 | */ 147 | public function execute($path = null) 148 | { 149 | $content = ''; 150 | 151 | /* 152 | * Let's first take out strings, comments and regular expressions. 153 | * All of these can contain JS code-like characters, and we should make 154 | * sure any further magic ignores anything inside of these. 155 | * 156 | * Consider this example, where we should not strip any whitespace: 157 | * var str = "a test"; 158 | * 159 | * Comments will be removed altogether, strings and regular expressions 160 | * will be replaced by placeholder text, which we'll restore later. 161 | */ 162 | $this->extractStrings('\'"`'); 163 | $this->stripComments(); 164 | $this->extractRegex(); 165 | 166 | // loop files 167 | foreach ($this->data as $source => $js) { 168 | // take out strings, comments & regex (for which we've registered 169 | // the regexes just a few lines earlier) 170 | $js = $this->replace($js); 171 | 172 | $js = $this->propertyNotation($js); 173 | $js = $this->shortenBools($js); 174 | $js = $this->stripWhitespace($js); 175 | 176 | // combine js: separating the scripts by a ; 177 | $content .= $js . ';'; 178 | } 179 | 180 | // clean up leftover `;`s from the combination of multiple scripts 181 | $content = ltrim($content, ';'); 182 | $content = (string) substr($content, 0, -1); 183 | 184 | /* 185 | * Earlier, we extracted strings & regular expressions and replaced them 186 | * with placeholder text. This will restore them. 187 | */ 188 | $content = $this->restoreExtractedData($content); 189 | 190 | return $content; 191 | } 192 | 193 | /** 194 | * Strip comments from source code. 195 | */ 196 | protected function stripComments() 197 | { 198 | $this->stripMultilineComments(); 199 | 200 | // single-line comments 201 | $this->registerPattern('/\/\/.*$/m', ''); 202 | } 203 | 204 | /** 205 | * JS can have /-delimited regular expressions, like: /ab+c/.match(string). 206 | * 207 | * The content inside the regex can contain characters that may be confused 208 | * for JS code: e.g. it could contain whitespace it needs to match & we 209 | * don't want to strip whitespace in there. 210 | * 211 | * The regex can be pretty simple: we don't have to care about comments, 212 | * (which also use slashes) because stripComments() will have stripped those 213 | * already. 214 | * 215 | * This method will replace all string content with simple REGEX# 216 | * placeholder text, so we've rid all regular expressions from characters 217 | * that may be misinterpreted. Original regex content will be saved in 218 | * $this->extracted and after doing all other minifying, we can restore the 219 | * original content via restoreRegex() 220 | */ 221 | protected function extractRegex() 222 | { 223 | // PHP only supports $this inside anonymous functions since 5.4 224 | $minifier = $this; 225 | $callback = function ($match) use ($minifier) { 226 | $count = count($minifier->extracted); 227 | $placeholder = '"' . $count . '"'; 228 | $minifier->extracted[$placeholder] = $match[0]; 229 | 230 | return $placeholder; 231 | }; 232 | 233 | // match all chars except `/` and `\` 234 | // `\` is allowed though, along with whatever char follows (which is the 235 | // one being escaped) 236 | // this should allow all chars, except for an unescaped `/` (= the one 237 | // closing the regex) 238 | // then also ignore bare `/` inside `[]`, where they don't need to be 239 | // escaped: anything inside `[]` can be ignored safely 240 | $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*'; 241 | 242 | // a regular expression can only be followed by a few operators or some 243 | // of the RegExp methods (a `\` followed by a variable or value is 244 | // likely part of a division, not a regex) 245 | $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof'); 246 | $before = '(^|[=:,;\+\-\*\?\/\}\(\{\[&\|!]|' . implode('|', $keywords) . ')\s*'; 247 | $propertiesAndMethods = array( 248 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 249 | 'constructor', 250 | 'flags', 251 | 'global', 252 | 'ignoreCase', 253 | 'multiline', 254 | 'source', 255 | 'sticky', 256 | 'unicode', 257 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2 258 | 'compile(', 259 | 'exec(', 260 | 'test(', 261 | 'toSource(', 262 | 'toString(', 263 | ); 264 | $delimiters = array_fill(0, count($propertiesAndMethods), '/'); 265 | $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters); 266 | $after = '(?=\s*([\.,;:\)\}&\|+]|\/\/|$|\.(' . implode('|', $propertiesAndMethods) . ')))'; 267 | $this->registerPattern('/' . $before . '\K' . $pattern . $after . '/', $callback); 268 | 269 | // regular expressions following a `)` are rather annoying to detect... 270 | // quite often, `/` after `)` is a division operator & if it happens to 271 | // be followed by another one (or a comment), it is likely to be 272 | // confused for a regular expression 273 | // however, it's perfectly possible for a regex to follow a `)`: after 274 | // a single-line `if()`, `while()`, ... statement, for example 275 | // since, when they occur like that, they're always the start of a 276 | // statement, there's only a limited amount of ways they can be useful: 277 | // by calling the regex methods directly 278 | // if a regex following `)` is not followed by `.`, 279 | // it's quite likely not a regex 280 | $before = '\)\s*'; 281 | $after = '(?=\s*\.(' . implode('|', $propertiesAndMethods) . '))'; 282 | $this->registerPattern('/' . $before . '\K' . $pattern . $after . '/', $callback); 283 | 284 | // 1 more edge case: a regex can be followed by a lot more operators or 285 | // keywords if there's a newline (ASI) in between, where the operator 286 | // actually starts a new statement 287 | // (https://github.com/matthiasmullie/minify/issues/56) 288 | $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); 289 | $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); 290 | $after = '(?=\s*\n\s*(' . implode('|', $operators) . '))'; 291 | $this->registerPattern('/' . $pattern . $after . '/', $callback); 292 | } 293 | 294 | /** 295 | * Strip whitespace. 296 | * 297 | * We won't strip *all* whitespace, but as much as possible. The thing that 298 | * we'll preserve are newlines we're unsure about. 299 | * JavaScript doesn't require statements to be terminated with a semicolon. 300 | * It will automatically fix missing semicolons with ASI (automatic semi- 301 | * colon insertion) at the end of line causing errors (without semicolon.) 302 | * 303 | * Because it's sometimes hard to tell if a newline is part of a statement 304 | * that should be terminated or not, we'll just leave some of them alone. 305 | * 306 | * @param string $content The content to strip the whitespace for 307 | * 308 | * @return string 309 | */ 310 | protected function stripWhitespace($content) 311 | { 312 | // uniform line endings, make them all line feed 313 | $content = str_replace(array("\r\n", "\r"), "\n", $content); 314 | 315 | // collapse all non-line feed whitespace into a single space 316 | $content = preg_replace('/[^\S\n]+/', ' ', $content); 317 | 318 | // strip leading & trailing whitespace 319 | $content = str_replace(array(" \n", "\n "), "\n", $content); 320 | 321 | // collapse consecutive line feeds into just 1 322 | $content = preg_replace('/\n+/', "\n", $content); 323 | 324 | $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/'); 325 | $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/'); 326 | $operators = $this->getOperatorsForRegex($this->operators, '/'); 327 | $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/'); 328 | $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/'); 329 | 330 | // strip whitespace that ends in (or next line begin with) an operator 331 | // that allows statements to be broken up over multiple lines 332 | unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); 333 | $content = preg_replace( 334 | array( 335 | '/(' . implode('|', $operatorsBefore) . ')\s+/', 336 | '/\s+(' . implode('|', $operatorsAfter) . ')/', 337 | ), 338 | '\\1', 339 | $content 340 | ); 341 | 342 | // make sure + and - can't be mistaken for, or joined into ++ and -- 343 | $content = preg_replace( 344 | array( 345 | '/(?%&|', $delimiter); 474 | $operators['='] = '(?keywordsReserved; 516 | $callback = function ($match) use ($minifier, $keywords) { 517 | $property = trim($minifier->extracted[$match[1]], '\'"'); 518 | 519 | /* 520 | * Check if the property is a reserved keyword. In this context (as 521 | * property of an object literal/array) it shouldn't matter, but IE8 522 | * freaks out with "Expected identifier". 523 | */ 524 | if (in_array($property, $keywords)) { 525 | return $match[0]; 526 | } 527 | 528 | /* 529 | * See if the property is in a variable-like format (e.g. 530 | * array['key-here'] can't be replaced by array.key-here since '-' 531 | * is not a valid character there. 532 | */ 533 | if (!preg_match('/^' . $minifier::REGEX_VARIABLE . '$/u', $property)) { 534 | return $match[0]; 535 | } 536 | 537 | return '.' . $property; 538 | }; 539 | 540 | /* 541 | * Figure out if previous character is a variable name (of the array 542 | * we want to use property notation on) - this is to make sure 543 | * standalone ['value'] arrays aren't confused for keys-of-an-array. 544 | * We can (and only have to) check the last character, because PHP's 545 | * regex implementation doesn't allow unfixed-length look-behind 546 | * assertions. 547 | */ 548 | preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar); 549 | $previousChar = $previousChar[1]; 550 | 551 | /* 552 | * Make sure word preceding the ['value'] is not a keyword, e.g. 553 | * return['x']. Because -again- PHP's regex implementation doesn't allow 554 | * unfixed-length look-behind assertions, I'm just going to do a lot of 555 | * separate look-behind assertions, one for each keyword. 556 | */ 557 | $keywords = $this->getKeywordsForRegex($keywords); 558 | $keywords = '(?