mirror of
https://github.com/mrclay/minify.git
synced 2025-09-06 12:10:45 +02:00
Compare commits
407 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
45463786f8 | ||
|
00e5e20abe | ||
|
9e182a6255 | ||
|
bc1ffdc5fc | ||
|
aa3721156e | ||
|
3047f5c48f | ||
|
104ccd2bfe | ||
|
dc02cdfba7 | ||
|
23f055d3f5 | ||
|
4970127e91 | ||
|
39c37bdd8c | ||
|
4a9d0595e3 | ||
|
ca8fdc6fdd | ||
|
8c69866874 | ||
|
ae5b9f0bfb | ||
|
e74adc4f6b | ||
|
5dee6538be | ||
|
455084b284 | ||
|
5112bb50f4 | ||
|
1962d3614c | ||
|
2226a6add1 | ||
|
8b91de51c3 | ||
|
1c000f9d05 | ||
|
cbda49556e | ||
|
b314554a0f | ||
|
9dac0ac873 | ||
|
c80add80a2 | ||
|
ad732493b8 | ||
|
ad0fb95556 | ||
|
f1572a580a | ||
|
40f9dafe5f | ||
|
c814ff4bae | ||
|
8382fb85d8 | ||
|
efec97278f | ||
|
e9d4db41ac | ||
|
b89bc57164 | ||
|
69e3eb241f | ||
|
b17c00c05c | ||
|
e3fe3001c3 | ||
|
8600312ed1 | ||
|
070c6c0349 | ||
|
b31855f6b8 | ||
|
031e804d08 | ||
|
263381a23f | ||
|
a36bfd50b0 | ||
|
8dba84a2d2 | ||
|
0f607be23a | ||
|
d2a3e8c1d2 | ||
|
e59454bed5 | ||
|
eb5197f9d4 | ||
|
20f336f598 | ||
|
94829874f9 | ||
|
f0aa0aa7d7 | ||
|
5705fc9d89 | ||
|
3036ee9f55 | ||
|
6e3ae8417a | ||
|
d39fdd9e6b | ||
|
d35e780865 | ||
|
7378a0efe8 | ||
|
c0dd8f50a1 | ||
|
63a812af8d | ||
|
8836df06d2 | ||
|
2f33b69786 | ||
|
8e4b0ceb86 | ||
|
266ddd7d54 | ||
|
07ed67aa77 | ||
|
2bbcce054d | ||
|
9040437901 | ||
|
f954e1bd58 | ||
|
a8a77e6054 | ||
|
25065b1559 | ||
|
807f41903e | ||
|
b81ad2289a | ||
|
fecf395de3 | ||
|
2bd66ae3f9 | ||
|
4453f3a4cb | ||
|
f50353a952 | ||
|
bd7d1077b3 | ||
|
40f89b528c | ||
|
b1bbdccef0 | ||
|
258e495451 | ||
|
b97a1db01d | ||
|
3081a88dbd | ||
|
df7fddfa09 | ||
|
c580d24e1b | ||
|
f9c96b6a3c | ||
|
d3decb27e1 | ||
|
0038cdb5de | ||
|
5659799c42 | ||
|
57be61586e | ||
|
2198482600 | ||
|
c2f40feb0c | ||
|
5c300aca97 | ||
|
76c1edc6ff | ||
|
919fc10139 | ||
|
bb7fc74330 | ||
|
eb4c0f6541 | ||
|
b6ef6fa397 | ||
|
00b9f28630 | ||
|
be40d4f9b2 | ||
|
9ee5fb7701 | ||
|
7d60fb9d0f | ||
|
6236431c8f | ||
|
404664509c | ||
|
9ed7f9dc24 | ||
|
d9b392c474 | ||
|
004f6bf730 | ||
|
72ece76cdf | ||
|
bde7a41021 | ||
|
88aca4019d | ||
|
70952d88f9 | ||
|
6b274afd0b | ||
|
449f009b39 | ||
|
6a5b09a00e | ||
|
93a1edd34b | ||
|
a6531d3481 | ||
|
9de63f3acc | ||
|
ed239fbb8b | ||
|
332f4bc839 | ||
|
79e5a22321 | ||
|
80c01c6f02 | ||
|
b6e4b7f152 | ||
|
a1489b2d8a | ||
|
d041f763c9 | ||
|
8056a4a9fb | ||
|
e3555570ef | ||
|
a8304c5fb3 | ||
|
dfb97a5c85 | ||
|
4626f6c1e0 | ||
|
e904f2e1ae | ||
|
62370404c8 | ||
|
532a010e0e | ||
|
d95690abc9 | ||
|
f79794bece | ||
|
5d334009ee | ||
|
cc1bd24af6 | ||
|
5f15af469d | ||
|
234b9459ca | ||
|
8333f333be | ||
|
a07991c052 | ||
|
b874144e99 | ||
|
ddf3a4e57f | ||
|
12b9096b23 | ||
|
d1786045b2 | ||
|
94436b214d | ||
|
1936c946e8 | ||
|
625bc5ac0a | ||
|
1f5641ea2c | ||
|
6fc0648a0d | ||
|
56d55abc01 | ||
|
005dc26e22 | ||
|
ed209f9a84 | ||
|
8fe915f486 | ||
|
6a330d9091 | ||
|
8949df33dd | ||
|
b8c79ac8d4 | ||
|
b31ddbf4c1 | ||
|
74042c87ad | ||
|
8b965059a1 | ||
|
16c811cd93 | ||
|
5fb7ea1ed1 | ||
|
0cc631c5a9 | ||
|
2809e3c2eb | ||
|
a9891a031c | ||
|
8d4a90e83f | ||
|
035183b2b8 | ||
|
72dd4db37a | ||
|
78d5921f91 | ||
|
2bd69ca5b1 | ||
|
3014900cd4 | ||
|
ae1fdf4a30 | ||
|
b5a0ed3e3a | ||
|
fdc69a369e | ||
|
9ca1bc9e74 | ||
|
2d2e06f438 | ||
|
60b89dcc0d | ||
|
140673f5b6 | ||
|
59d4c97ffc | ||
|
a0e781e278 | ||
|
578f99d6d2 | ||
|
02c859b01b | ||
|
49bd730e1a | ||
|
22fb644834 | ||
|
850e81c416 | ||
|
b37d837ff9 | ||
|
42b18c1e2a | ||
|
31518971be | ||
|
2045731d60 | ||
|
de39966e9c | ||
|
7aac6792bc | ||
|
6f94b78135 | ||
|
865946c0ed | ||
|
02e7358538 | ||
|
0db2af8741 | ||
|
6a2995f932 | ||
|
b38ed79042 | ||
|
bb6e4d5f2e | ||
|
28197576c6 | ||
|
b7cf3808d7 | ||
|
cff094781b | ||
|
5da955966b | ||
|
0eb2cfe78d | ||
|
ce2b8d694c | ||
|
2d3c417a51 | ||
|
24b601217e | ||
|
4710509c68 | ||
|
f4cb31135d | ||
|
7c95ee7540 | ||
|
c29dc274c4 | ||
|
afbcadf33c | ||
|
86bd761a5f | ||
|
7dbd2c87e4 | ||
|
237d83d3cb | ||
|
0b466c0892 | ||
|
9de4e927c7 | ||
|
72ae74f1c5 | ||
|
14bde12d3b | ||
|
30961eb2ee | ||
|
8fc327bec6 | ||
|
42875e8388 | ||
|
b2ac4a52bb | ||
|
e6f2df52b3 | ||
|
379feaba99 | ||
|
90bf31f53b | ||
|
22d34533ac | ||
|
cd49391833 | ||
|
c387014e27 | ||
|
7a73d781f2 | ||
|
f5c12ad0f4 | ||
|
01d4835d14 | ||
|
3fcb383f49 | ||
|
3f02443c1f | ||
|
da70e92cc1 | ||
|
6998e61654 | ||
|
04d7ab0856 | ||
|
a89b1b9efc | ||
|
3060d2861b | ||
|
0ab74bb892 | ||
|
1ab6bcffc1 | ||
|
07dd4f1958 | ||
|
628f9bdebb | ||
|
ff5b1249db | ||
|
2471fbfc6e | ||
|
6f923c66ee | ||
|
492222df18 | ||
|
fe359bbf8a | ||
|
8d1f4ad765 | ||
|
ac1e254c48 | ||
|
2819e133a5 | ||
|
47e2c178a3 | ||
|
316a109032 | ||
|
9370e96fd4 | ||
|
2a24e8a31a | ||
|
ae4b49fcee | ||
|
8a35a8d9ca | ||
|
6c95daf3cf | ||
|
8816e270e6 | ||
|
3a9687ad4e | ||
|
6e7767aa3e | ||
|
16b8296c34 | ||
|
fa9f5db843 | ||
|
5b82a781dc | ||
|
5d46afbc72 | ||
|
3cb97e2e47 | ||
|
f71dd4c79f | ||
|
554542401f | ||
|
ed79a279a9 | ||
|
e328c0b4af | ||
|
79bcb7d5fe | ||
|
ec22663e3e | ||
|
d91bcf29fb | ||
|
3f0b99fb52 | ||
|
316b384b1f | ||
|
badb6ce191 | ||
|
f9f3e3ca83 | ||
|
31d81df2af | ||
|
830640c0e1 | ||
|
47c2af74d6 | ||
|
4f344b5d21 | ||
|
a856756705 | ||
|
68f011d73f | ||
|
4bfe976916 | ||
|
08070aecdb | ||
|
6838349e3a | ||
|
e2bb6b0565 | ||
|
27b5f4b2d0 | ||
|
5458206700 | ||
|
273a08f2ee | ||
|
29f20d95b3 | ||
|
1f9166a693 | ||
|
35a9ab7d4a | ||
|
0a74b9c4a6 | ||
|
0873db81b9 | ||
|
aa825fc4ed | ||
|
e271c370a9 | ||
|
8e85853af8 | ||
|
590cf9b57e | ||
|
d184597a2f | ||
|
3a3354aec6 | ||
|
5f2c54f636 | ||
|
70c0d4b3bf | ||
|
53a832fd90 | ||
|
5793db9098 | ||
|
ed06912f74 | ||
|
6e8a656480 | ||
|
674460ccd3 | ||
|
b7b26e3a83 | ||
|
1aaf8f014f | ||
|
12e7a8d0dd | ||
|
a566536f80 | ||
|
f769e35233 | ||
|
271d8ca5bf | ||
|
a0d99f5be5 | ||
|
ed1d2d9baf | ||
|
e2efb342a8 | ||
|
fede83cd48 | ||
|
7c607ff932 | ||
|
db8915b2db | ||
|
6e08631c1d | ||
|
61ba5a47a5 | ||
|
0bc3769802 | ||
|
db1ea29133 | ||
|
e596b35fc4 | ||
|
2720239c8c | ||
|
0cb403096b | ||
|
de8e8be37e | ||
|
80f50b1915 | ||
|
2e79efe47c | ||
|
f88109bbf1 | ||
|
f928043059 | ||
|
588c859a78 | ||
|
f941628393 | ||
|
6b01037509 | ||
|
a16413a6b1 | ||
|
20fe2fd67c | ||
|
2847351c10 | ||
|
24e7b0fbfe | ||
|
1c3c370dd4 | ||
|
9eaef368e0 | ||
|
5b6b891cc3 | ||
|
d71b11ec98 | ||
|
44e35e69c3 | ||
|
c24af9fe02 | ||
|
69019960c9 | ||
|
0dea940e5f | ||
|
f1ef53e84e | ||
|
d233b65d3d | ||
|
cabd595a5a | ||
|
f9b728918a | ||
|
73a085b6c1 | ||
|
7775469b78 | ||
|
d3006ddb63 | ||
|
76e2669180 | ||
|
4f0741a935 | ||
|
44683b2e63 | ||
|
3c11ba8232 | ||
|
ecbb5ae376 | ||
|
0e9e1237c6 | ||
|
efe603d454 | ||
|
bce6facdca | ||
|
0cbbcd0a53 | ||
|
4b8de3d14c | ||
|
96da497e9c | ||
|
4b495f6ae0 | ||
|
d9bebb5566 | ||
|
16069cc0c0 | ||
|
bd9a450694 | ||
|
beb750df3e | ||
|
9716fe802c | ||
|
6d9fe1531e | ||
|
c75f97f3bc | ||
|
4910238a9f | ||
|
7ff9438362 | ||
|
ee47d21be2 | ||
|
31a80b43a6 | ||
|
5193c76581 | ||
|
bd8f3faacc | ||
|
f29e1e6b06 | ||
|
54cec8b304 | ||
|
bb67f5762b | ||
|
d1b299d33f | ||
|
bceffd5afb | ||
|
d40d71cdff | ||
|
153e08b5a0 | ||
|
ce109925aa | ||
|
a0c5ecb514 | ||
|
45cf750b3a | ||
|
dca6be63e3 | ||
|
0624820859 | ||
|
6817584513 | ||
|
4f5cdbb746 | ||
|
6b469640a7 | ||
|
fb3931f8cd | ||
|
50488ce207 | ||
|
ebc91b5d33 | ||
|
e7728405f5 | ||
|
537b4b314a | ||
|
4a884a381f | ||
|
b018c834da | ||
|
27b4f758bc | ||
|
9231aed49f | ||
|
ea7fe90554 | ||
|
5ceada8a22 | ||
|
66f2622705 | ||
|
f3a408fd74 | ||
|
c60b6a2984 | ||
|
54a53c9c19 |
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
|
||||
[*.php]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
|
||||
[vendor/**]
|
||||
; Use editor default (possible autodetection).
|
||||
indent_style =
|
||||
indent_size =
|
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/.editorconfig export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/min_extras export-ignore
|
||||
/tests export-ignore
|
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,7 +1,12 @@
|
||||
|
||||
# /
|
||||
/test
|
||||
/docs
|
||||
.idea/
|
||||
# ignore IDE/hidden/OS cache files
|
||||
*~
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
.DS_Store
|
||||
vendor
|
||||
|
||||
/.idea/
|
||||
/composer.lock
|
||||
/vendor
|
||||
/.php_cs.cache
|
||||
/static/[0-9]*
|
||||
/tests/compiler.jar
|
||||
|
23
.php_cs
Normal file
23
.php_cs
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
$rules = array(
|
||||
'@PSR2' => true,
|
||||
);
|
||||
|
||||
$config = PhpCsFixer\Config::create();
|
||||
$finder = $config->getFinder();
|
||||
|
||||
$finder
|
||||
->in(array('.', 'builder/', 'lib/', 'tests/', 'min_extras/', 'static/'))
|
||||
->name('*.php')
|
||||
->ignoreDotFiles(true)
|
||||
->ignoreVCS(true);
|
||||
|
||||
return $config
|
||||
->setUsingCache(true)
|
||||
->setRiskyAllowed(true)
|
||||
->setRules($rules)
|
||||
->setIndent(' ')
|
||||
->setLineEnding("\n");
|
||||
|
||||
// vim:ft=php
|
6
.semver
Normal file
6
.semver
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
:major: 3
|
||||
:minor: 0
|
||||
:patch: 14
|
||||
:special: ''
|
||||
:metadata: ''
|
55
.travis.yml
Normal file
55
.travis.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
language: php
|
||||
sudo: false
|
||||
dist: trusty
|
||||
|
||||
jobs:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- php: "nightly"
|
||||
- name: "Php CS Fixer"
|
||||
include:
|
||||
- php: "5.3"
|
||||
dist: precise
|
||||
- php: "5.4"
|
||||
- php: "5.5"
|
||||
- php: "5.6"
|
||||
- php: "7.0"
|
||||
- php: "7.1"
|
||||
- php: "7.2"
|
||||
- php: "7.3"
|
||||
- php: "7.4"
|
||||
- php: "nightly"
|
||||
- name: "Php CS Fixer"
|
||||
php: "7.3"
|
||||
env:
|
||||
- PHP_CS_FIXER=1
|
||||
|
||||
env:
|
||||
- CLOSURE_VERSION: 20161024
|
||||
|
||||
services:
|
||||
- memcached
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
- vendor
|
||||
|
||||
install:
|
||||
- composer update --no-interaction --prefer-source
|
||||
|
||||
before_script:
|
||||
- tests/dl-closure.sh
|
||||
|
||||
script:
|
||||
- PATH=vendor/bin:$PATH
|
||||
- composer validate
|
||||
- |
|
||||
if [ "$PHP_CS_FIXER" ]; then
|
||||
composer config --unset platform.php
|
||||
composer require --dev friendsofphp/php-cs-fixer:2.16
|
||||
php-cs-fixer fix --verbose --diff --dry-run --ansi
|
||||
fi
|
||||
- vendor/bin/phpunit --verbose
|
||||
|
||||
# vim:ts=2:sw=2:et
|
310
CHANGELOG.md
Normal file
310
CHANGELOG.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [4.0.0] - 2024-01-04
|
||||
|
||||
- Support PHP 8.1+
|
||||
- Update PHPUnit to 8.x
|
||||
- Replace `leafo/scssphp` with `scssphp/scssphp`
|
||||
|
||||
## [3.0.14] - 2023-05-05
|
||||
|
||||
- Support monolog v3, [#705]
|
||||
- Allow invalidation from manual invocation, [#700]
|
||||
- Add property declaration, [#699]
|
||||
|
||||
[3.0.14]: https://github.com/mrclay/minify/compare/3.0.13...3.0.14
|
||||
[#705]: https://github.com/mrclay/minify/pull/705
|
||||
[#700]: https://github.com/mrclay/minify/pull/700
|
||||
[#699]: https://github.com/mrclay/minify/pull/699
|
||||
|
||||
## [3.0.13] - 2022-10-03
|
||||
|
||||
- Add `Minify_Cache_APCu` to replace `Minify_Cache_APC`, [#697]
|
||||
- Require `marcusschwarz/lesserphp:^0.5.5` to fix php 7.4 compatibility, [#688]
|
||||
|
||||
[3.0.13]: https://github.com/mrclay/minify/compare/3.0.12...3.0.13
|
||||
[#697]: https://github.com/mrclay/minify/pull/697
|
||||
[#688]: https://github.com/mrclay/minify/pull/688
|
||||
|
||||
## [3.0.12] - 2022-05-14
|
||||
|
||||
- Update jquery to 1.12.4 to avoid xss attacks, [#692]
|
||||
- Fix null argument to preg_split, [#696], [#695]
|
||||
|
||||
[3.0.12]: https://github.com/mrclay/minify/compare/3.0.11...3.0.12
|
||||
[#692]: https://github.com/mrclay/minify/pull/692
|
||||
[#696]: https://github.com/mrclay/minify/pull/696
|
||||
[#695]: https://github.com/mrclay/minify/issues/695
|
||||
|
||||
## [3.0.11] - 2021-03-11
|
||||
|
||||
- PHP 8.0 support, [#685], [#682], [#677]
|
||||
|
||||
[3.0.11]: https://github.com/mrclay/minify/compare/3.0.10...3.0.11
|
||||
[#685]: https://github.com/mrclay/minify/pull/685
|
||||
[#682]: https://github.com/mrclay/minify/pull/682
|
||||
[#677]: https://github.com/mrclay/minify/pull/677
|
||||
|
||||
## [3.0.10] - 2020-04-02
|
||||
|
||||
- Exclude SSI Comments from HTML minify, [#670], [#671]
|
||||
|
||||
[3.0.10]: https://github.com/mrclay/minify/compare/3.0.9...3.0.10
|
||||
[#671]: https://github.com/mrclay/minify/issues/671
|
||||
[#670]: https://github.com/mrclay/minify/pull/670
|
||||
|
||||
## [3.0.9] - 2020-03-24
|
||||
|
||||
- Allow `intervention/httpauth` 3.x, [#667], [#666], [#664]
|
||||
|
||||
[3.0.9]: https://github.com/mrclay/minify/compare/3.0.8...3.0.9
|
||||
[#664]: https://github.com/mrclay/minify/issues/664
|
||||
[#666]: https://github.com/mrclay/minify/pull/666
|
||||
[#667]: https://github.com/mrclay/minify/pull/667
|
||||
|
||||
## [3.0.8] - 2020-03-19
|
||||
|
||||
- Removed deprecated get_magic_quotes_gpc() function that since PHP 5.4.0 returns FALSE always, and since PHP 7.4 is deprecated, [#661]
|
||||
|
||||
[3.0.8]: https://github.com/mrclay/minify/compare/3.0.7...3.0.8
|
||||
[#661]: https://github.com/mrclay/minify/pull/661
|
||||
|
||||
## [3.0.7] - 2019-12-10
|
||||
|
||||
- Allow mrclay/props-dic ^3.0, [#658]
|
||||
|
||||
[3.0.7]: https://github.com/mrclay/minify/compare/3.0.6...3.0.7
|
||||
[#658]: https://github.com/mrclay/minify/pull/658
|
||||
|
||||
## [3.0.6] - 2019-10-28
|
||||
|
||||
- Bugfix for option sanitizer, [#654], [#655]
|
||||
|
||||
[3.0.6]: https://github.com/mrclay/minify/compare/3.0.5...3.0.6
|
||||
[#654]: https://github.com/mrclay/minify/issues/654
|
||||
[#655]: https://github.com/mrclay/minify/pull/655
|
||||
|
||||
## [3.0.5] - 2019-10-01
|
||||
|
||||
- Fix syntax error in composer.json, [#653]
|
||||
|
||||
[3.0.5]: https://github.com/mrclay/minify/compare/3.0.4...3.0.5
|
||||
[#653]: https://github.com/mrclay/minify/pull/653
|
||||
|
||||
## 3.0.4 - 2019-09-24
|
||||
|
||||
- Fix PHP 7.3 compatibility issues, [#648]
|
||||
|
||||
[3.0.4]: https://github.com/mrclay/minify/compare/3.0.3...3.0.4
|
||||
[#648]: https://github.com/mrclay/minify/issues/648
|
||||
|
||||
## [3.0.3] - 2017-11-03
|
||||
|
||||
- Fix closure-compiler's error "redirection limit reached". [#618], [#619]
|
||||
|
||||
[3.0.3]: https://github.com/mrclay/minify/compare/3.0.2...3.0.3
|
||||
[#618]: https://github.com/mrclay/minify/pull/618
|
||||
[#619]: https://github.com/mrclay/minify/issues/619
|
||||
|
||||
## [3.0.2] - 2017-09-14
|
||||
|
||||
- Fixes syntax error in Groups controller, [#613]
|
||||
- Better-maintained lessphp fork, [#610]
|
||||
- No longer corrupts some chars in some environments, [#608]
|
||||
|
||||
[3.0.2]: https://github.com/mrclay/minify/compare/3.0.1...3.0.2
|
||||
[#608]: https://github.com/mrclay/minify/pull/608
|
||||
[#610]: https://github.com/mrclay/minify/pull/610
|
||||
[#613]: https://github.com/mrclay/minify/issues/613
|
||||
|
||||
## [3.0.1] - 2017-06-09
|
||||
|
||||
- Update CSSmin to v4, [#599], [#590]
|
||||
|
||||
[3.0.1]: https://github.com/mrclay/minify/compare/3.0.0...3.0.1
|
||||
[#590]: https://github.com/mrclay/minify/issues/590
|
||||
[#599]: https://github.com/mrclay/minify/pull/599
|
||||
|
||||
## 3.0.0 - 2017-04-03
|
||||
|
||||
* Improved CSS minification via Túbal Martín's CSSMin
|
||||
* Easier error identification (just see error_log)
|
||||
* Adds feature to serve static files directly
|
||||
* Adds config option for simply concatenating files
|
||||
* Adds config option for altering creation of Minify/MinApp objects
|
||||
* Missing spec no longer redirects, instead links to docs
|
||||
* Installation requires use of Composer to install dependencies
|
||||
* Minify::VERSION is an int that tracks the major version number
|
||||
* BREAKING: The project root is now what gets deployed as `min`
|
||||
* BREAKING: Removes JSMin
|
||||
* BREAKING: Removes JSMin+ (unmaintained, high memory usage)
|
||||
* BREAKING: Removes DooDigestAuth
|
||||
* BREAKING: Removes Minify_Loader (uses Composer)
|
||||
* BREAKING: Removes Minify_Logger (uses Monolog)
|
||||
* BREAKING: Removes `$min_libPath` option
|
||||
* BREAKING: The Minify, source, and controller components have changed APIs
|
||||
|
||||
## 2.3.0 - 2016-03-11
|
||||
|
||||
* Adds `$min_concatOnly` option to just concatenate files
|
||||
* Deprecates use of Minify_Loader
|
||||
* Deprecates use of Minify_Logger
|
||||
* Deprecates use of JSMinPlus
|
||||
* Deprecates use of FirePHP
|
||||
* Deprecates use of DooDigestAuth
|
||||
|
||||
## 2.2.1 - 2014-10-30
|
||||
|
||||
* Builder styled with Bootstrap (thanks to help from acidvertigo)
|
||||
* Update CSSmin to v.2.4.8
|
||||
* Added WinCache
|
||||
* URLs with spaces properly rewritten
|
||||
|
||||
## 2.2.0 - 2014-03-12
|
||||
|
||||
* Fix handling of RegEx in certain situations in JSMin
|
||||
* Thanks to Vovan-VE for reporting this
|
||||
* Update composer.json with support info
|
||||
* Add ability to set ClosureCompiler URL
|
||||
* Thanks Elan Ruusamäe for the pull request
|
||||
* Better report of temp directory errors
|
||||
* Also thanks to Elan Ruusamäe for anatoher pull request
|
||||
* Updated CSSmin and added Minify_CSSmin wrapper
|
||||
* Fix windows issue associated with long cache filenames
|
||||
* Fix issue with web-based tool
|
||||
* Fix bug in JSMin exceptions
|
||||
* Fix "about:blank" bug in CSS_UriRewriter
|
||||
* Cite is no longer a block element in HTML minification
|
||||
* Allow for definition of custom config locations outside of the min directory
|
||||
* Thanks Sam Bauers for the pull request
|
||||
* Allow option for overriding the maximum byte size POST limit for ClosureCompiler and other additions
|
||||
* Thanks Joscha Feth for the code
|
||||
* Fixes to file-relative URL identification in UriRewriter
|
||||
* Allow far-future expiration and file versioning with the "v" querystirng parameter in addition to existing method
|
||||
* Lots of general code tidy ups
|
||||
|
||||
## 2.1.7 - 2013-07-23
|
||||
|
||||
* Fixes arbitrary file inclusion vulnerability on some systems
|
||||
* Thanks to Matt Mecham for reporting this
|
||||
|
||||
## 2.1.6 - 2013-07-19
|
||||
|
||||
* JSMin fixes
|
||||
* Prevents some Closure Compiler API failures
|
||||
* Uses autoloading for all class loading
|
||||
* Multiple group support in HTML Helper
|
||||
* Cache adaptor for XCache
|
||||
* Allow setting stack-size in YUI Compressor wrapper
|
||||
* Adds jsCleanComments option to HTML minifier
|
||||
* Upgrades CSSmin
|
||||
* CLI script more portable
|
||||
* Adds composer.json
|
||||
|
||||
## 2.1.5 - 2012-03-10
|
||||
|
||||
* Removed XSS vulnerability
|
||||
* Disabled builder by default
|
||||
* command line tools to minify and rewrite URIs in CSS
|
||||
* upgrade (optional) JSMin+ library
|
||||
* more efficient JS minification when using CC/YUIC
|
||||
* Closure Compiler uses cURL when allow\_url\_fopen is off
|
||||
* Missing file notices when using groups
|
||||
|
||||
## 2.1.4b - 2010-07-10
|
||||
|
||||
* Option to minify JS with Closure Compiler API w/ JSMin failover
|
||||
* Cookie/bookmarklet-based debug mode. No HTML editing!
|
||||
* Allows 1 file to be missing w/o complete failure
|
||||
* Combine multiple groups and files in single URI
|
||||
* More useful HTML helpers for writing versioned URIs
|
||||
* More detailed error logging, including minifier exceptions
|
||||
* Builder offers more helpful messages/PHP environment warnings
|
||||
* Bypass minification based on filename pattern. e.g. foo.min.js / foo-min.css
|
||||
* JSMin won't choke on common Closure compiler syntaxes (`i+ ++j`)
|
||||
* Better caching in IE6
|
||||
* Cache ids are influenced by group/file names
|
||||
* Debug mode for Javascript doesn't break on common XPath strings (Prototype 1.6)
|
||||
* Removed annoying maxFiles limit
|
||||
* mbstring.func\_overload usage is safer
|
||||
|
||||
## 2.1.3 - 2009-06-30
|
||||
|
||||
* CSS fixes
|
||||
* A few URI rewriting bugs fixed
|
||||
* comment/whitespace removal no longer breaks some values
|
||||
* IE6 [pseudo-element selector bug](http://www.crankygeek.com/ie6pebug/) no longer triggered
|
||||
* HTTP fixes
|
||||
* Proper Expires handling in webkit (dropped "must-revalidate", which triggered a [webkit bug](http://mrclay.org/index.php/2009/02/24/safari-4-beta-cache-controlmust-revalidate-bug/))
|
||||
* ETag generation now valid ([must be unique when gzipped](https://issues.apache.org/bugzilla/show_bug.cgi?id=39727))
|
||||
* Vary header always sent when Accept-Encoding is sniffed
|
||||
* Dropped deflate encoding, since browser and proxy support [could be buggy](http://stackoverflow.com/questions/883841/).
|
||||
* File cache now works w/o setting `$min_cachePath`
|
||||
* No more 5.3 deprecation warnings: `split()` removed
|
||||
* API: Can set contentType Minify\_Source objects (fixes an annoying [caveat](http://groups.google.com/group/minify/msg/8446d32ee99a4961))
|
||||
* [Resolved Issue list](http://code.google.com/p/minify/issues/list?can=1&q=label%3ARelease-2.1.2%20status%3AVerified)
|
||||
|
||||
## 2.1.2 - 2009-03-04
|
||||
|
||||
* Javascript fixes
|
||||
* Debug mode no longer confused by `*/*` in strings/RegExps (jQuery)
|
||||
* quote characters inside RegExp literals no longer cause exception
|
||||
* files ending in single-line comments no longer cause code loss
|
||||
* CSS: data: URLs no longer mangled
|
||||
* Optional error logging to Firefox's FirePHP extension
|
||||
* Unit tests to check for common DOCUMENT\_ROOT problems
|
||||
* DOCUMENT\_ROOT no longer overwritten on IIS servers
|
||||
* Builder app doesn't fail on systems without gzdeflate()
|
||||
* APC caching class included
|
||||
|
||||
## 2.1.1 - 2008-10-19
|
||||
|
||||
* Bug fix release
|
||||
* Detection and workarounds for zlib.output\_compression and non-PHP encoding modules
|
||||
* Zlib not required (mod\_rewrite, et.al., can still be used for encoding)
|
||||
* HTML : More IE conditional comments preserved
|
||||
* Minify\_groupUri() utility fixed
|
||||
|
||||
## 2.1.0 - 2008-09-18
|
||||
|
||||
* "min" default application for quick deployment
|
||||
* Minify URI Builder app & bookmarklet for quickly creating minify URIs
|
||||
* Relative URIs in CSS file are fixed automatically by default
|
||||
* "debug" mode for revealing original line #s in combined files
|
||||
* Better IIS support
|
||||
* Improved minifier classes:
|
||||
* JS: preserves IE conditional comments
|
||||
* CSS: smaller output, preserves more hacks and valid CSS syntax, shorter line lengths, other bug fixes
|
||||
* HTML: smaller output, shorter line lengths, other bug fixes
|
||||
* Default Cache-Control: max-age of 30 minutes
|
||||
* Conditional GETs supported even when max-age sent
|
||||
* Experimental memcache cache class (default is files)
|
||||
* Minify\_Cache\_File has flock()s (by default)
|
||||
* Workaround for Windows mtime reporting bug
|
||||
|
||||
## 2.0.0 - 2008-05-22
|
||||
|
||||
* Complete code overhaul. Minify is now a PEAR-style class and toolkit for building customized minifying file servers.
|
||||
* Content-Encoding: deflate/gzip/compress, based on request headers
|
||||
* Expanded CSS and HTML minifiers with test cases
|
||||
* Easily plug-in 3rd-party minifiers (like Packer)
|
||||
* Plug-able front end controller allows changing the way files are chosen
|
||||
* Compression & encoding modules lazy-loaded as needed (304 responses use minimal code)
|
||||
* Separate utility classes for HTTP encoding and cache control
|
||||
|
||||
## 1.0.1 - 2007-05-05
|
||||
|
||||
* Fixed various problems resolving pathnames when hosted on an NFS mount.
|
||||
* Fixed 'undefined constant' notice.
|
||||
* Replaced old JSMin library with a much faster custom implementation.
|
||||
|
||||
## 1.0.0 - 2007-05-02
|
||||
|
||||
* First release.
|
138
HISTORY.txt
138
HISTORY.txt
@@ -1,138 +0,0 @@
|
||||
Minify Release History
|
||||
|
||||
Version 2.2.0
|
||||
* Fix handling of RegEx in certain situations in JSMin
|
||||
* Thanks to Vovan-VE for reporting this
|
||||
* Update composer.json with support info
|
||||
* Add ability to set ClosureCompiler URL
|
||||
* Thanks Elan Ruusamäe for the pull request
|
||||
* Better report of temp directory errors
|
||||
* Also thanks to Elan Ruusamäe for anatoher pull request
|
||||
* Updated CSSmin and added Minify_CSSmin wrapper
|
||||
* Fix windows issue associated with long cache filenames
|
||||
* Fix issue with web-based tool
|
||||
* Fix bug in JSMin exceptions
|
||||
* Fix "about:blank" bug in CSS_UriRewriter
|
||||
* Cite is no longer a block element in HTML minification
|
||||
* Allow for definition of custom config locations outside of the min directory
|
||||
* Thanks Sam Bauers for the pull request
|
||||
* Allow option for overriding the maximum byte size POST limit for ClosureCompiler and other additions
|
||||
* Thanks Joscha Feth for the code
|
||||
* Fixes to file-relative URL identification in UriRewriter
|
||||
* Allow far-future expiration and file versioning with the "v" querystirng parameter in addition to existing method
|
||||
* Lots of general code tidy ups
|
||||
|
||||
Version 2.1.7
|
||||
* Fixes arbitrary file inclusion vulnerability on some systems
|
||||
* Thanks to Matt Mecham for reporting this
|
||||
|
||||
Version 2.1.6
|
||||
* JSMin fixes
|
||||
* Prevents some Closure Compiler API failures
|
||||
* Uses autoloading for all class loading
|
||||
* Multiple group support in HTML Helper
|
||||
* Cache adaptor for XCache
|
||||
* Allow setting stack-size in YUI Compressor wrapper
|
||||
* Adds jsCleanComments option to HTML minifier
|
||||
* Upgrades CSSmin
|
||||
* CLI script more portable
|
||||
* Adds composer.json
|
||||
|
||||
Version 2.1.5
|
||||
* Removed XSS vulnerability
|
||||
* Disabled builder bby default
|
||||
* command line tools to minify and rewrite URIs in CSS
|
||||
* upgrade (optional) JSMin+ library
|
||||
* more efficient JS minification when using CC/YUIC
|
||||
* Closure Compiler uses cURL when allow_url_fopen is off
|
||||
* Missing file notices when using groups
|
||||
|
||||
Version 2.1.4
|
||||
* Option to minify JS with Closure Compiler API w/ JSMin failover
|
||||
* Cookie/bookmarklet-based debug mode. No HTML editing!
|
||||
* Allows 1 file to be missing w/o complete failure
|
||||
* Combine multiple groups and files in single URI
|
||||
* More useful HTML helpers for writing versioned URIs
|
||||
* More detailed error logging, including minifier exceptions
|
||||
* Builder offers more helpful messages/PHP environment warnings
|
||||
* Bypass minification based on filename pattern. e.g. foo.min.js / foo-min.css
|
||||
* JSMin won't choke on common Closure compiler syntaxes (i+ ++j)
|
||||
* Better caching in IE6
|
||||
* Cache ids are influenced by group/file names
|
||||
* Debug mode for Javascript doesn't break on common XPath strings (Prototype 1.6)
|
||||
* Removed annoying maxFiles limit
|
||||
* mbstring.func_overload usage is safer
|
||||
|
||||
Version 2.1.3
|
||||
* HTTP fixes
|
||||
* ETag generation now valid (different when gzipped)
|
||||
* Vary header always sent when Accept-Encoding is sniffed
|
||||
* Cache-Control no longer has "must-revalidate" due to webkit bug
|
||||
See: http://mrclay.org/index.php/2009/02/24/safari-4-beta-cache-controlmust-revalidate-bug/
|
||||
* Dropped deflate encoding. Browser and proxy support could be buggy.
|
||||
See: http://stackoverflow.com/questions/883841/
|
||||
* File cache now works w/o setting $min_cachePath
|
||||
* Allow setting contentType in Minify_Source objects
|
||||
* No more 5.3 deprecation warnings: split() removed
|
||||
|
||||
Version 2.1.2
|
||||
* Javascript fixes
|
||||
* Debug mode no longer confused by "*/*" in strings/RegExps (jQuery)
|
||||
* quote characters inside RegExp literals no longer cause exception
|
||||
* files ending in single-line comments no longer cause code loss
|
||||
* CSS: data: URLs no longer mangled
|
||||
* Optional error logging to Firefox's FirePHP extension
|
||||
* Unit tests to check for common DOCUMENT_ROOT problems
|
||||
* DOCUMENT_ROOT no longer overwritten on IIS servers
|
||||
* Builder app doesn't fail on systems without gzdeflate()
|
||||
* APC caching class included
|
||||
|
||||
Version 2.1.1
|
||||
* Bug fix release
|
||||
* Detection and workarounds for zlib.output_compression and non-PHP encoding modules
|
||||
* Zlib not required (mod_rewrite, et.al., can still be used for encoding)
|
||||
* HTML : More IE conditional comments preserved
|
||||
* Minify_groupUri() utility fixed
|
||||
|
||||
Version 2.1.0
|
||||
* "min" default application for quick deployment
|
||||
* Minify URI Builder app & bookmarklet for quickly creating minify URIs
|
||||
* Relative URIs in CSS file are fixed automatically by default
|
||||
* "debug" mode for revealing original line #s in combined files
|
||||
* Better IIS support
|
||||
* Improved minifier classes:
|
||||
* JS: preserves IE conditional comments
|
||||
* CSS: smaller output, preserves more hacks and valid CSS syntax,
|
||||
shorter line lengths, other bug fixes
|
||||
* HTML: smaller output, shorter line lengths, other bug fixes
|
||||
* Default Cache-Control: max-age of 30 minutes
|
||||
* Conditional GETs supported even when max-age sent
|
||||
* Experimental memcache cache class (default is files)
|
||||
* Minify_Cache_File has flock()s (by default)
|
||||
* Workaround for Windows mtime reporting bug
|
||||
|
||||
Version 2.0.2 beta (2008-06-24)
|
||||
* Fast new cache system. Cached files served almost 3x as fast.
|
||||
* Dropped support of compress encoding (though HTTP_Encoder still supports it)
|
||||
|
||||
Version 2.0.1 (2008-05-31)
|
||||
* E_STRICT compliance (Cache_Lite_File).
|
||||
|
||||
Version 2.0.0 (2008-05-22)
|
||||
* Complete code overhaul. Minify is now a PEAR-style class and toolkit
|
||||
for building customized minifying file servers.
|
||||
* Content-Encoding: deflate/gzip/compress, based on request headers
|
||||
* Expanded CSS and HTML minifiers with test cases
|
||||
* Easily plug-in 3rd-party minifiers (like Packer)
|
||||
* Plug-able front end controller allows changing the way files are chosen
|
||||
* Compression & encoding modules lazy-loaded as needed (304 responses use
|
||||
use minimal code)
|
||||
* Separate utility classes for HTTP encoding and cache control
|
||||
|
||||
Version 1.0.1 (2007-05-05)
|
||||
* Fixed various problems resolving pathnames when hosted on an NFS mount.
|
||||
* Fixed 'undefined constant' notice.
|
||||
* Replaced old JSMin library with a much faster custom implementation.
|
||||
|
||||
Version 1.0.0 (2007-05-02)
|
||||
* First release.
|
181
MIN.txt
181
MIN.txt
@@ -1,181 +0,0 @@
|
||||
The files in the /min/ directory represent the default Minify setup designed to ease
|
||||
integration with your site. This app will combine and minify your Javascript or
|
||||
CSS files and serve them with HTTP compression and cache headers.
|
||||
|
||||
|
||||
RECOMMENDED
|
||||
|
||||
It's recommended to edit /min/config.php to set $min_cachePath to a writeable
|
||||
(by PHP) directory on your system. This will improve performance.
|
||||
|
||||
|
||||
GETTING STARTED
|
||||
|
||||
The quickest way to get started is to use the Minify URI Builder application
|
||||
on your website: http://example.com/min/builder/
|
||||
|
||||
|
||||
MINIFYING A SINGLE FILE
|
||||
|
||||
Let's say you want to serve this file:
|
||||
http://example.com/wp-content/themes/default/default.css
|
||||
|
||||
Here's the "Minify URL" for this file:
|
||||
http://example.com/min/?f=wp-content/themes/default/default.css
|
||||
|
||||
In other words, the "f" argument is set to the file path from root without the
|
||||
initial "/". As CSS files may contain relative URIs, Minify will automatically
|
||||
"fix" these by rewriting them as root relative.
|
||||
|
||||
|
||||
COMBINING MULTIPLE FILES IN ONE DOWNLOAD
|
||||
|
||||
Separate the paths given to "f" with commas.
|
||||
|
||||
Let's say you have CSS files at these URLs:
|
||||
http://example.com/scripts/jquery-1.2.6.js
|
||||
http://example.com/scripts/site.js
|
||||
|
||||
You can combine these files through Minify by requesting this URL:
|
||||
http://example.com/min/?f=scripts/jquery-1.2.6.js,scripts/site.js
|
||||
|
||||
|
||||
SIMPLIFYING URLS WITH A BASE PATH
|
||||
|
||||
If you're combining files that share the same ancestor directory, you can use
|
||||
the "b" argument to set the base directory for the "f" argument. Do not include
|
||||
the leading or trailing "/" characters.
|
||||
|
||||
E.g., the following URLs will serve the exact same content:
|
||||
http://example.com/min/?f=scripts/jquery-1.2.6.js,scripts/site.js,scripts/home.js
|
||||
http://example.com/min/?b=scripts&f=jquery-1.2.6.js,site.js,home.js
|
||||
|
||||
|
||||
MINIFY URLS IN HTML
|
||||
|
||||
In HTML files, don't forget to replace any "&" characters with "&".
|
||||
|
||||
|
||||
SPECIFYING ALLOWED DIRECTORIES
|
||||
|
||||
By default, Minify will serve any *.css/*.js files within the DOCUMENT_ROOT. If
|
||||
you'd prefer to limit Minify's access to certain directories, set the
|
||||
$min_serveOptions['minApp']['allowDirs'] array in config.php. E.g. to limit
|
||||
to the /js and /themes/default directories, use:
|
||||
|
||||
$min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default');
|
||||
|
||||
|
||||
GROUPS: NICER URLS
|
||||
|
||||
For nicer URLs, edit groupsConfig.php to pre-specify groups of files
|
||||
to be combined under preset keys. E.g., here's an example configuration in
|
||||
groupsConfig.php:
|
||||
|
||||
return array(
|
||||
'js' => array('//js/Class.js', '//js/email.js')
|
||||
);
|
||||
|
||||
This pre-selects the following files to be combined under the key "js":
|
||||
http://example.com/js/Class.js
|
||||
http://example.com/js/email.js
|
||||
|
||||
You can now serve these files with this simple URL:
|
||||
http://example.com/min/?g=js
|
||||
|
||||
|
||||
GROUPS: SPECIFYING FILES OUTSIDE THE DOC_ROOT
|
||||
|
||||
In the groupsConfig.php array, the "//" in the file paths is a shortcut for
|
||||
the DOCUMENT_ROOT, but you can also specify paths from the root of the filesystem
|
||||
or relative to the DOC_ROOT:
|
||||
|
||||
return array(
|
||||
'js' => array(
|
||||
'//js/file.js' // file within DOC_ROOT
|
||||
,'//../file.js' // file in parent directory of DOC_ROOT
|
||||
,'C:/Users/Steve/file.js' // file anywhere on filesystem
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
COMBINE MULTIPLE GROUPS AND FILES IN ONE URL
|
||||
|
||||
E.g.: http://example.com/min/?g=js&f=more/scripts.js
|
||||
|
||||
Separate group keys with commas:
|
||||
http://example.com/min/?g=baseCss,css1&f=moreStyles.css
|
||||
|
||||
|
||||
FAR-FUTURE EXPIRES HEADERS
|
||||
|
||||
Minify can send far-future (one year) Expires headers. To enable this you must
|
||||
add a number or the parameter "v" to the querystring (e.g. /min/?g=js&1234 or
|
||||
/min/?g=js&v=1234) and alter it whenever a source file is changed. If you have a
|
||||
build process you can use a build/source control revision number.
|
||||
|
||||
You can alternately use the utility function Minify_getUri() to get a "versioned"
|
||||
Minify URI for use in your HTML. E.g.:
|
||||
|
||||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'] . '/min/utils.php';
|
||||
|
||||
$jsUri = Minify_getUri('js'); // a key in groupsConfig.php
|
||||
echo "<script src='{$jsUri}'></script>";
|
||||
|
||||
$cssUri = Minify_getUri(array(
|
||||
'//css/styles1.css'
|
||||
,'//css/styles2.css'
|
||||
)); // a list of files
|
||||
echo "<link rel=stylesheet href='{$cssUri}'>";
|
||||
|
||||
|
||||
STORING CONFIG FILES OUTSIDE THE MINIFY DIRECTORY
|
||||
|
||||
It is possible to store config files (min/config.php, min/config-test.php,
|
||||
min/groupsConfig.php) in a custom directory outside the Minify directory. This is
|
||||
useful if you wish to include Minify as an external dependency inside another
|
||||
project via SVN external or Git submodule inclusion.
|
||||
|
||||
For example, let's assume you have a Minify directory "min" in your site root. Then
|
||||
you could create a new directory called "min-configs" in the site root. Copy any
|
||||
config files you wish to modify to "min-configs", and modify as desired.
|
||||
|
||||
Then create a new file, for example "min.php" in your site root. The contents of
|
||||
this file could look like this:
|
||||
|
||||
<?php
|
||||
$customConfigDirectory = dirname(__FILE__) . '/min-configs';
|
||||
$min_customConfigPaths = array(
|
||||
'base' => $customConfigDirectory . '/config.php',
|
||||
'test' => $customConfigDirectory . '/config-test.php',
|
||||
'groups' => $customConfigDirectory . '/groupsConfig.php'
|
||||
);
|
||||
|
||||
include_once 'min/index.php';
|
||||
|
||||
You would then reference min.php in your JS and CSS links instead of min/index.php.
|
||||
|
||||
This method will affect those using the Minify_getUri() function. You will need
|
||||
to add options to calls to that function, e.g.:
|
||||
|
||||
<?php
|
||||
require $_SERVER['DOCUMENT_ROOT'] . '/min/utils.php';
|
||||
|
||||
$jsUri = Minify_getUri('//js/file.js', array('minAppUri' => '/min.php'));
|
||||
echo "<script src='{$jsUri}'></script>";
|
||||
|
||||
|
||||
DEBUG MODE
|
||||
|
||||
In debug mode, instead of compressing files, Minify sends combined files with
|
||||
comments prepended to each line to show the line number in the original source
|
||||
file. To enable this, set $min_allowDebugFlag to true in config.php and append
|
||||
"&debug=1" to your URIs. E.g. /min/?f=script1.js,script2.js&debug=1
|
||||
|
||||
Known issue: files with comment-like strings/regexps can cause problems in this mode.
|
||||
|
||||
|
||||
QUESTIONS?
|
||||
|
||||
http://groups.google.com/group/minify
|
54
README.md
Normal file
54
README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Welcome to Minify!
|
||||
|
||||
Minify is an HTTP server for JS and CSS assets. It compresses and combines files
|
||||
and serves it with appropriate headers, allowing conditional GET or long-Expires.
|
||||
|
||||
| *Before* |  |
|
||||
|----------|-----------------------------------------------------------------|
|
||||
| *After* |  |
|
||||
|
||||
The stats above are from a [brief walkthrough](http://mrclay.org/index.php/2008/09/19/minify-21-on-mrclayorg/) which shows how easy it is to set up Minify on an existing site. It eliminated 5 HTTP requests and reduced JS/CSS bandwidth by 70%.
|
||||
|
||||
Relative URLs in CSS files are rewritten to compensate for being served from a different directory.
|
||||
|
||||
## Static file serving
|
||||
|
||||
Version 3 allows [serving files directly from the filesystem](static/README.md) for much better performance. We encourage you to try this feature.
|
||||
|
||||
## Support
|
||||
|
||||
Post to the [Google Group](http://groups.google.com/group/minify).
|
||||
|
||||
## Installation
|
||||
|
||||
See the [install guide](docs/Install.wiki.md).
|
||||
|
||||
## Configuration & Usage
|
||||
|
||||
(Using 2.x? [Here are the 2.x docs](https://github.com/mrclay/minify/tree/2.x/docs).)
|
||||
|
||||
See the [user guide](docs/UserGuide.wiki.md).
|
||||
|
||||
Minify also comes with a [URI Builder application](docs/BuilderApp.wiki.md) that can help you write URLs
|
||||
for use with Minify or configure groups of files.
|
||||
|
||||
See the [cookbook](docs/CookBook.wiki.md) for more advanced options for minification.
|
||||
|
||||
More [docs are available](docs).
|
||||
|
||||
## Unit Testing
|
||||
|
||||
1. Install dev deps via Composer: `composer install`
|
||||
1. `composer test` or `phpunit`
|
||||
|
||||
## Warnings
|
||||
|
||||
* Minify is designed for efficiency, but, for very high traffic sites, it will probably serve files slower than your HTTPd due to the CGI overhead of PHP. See the [FAQ](docs/FAQ.wiki.md#how-fast-is-it) and [CookBook](docs/CookBook.wiki.md) for more info.
|
||||
* If you combine a lot of CSS, watch out for [IE's 4096 selectors-per-file limit](http://stackoverflow.com/a/9906889/3779), affects IE 6 through 9.
|
||||
* Minify *should* work fine with files encoded in UTF-8 or other 8-bit encodings like ISO 8859/Windows-1252. By default Minify appends ";charset=utf-8" to the Content-Type headers it sends.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Minify was inspired by [jscsscomp](http://code.google.com/p/jscsscomp/) by Maxim Martynyuk and by the article [Supercharged JavaScript](http://www.hunlock.com/blogs/Supercharged_Javascript) by Patrick Hunlock.
|
||||
|
||||
The [JSMin library](http://www.crockford.com/javascript/jsmin.html) used for JavaScript minification was originally written by Douglas Crockford and was [ported to PHP](https://github.com/mrclay/jsmin-php) by Ryan Grove specifically for use in Minify.
|
68
README.txt
68
README.txt
@@ -1,68 +0,0 @@
|
||||
WELCOME TO MINIFY!
|
||||
|
||||
Minify is an HTTP content server. It compresses sources of content
|
||||
(usually files), combines the result and serves it with appropriate
|
||||
HTTP headers. These headers can allow clients to perform conditional
|
||||
GETs (serving content only when clients do not have a valid cache)
|
||||
and tell clients to cache the file for a period of time.
|
||||
More info: http://code.google.com/p/minify/
|
||||
|
||||
|
||||
WORDPRESS USER?
|
||||
|
||||
These WP plugins integrate Minify into WordPress's style and script hooks to
|
||||
get you set up faster.
|
||||
http://wordpress.org/extend/plugins/bwp-minify/
|
||||
http://wordpress.org/extend/plugins/w3-total-cache/
|
||||
|
||||
|
||||
INSTALLATION
|
||||
|
||||
Place the /min/ directory as a child of your DOCUMENT_ROOT
|
||||
directory: i.e. you will have: /home/example/www/min
|
||||
|
||||
You can see verify that it is working by visiting these two URLs:
|
||||
http://example.org/min/?f=min/quick-test.js
|
||||
http://example.org/min/?f=min/quick-test.css
|
||||
|
||||
If your server supports mod_rewrite, this URL should also work:
|
||||
http://example.org/min/f=min/quick-test.js
|
||||
|
||||
CONFIGURATION & USAGE
|
||||
|
||||
See the MIN.txt file and http://code.google.com/p/minify/wiki/UserGuide
|
||||
|
||||
Minify also comes with a URI Builder application that can help you write URLs
|
||||
for use with Minify or configure groups of files. See here for details:
|
||||
http://code.google.com/p/minify/wiki/BuilderApp
|
||||
|
||||
The cookbook also provides some more advanced options for minification:
|
||||
http://code.google.com/p/minify/wiki/CookBook
|
||||
|
||||
UPGRADING
|
||||
|
||||
See UPGRADING.txt for instructions.
|
||||
|
||||
|
||||
UNIT TESTING:
|
||||
|
||||
1. Place the /min_unit_tests/ directory as a child of your DOCUMENT_ROOT
|
||||
directory: i.e. you will have: /home/example/www/min_unit_tests
|
||||
|
||||
2. To run unit tests, access: http://example.org/min_unit_tests/test_all.php
|
||||
|
||||
(If you wish, the other test_*.php files can be run to test individual
|
||||
components with more verbose output.)
|
||||
|
||||
3. Remove /min_unit_tests/ from your DOCUMENT_ROOT when you are done.
|
||||
|
||||
|
||||
FILE ENCODINGS
|
||||
|
||||
Minify *should* work fine with files encoded in UTF-8 or other 8-bit
|
||||
encodings like ISO 8859/Windows-1252. By default Minify appends
|
||||
";charset=utf-8" to the Content-Type headers it sends.
|
||||
|
||||
Leading UTF-8 BOMs are stripped from all sources to prevent
|
||||
duplication in output files, and files are converted to Unix newlines.
|
||||
|
@@ -7,7 +7,7 @@ UPGRADING FROM 2.1.*
|
||||
/min/config.php --> /min/old_config.php
|
||||
/min/groupsConfig.php --> /min/old_groupsConfig.php
|
||||
|
||||
2. Overwrite all files in /min (and /min_unit_tests) with those from this zip.
|
||||
2. Overwrite all files in /min with those from this zip.
|
||||
|
||||
3. Delete /min/groupsConfig.php
|
||||
|
||||
|
25
bootstrap.php
Normal file
25
bootstrap.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up autoloading and returns the Minify\App
|
||||
*/
|
||||
|
||||
call_user_func(function () {
|
||||
if (is_dir(__DIR__ . '/../../../vendor')) {
|
||||
// Used as a composer library
|
||||
$vendorDir = __DIR__ . '/../../../vendor';
|
||||
} else {
|
||||
$vendorDir = __DIR__ . '/vendor';
|
||||
}
|
||||
|
||||
$file = $vendorDir . '/autoload.php';
|
||||
if (!is_file($file)) {
|
||||
echo 'You must set up the project dependencies, run the following commands:'.PHP_EOL.
|
||||
'curl -sS https://getcomposer.org/installer | php'.PHP_EOL.
|
||||
'php composer.phar install'.PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require $file;
|
||||
});
|
||||
|
||||
return new \Minify\App(__DIR__);
|
@@ -12,7 +12,7 @@ var MUB = {
|
||||
$.ajax({
|
||||
url : '../f=' + testUri + '&' + (new Date()).getTime(),
|
||||
success : function (data) {
|
||||
if (data === '1') {
|
||||
if (data === '1' || data === '1;') {
|
||||
MUB._minRoot = '/min/';
|
||||
$('span.minRoot').html('/min/');
|
||||
} else
|
||||
@@ -25,9 +25,9 @@ var MUB = {
|
||||
* Get markup for new source LI element
|
||||
*/
|
||||
newLi : function () {
|
||||
return '<li id="li' + MUB._uid + '">http://' + location.host + '/<input type=text size=20>' +
|
||||
' <button title="Remove">x</button> <button title="Include Earlier">↑</button>' +
|
||||
' <button title="Include Later">↓</button> <span></span></li>';
|
||||
return '<li id="li' + MUB._uid + '">' + location.protocol + '//' + location.host + '/<input type=text size=20>' +
|
||||
' <button class="btn btn-danger btn-sm" title="Remove">x</button> <button class="btn btn-default btn-sm" title="Include Earlier">↑</button>' +
|
||||
' <button class="btn btn-default btn-sm" title="Include Later">↓</button> <span></span></li>';
|
||||
},
|
||||
/**
|
||||
* Add new empty source LI and attach handlers to buttons
|
||||
@@ -36,13 +36,13 @@ var MUB = {
|
||||
$('#sources').append(MUB.newLi());
|
||||
var li = $('#li' + MUB._uid)[0];
|
||||
$('button[title=Remove]', li).click(function () {
|
||||
$('#results').hide();
|
||||
$('#results').addClass('hide');
|
||||
var hadValue = !!$('input', li)[0].value;
|
||||
$(li).remove();
|
||||
});
|
||||
$('button[title$=Earlier]', li).click(function () {
|
||||
$(li).prev('li').find('input').each(function () {
|
||||
$('#results').hide();
|
||||
$('#results').addClass('hide');
|
||||
// this = previous li input
|
||||
var tmp = this.value;
|
||||
this.value = $('input', li).val();
|
||||
@@ -52,7 +52,7 @@ var MUB = {
|
||||
});
|
||||
$('button[title$=Later]', li).click(function () {
|
||||
$(li).next('li').find('input').each(function () {
|
||||
$('#results').hide();
|
||||
$('#results').addClass('hide');
|
||||
// this = next li input
|
||||
var tmp = this.value;
|
||||
this.value = $('input', li).val();
|
||||
@@ -77,9 +77,9 @@ var MUB = {
|
||||
url : url,
|
||||
complete : function (xhr, stat) {
|
||||
if ('success' === stat)
|
||||
$('span', li).html('✓');
|
||||
$('span', li).html('<a href="#" class="btn btn-success btn-sm disabled">✓</a>');
|
||||
else {
|
||||
$('span', li).html('<button><b>404! </b> recheck</button>')
|
||||
$('span', li).html('<button class="btn btn-warning btn-sm"><b>404! </b> recheck</button>')
|
||||
.find('button').click(function () {
|
||||
MUB.liUpdateTestLink.call(li);
|
||||
});
|
||||
@@ -184,16 +184,16 @@ var MUB = {
|
||||
markup = '<link type="text/css" rel="stylesheet" href="' + uriH + '" />';
|
||||
}
|
||||
$('#uriHtml').val(markup);
|
||||
$('#results').show();
|
||||
$('#results').removeClass('hide');
|
||||
},
|
||||
/**
|
||||
* Handler for the "Add file +" button
|
||||
*/
|
||||
addButtonClick : function () {
|
||||
$('#results').hide();
|
||||
$('#results').addClass('hide');
|
||||
MUB.addLi();
|
||||
MUB.updateAllTestLinks();
|
||||
$('#update').show().click(MUB.update);
|
||||
$('#update').removeClass('hide').click(MUB.update);
|
||||
$('#sources li:last input')[0].focus();
|
||||
},
|
||||
/**
|
||||
@@ -201,7 +201,7 @@ var MUB = {
|
||||
*/
|
||||
init : function () {
|
||||
$('#jsDidntLoad').remove();
|
||||
$('#app').show();
|
||||
$('#app').removeClass('hide');
|
||||
$('#sources').html('');
|
||||
$('#add button').click(MUB.addButtonClick);
|
||||
// make easier to copy text out of
|
||||
@@ -213,7 +213,7 @@ var MUB = {
|
||||
$('a.ext').attr({target:'_blank'});
|
||||
if (location.hash) {
|
||||
// make links out of URIs from bookmarklet
|
||||
$('#getBm').hide();
|
||||
$('#getBm').addClass('hide');
|
||||
var i = 0, found = location.hash.substr(1).split(','), l = found.length;
|
||||
$('#bmUris').html('<p><strong>Found by bookmarklet:</strong> /</p>');
|
||||
var $p = $('#bmUris p');
|
||||
@@ -227,7 +227,7 @@ var MUB = {
|
||||
MUB.addButtonClick();
|
||||
$('#sources li:last input').val(this.innerHTML);
|
||||
MUB.liUpdateTestLink.call($('#sources li:last')[0]);
|
||||
$('#results').hide();
|
||||
$('#results').addClass('hide');
|
||||
return false;
|
||||
}).attr({title:'Add file +'});
|
||||
} else {
|
@@ -5,9 +5,11 @@ javascript:(function() {
|
||||
,o
|
||||
,home = (location + '').split('/').splice(0, 3).join('/') + '/';
|
||||
function add(uri) {
|
||||
return (0 === uri.indexOf(home))
|
||||
&& (!/[\?&]/.test(uri))
|
||||
&& uris.push(escape(uri.substr(home.length)));
|
||||
if (0 !== uri.indexOf(home)) {
|
||||
return;
|
||||
}
|
||||
uri = uri.replace(/\?.*/, '');
|
||||
uris.push(escape(uri.substr(home.length)));
|
||||
};
|
||||
function sheet(ss) {
|
||||
// we must check the domain with add() before accessing ss.cssRules
|
@@ -1,47 +1,37 @@
|
||||
<?php
|
||||
<?php
|
||||
|
||||
if (phpversion() < 5) {
|
||||
exit('Minify requires PHP5 or greater.');
|
||||
}
|
||||
$app = (require __DIR__ . '/../bootstrap.php');
|
||||
/* @var \Minify\App $app */
|
||||
|
||||
// check for auto-encoding
|
||||
$encodeOutput = (function_exists('gzdeflate')
|
||||
&& !ini_get('zlib.output_compression'));
|
||||
$config = $app->config;
|
||||
|
||||
// recommend $min_symlinks setting for Apache UserDir
|
||||
$symlinkOption = '';
|
||||
if (0 === strpos($_SERVER["SERVER_SOFTWARE"], 'Apache/')
|
||||
&& preg_match('@^/\\~(\\w+)/@', $_SERVER['REQUEST_URI'], $m)
|
||||
if (0 === strpos($app->env->server("SERVER_SOFTWARE"), 'Apache/')
|
||||
&& preg_match('@^/\\~(\\w+)/@', $app->env->server('REQUEST_URI'), $m)
|
||||
) {
|
||||
$userDir = DIRECTORY_SEPARATOR . $m[1] . DIRECTORY_SEPARATOR;
|
||||
if (false !== strpos(__FILE__, $userDir)) {
|
||||
$sm = array();
|
||||
$sm["//~{$m[1]}"] = dirname(dirname(__FILE__));
|
||||
$sm["//~{$m[1]}"] = dirname(__DIR__);
|
||||
$array = str_replace('array (', 'array(', var_export($sm, 1));
|
||||
$symlinkOption = "\$min_symlinks = $array;";
|
||||
}
|
||||
}
|
||||
|
||||
require dirname(__FILE__) . '/../config.php';
|
||||
|
||||
require "$min_libPath/Minify/Loader.php";
|
||||
Minify_Loader::register();
|
||||
|
||||
if (! $min_enableBuilder) {
|
||||
if (!$config->enableBuilder) {
|
||||
header('Content-Type: text/plain');
|
||||
die('This application is not enabled. See http://code.google.com/p/minify/wiki/BuilderApp');
|
||||
die('This application is not enabled. See https://github.com/mrclay/minify/blob/master/docs/BuilderApp.wiki.md');
|
||||
}
|
||||
|
||||
if (isset($min_builderPassword)
|
||||
&& is_string($min_builderPassword)
|
||||
&& $min_builderPassword !== '') {
|
||||
DooDigestAuth::http_auth('Minify Builder', array('admin' => $min_builderPassword));
|
||||
}
|
||||
|
||||
$cachePathCode = '';
|
||||
if (! isset($min_cachePath) && ! function_exists('sys_get_temp_dir')) {
|
||||
$detectedTmp = Minify_Cache_File::tmp();
|
||||
$cachePathCode = "\$min_cachePath = " . var_export($detectedTmp, 1) . ';';
|
||||
if ($config->builderPassword && $config->builderPassword !== '') {
|
||||
$auth = new Intervention\Httpauth\Httpauth(array(
|
||||
'username' => 'admin',
|
||||
'password' => $config->builderPassword,
|
||||
'type' => 'digest',
|
||||
'realm' => 'Minify Builder',
|
||||
));
|
||||
$auth->secure();
|
||||
}
|
||||
|
||||
ob_start();
|
||||
@@ -49,6 +39,7 @@ ob_start();
|
||||
<!DOCTYPE html>
|
||||
<title>Minify URI Builder</title>
|
||||
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
|
||||
<style>
|
||||
body {margin:1em 60px;}
|
||||
h1, h2, h3 {margin-left:-25px; position:relative;}
|
||||
@@ -79,18 +70,11 @@ b {color:#c00}
|
||||
|
||||
<p class=topWarning id=jsDidntLoad><strong>Uh Oh.</strong> Minify was unable to
|
||||
serve Javascript for this app. To troubleshoot this,
|
||||
<a href="http://code.google.com/p/minify/wiki/Debugging">enable FirePHP debugging</a>
|
||||
<a href="https://github.com/mrclay/minify/blob/master/docs/Debugging.wiki.md">enable FirePHP debugging</a>
|
||||
and request the <a id=builderScriptSrc href=#>Minify URL</a> directly. Hopefully the
|
||||
FirePHP console will report the cause of the error.
|
||||
</p>
|
||||
|
||||
<?php if ($cachePathCode): ?>
|
||||
<p class=topNote><strong>Note:</strong> <code><?php echo
|
||||
htmlspecialchars($detectedTmp); ?></code> was discovered as a usable temp directory.<br>To
|
||||
slightly improve performance you can hardcode this in /min/config.php:
|
||||
<code><?php echo htmlspecialchars($cachePathCode); ?></code></p>
|
||||
<?php endIf; ?>
|
||||
|
||||
<p id=minRewriteFailed class="hide"><strong>Note:</strong> Your webserver does not seem to
|
||||
support mod_rewrite (used in /min/.htaccess). Your Minify URIs will contain "?", which
|
||||
<a href="http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/"
|
||||
@@ -111,7 +95,7 @@ and click [Update].</p>
|
||||
|
||||
<div id=bmUris></div>
|
||||
|
||||
<p><button id=update class=hide>Update</button></p>
|
||||
<p><button class="btn btn-primary hide" id=update>Update</button></p>
|
||||
|
||||
<div id=results class=hide>
|
||||
|
||||
@@ -164,13 +148,13 @@ by Minify. E.g. <code>@import "<span class=minRoot>/min/?</span>g=css2";</code><
|
||||
</div><!-- #app -->
|
||||
|
||||
<hr>
|
||||
<p>Need help? Check the <a href="http://code.google.com/p/minify/w/list?can=3">wiki</a>,
|
||||
<p>Need help? Check the <a href="https://github.com/mrclay/minify/tree/master/docs">wiki</a>,
|
||||
or post to the <a class=ext href="http://groups.google.com/group/minify">discussion
|
||||
list</a>.</p>
|
||||
<p><small>Powered by Minify <?php echo Minify::VERSION; ?></small></p>
|
||||
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js"></script>
|
||||
<script>window.jQuery || document.write('<script src="jquery-1.6.3.min.js"><\/script>')</script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
|
||||
<script>window.jQuery || document.write('<script src="jquery-1.12.4.min.js"><\/script>')</script>
|
||||
<script>
|
||||
(function () {
|
||||
// workaround required to test when /min isn't child of web root
|
||||
@@ -192,7 +176,11 @@ by Minify. E.g. <code>@import "<span class=minRoot>/min/?</span>g=css2";</code><
|
||||
var url = 'ocCheck.php?' + (new Date()).getTime();
|
||||
$.get(url, function (ocStatus) {
|
||||
$.get(url + '&hello=1', function (ocHello) {
|
||||
if (ocHello != 'World!') {
|
||||
var expected = [];
|
||||
for (var i = 0; i < 500; i++) {
|
||||
expected.push('0123456789');
|
||||
}
|
||||
if (ocHello != expected.join('')) {
|
||||
msg += 'It appears output is being automatically compressed, interfering '
|
||||
+ ' with Minify\'s own compression. ';
|
||||
if (ocStatus == '1')
|
||||
@@ -219,22 +207,15 @@ by Minify. E.g. <code>@import "<span class=minRoot>/min/?</span>g=css2";</code><
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
|
||||
// setup Minify
|
||||
Minify::setCache(
|
||||
isset($min_cachePath) ? $min_cachePath : ''
|
||||
,$min_cacheFileLocking
|
||||
);
|
||||
Minify::$uploaderHoursBehind = $min_uploaderHoursBehind;
|
||||
|
||||
Minify::serve('Page', array(
|
||||
'content' => $content
|
||||
,'id' => __FILE__
|
||||
,'lastModifiedTime' => max(
|
||||
$controller = new Minify_Controller_Page($app->env, $app->sourceFactory);
|
||||
$minify = $app->minify->serve($controller, array(
|
||||
'content' => $content,
|
||||
'id' => __FILE__,
|
||||
'lastModifiedTime' => max(
|
||||
// regenerate cache if any of these change
|
||||
filemtime(__FILE__)
|
||||
,filemtime(dirname(__FILE__) . '/../config.php')
|
||||
,filemtime(dirname(__FILE__) . '/../lib/Minify.php')
|
||||
)
|
||||
,'minifyAll' => true
|
||||
,'encodeOutput' => $encodeOutput
|
||||
filemtime(__FILE__),
|
||||
filemtime(__DIR__ . '/../config.php'),
|
||||
filemtime(__DIR__ . '/../lib/Minify.php')
|
||||
),
|
||||
'minifyAll' => true,
|
||||
));
|
5
builder/jquery-1.12.4.min.js
vendored
Normal file
5
builder/jquery-1.12.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,34 +1,34 @@
|
||||
<?php
|
||||
<?php
|
||||
/**
|
||||
* AJAX checks for zlib.output_compression
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
$app = (require __DIR__ . '/../bootstrap.php');
|
||||
/* @var \Minify\App $app */
|
||||
|
||||
$_oc = ini_get('zlib.output_compression');
|
||||
|
||||
// allow access only if builder is enabled
|
||||
require dirname(__FILE__) . '/../config.php';
|
||||
if (! $min_enableBuilder) {
|
||||
if (!$app->config->enableBuilder) {
|
||||
header('Location: /');
|
||||
exit();
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_GET['hello'])) {
|
||||
if ($app->env->get('hello')) {
|
||||
// echo 'World!'
|
||||
|
||||
// try to prevent double encoding (may not have an effect)
|
||||
ini_set('zlib.output_compression', '0');
|
||||
|
||||
require $min_libPath . '/HTTP/Encoder.php';
|
||||
HTTP_Encoder::$encodeToIe6 = true; // just in case
|
||||
$he = new HTTP_Encoder(array(
|
||||
'content' => 'World!'
|
||||
,'method' => 'deflate'
|
||||
'content' => str_repeat('0123456789', 500),
|
||||
'method' => 'deflate',
|
||||
));
|
||||
$he->encode();
|
||||
$he->sendAll();
|
||||
|
||||
$he->encode();
|
||||
$he->sendAll();
|
||||
} else {
|
||||
// echo status "0" or "1"
|
||||
header('Content-Type: text/plain');
|
@@ -1,14 +1,14 @@
|
||||
<?php
|
||||
exit();
|
||||
exit;
|
||||
/* currently unused.
|
||||
|
||||
// capture PHP's default setting (may get overridden in config
|
||||
$_oc = ini_get('zlib.output_compression');
|
||||
|
||||
// allow access only if builder is enabled
|
||||
require dirname(__FILE__) . '/../config.php';
|
||||
require __DIR__ . '/../config.php';
|
||||
if (! $min_enableBuilder) {
|
||||
exit();
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_GET['oc'])) {
|
||||
@@ -37,7 +37,7 @@ if (isset($_GET['oc'])) {
|
||||
echo "<p class=topNote><strong>\$_SERVER['SUBDOMAIN_DOCUMENT_ROOT'] is set.</strong> "
|
||||
. "You may need to set \$min_documentRoot to this in config.php</p>";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//*/
|
||||
//*/
|
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "mrclay/minify",
|
||||
"type": "library",
|
||||
"description": "Minify is a PHP5 app that helps you follow several rules for client-side performance. It combines multiple CSS or Javascript files, removes unnecessary whitespace and comments, and serves them with gzip encoding and optimal client-side cache headers",
|
||||
"homepage": "http://code.google.com/p/minify/",
|
||||
"description": "Minify is a PHP app that helps you follow several rules for client-side performance. It combines multiple CSS or Javascript files, removes unnecessary whitespace and comments, and serves them with gzip encoding and optimal client-side cache headers",
|
||||
"homepage": "https://github.com/mrclay/minify",
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
@@ -13,14 +13,52 @@
|
||||
],
|
||||
"support": {
|
||||
"email": "minify@googlegroups.com",
|
||||
"issues": "http://code.google.com/p/minify/issues/list",
|
||||
"wiki": "http://code.google.com/p/minify/w/list"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.1",
|
||||
"ext-pcre": "*"
|
||||
"issues": "https://github.com/mrclay/minify/issues",
|
||||
"wiki": "https://github.com/mrclay/minify/blob/master/docs"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["min/lib/"]
|
||||
"classmap": [
|
||||
"lib/"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Minify\\Test\\": "tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "8.1.0"
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"ext-pcre": "*",
|
||||
"intervention/httpauth": "^2.0|^3.0",
|
||||
"marcusschwarz/lesserphp": "^0.5.5",
|
||||
"monolog/monolog": "~1.1|~2.0|~3.0",
|
||||
"mrclay/jsmin-php": "~2",
|
||||
"mrclay/props-dic": "^4",
|
||||
"tubalmartin/cssmin": "~4"
|
||||
},
|
||||
"require-dev": {
|
||||
"firephp/firephp-core": "~0.4.0",
|
||||
"meenie/javascript-packer": "~1.1",
|
||||
"phpunit/phpunit": "^8",
|
||||
"scssphp/scssphp": "^1.12",
|
||||
"tedivm/jshrink": "~1.1.0"
|
||||
},
|
||||
"suggest": {
|
||||
"firephp/firephp-core": "Use FirePHP for Log messages",
|
||||
"meenie/javascript-packer": "Keep track of the Packer PHP port using Composer"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@
|
||||
*
|
||||
* To test config options, place them in this file and add "&test" to your Minify URL.
|
||||
* Note that if this is on a public server, anyone can execute your test.
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
|
@@ -2,16 +2,29 @@
|
||||
/**
|
||||
* Configuration for "min", the default application built with the Minify
|
||||
* library
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Enable the static serving feature
|
||||
*/
|
||||
$min_enableStatic = false;
|
||||
|
||||
|
||||
/**
|
||||
* Allow use of the Minify URI Builder app. Only set this to true while you need it.
|
||||
*/
|
||||
$min_enableBuilder = false;
|
||||
|
||||
|
||||
/**
|
||||
* Concatenate but do not minify the files. This can be used for testing.
|
||||
*/
|
||||
$min_concatOnly = false;
|
||||
|
||||
|
||||
/**
|
||||
* If non-empty, the Builder will be protected with HTTP Digest auth.
|
||||
* The username is "admin".
|
||||
@@ -20,12 +33,8 @@ $min_builderPassword = 'admin';
|
||||
|
||||
|
||||
/**
|
||||
* Set to true to log messages to FirePHP (Firefox Firebug addon).
|
||||
* Set to true to log messages to FirePHP (Firefox Firebug addon) and PHP's error_log
|
||||
* Set to false for no error logging (Minify may be slightly faster).
|
||||
* @link http://www.firephp.org/
|
||||
*
|
||||
* If you want to use a custom error logger, set this to your logger
|
||||
* instance. Your object should have a method log(string $message).
|
||||
*/
|
||||
$min_errorLogger = false;
|
||||
|
||||
@@ -38,7 +47,7 @@ $min_errorLogger = false;
|
||||
* /min/f=file1.js send the cookie minDebug=file1.js
|
||||
* You can manually enable debugging by appending "&debug" to a URI.
|
||||
* E.g. /min/?f=script1.js,script2.js&debug
|
||||
*
|
||||
*
|
||||
* In 'debug' mode, Minify combines files with no minification and adds comments
|
||||
* to indicate line #s of the original files.
|
||||
*/
|
||||
@@ -52,31 +61,33 @@ $min_allowDebugFlag = false;
|
||||
//$min_cachePath = 'c:\\WINDOWS\\Temp';
|
||||
//$min_cachePath = '/tmp';
|
||||
//$min_cachePath = preg_replace('/^\\d+;/', '', session_save_path());
|
||||
|
||||
|
||||
/**
|
||||
* To use APC/Memcache/ZendPlatform for cache storage, require the class and
|
||||
* set $min_cachePath to an instance. Example below:
|
||||
*/
|
||||
//require dirname(__FILE__) . '/lib/Minify/Cache/APC.php';
|
||||
//$min_cachePath = new Minify_Cache_APC();
|
||||
|
||||
|
||||
/**
|
||||
* Leave an empty string to use PHP's $_SERVER['DOCUMENT_ROOT'].
|
||||
*
|
||||
* On some servers, this value may be misconfigured or missing. If so, set this
|
||||
* On some servers, this value may be misconfigured or missing. If so, set this
|
||||
* to your full document root path with no trailing slash.
|
||||
* E.g. '/home/accountname/public_html' or 'c:\\xampp\\htdocs'
|
||||
*
|
||||
* If /min/ is directly inside your document root, just uncomment the
|
||||
* If /min/ is directly inside your document root, just uncomment the
|
||||
* second line. The third line might work on some Apache servers.
|
||||
*/
|
||||
$min_documentRoot = '';
|
||||
//$min_documentRoot = dirname(dirname(__DIR__));
|
||||
//$min_documentRoot = substr(__FILE__, 0, -15);
|
||||
//$min_documentRoot = $_SERVER['SUBDOMAIN_DOCUMENT_ROOT'];
|
||||
|
||||
|
||||
/**
|
||||
* Cache file locking. Set to false if filesystem is NFS. On at least one
|
||||
* Cache file locking. Set to false if filesystem is NFS. On at least one
|
||||
* NFS system flock-ing attempts stalled PHP for 30 seconds!
|
||||
*/
|
||||
$min_cacheFileLocking = true;
|
||||
@@ -85,9 +96,9 @@ $min_cacheFileLocking = true;
|
||||
/**
|
||||
* Combining multiple CSS files can place @import declarations after rules, which
|
||||
* is invalid. Minify will attempt to detect when this happens and place a
|
||||
* warning comment at the top of the CSS output. To resolve this you can either
|
||||
* move the @imports within your CSS files, or enable this option, which will
|
||||
* move all @imports to the top of the output. Note that moving @imports could
|
||||
* warning comment at the top of the CSS output. To resolve this you can either
|
||||
* move the @imports within your CSS files, or enable this option, which will
|
||||
* move all @imports to the top of the output. Note that moving @imports could
|
||||
* affect CSS values (which is why this option is disabled by default).
|
||||
*/
|
||||
$min_serveOptions['bubbleCssImports'] = false;
|
||||
@@ -106,9 +117,9 @@ $min_serveOptions['maxAge'] = 1800;
|
||||
|
||||
|
||||
/**
|
||||
* To use CSSmin (Túbal Martín's port of the YUI CSS compressor), uncomment the following line:
|
||||
* To use the CSS compressor that shipped with 2.x, uncomment the following line:
|
||||
*/
|
||||
//$min_serveOptions['minifiers']['text/css'] = array('Minify_CSSmin', 'minify');
|
||||
//$min_serveOptions['minifiers'][Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
|
||||
|
||||
|
||||
/**
|
||||
@@ -123,8 +134,8 @@ $min_serveOptions['maxAge'] = 1800;
|
||||
* particular directories below DOCUMENT_ROOT, set this here.
|
||||
* You will still need to include the directory in the
|
||||
* f or b GET parameters.
|
||||
*
|
||||
* // = shortcut for DOCUMENT_ROOT
|
||||
*
|
||||
* // = shortcut for DOCUMENT_ROOT
|
||||
*/
|
||||
//$min_serveOptions['minApp']['allowDirs'] = array('//js', '//css');
|
||||
|
||||
@@ -138,7 +149,7 @@ $min_serveOptions['minApp']['groupsOnly'] = false;
|
||||
/**
|
||||
* By default, Minify will not minify files with names containing .min or -min
|
||||
* before the extension. E.g. myFile.min.js will not be processed by JSMin
|
||||
*
|
||||
*
|
||||
* To minify all files, set this option to null. You could also specify your
|
||||
* own pattern that is matched against the filename.
|
||||
*/
|
||||
@@ -149,8 +160,8 @@ $min_serveOptions['minApp']['groupsOnly'] = false;
|
||||
* If you minify CSS files stored in symlink-ed directories, the URI rewriting
|
||||
* algorithm can fail. To prevent this, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root.
|
||||
*
|
||||
* Because paths need to be normalized for this to work, use "//" to substitute
|
||||
*
|
||||
* Because paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
@@ -162,17 +173,17 @@ $min_symlinks = array();
|
||||
|
||||
/**
|
||||
* If you upload files from Windows to a non-Windows server, Windows may report
|
||||
* incorrect mtimes for the files. This may cause Minify to keep serving stale
|
||||
* incorrect mtimes for the files. This may cause Minify to keep serving stale
|
||||
* cache files when source file changes are made too frequently (e.g. more than
|
||||
* once an hour).
|
||||
*
|
||||
* Immediately after modifying and uploading a file, use the touch command to
|
||||
*
|
||||
* Immediately after modifying and uploading a file, use the touch command to
|
||||
* update the mtime on the server. If the mtime jumps ahead by a number of hours,
|
||||
* set this variable to that number. If the mtime moves back, this should not be
|
||||
* set this variable to that number. If the mtime moves back, this should not be
|
||||
* needed.
|
||||
*
|
||||
* In the Windows SFTP client WinSCP, there's an option that may fix this
|
||||
* issue without changing the variable below. Under login > environment,
|
||||
* In the Windows SFTP client WinSCP, there's an option that may fix this
|
||||
* issue without changing the variable below. Under login > environment,
|
||||
* select the option "Adjust remote timestamp with DST".
|
||||
* @link http://winscp.net/eng/docs/ui_login_environment#daylight_saving_time
|
||||
*/
|
||||
@@ -180,11 +191,10 @@ $min_uploaderHoursBehind = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Path to Minify's lib folder. If you happen to move it, change
|
||||
* this accordingly.
|
||||
* Advanced: you can replace some of the PHP classes Minify uses to serve requests.
|
||||
* To do this, assign a callable to one of the elements of the $min_factories array.
|
||||
*
|
||||
* You can see the default implementations (and what gets passed in) in index.php.
|
||||
*/
|
||||
$min_libPath = dirname(__FILE__) . '/lib';
|
||||
|
||||
|
||||
// try to disable output_compression (may not have an effect)
|
||||
ini_set('zlib.output_compression', '0');
|
||||
//$min_factories['minify'] = ... a callable accepting a Minify\App object
|
||||
//$min_factories['controller'] = ... a callable accepting a Minify\App object
|
8
docker-compose.yml
Normal file
8
docker-compose.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
apache:
|
||||
image: php:8.3-apache
|
||||
ports:
|
||||
- 8080:80
|
||||
volumes:
|
||||
- .:/var/www/html/min
|
30
docs/AlternateFileLayouts.wiki.md
Normal file
30
docs/AlternateFileLayouts.wiki.md
Normal file
@@ -0,0 +1,30 @@
|
||||
If you test sites in a subdirectory (e.g. `http://localhost/testSite/`) rather than a virtualhost, then you'll need to adjust the way you use Minify to rewrite CSS correctly.
|
||||
|
||||
1. Place the following in `config.php`:
|
||||
|
||||
```php
|
||||
// Set the document root to be the path of the "site root"
|
||||
$min_documentRoot = substr(__FILE__, 0, -11);
|
||||
|
||||
// Set $sitePrefix to the path of the site from the webserver's real docroot
|
||||
list($sitePrefix) = explode('/index.php', $_SERVER['SCRIPT_NAME'], 2);
|
||||
|
||||
// Prepend $sitePrefix to the rewritten URIs in CSS files
|
||||
$min_symlinks['//' . ltrim($sitePrefix, '/')] = $min_documentRoot;
|
||||
```
|
||||
|
||||
2. In the HTML, make your Minify URIs document-relative (e.g. `min/f=js/file.js` and `../min/f=js/file.js`), not root-relative.
|
||||
|
||||
Now the `min` application should operate correctly from a subdirectory and will serve files relative to your "site" root rather than the document root. E.g.
|
||||
|
||||
| **environment** | production | testing |
|
||||
|:----------------|:-----------|:--------|
|
||||
| **server document root** | `/home/mysite_com/www` | `/var/www` |
|
||||
| **`$min_documentRoot` ("site root")** | `/home/mysite_com/www` | `/var/www/testSite` |
|
||||
| **`$sitePrefix`** | (empty) | `/testSite` |
|
||||
| **Minify URL** | `http://mysite.com/min/f=js/file1.js` | `http://localhost/testSite/min/f=js/file1.js` |
|
||||
| **file served** | `/home/mysite_com/www/js/file1.js` | `/var/www/testSite/js/file1.js` |
|
||||
|
||||
Caveats:
|
||||
* This configuration may break the Builder application (located at `/min/builder/`) used to create Minify URIs, but you can still create them by hand.
|
||||
* Make sure you don't reset `$min_symlinks` to a different value lower in your config file.
|
9
docs/BuilderApp.wiki.md
Normal file
9
docs/BuilderApp.wiki.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Minify ships with "Builder", a simple Javascript app for constructing URIs to use with Minify. ([screenshots of the 2.1.0 version](http://www.mrclay.org/index.php/2008/09/19/minify-21-on-mrclayorg/))
|
||||
|
||||
It also does some run-time checks of your PHP and Minify configuration to look for problematic settings like [auto-encoding](CommonProblems.wiki.md#output-is-distortedrandom-chars).
|
||||
|
||||
After installation, this is found at **`http://example.com/min/builder/`**
|
||||
|
||||
You must enable it by editing `config.php` and setting `$min_enableBuilder = true;`
|
||||
|
||||
After use, you should disable it by resetting `$min_enableBuilder = false;`
|
132
docs/CommonProblems.wiki.md
Normal file
132
docs/CommonProblems.wiki.md
Normal file
@@ -0,0 +1,132 @@
|
||||
If this page doesn't help, please post a question on our [Google group](http://groups.google.com/group/minify).
|
||||
|
||||
## URIs are re-written incorrectly in CSS output
|
||||
|
||||
See [UriRewriting](UriRewriting.wiki.md).
|
||||
|
||||
## Builder Fails / 400 Errors
|
||||
|
||||
This is usually due to an unusual server setup. You can see the cause of 400 responses using FirePHP (See [Debugging](Debugging.wiki.md)).
|
||||
|
||||
## Long URL parameters are ignored
|
||||
|
||||
Some server setups will refuse to populate very long `$_GET` params. Use [groups](UserGuide.wiki.md#using-groups-for-nicer-urls) to shorten the URLs.
|
||||
|
||||
## PHP/Apache crashes
|
||||
|
||||
[PCRE (which provides regular expressions) commonly crashes PHP](https://www.google.com/search?q=pcre+php+crash) and this is nearly impossible to solve in PHP code. Things to try:
|
||||
|
||||
* Raise Apache's [ThreadStackSize](http://stackoverflow.com/a/7597506/3779)
|
||||
* In [php.ini](http://php.net/manual/en/pcre.configuration.php) raise `pcre.backtrack_limit` and `pcre.recursion_limit` to 1000000. These will allow processing longer strings, but also require a larger stack size.
|
||||
* Use YUICompressor instead of PHP-based CSS compressors
|
||||
|
||||
## Dealing with Javascript errors
|
||||
|
||||
Short answer: **use Minify 2.1.4+, use a pre-compressed version of your file, and rename it `*.min.js` or `*-min.js`**. By default Minify won't try to minify these files (but will still gzip them). The [Compressor Rater](http://compressorrater.thruhere.net/) is handy for compressing files individually.
|
||||
|
||||
If the error is in your code, enable [debug mode](Debugging.wiki.md) while debugging your code in Firebug or your favorite browser's Javascript debugger. This will insert comments to allow you to keep track of the individual source locations in the combined file.
|
||||
|
||||
If you have Java on your web host, you can use the [wrapper for YUI Compressor](../lib/Minify/YUICompressor.php) instead of JSMin. [This thread](http://groups.google.com/group/minify/browse_thread/thread/f12f25f27e1256fe) shows how a user has done this.
|
||||
|
||||
## Javascript isn't being minified
|
||||
|
||||
If the filename ends with **`-min.js`** or **`.min.js`**, Minify will assume the file is already compressed and just combine it with any other files.
|
||||
|
||||
### Scriptaculous
|
||||
|
||||
Scriptaculous 1.8.2 (and probably all 1.x) has an [autoloader script](http://github.com/madrobby/scriptaculous/blob/4b49fd8884920d4ee760b0194431f4f433f878df/src/scriptaculous.js#L54) that requires files to be in a particular place on disk. To serve Scriptaculous modules with Minify, just serve `prototype.js` and the individual support files (e.g. `dragdrop.js`, `effects.js`) and the library should work fine. E.g.:
|
||||
|
||||
```
|
||||
<script src="/min/f=scriptaculous/lib/prototype.js" type="text/javascript"></script>
|
||||
<script src="/min/b=scriptaculous/src&f=effects.js,dragdrop.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
/* DragDrop and Effects modules can be used here. */
|
||||
</script>
|
||||
```
|
||||
|
||||
## Server cache files won't update
|
||||
|
||||
If you upload files using [Coda or Transmit](http://groups.google.com/group/coda-users/browse_thread/thread/572d2dc315ec02e7/) or from a Windows PC to a non-Windows server, your new files may end up with the wrong `mtime` (timestamp) on the server, confusing the cache system.
|
||||
|
||||
Setting the [$min\_uploaderHoursBehind option](../config.php#L171) in `config.php` can compensate for this.
|
||||
|
||||
WinSCP has a [Daylight Saving Time option](http://winscp.net/eng/docs/ui_login_environment#daylight_saving_time) that can prevent this issue.
|
||||
|
||||
This can also occur if your files are changed, and the `mtime` is set in the past (e.g. via a `git checkout` operation). If so you'll have to `touch` the changed files or use some other method to make the `mtime` current.
|
||||
|
||||
## Can't see changes in browser
|
||||
|
||||
Generally changes aren't seen because a) the browser is refusing to send a new request, or b) the server doesn't recognize that your source files have been modified after the server cache was created.
|
||||
|
||||
First, place the Minify URL directly in the address bar and refresh.
|
||||
|
||||
If a change is not seen, verify that the server cache file is being updated.
|
||||
|
||||
## Disable Caching
|
||||
|
||||
If you'd like to temporarily disable the cache without using [debug mode](Debugging.wiki.md), place these settings at the end of `config.php`:
|
||||
```php
|
||||
// disable server caching
|
||||
$min_cachePath = null;
|
||||
// prevent client caching
|
||||
$min_serveOptions['maxAge'] = 0;
|
||||
$min_serveOptions['lastModifiedTime'] = $_SERVER['REQUEST_TIME'];
|
||||
```
|
||||
**Don't do this on a production server!** Minify will have to combine, minify, and gzencode on every request.
|
||||
|
||||
## Character Encodings
|
||||
|
||||
_Please_ use UTF-8. The string processing may work on encodings like Windows-1251 but will certainly fail on radically different ones like UTF-16.
|
||||
|
||||
If you consistently use a different encoding, in `config.php` set `$min_serveOptions['contentTypeCharset']` to this encoding to send it in the Content-Type header.
|
||||
|
||||
Otherwise, set it to `false` to remove it altogether. You can still, in CSS, use the [@charset](http://www.w3.org/TR/CSS2/syndata.html#x50) directive to tell the browser the encoding, but (a) it must appear first and (b) shouldn't appear later in the output (and Minify won't enforce this).
|
||||
|
||||
Moral? To minimize problems, use UTF-8 and remove any `@charset` directives from your CSS.
|
||||
|
||||
## @imports can appear in invalid locations in combined CSS files
|
||||
|
||||
If you combine CSS files, @import declarations can appear after CSS rules, invalidating the stylesheet. As of version 2.1.2, if Minify detects this, it will prepend a warning to the output CSS file. To resolve this, you can either move your @import statements within your files, or enable the option 'bubbleCssImports'.
|
||||
|
||||
## Debug mode can cause a Javascript error
|
||||
|
||||
This issue was resolved in version 2.1.2.
|
||||
|
||||
Debug mode adds line numbers in comments. Unfortunately, in versions <= 2.1.1, if the source file had a string or regex containing (what looks like) a C-style comment token, the algorithm was confused and the injected comments caused a syntax error.
|
||||
|
||||
## Minification can cause a Javascript error
|
||||
|
||||
This issue was resolved in version 2.1.2.
|
||||
|
||||
In rare instances the [JSMin](https://github.com/mrclay/jsmin-php/blob/master/src/JSMin/JSMin.php) algorithm in versions <= 2.1.1 could be confused by regexes in certain contexts and throw an exception. The workaround was to simply wrap the expression in parenthesis. E.g.
|
||||
```js
|
||||
// in 2.1.1 and previous
|
||||
return /'/; // JSMin throws error
|
||||
return (/'/); // no error
|
||||
```
|
||||
|
||||
## Output is distorted/random chars
|
||||
|
||||
What you're seeing is a mismatch between the content encoding the browser expects and what it receives.
|
||||
|
||||
The usual problem is that a global PHP or web server configuration is causing the output of PHP scripts to be automatically gzipped. Since Minify already outputs gzipped content, the browser receives "double encoded" content which it interprets as noise. The Builder app in 2.1.4 sometimes can tell you which component is causing the auto-encoding.
|
||||
|
||||
## Can't specify more than 10 files via URL
|
||||
|
||||
Use Minify 2.1.4+. Before there was a setting to adjust the maximum allowed.
|
||||
|
||||
## Directory Listing Denied
|
||||
|
||||
This may also appear as "Virtual Directory does not allow contents to be listed". Minify requires that the URI `/min/` (a request for a directory listing) result in the execution of `/min/index.php`. On Apache, you would make sure `index.php` is listed in the [DirectoryIndex directive](http://httpd.apache.org/docs/2.0/mod/mod_dir.html#directoryindex). IIS calls this the [Default Document](http://www.iis.net/ConfigReference/system.webServer/defaultDocument).
|
||||
|
||||
## "WARN: environment : Local HTTP request failed. Testing cannot continue."
|
||||
|
||||
The `test_environment.php` unit test makes a few local HTTP requests to sniff for `zlib.output_compression` and other auto-encoding behavior, which may break Minify's output. This warning will appear if `allow_url_fopen` is disabled in php.ini, but **does not** necessarily mean there is a problem.
|
||||
|
||||
If Minify seems to work fine, ignore the warning. If Minify produces garbled output, enable `allow_url_fopen` in php.ini and re-run the tests. The tests may be able to tell you if PHP or your server is automatically encoding output.
|
||||
|
||||
Unless you need it in other scripts, disable `allow_url_fopen` once the issue is resolved. Minify does not need it.
|
||||
|
||||
## See Also
|
||||
|
||||
* [Debugging](Debugging.wiki.md)
|
16
docs/ComponentClasses.wiki.md
Normal file
16
docs/ComponentClasses.wiki.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# PHP5 Component Classes
|
||||
|
||||
(This information is not updated for version 3)
|
||||
|
||||
| **Class** | **Functionality** |
|
||||
|:----------|:------------------|
|
||||
| [Minify](http://code.google.com/p/minify/source/browse/min/lib/Minify.php) | Combine, process, and serve pieces of content (usually CSS/JS files). Almost all behavior is configurable including request handling (via [controllers](http://code.google.com/p/minify/source/browse/min/lib/Minify/Controller/)), HTTP headers & encoding, caching ([file/APC/memcached](http://code.google.com/p/minify/source/browse/min/lib/Minify/Cache/)), and compression functions (by default, the classes below) |
|
||||
| [JSMin](http://code.google.com/p/minify/source/browse/min/lib/JSMin.php) | A port of [jsmin.c](http://www.crockford.com/javascript/jsmin.html), extended to preserve some multi-line comments and IE conditional comments (since these occasionally have their [uses](http://dean.edwards.name/weblog/2007/03/sniff/)). |
|
||||
| [Minify\_CSS](http://code.google.com/p/minify/source/browse/min/lib/Minify/CSS.php) | Uses the two classes below to minify CSS and optionally rewrite URIs. |
|
||||
| [Minify\_CSS\_Compressor](http://code.google.com/p/minify/source/browse/min/lib/Minify/CSS/Compressor.php) | Agressively minifies CSS. Includes a thorough [test suite](http://code.google.com/p/minify/source/browse/min_unit_tests/_test_files/css/) to make sure most common [hacks](http://code.google.com/p/minify/source/browse/min_unit_tests/_test_files/css/hacks.css) and [unusual syntaxes](http://code.google.com/p/minify/source/browse/min_unit_tests/_test_files/css/unusual_strings.css) are preserved. |
|
||||
| [Minify\_CSS\_UriRewriter](http://code.google.com/p/minify/source/browse/min/lib/Minify/CSS/UriRewriter.php) | In CSS files, rewrites file-relative URIs as root-relative. With [test suite](http://code.google.com/p/minify/source/browse/min_unit_tests/_test_files/css_uriRewriter/) |
|
||||
| [Minify\_HTML](http://code.google.com/p/minify/source/browse/min/lib/Minify/HTML.php) | Agressively minifies (X)HTML markup. Preserves TEXTAREA/PRE contents, can invoke minifiers on STYLE and SCRIPT blocks, and wraps content in CDATA blocks in XHTML when necessary. Includes [test suite](http://code.google.com/p/minify/source/browse/min_unit_tests/_test_files/html/). |
|
||||
| [Minify\_YUICompressor](http://code.google.com/p/minify/source/browse/min/lib/Minify/YUICompressor.php) | (Experimental) PHP wrapper for invoking the Java-based [YUI Compressor](http://developer.yahoo.com/yui/compressor/) |
|
||||
| [Minify\_ImportProcessor](http://code.google.com/p/minify/source/browse/min/lib/Minify/ImportProcessor.php) | Linearize a CSS/JS file by including content specified by CSS import declarations. In CSS files, relative URIs are fixed. This has a test suite but should be considered "experimental". |
|
||||
| [HTTP\_Encoder](http://code.google.com/p/minify/source/browse/min/lib/HTTP/Encoder.php) | Applies HTTP encoding to content. Can serve content with conditional encoding based on Accept-Encoding headers (parsed according to [HTTP spec](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)) and known browser bugs. |
|
||||
| [HTTP\_ConditionalGet](http://code.google.com/p/minify/source/browse/min/lib/HTTP/ConditionalGet.php) | Implements HTTP conditional GETs via a simple, flexible API. |
|
142
docs/CookBook.wiki.md
Normal file
142
docs/CookBook.wiki.md
Normal file
@@ -0,0 +1,142 @@
|
||||
Unless mentioned, all the following snippets go in `config.php`.
|
||||
|
||||
## Faster Cache Performance
|
||||
|
||||
By default, Minify uses `Minify_Cache_File`. It uses `readfile`/`fpassthru` to improve performance over most file-based systems, but it's still file IO, so the following caching options should be faster. In all cases, Minify cache ids begin with `"minify_"`.
|
||||
|
||||
### APC
|
||||
|
||||
```php
|
||||
$min_cachePath = new Minify_Cache_APC();
|
||||
```
|
||||
|
||||
### Memcache
|
||||
|
||||
You must create and connect your Memcache object then pass it to `Minify_Cache_Memcache`'s constructor.
|
||||
```php
|
||||
$memcache = new Memcache;
|
||||
$memcache->connect('localhost', 11211);
|
||||
$min_cachePath = new Minify_Cache_Memcache($memcache);
|
||||
```
|
||||
|
||||
### Zend Platform
|
||||
|
||||
```php
|
||||
$min_cachePath = new Minify_Cache_ZendPlatform();
|
||||
```
|
||||
|
||||
### XCache
|
||||
|
||||
```php
|
||||
$min_cachePath = new Minify_Cache_XCache();
|
||||
```
|
||||
|
||||
### WinCache
|
||||
|
||||
```php
|
||||
$min_cachePath = new Minify_Cache_WinCache();
|
||||
```
|
||||
|
||||
## Closure Compiler API Wrapper
|
||||
|
||||
An [experimental wrapper for Google's closure compiler API](../lib/Minify/JS/ClosureCompiler.php) is available for compressing Javascript. If the API fails for any reason, JSMin is used as the default backup minifier.
|
||||
```php
|
||||
$min_serveOptions['minifiers'][Minify::TYPE_JS] = array('Minify_JS_ClosureCompiler', 'minify');
|
||||
```
|
||||
|
||||
## YUICompressor
|
||||
|
||||
If your host can execute Java, you can use Minify's YUI Compressor wrapper. You'll need the latest [yuicompressor-x.x.x.jar](https://github.com/yui/yuicompressor/releases) and a temp directory. Place the .jar in `min/lib`, then:
|
||||
```php
|
||||
function yuiJs($js) {
|
||||
Minify_YUICompressor::$jarFile = __DIR__ . '/lib/yuicompressor-x.x.x.jar';
|
||||
Minify_YUICompressor::$tempDir = '/tmp';
|
||||
return Minify_YUICompressor::minifyJs($js);
|
||||
}
|
||||
$min_serveOptions['minifiers'][Minify::TYPE_JS] = 'yuiJs';
|
||||
```
|
||||
|
||||
To use YUIC for CSS with fixed URIs:
|
||||
|
||||
```php
|
||||
function yuiCss($css, $options) {
|
||||
Minify_YUICompressor::$jarFile = __DIR__ . '/lib/yuicompressor-x.x.x.jar';
|
||||
Minify_YUICompressor::$tempDir = '/tmp';
|
||||
$css = Minify_YUICompressor::minifyCss($css);
|
||||
|
||||
$css = Minify_CSS_UriRewriter::rewrite(
|
||||
$css
|
||||
,$options['currentDir']
|
||||
,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']
|
||||
,isset($options['symlinks']) ? $options['symlinks'] : array()
|
||||
);
|
||||
return $css;
|
||||
}
|
||||
$min_serveOptions['minifiers'][Minify::TYPE_CSS] = 'yuiCss';
|
||||
```
|
||||
|
||||
## Legacy CSS compressor
|
||||
|
||||
In 3.x, Minify uses [CSSmin](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port), a PHP port of the YUI CSS compressor. To use the compressor that came with Minify 2.x (not recommended), uncomment this line in your `config.php` file:
|
||||
|
||||
```php
|
||||
//$min_serveOptions['minifiers'][Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
|
||||
```
|
||||
|
||||
## Server-specific Options
|
||||
|
||||
You may need to have different options depending on what server you're on. You can do this just how you'd expect:
|
||||
```php
|
||||
if ($_SERVER['SERVER_NAME'] == 'myTestingWorkstation') {
|
||||
// testing
|
||||
$min_allowDebugFlag = true;
|
||||
$min_errorLogger = true;
|
||||
$min_enableBuilder = true;
|
||||
$min_cachePath = 'c:\\WINDOWS\\Temp';
|
||||
$min_serveOptions['maxAge'] = 0; // see changes immediately
|
||||
} else {
|
||||
// production
|
||||
$min_allowDebugFlag = false;
|
||||
$min_errorLogger = false;
|
||||
$min_enableBuilder = false;
|
||||
$min_cachePath = '/tmp';
|
||||
$min_serveOptions['maxAge'] = 86400;
|
||||
}
|
||||
```
|
||||
|
||||
## Site in a Subdirectory
|
||||
|
||||
If you test/develop sites in a subdirectory (e.g. `http://localhost/siteA/`), see AlternateFileLayouts.
|
||||
|
||||
## Group-specific Options
|
||||
|
||||
In "group" requests, `$_GET['g']` holds the group key, so you can code based on it:
|
||||
```php
|
||||
if (isset($_GET['g'])) {
|
||||
switch ($_GET['g']) {
|
||||
case 'js' : $min_serveOptions['maxAge'] = 86400 * 7;
|
||||
break;
|
||||
case 'css': $min_serveOptions['contentTypeCharset'] = 'iso-8859-1';
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## File/Source-specific Options
|
||||
|
||||
See CustomSource.
|
||||
|
||||
## Processing Output After Minification
|
||||
|
||||
If `$min_serveOptions['postprocessor']` is set to a callback, Minify will pass the minified content to this function with type as the second argument. This allows you to apply changes to your minified content without making your own custom minifier. E.g.:
|
||||
```php
|
||||
function postProcess($content, $type) {
|
||||
if ($type === Minify::TYPE_CSS) {
|
||||
require_once 'CssColorReplacer.php';
|
||||
return CssColorReplacer::process($content);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
$min_serveOptions['postprocessor'] = 'postProcess';
|
||||
```
|
||||
This function is only called once immediately after minification and its output is stored in the cache.
|
108
docs/CustomServer.wiki.md
Normal file
108
docs/CustomServer.wiki.md
Normal file
@@ -0,0 +1,108 @@
|
||||
Please see the UserGuide if you're just getting started with Minify. This guide is for more advanced users who wish to implement an HTTP server in PHP using the `Minify` class.
|
||||
|
||||
## Pull in Minify via Composer
|
||||
|
||||
In composer.json:
|
||||
```
|
||||
"require": {
|
||||
"mrclay/minify": "~3"
|
||||
},
|
||||
```
|
||||
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
## Set up autoloading, caching, and the Minify instance
|
||||
|
||||
Minify ships with several [cache classes](https://github.com/mrclay/minify/tree/master/lib/Minify/Cache) for files, APC, memcache, etc.:
|
||||
|
||||
```php
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
$cache = new Minify_Cache_APC();
|
||||
$minify = new Minify($cache);
|
||||
```
|
||||
|
||||
## Create the environment and the factory for source objects
|
||||
|
||||
```php
|
||||
$env = new Minify_Env();
|
||||
$sourceFactory = new Minify_Source_Factory($env, [], $cache);
|
||||
```
|
||||
|
||||
## Choose a Minify controller
|
||||
|
||||
Minify uses controller classes to analyze the environment (HTTP requests) and determine which sources will make up the response. (The content isn't always returned; if the browser's cache is valid, Minify can return a 304 header instead).
|
||||
|
||||
The `Files` controller doesn't care about the HTTP request. It just specifies a given array of sources (file paths or [source objects](CustomSource.md)).
|
||||
|
||||
The `Groups` controller uses `$_SERVER['PATH_INFO']` to choose from an array of source lists. There's an example at the end of this page.
|
||||
|
||||
The Files controller is simple, so we'll use it here.
|
||||
|
||||
```php
|
||||
$controller = new Minify_Controller_Files($env, $sourceFactory);
|
||||
```
|
||||
|
||||
## Set up service and controller options
|
||||
|
||||
A single array is used for configuring both the behavior of `Minify::serve` (see the [default options](../lib/Minify.php#L73)) and the controller, which has its own option keys.
|
||||
|
||||
The Files controller only needs one key: `files`: the array of sources to be combined and served.
|
||||
|
||||
The only `serve` option we'll set is `maxAge` (the default is only 1800 seconds).
|
||||
|
||||
```php
|
||||
$options = [
|
||||
// options for the controller
|
||||
'files' => ['//js/file1.js', '//js/file2.js'],
|
||||
|
||||
// options for Minify::serve
|
||||
'maxAge' => 86400,
|
||||
'minifierOptions' => [
|
||||
'text/css' => [
|
||||
'docRoot' => $env->getDocRoot(), // allows URL rewriting
|
||||
],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
Note: `files` can also accept `Minify_Source` objects, which allow serving more than static files.
|
||||
|
||||
## Handle the request
|
||||
|
||||
```php
|
||||
$minify->serve($controller, $options);
|
||||
```
|
||||
|
||||
That's it...
|
||||
|
||||
Minify's default application (`index.php`) is implemented similarly, but uses the `MinApp` controller. If you need URL rewriting in CSS files, you'll need to configure
|
||||
|
||||
# The Request Cycle
|
||||
|
||||
In handling a request, `Minify::serve` does a number of operations:
|
||||
|
||||
1. Minify merges your given options with its default options
|
||||
1. calls the controller's `createConfiguration()`, which analyzes the request and returns a `Minify_ServeConfiguration` object, encapsulating the source objects found.
|
||||
1. uses `analyzeSources()` to determine the Content-Type and last modified time.
|
||||
1. determines if the browser accepts gzip
|
||||
1. validates the browser cache (optionally responding with a 304)
|
||||
1. validates the server cache. If it needs refreshing, `combineMinify()` fetchs and combines the content of each source.
|
||||
1. sets up headers to be sent
|
||||
1. either returns the headers and content in an array (if `quiet` is set), or sends it to the browser.
|
||||
|
||||
# Using the Groups controller
|
||||
|
||||
The Groups controller uses `$_SERVER['PATH_INFO']` to select an array of sources from the given options:
|
||||
```
|
||||
$options = [
|
||||
'groups' => [
|
||||
'js' => ['//js/file1.js', '//js/file2.js'],
|
||||
'css' => ['//css/file1.css', '//css/file2.css'],
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
With the above, if you request `http://example.org/myServer.php/css`, Apache will set `$_SERVER['PATH_INFO'] = '/css'` and the sources in `$options['groups']['css']` will be served.
|
136
docs/CustomSource.wiki.md
Normal file
136
docs/CustomSource.wiki.md
Normal file
@@ -0,0 +1,136 @@
|
||||
In `groupsConfig.php`, usually you specify source file paths using strings like `/path/to/file.js` or `//js/file1.js` (Minify expands this to `"{$_SERVER['DOCUMENT_ROOT']}/js/file1.js"` ).
|
||||
|
||||
Instead of a string, you may substitute an instance of class `Minify_Source`. This allows you to customize how minification is applied, and/or pull content from a non-file location (e.g. a URL).
|
||||
|
||||
### Example: filepath
|
||||
|
||||
In the `$spec` array, set the key `filepath` to produce a source based on a file path:
|
||||
|
||||
```php
|
||||
$src1 = new Minify_Source(array(
|
||||
'filepath' => '//js/file1.js',
|
||||
));
|
||||
$src2 = new Minify_Source(array(
|
||||
'filepath' => '//js/file2.js',
|
||||
));
|
||||
|
||||
return [
|
||||
'js' => [$src1, $src2]
|
||||
];
|
||||
```
|
||||
|
||||
Note the above is functionally identical to:
|
||||
```php
|
||||
return [
|
||||
'js' => ['//js/file1.js', '//js/file2.js'],
|
||||
];
|
||||
```
|
||||
|
||||
### Example: Specify a different minifier or none at all
|
||||
|
||||
To change minifier, set `minifier` to a [callback](http://php.net/manual/en/language.pseudo-types.php)`*` or the empty string (for none):
|
||||
|
||||
**`*`Prepare for `groupsConfig.php` to be executed more than once.** (This is likely if you're using the functions in `/min/utils.php`.) In practice this just means making sure functions are conditionally defined if they don't already exist, etc.
|
||||
|
||||
```php
|
||||
$src1 = new Minify_Source(array(
|
||||
'filepath' => '//js/file1.js',
|
||||
'minifier' => 'myJsMinifier',
|
||||
));
|
||||
$src2 = new Minify_Source(array(
|
||||
'filepath' => '//js/file2.js',
|
||||
'minifier' => 'Minify::nullMinifier', // don't compress
|
||||
));
|
||||
```
|
||||
In the above, `JmyJsMinifier()` is only called when the contents of `$src1` is needed.
|
||||
|
||||
**`*`Do _not_ use `create_function()` or anonymous functions for the minifier.** The internal names of these function tend to vary, causing endless cache misses, killing performance and filling cache storage up.
|
||||
|
||||
## Non-File Sources
|
||||
|
||||
You're not limited to flat js/css files, but without `filepath`, the `$spec` array must contain these keys:
|
||||
|
||||
* **`id `** a unique string id for this source. (e.g. `'my source'`)
|
||||
* **`getContentFunc `** a [callback](http://php.net/manual/en/language.pseudo-types.php) that returns the content. The function is only called when the cache is rebuilt.
|
||||
* **`contentType `** `Minify::TYPE_JS` or `Minify::TYPE_CSS`
|
||||
* **`lastModified `** a timestamp indicating when the content last changed. (If you can't determine this quickly, you can "fake" it using a step function, causing the cache to be periodically rebuilt.)
|
||||
|
||||
### Example: Content from a URL
|
||||
|
||||
Here we want to fetch javascript from a URL. We don't know when it will change, so we use a stepping expression to re-fetch it every midnight:
|
||||
```php
|
||||
if (! function_exists('src1_fetch')) {
|
||||
function src1_fetch() {
|
||||
return file_get_contents('http://example.org/javascript.php');
|
||||
}
|
||||
}
|
||||
$src1 = new Minify_Source([
|
||||
'id' => 'source1',
|
||||
'getContentFunc' => 'src1_fetch',
|
||||
'contentType' => Minify::TYPE_JS,
|
||||
'lastModified' => ($_SERVER['REQUEST_TIME'] - $_SERVER['REQUEST_TIME'] % 86400),
|
||||
]);
|
||||
```
|
||||
|
||||
If you know that the URL content only depends on a few local files, you can use the maximum of their `mtime`s as the `lastModified` key:
|
||||
```php
|
||||
$src1 = new Minify_Source([
|
||||
'id' => 'source1',
|
||||
'getContentFunc' => 'src1_fetch',
|
||||
'contentType' => Minify::TYPE_JS,
|
||||
'lastModified' => max(
|
||||
filemtime('/path/to/javascript.php')
|
||||
,filemtime('/path/to/javascript_input.css')
|
||||
),
|
||||
]);
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
Be aware that all the code you put in `groupsConfig.php` will be evaluated upon every request like `/min/g=...`, so make it as light as possible.
|
||||
|
||||
If you wish to keep `groupsConfig.php` "clean", you can alternately create a separate PHP script that manually sets up sources, caching, options, and calls Minify::serve().
|
||||
|
||||
```php
|
||||
// myServer.php
|
||||
/**
|
||||
* This script implements a Minify server for a single set of sources.
|
||||
* If you don't want '.php' in the URL, use mod_rewrite...
|
||||
*/
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// setup Minify
|
||||
$cache = new Minify_Cache_File();
|
||||
$minify = new Minify($cache);
|
||||
$env = new Minify_Env();
|
||||
$sourceFactory = new Minify_Source_Factory($env, [], $cache);
|
||||
$controller = new Minify_Controller_Files($env, $sourceFactory);
|
||||
|
||||
function src1_fetch() {
|
||||
return file_get_contents('http://example.org/javascript.php');
|
||||
}
|
||||
|
||||
// setup sources
|
||||
$sources = [];
|
||||
$sources[] = new Minify_Source([
|
||||
'id' => 'source1',
|
||||
'getContentFunc' => 'src1_fetch',
|
||||
'contentType' => Minify::TYPE_JS,
|
||||
'lastModified' => max(
|
||||
filemtime('/path/to/javascript.php'),
|
||||
filemtime('/path/to/javascript_input.js')
|
||||
),
|
||||
]);
|
||||
$sources[] = '//file2.js';
|
||||
$sources[] = '//file3.js';
|
||||
|
||||
// setup serve and controller options
|
||||
$options = [
|
||||
'files' => $sources,
|
||||
'maxAge' => 86400,
|
||||
];
|
||||
|
||||
// handle request
|
||||
$minify->serve($controller, $options);
|
||||
```
|
80
docs/Debugging.wiki.md
Normal file
80
docs/Debugging.wiki.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Server Errors
|
||||
|
||||
| **Code** | **Most likely cause** |
|
||||
|:---------|:----------------------|
|
||||
| 400 | Controller failed to return valid set of sources to serve |
|
||||
| 500 | Minifier threw exception (e.g. JSMin choked on syntax) |
|
||||
|
||||
You can find details by enabling FirePHP logging:
|
||||
|
||||
1. Install/enable FirePHP for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/6149) or [Chrome](https://chrome.google.com/webstore/detail/firephp4chrome/gpgbmonepdpnacijbbdijfbecmgoojma?hl=en-US).
|
||||
1. Open the Chrome DevTools/Firebug console
|
||||
1. Set `$min_errorLogger = true;` in config.php
|
||||
1. Reload the Minify URL
|
||||
|
||||
Hopefully you'll see the error appear:
|
||||
|
||||
```
|
||||
Minify: Something bad happened!
|
||||
```
|
||||
|
||||
# Javascript/CSS Problems
|
||||
|
||||
When Javascript errors occur, or URIs in CSS files are incorrectly rewritten, enable "debug mode" to ease debugging combined files:
|
||||
|
||||
1. Set `$min_allowDebugFlag = true;` in config.php
|
||||
1. Append `&debug` to the Minify URI. E.g. `/min/?f=script1.js,script2.js&debug` (or use the bookmarklet provided by /min/builder/)
|
||||
|
||||
In "debug mode":
|
||||
|
||||
* comments are inserted into the output showing you line numbers in the original file(s)
|
||||
* no minification is performed
|
||||
* In CSS, URI rewriting _is_ performed
|
||||
* In CSS, a leading comment shows how URIs were rewritten.
|
||||
|
||||
Example: a combination of two Javascript files in debug mode
|
||||
|
||||
```js
|
||||
/* firstFile.js */
|
||||
|
||||
/* 1 */ (function () {
|
||||
/* 2 */ if (window.foo) {
|
||||
...
|
||||
/* 11 */ })();
|
||||
|
||||
;
|
||||
/* secondFile.js */
|
||||
|
||||
/* 1 */ var Foo = window.Foo || {};
|
||||
/* 2 */
|
||||
...
|
||||
```
|
||||
|
||||
Example: Top of CSS output in debug mode
|
||||
|
||||
```
|
||||
docRoot : M:\home\foo\www
|
||||
currentDir : M:\home\foo\www\css
|
||||
|
||||
file-relative URI : typography.css
|
||||
path prepended : M:\home\foo\www\css\typography.css
|
||||
docroot stripped : \css\typography.css
|
||||
traversals removed : /css/typography.css
|
||||
|
||||
file-relative URI : ../images/bg.jpg
|
||||
path prepended : M:\home\foo\www\css\..\images\bg.jpg
|
||||
docroot stripped : \css\..\images\bg.jpg
|
||||
traversals removed : /images/bg.jpg
|
||||
```
|
||||
|
||||
### Tips for handling Javascript errors
|
||||
|
||||
* Use the latest version (2.1.4 beta as of Dec 2010)
|
||||
* Try [debug mode](#javascriptcss-problems) to make the combined file more readable (and error locations findable)
|
||||
* Find out if other browsers have the same error
|
||||
* For pre-minified files, make the filenames end in `.min.js` or `-min.js`, which will prevent Minify from altering them
|
||||
* Test your scripts in [JSLint](http://www.jslint.com/).
|
||||
|
||||
## See Also
|
||||
|
||||
* [CommonProblems](CommonProblems.wiki.md)
|
51
docs/FAQ.wiki.md
Normal file
51
docs/FAQ.wiki.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## Minify (JSMin) doesn't compress as much as product XYZ. Why not?
|
||||
|
||||
The simple JSMin algorithm is the most reliable in PHP, but check the [CookBook](CookBook.wiki.md) to plug in other minifiers.
|
||||
|
||||
## How fast is it?
|
||||
|
||||
If you [serve static files](../static/README.md), it's as fast as your web server, and you should do this for high-traffic sites.
|
||||
|
||||
The PHP-based server is not as fast, but still performs well thanks to an internal cache. Tips:
|
||||
|
||||
* **Use a reverse proxy** to cache the Minify URLs. This is by far the most important tip.
|
||||
* Revision your Minify URIs (so far-off Expires headers will be sent). One way to do this is using [groups](UserGuide.wiki.md#using-groups-for-nicer-urls) and the [Minify_groupUri()](UserGuide.wiki.md#far-future-expires-headers) utility function. Without this, clients will re-request Minify URLs every 30 minutes to check for updates.
|
||||
* Use the [APC/Memcache adapters](CookBook.wiki.md).
|
||||
|
||||
## Does it support gzip compression?
|
||||
|
||||
Yes. Based on the browser's Accept-Encoding header.
|
||||
|
||||
## Does it work with PHP opcode caches?
|
||||
|
||||
Yes, and you can also use [APC for content caching](CookBook.wiki.md).
|
||||
|
||||
## Can it minify remote files/the output of dynamic scripts?
|
||||
|
||||
[Yes](CustomSource.wiki.md#non-file-sources), but it's not a straightforward setup, and probably best avoided.
|
||||
|
||||
## Is there a minifier for HTML?
|
||||
|
||||
The class `Minify_HTML` can do this (and minify embedded STYLE and SCRIPT elements), but it's too slow to use directly. You'd want to integrate it into a system that caches the output. E.g., in a CMS, keep one copy for editing and one minified for serving.
|
||||
|
||||
## How does it ensure that the client can't request files it shouldn't have access to?
|
||||
|
||||
Minify allows files to be specified using the URI, or using pre-configured sets of files. With URI-specified files, Minify is very careful to serve only JS/CSS files that are already public on your server, but if you hide public directories--with .htaccess, e.g.--Minify can't know that. Obvious Tip: don't put sensitive info in JS/CSS files inside DOC_ROOT :)
|
||||
|
||||
An included option can disable URI-specified files so Minify will serve only the pre-configured file sets.
|
||||
|
||||
## Is it used in production by any large-scale websites?
|
||||
|
||||
The libraries are used in many CMS's and frameworks, but the use of `index.php` to serve URLs like http://example.com/min/f=hello.js probably is rare. Minify is made to drop in place to boost small to medium sites not already built for performance.
|
||||
|
||||
Version 2.1.1 had 54K downloads.
|
||||
|
||||
## Can I use it with my commercial website or product?
|
||||
|
||||
Yes. Minify is distributed under the [New BSD License](http://www.opensource.org/licenses/bsd-license.php).
|
||||
|
||||
## How can I tell if my server cache is working?
|
||||
|
||||
The easiest way is to place a Minify URL directly in your browser's address bar and refresh (F5), which should override the client-side caching that Minify specifies and force Minify to send you a complete response. With cache working, this response should take 100ms or so. Without cache, it could be multiple seconds.
|
||||
|
||||
If you have file access to the server you can check your cache path directly for filenames beginning with `minify_`.
|
61
docs/Install.wiki.md
Normal file
61
docs/Install.wiki.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Installation
|
||||
|
||||
Minify requires PHP 5.3+, `git`, and `composer`.
|
||||
|
||||
## Typical Installation
|
||||
|
||||
Clone the project into the `min/` directory inside your document root and install its dependencies:
|
||||
|
||||
```bash
|
||||
cd /path/to/public_html
|
||||
git clone https://github.com/mrclay/minify.git min
|
||||
cd min
|
||||
composer install --no-dev
|
||||
```
|
||||
|
||||
**Note:** If you do this on localhost, make sure the `min/vendor/` directory gets deployed to production.
|
||||
|
||||
## Installing as a composer dependency
|
||||
|
||||
Add `"mrclay/minify": "~3.0.0"` to your site's composer.json, and `composer install`.
|
||||
|
||||
The following assumes your `vendor` directory is in your document root. Adjust the `MINIFY` path as needed:
|
||||
|
||||
```bash
|
||||
cd /path/to/public_html
|
||||
mkdir min
|
||||
MIN=min/
|
||||
MINIFY=vendor/mrclay/minify/
|
||||
cp ${MINIFY}example.index.php ${MIN}index.php
|
||||
cp ${MINIFY}.htaccess ${MIN}
|
||||
cp ${MINIFY}config.php ${MIN}
|
||||
cp ${MINIFY}groupsConfig.php ${MIN}
|
||||
cp ${MINIFY}quick-test.js ${MIN}
|
||||
cp ${MINIFY}quick-test.css ${MIN}
|
||||
```
|
||||
|
||||
Edit `min/index.php` to remove the ``die()`` statement and adjust the `vendor` path as needed.
|
||||
|
||||
**Note:** This does not install the [URL builder](BuilderApp.wiki.md), but it's not necessary for operation.
|
||||
|
||||
## Verifing it works
|
||||
|
||||
You can verify it works via these two URLs:
|
||||
|
||||
* http://example.org/min/?f=min/quick-test.js
|
||||
* http://example.org/min/?f=min/quick-test.css
|
||||
|
||||
If your server supports mod_rewrite, the `?` are not necessary:
|
||||
|
||||
* http://example.org/min/f=min/quick-test.js
|
||||
* http://example.org/min/f=min/quick-test.css
|
||||
|
||||
## Having trouble?
|
||||
|
||||
Write the [Google Group](http://groups.google.com/group/minify) for help.
|
||||
|
||||
## More links
|
||||
|
||||
* [Usage instructions](UserGuide.wiki.md)
|
||||
* [Cookbook](CookBook.wiki.md) for more advanced options
|
||||
* [All docs](docs)
|
14
docs/MinApp.wiki.md
Normal file
14
docs/MinApp.wiki.md
Normal file
@@ -0,0 +1,14 @@
|
||||
"Min" is the front end application `index.php` that handles requests to `http://example.com/min/` and responds with compressed/combined content.
|
||||
|
||||
When the documentation refers to "Minify" it usually means this application, but sometimes refers to the [ComponentClasses](ComponentClasses.wiki.md).
|
||||
|
||||
User-configurable files:
|
||||
|
||||
* `/config.php`: general configuration
|
||||
* `/groupsConfig.php`: configuration of pre-defined groups of files
|
||||
|
||||
Other files of interest:
|
||||
|
||||
* `/.htaccess`: rewrites URLs for the front controller
|
||||
* `/index.php`: front controller
|
||||
* `/lib/Minify/Controller/MinApp.php`: determines which files are combined based on `$_GET`, sets some default options
|
73
docs/UriRewriting.wiki.md
Normal file
73
docs/UriRewriting.wiki.md
Normal file
@@ -0,0 +1,73 @@
|
||||
## Default operation
|
||||
|
||||
Minify uses an algorithm to rewrite relative URIs in CSS output to root-relative URIs so that each link points to the same location it did in its original file.
|
||||
|
||||
Say your style sheet `http://example.org/theme/fashion/style.css` contains:
|
||||
```
|
||||
body { background: url(bg.jpg); }
|
||||
```
|
||||
|
||||
When Minify serves this content (from `http://example.org/min/f=theme/fashion/style.css` or `http://example.org/min/g=css`) it re-writes the URI so the image is correctly linked:
|
||||
```
|
||||
body{background:url(/theme/fashion/bg.jpg)}
|
||||
```
|
||||
|
||||
You can see the steps used to rewrite your URIs by enabling [debug mode](Debugging.wiki.md).
|
||||
|
||||
## Disable Rewriting
|
||||
|
||||
You can disable the automatic rewriting by setting this in config.php:
|
||||
```php
|
||||
$min_serveOptions['rewriteCssUris'] = false;
|
||||
```
|
||||
|
||||
## Manual Rewriting
|
||||
|
||||
You can manually rewrite relative URIs in CSS in a couple ways. The simplest is to prepend a string to each relative URI:
|
||||
```php
|
||||
$min_serveOptions['rewriteCssUris'] = false;
|
||||
$min_serveOptions['minifierOptions'][Minify::TYPE_CSS]['prependRelativePath'] = '/css/';
|
||||
```
|
||||
|
||||
Or you can run the minified output through a custom [post-processor](CookBook.wiki.md#Processing_Output_After_Minification.md) function.
|
||||
|
||||
## Document Root Confusion
|
||||
|
||||
Out-of-the-box, Minify gets confused when `min` is placed in a subdirectory of the real document root. There's now a [simple workaround](AlternateFileLayouts.md) for this, making `min` more portable.
|
||||
|
||||
## Aliases / Symlinks / Virtual Directories
|
||||
|
||||
Whether you use [aliases](http://httpd.apache.org/docs/2.2/mod/mod_alias.html), [symlinks](http://en.wikipedia.org/wiki/Symbolic_link), or [virtual directories](http://msdn.microsoft.com/en-us/library/zwk103ab.aspx), if you make content outside of the DOC\_ROOT available at public URLs, Minify may need manual configuration of the `$min_symlinks` option to rewrite some URIs correctly. Consider this scenario, where `http://example.org/static/style.css` will serve `/etc/static_content/style.css`:
|
||||
|
||||
| document root | `/var/www` |
|
||||
|:------------------|:--------------------------------------------|
|
||||
| Apache mod\_alias | `Alias /static /etc/static_content` |
|
||||
| ...or symlink | `ln -s /etc/static_content /var/www/static` |
|
||||
|
||||
In `/config.php` you'll need the following:
|
||||
```php
|
||||
// map URL path to file path
|
||||
$min_symlinks = array(
|
||||
'//static' => '/etc/static_content'
|
||||
);
|
||||
```
|
||||
This lets Minify know during the rewriting process that an internal file path starting with `/etc/static_content` should be rewritten as a public URI beginning with `/static`.
|
||||
|
||||
If your alias target directory is outside of DOC\_ROOT, you'll also need to explicitly allow Minify to serve files from it:
|
||||
```php
|
||||
$min_serveOptions['minApp']['allowDirs'] = array(
|
||||
'//', // allow from the normal DOC_ROOT
|
||||
'/etc/static_content' // allow from our alias target
|
||||
);
|
||||
```
|
||||
|
||||
### What's my document root?
|
||||
|
||||
You can enable the script `min/server-info.php` and open http://example.org/min/server-info.php to find useful `$_SERVER` values. People in the [Google Group](https://groups.google.com/forum/#!forum/minify) might need these to help you.
|
||||
|
||||
## It's still not working
|
||||
|
||||
1. Make sure you have the latest version.
|
||||
1. Enable [debug mode](Debugging.wiki.md), which will show you the URI transformation process.
|
||||
1. Check that `$_SERVER['DOCUMENT_ROOT']` is the correct directory path. If not, URI rewriting will fail. If you cannot fix this in httpd.conf, etc., set `$min_documentRoot` in config.php.
|
||||
1. Paste your [debug mode](Debugging.wiki.md) comment block into a new post on the [minify mailing list](http://groups.google.com/group/minify).
|
129
docs/UserGuide.wiki.md
Normal file
129
docs/UserGuide.wiki.md
Normal file
@@ -0,0 +1,129 @@
|
||||
If this page doesn't help, please post a question on our [Google group](http://groups.google.com/group/minify).
|
||||
|
||||
# Creating Minify URLs
|
||||
|
||||
Let's say you want to serve the file http://example.com/css/foo/bar.css
|
||||
|
||||
You would use the URL http://example.com/min/?f=css/foo/bar.css
|
||||
|
||||
In other words, the "f" argument is set to the file path from root without the initial `/`. As CSS files may contain relative URIs, Minify will automatically "fix" these by rewriting them as root relative.
|
||||
|
||||
To combine multiple files, separate the paths given to "f" with commas.
|
||||
|
||||
Let's say you have JS files at these URLs:
|
||||
|
||||
* http://example.com/scripts/library-1.5.js
|
||||
* http://example.com/scripts/site.js
|
||||
|
||||
You'd use the URL http://example.com/min/?f=scripts/library-1.5.js,scripts/site.js
|
||||
|
||||
## Shortening URLs with common directories
|
||||
|
||||
If you're combining files that share the same ancestor directory, you can use the "b" argument to set the base directory for the "f" argument. Do not include the leading or trailing `/` characters.
|
||||
|
||||
E.g., the following URLs will serve the exact same content:
|
||||
|
||||
* http://example.com/min/?f=path/to/scripts/library-1.5.js,path/to/scripts/foo/site.js
|
||||
* http://example.com/min/?b=path/to/scripts&f=library-1.5.js,site.js,home.js
|
||||
|
||||
# Limiting access to directories
|
||||
|
||||
By default, Minify will serve any CSS/JS files within the DOCUMENT_ROOT. If you'd prefer to limit Minify's access to certain directories, set the `$min_serveOptions['minApp']['allowDirs']` array in config.php. E.g. to limit to the `/js` and `/themes/default` directories, use:
|
||||
|
||||
```php
|
||||
$min_serveOptions['minApp']['allowDirs'] = ['//js', '//themes/default'];
|
||||
```
|
||||
|
||||
# Using groups for nicer URLs
|
||||
|
||||
For nicer URLs, edit groupsConfig.php to pre-specify groups of files to be combined under preset keys. E.g., here's an example configuration in groupsConfig.php:
|
||||
|
||||
```php
|
||||
return [
|
||||
'js' => ['//js/Class.js', '//js/email.js'],
|
||||
];
|
||||
```
|
||||
|
||||
This pre-selects the following files to be combined under the key "js":
|
||||
|
||||
* http://example.com/js/Class.js
|
||||
* http://example.com/js/email.js
|
||||
|
||||
You can now serve these files with http://example.com/min/?g=js
|
||||
|
||||
## Specifying files outside the document root
|
||||
|
||||
In the groupsConfig.php array, the `//` in the file paths is a shortcut for the DOCUMENT_ROOT, but you can also specify paths from the root of the filesystem or relative to the DOC_ROOT:
|
||||
|
||||
```php
|
||||
return [
|
||||
'js' => [
|
||||
'//js/file.js', // file within DOC_ROOT
|
||||
'//../file.js', // file in parent directory of DOC_ROOT
|
||||
'C:/Users/Steve/file.js', // file anywhere on filesystem
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Multiple groups and files in one URL
|
||||
|
||||
E.g.: http://example.com/min/?g=js&f=more/scripts.js
|
||||
|
||||
Separate group keys with commas: http://example.com/min/?g=baseCss,css1&f=moreStyles.css
|
||||
|
||||
## Creating URLs with the Builder
|
||||
|
||||
Enable the [BuilderApp](BuilderApp.wiki.md) via config.php. The default password is "admin", but even if no password is used there's very little server information disclosed.
|
||||
|
||||
Browse to http://example.com/min/builder/
|
||||
|
||||
The Minify URI Builder will help you create URIs you can use to minify existing files on your site. You can see screenshots and get a feel for this process from this [walkthrough on mrclay.org](http://mrclay.org/index.php/2008/09/19/minify-21-on-mrclayorg/)
|
||||
|
||||
You may want to disable the [BuilderApp](BuilderApp.wiki.md) when not in use.
|
||||
|
||||
# Far-future Expires headers
|
||||
|
||||
Minify can send far-future (one year) Expires headers. To enable this you must add a number or the parameter "v" to the querystring (e.g. `/min/?g=js&1234` or `/min/?g=js&v=1234`) and alter it whenever a source file is changed. If you have a build process you can use a build/source control revision number.
|
||||
|
||||
You can alternately use the utility function `Minify_getUri()` to get a "versioned" Minify URI for use in your HTML (it sniffs the `mtime` of the files). E.g.:
|
||||
|
||||
```php
|
||||
require 'path/to/min/utils.php';
|
||||
|
||||
$jsUri = Minify_getUri('js'); // a key in groupsConfig.php
|
||||
echo "<script src='{$jsUri}'></script>";
|
||||
|
||||
$cssUri = Minify_getUri([ // a list of files
|
||||
'//css/styles1.css',
|
||||
'//css/styles2.css',
|
||||
]);
|
||||
echo "<link rel=stylesheet href='{$cssUri}'>";
|
||||
```
|
||||
|
||||
# Debug mode
|
||||
|
||||
In debug mode, instead of compressing files, Minify sends combined files with comments prepended to each line to show the line number in the original source file. To enable this, set `$min_allowDebugFlag` to `true` in config.php and append `&debug=1` to your URIs. E.g. `/min/?f=script1.js,script2.js&debug=1`
|
||||
|
||||
Known issue: files with comment-like strings/regexps can cause problems in this mode.
|
||||
|
||||
# Configuration
|
||||
|
||||
See [config.php](../config.php) for general config options.
|
||||
|
||||
[groupsConfig.php](../groupsConfig.php) holds preset groups of files to minify. (The builder application can help with this).
|
||||
|
||||
[CookBook](CookBook.wiki.md) shows how to customize settings between production/development environments, and between groups.
|
||||
|
||||
[CustomSource](CustomSource.wiki.md) shows how to set some file/source-specific options, or serve content from a PHP script or URL.
|
||||
|
||||
### Hosting on Lighttpd
|
||||
|
||||
Minify comes with Apache mod_rewrite rules, but this does the same for Lighttpd:
|
||||
|
||||
```
|
||||
url.rewrite-once = ( "^/min/([a-z]=.*)" => "/min/index.php?$1" )
|
||||
```
|
||||
|
||||
# Problems?
|
||||
|
||||
See [CommonProblems](CommonProblems.wiki.md) and [Debugging](Debugging.wiki.md). You might also try running `server-info.php` in particular.
|
31
docs/old/HowItWorks.wiki.md
Normal file
31
docs/old/HowItWorks.wiki.md
Normal file
@@ -0,0 +1,31 @@
|
||||
### Browsers, trust your cache
|
||||
|
||||
In all responses a `Cache-Control` header is sent, telling the browser it doesn't need to "check in" with the server for some period of time. The ideal request is the one that never leaves the browser!
|
||||
|
||||
### Convert a request to source objects
|
||||
|
||||
When the browser makes a request like `/min/g=css`, Apache rewrites this to `/min/index.php?g=css`, which calls Minify's front controller.
|
||||
|
||||
A separate controller then uses the querystring to establish a "sources" array, specifying exactly which objects (usually files) are to be included in the final output.
|
||||
|
||||
### Try browser cache
|
||||
|
||||
Minify finds the latest modification time of all the source objects (`filemtime` for files, so if you use a tool that doesn't update this, you might need to `touch` your modified files).
|
||||
|
||||
If the browser has sent an `If-Modified-Since` header, and it's valid (the given date is older than the most recent source), then a 304 header is returned, execution stops, and the browser uses its cache copy.
|
||||
|
||||
### Try server cache
|
||||
|
||||
Minify generates a unique cache ID for the particular set of sources and their options. This is used to maintain a cache (file usually) of the final output.
|
||||
|
||||
If the cache is "valid" (younger than the most recently modified source), then its content is sent along with a `Last-Modified` header with the most recent source's modification time, and execution stops.
|
||||
|
||||
### Minification has to be done
|
||||
|
||||
If any source is younger than the cache, the cache must be rebuilt from the minification process (slow, but infrequently done):
|
||||
|
||||
Minify processes each source with a "minifier" function (determined by the content type of the sources and source-specific options), combines them to a single string, saves this to the cache object, then serves it with the `Last-Modified` header as sbove.
|
||||
|
||||
#### Content encoding
|
||||
|
||||
Minify actually stores a gzipped version of each output in a second cache object. If the browser supports it, Minify streams the pre-compressed content straight from cache (disk usually) to the browser.
|
63
docs/old/HttpCaching.wiki.md
Normal file
63
docs/old/HttpCaching.wiki.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# HTTP Caching in Minify
|
||||
|
||||
## Conditional GET
|
||||
|
||||
Minify sends all files with Last-Modified and ETag headers. If the browser requests a file again it will send along If-Modified-Since and If-None-Match headers. Minify checks these headers and, if the browser has the latest version, sends back only a slim "304 Not Modified" response (no content), telling the browser to use its cached file.
|
||||
|
||||
## Expires Header
|
||||
|
||||
Minify also sends Expires and Cache-Control: max-age headers, indicating that the file should be considered valid for a period of time. In future page views the browser will not re-request the file (unless the user refreshes), and instead will use the cached version.
|
||||
|
||||
By default, Minify sends an Expires header for 1800 seconds (30 minutes) into the future (configurable via `$min_serveOptions['maxAge']`). This means your file changes may not be seen by users immediately after you make them. If your changes must be seen immediately, you should reduce max-age to 0, but note you will not get as much benefit, as browsers will still have to send requests **every time**.
|
||||
|
||||
## Far-off Expires
|
||||
|
||||
When pre-set groups are used and a number is appended to the minify URI (e.g. `/min/g=css&456`), then Minify sends an Expires date of 1 year in the future. This is great for caching, but places responsibility on your HTML pages. They _must_ change the number whenever a JS/CSS source file is updated, or the browser will not know to re-request the file. If you're generating your page with PHP, the [Minify\_groupUri](http://code.google.com/p/minify/source/browse/min/utils.php?r=222#11) utility function can make this easier to manage.
|
||||
|
||||
# Using `HTTP_ConditionalGet` in other projects
|
||||
|
||||
Minify uses the PHP class [HTTP\_ConditionalGet](http://code.google.com/p/minify/source/browse/lib/HTTP/ConditionalGet.php) to implement the conditional GET model. To use this in your own project you'll need the last modification time of your content (for a file, use [filemtime()](http://www.php.net/filemtime)), or a short hash digest of the content (something that changes when the content changes). You'll also want to consider if the content can be stored in public caches, or should only be stored in the user's browser.
|
||||
|
||||
## When the last modification time is known
|
||||
|
||||
In this example we implement conditional GET for a mostly-static PHP script. The browser needs to redownload the content only when the file is updated.
|
||||
|
||||
```
|
||||
// top of file
|
||||
$cg = new HTTP_ConditionalGet(array(
|
||||
'isPublic' => true,
|
||||
'lastModifiedTime' => filemtime(__FILE__)
|
||||
));
|
||||
$cg->sendHeaders();
|
||||
if ($cg->cacheIsValid) { // 304 already sent
|
||||
exit();
|
||||
}
|
||||
// rest of script
|
||||
```
|
||||
|
||||
For the first request the browser's cache won't be valid, so the full script will execute, sending the full content. On the next, the cache will be valid and the sendHeaders() method will have already set the 304 header, so the script can be halted.
|
||||
|
||||
There's also a shortcut static method for this:
|
||||
```
|
||||
HTTP_ConditionalGet::check(filemtime(__FILE__), true); // exits if client has cache
|
||||
// rest of script
|
||||
```
|
||||
|
||||
## When last modification time isn't known
|
||||
|
||||
Let's say you have an HTML page in a database, but no modification time. To reduce DB requests, you cache this content in a file/memory, but you'd also like to reduce bandwidth. In this case, what you can do is also cache a hash of the page along with the content. Now you can do this:
|
||||
|
||||
```
|
||||
$cache = getCache();
|
||||
$cg = new HTTP_ConditionalGet(array(
|
||||
'isPublic' => true,
|
||||
'contentHash' => $cache['hash']
|
||||
));
|
||||
$cg->sendHeaders();
|
||||
if ($cg->cacheIsValid) { // 304 already sent
|
||||
exit();
|
||||
}
|
||||
echo $cache['page'];
|
||||
```
|
||||
|
||||
Although Last-Modified cannot be set, ETag will serve the same purposes in most browsers, allowing the conditional GET.
|
13
docs/old/ProjectGoals.wiki.md
Normal file
13
docs/old/ProjectGoals.wiki.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## 1. Be easy to implement
|
||||
|
||||
A PHP user should be able to quickly start serving JS/CSS much more optimally.
|
||||
|
||||
## 2. Be extensible/versatile
|
||||
|
||||
The user should be able to work Minify into an environment without modifying Minify's source. Right now this should be easy on the controller side, but the Minify class is static, which is fast, but may hinder extensibility.
|
||||
|
||||
## 3. Be fast as possible
|
||||
|
||||
With the release of 2.0.2 Minify will be much faster, outperforming even Apache's mod\_deflate (which apparently doesn't cache the encoded content like mod\_gzip does).
|
||||
|
||||
Since testing has shown that [pre-encoding files and using type-maps on Apache is blazingly fast](http://mrclay.org/index.php/2008/06/03/pre-encoding-vs-mod_deflate/), Minify should work towards maintaining builds of pre-encoded files and letting Apache serve them. It's not clear if there's something similar to type-maps on lighttpd, but it's worth looking into.
|
15
docs/old/Security.wiki.md
Normal file
15
docs/old/Security.wiki.md
Normal file
@@ -0,0 +1,15 @@
|
||||
This was quickly converted from an e-mail, please consider it "temporary".
|
||||
|
||||
## Each file specified by `$_GET['f']` must:
|
||||
|
||||
* Have the [same extension, either "css" or "js"](http://code.google.com/p/minify/source/browse/tags/release_2.1.1/min/lib/Minify/Controller/MinApp.php#66),
|
||||
* Exist, and...
|
||||
* Have a [realpath() within a whitelist of subdirectories](http://code.google.com/p/minify/source/browse/tags/release_2.1.1/min/lib/Minify/Controller/Base.php#122).
|
||||
|
||||
The default whitelist contains only DOCUMENT\_ROOT, but can be [specified](http://code.google.com/p/minify/source/browse/tags/release_2.1.1/min/config.php#57).
|
||||
|
||||
Then, a few more steps just to be paranoid:
|
||||
|
||||
* If a base was given by `$_GET['b']`, [it can't have ".."](http://code.google.com/p/minify/source/browse/tags/release_2.1.1/min/lib/Minify/Controller/MinApp.php#84).
|
||||
* `$_GET['f']` [must not contain "//", "\", or "./"](http://code.google.com/p/minify/source/browse/tags/release_2.1.1/min/lib/Minify/Controller/MinApp.php#64).
|
||||
* There can be [no duplicates](http://code.google.com/p/minify/source/browse/tags/release_2.1.1/min/lib/Minify/Controller/MinApp.php#77) and only a [limited number of files](http://code.google.com/p/minify/source/browse/tags/release_2.1.1/min/config.php#73) can be specified.
|
35
docs/old/VersionTwo.wiki.md
Normal file
35
docs/old/VersionTwo.wiki.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Minify 2
|
||||
|
||||
[Minify 2.0.0](http://code.google.com/p/minify/source/browse/tags/release_2.0.0/) was released May 22, 2008 and represents an architectural redesign of Minify's code and its usage. 2.0 is built as a library of classes allowing users to easily build customized minified-file servers; or add minification, HTTP encoding, or [conditional GET](http://fishbowl.pastiche.org/2002/10/21/http_conditional_get_for_rss_hackers) to existing projects.
|
||||
|
||||
The release includes 3 [example sites](http://code.google.com/p/minify/source/browse/tags/release_2.0.0/web/examples) to demostrate usage and [unit tests](http://code.google.com/p/minify/source/browse/tags/release_2.0.0/web/test/) you can run on your system.
|
||||
|
||||
## Documentation
|
||||
|
||||
Each PHP file is documented, but, for now, the [README file](http://code.google.com/p/minify/source/browse/tags/release_2.0.0/README) is the best reference for getting started with the library. This [blog post](http://mrclay.org/index.php/2008/03/27/minifying-javascript-and-css-on-mrclayorg/) may also give you some ideas.
|
||||
|
||||
The best place for questions is the [minify Google group](http://groups.google.com/group/minify).
|
||||
|
||||
### Included HTTP Classes
|
||||
|
||||
The two HTTP utility classes, [HTTP\_ConditionalGet](http://code.google.com/p/minify/source/browse/lib/HTTP/ConditionalGet.php) and [HTTP\_Encoder](http://code.google.com/p/minify/source/browse/lib/HTTP/Encoder.php), are already fairly well-tested and include a set of test pages to see how they work. On the [Florida Automated Weather Network](http://fawn.ifas.ufl.edu/) site, these are used especially in scripts that serve data to our Flash components.
|
||||
|
||||
Here's an example of using both to conditionally serve a text file gzipped:
|
||||
```
|
||||
$cg = new HTTP_ConditionalGet(array(
|
||||
'lastModifiedTime' => filemtime($filepath)
|
||||
,'isPublic' => true
|
||||
));
|
||||
$cg->sendHeaders();
|
||||
if ($cg->cacheIsValid) {
|
||||
// client cache was valid, no content needed
|
||||
exit();
|
||||
}
|
||||
require 'HTTP/Encoder.php';
|
||||
$he = new HTTP_Encoder(array(
|
||||
'content' => file_get_contents($filepath)
|
||||
,'type' => 'text/plain'
|
||||
));
|
||||
$he->encode();
|
||||
$he->sendAll();
|
||||
```
|
11
example.index.php
Normal file
11
example.index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// template file for creating your own Minify endpoint
|
||||
|
||||
// remove this
|
||||
die('disabled');
|
||||
|
||||
// adjust this path as necessary
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$app = new \Minify\App(__DIR__);
|
||||
$app->runServer();
|
19
groupsConfig.php
Normal file
19
groupsConfig.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* Groups configuration for default Minify implementation
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* You may wish to use the Minify URI Builder app to suggest
|
||||
* changes. http://yourdomain/min/builder/
|
||||
*
|
||||
* See https://github.com/mrclay/minify/blob/master/docs/CustomServer.wiki.md for other ideas
|
||||
**/
|
||||
|
||||
return array(
|
||||
// 'testJs' => array('//minify/quick-test.js'),
|
||||
// 'testCss' => array('//minify/quick-test.css'),
|
||||
// 'js' => array('//js/file1.js', '//js/file2.js'),
|
||||
// 'css' => array('//css/file1.css', '//css/file2.css'),
|
||||
);
|
13
index.php
Normal file
13
index.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Sets up MinApp controller and serves files
|
||||
*
|
||||
* DO NOT EDIT! Configure this utility via config.php and groupsConfig.php
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
$app = (require __DIR__ . '/bootstrap.php');
|
||||
/* @var \Minify\App $app */
|
||||
|
||||
$app->runServer();
|
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Class HTTP_ConditionalGet
|
||||
* Class HTTP_ConditionalGet
|
||||
* @package Minify
|
||||
* @subpackage HTTP
|
||||
*/
|
||||
@@ -21,7 +21,7 @@
|
||||
* }
|
||||
* echo $content;
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* E.g. Shortcut for the above
|
||||
* <code>
|
||||
* HTTP_ConditionalGet::check($updateTime, true); // exits if client has cache
|
||||
@@ -40,7 +40,7 @@
|
||||
* }
|
||||
* echo $content;
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* E.g. Static content with some static includes:
|
||||
* <code>
|
||||
* // before content
|
||||
@@ -60,11 +60,12 @@
|
||||
* @subpackage HTTP
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class HTTP_ConditionalGet {
|
||||
class HTTP_ConditionalGet
|
||||
{
|
||||
|
||||
/**
|
||||
* Does the client have a valid copy of the requested resource?
|
||||
*
|
||||
*
|
||||
* You'll want to check this after instantiating the object. If true, do
|
||||
* not send content, just call sendHeaders() if you haven't already.
|
||||
*
|
||||
@@ -74,36 +75,36 @@ class HTTP_ConditionalGet {
|
||||
|
||||
/**
|
||||
* @param array $spec options
|
||||
*
|
||||
*
|
||||
* 'isPublic': (bool) if false, the Cache-Control header will contain
|
||||
* "private", allowing only browser caching. (default false)
|
||||
*
|
||||
*
|
||||
* 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers
|
||||
* will be sent with content. This is recommended.
|
||||
*
|
||||
* 'encoding': (string) if set, the header "Vary: Accept-Encoding" will
|
||||
* always be sent and a truncated version of the encoding will be appended
|
||||
* to the ETag. E.g. "pub123456;gz". This will also trigger a more lenient
|
||||
* to the ETag. E.g. "pub123456;gz". This will also trigger a more lenient
|
||||
* checking of the client's If-None-Match header, as the encoding portion of
|
||||
* the ETag will be stripped before comparison.
|
||||
*
|
||||
*
|
||||
* 'contentHash': (string) if given, only the ETag header can be sent with
|
||||
* content (only HTTP1.1 clients can conditionally GET). The given string
|
||||
* should be short with no quote characters and always change when the
|
||||
* resource changes (recommend md5()). This is not needed/used if
|
||||
* content (only HTTP1.1 clients can conditionally GET). The given string
|
||||
* should be short with no quote characters and always change when the
|
||||
* resource changes (recommend md5()). This is not needed/used if
|
||||
* lastModifiedTime is given.
|
||||
*
|
||||
*
|
||||
* 'eTag': (string) if given, this will be used as the ETag header rather
|
||||
* than values based on lastModifiedTime or contentHash. Also the encoding
|
||||
* string will not be appended to the given value as described above.
|
||||
*
|
||||
*
|
||||
* 'invalidate': (bool) if true, the client cache will be considered invalid
|
||||
* without testing. Effectively this disables conditional GET.
|
||||
* without testing. Effectively this disables conditional GET.
|
||||
* (default false)
|
||||
*
|
||||
* 'maxAge': (int) if given, this will set the Cache-Control max-age in
|
||||
* seconds, and also set the Expires header to the equivalent GMT date.
|
||||
* After the max-age period has passed, the browser will again send a
|
||||
*
|
||||
* 'maxAge': (int) if given, this will set the Cache-Control max-age in
|
||||
* seconds, and also set the Expires header to the equivalent GMT date.
|
||||
* After the max-age period has passed, the browser will again send a
|
||||
* conditional GET to revalidate its cache.
|
||||
*/
|
||||
public function __construct($spec)
|
||||
@@ -113,7 +114,7 @@ class HTTP_ConditionalGet {
|
||||
: 'private';
|
||||
$maxAge = 0;
|
||||
// backwards compatibility (can be removed later)
|
||||
if (isset($spec['setExpires'])
|
||||
if (isset($spec['setExpires'])
|
||||
&& is_numeric($spec['setExpires'])
|
||||
&& ! isset($spec['maxAge'])) {
|
||||
$spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME'];
|
||||
@@ -121,14 +122,14 @@ class HTTP_ConditionalGet {
|
||||
if (isset($spec['maxAge'])) {
|
||||
$maxAge = $spec['maxAge'];
|
||||
$this->_headers['Expires'] = self::gmtDate(
|
||||
$_SERVER['REQUEST_TIME'] + $spec['maxAge']
|
||||
$_SERVER['REQUEST_TIME'] + $spec['maxAge']
|
||||
);
|
||||
}
|
||||
$etagAppend = '';
|
||||
if (isset($spec['encoding'])) {
|
||||
$this->_stripEtag = true;
|
||||
$this->_headers['Vary'] = 'Accept-Encoding';
|
||||
if ('' !== $spec['encoding']) {
|
||||
$this->_headers['Vary'] = 'Accept-Encoding';
|
||||
if (0 === strpos($spec['encoding'], 'x-')) {
|
||||
$spec['encoding'] = substr($spec['encoding'], 2);
|
||||
}
|
||||
@@ -156,14 +157,14 @@ class HTTP_ConditionalGet {
|
||||
? false
|
||||
: $this->_isCacheValid();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get array of output headers to be sent
|
||||
*
|
||||
*
|
||||
* In the case of 304 responses, this array will only contain the response
|
||||
* code header: array('_responseCode' => 'HTTP/1.0 304 Not Modified')
|
||||
*
|
||||
* Otherwise something like:
|
||||
*
|
||||
* Otherwise something like:
|
||||
* <code>
|
||||
* array(
|
||||
* 'Cache-Control' => 'max-age=0, public'
|
||||
@@ -171,7 +172,7 @@ class HTTP_ConditionalGet {
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @return array
|
||||
* @return array
|
||||
*/
|
||||
public function getHeaders()
|
||||
{
|
||||
@@ -180,13 +181,13 @@ class HTTP_ConditionalGet {
|
||||
|
||||
/**
|
||||
* Set the Content-Length header in bytes
|
||||
*
|
||||
*
|
||||
* With most PHP configs, as long as you don't flush() output, this method
|
||||
* is not needed and PHP will buffer all output and set Content-Length for
|
||||
* is not needed and PHP will buffer all output and set Content-Length for
|
||||
* you. Otherwise you'll want to call this to let the client know up front.
|
||||
*
|
||||
*
|
||||
* @param int $bytes
|
||||
*
|
||||
*
|
||||
* @return int copy of input $bytes
|
||||
*/
|
||||
public function setContentLength($bytes)
|
||||
@@ -196,9 +197,9 @@ class HTTP_ConditionalGet {
|
||||
|
||||
/**
|
||||
* Send headers
|
||||
*
|
||||
*
|
||||
* @see getHeaders()
|
||||
*
|
||||
*
|
||||
* Note this doesn't "clear" the headers. Calling sendHeaders() will
|
||||
* call header() again (but probably have not effect) and getHeaders() will
|
||||
* still return the headers.
|
||||
@@ -218,7 +219,7 @@ class HTTP_ConditionalGet {
|
||||
header($name . ': ' . $val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exit if the client's cache is valid for this resource
|
||||
*
|
||||
@@ -227,8 +228,8 @@ class HTTP_ConditionalGet {
|
||||
* @param int $lastModifiedTime if given, both ETag AND Last-Modified headers
|
||||
* will be sent with content. This is recommended.
|
||||
*
|
||||
* @param bool $isPublic (default false) if true, the Cache-Control header
|
||||
* will contain "public", allowing proxies to cache the content. Otherwise
|
||||
* @param bool $isPublic (default false) if true, the Cache-Control header
|
||||
* will contain "public", allowing proxies to cache the content. Otherwise
|
||||
* "private" will be sent, allowing only browser caching.
|
||||
*
|
||||
* @param array $options (default empty) additional options for constructor
|
||||
@@ -245,24 +246,23 @@ class HTTP_ConditionalGet {
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get a GMT formatted date for use in HTTP headers
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* header('Expires: ' . HTTP_ConditionalGet::gmtdate($time));
|
||||
* </code>
|
||||
* </code>
|
||||
*
|
||||
* @param int $time unix timestamp
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function gmtDate($time)
|
||||
{
|
||||
return gmdate('D, d M Y H:i:s \G\M\T', $time);
|
||||
}
|
||||
|
||||
|
||||
protected $_headers = array();
|
||||
protected $_lmTime = null;
|
||||
protected $_etag = null;
|
||||
@@ -297,7 +297,7 @@ class HTTP_ConditionalGet {
|
||||
{
|
||||
if (null === $this->_etag) {
|
||||
// lmTime is copied to ETag, so this condition implies that the
|
||||
// server sent neither ETag nor Last-Modified, so the client can't
|
||||
// server sent neither ETag nor Last-Modified, so the client can't
|
||||
// possibly has a valid cache.
|
||||
return false;
|
||||
}
|
||||
@@ -305,6 +305,7 @@ class HTTP_ConditionalGet {
|
||||
if ($isValid) {
|
||||
$this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified';
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
@@ -316,20 +317,22 @@ class HTTP_ConditionalGet {
|
||||
if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
|
||||
return false;
|
||||
}
|
||||
$clientEtagList = get_magic_quotes_gpc()
|
||||
$clientEtagList = PHP_VERSION_ID < 50400 && get_magic_quotes_gpc()
|
||||
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
|
||||
: $_SERVER['HTTP_IF_NONE_MATCH'];
|
||||
$clientEtags = explode(',', $clientEtagList);
|
||||
|
||||
|
||||
$compareTo = $this->normalizeEtag($this->_etag);
|
||||
foreach ($clientEtags as $clientEtag) {
|
||||
if ($this->normalizeEtag($clientEtag) === $compareTo) {
|
||||
// respond with the client's matched ETag, even if it's not what
|
||||
// we would've sent by default
|
||||
$this->_headers['ETag'] = trim($clientEtag);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -338,8 +341,10 @@ class HTTP_ConditionalGet {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeEtag($etag) {
|
||||
protected function normalizeEtag($etag)
|
||||
{
|
||||
$etag = trim($etag);
|
||||
|
||||
return $this->_stripEtag
|
||||
? preg_replace('/;\\w\\w"$/', '"', $etag)
|
||||
: $etag;
|
||||
@@ -355,12 +360,17 @@ class HTTP_ConditionalGet {
|
||||
}
|
||||
// strip off IE's extra data (semicolon)
|
||||
list($ifModifiedSince) = explode(';', $_SERVER['HTTP_IF_MODIFIED_SINCE'], 2);
|
||||
if (strtotime($ifModifiedSince) >= $this->_lmTime) {
|
||||
// Apache 2.2's behavior. If there was no ETag match, send the
|
||||
|
||||
$date = new DateTime($ifModifiedSince, new DateTimeZone('UTC'));
|
||||
|
||||
if ($date->getTimestamp() >= $this->_lmTime) {
|
||||
// Apache 2.2's behavior. If there was no ETag match, send the
|
||||
// non-encoded version of the ETag value.
|
||||
$this->_headers['ETag'] = $this->normalizeEtag($this->_etag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,14 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Class HTTP_Encoder
|
||||
* Class HTTP_Encoder
|
||||
* @package Minify
|
||||
* @subpackage HTTP
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Encode and send gzipped/deflated content
|
||||
*
|
||||
* The "Vary: Accept-Encoding" header is sent. If the client allows encoding,
|
||||
* The "Vary: Accept-Encoding" header is sent. If the client allows encoding,
|
||||
* Content-Encoding and Content-Length are added.
|
||||
*
|
||||
* <code>
|
||||
@@ -26,7 +26,7 @@
|
||||
* header('Content-Type: text/css'); // needed if not HTML
|
||||
* HTTP_Encoder::output($css);
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* // Just sniff for the accepted encoding
|
||||
* $encoding = HTTP_Encoder::getAcceptedEncoding();
|
||||
@@ -34,59 +34,58 @@
|
||||
*
|
||||
* For more control over headers, use getHeaders() and getData() and send your
|
||||
* own output.
|
||||
*
|
||||
* Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate,
|
||||
*
|
||||
* Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate,
|
||||
* and gzcompress functions for gzip, deflate, and compress-encoding
|
||||
* respectively.
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
* @subpackage HTTP
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class HTTP_Encoder {
|
||||
class HTTP_Encoder
|
||||
{
|
||||
|
||||
/**
|
||||
* Should the encoder allow HTTP encoding to IE6?
|
||||
*
|
||||
* If you have many IE6 users and the bandwidth savings is worth troubling
|
||||
* Should the encoder allow HTTP encoding to IE6?
|
||||
*
|
||||
* If you have many IE6 users and the bandwidth savings is worth troubling
|
||||
* some of them, set this to true.
|
||||
*
|
||||
*
|
||||
* By default, encoding is only offered to IE7+. When this is true,
|
||||
* getAcceptedEncoding() will return an encoding for IE6 if its user agent
|
||||
* string contains "SV1". This has been documented in many places as "safe",
|
||||
* but there seem to be remaining, intermittent encoding bugs in patched
|
||||
* but there seem to be remaining, intermittent encoding bugs in patched
|
||||
* IE6 on the wild web.
|
||||
*
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $encodeToIe6 = true;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Default compression level for zlib operations
|
||||
*
|
||||
*
|
||||
* This level is used if encode() is not given a $compressionLevel
|
||||
*
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $compressionLevel = 6;
|
||||
|
||||
|
||||
/**
|
||||
* Get an HTTP Encoder object
|
||||
*
|
||||
*
|
||||
* @param array $spec options
|
||||
*
|
||||
*
|
||||
* 'content': (string required) content to be encoded
|
||||
*
|
||||
*
|
||||
* 'type': (string) if set, the Content-Type header will have this value.
|
||||
*
|
||||
*
|
||||
* 'method: (string) only set this if you are forcing a particular encoding
|
||||
* method. If not set, the best method will be chosen by getAcceptedEncoding()
|
||||
* The available methods are 'gzip', 'deflate', 'compress', and '' (no
|
||||
* encoding)
|
||||
*/
|
||||
public function __construct($spec)
|
||||
public function __construct($spec)
|
||||
{
|
||||
$this->_useMbStrlen = (function_exists('mb_strlen')
|
||||
&& (ini_get('mbstring.func_overload') !== '')
|
||||
@@ -99,8 +98,7 @@ class HTTP_Encoder {
|
||||
$this->_headers['Content-Type'] = $spec['type'];
|
||||
}
|
||||
if (isset($spec['method'])
|
||||
&& in_array($spec['method'], array('gzip', 'deflate', 'compress', '')))
|
||||
{
|
||||
&& in_array($spec['method'], array('gzip', 'deflate', 'compress', ''))) {
|
||||
$this->_encodeMethod = array($spec['method'], $spec['method']);
|
||||
} else {
|
||||
$this->_encodeMethod = self::getAcceptedEncoding();
|
||||
@@ -109,19 +107,19 @@ class HTTP_Encoder {
|
||||
|
||||
/**
|
||||
* Get content in current form
|
||||
*
|
||||
*
|
||||
* Call after encode() for encoded content.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
public function getContent()
|
||||
{
|
||||
return $this->_content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get array of output headers to be sent
|
||||
*
|
||||
*
|
||||
* E.g.
|
||||
* <code>
|
||||
* array(
|
||||
@@ -131,7 +129,7 @@ class HTTP_Encoder {
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* @return array
|
||||
* @return array
|
||||
*/
|
||||
public function getHeaders()
|
||||
{
|
||||
@@ -140,11 +138,11 @@ class HTTP_Encoder {
|
||||
|
||||
/**
|
||||
* Send output headers
|
||||
*
|
||||
*
|
||||
* You must call this before headers are sent and it probably cannot be
|
||||
* used in conjunction with zlib output buffering / mod_gzip. Errors are
|
||||
* not handled purposefully.
|
||||
*
|
||||
*
|
||||
* @see getHeaders()
|
||||
*/
|
||||
public function sendHeaders()
|
||||
@@ -153,10 +151,10 @@ class HTTP_Encoder {
|
||||
header($name . ': ' . $val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send output headers and content
|
||||
*
|
||||
*
|
||||
* A shortcut for sendHeaders() and echo getContent()
|
||||
*
|
||||
* You must call this before headers are sent and it probably cannot be
|
||||
@@ -170,21 +168,21 @@ class HTTP_Encoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the client's best encoding method from the HTTP Accept-Encoding
|
||||
* Determine the client's best encoding method from the HTTP Accept-Encoding
|
||||
* header.
|
||||
*
|
||||
*
|
||||
* If no Accept-Encoding header is set, or the browser is IE before v6 SP2,
|
||||
* this will return ('', ''), the "identity" encoding.
|
||||
*
|
||||
*
|
||||
* A syntax-aware scan is done of the Accept-Encoding, so the method must
|
||||
* be non 0. The methods are favored in order of gzip, deflate, then
|
||||
* compress. Deflate is always smallest and generally faster, but is
|
||||
* be non 0. The methods are favored in order of gzip, deflate, then
|
||||
* compress. Deflate is always smallest and generally faster, but is
|
||||
* rarely sent by servers, so client support could be buggier.
|
||||
*
|
||||
*
|
||||
* @param bool $allowCompress allow the older compress encoding
|
||||
*
|
||||
*
|
||||
* @param bool $allowDeflate allow the more recent deflate encoding
|
||||
*
|
||||
*
|
||||
* @return array two values, 1st is the actual encoding method, 2nd is the
|
||||
* alias of that method to use in the Content-Encoding header (some browsers
|
||||
* call gzip "x-gzip" etc.)
|
||||
@@ -192,10 +190,9 @@ class HTTP_Encoder {
|
||||
public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
|
||||
{
|
||||
// @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||
|
||||
|
||||
if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])
|
||||
|| self::isBuggyIe())
|
||||
{
|
||||
|| self::isBuggyIe()) {
|
||||
return array('', '');
|
||||
}
|
||||
$ae = $_SERVER['HTTP_ACCEPT_ENCODING'];
|
||||
@@ -207,47 +204,52 @@ class HTTP_Encoder {
|
||||
}
|
||||
// gzip checks (slow)
|
||||
if (preg_match(
|
||||
'@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
|
||||
,$ae
|
||||
,$m)) {
|
||||
'@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@',
|
||||
$ae,
|
||||
$m
|
||||
)) {
|
||||
return array('gzip', $m[1]);
|
||||
}
|
||||
if ($allowDeflate) {
|
||||
// deflate checks
|
||||
// deflate checks
|
||||
$aeRev = strrev($ae);
|
||||
if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit
|
||||
|| 0 === strpos($aeRev, 'etalfed,') // gecko
|
||||
|| 0 === strpos($ae, 'deflate,') // opera
|
||||
// slow parsing
|
||||
|| preg_match(
|
||||
'@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) {
|
||||
'@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@',
|
||||
$ae
|
||||
)) {
|
||||
return array('deflate', 'deflate');
|
||||
}
|
||||
}
|
||||
if ($allowCompress && preg_match(
|
||||
'@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
|
||||
,$ae
|
||||
,$m)) {
|
||||
'@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@',
|
||||
$ae,
|
||||
$m
|
||||
)) {
|
||||
return array('compress', $m[1]);
|
||||
}
|
||||
|
||||
return array('', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode (compress) the content
|
||||
*
|
||||
*
|
||||
* If the encode method is '' (none) or compression level is 0, or the 'zlib'
|
||||
* extension isn't loaded, we return false.
|
||||
*
|
||||
*
|
||||
* Then the appropriate gz_* function is called to compress the content. If
|
||||
* this fails, false is returned.
|
||||
*
|
||||
* The header "Vary: Accept-Encoding" is added. If encoding is successful,
|
||||
*
|
||||
* The header "Vary: Accept-Encoding" is added. If encoding is successful,
|
||||
* the Content-Length header is updated, and Content-Encoding is also added.
|
||||
*
|
||||
*
|
||||
* @param int $compressionLevel given to zlib functions. If not given, the
|
||||
* class default will be used.
|
||||
*
|
||||
*
|
||||
* @return bool success true if the content was actually compressed
|
||||
*/
|
||||
public function encode($compressionLevel = null)
|
||||
@@ -260,8 +262,7 @@ class HTTP_Encoder {
|
||||
}
|
||||
if ('' === $this->_encodeMethod[0]
|
||||
|| ($compressionLevel == 0)
|
||||
|| !extension_loaded('zlib'))
|
||||
{
|
||||
|| !extension_loaded('zlib')) {
|
||||
return false;
|
||||
}
|
||||
if ($this->_encodeMethod[0] === 'deflate') {
|
||||
@@ -279,19 +280,20 @@ class HTTP_Encoder {
|
||||
: (string)strlen($encoded);
|
||||
$this->_headers['Content-Encoding'] = $this->_encodeMethod[1];
|
||||
$this->_content = $encoded;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Encode and send appropriate headers and content
|
||||
*
|
||||
* This is a convenience method for common use of the class
|
||||
*
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
*
|
||||
* @param int $compressionLevel given to zlib functions. If not given, the
|
||||
* class default will be used.
|
||||
*
|
||||
*
|
||||
* @return bool success true if the content was actually compressed
|
||||
*/
|
||||
public static function output($content, $compressionLevel = null)
|
||||
@@ -302,6 +304,7 @@ class HTTP_Encoder {
|
||||
$he = new HTTP_Encoder(array('content' => $content));
|
||||
$ret = $he->encode($compressionLevel);
|
||||
$he->sendAll();
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
@@ -323,11 +326,12 @@ class HTTP_Encoder {
|
||||
}
|
||||
// no regex = faaast
|
||||
$version = (float)substr($ua, 30);
|
||||
|
||||
return self::$encodeToIe6
|
||||
? ($version < 6 || ($version == 6 && false === strpos($ua, 'SV1')))
|
||||
: ($version < 7);
|
||||
}
|
||||
|
||||
|
||||
protected $_content = '';
|
||||
protected $_headers = array();
|
||||
protected $_encodeMethod = array('', '');
|
761
lib/Minify.php
Normal file
761
lib/Minify.php
Normal file
@@ -0,0 +1,761 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Minify - Combines, minifies, and caches JavaScript and CSS files on demand.
|
||||
*
|
||||
* See README for usage instructions (for now).
|
||||
*
|
||||
* This library was inspired by {@link mailto:flashkot@mail.ru jscsscomp by Maxim Martynyuk}
|
||||
* and by the article {@link http://www.hunlock.com/blogs/Supercharged_Javascript "Supercharged JavaScript" by Patrick Hunlock}.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Ryan Grove <ryan@wonko.com>
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved.
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @link https://github.com/mrclay/minify
|
||||
*/
|
||||
class Minify
|
||||
{
|
||||
|
||||
/**
|
||||
* API version
|
||||
*
|
||||
* This is only bumped when API breaks are done and should follow the major version of the library
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const VERSION = 3;
|
||||
|
||||
const TYPE_CSS = 'text/css';
|
||||
const TYPE_HTML = 'text/html';
|
||||
// there is some debate over the ideal JS Content-Type, but this is the
|
||||
// Apache default and what Yahoo! uses..
|
||||
const TYPE_JS = 'application/x-javascript';
|
||||
const URL_DEBUG = 'https://github.com/mrclay/minify/blob/master/docs/Debugging.wiki.md';
|
||||
|
||||
/**
|
||||
* Any Minify_Cache_* object or null (i.e. no server cache is used)
|
||||
*
|
||||
* @var Minify_CacheInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* Active controller for current request
|
||||
*
|
||||
* @var Minify_Controller_Base
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @var Minify_Env
|
||||
*/
|
||||
protected $env;
|
||||
|
||||
/**
|
||||
* @var Minify_SourceInterface[]
|
||||
*/
|
||||
protected $sources;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $selectionId;
|
||||
|
||||
/**
|
||||
* Options for current request
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface|null
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @param Minify_CacheInterface $cache
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(Minify_CacheInterface $cache, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default Minify options.
|
||||
*
|
||||
* @return array options for Minify
|
||||
*/
|
||||
public function getDefaultOptions()
|
||||
{
|
||||
return array(
|
||||
'isPublic' => true,
|
||||
'encodeOutput' => function_exists('gzdeflate'),
|
||||
'encodeMethod' => null, // determine later
|
||||
'encodeLevel' => 9,
|
||||
|
||||
'minifiers' => array(
|
||||
Minify::TYPE_JS => array('JSMin\\JSMin', 'minify'),
|
||||
Minify::TYPE_CSS => array('Minify_CSSmin', 'minify'),
|
||||
Minify::TYPE_HTML => array('Minify_HTML', 'minify'),
|
||||
),
|
||||
'minifierOptions' => array(), // no minifier options
|
||||
|
||||
'contentTypeCharset' => 'utf-8',
|
||||
'maxAge' => 1800, // 30 minutes
|
||||
'rewriteCssUris' => true,
|
||||
'bubbleCssImports' => false,
|
||||
'quiet' => false, // serve() will send headers and output
|
||||
'debug' => false,
|
||||
'concatOnly' => false,
|
||||
'invalidate' => false,
|
||||
|
||||
// if you override these, the response codes MUST be directly after
|
||||
// the first space.
|
||||
'badRequestHeader' => 'HTTP/1.0 400 Bad Request',
|
||||
'errorHeader' => 'HTTP/1.0 500 Internal Server Error',
|
||||
|
||||
// callback function to see/modify content of all sources
|
||||
'postprocessor' => null,
|
||||
// file to require to load preprocessor
|
||||
'postprocessorRequire' => null,
|
||||
|
||||
/**
|
||||
* If this string is not empty AND the serve() option 'bubbleCssImports' is
|
||||
* NOT set, then serve() will check CSS files for @import declarations that
|
||||
* appear too late in the combined stylesheet. If found, serve() will prepend
|
||||
* the output with this warning.
|
||||
*/
|
||||
'importWarning' => "/* See https://github.com/mrclay/minify/blob/master/docs/CommonProblems.wiki.md#imports-can-appear-in-invalid-locations-in-combined-css-files */\n"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve a request for a minified file.
|
||||
*
|
||||
* Here are the available options and defaults:
|
||||
*
|
||||
* 'isPublic' : send "public" instead of "private" in Cache-Control
|
||||
* headers, allowing shared caches to cache the output. (default true)
|
||||
*
|
||||
* 'quiet' : set to true to have serve() return an array rather than sending
|
||||
* any headers/output (default false)
|
||||
*
|
||||
* 'encodeOutput' : set to false to disable content encoding, and not send
|
||||
* the Vary header (default true)
|
||||
*
|
||||
* 'encodeMethod' : generally you should let this be determined by
|
||||
* HTTP_Encoder (leave null), but you can force a particular encoding
|
||||
* to be returned, by setting this to 'gzip' or '' (no encoding)
|
||||
*
|
||||
* 'encodeLevel' : level of encoding compression (0 to 9, default 9)
|
||||
*
|
||||
* 'contentTypeCharset' : appended to the Content-Type header sent. Set to a falsey
|
||||
* value to remove. (default 'utf-8')
|
||||
*
|
||||
* 'maxAge' : set this to the number of seconds the client should use its cache
|
||||
* before revalidating with the server. This sets Cache-Control: max-age and the
|
||||
* Expires header. Unlike the old 'setExpires' setting, this setting will NOT
|
||||
* prevent conditional GETs. Note this has nothing to do with server-side caching.
|
||||
*
|
||||
* 'rewriteCssUris' : If true, serve() will automatically set the 'currentDir'
|
||||
* minifier option to enable URI rewriting in CSS files (default true)
|
||||
*
|
||||
* 'bubbleCssImports' : If true, all @import declarations in combined CSS
|
||||
* files will be move to the top. Note this may alter effective CSS values
|
||||
* due to a change in order. (default false)
|
||||
*
|
||||
* 'debug' : set to true to minify all sources with the 'Lines' controller, which
|
||||
* eases the debugging of combined files. This also prevents 304 responses.
|
||||
* @see Minify_Lines::minify()
|
||||
*
|
||||
* 'concatOnly' : set to true to disable minification and simply concatenate the files.
|
||||
* For JS, no minifier will be used. For CSS, only URI rewriting is still performed.
|
||||
*
|
||||
* 'minifiers' : to override Minify's default choice of minifier function for
|
||||
* a particular content-type, specify your callback under the key of the
|
||||
* content-type:
|
||||
* <code>
|
||||
* // call customCssMinifier($css) for all CSS minification
|
||||
* $options['minifiers'][Minify::TYPE_CSS] = 'customCssMinifier';
|
||||
*
|
||||
* // don't minify Javascript at all
|
||||
* $options['minifiers'][Minify::TYPE_JS] = 'Minify::nullMinifier';
|
||||
* </code>
|
||||
*
|
||||
* 'minifierOptions' : to send options to the minifier function, specify your options
|
||||
* under the key of the content-type. E.g. To send the CSS minifier an option:
|
||||
* <code>
|
||||
* // give CSS minifier array('optionName' => 'optionValue') as 2nd argument
|
||||
* $options['minifierOptions'][Minify::TYPE_CSS]['optionName'] = 'optionValue';
|
||||
* </code>
|
||||
*
|
||||
* 'contentType' : (optional) this is only needed if your file extension is not
|
||||
* js/css/html. The given content-type will be sent regardless of source file
|
||||
* extension, so this should not be used in a Groups config with other
|
||||
* Javascript/CSS files.
|
||||
*
|
||||
* 'importWarning' : serve() will check CSS files for @import declarations that
|
||||
* appear too late in the combined stylesheet. If found, serve() will prepend
|
||||
* the output with this warning. To disable this, set this option to empty string.
|
||||
*
|
||||
* Any controller options are documented in that controller's createConfiguration() method.
|
||||
*
|
||||
* @param Minify_ControllerInterface $controller instance of subclass of Minify_Controller_Base
|
||||
*
|
||||
* @param array $options controller/serve options
|
||||
*
|
||||
* @return null|array if the 'quiet' option is set to true, an array
|
||||
* with keys "success" (bool), "statusCode" (int), "content" (string), and
|
||||
* "headers" (array).
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function serve(Minify_ControllerInterface $controller, $options = array())
|
||||
{
|
||||
$this->env = $controller->getEnv();
|
||||
|
||||
$options = array_merge($this->getDefaultOptions(), $options);
|
||||
|
||||
$config = $controller->createConfiguration($options);
|
||||
|
||||
$this->sources = $config->getSources();
|
||||
$this->selectionId = $config->getSelectionId();
|
||||
$this->options = $this->analyzeSources($config->getOptions());
|
||||
|
||||
if (!$this->options['quiet'] && !headers_sent()) {
|
||||
ini_set('zlib.output_compression', '0');
|
||||
}
|
||||
|
||||
// check request validity
|
||||
if (!$this->sources) {
|
||||
// invalid request!
|
||||
if (! $this->options['quiet']) {
|
||||
$this->errorExit($this->options['badRequestHeader'], self::URL_DEBUG);
|
||||
} else {
|
||||
list(, $statusCode) = explode(' ', $this->options['badRequestHeader']);
|
||||
|
||||
return array(
|
||||
'success' => false,
|
||||
'statusCode' => (int)$statusCode,
|
||||
'content' => '',
|
||||
'headers' => array(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->controller = $controller;
|
||||
|
||||
if ($this->options['debug']) {
|
||||
$this->setupDebug();
|
||||
$this->options['maxAge'] = 0;
|
||||
}
|
||||
|
||||
// determine encoding
|
||||
if ($this->options['encodeOutput']) {
|
||||
$sendVary = true;
|
||||
if ($this->options['encodeMethod'] !== null) {
|
||||
// controller specifically requested this
|
||||
$contentEncoding = $this->options['encodeMethod'];
|
||||
} else {
|
||||
// sniff request header
|
||||
// depending on what the client accepts, $contentEncoding may be
|
||||
// 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
|
||||
// getAcceptedEncoding(false, false) leaves out compress and deflate as options.
|
||||
$list = HTTP_Encoder::getAcceptedEncoding(false, false);
|
||||
list($this->options['encodeMethod'], $contentEncoding) = $list;
|
||||
$sendVary = ! HTTP_Encoder::isBuggyIe();
|
||||
}
|
||||
} else {
|
||||
$this->options['encodeMethod'] = ''; // identity (no encoding)
|
||||
}
|
||||
|
||||
// check client cache
|
||||
$cgOptions = array(
|
||||
'lastModifiedTime' => $this->options['lastModifiedTime'],
|
||||
'isPublic' => $this->options['isPublic'],
|
||||
'encoding' => $this->options['encodeMethod'],
|
||||
'invalidate' => $this->options['invalidate'],
|
||||
);
|
||||
|
||||
if ($this->options['maxAge'] > 0) {
|
||||
$cgOptions['maxAge'] = $this->options['maxAge'];
|
||||
} elseif ($this->options['debug']) {
|
||||
$cgOptions['invalidate'] = true;
|
||||
}
|
||||
|
||||
$cg = new HTTP_ConditionalGet($cgOptions);
|
||||
if ($cg->cacheIsValid) {
|
||||
// client's cache is valid
|
||||
if (! $this->options['quiet']) {
|
||||
$cg->sendHeaders();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'statusCode' => 304,
|
||||
'content' => '',
|
||||
'headers' => $cg->getHeaders(),
|
||||
);
|
||||
}
|
||||
|
||||
// client will need output
|
||||
$headers = $cg->getHeaders();
|
||||
unset($cg);
|
||||
|
||||
if ($this->options['contentType'] === self::TYPE_CSS && $this->options['rewriteCssUris']) {
|
||||
$this->setupUriRewrites();
|
||||
}
|
||||
|
||||
if ($this->options['concatOnly']) {
|
||||
$this->options['minifiers'][self::TYPE_JS] = false;
|
||||
foreach ($this->sources as $key => $source) {
|
||||
if ($this->options['contentType'] === self::TYPE_JS) {
|
||||
$source->setMinifier('Minify::nullMinifier');
|
||||
} elseif ($this->options['contentType'] === self::TYPE_CSS) {
|
||||
$source->setMinifier(array('Minify_CSSmin', 'minify'));
|
||||
$sourceOpts = $source->getMinifierOptions();
|
||||
$sourceOpts['compress'] = false;
|
||||
$source->setMinifierOptions($sourceOpts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check server cache
|
||||
if (! $this->options['debug']) {
|
||||
// using cache
|
||||
// the goal is to use only the cache methods to sniff the length and
|
||||
// output the content, as they do not require ever loading the file into
|
||||
// memory.
|
||||
$cacheId = $this->_getCacheId();
|
||||
$fullCacheId = ($this->options['encodeMethod']) ? $cacheId . '.gz' : $cacheId;
|
||||
|
||||
// check cache for valid entry
|
||||
$cacheIsReady = $this->cache->isValid($fullCacheId, $this->options['lastModifiedTime']);
|
||||
if ($cacheIsReady) {
|
||||
$cacheContentLength = $this->cache->getSize($fullCacheId);
|
||||
} else {
|
||||
// generate & cache content
|
||||
try {
|
||||
$content = $this->combineMinify();
|
||||
} catch (Exception $e) {
|
||||
$this->logger && $this->logger->critical($e->getMessage());
|
||||
if (! $this->options['quiet']) {
|
||||
$this->errorExit($this->options['errorHeader'], self::URL_DEBUG);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
$this->cache->store($cacheId, $content);
|
||||
if (function_exists('gzencode') && $this->options['encodeMethod']) {
|
||||
$this->cache->store($cacheId . '.gz', gzencode($content, $this->options['encodeLevel']));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no cache
|
||||
$cacheIsReady = false;
|
||||
try {
|
||||
$content = $this->combineMinify();
|
||||
} catch (Exception $e) {
|
||||
$this->logger && $this->logger->critical($e->getMessage());
|
||||
if (! $this->options['quiet']) {
|
||||
$this->errorExit($this->options['errorHeader'], self::URL_DEBUG);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
if (! $cacheIsReady && $this->options['encodeMethod']) {
|
||||
// still need to encode
|
||||
$content = gzencode($content, $this->options['encodeLevel']);
|
||||
}
|
||||
|
||||
// add headers
|
||||
if ($cacheIsReady) {
|
||||
$headers['Content-Length'] = $cacheContentLength;
|
||||
} else {
|
||||
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
|
||||
$headers['Content-Length'] = mb_strlen($content, '8bit');
|
||||
} else {
|
||||
$headers['Content-Length'] = strlen($content);
|
||||
}
|
||||
}
|
||||
|
||||
$headers['Content-Type'] = $this->options['contentType'];
|
||||
if ($this->options['contentTypeCharset']) {
|
||||
$headers['Content-Type'] .= '; charset=' . $this->options['contentTypeCharset'];
|
||||
}
|
||||
|
||||
if ($this->options['encodeMethod'] !== '') {
|
||||
$headers['Content-Encoding'] = $contentEncoding;
|
||||
}
|
||||
if ($this->options['encodeOutput'] && $sendVary) {
|
||||
$headers['Vary'] = 'Accept-Encoding';
|
||||
}
|
||||
|
||||
if (! $this->options['quiet']) {
|
||||
// output headers & content
|
||||
foreach ($headers as $name => $val) {
|
||||
header($name . ': ' . $val);
|
||||
}
|
||||
if ($cacheIsReady) {
|
||||
$this->cache->display($fullCacheId);
|
||||
} else {
|
||||
echo $content;
|
||||
}
|
||||
} else {
|
||||
return array(
|
||||
'success' => true,
|
||||
'statusCode' => 200,
|
||||
'content' => $cacheIsReady ? $this->cache->fetch($fullCacheId) : $content,
|
||||
'headers' => $headers,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return combined minified content for a set of sources
|
||||
*
|
||||
* No internal caching will be used and the content will not be HTTP encoded.
|
||||
*
|
||||
* @param array $sources array of filepaths and/or Minify_Source objects
|
||||
*
|
||||
* @param array $options (optional) array of options for serve.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function combine($sources, $options = array())
|
||||
{
|
||||
$tmpCache = $this->cache;
|
||||
$this->cache = new Minify_Cache_Null();
|
||||
|
||||
$env = new Minify_Env();
|
||||
$sourceFactory = new Minify_Source_Factory($env, array(
|
||||
'checkAllowDirs' => false,
|
||||
), $this->cache);
|
||||
$controller = new Minify_Controller_Files($env, $sourceFactory, $this->logger);
|
||||
|
||||
$options = array_merge($options, array(
|
||||
'files' => (array)$sources,
|
||||
'quiet' => true,
|
||||
'encodeMethod' => '',
|
||||
'lastModifiedTime' => 0,
|
||||
));
|
||||
$out = $this->serve($controller, $options);
|
||||
|
||||
$this->cache = $tmpCache;
|
||||
|
||||
return $out['content'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error page
|
||||
*
|
||||
* @param string $header Full header. E.g. 'HTTP/1.0 500 Internal Server Error'
|
||||
* @param string $url URL to direct the user to
|
||||
* @param string $msgHtml HTML message for the client
|
||||
*
|
||||
* @return void
|
||||
* @internal This is not part of the public API and is subject to change
|
||||
* @access private
|
||||
*/
|
||||
public function errorExit($header, $url = '', $msgHtml = '')
|
||||
{
|
||||
$url = htmlspecialchars($url, ENT_QUOTES);
|
||||
list(, $h1) = explode(' ', $header, 2);
|
||||
$h1 = htmlspecialchars($h1);
|
||||
// FastCGI environments require 3rd arg to header() to be set
|
||||
list(, $code) = explode(' ', $header, 3);
|
||||
header($header, true, $code);
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo "<h1>$h1</h1>";
|
||||
if ($msgHtml) {
|
||||
echo $msgHtml;
|
||||
}
|
||||
if ($url) {
|
||||
echo "<p>Please see <a href='$url'>$url</a>.</p>";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default minifier for .min or -min JS files.
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
public static function nullMinifier($content)
|
||||
{
|
||||
if (isset($content[0]) && $content[0] === "\xef") {
|
||||
$content = substr($content, 3);
|
||||
}
|
||||
$content = str_replace("\r\n", "\n", $content);
|
||||
|
||||
return trim($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup CSS sources for URI rewriting
|
||||
*/
|
||||
protected function setupUriRewrites()
|
||||
{
|
||||
foreach ($this->sources as $key => $source) {
|
||||
$file = $this->env->normalizePath($source->getFilePath());
|
||||
$minifyOptions = $source->getMinifierOptions();
|
||||
|
||||
if ($file
|
||||
&& !isset($minifyOptions['currentDir'])
|
||||
&& !isset($minifyOptions['prependRelativePath'])) {
|
||||
$minifyOptions['currentDir'] = dirname($file);
|
||||
$source->setMinifierOptions($minifyOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up sources to use Minify_Lines
|
||||
*/
|
||||
protected function setupDebug()
|
||||
{
|
||||
foreach ($this->sources as $source) {
|
||||
$source->setMinifier(array('Minify_Lines', 'minify'));
|
||||
$id = $source->getId();
|
||||
$source->setMinifierOptions(array(
|
||||
'id' => (is_file($id) ? basename($id) : $id),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines sources and minifies the result.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function combineMinify()
|
||||
{
|
||||
$type = $this->options['contentType']; // ease readability
|
||||
|
||||
// when combining scripts, make sure all statements separated and
|
||||
// trailing single line comment is terminated
|
||||
$implodeSeparator = ($type === self::TYPE_JS) ? "\n;" : '';
|
||||
|
||||
// allow the user to pass a particular array of options to each
|
||||
// minifier (designated by type). source objects may still override
|
||||
// these
|
||||
if (isset($this->options['minifierOptions'][$type])) {
|
||||
$defaultOptions = $this->options['minifierOptions'][$type];
|
||||
} else {
|
||||
$defaultOptions = array();
|
||||
}
|
||||
|
||||
// if minifier not set, default is no minification. source objects
|
||||
// may still override this
|
||||
if (isset($this->options['minifiers'][$type])) {
|
||||
$defaultMinifier = $this->options['minifiers'][$type];
|
||||
} else {
|
||||
$defaultMinifier = false;
|
||||
}
|
||||
|
||||
// process groups of sources with identical minifiers/options
|
||||
$content = array();
|
||||
$i = 0;
|
||||
$l = count($this->sources);
|
||||
$groupToProcessTogether = array();
|
||||
$lastMinifier = null;
|
||||
$lastOptions = null;
|
||||
do {
|
||||
// get next source
|
||||
$source = null;
|
||||
if ($i < $l) {
|
||||
$source = $this->sources[$i];
|
||||
$sourceContent = $source->getContent();
|
||||
|
||||
// allow the source to override our minifier and options
|
||||
$minifier = $source->getMinifier();
|
||||
if (!$minifier) {
|
||||
$minifier = $defaultMinifier;
|
||||
}
|
||||
$options = array_merge($defaultOptions, $source->getMinifierOptions());
|
||||
}
|
||||
// do we need to process our group right now?
|
||||
if ($i > 0 // yes, we have at least the first group populated
|
||||
&& (
|
||||
! $source // yes, we ran out of sources
|
||||
|| $type === self::TYPE_CSS // yes, to process CSS individually (avoiding PCRE bugs/limits)
|
||||
|| $minifier !== $lastMinifier // yes, minifier changed
|
||||
|| $options !== $lastOptions // yes, options changed
|
||||
)) {
|
||||
// minify previous sources with last settings
|
||||
$imploded = implode($implodeSeparator, $groupToProcessTogether);
|
||||
$groupToProcessTogether = array();
|
||||
if ($lastMinifier) {
|
||||
try {
|
||||
$content[] = call_user_func($lastMinifier, $imploded, $lastOptions);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception("Exception in minifier: " . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
$content[] = $imploded;
|
||||
}
|
||||
}
|
||||
// add content to the group
|
||||
if ($source) {
|
||||
$groupToProcessTogether[] = $sourceContent;
|
||||
$lastMinifier = $minifier;
|
||||
$lastOptions = $options;
|
||||
}
|
||||
$i++;
|
||||
} while ($source);
|
||||
|
||||
$content = implode($implodeSeparator, $content);
|
||||
|
||||
if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) {
|
||||
$content = $this->handleCssImports($content);
|
||||
}
|
||||
|
||||
// do any post-processing (esp. for editing build URIs)
|
||||
if ($this->options['postprocessorRequire']) {
|
||||
require_once $this->options['postprocessorRequire'];
|
||||
}
|
||||
if ($this->options['postprocessor']) {
|
||||
$content = call_user_func($this->options['postprocessor'], $content, $type);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a unique cache id for for this request.
|
||||
*
|
||||
* Any settings that could affect output are taken into consideration
|
||||
*
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _getCacheId($prefix = 'minify')
|
||||
{
|
||||
$name = preg_replace('/[^a-zA-Z0-9\\.=_,]/', '', $this->selectionId);
|
||||
$name = preg_replace('/\\.+/', '.', $name);
|
||||
$name = substr($name, 0, 100 - 34 - strlen($prefix));
|
||||
$md5 = md5(serialize(array(
|
||||
Minify_SourceSet::getDigest($this->sources),
|
||||
$this->options['minifiers'],
|
||||
$this->options['minifierOptions'],
|
||||
$this->options['postprocessor'],
|
||||
$this->options['bubbleCssImports'],
|
||||
Minify::VERSION,
|
||||
)));
|
||||
|
||||
return "{$prefix}_{$name}_{$md5}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble CSS @imports to the top or prepend a warning if an import is detected not at the top.
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function handleCssImports($css)
|
||||
{
|
||||
if ($this->options['bubbleCssImports']) {
|
||||
// bubble CSS imports
|
||||
preg_match_all('/@import.*?;/', $css, $imports);
|
||||
$css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css);
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
if ('' === $this->options['importWarning']) {
|
||||
return $css;
|
||||
}
|
||||
|
||||
// remove comments so we don't mistake { in a comment as a block
|
||||
$noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css);
|
||||
$lastImportPos = strrpos($noCommentCss, '@import');
|
||||
$firstBlockPos = strpos($noCommentCss, '{');
|
||||
if (false !== $lastImportPos
|
||||
&& false !== $firstBlockPos
|
||||
&& $firstBlockPos < $lastImportPos
|
||||
) {
|
||||
// { appears before @import : prepend warning
|
||||
$css = $this->options['importWarning'] . $css;
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze sources (if there are any) and set $options 'contentType'
|
||||
* and 'lastModifiedTime' if they already aren't.
|
||||
*
|
||||
* @param array $options options for Minify
|
||||
*
|
||||
* @return array options for Minify
|
||||
*/
|
||||
protected function analyzeSources($options = array())
|
||||
{
|
||||
if (!$this->sources) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
$type = null;
|
||||
foreach ($this->sources as $source) {
|
||||
$sourceType = $source->getContentType();
|
||||
|
||||
if (!empty($options['contentType'])) {
|
||||
// just verify sources have null content type or match the options
|
||||
if ($sourceType !== null && $sourceType !== $options['contentType']) {
|
||||
$this->logger && $this->logger->warning("ContentType mismatch: '{$sourceType}' != '{$options['contentType']}'");
|
||||
|
||||
$this->sources = array();
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === null) {
|
||||
$type = $sourceType;
|
||||
} elseif ($sourceType !== $type) {
|
||||
$this->logger && $this->logger->warning("ContentType mismatch: '{$sourceType}' != '{$type}'");
|
||||
|
||||
$this->sources = array();
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options['contentType'])) {
|
||||
if (null === $type) {
|
||||
$type = 'text/plain';
|
||||
}
|
||||
$options['contentType'] = $type;
|
||||
}
|
||||
|
||||
// last modified is needed for caching, even if setExpires is set
|
||||
if (!isset($options['lastModifiedTime'])) {
|
||||
$max = 0;
|
||||
foreach ($this->sources as $source) {
|
||||
$max = max($source->getLastModified(), $max);
|
||||
}
|
||||
$options['lastModifiedTime'] = $max;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
299
lib/Minify/App.php
Normal file
299
lib/Minify/App.php
Normal file
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
namespace Minify;
|
||||
|
||||
use Minify_Cache_File;
|
||||
use Minify_CacheInterface;
|
||||
use Minify_Controller_MinApp;
|
||||
use Minify_ControllerInterface;
|
||||
use Minify_DebugDetector;
|
||||
use Minify_Env;
|
||||
use Minify_Source_Factory;
|
||||
use Props\Container;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use Monolog;
|
||||
use Minify;
|
||||
|
||||
/**
|
||||
* @property Minify_CacheInterface $cache
|
||||
* @property Config $config
|
||||
* @property string $configPath
|
||||
* @property Minify_ControllerInterface $controller
|
||||
* @property string $dir
|
||||
* @property string $docRoot
|
||||
* @property Minify_Env $env
|
||||
* @property Monolog\Handler\ErrorLogHandler $errorLogHandler
|
||||
* @property array $groupsConfig
|
||||
* @property string $groupsConfigPath
|
||||
* @property LoggerInterface $logger
|
||||
* @property \Minify $minify
|
||||
* @property array $serveOptions
|
||||
* @property Minify_Source_Factory $sourceFactory
|
||||
* @property array $sourceFactoryOptions
|
||||
*/
|
||||
class App extends Container
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $dir Directory containing config files
|
||||
*/
|
||||
public function __construct($dir)
|
||||
{
|
||||
$that = $this;
|
||||
|
||||
$this->dir = rtrim($dir, '/\\');
|
||||
|
||||
$this->cache = function (App $app) use ($that) {
|
||||
$config = $app->config;
|
||||
|
||||
if ($config->cachePath instanceof Minify_CacheInterface) {
|
||||
return $config->cachePath;
|
||||
}
|
||||
|
||||
if (!$config->cachePath || is_string($config->cachePath)) {
|
||||
return new Minify_Cache_File($config->cachePath, $config->cacheFileLocking, $app->logger);
|
||||
}
|
||||
|
||||
$type = $that->typeOf($config->cachePath);
|
||||
throw new RuntimeException('$min_cachePath must be a path or implement Minify_CacheInterface.'
|
||||
. " Given $type");
|
||||
};
|
||||
|
||||
$this->config = function (App $app) {
|
||||
$config = (require $app->configPath);
|
||||
|
||||
if ($config instanceof Minify\Config) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
// copy from vars into properties
|
||||
|
||||
$config = new Minify\Config();
|
||||
|
||||
$propNames = array_keys(get_object_vars($config));
|
||||
|
||||
$prefixer = function ($name) {
|
||||
return "min_$name";
|
||||
};
|
||||
$varNames = array_map($prefixer, $propNames);
|
||||
|
||||
$varDefined = get_defined_vars();
|
||||
|
||||
$varNames = array_filter($varNames, function ($name) use ($varDefined) {
|
||||
return array_key_exists($name, $varDefined);
|
||||
});
|
||||
|
||||
$vars = compact($varNames);
|
||||
|
||||
foreach ($varNames as $varName) {
|
||||
if (isset($vars[$varName])) {
|
||||
$config->{substr($varName, 4)} = $vars[$varName];
|
||||
}
|
||||
}
|
||||
|
||||
if ($config->documentRoot) {
|
||||
// copy into env
|
||||
if (empty($config->envArgs['server'])) {
|
||||
$config->envArgs['server'] = $_SERVER;
|
||||
}
|
||||
$config->envArgs['server']['DOCUMENT_ROOT'] = $config->documentRoot;
|
||||
}
|
||||
|
||||
return $config;
|
||||
};
|
||||
|
||||
$this->configPath = "{$this->dir}/config.php";
|
||||
|
||||
$this->controller = function (App $app) use ($that) {
|
||||
$config = $app->config;
|
||||
|
||||
if (empty($config->factories['controller'])) {
|
||||
$ctrl = new Minify_Controller_MinApp($app->env, $app->sourceFactory, $app->logger);
|
||||
} else {
|
||||
$ctrl = call_user_func($config->factories['controller'], $app);
|
||||
}
|
||||
|
||||
if ($ctrl instanceof Minify_ControllerInterface) {
|
||||
return $ctrl;
|
||||
}
|
||||
|
||||
$type = $that->typeOf($ctrl);
|
||||
throw new RuntimeException('$min_factories["controller"] callable must return an implementation'
|
||||
. " of Minify_CacheInterface. Returned $type");
|
||||
};
|
||||
|
||||
$this->docRoot = function (App $app) {
|
||||
$config = $app->config;
|
||||
if (empty($config->documentRoot)) {
|
||||
return $app->env->getDocRoot();
|
||||
}
|
||||
|
||||
return $app->env->normalizePath($config->documentRoot);
|
||||
};
|
||||
|
||||
$this->env = function (App $app) {
|
||||
return new Minify_Env($app->config->envArgs);
|
||||
};
|
||||
|
||||
$this->errorLogHandler = function (App $app) {
|
||||
$format = "%channel%.%level_name%: %message% %context% %extra%";
|
||||
$handler = new Monolog\Handler\ErrorLogHandler();
|
||||
$handler->setFormatter(new Monolog\Formatter\LineFormatter($format));
|
||||
|
||||
return $handler;
|
||||
};
|
||||
|
||||
$this->groupsConfig = function (App $app) {
|
||||
return (require $app->groupsConfigPath);
|
||||
};
|
||||
|
||||
$this->groupsConfigPath = "{$this->dir}/groupsConfig.php";
|
||||
|
||||
$this->logger = function (App $app) use ($that) {
|
||||
$value = $app->config->errorLogger;
|
||||
|
||||
if ($value instanceof LoggerInterface) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$logger = new Monolog\Logger('minify');
|
||||
|
||||
if (!$value) {
|
||||
return $logger;
|
||||
}
|
||||
|
||||
if ($value === true || $value instanceof \FirePHP) {
|
||||
$logger->pushHandler($app->errorLogHandler);
|
||||
$logger->pushHandler(new Monolog\Handler\FirePHPHandler());
|
||||
|
||||
return $logger;
|
||||
}
|
||||
|
||||
if ($value instanceof Monolog\Handler\HandlerInterface) {
|
||||
$logger->pushHandler($value);
|
||||
|
||||
return $logger;
|
||||
}
|
||||
|
||||
// BC
|
||||
if (is_object($value) && is_callable(array($value, 'log'))) {
|
||||
$handler = new Minify\Logger\LegacyHandler($value);
|
||||
$logger->pushHandler($handler);
|
||||
|
||||
return $logger;
|
||||
}
|
||||
|
||||
$type = $that->typeOf($value);
|
||||
throw new RuntimeException('If set, $min_errorLogger must be a PSR-3 logger or a Monolog handler.'
|
||||
. " Given $type");
|
||||
};
|
||||
|
||||
$this->minify = function (App $app) use ($that) {
|
||||
$config = $app->config;
|
||||
|
||||
if (empty($config->factories['minify'])) {
|
||||
return new \Minify($app->cache, $app->logger);
|
||||
}
|
||||
|
||||
$minify = call_user_func($config->factories['minify'], $app);
|
||||
if ($minify instanceof \Minify) {
|
||||
return $minify;
|
||||
}
|
||||
|
||||
$type = $that->typeOf($minify);
|
||||
throw new RuntimeException('$min_factories["minify"] callable must return a Minify object.'
|
||||
. " Returned $type");
|
||||
};
|
||||
|
||||
$this->serveOptions = function (App $app) {
|
||||
$config = $app->config;
|
||||
$env = $app->env;
|
||||
|
||||
$ret = $config->serveOptions;
|
||||
|
||||
$ret['minifierOptions']['text/css']['docRoot'] = $app->docRoot;
|
||||
$ret['minifierOptions']['text/css']['symlinks'] = $config->symlinks;
|
||||
$ret['minApp']['symlinks'] = $config->symlinks;
|
||||
|
||||
// auto-add targets to allowDirs
|
||||
foreach ($config->symlinks as $uri => $target) {
|
||||
$ret['minApp']['allowDirs'][] = $target;
|
||||
}
|
||||
|
||||
if ($config->allowDebugFlag) {
|
||||
$ret['debug'] = Minify_DebugDetector::shouldDebugRequest($env);
|
||||
}
|
||||
|
||||
if ($config->concatOnly) {
|
||||
$ret['concatOnly'] = true;
|
||||
}
|
||||
|
||||
// check for URI versioning
|
||||
if ($env->get('v') !== null || preg_match('/&\\d/', $app->env->server('QUERY_STRING') ?? '')) {
|
||||
$ret['maxAge'] = 31536000;
|
||||
}
|
||||
|
||||
// need groups config?
|
||||
if ($env->get('g') !== null) {
|
||||
$ret['minApp']['groups'] = $app->groupsConfig;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
};
|
||||
|
||||
$this->sourceFactory = function (App $app) {
|
||||
return new Minify_Source_Factory($app->env, $app->sourceFactoryOptions, $app->cache);
|
||||
};
|
||||
|
||||
$this->sourceFactoryOptions = function (App $app) {
|
||||
$serveOptions = $app->serveOptions;
|
||||
$ret = array();
|
||||
|
||||
// translate legacy setting to option for source factory
|
||||
if (isset($serveOptions['minApp']['noMinPattern'])) {
|
||||
$ret['noMinPattern'] = $serveOptions['minApp']['noMinPattern'];
|
||||
}
|
||||
|
||||
if (isset($serveOptions['minApp']['allowDirs'])) {
|
||||
$ret['allowDirs'] = $serveOptions['minApp']['allowDirs'];
|
||||
}
|
||||
|
||||
if (isset($serveOptions['checkAllowDirs'])) {
|
||||
$ret['checkAllowDirs'] = $serveOptions['checkAllowDirs'];
|
||||
}
|
||||
|
||||
if (is_numeric($app->config->uploaderHoursBehind)) {
|
||||
$ret['uploaderHoursBehind'] = $app->config->uploaderHoursBehind;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
};
|
||||
}
|
||||
|
||||
public function runServer()
|
||||
{
|
||||
if (!$this->env->get('f') && $this->env->get('g') === null) {
|
||||
// no spec given
|
||||
$msg = '<p>No "f" or "g" parameters were detected.</p>';
|
||||
$url = 'https://github.com/mrclay/minify/blob/master/docs/CommonProblems.wiki.md#long-url-parameters-are-ignored';
|
||||
$defaults = $this->minify->getDefaultOptions();
|
||||
$this->minify->errorExit($defaults['badRequestHeader'], $url, $msg);
|
||||
}
|
||||
|
||||
$this->minify->serve($this->controller, $this->serveOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $var
|
||||
* @return string
|
||||
*/
|
||||
private function typeOf($var)
|
||||
{
|
||||
$type = gettype($var);
|
||||
|
||||
return $type === 'object' ? get_class($var) : $type;
|
||||
}
|
||||
}
|
@@ -1,48 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Build
|
||||
* Class Minify_Build
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maintain a single last modification time for a group of Minify sources to
|
||||
* allow use of far off Expires headers in Minify.
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* // in config file
|
||||
* $groupSources = array(
|
||||
* 'js' => array('file1.js', 'file2.js')
|
||||
* ,'css' => array('file1.css', 'file2.css', 'file3.css')
|
||||
* )
|
||||
*
|
||||
*
|
||||
* // during HTML generation
|
||||
* $jsBuild = new Minify_Build($groupSources['js']);
|
||||
* $cssBuild = new Minify_Build($groupSources['css']);
|
||||
*
|
||||
*
|
||||
* $script = "<script type='text/javascript' src='"
|
||||
* . $jsBuild->uri('/min.php/js') . "'></script>";
|
||||
* $link = "<link rel='stylesheet' type='text/css' href='"
|
||||
* . $cssBuild->uri('/min.php/css') . "'>";
|
||||
*
|
||||
*
|
||||
* // in min.php
|
||||
* Minify::serve('Groups', array(
|
||||
* 'groups' => $groupSources
|
||||
* ,'setExpires' => (time() + 86400 * 365)
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_Build {
|
||||
|
||||
class Minify_Build
|
||||
{
|
||||
|
||||
/**
|
||||
* Last modification time of all files in the build
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $lastModified = 0;
|
||||
|
||||
|
||||
/**
|
||||
* String to use as ampersand in uri(). Set this to '&' if
|
||||
* you are not HTML-escaping URIs.
|
||||
@@ -50,43 +51,42 @@ class Minify_Build {
|
||||
* @var string
|
||||
*/
|
||||
public static $ampersand = '&';
|
||||
|
||||
|
||||
/**
|
||||
* Get a time-stamped URI
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* echo $b->uri('/site.js');
|
||||
* // outputs "/site.js?1678242"
|
||||
*
|
||||
*
|
||||
* echo $b->uri('/scriptaculous.js?load=effects');
|
||||
* // outputs "/scriptaculous.js?load=effects&1678242"
|
||||
* </code>
|
||||
*
|
||||
* @param string $uri
|
||||
* @param boolean $forceAmpersand (default = false) Force the use of ampersand to
|
||||
* @param boolean $forceAmpersand (default = false) Force the use of ampersand to
|
||||
* append the timestamp to the URI.
|
||||
* @return string
|
||||
*/
|
||||
public function uri($uri, $forceAmpersand = false) {
|
||||
$sep = ($forceAmpersand || strpos($uri, '?') !== false)
|
||||
? self::$ampersand
|
||||
: '?';
|
||||
public function uri($uri, $forceAmpersand = false)
|
||||
{
|
||||
$sep = ($forceAmpersand || strpos($uri, '?') !== false) ? self::$ampersand : '?';
|
||||
|
||||
return "{$uri}{$sep}{$this->lastModified}";
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Create a build object
|
||||
*
|
||||
*
|
||||
* @param array $sources array of Minify_Source objects and/or file paths
|
||||
*
|
||||
* @return null
|
||||
*
|
||||
*/
|
||||
public function __construct($sources)
|
||||
public function __construct($sources)
|
||||
{
|
||||
$max = 0;
|
||||
foreach ((array)$sources as $source) {
|
||||
if ($source instanceof Minify_Source) {
|
||||
$max = max($max, $source->lastModified);
|
||||
$max = max($max, $source->getLastModified());
|
||||
} elseif (is_string($source)) {
|
||||
if (0 === strpos($source, '//')) {
|
||||
$source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
|
@@ -1,99 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_CSS
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Minify CSS
|
||||
*
|
||||
* This class uses Minify_CSS_Compressor and Minify_CSS_UriRewriter to
|
||||
* minify CSS and rewrite relative URIs.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
|
||||
*/
|
||||
class Minify_CSS {
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @param array $options available options:
|
||||
*
|
||||
* 'preserveComments': (default true) multi-line comments that begin
|
||||
* with "/*!" will be preserved with newlines before and after to
|
||||
* enhance readability.
|
||||
*
|
||||
* 'removeCharsets': (default true) remove all @charset at-rules
|
||||
*
|
||||
* 'prependRelativePath': (default null) if given, this string will be
|
||||
* prepended to all relative URIs in import/url declarations
|
||||
*
|
||||
* 'currentDir': (default null) if given, this is assumed to be the
|
||||
* directory of the current CSS file. Using this, minify will rewrite
|
||||
* all relative URIs in import/url declarations to correctly point to
|
||||
* the desired files. For this to work, the files *must* exist and be
|
||||
* visible by the PHP process.
|
||||
*
|
||||
* 'symlinks': (default = array()) If the CSS file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
* array('//static' => 'D:\\staticStorage') // Windows
|
||||
* </code>
|
||||
*
|
||||
* 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
|
||||
* see Minify_CSS_UriRewriter::rewrite
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($css, $options = array())
|
||||
{
|
||||
$options = array_merge(array(
|
||||
'compress' => true,
|
||||
'removeCharsets' => true,
|
||||
'preserveComments' => true,
|
||||
'currentDir' => null,
|
||||
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
|
||||
'prependRelativePath' => null,
|
||||
'symlinks' => array(),
|
||||
), $options);
|
||||
|
||||
if ($options['removeCharsets']) {
|
||||
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
|
||||
}
|
||||
if ($options['compress']) {
|
||||
if (! $options['preserveComments']) {
|
||||
$css = Minify_CSS_Compressor::process($css, $options);
|
||||
} else {
|
||||
$css = Minify_CommentPreserver::process(
|
||||
$css
|
||||
,array('Minify_CSS_Compressor', 'process')
|
||||
,array($options)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
|
||||
return $css;
|
||||
}
|
||||
if ($options['currentDir']) {
|
||||
return Minify_CSS_UriRewriter::rewrite(
|
||||
$css
|
||||
,$options['currentDir']
|
||||
,$options['docRoot']
|
||||
,$options['symlinks']
|
||||
);
|
||||
} else {
|
||||
return Minify_CSS_UriRewriter::prepend(
|
||||
$css
|
||||
,$options['prependRelativePath']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_CSS
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Minify CSS
|
||||
*
|
||||
* This class uses Minify_CSS_Compressor and Minify_CSS_UriRewriter to
|
||||
* minify CSS and rewrite relative URIs.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
|
||||
*
|
||||
* @deprecated Use Minify_CSSmin
|
||||
*/
|
||||
class Minify_CSS
|
||||
{
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @param array $options available options:
|
||||
*
|
||||
* 'preserveComments': (default true) multi-line comments that begin
|
||||
* with "/*!" will be preserved with newlines before and after to
|
||||
* enhance readability.
|
||||
*
|
||||
* 'removeCharsets': (default true) remove all @charset at-rules
|
||||
*
|
||||
* 'prependRelativePath': (default null) if given, this string will be
|
||||
* prepended to all relative URIs in import/url declarations
|
||||
*
|
||||
* 'currentDir': (default null) if given, this is assumed to be the
|
||||
* directory of the current CSS file. Using this, minify will rewrite
|
||||
* all relative URIs in import/url declarations to correctly point to
|
||||
* the desired files. For this to work, the files *must* exist and be
|
||||
* visible by the PHP process.
|
||||
*
|
||||
* 'symlinks': (default = array()) If the CSS file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
* array('//static' => 'D:\\staticStorage') // Windows
|
||||
* </code>
|
||||
*
|
||||
* 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
|
||||
* see Minify_CSS_UriRewriter::rewrite
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($css, $options = array())
|
||||
{
|
||||
$options = array_merge(array(
|
||||
'compress' => true,
|
||||
'removeCharsets' => true,
|
||||
'preserveComments' => true,
|
||||
'currentDir' => null,
|
||||
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
|
||||
'prependRelativePath' => null,
|
||||
'symlinks' => array(),
|
||||
), $options);
|
||||
|
||||
if ($options['removeCharsets']) {
|
||||
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
|
||||
}
|
||||
|
||||
if ($options['compress']) {
|
||||
if (! $options['preserveComments']) {
|
||||
$css = Minify_CSS_Compressor::process($css, $options);
|
||||
} else {
|
||||
$processor = array('Minify_CSS_Compressor', 'process');
|
||||
$css = Minify_CommentPreserver::process($css, $processor, array($options));
|
||||
}
|
||||
}
|
||||
|
||||
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
|
||||
return $css;
|
||||
}
|
||||
|
||||
if ($options['currentDir']) {
|
||||
$currentDir = $options['currentDir'];
|
||||
$docRoot = $options['docRoot'];
|
||||
$symlinks = $options['symlinks'];
|
||||
|
||||
return Minify_CSS_UriRewriter::rewrite($css, $currentDir, $docRoot, $symlinks);
|
||||
}
|
||||
|
||||
return Minify_CSS_UriRewriter::prepend($css, $options['prependRelativePath']);
|
||||
}
|
||||
}
|
@@ -1,249 +1,275 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_CSS_Compressor
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress CSS
|
||||
*
|
||||
* This is a heavy regex-based removal of whitespace, unnecessary
|
||||
* comments and tokens, and some CSS value minimization, where practical.
|
||||
* Many steps have been taken to avoid breaking comment-based hacks,
|
||||
* including the ie5/mac filter (and its inversion), but expect tricky
|
||||
* hacks involving comment tokens in 'content' value strings to break
|
||||
* minimization badly. A test suite is available.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
|
||||
*/
|
||||
class Minify_CSS_Compressor {
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @param array $options (currently ignored)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function process($css, $options = array())
|
||||
{
|
||||
$obj = new Minify_CSS_Compressor($options);
|
||||
return $obj->_process($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_options = null;
|
||||
|
||||
/**
|
||||
* Are we "in" a hack? I.e. are some browsers targetted until the next comment?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_inHack = false;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options (currently ignored)
|
||||
*/
|
||||
private function __construct($options) {
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _process($css)
|
||||
{
|
||||
$css = str_replace("\r\n", "\n", $css);
|
||||
|
||||
// preserve empty comment after '>'
|
||||
// http://www.webdevout.net/css-hacks#in_css-selectors
|
||||
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
|
||||
|
||||
// preserve empty comment between property and value
|
||||
// http://css-discuss.incutio.com/?page=BoxModelHack
|
||||
$css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
|
||||
$css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
|
||||
|
||||
// apply callback to all valid comments (and strip out surrounding ws
|
||||
$css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
|
||||
,array($this, '_commentCB'), $css);
|
||||
|
||||
// remove ws around { } and last semicolon in declaration block
|
||||
$css = preg_replace('/\\s*{\\s*/', '{', $css);
|
||||
$css = preg_replace('/;?\\s*}\\s*/', '}', $css);
|
||||
|
||||
// remove ws surrounding semicolons
|
||||
$css = preg_replace('/\\s*;\\s*/', ';', $css);
|
||||
|
||||
// remove ws around urls
|
||||
$css = preg_replace('/
|
||||
url\\( # url(
|
||||
\\s*
|
||||
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
|
||||
\\s*
|
||||
\\) # )
|
||||
/x', 'url($1)', $css);
|
||||
|
||||
// remove ws between rules and colons
|
||||
$css = preg_replace('/
|
||||
\\s*
|
||||
([{;]) # 1 = beginning of block or rule separator
|
||||
\\s*
|
||||
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
|
||||
\\s*
|
||||
:
|
||||
\\s*
|
||||
(\\b|[#\'"-]) # 3 = first character of a value
|
||||
/x', '$1$2:$3', $css);
|
||||
|
||||
// remove ws in selectors
|
||||
$css = preg_replace_callback('/
|
||||
(?: # non-capture
|
||||
\\s*
|
||||
[^~>+,\\s]+ # selector part
|
||||
\\s*
|
||||
[,>+~] # combinators
|
||||
)+
|
||||
\\s*
|
||||
[^~>+,\\s]+ # selector part
|
||||
{ # open declaration block
|
||||
/x'
|
||||
,array($this, '_selectorsCB'), $css);
|
||||
|
||||
// minimize hex colors
|
||||
$css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
|
||||
, '$1#$2$3$4$5', $css);
|
||||
|
||||
// remove spaces between font families
|
||||
$css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
|
||||
,array($this, '_fontFamilyCB'), $css);
|
||||
|
||||
$css = preg_replace('/@import\\s+url/', '@import url', $css);
|
||||
|
||||
// replace any ws involving newlines with a single newline
|
||||
$css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
|
||||
|
||||
// separate common descendent selectors w/ newlines (to limit line lengths)
|
||||
$css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
|
||||
|
||||
// Use newline after 1st numeric value (to limit line lengths).
|
||||
$css = preg_replace('/
|
||||
((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
|
||||
\\s+
|
||||
/x'
|
||||
,"$1\n", $css);
|
||||
|
||||
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
|
||||
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
|
||||
|
||||
return trim($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace what looks like a set of selectors
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _selectorsCB($m)
|
||||
{
|
||||
// remove ws around the combinators
|
||||
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a comment and return a replacement
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _commentCB($m)
|
||||
{
|
||||
$hasSurroundingWs = (trim($m[0]) !== $m[1]);
|
||||
$m = $m[1];
|
||||
// $m is the comment content w/o the surrounding tokens,
|
||||
// but the return value will replace the entire comment.
|
||||
if ($m === 'keep') {
|
||||
return '/**/';
|
||||
}
|
||||
if ($m === '" "') {
|
||||
// component of http://tantek.com/CSS/Examples/midpass.html
|
||||
return '/*" "*/';
|
||||
}
|
||||
if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
|
||||
// component of http://tantek.com/CSS/Examples/midpass.html
|
||||
return '/*";}}/* */';
|
||||
}
|
||||
if ($this->_inHack) {
|
||||
// inversion: feeding only to one browser
|
||||
if (preg_match('@
|
||||
^/ # comment started like /*/
|
||||
\\s*
|
||||
(\\S[\\s\\S]+?) # has at least some non-ws content
|
||||
\\s*
|
||||
/\\* # ends like /*/ or /**/
|
||||
@x', $m, $n)) {
|
||||
// end hack mode after this comment, but preserve the hack and comment content
|
||||
$this->_inHack = false;
|
||||
return "/*/{$n[1]}/**/";
|
||||
}
|
||||
}
|
||||
if (substr($m, -1) === '\\') { // comment ends like \*/
|
||||
// begin hack mode and preserve hack
|
||||
$this->_inHack = true;
|
||||
return '/*\\*/';
|
||||
}
|
||||
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
|
||||
// begin hack mode and preserve hack
|
||||
$this->_inHack = true;
|
||||
return '/*/*/';
|
||||
}
|
||||
if ($this->_inHack) {
|
||||
// a regular comment ends hack mode but should be preserved
|
||||
$this->_inHack = false;
|
||||
return '/**/';
|
||||
}
|
||||
// Issue 107: if there's any surrounding whitespace, it may be important, so
|
||||
// replace the comment with a single space
|
||||
return $hasSurroundingWs // remove all other comments
|
||||
? ' '
|
||||
: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a font-family listing and return a replacement
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _fontFamilyCB($m)
|
||||
{
|
||||
// Issue 210: must not eliminate WS between words in unquoted families
|
||||
$pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
$out = 'font-family:';
|
||||
while (null !== ($piece = array_shift($pieces))) {
|
||||
if ($piece[0] !== '"' && $piece[0] !== "'") {
|
||||
$piece = preg_replace('/\\s+/', ' ', $piece);
|
||||
$piece = preg_replace('/\\s?,\\s?/', ',', $piece);
|
||||
}
|
||||
$out .= $piece;
|
||||
}
|
||||
return $out . $m[2];
|
||||
}
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_CSS_Compressor
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress CSS
|
||||
*
|
||||
* This is a heavy regex-based removal of whitespace, unnecessary
|
||||
* comments and tokens, and some CSS value minimization, where practical.
|
||||
* Many steps have been taken to avoid breaking comment-based hacks,
|
||||
* including the ie5/mac filter (and its inversion), but expect tricky
|
||||
* hacks involving comment tokens in 'content' value strings to break
|
||||
* minimization badly. A test suite is available.
|
||||
*
|
||||
* Note: This replaces a lot of spaces with line breaks. It's rumored
|
||||
* (https://github.com/yui/yuicompressor/blob/master/README.md#global-options)
|
||||
* that some source control tools and old browsers don't like very long lines.
|
||||
* Compressed files with shorter lines are also easier to diff. If this is
|
||||
* unacceptable please use CSSmin instead.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
|
||||
*
|
||||
* @deprecated Use CSSmin (tubalmartin/cssmin)
|
||||
*/
|
||||
class Minify_CSS_Compressor
|
||||
{
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @param array $options (currently ignored)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function process($css, $options = array())
|
||||
{
|
||||
$obj = new Minify_CSS_Compressor($options);
|
||||
|
||||
return $obj->_process($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $_options = null;
|
||||
|
||||
/**
|
||||
* Are we "in" a hack? I.e. are some browsers targetted until the next comment?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_inHack = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $options (currently ignored)
|
||||
*/
|
||||
private function __construct($options)
|
||||
{
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _process($css)
|
||||
{
|
||||
$css = str_replace("\r\n", "\n", $css);
|
||||
|
||||
// preserve empty comment after '>'
|
||||
// http://www.webdevout.net/css-hacks#in_css-selectors
|
||||
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
|
||||
|
||||
// preserve empty comment between property and value
|
||||
// http://css-discuss.incutio.com/?page=BoxModelHack
|
||||
$css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
|
||||
$css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
|
||||
|
||||
// apply callback to all valid comments (and strip out surrounding ws
|
||||
$pattern = '@\\s*/\\*([\\s\\S]*?)\\*/\\s*@';
|
||||
$css = preg_replace_callback($pattern, array($this, '_commentCB'), $css);
|
||||
|
||||
// remove ws around { } and last semicolon in declaration block
|
||||
$css = preg_replace('/\\s*{\\s*/', '{', $css);
|
||||
$css = preg_replace('/;?\\s*}\\s*/', '}', $css);
|
||||
|
||||
// remove ws surrounding semicolons
|
||||
$css = preg_replace('/\\s*;\\s*/', ';', $css);
|
||||
|
||||
// remove ws around urls
|
||||
$pattern = '/
|
||||
url\\( # url(
|
||||
\\s*
|
||||
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
|
||||
\\s*
|
||||
\\) # )
|
||||
/x';
|
||||
$css = preg_replace($pattern, 'url($1)', $css);
|
||||
|
||||
// remove ws between rules and colons
|
||||
$pattern = '/
|
||||
\\s*
|
||||
([{;]) # 1 = beginning of block or rule separator
|
||||
\\s*
|
||||
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
|
||||
\\s*
|
||||
:
|
||||
\\s*
|
||||
(\\b|[#\'"-]) # 3 = first character of a value
|
||||
/x';
|
||||
$css = preg_replace($pattern, '$1$2:$3', $css);
|
||||
|
||||
// remove ws in selectors
|
||||
$pattern = '/
|
||||
(?: # non-capture
|
||||
\\s*
|
||||
[^~>+,\\s]+ # selector part
|
||||
\\s*
|
||||
[,>+~] # combinators
|
||||
)+
|
||||
\\s*
|
||||
[^~>+,\\s]+ # selector part
|
||||
{ # open declaration block
|
||||
/x';
|
||||
$css = preg_replace_callback($pattern, array($this, '_selectorsCB'), $css);
|
||||
|
||||
// minimize hex colors
|
||||
$pattern = '/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i';
|
||||
$css = preg_replace($pattern, '$1#$2$3$4$5', $css);
|
||||
|
||||
// remove spaces between font families
|
||||
$pattern = '/font-family:([^;}]+)([;}])/';
|
||||
$css = preg_replace_callback($pattern, array($this, '_fontFamilyCB'), $css);
|
||||
|
||||
$css = preg_replace('/@import\\s+url/', '@import url', $css);
|
||||
|
||||
// replace any ws involving newlines with a single newline
|
||||
$css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
|
||||
|
||||
// separate common descendent selectors w/ newlines (to limit line lengths)
|
||||
$pattern = '/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/';
|
||||
$css = preg_replace($pattern, "$1\n$2{", $css);
|
||||
|
||||
// Use newline after 1st numeric value (to limit line lengths).
|
||||
$pattern = '/
|
||||
((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
|
||||
\\s+
|
||||
/x';
|
||||
$css = preg_replace($pattern, "$1\n", $css);
|
||||
|
||||
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
|
||||
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
|
||||
|
||||
return trim($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace what looks like a set of selectors
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _selectorsCB($m)
|
||||
{
|
||||
// remove ws around the combinators
|
||||
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a comment and return a replacement
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _commentCB($m)
|
||||
{
|
||||
$hasSurroundingWs = (trim($m[0]) !== $m[1]);
|
||||
$m = $m[1];
|
||||
// $m is the comment content w/o the surrounding tokens,
|
||||
// but the return value will replace the entire comment.
|
||||
if ($m === 'keep') {
|
||||
return '/**/';
|
||||
}
|
||||
|
||||
if ($m === '" "') {
|
||||
// component of http://tantek.com/CSS/Examples/midpass.html
|
||||
return '/*" "*/';
|
||||
}
|
||||
|
||||
if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
|
||||
// component of http://tantek.com/CSS/Examples/midpass.html
|
||||
return '/*";}}/* */';
|
||||
}
|
||||
|
||||
if ($this->_inHack) {
|
||||
// inversion: feeding only to one browser
|
||||
$pattern = '@
|
||||
^/ # comment started like /*/
|
||||
\\s*
|
||||
(\\S[\\s\\S]+?) # has at least some non-ws content
|
||||
\\s*
|
||||
/\\* # ends like /*/ or /**/
|
||||
@x';
|
||||
if (preg_match($pattern, $m, $n)) {
|
||||
// end hack mode after this comment, but preserve the hack and comment content
|
||||
$this->_inHack = false;
|
||||
|
||||
return "/*/{$n[1]}/**/";
|
||||
}
|
||||
}
|
||||
|
||||
if (substr($m, -1) === '\\') { // comment ends like \*/
|
||||
// begin hack mode and preserve hack
|
||||
$this->_inHack = true;
|
||||
|
||||
return '/*\\*/';
|
||||
}
|
||||
|
||||
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
|
||||
// begin hack mode and preserve hack
|
||||
$this->_inHack = true;
|
||||
|
||||
return '/*/*/';
|
||||
}
|
||||
|
||||
if ($this->_inHack) {
|
||||
// a regular comment ends hack mode but should be preserved
|
||||
$this->_inHack = false;
|
||||
|
||||
return '/**/';
|
||||
}
|
||||
|
||||
// Issue 107: if there's any surrounding whitespace, it may be important, so
|
||||
// replace the comment with a single space
|
||||
return $hasSurroundingWs ? ' ' : ''; // remove all other comments
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a font-family listing and return a replacement
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _fontFamilyCB($m)
|
||||
{
|
||||
// Issue 210: must not eliminate WS between words in unquoted families
|
||||
$flags = PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY;
|
||||
$pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], -1, $flags);
|
||||
$out = 'font-family:';
|
||||
|
||||
while (null !== ($piece = array_shift($pieces))) {
|
||||
if ($piece[0] !== '"' && $piece[0] !== "'") {
|
||||
$piece = preg_replace('/\\s+/', ' ', $piece);
|
||||
$piece = preg_replace('/\\s?,\\s?/', ',', $piece);
|
||||
}
|
||||
$out .= $piece;
|
||||
}
|
||||
|
||||
return $out . $m[2];
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_CSS_UriRewriter
|
||||
* Class Minify_CSS_UriRewriter
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
@@ -10,97 +10,108 @@
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_CSS_UriRewriter {
|
||||
|
||||
class Minify_CSS_UriRewriter
|
||||
{
|
||||
|
||||
/**
|
||||
* rewrite() and rewriteRelative() append debugging information here
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $debugText = '';
|
||||
|
||||
|
||||
/**
|
||||
* In CSS content, rewrite file relative URIs as root relative
|
||||
*
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
*
|
||||
* @param string $currentDir The directory of the current CSS file.
|
||||
*
|
||||
* @param string $docRoot The document root of the web site in which
|
||||
*
|
||||
* @param string $docRoot The document root of the web site in which
|
||||
* the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
|
||||
*
|
||||
* @param array $symlinks (default = array()) If the CSS file is stored in
|
||||
*
|
||||
* @param array $symlinks (default = array()) If the CSS file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
* array('//static' => 'D:\\staticStorage') // Windows
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
|
||||
public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array())
|
||||
{
|
||||
self::$_docRoot = self::_realpath(
|
||||
$docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
|
||||
);
|
||||
self::$_currentDir = self::_realpath($currentDir);
|
||||
self::$_symlinks = array();
|
||||
|
||||
// normalize symlinks
|
||||
|
||||
// normalize symlinks in order to map to link
|
||||
foreach ($symlinks as $link => $target) {
|
||||
$link = ($link === '//')
|
||||
? self::$_docRoot
|
||||
: str_replace('//', self::$_docRoot . '/', $link);
|
||||
$link = ($link === '//') ? self::$_docRoot : str_replace('//', self::$_docRoot . '/', $link);
|
||||
$link = strtr($link, '/', DIRECTORY_SEPARATOR);
|
||||
|
||||
self::$_symlinks[$link] = self::_realpath($target);
|
||||
}
|
||||
|
||||
|
||||
self::$debugText .= "docRoot : " . self::$_docRoot . "\n"
|
||||
. "currentDir : " . self::$_currentDir . "\n";
|
||||
if (self::$_symlinks) {
|
||||
self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
|
||||
}
|
||||
self::$debugText .= "\n";
|
||||
|
||||
|
||||
$css = self::_trimUrls($css);
|
||||
|
||||
|
||||
$css = self::_owlifySvgPaths($css);
|
||||
|
||||
// rewrite
|
||||
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
|
||||
,array(self::$className, '_processUriCB'), $css);
|
||||
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||
,array(self::$className, '_processUriCB'), $css);
|
||||
$pattern = '/@import\\s+([\'"])(.*?)[\'"]/';
|
||||
$css = preg_replace_callback($pattern, array(self::$className, '_processUriCB'), $css);
|
||||
|
||||
$pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/';
|
||||
$css = preg_replace_callback($pattern, array(self::$className, '_processUriCB'), $css);
|
||||
|
||||
$css = self::_unOwlify($css);
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* In CSS content, prepend a path to relative URIs
|
||||
*
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
*
|
||||
* @param string $path The path to prepend.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function prepend($css, $path)
|
||||
{
|
||||
self::$_prependPath = $path;
|
||||
|
||||
|
||||
$css = self::_trimUrls($css);
|
||||
|
||||
$css = self::_owlifySvgPaths($css);
|
||||
|
||||
// append
|
||||
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
|
||||
,array(self::$className, '_processUriCB'), $css);
|
||||
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||
,array(self::$className, '_processUriCB'), $css);
|
||||
$pattern = '/@import\\s+([\'"])(.*?)[\'"]/';
|
||||
$css = preg_replace_callback($pattern, array(self::$className, '_processUriCB'), $css);
|
||||
|
||||
$pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/';
|
||||
$css = preg_replace_callback($pattern, array(self::$className, '_processUriCB'), $css);
|
||||
|
||||
$css = self::_unOwlify($css);
|
||||
|
||||
self::$_prependPath = null;
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a root relative URI from a file relative URI
|
||||
*
|
||||
@@ -111,7 +122,7 @@ class Minify_CSS_UriRewriter {
|
||||
* , '/home/user/www' // doc root
|
||||
* );
|
||||
* // returns '/img/hello.gif'
|
||||
*
|
||||
*
|
||||
* // example where static files are stored in a symlinked directory
|
||||
* Minify_CSS_UriRewriter::rewriteRelative(
|
||||
* 'hello.gif'
|
||||
@@ -121,55 +132,55 @@ class Minify_CSS_UriRewriter {
|
||||
* );
|
||||
* // returns '/static/theme/hello.gif'
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @param string $uri file relative URI
|
||||
*
|
||||
*
|
||||
* @param string $realCurrentDir realpath of the current file's directory.
|
||||
*
|
||||
*
|
||||
* @param string $realDocRoot realpath of the site document root.
|
||||
*
|
||||
* @param array $symlinks (default = array()) If the file is stored in
|
||||
*
|
||||
* @param array $symlinks (default = array()) If the file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* real target paths, where the link paths "appear" to be within the document
|
||||
* real target paths, where the link paths "appear" to be within the document
|
||||
* root. E.g.:
|
||||
* <code>
|
||||
* array('/home/foo/www/not/real/path' => '/real/target/path') // unix
|
||||
* array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
|
||||
{
|
||||
// prepend path with current dir separator (OS-independent)
|
||||
$path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
|
||||
. DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
|
||||
|
||||
$path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR);
|
||||
$path .= DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
|
||||
|
||||
self::$debugText .= "file-relative URI : {$uri}\n"
|
||||
. "path prepended : {$path}\n";
|
||||
|
||||
|
||||
// "unresolve" a symlink back to doc root
|
||||
foreach ($symlinks as $link => $target) {
|
||||
if (0 === strpos($path, $target)) {
|
||||
// replace $target with $link
|
||||
$path = $link . substr($path, strlen($target));
|
||||
|
||||
|
||||
self::$debugText .= "symlink unresolved : {$path}\n";
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
// strip doc root
|
||||
$path = substr($path, strlen($realDocRoot));
|
||||
|
||||
|
||||
self::$debugText .= "docroot stripped : {$path}\n";
|
||||
|
||||
|
||||
// fix to root-relative URI
|
||||
$uri = strtr($path, '/\\', '//');
|
||||
$uri = self::removeDots($uri);
|
||||
|
||||
|
||||
self::$debugText .= "traversals removed : {$uri}\n\n";
|
||||
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
@@ -187,9 +198,10 @@ class Minify_CSS_UriRewriter {
|
||||
do {
|
||||
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
|
||||
} while ($changed);
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines which class to call as part of callbacks, change this
|
||||
* if you extend Minify_CSS_UriRewriter
|
||||
@@ -201,9 +213,9 @@ class Minify_CSS_UriRewriter {
|
||||
/**
|
||||
* Get realpath with any trailing slash removed. If realpath() fails,
|
||||
* just remove the trailing slash.
|
||||
*
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
*
|
||||
* @return mixed path with no trailing slash
|
||||
*/
|
||||
protected static function _realpath($path)
|
||||
@@ -212,6 +224,7 @@ class Minify_CSS_UriRewriter {
|
||||
if ($realPath !== false) {
|
||||
$path = $realPath;
|
||||
}
|
||||
|
||||
return rtrim($path, '/\\');
|
||||
}
|
||||
|
||||
@@ -251,13 +264,15 @@ class Minify_CSS_UriRewriter {
|
||||
*/
|
||||
private static function _trimUrls($css)
|
||||
{
|
||||
return preg_replace('/
|
||||
$pattern = '/
|
||||
url\\( # url(
|
||||
\\s*
|
||||
([^\\)]+?) # 1 = URI (assuming does not contain ")")
|
||||
\\s*
|
||||
\\) # )
|
||||
/x', 'url($1)', $css);
|
||||
/x';
|
||||
|
||||
return preg_replace($pattern, 'url($1)', $css);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,13 +290,15 @@ class Minify_CSS_UriRewriter {
|
||||
$uri = $m[2];
|
||||
} else {
|
||||
// $m[1] is either quoted or not
|
||||
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||
? $m[1][0]
|
||||
: '';
|
||||
$uri = ($quoteChar === '')
|
||||
? $m[1]
|
||||
: substr($m[1], 1, strlen($m[1]) - 2);
|
||||
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"') ? $m[1][0] : '';
|
||||
|
||||
$uri = ($quoteChar === '') ? $m[1] : substr($m[1], 1, strlen($m[1]) - 2);
|
||||
}
|
||||
|
||||
if ($uri === '') {
|
||||
return $m[0];
|
||||
}
|
||||
|
||||
// if not root/scheme relative and not starts with scheme
|
||||
if (!preg_match('~^(/|[a-z]+\:)~', $uri)) {
|
||||
// URI is file-relative: rewrite depending on options
|
||||
@@ -300,8 +317,42 @@ class Minify_CSS_UriRewriter {
|
||||
}
|
||||
}
|
||||
}
|
||||
return $isImport
|
||||
? "@import {$quoteChar}{$uri}{$quoteChar}"
|
||||
: "url({$quoteChar}{$uri}{$quoteChar})";
|
||||
|
||||
if ($isImport) {
|
||||
return "@import {$quoteChar}{$uri}{$quoteChar}";
|
||||
} else {
|
||||
return "url({$quoteChar}{$uri}{$quoteChar})";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mungs some inline SVG URL declarations so they won't be touched
|
||||
*
|
||||
* @link https://github.com/mrclay/minify/issues/517
|
||||
* @see _unOwlify
|
||||
*
|
||||
* @param string $css
|
||||
* @return string
|
||||
*/
|
||||
private static function _owlifySvgPaths($css)
|
||||
{
|
||||
$pattern = '~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)url(\(\s*#\w+\s*\))~';
|
||||
|
||||
return preg_replace($pattern, '$1owl$2', $css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo work of _owlify
|
||||
*
|
||||
* @see _owlifySvgPaths
|
||||
*
|
||||
* @param string $css
|
||||
* @return string
|
||||
*/
|
||||
private static function _unOwlify($css)
|
||||
{
|
||||
$pattern = '~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)owl~';
|
||||
|
||||
return preg_replace($pattern, '$1url', $css);
|
||||
}
|
||||
}
|
@@ -1,85 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_CSSmin
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wrapper for CSSmin
|
||||
*
|
||||
* This class uses CSSmin and Minify_CSS_UriRewriter to minify CSS and rewrite relative URIs.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_CSSmin {
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @param array $options available options:
|
||||
*
|
||||
* 'removeCharsets': (default true) remove all @charset at-rules
|
||||
*
|
||||
* 'prependRelativePath': (default null) if given, this string will be
|
||||
* prepended to all relative URIs in import/url declarations
|
||||
*
|
||||
* 'currentDir': (default null) if given, this is assumed to be the
|
||||
* directory of the current CSS file. Using this, minify will rewrite
|
||||
* all relative URIs in import/url declarations to correctly point to
|
||||
* the desired files. For this to work, the files *must* exist and be
|
||||
* visible by the PHP process.
|
||||
*
|
||||
* 'symlinks': (default = array()) If the CSS file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
* array('//static' => 'D:\\staticStorage') // Windows
|
||||
* </code>
|
||||
*
|
||||
* 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
|
||||
* see Minify_CSS_UriRewriter::rewrite
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($css, $options = array())
|
||||
{
|
||||
$options = array_merge(array(
|
||||
'compress' => true,
|
||||
'removeCharsets' => true,
|
||||
'currentDir' => null,
|
||||
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
|
||||
'prependRelativePath' => null,
|
||||
'symlinks' => array(),
|
||||
), $options);
|
||||
|
||||
if ($options['removeCharsets']) {
|
||||
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
|
||||
}
|
||||
if ($options['compress']) {
|
||||
$obj = new CSSmin();
|
||||
$css = $obj->run($css);
|
||||
}
|
||||
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
|
||||
return $css;
|
||||
}
|
||||
if ($options['currentDir']) {
|
||||
return Minify_CSS_UriRewriter::rewrite(
|
||||
$css
|
||||
,$options['currentDir']
|
||||
,$options['docRoot']
|
||||
,$options['symlinks']
|
||||
);
|
||||
} else {
|
||||
return Minify_CSS_UriRewriter::prepend(
|
||||
$css
|
||||
,$options['prependRelativePath']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_CSSmin
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
use tubalmartin\CssMin\Minifier as CSSmin;
|
||||
|
||||
/**
|
||||
* Wrapper for CSSmin
|
||||
*
|
||||
* This class uses CSSmin and Minify_CSS_UriRewriter to minify CSS and rewrite relative URIs.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_CSSmin
|
||||
{
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @param array $options available options:
|
||||
*
|
||||
* 'removeCharsets': (default true) remove all @charset at-rules
|
||||
*
|
||||
* 'prependRelativePath': (default null) if given, this string will be
|
||||
* prepended to all relative URIs in import/url declarations
|
||||
*
|
||||
* 'currentDir': (default null) if given, this is assumed to be the
|
||||
* directory of the current CSS file. Using this, minify will rewrite
|
||||
* all relative URIs in import/url declarations to correctly point to
|
||||
* the desired files. For this to work, the files *must* exist and be
|
||||
* visible by the PHP process.
|
||||
*
|
||||
* 'symlinks': (default = array()) If the CSS file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
* array('//static' => 'D:\\staticStorage') // Windows
|
||||
* </code>
|
||||
*
|
||||
* 'docRoot': (default = $_SERVER['DOCUMENT_ROOT'])
|
||||
* see Minify_CSS_UriRewriter::rewrite
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($css, $options = array())
|
||||
{
|
||||
$options = array_merge(array(
|
||||
'compress' => true,
|
||||
'removeCharsets' => true,
|
||||
'currentDir' => null,
|
||||
'docRoot' => $_SERVER['DOCUMENT_ROOT'],
|
||||
'prependRelativePath' => null,
|
||||
'symlinks' => array(),
|
||||
), $options);
|
||||
|
||||
if ($options['removeCharsets']) {
|
||||
$css = preg_replace('/@charset[^;]+;\\s*/', '', $css);
|
||||
}
|
||||
if ($options['compress']) {
|
||||
$obj = new CSSmin();
|
||||
$css = $obj->run($css);
|
||||
}
|
||||
if (! $options['currentDir'] && ! $options['prependRelativePath']) {
|
||||
return $css;
|
||||
}
|
||||
if ($options['currentDir']) {
|
||||
return Minify_CSS_UriRewriter::rewrite(
|
||||
$css,
|
||||
$options['currentDir'],
|
||||
$options['docRoot'],
|
||||
$options['symlinks']
|
||||
);
|
||||
}
|
||||
|
||||
return Minify_CSS_UriRewriter::prepend(
|
||||
$css,
|
||||
$options['prependRelativePath']
|
||||
);
|
||||
}
|
||||
}
|
@@ -6,15 +6,18 @@
|
||||
|
||||
/**
|
||||
* APC-based cache class for Minify
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* Minify::setCache(new Minify_Cache_APC());
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
* @author Chris Edwards
|
||||
*
|
||||
* @deprecated Use Minify_Cache_APCu
|
||||
**/
|
||||
class Minify_Cache_APC {
|
||||
class Minify_Cache_APC implements Minify_CacheInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a Minify_Cache_APC object, to be passed to
|
||||
@@ -57,9 +60,12 @@ class Minify_Cache_APC {
|
||||
if (! $this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||
? mb_strlen($this->_data, '8bit')
|
||||
: strlen($this->_data);
|
||||
|
||||
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
|
||||
return mb_strlen($this->_data, '8bit');
|
||||
} else {
|
||||
return strlen($this->_data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,9 +89,7 @@ class Minify_Cache_APC {
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
echo $this->_fetch($id)
|
||||
? $this->_data
|
||||
: '';
|
||||
echo $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,9 +101,7 @@ class Minify_Cache_APC {
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? $this->_data
|
||||
: '';
|
||||
return $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
private $_exp = null;
|
||||
@@ -124,10 +126,13 @@ class Minify_Cache_APC {
|
||||
$ret = apc_fetch($id);
|
||||
if (false === $ret) {
|
||||
$this->_id = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||
$this->_id = $id;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
136
lib/Minify/Cache/APCu.php
Normal file
136
lib/Minify/Cache/APCu.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Cache_APCu
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* APCu-based cache class for Minify
|
||||
*
|
||||
* <code>
|
||||
* Minify::setCache(new Minify_Cache_APCu());
|
||||
* </code>
|
||||
*
|
||||
* @package Minify
|
||||
* @author Chris Edwards
|
||||
**/
|
||||
class Minify_Cache_APCu implements Minify_CacheInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a Minify_Cache_APCu object, to be passed to
|
||||
* Minify::setCache().
|
||||
*
|
||||
*
|
||||
* @param int $expire seconds until expiration (default = 0
|
||||
* meaning the item will not get an expiration date)
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function __construct($expire = 0)
|
||||
{
|
||||
$this->_exp = $expire;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
public function store($id, $data)
|
||||
{
|
||||
return apcu_store($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a cache entry
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
* @return int size in bytes
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
if (! $this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
|
||||
return mb_strlen($this->_data, '8bit');
|
||||
} else {
|
||||
return strlen($this->_data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a valid cache entry exist?
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
* @param int $srcMtime mtime of the original source file(s)
|
||||
*
|
||||
* @return bool exists
|
||||
*/
|
||||
public function isValid($id, $srcMtime)
|
||||
{
|
||||
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the cached content to output
|
||||
*
|
||||
* @param string $id cache id
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
echo $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the cached content
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
return $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
private $_exp = null;
|
||||
|
||||
// cache of most recently fetched id
|
||||
private $_lm = null;
|
||||
private $_data = null;
|
||||
private $_id = null;
|
||||
|
||||
/**
|
||||
* Fetch data and timestamp from apcu, store in instance
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
private function _fetch($id)
|
||||
{
|
||||
if ($this->_id === $id) {
|
||||
return true;
|
||||
}
|
||||
$ret = apcu_fetch($id);
|
||||
if (false === $ret) {
|
||||
$this->_id = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||
$this->_id = $id;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
183
lib/Minify/Cache/File.php
Normal file
183
lib/Minify/Cache/File.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Cache_File
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Minify_Cache_File implements Minify_CacheInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $locking;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param bool $fileLocking
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct($path = '', $fileLocking = false, LoggerInterface $logger = null)
|
||||
{
|
||||
if (! $path) {
|
||||
$path = sys_get_temp_dir();
|
||||
}
|
||||
$this->locking = $fileLocking;
|
||||
$this->path = $path;
|
||||
|
||||
if (!$logger) {
|
||||
$logger = new \Monolog\Logger('minify');
|
||||
}
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
public function store($id, $data)
|
||||
{
|
||||
$flag = $this->locking ? LOCK_EX : null;
|
||||
$file = $this->path . '/' . $id;
|
||||
|
||||
if (! @file_put_contents($file, $data, $flag)) {
|
||||
$this->logger->warning("Minify_Cache_File: Write failed to '$file'");
|
||||
}
|
||||
|
||||
// write control
|
||||
if ($data !== $this->fetch($id)) {
|
||||
@unlink($file);
|
||||
$this->logger->warning("Minify_Cache_File: Post-write read failed for '$file'");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a cache entry
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*
|
||||
* @return int size in bytes
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
return filesize($this->path . '/' . $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a valid cache entry exist?
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*
|
||||
* @param int $srcMtime mtime of the original source file(s)
|
||||
*
|
||||
* @return bool exists
|
||||
*/
|
||||
public function isValid($id, $srcMtime)
|
||||
{
|
||||
$file = $this->path . '/' . $id;
|
||||
|
||||
return (is_file($file) && (filemtime($file) >= $srcMtime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the cached content to output
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
if (!$this->locking) {
|
||||
readfile($this->path . '/' . $id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fp = fopen($this->path . '/' . $id, 'rb');
|
||||
flock($fp, LOCK_SH);
|
||||
fpassthru($fp);
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the cached content
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
if (!$this->locking) {
|
||||
return file_get_contents($this->path . '/' . $id);
|
||||
}
|
||||
|
||||
$fp = fopen($this->path . '/' . $id, 'rb');
|
||||
if (!$fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
flock($fp, LOCK_SH);
|
||||
$ret = stream_get_contents($fp);
|
||||
flock($fp, LOCK_UN);
|
||||
fclose($fp);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the cache path used
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a usable temp directory
|
||||
*
|
||||
* @return string
|
||||
* @deprecated
|
||||
*/
|
||||
public static function tmp()
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated in Minfy 3.0', E_USER_DEPRECATED);
|
||||
|
||||
return sys_get_temp_dir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to the Minify logger
|
||||
* @param string $msg
|
||||
* @return null
|
||||
* @deprecated Use $this->logger
|
||||
*/
|
||||
protected function _log($msg)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated in Minify 3.0.', E_USER_DEPRECATED);
|
||||
$this->logger->warning($msg);
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@
|
||||
|
||||
/**
|
||||
* Memcache-based cache class for Minify
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* // fall back to disk caching if memcache can't connect
|
||||
* $memcache = new Memcache;
|
||||
@@ -17,45 +17,43 @@
|
||||
* }
|
||||
* </code>
|
||||
**/
|
||||
class Minify_Cache_Memcache {
|
||||
|
||||
class Minify_Cache_Memcache implements Minify_CacheInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a Minify_Cache_Memcache object, to be passed to
|
||||
* Create a Minify_Cache_Memcache object, to be passed to
|
||||
* Minify::setCache().
|
||||
*
|
||||
* @param Memcache $memcache already-connected instance
|
||||
*
|
||||
*
|
||||
* @param int $expire seconds until expiration (default = 0
|
||||
* meaning the item will not get an expiration date)
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function __construct($memcache, $expire = 0)
|
||||
{
|
||||
$this->_mc = $memcache;
|
||||
$this->_exp = $expire;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
public function store($id, $data)
|
||||
{
|
||||
return $this->_mc->set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", 0, $this->_exp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the size of a cache entry
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
*
|
||||
* @return int size in bytes
|
||||
*/
|
||||
public function getSize($id)
|
||||
@@ -63,25 +61,28 @@ class Minify_Cache_Memcache {
|
||||
if (! $this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||
? mb_strlen($this->_data, '8bit')
|
||||
: strlen($this->_data);
|
||||
|
||||
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
|
||||
return mb_strlen($this->_data, '8bit');
|
||||
} else {
|
||||
return strlen($this->_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does a valid cache entry exist?
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
*
|
||||
* @param int $srcMtime mtime of the original source file(s)
|
||||
*
|
||||
*
|
||||
* @return bool exists
|
||||
*/
|
||||
public function isValid($id, $srcMtime)
|
||||
{
|
||||
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the cached content to output
|
||||
*
|
||||
@@ -89,38 +90,34 @@ class Minify_Cache_Memcache {
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
echo $this->_fetch($id)
|
||||
? $this->_data
|
||||
: '';
|
||||
echo $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Fetch the cached content
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? $this->_data
|
||||
: '';
|
||||
return $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
|
||||
private $_mc = null;
|
||||
private $_exp = null;
|
||||
|
||||
|
||||
// cache of most recently fetched id
|
||||
private $_lm = null;
|
||||
private $_data = null;
|
||||
private $_id = null;
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Fetch data and timestamp from memcache, store in instance
|
||||
*
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
private function _fetch($id)
|
||||
@@ -128,13 +125,17 @@ class Minify_Cache_Memcache {
|
||||
if ($this->_id === $id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ret = $this->_mc->get($id);
|
||||
if (false === $ret) {
|
||||
$this->_id = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||
$this->_id = $id;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
67
lib/Minify/Cache/Null.php
Normal file
67
lib/Minify/Cache/Null.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class Minify_Cache_Null
|
||||
*
|
||||
* If this is used, Minify will not use a cache and, for each 200 response, will
|
||||
* need to recombine files, minify and encode the output.
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
class Minify_Cache_Null implements Minify_CacheInterface
|
||||
{
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
* @param string $data
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
public function store($id, $data)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a cache entry
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*
|
||||
* @return int size in bytes
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a valid cache entry exist?
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
* @param int $srcMtime mtime of the original source file(s)
|
||||
*
|
||||
* @return bool exists
|
||||
*/
|
||||
public function isValid($id, $srcMtime)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the cached content to output
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the cached content
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
}
|
||||
}
|
139
lib/Minify/Cache/WinCache.php
Normal file
139
lib/Minify/Cache/WinCache.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Cache_Wincache
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* WinCache-based cache class for Minify
|
||||
*
|
||||
* <code>
|
||||
* Minify::setCache(new Minify_Cache_WinCache());
|
||||
* </code>
|
||||
*
|
||||
* @package Minify
|
||||
* @author Matthias Fax
|
||||
**/
|
||||
class Minify_Cache_WinCache implements Minify_CacheInterface
|
||||
{
|
||||
/**
|
||||
* Create a Minify_Cache_Wincache object, to be passed to
|
||||
* Minify::setCache().
|
||||
*
|
||||
* @param int $expire seconds until expiration (default = 0
|
||||
* meaning the item will not get an expiration date)
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($expire = 0)
|
||||
{
|
||||
if (!function_exists('wincache_ucache_info')) {
|
||||
throw new Exception("WinCache for PHP is not installed to be able to use Minify_Cache_WinCache!");
|
||||
}
|
||||
$this->_exp = $expire;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
* @param string $data
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
public function store($id, $data)
|
||||
{
|
||||
return wincache_ucache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a cache entry
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
* @return int size in bytes
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
if (!$this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (function_exists('mb_strlen') && ((int) ini_get('mbstring.func_overload') & 2)) {
|
||||
return mb_strlen($this->_data, '8bit');
|
||||
} else {
|
||||
return strlen($this->_data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a valid cache entry exist?
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
* @param int $srcMtime mtime of the original source file(s)
|
||||
*
|
||||
* @return bool exists
|
||||
*/
|
||||
public function isValid($id, $srcMtime)
|
||||
{
|
||||
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the cached content to output
|
||||
*
|
||||
* @param string $id cache id
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
echo $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the cached content
|
||||
*
|
||||
* @param string $id cache id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
return $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
private $_exp = null;
|
||||
|
||||
// cache of most recently fetched id
|
||||
private $_lm = null;
|
||||
private $_data = null;
|
||||
private $_id = null;
|
||||
|
||||
/**
|
||||
* Fetch data and timestamp from WinCache, store in instance
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
private function _fetch($id)
|
||||
{
|
||||
if ($this->_id === $id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$suc = false;
|
||||
$ret = wincache_ucache_get($id, $suc);
|
||||
if (!$suc) {
|
||||
$this->_id = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||
$this->_id = $id;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -17,7 +17,8 @@
|
||||
* @package Minify
|
||||
* @author Elan Ruusamäe <glen@delfi.ee>
|
||||
**/
|
||||
class Minify_Cache_XCache {
|
||||
class Minify_Cache_XCache implements Minify_CacheInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a Minify_Cache_XCache object, to be passed to
|
||||
@@ -54,9 +55,12 @@ class Minify_Cache_XCache {
|
||||
if (! $this->_fetch($id)) {
|
||||
return false;
|
||||
}
|
||||
return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
|
||||
? mb_strlen($this->_data, '8bit')
|
||||
: strlen($this->_data);
|
||||
|
||||
if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
|
||||
return mb_strlen($this->_data, '8bit');
|
||||
} else {
|
||||
return strlen($this->_data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,9 +82,7 @@ class Minify_Cache_XCache {
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
echo $this->_fetch($id)
|
||||
? $this->_data
|
||||
: '';
|
||||
echo $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,9 +93,7 @@ class Minify_Cache_XCache {
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? $this->_data
|
||||
: '';
|
||||
return $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
private $_exp = null;
|
||||
@@ -114,13 +114,17 @@ class Minify_Cache_XCache {
|
||||
if ($this->_id === $id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ret = xcache_get($id);
|
||||
if (false === $ret) {
|
||||
$this->_id = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||
$this->_id = $id;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -4,12 +4,11 @@
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* ZendPlatform-based cache class for Minify
|
||||
*
|
||||
* Based on Minify_Cache_APC, uses output_cache_get/put (currently deprecated)
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* Minify::setCache(new Minify_Cache_ZendPlatform());
|
||||
* </code>
|
||||
@@ -17,8 +16,8 @@
|
||||
* @package Minify
|
||||
* @author Patrick van Dissel
|
||||
*/
|
||||
class Minify_Cache_ZendPlatform {
|
||||
|
||||
class Minify_Cache_ZendPlatform implements Minify_CacheInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a Minify_Cache_ZendPlatform object, to be passed to
|
||||
@@ -27,14 +26,12 @@ class Minify_Cache_ZendPlatform {
|
||||
* @param int $expire seconds until expiration (default = 0
|
||||
* meaning the item will not get an expiration date)
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function __construct($expire = 0)
|
||||
{
|
||||
$this->_exp = $expire;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
@@ -49,7 +46,6 @@ class Minify_Cache_ZendPlatform {
|
||||
return output_cache_put($id, "{$_SERVER['REQUEST_TIME']}|{$data}");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the size of a cache entry
|
||||
*
|
||||
@@ -59,12 +55,9 @@ class Minify_Cache_ZendPlatform {
|
||||
*/
|
||||
public function getSize($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? strlen($this->_data)
|
||||
: false;
|
||||
return $this->_fetch($id) ? strlen($this->_data) : false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does a valid cache entry exist?
|
||||
*
|
||||
@@ -76,11 +69,9 @@ class Minify_Cache_ZendPlatform {
|
||||
*/
|
||||
public function isValid($id, $srcMtime)
|
||||
{
|
||||
$ret = ($this->_fetch($id) && ($this->_lm >= $srcMtime));
|
||||
return $ret;
|
||||
return ($this->_fetch($id) && ($this->_lm >= $srcMtime));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send the cached content to output
|
||||
*
|
||||
@@ -88,12 +79,9 @@ class Minify_Cache_ZendPlatform {
|
||||
*/
|
||||
public function display($id)
|
||||
{
|
||||
echo $this->_fetch($id)
|
||||
? $this->_data
|
||||
: '';
|
||||
echo $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch the cached content
|
||||
*
|
||||
@@ -103,21 +91,16 @@ class Minify_Cache_ZendPlatform {
|
||||
*/
|
||||
public function fetch($id)
|
||||
{
|
||||
return $this->_fetch($id)
|
||||
? $this->_data
|
||||
: '';
|
||||
return $this->_fetch($id) ? $this->_data : '';
|
||||
}
|
||||
|
||||
|
||||
private $_exp = null;
|
||||
|
||||
|
||||
// cache of most recently fetched id
|
||||
private $_lm = null;
|
||||
private $_data = null;
|
||||
private $_id = null;
|
||||
|
||||
|
||||
/**
|
||||
* Fetch data and timestamp from ZendPlatform, store in instance
|
||||
*
|
||||
@@ -130,13 +113,17 @@ class Minify_Cache_ZendPlatform {
|
||||
if ($this->_id === $id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ret = output_cache_get($id, $this->_exp);
|
||||
if (false === $ret) {
|
||||
$this->_id = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
list($this->_lm, $this->_data) = explode('|', $ret, 2);
|
||||
$this->_id = $id;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
58
lib/Minify/CacheInterface.php
Normal file
58
lib/Minify/CacheInterface.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface Minify_CacheInterface
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for Minify cache adapters
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
interface Minify_CacheInterface
|
||||
{
|
||||
/**
|
||||
* Write data to cache.
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
* @param string $data
|
||||
*
|
||||
* @return bool success
|
||||
*/
|
||||
public function store($id, $data);
|
||||
|
||||
/**
|
||||
* Get the size of a cache entry
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*
|
||||
* @return int size in bytes
|
||||
*/
|
||||
public function getSize($id);
|
||||
|
||||
/**
|
||||
* Does a valid cache entry exist?
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
* @param int $srcMtime mtime of the original source file(s)
|
||||
*
|
||||
* @return bool exists
|
||||
*/
|
||||
public function isValid($id, $srcMtime);
|
||||
|
||||
/**
|
||||
* Send the cached content to output
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*/
|
||||
public function display($id);
|
||||
|
||||
/**
|
||||
* Fetch the cached content
|
||||
*
|
||||
* @param string $id cache id (e.g. a filename)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch($id);
|
||||
}
|
240
lib/Minify/ClosureCompiler.php
Normal file
240
lib/Minify/ClosureCompiler.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_ClosureCompiler
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress Javascript using the Closure Compiler
|
||||
*
|
||||
* You must set $jarFile and $tempDir before calling the minify functions.
|
||||
* Also, depending on your shell's environment, you may need to specify
|
||||
* the full path to java in $javaExecutable or use putenv() to setup the
|
||||
* Java environment.
|
||||
*
|
||||
* <code>
|
||||
* Minify_ClosureCompiler::$jarFile = '/path/to/closure-compiler-20120123.jar';
|
||||
* Minify_ClosureCompiler::$tempDir = '/tmp';
|
||||
* $code = Minify_ClosureCompiler::minify(
|
||||
* $code,
|
||||
* array('compilation_level' => 'SIMPLE_OPTIMIZATIONS')
|
||||
* );
|
||||
*
|
||||
* --compilation_level WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS
|
||||
*
|
||||
* </code>
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @author Elan Ruusamäe <glen@delfi.ee>
|
||||
*/
|
||||
class Minify_ClosureCompiler
|
||||
{
|
||||
public static $isDebug = false;
|
||||
|
||||
/**
|
||||
* Filepath of the Closure Compiler jar file. This must be set before
|
||||
* calling minifyJs().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $jarFile;
|
||||
|
||||
/**
|
||||
* Writable temp directory. This must be set before calling minifyJs().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $tempDir;
|
||||
|
||||
/**
|
||||
* Filepath of "java" executable (may be needed if not in shell's PATH)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $javaExecutable = 'java';
|
||||
|
||||
/**
|
||||
* Default command line options passed to closure-compiler
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $defaultOptions = array(
|
||||
'charset' => 'utf-8',
|
||||
'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
|
||||
'warning_level' => 'QUIET',
|
||||
);
|
||||
|
||||
/**
|
||||
* Minify a Javascript string
|
||||
*
|
||||
* @param string $js
|
||||
* @param array $options (verbose is ignored)
|
||||
* @see https://code.google.com/p/closure-compiler/source/browse/trunk/README
|
||||
* @return string
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
public static function minify($js, $options = array())
|
||||
{
|
||||
$min = new static();
|
||||
|
||||
return $min->process($js, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process $js using $options.
|
||||
*
|
||||
* @param string $js
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
public function process($js, $options)
|
||||
{
|
||||
$tmpFile = $this->dumpFile(self::$tempDir, $js);
|
||||
try {
|
||||
$result = $this->compile($tmpFile, $options);
|
||||
} catch (Exception $e) {
|
||||
unlink($tmpFile);
|
||||
throw $e;
|
||||
}
|
||||
unlink($tmpFile);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tmpFile
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
protected function compile($tmpFile, $options)
|
||||
{
|
||||
$command = $this->getCommand($options, $tmpFile);
|
||||
|
||||
return implode("\n", $this->shell($command));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $userOptions
|
||||
* @param string $tmpFile
|
||||
* @return string
|
||||
*/
|
||||
protected function getCommand($userOptions, $tmpFile)
|
||||
{
|
||||
$args = array_merge(
|
||||
$this->getCompilerCommandLine(),
|
||||
$this->getOptionsCommandLine($userOptions)
|
||||
);
|
||||
|
||||
return implode(' ', $args) . ' ' . escapeshellarg($tmpFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
protected function getCompilerCommandLine()
|
||||
{
|
||||
$this->checkJar(self::$jarFile);
|
||||
$server = array(
|
||||
self::$javaExecutable,
|
||||
'-jar',
|
||||
escapeshellarg(self::$jarFile)
|
||||
);
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $userOptions
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptionsCommandLine($userOptions)
|
||||
{
|
||||
$args = array();
|
||||
|
||||
$options = array_merge(
|
||||
static::$defaultOptions,
|
||||
$userOptions
|
||||
);
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
$args[] = "--{$key} " . escapeshellarg($value);
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $jarFile
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
protected function checkJar($jarFile)
|
||||
{
|
||||
if (!is_file($jarFile)) {
|
||||
throw new Minify_ClosureCompiler_Exception('$jarFile(' . $jarFile . ') is not a valid file.');
|
||||
}
|
||||
if (!is_readable($jarFile)) {
|
||||
throw new Minify_ClosureCompiler_Exception('$jarFile(' . $jarFile . ') is not readable.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tempDir
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
protected function checkTempdir($tempDir)
|
||||
{
|
||||
if ($tempDir === null || !is_dir($tempDir)) {
|
||||
throw new Minify_ClosureCompiler_Exception('$tempDir(' . $tempDir . ') is not a valid direcotry.');
|
||||
}
|
||||
if (!is_writable($tempDir)) {
|
||||
throw new Minify_ClosureCompiler_Exception('$tempDir(' . $tempDir . ') is not writable.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write $content to a temporary file residing in $dir.
|
||||
*
|
||||
* @param string $dir
|
||||
* @param string $content
|
||||
* @return string
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
protected function dumpFile($dir, $content)
|
||||
{
|
||||
$this->checkTempdir($dir);
|
||||
$tmpFile = tempnam($dir, 'cc_');
|
||||
if (!$tmpFile) {
|
||||
throw new Minify_ClosureCompiler_Exception('Could not create temp file in "' . $dir . '".');
|
||||
}
|
||||
file_put_contents($tmpFile, $content);
|
||||
|
||||
return $tmpFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute command, throw if exit code is not in $expectedCodes array
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $expectedCodes
|
||||
* @return mixed
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
protected function shell($command, $expectedCodes = array(0))
|
||||
{
|
||||
exec($command, $output, $result_code);
|
||||
if (!in_array($result_code, $expectedCodes)) {
|
||||
throw new Minify_ClosureCompiler_Exception("Unpexpected return code: $result_code");
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
class Minify_ClosureCompiler_Exception extends Exception
|
||||
{
|
||||
}
|
@@ -1,41 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_CommentPreserver
|
||||
* Class Minify_CommentPreserver
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Process a string in pieces preserving C-style comments that begin with "/*!"
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_CommentPreserver {
|
||||
|
||||
class Minify_CommentPreserver
|
||||
{
|
||||
|
||||
/**
|
||||
* String to be prepended to each preserved comment
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $prepend = "\n";
|
||||
|
||||
|
||||
/**
|
||||
* String to be appended to each preserved comment
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $append = "\n";
|
||||
|
||||
|
||||
/**
|
||||
* Process a string outside of C-style comments that begin with "/*!"
|
||||
*
|
||||
* On each non-empty string outside these comments, the given processor
|
||||
* function will be called. The comments will be surrounded by
|
||||
* On each non-empty string outside these comments, the given processor
|
||||
* function will be called. The comments will be surrounded by
|
||||
* Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
|
||||
*
|
||||
*
|
||||
* @param string $content
|
||||
* @param callback $processor function
|
||||
* @param array $args array of extra arguments to pass to the processor
|
||||
* @param array $args array of extra arguments to pass to the processor
|
||||
* function (default = array())
|
||||
* @return string
|
||||
*/
|
||||
@@ -47,7 +48,7 @@ class Minify_CommentPreserver {
|
||||
if ('' !== $beforeComment) {
|
||||
$callArgs = $args;
|
||||
array_unshift($callArgs, $beforeComment);
|
||||
$ret .= call_user_func_array($processor, $callArgs);
|
||||
$ret .= call_user_func_array($processor, $callArgs);
|
||||
}
|
||||
if (false === $comment) {
|
||||
break;
|
||||
@@ -55,35 +56,32 @@ class Minify_CommentPreserver {
|
||||
$ret .= $comment;
|
||||
$content = $afterComment;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract comments that YUI Compressor preserves.
|
||||
*
|
||||
*
|
||||
* @param string $in input
|
||||
*
|
||||
*
|
||||
* @return array 3 elements are returned. If a YUI comment is found, the
|
||||
* 2nd element is the comment and the 1st and 3rd are the surrounding
|
||||
* strings. If no comment is found, the entire string is returned as the
|
||||
* strings. If no comment is found, the entire string is returned as the
|
||||
* 1st element and the other two are false.
|
||||
*/
|
||||
private static function _nextComment($in)
|
||||
{
|
||||
if (
|
||||
false === ($start = strpos($in, '/*!'))
|
||||
|| false === ($end = strpos($in, '*/', $start + 3))
|
||||
) {
|
||||
if (false === ($start = strpos($in, '/*!')) || false === ($end = strpos($in, '*/', $start + 3))) {
|
||||
return array($in, false, false);
|
||||
}
|
||||
$ret = array(
|
||||
substr($in, 0, $start)
|
||||
,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append
|
||||
);
|
||||
|
||||
$beforeComment = substr($in, 0, $start);
|
||||
$comment = self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append;
|
||||
|
||||
$endChars = (strlen($in) - $end - 2);
|
||||
$ret[] = (0 === $endChars)
|
||||
? ''
|
||||
: substr($in, -$endChars);
|
||||
return $ret;
|
||||
$afterComment = (0 === $endChars) ? '' : substr($in, -$endChars);
|
||||
|
||||
return array($beforeComment, $comment, $afterComment);
|
||||
}
|
||||
}
|
78
lib/Minify/Config.php
Normal file
78
lib/Minify/Config.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Minify;
|
||||
|
||||
use Minify_CacheInterface;
|
||||
|
||||
class Config
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $enableBuilder = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $enableStatic = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $concatOnly = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $builderPassword = 'admin';
|
||||
|
||||
/**
|
||||
* @var bool|object
|
||||
*/
|
||||
public $errorLogger = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $allowDebugFlag = false;
|
||||
|
||||
/**
|
||||
* @var string|Minify_CacheInterface
|
||||
*/
|
||||
public $cachePath = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $documentRoot = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $cacheFileLocking = true;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $serveOptions = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $symlinks = array();
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $uploaderHoursBehind = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $envArgs = array();
|
||||
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
public $factories = array();
|
||||
}
|
81
lib/Minify/Controller/Base.php
Normal file
81
lib/Minify/Controller/Base.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Controller_Base
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* Base class for Minify controller
|
||||
*
|
||||
* The controller class validates a request and uses it to create a configuration for Minify::serve().
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
abstract class Minify_Controller_Base implements Minify_ControllerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Minify_Env
|
||||
*/
|
||||
protected $env;
|
||||
|
||||
/**
|
||||
* @var Minify_Source_Factory
|
||||
*/
|
||||
protected $sourceFactory;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @param Minify_Env $env
|
||||
* @param Minify_Source_Factory $sourceFactory
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(Minify_Env $env, Minify_Source_Factory $sourceFactory, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->env = $env;
|
||||
$this->sourceFactory = $sourceFactory;
|
||||
if (!$logger) {
|
||||
$logger = new Logger('minify');
|
||||
}
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create controller sources and options for Minify::serve()
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
*
|
||||
* @return Minify_ServeConfiguration
|
||||
*/
|
||||
abstract public function createConfiguration(array $options);
|
||||
|
||||
/**
|
||||
* Send message to the Minify logger
|
||||
*
|
||||
* @param string $msg
|
||||
*
|
||||
* @return null
|
||||
* @deprecated use $this->logger
|
||||
*/
|
||||
public function log($msg)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated in Minify 3.0.', E_USER_DEPRECATED);
|
||||
$this->logger->info($msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEnv()
|
||||
{
|
||||
return $this->env;
|
||||
}
|
||||
}
|
70
lib/Minify/Controller/Files.php
Normal file
70
lib/Minify/Controller/Files.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Controller_Files
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* Controller class for minifying a set of files
|
||||
*
|
||||
* E.g. the following would serve the minified Javascript for a site
|
||||
* <code>
|
||||
* $options = [
|
||||
* 'checkAllowDirs' => false, // allow files to be anywhere
|
||||
* ];
|
||||
* $sourceFactory = new Minify_Source_Factory($env, $options, $cache);
|
||||
* $controller = new Minify_Controller_Files($env, $sourceFactory);
|
||||
* $minify->serve($controller, [
|
||||
* 'files' => [
|
||||
* '//js/jquery.js',
|
||||
* '//js/plugins.js',
|
||||
* '/home/username/file.js',
|
||||
* ],
|
||||
* ]);
|
||||
* </code>
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_Controller_Files extends Minify_Controller_Base
|
||||
{
|
||||
|
||||
/**
|
||||
* Set up file sources
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
* @return Minify_ServeConfiguration
|
||||
*
|
||||
* Controller options:
|
||||
*
|
||||
* 'files': (required) array of complete file paths, or a single path
|
||||
*/
|
||||
public function createConfiguration(array $options)
|
||||
{
|
||||
// strip controller options
|
||||
|
||||
$files = $options['files'];
|
||||
// if $files is a single object, casting will break it
|
||||
if (is_object($files)) {
|
||||
$files = array($files);
|
||||
} elseif (! is_array($files)) {
|
||||
$files = (array)$files;
|
||||
}
|
||||
unset($options['files']);
|
||||
|
||||
$sources = array();
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
$sources[] = $this->sourceFactory->makeSource($file);
|
||||
} catch (Minify_Source_FactoryException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
}
|
||||
|
||||
return new Minify_ServeConfiguration($options, $sources);
|
||||
}
|
||||
}
|
75
lib/Minify/Controller/Groups.php
Normal file
75
lib/Minify/Controller/Groups.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Controller_Groups
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller class for serving predetermined groups of minimized sets, selected
|
||||
* by PATH_INFO
|
||||
*
|
||||
* <code>
|
||||
* Minify::serve('Groups', array(
|
||||
* 'groups' => array(
|
||||
* 'css' => array('//css/type.css', '//css/layout.css')
|
||||
* ,'js' => array('//js/jquery.js', '//js/site.js')
|
||||
* )
|
||||
* ));
|
||||
* </code>
|
||||
*
|
||||
* If the above code were placed in /serve.php, it would enable the URLs
|
||||
* /serve.php/js and /serve.php/css
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_Controller_Groups extends Minify_Controller_Files
|
||||
{
|
||||
|
||||
/**
|
||||
* Set up groups of files as sources
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
*
|
||||
* 'groups': (required) array mapping PATH_INFO strings to arrays
|
||||
* of complete file paths. @see Minify_Controller_Groups
|
||||
*
|
||||
* @return array Minify options
|
||||
*/
|
||||
public function createConfiguration(array $options)
|
||||
{
|
||||
// strip controller options
|
||||
$groups = $options['groups'];
|
||||
unset($options['groups']);
|
||||
|
||||
$server = $this->env->server();
|
||||
|
||||
// mod_fcgid places PATH_INFO in ORIG_PATH_INFO
|
||||
if (isset($server['ORIG_PATH_INFO'])) {
|
||||
$pathInfo = substr($server['ORIG_PATH_INFO'], 1);
|
||||
} elseif (isset($server['PATH_INFO'])) {
|
||||
$pathInfo = substr($server['PATH_INFO'], 1);
|
||||
} else {
|
||||
$pathInfo = false;
|
||||
}
|
||||
|
||||
if (false === $pathInfo || ! isset($groups[$pathInfo])) {
|
||||
// no PATH_INFO or not a valid group
|
||||
$this->logger->info("Missing PATH_INFO or no group set for \"$pathInfo\"");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
|
||||
$files = $groups[$pathInfo];
|
||||
// if $files is a single object, casting will break it
|
||||
if (is_object($files)) {
|
||||
$files = array($files);
|
||||
} elseif (! is_array($files)) {
|
||||
$files = (array)$files;
|
||||
}
|
||||
|
||||
$options['files'] = $files;
|
||||
|
||||
return parent::createConfiguration($options);
|
||||
}
|
||||
}
|
196
lib/Minify/Controller/MinApp.php
Normal file
196
lib/Minify/Controller/MinApp.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Controller_MinApp
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller class for requests to /min/index.php
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_Controller_MinApp extends Minify_Controller_Base
|
||||
{
|
||||
|
||||
/**
|
||||
* Set up groups of files as sources
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
*
|
||||
* @return array Minify options
|
||||
*/
|
||||
public function createConfiguration(array $options)
|
||||
{
|
||||
// PHP insecure by default: realpath() and other FS functions can't handle null bytes.
|
||||
$get = $this->env->get();
|
||||
foreach (array('g', 'b', 'f') as $key) {
|
||||
if (isset($get[$key])) {
|
||||
$get[$key] = str_replace("\x00", '', (string)$get[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
// filter controller options
|
||||
$defaults = array(
|
||||
'groupsOnly' => false,
|
||||
'groups' => array(),
|
||||
'symlinks' => array(),
|
||||
);
|
||||
$minApp = isset($options['minApp']) ? $options['minApp'] : array();
|
||||
$localOptions = array_merge($defaults, $minApp);
|
||||
|
||||
unset($options['minApp']);
|
||||
|
||||
// normalize $symlinks in order to map to target
|
||||
$symlinks = array();
|
||||
foreach ($localOptions['symlinks'] as $link => $target) {
|
||||
if (0 === strpos($link, '//')) {
|
||||
$link = rtrim(substr($link, 1), '/') . '/';
|
||||
$target = rtrim($target, '/\\');
|
||||
$symlinks[$link] = $target;
|
||||
}
|
||||
}
|
||||
|
||||
$sources = array();
|
||||
$selectionId = '';
|
||||
$firstMissing = null;
|
||||
|
||||
if (isset($get['g'])) {
|
||||
// add group(s)
|
||||
$selectionId .= 'g=' . $get['g'];
|
||||
$keys = explode(',', $get['g']);
|
||||
if ($keys != array_unique($keys)) {
|
||||
$this->logger->info("Duplicate group key found.");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
foreach ($keys as $key) {
|
||||
if (! isset($localOptions['groups'][$key])) {
|
||||
$this->logger->info("A group configuration for \"{$key}\" was not found");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
$files = $localOptions['groups'][$key];
|
||||
// if $files is a single object, casting will break it
|
||||
if (is_object($files)) {
|
||||
$files = array($files);
|
||||
} elseif (! is_array($files)) {
|
||||
$files = (array)$files;
|
||||
}
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
$source = $this->sourceFactory->makeSource($file);
|
||||
$sources[] = $source;
|
||||
} catch (Minify_Source_FactoryException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
if (null === $firstMissing) {
|
||||
$firstMissing = basename($file);
|
||||
continue;
|
||||
} else {
|
||||
$secondMissing = basename($file);
|
||||
$this->logger->info("More than one file was missing: '$firstMissing', '$secondMissing'");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! $localOptions['groupsOnly'] && isset($get['f'])) {
|
||||
// try user files
|
||||
// The following restrictions are to limit the URLs that minify will
|
||||
// respond to.
|
||||
|
||||
// verify at least one file, files are single comma separated, and are all same extension
|
||||
$validPattern = preg_match('/^[^,]+\\.(css|less|scss|js)(?:,[^,]+\\.\\1)*$/', $get['f'], $m);
|
||||
$hasComment = strpos($get['f'], '//') !== false;
|
||||
$hasEscape = strpos($get['f'], '\\') !== false;
|
||||
|
||||
if (!$validPattern || $hasComment || $hasEscape) {
|
||||
$this->logger->info("GET param 'f' was invalid");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
|
||||
$ext = ".{$m[1]}";
|
||||
$files = explode(',', $get['f']);
|
||||
if ($files != array_unique($files)) {
|
||||
$this->logger->info("Duplicate files were specified");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
|
||||
if (isset($get['b'])) {
|
||||
// check for validity
|
||||
$isValidBase = preg_match('@^[^/]+(?:/[^/]+)*$@', $get['b']);
|
||||
$hasDots = false !== strpos($get['b'], '..');
|
||||
$isDot = $get['b'] === '.';
|
||||
|
||||
if ($isValidBase && !$hasDots && !$isDot) {
|
||||
// valid base
|
||||
$base = "/{$get['b']}/";
|
||||
} else {
|
||||
$this->logger->info("GET param 'b' was invalid");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
} else {
|
||||
$base = '/';
|
||||
}
|
||||
|
||||
$basenames = array(); // just for cache id
|
||||
foreach ($files as $file) {
|
||||
$uri = $base . $file;
|
||||
$path = $this->env->getDocRoot() . $uri;
|
||||
|
||||
// try to rewrite path
|
||||
foreach ($symlinks as $link => $target) {
|
||||
if (0 === strpos($uri, $link)) {
|
||||
$path = $target . DIRECTORY_SEPARATOR . substr($uri, strlen($link));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$source = $this->sourceFactory->makeSource($path);
|
||||
$sources[] = $source;
|
||||
$basenames[] = basename($path, $ext);
|
||||
} catch (Minify_Source_FactoryException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
if (null === $firstMissing) {
|
||||
$firstMissing = $uri;
|
||||
continue;
|
||||
} else {
|
||||
$secondMissing = $uri;
|
||||
$this->logger->info("More than one file was missing: '$firstMissing', '$secondMissing`'");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($selectionId) {
|
||||
$selectionId .= '_f=';
|
||||
}
|
||||
$selectionId .= implode(',', $basenames) . $ext;
|
||||
}
|
||||
|
||||
if (!$sources) {
|
||||
$this->logger->info("No sources to serve");
|
||||
|
||||
return new Minify_ServeConfiguration($options);
|
||||
}
|
||||
|
||||
if (null !== $firstMissing) {
|
||||
array_unshift($sources, new Minify_Source(array(
|
||||
'id' => 'missingFile',
|
||||
// should not cause cache invalidation
|
||||
'lastModified' => 0,
|
||||
// due to caching, filename is unreliable.
|
||||
'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n",
|
||||
'minifier' => 'Minify::nullMinifier',
|
||||
)));
|
||||
}
|
||||
|
||||
return new Minify_ServeConfiguration($options, $sources, $selectionId);
|
||||
}
|
||||
}
|
@@ -1,39 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Controller_Page
|
||||
* Class Minify_Controller_Page
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller class for serving a single HTML page
|
||||
*
|
||||
*
|
||||
* @link http://code.google.com/p/minify/source/browse/trunk/web/examples/1/index.php#59
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_Controller_Page extends Minify_Controller_Base {
|
||||
|
||||
class Minify_Controller_Page extends Minify_Controller_Base
|
||||
{
|
||||
|
||||
/**
|
||||
* Set up source of HTML content
|
||||
*
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
* @return array Minify options
|
||||
*
|
||||
*
|
||||
* Controller options:
|
||||
*
|
||||
*
|
||||
* 'content': (required) HTML markup
|
||||
*
|
||||
*
|
||||
* 'id': (required) id of page (string for use in server-side caching)
|
||||
*
|
||||
*
|
||||
* 'lastModifiedTime': timestamp of when this content changed. This
|
||||
* is recommended to allow both server and client-side caching.
|
||||
*
|
||||
* 'minifyAll': should all CSS and Javascript blocks be individually
|
||||
* minified? (default false)
|
||||
*
|
||||
* @todo Add 'file' option to read HTML file.
|
||||
* 'minifyAll': should all CSS and Javascript blocks be individually
|
||||
* minified? (default false)
|
||||
*/
|
||||
public function setupSources($options) {
|
||||
public function createConfiguration(array $options)
|
||||
{
|
||||
if (isset($options['file'])) {
|
||||
$sourceSpec = array(
|
||||
'filepath' => $options['file']
|
||||
@@ -42,27 +42,27 @@ class Minify_Controller_Page extends Minify_Controller_Base {
|
||||
} else {
|
||||
// strip controller options
|
||||
$sourceSpec = array(
|
||||
'content' => $options['content']
|
||||
,'id' => $options['id']
|
||||
'content' => $options['content'],
|
||||
'id' => $options['id'],
|
||||
);
|
||||
$f = $options['id'];
|
||||
unset($options['content'], $options['id']);
|
||||
}
|
||||
// something like "builder,index.php" or "directory,file.html"
|
||||
$this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,');
|
||||
$selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,');
|
||||
|
||||
if (isset($options['minifyAll'])) {
|
||||
// this will be the 2nd argument passed to Minify_HTML::minify()
|
||||
$sourceSpec['minifyOptions'] = array(
|
||||
'cssMinifier' => array('Minify_CSS', 'minify')
|
||||
,'jsMinifier' => array('JSMin', 'minify')
|
||||
'cssMinifier' => array('Minify_CSSmin', 'minify'),
|
||||
'jsMinifier' => array('JSMin\\JSMin', 'minify'),
|
||||
);
|
||||
unset($options['minifyAll']);
|
||||
}
|
||||
$this->sources[] = new Minify_Source($sourceSpec);
|
||||
|
||||
$options['contentType'] = Minify::TYPE_HTML;
|
||||
return $options;
|
||||
|
||||
$sourceSpec['contentType'] = Minify::TYPE_HTML;
|
||||
$sources[] = new Minify_Source($sourceSpec);
|
||||
|
||||
return new Minify_ServeConfiguration($options, $sources, $selectionId);
|
||||
}
|
||||
}
|
||||
|
22
lib/Minify/ControllerInterface.php
Normal file
22
lib/Minify/ControllerInterface.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
|
||||
interface Minify_ControllerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Create controller sources and options for Minify::serve()
|
||||
*
|
||||
* @param array $options controller and Minify options
|
||||
*
|
||||
* @return Minify_ServeConfiguration
|
||||
*/
|
||||
public function createConfiguration(array $options);
|
||||
|
||||
/**
|
||||
* Get the Env component
|
||||
*
|
||||
* @return Minify_Env
|
||||
*/
|
||||
public function getEnv();
|
||||
}
|
@@ -1,26 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Detect whether request should be debugged
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_DebugDetector {
|
||||
public static function shouldDebugRequest($cookie, $get, $requestUri)
|
||||
{
|
||||
if (isset($get['debug'])) {
|
||||
return true;
|
||||
}
|
||||
if (! empty($cookie['minifyDebug'])) {
|
||||
foreach (preg_split('/\\s+/', $cookie['minifyDebug']) as $debugUri) {
|
||||
$pattern = '@' . preg_quote($debugUri, '@') . '@i';
|
||||
$pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern);
|
||||
if (preg_match($pattern, $requestUri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Detect whether request should be debugged
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_DebugDetector
|
||||
{
|
||||
public static function shouldDebugRequest(Minify_Env $env)
|
||||
{
|
||||
if ($env->get('debug') !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$cookieValue = $env->cookie('minifyDebug');
|
||||
if ($cookieValue) {
|
||||
foreach (preg_split('/\\s+/', $cookieValue) as $debugUri) {
|
||||
$pattern = '@' . preg_quote($debugUri, '@') . '@i';
|
||||
$pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern);
|
||||
if (preg_match($pattern, $env->getRequestUri())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
127
lib/Minify/Env.php
Normal file
127
lib/Minify/Env.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
class Minify_Env
|
||||
{
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDocRoot()
|
||||
{
|
||||
return $this->server['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestUri()
|
||||
{
|
||||
return $this->server['REQUEST_URI'];
|
||||
}
|
||||
|
||||
public function __construct($options = array())
|
||||
{
|
||||
$options = array_merge(array(
|
||||
'server' => $_SERVER,
|
||||
'get' => $_GET,
|
||||
'post' => $_POST,
|
||||
'cookie' => $_COOKIE,
|
||||
), $options);
|
||||
|
||||
$this->server = $options['server'];
|
||||
if (empty($this->server['DOCUMENT_ROOT'])) {
|
||||
$this->server['DOCUMENT_ROOT'] = $this->computeDocRoot($options['server']);
|
||||
} else {
|
||||
$this->server['DOCUMENT_ROOT'] = rtrim($this->server['DOCUMENT_ROOT'], '/\\');
|
||||
}
|
||||
|
||||
$this->server['DOCUMENT_ROOT'] = $this->normalizePath($this->server['DOCUMENT_ROOT']);
|
||||
$this->get = $options['get'];
|
||||
$this->post = $options['post'];
|
||||
$this->cookie = $options['cookie'];
|
||||
}
|
||||
|
||||
public function server($key = null)
|
||||
{
|
||||
if (null === $key) {
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
return isset($this->server[$key]) ? $this->server[$key] : null;
|
||||
}
|
||||
|
||||
public function cookie($key = null, $default = null)
|
||||
{
|
||||
if (null === $key) {
|
||||
return $this->cookie;
|
||||
}
|
||||
|
||||
return isset($this->cookie[$key]) ? $this->cookie[$key] : $default;
|
||||
}
|
||||
|
||||
public function get($key = null, $default = null)
|
||||
{
|
||||
if (null === $key) {
|
||||
return $this->get;
|
||||
}
|
||||
|
||||
return isset($this->get[$key]) ? $this->get[$key] : $default;
|
||||
}
|
||||
|
||||
public function post($key = null, $default = null)
|
||||
{
|
||||
if (null === $key) {
|
||||
return $this->post;
|
||||
}
|
||||
|
||||
return isset($this->post[$key]) ? $this->post[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* turn windows-style slashes into unix-style,
|
||||
* remove trailing slash
|
||||
* and lowercase drive letter
|
||||
*
|
||||
* @param string $path absolute path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function normalizePath($path)
|
||||
{
|
||||
$realpath = realpath($path);
|
||||
if ($realpath) {
|
||||
$path = $realpath;
|
||||
}
|
||||
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = rtrim($path, '/');
|
||||
if (substr($path, 1, 1) === ':') {
|
||||
$path = lcfirst($path);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
protected $server;
|
||||
protected $get;
|
||||
protected $post;
|
||||
protected $cookie;
|
||||
|
||||
/**
|
||||
* Compute $_SERVER['DOCUMENT_ROOT'] for IIS using SCRIPT_FILENAME and SCRIPT_NAME.
|
||||
*
|
||||
* @param array $server
|
||||
* @return string
|
||||
*/
|
||||
protected function computeDocRoot(array $server)
|
||||
{
|
||||
if (isset($server['SERVER_SOFTWARE']) && 0 !== strpos($server['SERVER_SOFTWARE'], 'Microsoft-IIS/')) {
|
||||
throw new InvalidArgumentException('DOCUMENT_ROOT is not provided and could not be computed');
|
||||
}
|
||||
|
||||
$substrLength = strlen($server['SCRIPT_FILENAME']) - strlen($server['SCRIPT_NAME']);
|
||||
$docRoot = substr($server['SCRIPT_FILENAME'], 0, $substrLength);
|
||||
|
||||
return rtrim($docRoot, '\\');
|
||||
}
|
||||
}
|
@@ -1,255 +1,268 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_HTML
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress HTML
|
||||
*
|
||||
* This is a heavy regex-based removal of whitespace, unnecessary comments and
|
||||
* tokens. IE conditional comments are preserved. There are also options to have
|
||||
* STYLE and SCRIPT blocks compressed by callback functions.
|
||||
*
|
||||
* A test suite is available.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_HTML {
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $_jsCleanComments = true;
|
||||
|
||||
/**
|
||||
* "Minify" an HTML page
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($html, $options = array()) {
|
||||
$min = new self($html, $options);
|
||||
return $min->process();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a minifier object
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*/
|
||||
public function __construct($html, $options = array())
|
||||
{
|
||||
$this->_html = str_replace("\r\n", "\n", trim($html));
|
||||
if (isset($options['xhtml'])) {
|
||||
$this->_isXhtml = (bool)$options['xhtml'];
|
||||
}
|
||||
if (isset($options['cssMinifier'])) {
|
||||
$this->_cssMinifier = $options['cssMinifier'];
|
||||
}
|
||||
if (isset($options['jsMinifier'])) {
|
||||
$this->_jsMinifier = $options['jsMinifier'];
|
||||
}
|
||||
if (isset($options['jsCleanComments'])) {
|
||||
$this->_jsCleanComments = (bool)$options['jsCleanComments'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Minify the markeup given in the constructor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
if ($this->_isXhtml === null) {
|
||||
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
|
||||
}
|
||||
|
||||
$this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
|
||||
$this->_placeholders = array();
|
||||
|
||||
// replace SCRIPTs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
|
||||
,array($this, '_removeScriptCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace STYLEs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
|
||||
,array($this, '_removeStyleCB')
|
||||
,$this->_html);
|
||||
|
||||
// remove HTML comments (not containing IE conditional comments).
|
||||
$this->_html = preg_replace_callback(
|
||||
'/<!--([\\s\\S]*?)-->/'
|
||||
,array($this, '_commentCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace PREs with placeholders
|
||||
$this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
|
||||
,array($this, '_removePreCB')
|
||||
,$this->_html);
|
||||
|
||||
// replace TEXTAREAs with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
|
||||
,array($this, '_removeTextareaCB')
|
||||
,$this->_html);
|
||||
|
||||
// trim each line.
|
||||
// @todo take into account attribute values that span multiple lines.
|
||||
$this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
|
||||
|
||||
// remove ws around block/undisplayed elements
|
||||
$this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
|
||||
.'|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
|
||||
.'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
|
||||
.'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
|
||||
.'|ul)\\b[^>]*>)/i', '$1', $this->_html);
|
||||
|
||||
// remove ws outside of all elements
|
||||
$this->_html = preg_replace(
|
||||
'/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
|
||||
,'>$1$2$3<'
|
||||
,$this->_html);
|
||||
|
||||
// use newlines before 1st attribute in open tags (to limit line lengths)
|
||||
$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
|
||||
|
||||
// fill placeholders
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders)
|
||||
,array_values($this->_placeholders)
|
||||
,$this->_html
|
||||
);
|
||||
// issue 229: multi-pass to catch scripts that didn't get replaced in textareas
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders)
|
||||
,array_values($this->_placeholders)
|
||||
,$this->_html
|
||||
);
|
||||
return $this->_html;
|
||||
}
|
||||
|
||||
protected function _commentCB($m)
|
||||
{
|
||||
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
|
||||
? $m[0]
|
||||
: '';
|
||||
}
|
||||
|
||||
protected function _reservePlace($content)
|
||||
{
|
||||
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
|
||||
$this->_placeholders[$placeholder] = $content;
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
protected $_isXhtml = null;
|
||||
protected $_replacementHash = null;
|
||||
protected $_placeholders = array();
|
||||
protected $_cssMinifier = null;
|
||||
protected $_jsMinifier = null;
|
||||
|
||||
protected function _removePreCB($m)
|
||||
{
|
||||
return $this->_reservePlace("<pre{$m[1]}");
|
||||
}
|
||||
|
||||
protected function _removeTextareaCB($m)
|
||||
{
|
||||
return $this->_reservePlace("<textarea{$m[1]}");
|
||||
}
|
||||
|
||||
protected function _removeStyleCB($m)
|
||||
{
|
||||
$openStyle = "<style{$m[1]}";
|
||||
$css = $m[2];
|
||||
// remove HTML comments
|
||||
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
|
||||
|
||||
// remove CDATA section markers
|
||||
$css = $this->_removeCdata($css);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_cssMinifier
|
||||
? $this->_cssMinifier
|
||||
: 'trim';
|
||||
$css = call_user_func($minifier, $css);
|
||||
|
||||
return $this->_reservePlace($this->_needsCdata($css)
|
||||
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
|
||||
: "{$openStyle}{$css}</style>"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeScriptCB($m)
|
||||
{
|
||||
$openScript = "<script{$m[2]}";
|
||||
$js = $m[3];
|
||||
|
||||
// whitespace surrounding? preserve at least one space
|
||||
$ws1 = ($m[1] === '') ? '' : ' ';
|
||||
$ws2 = ($m[4] === '') ? '' : ' ';
|
||||
|
||||
// remove HTML comments (and ending "//" if present)
|
||||
if ($this->_jsCleanComments) {
|
||||
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
|
||||
}
|
||||
|
||||
// remove CDATA section markers
|
||||
$js = $this->_removeCdata($js);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_jsMinifier
|
||||
? $this->_jsMinifier
|
||||
: 'trim';
|
||||
$js = call_user_func($minifier, $js);
|
||||
|
||||
return $this->_reservePlace($this->_needsCdata($js)
|
||||
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
|
||||
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeCdata($str)
|
||||
{
|
||||
return (false !== strpos($str, '<![CDATA['))
|
||||
? str_replace(array('<![CDATA[', ']]>'), '', $str)
|
||||
: $str;
|
||||
}
|
||||
|
||||
protected function _needsCdata($str)
|
||||
{
|
||||
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
|
||||
}
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_HTML
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress HTML
|
||||
*
|
||||
* This is a heavy regex-based removal of whitespace, unnecessary comments and
|
||||
* tokens. IE conditional comments are preserved. There are also options to have
|
||||
* STYLE and SCRIPT blocks compressed by callback functions.
|
||||
*
|
||||
* A test suite is available.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_HTML
|
||||
{
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $_jsCleanComments = true;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $_html;
|
||||
|
||||
/**
|
||||
* "Minify" an HTML page
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($html, $options = array())
|
||||
{
|
||||
$min = new self($html, $options);
|
||||
|
||||
return $min->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a minifier object
|
||||
*
|
||||
* @param string $html
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* 'cssMinifier' : (optional) callback function to process content of STYLE
|
||||
* elements.
|
||||
*
|
||||
* 'jsMinifier' : (optional) callback function to process content of SCRIPT
|
||||
* elements. Note: the type attribute is ignored.
|
||||
*
|
||||
* 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
|
||||
*
|
||||
* 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
|
||||
* unset, minify will sniff for an XHTML doctype.
|
||||
*/
|
||||
public function __construct($html, $options = array())
|
||||
{
|
||||
$this->_html = str_replace("\r\n", "\n", trim($html));
|
||||
if (isset($options['xhtml'])) {
|
||||
$this->_isXhtml = (bool)$options['xhtml'];
|
||||
}
|
||||
if (isset($options['cssMinifier'])) {
|
||||
$this->_cssMinifier = $options['cssMinifier'];
|
||||
}
|
||||
if (isset($options['jsMinifier'])) {
|
||||
$this->_jsMinifier = $options['jsMinifier'];
|
||||
}
|
||||
if (isset($options['jsCleanComments'])) {
|
||||
$this->_jsCleanComments = (bool)$options['jsCleanComments'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify the markeup given in the constructor
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function process()
|
||||
{
|
||||
if ($this->_isXhtml === null) {
|
||||
$this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
|
||||
}
|
||||
|
||||
$this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
|
||||
$this->_placeholders = array();
|
||||
|
||||
// replace SCRIPTs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/iu',
|
||||
array($this, '_removeScriptCB'),
|
||||
$this->_html
|
||||
);
|
||||
|
||||
// replace STYLEs (and minify) with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/iu',
|
||||
array($this, '_removeStyleCB'),
|
||||
$this->_html
|
||||
);
|
||||
|
||||
// remove HTML comments (not containing IE conditional comments).
|
||||
$this->_html = preg_replace_callback(
|
||||
'/<!--([\\s\\S]*?)-->/u',
|
||||
array($this, '_commentCB'),
|
||||
$this->_html
|
||||
);
|
||||
|
||||
// replace PREs with placeholders
|
||||
$this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/iu', array($this, '_removePreCB'), $this->_html);
|
||||
|
||||
// replace TEXTAREAs with placeholders
|
||||
$this->_html = preg_replace_callback(
|
||||
'/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/iu',
|
||||
array($this, '_removeTextareaCB'),
|
||||
$this->_html
|
||||
);
|
||||
|
||||
// trim each line.
|
||||
// @todo take into account attribute values that span multiple lines.
|
||||
$this->_html = preg_replace('/^\\s+|\\s+$/mu', '', $this->_html);
|
||||
|
||||
// remove ws around block/undisplayed elements
|
||||
$this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body'
|
||||
.'|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form'
|
||||
.'|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav'
|
||||
.'|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)'
|
||||
.'|ul|video)\\b[^>]*>)/iu', '$1', $this->_html);
|
||||
|
||||
// remove ws outside of all elements
|
||||
$this->_html = preg_replace(
|
||||
'/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</u',
|
||||
'>$1$2$3<',
|
||||
$this->_html
|
||||
);
|
||||
|
||||
// use newlines before 1st attribute in open tags (to limit line lengths)
|
||||
$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/iu', "$1\n$2", $this->_html);
|
||||
|
||||
// fill placeholders
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders),
|
||||
array_values($this->_placeholders),
|
||||
$this->_html
|
||||
);
|
||||
// issue 229: multi-pass to catch scripts that didn't get replaced in textareas
|
||||
$this->_html = str_replace(
|
||||
array_keys($this->_placeholders),
|
||||
array_values($this->_placeholders),
|
||||
$this->_html
|
||||
);
|
||||
|
||||
return $this->_html;
|
||||
}
|
||||
|
||||
protected function _commentCB($m)
|
||||
{
|
||||
return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<![') || 0 === strpos($m[1], '#'))
|
||||
? $m[0]
|
||||
: '';
|
||||
}
|
||||
|
||||
protected function _reservePlace($content)
|
||||
{
|
||||
$placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
|
||||
$this->_placeholders[$placeholder] = $content;
|
||||
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
protected $_isXhtml;
|
||||
protected $_replacementHash;
|
||||
protected $_placeholders = array();
|
||||
protected $_cssMinifier;
|
||||
protected $_jsMinifier;
|
||||
|
||||
protected function _removePreCB($m)
|
||||
{
|
||||
return $this->_reservePlace("<pre{$m[1]}");
|
||||
}
|
||||
|
||||
protected function _removeTextareaCB($m)
|
||||
{
|
||||
return $this->_reservePlace("<textarea{$m[1]}");
|
||||
}
|
||||
|
||||
protected function _removeStyleCB($m)
|
||||
{
|
||||
$openStyle = "<style{$m[1]}";
|
||||
$css = $m[2];
|
||||
// remove HTML comments
|
||||
$css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/u', '', $css);
|
||||
|
||||
// remove CDATA section markers
|
||||
$css = $this->_removeCdata($css);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_cssMinifier
|
||||
? $this->_cssMinifier
|
||||
: 'trim';
|
||||
$css = call_user_func($minifier, $css);
|
||||
|
||||
return $this->_reservePlace(
|
||||
$this->_needsCdata($css)
|
||||
? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
|
||||
: "{$openStyle}{$css}</style>"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeScriptCB($m)
|
||||
{
|
||||
$openScript = "<script{$m[2]}";
|
||||
$js = $m[3];
|
||||
|
||||
// whitespace surrounding? preserve at least one space
|
||||
$ws1 = ($m[1] === '') ? '' : ' ';
|
||||
$ws2 = ($m[4] === '') ? '' : ' ';
|
||||
|
||||
// remove HTML comments (and ending "//" if present)
|
||||
if ($this->_jsCleanComments) {
|
||||
$js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/u', '', $js);
|
||||
}
|
||||
|
||||
// remove CDATA section markers
|
||||
$js = $this->_removeCdata($js);
|
||||
|
||||
// minify
|
||||
$minifier = $this->_jsMinifier
|
||||
? $this->_jsMinifier
|
||||
: 'trim';
|
||||
$js = call_user_func($minifier, $js);
|
||||
|
||||
return $this->_reservePlace(
|
||||
$this->_needsCdata($js)
|
||||
? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
|
||||
: "{$ws1}{$openScript}{$js}</script>{$ws2}"
|
||||
);
|
||||
}
|
||||
|
||||
protected function _removeCdata($str)
|
||||
{
|
||||
return (false !== strpos($str, '<![CDATA['))
|
||||
? str_replace(array('<![CDATA[', ']]>'), '', $str)
|
||||
: $str;
|
||||
}
|
||||
|
||||
protected function _needsCdata($str)
|
||||
{
|
||||
return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/u', $str));
|
||||
}
|
||||
}
|
@@ -5,19 +5,20 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helpers for writing Minfy URIs into HTML
|
||||
* Helpers for writing Minify URIs into HTML
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_HTML_Helper {
|
||||
class Minify_HTML_Helper
|
||||
{
|
||||
public $rewriteWorks = true;
|
||||
public $minAppUri = '/min';
|
||||
public $groupsConfigFile = '';
|
||||
|
||||
/**
|
||||
* Get an HTML-escaped Minify URI for a group or set of files
|
||||
*
|
||||
*
|
||||
* @param string|array $keyOrFiles a group key or array of filepaths/URIs
|
||||
* @param array $opts options:
|
||||
* 'farExpires' : (default true) append a modified timestamp for cache revving
|
||||
@@ -31,23 +32,26 @@ class Minify_HTML_Helper {
|
||||
public static function getUri($keyOrFiles, $opts = array())
|
||||
{
|
||||
$opts = array_merge(array( // default options
|
||||
'farExpires' => true
|
||||
,'debug' => false
|
||||
,'charset' => 'UTF-8'
|
||||
,'minAppUri' => '/min'
|
||||
,'rewriteWorks' => true
|
||||
,'groupsConfigFile' => ''
|
||||
'farExpires' => true,
|
||||
'debug' => false,
|
||||
'charset' => 'UTF-8',
|
||||
'minAppUri' => '/min',
|
||||
'rewriteWorks' => true,
|
||||
'groupsConfigFile' => self::app()->groupsConfigPath,
|
||||
), $opts);
|
||||
|
||||
$h = new self;
|
||||
$h->minAppUri = $opts['minAppUri'];
|
||||
$h->rewriteWorks = $opts['rewriteWorks'];
|
||||
$h->groupsConfigFile = $opts['groupsConfigFile'];
|
||||
|
||||
if (is_array($keyOrFiles)) {
|
||||
$h->setFiles($keyOrFiles, $opts['farExpires']);
|
||||
} else {
|
||||
$h->setGroup($keyOrFiles, $opts['farExpires']);
|
||||
}
|
||||
$uri = $h->getRawUri($opts['farExpires'], $opts['debug']);
|
||||
|
||||
return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']);
|
||||
}
|
||||
|
||||
@@ -75,6 +79,7 @@ class Minify_HTML_Helper {
|
||||
} elseif ($farExpires && $this->_lastModified) {
|
||||
$path .= "&" . $this->_lastModified;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
@@ -94,9 +99,8 @@ class Minify_HTML_Helper {
|
||||
foreach ($files as $k => $file) {
|
||||
if (0 === strpos($file, '//')) {
|
||||
$file = substr($file, 2);
|
||||
} elseif (0 === strpos($file, '/')
|
||||
|| 1 === strpos($file, ':\\')) {
|
||||
$file = substr($file, strlen($_SERVER['DOCUMENT_ROOT']) + 1);
|
||||
} elseif (0 === strpos($file, '/') || 1 === strpos($file, ':\\')) {
|
||||
$file = substr($file, strlen(self::app()->env->getDocRoot()) + 1);
|
||||
}
|
||||
$file = strtr($file, '\\', '/');
|
||||
$files[$k] = $file;
|
||||
@@ -115,15 +119,19 @@ class Minify_HTML_Helper {
|
||||
$this->_groupKey = $key;
|
||||
if ($checkLastModified) {
|
||||
if (! $this->groupsConfigFile) {
|
||||
$this->groupsConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php';
|
||||
$this->groupsConfigFile = self::app()->groupsConfigPath;
|
||||
}
|
||||
if (is_file($this->groupsConfigFile)) {
|
||||
$gc = (require $this->groupsConfigFile);
|
||||
$keys = explode(',', $key);
|
||||
foreach ($keys as $key) {
|
||||
if (isset($gc[$key])) {
|
||||
$this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified);
|
||||
if (!isset($gc[$key])) {
|
||||
// this can happen if value is null
|
||||
// which could be solved with array_filter
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,26 +147,41 @@ class Minify_HTML_Helper {
|
||||
public static function getLastModified($sources, $lastModified = 0)
|
||||
{
|
||||
$max = $lastModified;
|
||||
$factory = self::app()->sourceFactory;
|
||||
|
||||
/** @var Minify_Source $source */
|
||||
foreach ((array)$sources as $source) {
|
||||
if (is_object($source) && isset($source->lastModified)) {
|
||||
$max = max($max, $source->lastModified);
|
||||
} elseif (is_string($source)) {
|
||||
if (0 === strpos($source, '//')) {
|
||||
$source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1);
|
||||
}
|
||||
if (is_file($source)) {
|
||||
$max = max($max, filemtime($source));
|
||||
}
|
||||
}
|
||||
$source = $factory->makeSource($source);
|
||||
$max = max($max, $source->getLastModified());
|
||||
}
|
||||
|
||||
return $max;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Minify\App $app
|
||||
* @return \Minify\App
|
||||
* @internal
|
||||
*/
|
||||
public static function app(\Minify\App $app = null)
|
||||
{
|
||||
static $cached;
|
||||
if ($app) {
|
||||
$cached = $app;
|
||||
|
||||
return $app;
|
||||
}
|
||||
if ($cached === null) {
|
||||
$cached = (require __DIR__ . '/../../../bootstrap.php');
|
||||
}
|
||||
|
||||
return $cached;
|
||||
}
|
||||
|
||||
protected $_groupKey = null; // if present, URI will be like g=...
|
||||
protected $_filePaths = array();
|
||||
protected $_lastModified = null;
|
||||
|
||||
|
||||
/**
|
||||
* In a given array of strings, find the character they all have at
|
||||
* a particular index
|
||||
@@ -167,7 +190,8 @@ class Minify_HTML_Helper {
|
||||
* @param int $pos index to check
|
||||
* @return mixed a common char or '' if any do not match
|
||||
*/
|
||||
protected static function _getCommonCharAtPos($arr, $pos) {
|
||||
protected static function _getCommonCharAtPos($arr, $pos)
|
||||
{
|
||||
if (!isset($arr[0][$pos])) {
|
||||
return '';
|
||||
}
|
||||
@@ -181,6 +205,7 @@ class Minify_HTML_Helper {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return $c;
|
||||
}
|
||||
|
||||
@@ -191,7 +216,8 @@ class Minify_HTML_Helper {
|
||||
* @param string $minRoot root-relative URI of the "min" application
|
||||
* @return string
|
||||
*/
|
||||
protected static function _getShortestUri($paths, $minRoot = '/min/') {
|
||||
protected static function _getShortestUri($paths, $minRoot = '/min/')
|
||||
{
|
||||
$pos = 0;
|
||||
$base = '';
|
||||
while (true) {
|
||||
@@ -205,7 +231,7 @@ class Minify_HTML_Helper {
|
||||
}
|
||||
$base = preg_replace('@[^/]+$@', '', $base);
|
||||
$uri = $minRoot . 'f=' . implode(',', $paths);
|
||||
|
||||
|
||||
if (substr($base, -1) === '/') {
|
||||
// we have a base dir!
|
||||
$basedPaths = $paths;
|
||||
@@ -216,10 +242,9 @@ class Minify_HTML_Helper {
|
||||
$base = substr($base, 0, strlen($base) - 1);
|
||||
$bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths);
|
||||
|
||||
$uri = strlen($uri) < strlen($bUri)
|
||||
? $uri
|
||||
: $bUri;
|
||||
$uri = strlen($uri) < strlen($bUri) ? $uri : $bUri;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
@@ -1,216 +1,217 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_ImportProcessor
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Linearize a CSS/JS file by including content specified by CSS import
|
||||
* declarations. In CSS files, relative URIs are fixed.
|
||||
*
|
||||
* @imports will be processed regardless of where they appear in the source
|
||||
* files; i.e. @imports commented out or in string content will still be
|
||||
* processed!
|
||||
*
|
||||
* This has a unit test but should be considered "experimental".
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @author Simon Schick <simonsimcity@gmail.com>
|
||||
*/
|
||||
class Minify_ImportProcessor {
|
||||
|
||||
public static $filesIncluded = array();
|
||||
|
||||
public static function process($file)
|
||||
{
|
||||
self::$filesIncluded = array();
|
||||
self::$_isCss = (strtolower(substr($file, -4)) === '.css');
|
||||
$obj = new Minify_ImportProcessor(dirname($file));
|
||||
return $obj->_getContent($file);
|
||||
}
|
||||
|
||||
// allows callback funcs to know the current directory
|
||||
private $_currentDir = null;
|
||||
|
||||
// allows callback funcs to know the directory of the file that inherits this one
|
||||
private $_previewsDir = null;
|
||||
|
||||
// allows _importCB to write the fetched content back to the obj
|
||||
private $_importedContent = '';
|
||||
|
||||
private static $_isCss = null;
|
||||
|
||||
/**
|
||||
* @param String $currentDir
|
||||
* @param String $previewsDir Is only used internally
|
||||
*/
|
||||
private function __construct($currentDir, $previewsDir = "")
|
||||
{
|
||||
$this->_currentDir = $currentDir;
|
||||
$this->_previewsDir = $previewsDir;
|
||||
}
|
||||
|
||||
private function _getContent($file, $is_imported = false)
|
||||
{
|
||||
$file = realpath($file);
|
||||
if (! $file
|
||||
|| in_array($file, self::$filesIncluded)
|
||||
|| false === ($content = @file_get_contents($file))
|
||||
) {
|
||||
// file missing, already included, or failed read
|
||||
return '';
|
||||
}
|
||||
self::$filesIncluded[] = realpath($file);
|
||||
$this->_currentDir = dirname($file);
|
||||
|
||||
// remove UTF-8 BOM if present
|
||||
if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) {
|
||||
$content = substr($content, 3);
|
||||
}
|
||||
// ensure uniform EOLs
|
||||
$content = str_replace("\r\n", "\n", $content);
|
||||
|
||||
// process @imports
|
||||
$content = preg_replace_callback(
|
||||
'/
|
||||
@import\\s+
|
||||
(?:url\\(\\s*)? # maybe url(
|
||||
[\'"]? # maybe quote
|
||||
(.*?) # 1 = URI
|
||||
[\'"]? # maybe end quote
|
||||
(?:\\s*\\))? # maybe )
|
||||
([a-zA-Z,\\s]*)? # 2 = media list
|
||||
; # end token
|
||||
/x'
|
||||
,array($this, '_importCB')
|
||||
,$content
|
||||
);
|
||||
|
||||
// You only need to rework the import-path if the script is imported
|
||||
if (self::$_isCss && $is_imported) {
|
||||
// rewrite remaining relative URIs
|
||||
$content = preg_replace_callback(
|
||||
'/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||
,array($this, '_urlCB')
|
||||
,$content
|
||||
);
|
||||
}
|
||||
|
||||
return $this->_importedContent . $content;
|
||||
}
|
||||
|
||||
private function _importCB($m)
|
||||
{
|
||||
$url = $m[1];
|
||||
$mediaList = preg_replace('/\\s+/', '', $m[2]);
|
||||
|
||||
if (strpos($url, '://') > 0) {
|
||||
// protocol, leave in place for CSS, comment for JS
|
||||
return self::$_isCss
|
||||
? $m[0]
|
||||
: "/* Minify_ImportProcessor will not include remote content */";
|
||||
}
|
||||
if ('/' === $url[0]) {
|
||||
// protocol-relative or root path
|
||||
$url = ltrim($url, '/');
|
||||
$file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
|
||||
. strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// relative to current path
|
||||
$file = $this->_currentDir . DIRECTORY_SEPARATOR
|
||||
. strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||
}
|
||||
$obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir);
|
||||
$content = $obj->_getContent($file, true);
|
||||
if ('' === $content) {
|
||||
// failed. leave in place for CSS, comment for JS
|
||||
return self::$_isCss
|
||||
? $m[0]
|
||||
: "/* Minify_ImportProcessor could not fetch '{$file}' */";
|
||||
}
|
||||
return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
|
||||
? $content
|
||||
: "@media {$mediaList} {\n{$content}\n}\n";
|
||||
}
|
||||
|
||||
private function _urlCB($m)
|
||||
{
|
||||
// $m[1] is either quoted or not
|
||||
$quote = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||
? $m[1][0]
|
||||
: '';
|
||||
$url = ($quote === '')
|
||||
? $m[1]
|
||||
: substr($m[1], 1, strlen($m[1]) - 2);
|
||||
if ('/' !== $url[0]) {
|
||||
if (strpos($url, '//') > 0) {
|
||||
// probably starts with protocol, do not alter
|
||||
} else {
|
||||
// prepend path with current dir separator (OS-independent)
|
||||
$path = $this->_currentDir
|
||||
. DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||
// update the relative path by the directory of the file that imported this one
|
||||
$url = self::getPathDiff(realpath($this->_previewsDir), $path);
|
||||
}
|
||||
}
|
||||
return "url({$quote}{$url}{$quote})";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param string $ps
|
||||
* @return string
|
||||
*/
|
||||
private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR)
|
||||
{
|
||||
$realFrom = $this->truepath($from);
|
||||
$realTo = $this->truepath($to);
|
||||
|
||||
$arFrom = explode($ps, rtrim($realFrom, $ps));
|
||||
$arTo = explode($ps, rtrim($realTo, $ps));
|
||||
while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
|
||||
{
|
||||
array_shift($arFrom);
|
||||
array_shift($arTo);
|
||||
}
|
||||
return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is to replace PHP's extremely buggy realpath().
|
||||
* @param string $path The original path, can be relative etc.
|
||||
* @return string The resolved path, it might not exist.
|
||||
* @see http://stackoverflow.com/questions/4049856/replace-phps-realpath
|
||||
*/
|
||||
function truepath($path)
|
||||
{
|
||||
// whether $path is unix or not
|
||||
$unipath = strlen($path) == 0 || $path{0} != '/';
|
||||
// attempts to detect if path is relative in which case, add cwd
|
||||
if (strpos($path, ':') === false && $unipath)
|
||||
$path = $this->_currentDir . DIRECTORY_SEPARATOR . $path;
|
||||
|
||||
// resolve path parts (single dot, double dot and double delimiters)
|
||||
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
|
||||
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
|
||||
$absolutes = array();
|
||||
foreach ($parts as $part) {
|
||||
if ('.' == $part)
|
||||
continue;
|
||||
if ('..' == $part) {
|
||||
array_pop($absolutes);
|
||||
} else {
|
||||
$absolutes[] = $part;
|
||||
}
|
||||
}
|
||||
$path = implode(DIRECTORY_SEPARATOR, $absolutes);
|
||||
// resolve any symlinks
|
||||
if (file_exists($path) && linkinfo($path) > 0)
|
||||
$path = readlink($path);
|
||||
// put initial separator that could have been lost
|
||||
$path = !$unipath ? '/' . $path : $path;
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_ImportProcessor
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Linearize a CSS/JS file by including content specified by CSS import
|
||||
* declarations. In CSS files, relative URIs are fixed.
|
||||
*
|
||||
* @imports will be processed regardless of where they appear in the source
|
||||
* files; i.e. @imports commented out or in string content will still be
|
||||
* processed!
|
||||
*
|
||||
* This has a unit test but should be considered "experimental".
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @author Simon Schick <simonsimcity@gmail.com>
|
||||
*/
|
||||
class Minify_ImportProcessor
|
||||
{
|
||||
public static $filesIncluded = array();
|
||||
|
||||
public static function process($file)
|
||||
{
|
||||
self::$filesIncluded = array();
|
||||
self::$_isCss = (strtolower(substr($file, -4)) === '.css');
|
||||
$obj = new Minify_ImportProcessor(dirname($file));
|
||||
|
||||
return $obj->_getContent($file);
|
||||
}
|
||||
|
||||
// allows callback funcs to know the current directory
|
||||
private $_currentDir;
|
||||
|
||||
// allows callback funcs to know the directory of the file that inherits this one
|
||||
private $_previewsDir;
|
||||
|
||||
// allows _importCB to write the fetched content back to the obj
|
||||
private $_importedContent = '';
|
||||
|
||||
private static $_isCss;
|
||||
|
||||
/**
|
||||
* @param String $currentDir
|
||||
* @param String $previewsDir Is only used internally
|
||||
*/
|
||||
private function __construct($currentDir, $previewsDir = "")
|
||||
{
|
||||
$this->_currentDir = $currentDir;
|
||||
$this->_previewsDir = $previewsDir;
|
||||
}
|
||||
|
||||
private function _getContent($file, $is_imported = false)
|
||||
{
|
||||
$file = preg_replace('~\\?.*~', '', $file);
|
||||
$file = realpath($file);
|
||||
if (! $file
|
||||
|| in_array($file, self::$filesIncluded)
|
||||
|| false === ($content = @file_get_contents($file))) {
|
||||
// file missing, already included, or failed read
|
||||
return '';
|
||||
}
|
||||
self::$filesIncluded[] = realpath($file);
|
||||
$this->_currentDir = dirname($file);
|
||||
|
||||
// remove UTF-8 BOM if present
|
||||
if (pack("CCC", 0xef, 0xbb, 0xbf) === substr($content, 0, 3)) {
|
||||
$content = substr($content, 3);
|
||||
}
|
||||
// ensure uniform EOLs
|
||||
$content = str_replace("\r\n", "\n", $content);
|
||||
|
||||
// process @imports
|
||||
$pattern = '/
|
||||
@import\\s+
|
||||
(?:url\\(\\s*)? # maybe url(
|
||||
[\'"]? # maybe quote
|
||||
(.*?) # 1 = URI
|
||||
[\'"]? # maybe end quote
|
||||
(?:\\s*\\))? # maybe )
|
||||
([a-zA-Z,\\s]*)? # 2 = media list
|
||||
; # end token
|
||||
/x';
|
||||
$content = preg_replace_callback($pattern, array($this, '_importCB'), $content);
|
||||
|
||||
// You only need to rework the import-path if the script is imported
|
||||
if (self::$_isCss && $is_imported) {
|
||||
// rewrite remaining relative URIs
|
||||
$pattern = '/url\\(\\s*([^\\)\\s]+)\\s*\\)/';
|
||||
$content = preg_replace_callback($pattern, array($this, '_urlCB'), $content);
|
||||
}
|
||||
|
||||
return $this->_importedContent . $content;
|
||||
}
|
||||
|
||||
private function _importCB($m)
|
||||
{
|
||||
$url = $m[1];
|
||||
$mediaList = preg_replace('/\\s+/', '', $m[2]);
|
||||
|
||||
if (strpos($url, '://') > 0) {
|
||||
// protocol, leave in place for CSS, comment for JS
|
||||
return self::$_isCss
|
||||
? $m[0]
|
||||
: "/* Minify_ImportProcessor will not include remote content */";
|
||||
}
|
||||
if ('/' === $url[0]) {
|
||||
// protocol-relative or root path
|
||||
$url = ltrim($url, '/');
|
||||
$file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR
|
||||
. strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// relative to current path
|
||||
$file = $this->_currentDir . DIRECTORY_SEPARATOR
|
||||
. strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||
}
|
||||
$obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir);
|
||||
$content = $obj->_getContent($file, true);
|
||||
if ('' === $content) {
|
||||
// failed. leave in place for CSS, comment for JS
|
||||
return self::$_isCss
|
||||
? $m[0]
|
||||
: "/* Minify_ImportProcessor could not fetch '{$file}' */";
|
||||
}
|
||||
|
||||
return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList))
|
||||
? $content
|
||||
: "@media {$mediaList} {\n{$content}\n}\n";
|
||||
}
|
||||
|
||||
private function _urlCB($m)
|
||||
{
|
||||
// $m[1] is either quoted or not
|
||||
$quote = ($m[1][0] === "'" || $m[1][0] === '"') ? $m[1][0] : '';
|
||||
|
||||
$url = ($quote === '') ? $m[1] : substr($m[1], 1, strlen($m[1]) - 2);
|
||||
|
||||
if ('/' !== $url[0]) {
|
||||
if (strpos($url, '//') > 0) {
|
||||
// probably starts with protocol, do not alter
|
||||
} else {
|
||||
// prepend path with current dir separator (OS-independent)
|
||||
$path = $this->_currentDir
|
||||
. DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
|
||||
// update the relative path by the directory of the file that imported this one
|
||||
$url = self::getPathDiff(realpath($this->_previewsDir), $path);
|
||||
}
|
||||
}
|
||||
|
||||
return "url({$quote}{$url}{$quote})";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param string $ps
|
||||
* @return string
|
||||
*/
|
||||
private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR)
|
||||
{
|
||||
$realFrom = $this->truepath($from);
|
||||
$realTo = $this->truepath($to);
|
||||
|
||||
$arFrom = explode($ps, rtrim($realFrom, $ps));
|
||||
$arTo = explode($ps, rtrim($realTo, $ps));
|
||||
while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) {
|
||||
array_shift($arFrom);
|
||||
array_shift($arTo);
|
||||
}
|
||||
|
||||
return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is to replace PHP's extremely buggy realpath().
|
||||
* @param string $path The original path, can be relative etc.
|
||||
* @return string The resolved path, it might not exist.
|
||||
* @see http://stackoverflow.com/questions/4049856/replace-phps-realpath
|
||||
*/
|
||||
private function truepath($path)
|
||||
{
|
||||
// whether $path is unix or not
|
||||
$unipath = ('' === $path) || ($path[0] !== '/');
|
||||
|
||||
// attempts to detect if path is relative in which case, add cwd
|
||||
if (strpos($path, ':') === false && $unipath) {
|
||||
$path = $this->_currentDir . DIRECTORY_SEPARATOR . $path;
|
||||
}
|
||||
|
||||
// resolve path parts (single dot, double dot and double delimiters)
|
||||
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
|
||||
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
|
||||
$absolutes = array();
|
||||
foreach ($parts as $part) {
|
||||
if ('.' === $part) {
|
||||
continue;
|
||||
}
|
||||
if ('..' === $part) {
|
||||
array_pop($absolutes);
|
||||
} else {
|
||||
$absolutes[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
$path = implode(DIRECTORY_SEPARATOR, $absolutes);
|
||||
// resolve any symlinks
|
||||
if (file_exists($path) && linkinfo($path) > 0) {
|
||||
$path = readlink($path);
|
||||
}
|
||||
// put initial separator that could have been lost
|
||||
$path = !$unipath ? '/' . $path : $path;
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
@@ -13,7 +13,8 @@
|
||||
*
|
||||
* @todo can use a stream wrapper to unit test this?
|
||||
*/
|
||||
class Minify_JS_ClosureCompiler {
|
||||
class Minify_JS_ClosureCompiler
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string The option key for the maximum POST byte size
|
||||
@@ -53,7 +54,7 @@ class Minify_JS_ClosureCompiler {
|
||||
/**
|
||||
* @var string $url URL of compiler server. defaults to Google's
|
||||
*/
|
||||
protected $serviceUrl = 'http://closure-compiler.appspot.com/compile';
|
||||
protected $serviceUrl = 'https://closure-compiler.appspot.com/compile';
|
||||
|
||||
/**
|
||||
* @var int $maxBytes The maximum JS size that can be sent to the compiler server in bytes
|
||||
@@ -68,7 +69,7 @@ class Minify_JS_ClosureCompiler {
|
||||
/**
|
||||
* @var callable Function to minify JS if service fails. Default is JSMin
|
||||
*/
|
||||
protected $fallbackMinifier = array('JSMin', 'minify');
|
||||
protected $fallbackMinifier = array('JSMin\\JSMin', 'minify');
|
||||
|
||||
/**
|
||||
* Minify JavaScript code via HTTP request to a Closure Compiler API
|
||||
@@ -81,6 +82,7 @@ class Minify_JS_ClosureCompiler {
|
||||
public static function minify($js, array $options = array())
|
||||
{
|
||||
$obj = new self($options);
|
||||
|
||||
return $obj->min($js);
|
||||
}
|
||||
|
||||
@@ -172,6 +174,9 @@ class Minify_JS_ClosureCompiler {
|
||||
$contents = file_get_contents($this->serviceUrl, false, stream_context_create(array(
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'compilation_level' => 'SIMPLE',
|
||||
'output_format' => 'text',
|
||||
'output_info' => 'compiled_code',
|
||||
'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n",
|
||||
'content' => $postBody,
|
||||
'max_redirects' => 0,
|
||||
@@ -190,13 +195,13 @@ class Minify_JS_ClosureCompiler {
|
||||
curl_close($ch);
|
||||
} else {
|
||||
throw new Minify_JS_ClosureCompiler_Exception(
|
||||
"Could not make HTTP request: allow_url_open is false and cURL not available"
|
||||
"Could not make HTTP request: allow_url_open is false and cURL not available"
|
||||
);
|
||||
}
|
||||
|
||||
if (false === $contents) {
|
||||
throw new Minify_JS_ClosureCompiler_Exception(
|
||||
"No HTTP response from server"
|
||||
"No HTTP response from server"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -227,4 +232,6 @@ class Minify_JS_ClosureCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
class Minify_JS_ClosureCompiler_Exception extends Exception {}
|
||||
class Minify_JS_ClosureCompiler_Exception extends Exception
|
||||
{
|
||||
}
|
48
lib/Minify/JS/JShrink.php
Normal file
48
lib/Minify/JS/JShrink.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify\JS\JShrink
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
namespace Minify\JS;
|
||||
|
||||
/**
|
||||
* Wrapper to Javascript Minifier built in PHP http://www.tedivm.com
|
||||
*
|
||||
* @package Minify
|
||||
* @author Elan Ruusamäe <glen@pld-linux.org>
|
||||
* @link https://github.com/tedious/JShrink
|
||||
*
|
||||
*/
|
||||
class JShrink
|
||||
{
|
||||
/**
|
||||
* Contains the default options for minification. This array is merged with
|
||||
* the one passed in by the user to create the request specific set of
|
||||
* options (stored in the $options attribute).
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $defaultOptions = array('flaggedComments' => true);
|
||||
|
||||
/**
|
||||
* Takes a string containing javascript and removes unneeded characters in
|
||||
* order to shrink the code without altering it's functionality.
|
||||
*
|
||||
* @param string $js The raw javascript to be minified
|
||||
* @param array $options Various runtime options in an associative array
|
||||
*
|
||||
* @see JShrink\Minifier::minify()
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($js, array $options = array())
|
||||
{
|
||||
$options = array_merge(
|
||||
self::$defaultOptions,
|
||||
$options
|
||||
);
|
||||
|
||||
return \JShrink\Minifier::minify($js, $options);
|
||||
}
|
||||
}
|
128
lib/Minify/LessCssSource.php
Normal file
128
lib/Minify/LessCssSource.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
class Minify_LessCssSource extends Minify_Source
|
||||
{
|
||||
/**
|
||||
* @var Minify_CacheInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* Parsed lessphp cache object
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $parsed;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct(array $spec, Minify_CacheInterface $cache)
|
||||
{
|
||||
parent::__construct($spec);
|
||||
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last modified of all parsed files
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
$cache = $this->getCache();
|
||||
|
||||
return $cache['lastModified'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
$cache = $this->getCache();
|
||||
|
||||
return $cache['compiled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lessphp cache object
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getCache()
|
||||
{
|
||||
// cache for single run
|
||||
// so that getLastModified and getContent in single request do not add additional cache roundtrips (i.e memcache)
|
||||
if (isset($this->parsed)) {
|
||||
return $this->parsed;
|
||||
}
|
||||
|
||||
// check from cache first
|
||||
$cache = null;
|
||||
$cacheId = $this->getCacheId();
|
||||
if ($this->cache->isValid($cacheId, 0)) {
|
||||
if ($cache = $this->cache->fetch($cacheId)) {
|
||||
$cache = unserialize($cache);
|
||||
}
|
||||
}
|
||||
|
||||
$less = $this->getCompiler();
|
||||
$input = $cache ? $cache : $this->filepath;
|
||||
$cache = $less->cachedCompile($input);
|
||||
|
||||
if (!is_array($input) || $cache['updated'] > $input['updated']) {
|
||||
$cache['lastModified'] = $this->getMaxLastModified($cache);
|
||||
$this->cache->store($cacheId, serialize($cache));
|
||||
}
|
||||
|
||||
return $this->parsed = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate maximum last modified of all files,
|
||||
* as the 'updated' timestamp in cache is not the same as file last modified timestamp:
|
||||
* @link https://github.com/leafo/lessphp/blob/v0.4.0/lessc.inc.php#L1904
|
||||
* @return int
|
||||
*/
|
||||
private function getMaxLastModified($cache)
|
||||
{
|
||||
$lastModified = 0;
|
||||
foreach ($cache['files'] as $mtime) {
|
||||
$lastModified = max($lastModified, $mtime);
|
||||
}
|
||||
|
||||
return $lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a unique cache id for for this source.
|
||||
*
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getCacheId($prefix = 'minify')
|
||||
{
|
||||
$md5 = md5($this->filepath);
|
||||
|
||||
return "{$prefix}_less2_{$md5}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get instance of less compiler
|
||||
*
|
||||
* @return lessc
|
||||
*/
|
||||
private function getCompiler()
|
||||
{
|
||||
$less = new lessc();
|
||||
// do not spend CPU time letting less doing minify
|
||||
$less->setPreserveComments(true);
|
||||
|
||||
return $less;
|
||||
}
|
||||
}
|
209
lib/Minify/Lines.php
Normal file
209
lib/Minify/Lines.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Lines
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add line numbers in C-style comments for easier debugging of combined content
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
* @author Adam Pedersen (Issue 55 fix)
|
||||
*/
|
||||
class Minify_Lines
|
||||
{
|
||||
|
||||
/**
|
||||
* Add line numbers in C-style comments
|
||||
*
|
||||
* This uses a very basic parser easily fooled by comment tokens inside
|
||||
* strings or regexes, but, otherwise, generally clean code will not be
|
||||
* mangled. URI rewriting can also be performed.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @param array $options available options:
|
||||
*
|
||||
* 'id': (optional) string to identify file. E.g. file name/path
|
||||
*
|
||||
* 'currentDir': (default null) if given, this is assumed to be the
|
||||
* directory of the current CSS file. Using this, minify will rewrite
|
||||
* all relative URIs in import/url declarations to correctly point to
|
||||
* the desired files, and prepend a comment with debugging information about
|
||||
* this process.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minify($content, $options = array())
|
||||
{
|
||||
$id = (isset($options['id']) && $options['id']) ? $options['id'] : '';
|
||||
$content = str_replace("\r\n", "\n", $content);
|
||||
|
||||
$lines = explode("\n", $content);
|
||||
$numLines = count($lines);
|
||||
// determine left padding
|
||||
$padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits
|
||||
$inComment = false;
|
||||
$i = 0;
|
||||
$newLines = array();
|
||||
|
||||
while (null !== ($line = array_shift($lines))) {
|
||||
if (('' !== $id) && (0 === $i % 50)) {
|
||||
if ($inComment) {
|
||||
array_push($newLines, '', "/* {$id} *|", '');
|
||||
} else {
|
||||
array_push($newLines, '', "/* {$id} */", '');
|
||||
}
|
||||
}
|
||||
|
||||
++$i;
|
||||
$newLines[] = self::_addNote($line, $i, $inComment, $padTo);
|
||||
$inComment = self::_eolInComment($line, $inComment);
|
||||
}
|
||||
|
||||
$content = implode("\n", $newLines) . "\n";
|
||||
|
||||
// check for desired URI rewriting
|
||||
if (isset($options['currentDir'])) {
|
||||
Minify_CSS_UriRewriter::$debugText = '';
|
||||
$docRoot = isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT'];
|
||||
$symlinks = isset($options['symlinks']) ? $options['symlinks'] : array();
|
||||
|
||||
$content = Minify_CSS_UriRewriter::rewrite($content, $options['currentDir'], $docRoot, $symlinks);
|
||||
|
||||
$content = "/* Minify_CSS_UriRewriter::\$debugText\n\n"
|
||||
. Minify_CSS_UriRewriter::$debugText . "*/\n"
|
||||
. $content;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the parser within a C-style comment at the end of this line?
|
||||
*
|
||||
* @param string $line current line of code
|
||||
*
|
||||
* @param bool $inComment was the parser in a C-style comment at the
|
||||
* beginning of the previous line?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function _eolInComment($line, $inComment)
|
||||
{
|
||||
while (strlen($line)) {
|
||||
if ($inComment) {
|
||||
// only "*/" can end the comment
|
||||
$index = self::_find($line, '*/');
|
||||
if ($index === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// stop comment and keep walking line
|
||||
$inComment = false;
|
||||
@$line = (string)substr($line, $index + 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
// look for "//" and "/*"
|
||||
$single = self::_find($line, '//');
|
||||
$multi = self::_find($line, '/*');
|
||||
if ($multi === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($single === false || $multi < $single) {
|
||||
// start comment and keep walking line
|
||||
$inComment = true;
|
||||
@$line = (string)substr($line, $multi + 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
// a single-line comment preceeded it
|
||||
return false;
|
||||
}
|
||||
|
||||
return $inComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a comment (or note) to the given line
|
||||
*
|
||||
* @param string $line current line of code
|
||||
*
|
||||
* @param string $note content of note/comment
|
||||
*
|
||||
* @param bool $inComment was the parser in a comment at the
|
||||
* beginning of the line?
|
||||
*
|
||||
* @param int $padTo minimum width of comment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function _addNote($line, $note, $inComment, $padTo)
|
||||
{
|
||||
if ($inComment) {
|
||||
$line = '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line;
|
||||
} else {
|
||||
$line = '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line;
|
||||
}
|
||||
|
||||
return rtrim($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a token trying to avoid false positives
|
||||
*
|
||||
* @param string $str String containing the token
|
||||
* @param string $token Token being checked
|
||||
* @return bool
|
||||
*/
|
||||
private static function _find($str, $token)
|
||||
{
|
||||
switch ($token) {
|
||||
case '//':
|
||||
$fakes = array(
|
||||
'://' => 1,
|
||||
'"//' => 1,
|
||||
'\'//' => 1,
|
||||
'".//' => 2,
|
||||
'\'.//' => 2,
|
||||
);
|
||||
break;
|
||||
case '/*':
|
||||
$fakes = array(
|
||||
'"/*' => 1,
|
||||
'\'/*' => 1,
|
||||
'"//*' => 2,
|
||||
'\'//*' => 2,
|
||||
'".//*' => 3,
|
||||
'\'.//*' => 3,
|
||||
'*/*' => 1,
|
||||
'\\/*' => 1,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
$fakes = array();
|
||||
}
|
||||
|
||||
$index = strpos($str, $token);
|
||||
$offset = 0;
|
||||
|
||||
while ($index !== false) {
|
||||
foreach ($fakes as $fake => $skip) {
|
||||
$check = substr($str, $index - $skip, strlen($fake));
|
||||
if ($check === $fake) {
|
||||
// move offset and scan again
|
||||
$offset += $index + strlen($token);
|
||||
$index = strpos($str, $token, $offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// legitimate find
|
||||
return $index;
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
}
|
24
lib/Minify/Logger/LegacyHandler.php
Normal file
24
lib/Minify/Logger/LegacyHandler.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Minify\Logger;
|
||||
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
|
||||
class LegacyHandler extends AbstractProcessingHandler
|
||||
{
|
||||
private $obj;
|
||||
|
||||
public function __construct($obj)
|
||||
{
|
||||
if (!is_callable(array($obj, 'log'))) {
|
||||
throw new \InvalidArgumentException('$obj must have a public log() method');
|
||||
}
|
||||
$this->obj = $obj;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function write(array $record)
|
||||
{
|
||||
$this->obj->log((string)$record['formatted']);
|
||||
}
|
||||
}
|
113
lib/Minify/NailgunClosureCompiler.php
Normal file
113
lib/Minify/NailgunClosureCompiler.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class Minify_ClosureCompiler
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Run Closure Compiler via NailGun
|
||||
*
|
||||
* @package Minify
|
||||
* @author Elan Ruusamäe <glen@delfi.ee>
|
||||
* @link https://github.com/martylamb/nailgun
|
||||
*/
|
||||
class Minify_NailgunClosureCompiler extends Minify_ClosureCompiler
|
||||
{
|
||||
const NG_SERVER = 'com.martiansoftware.nailgun.NGServer';
|
||||
const CC_MAIN = 'com.google.javascript.jscomp.CommandLineRunner';
|
||||
|
||||
/**
|
||||
* For some reasons Nailgun thinks that it's server
|
||||
* broke the connection and returns 227 instead of 0
|
||||
* We'll just handle this here instead of fixing
|
||||
* the nailgun client itself.
|
||||
*
|
||||
* It also sometimes breaks on 229 on the devbox.
|
||||
* To complete this whole madness and made future
|
||||
* 'fixes' easier I added this nice little array...
|
||||
* @var array
|
||||
*/
|
||||
private static $NG_EXIT_CODES = array(0, 227, 229);
|
||||
|
||||
/**
|
||||
* Filepath of "ng" executable (from Nailgun package)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $ngExecutable = 'ng';
|
||||
|
||||
/**
|
||||
* Filepath of the Nailgun jar file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $ngJarFile;
|
||||
|
||||
/**
|
||||
* Get command to launch NailGun server.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getServerCommandLine()
|
||||
{
|
||||
$this->checkJar(self::$ngJarFile);
|
||||
$this->checkJar(self::$jarFile);
|
||||
|
||||
$classPath = array(
|
||||
self::$ngJarFile,
|
||||
self::$jarFile,
|
||||
);
|
||||
|
||||
// The command for the server that should show up in the process list
|
||||
$server = array(
|
||||
self::$javaExecutable,
|
||||
'-server',
|
||||
'-cp', implode(':', $classPath),
|
||||
self::NG_SERVER,
|
||||
);
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
protected function getCompilerCommandLine()
|
||||
{
|
||||
$server = array(
|
||||
self::$ngExecutable,
|
||||
escapeshellarg(self::CC_MAIN)
|
||||
);
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tmpFile
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @throws Minify_ClosureCompiler_Exception
|
||||
*/
|
||||
protected function compile($tmpFile, $options)
|
||||
{
|
||||
$this->startServer();
|
||||
|
||||
$command = $this->getCommand($options, $tmpFile);
|
||||
|
||||
return implode("\n", $this->shell($command, self::$NG_EXIT_CODES));
|
||||
}
|
||||
|
||||
private function startServer()
|
||||
{
|
||||
$serverCommand = implode(' ', $this->getServerCommandLine());
|
||||
$psCommand = $this->shell("ps -o cmd= -C " . self::$javaExecutable);
|
||||
if (in_array($serverCommand, $psCommand, true)) {
|
||||
// already started!
|
||||
return;
|
||||
}
|
||||
|
||||
$this->shell("$serverCommand </dev/null >/dev/null 2>/dev/null & sleep 10");
|
||||
}
|
||||
}
|
@@ -4,34 +4,28 @@
|
||||
*
|
||||
* To use this class you must first download the PHP port of Packer
|
||||
* and place the file "class.JavaScriptPacker.php" in /lib (or your
|
||||
* include_path).
|
||||
* include_path).
|
||||
* @link http://joliclic.free.fr/php/javascript-packer/en/
|
||||
*
|
||||
* Be aware that, as long as HTTP encoding is used, scripts minified with JSMin
|
||||
* will provide better client-side performance, as they need not be unpacked in
|
||||
* client-side code.
|
||||
*
|
||||
* @package Minify
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
if (false === (@include 'class.JavaScriptPacker.php')) {
|
||||
trigger_error(
|
||||
'The script "class.JavaScriptPacker.php" is required. Please see: http:'
|
||||
.'//code.google.com/p/minify/source/browse/trunk/min/lib/Minify/Packer.php'
|
||||
,E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify Javascript using Dean Edward's Packer
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
class Minify_Packer {
|
||||
class Minify_Packer
|
||||
{
|
||||
public static function minify($code, $options = array())
|
||||
{
|
||||
// @todo: set encoding options based on $options :)
|
||||
$packer = new JavascriptPacker($code, 'Normal', true, false);
|
||||
|
||||
return trim($packer->pack());
|
||||
}
|
||||
}
|
175
lib/Minify/ScssCssSource.php
Normal file
175
lib/Minify/ScssCssSource.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
use ScssPhp\ScssPhp\Compiler;
|
||||
use ScssPhp\ScssPhp\Version;
|
||||
|
||||
/**
|
||||
* Class for using SCSS files
|
||||
*
|
||||
* @link https://github.com/leafo/scssphp/
|
||||
*/
|
||||
class Minify_ScssCssSource extends Minify_Source
|
||||
{
|
||||
/**
|
||||
* @var Minify_CacheInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* Parsed SCSS cache object
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $parsed;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct(array $spec, Minify_CacheInterface $cache)
|
||||
{
|
||||
parent::__construct($spec);
|
||||
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last modified of all parsed files
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
$cache = $this->getCache();
|
||||
|
||||
return $cache['updated'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
$cache = $this->getCache();
|
||||
|
||||
return $cache['content'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a unique cache id for for this source.
|
||||
*
|
||||
* @param string $prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getCacheId($prefix = 'minify')
|
||||
{
|
||||
$md5 = md5($this->filepath);
|
||||
|
||||
return "{$prefix}_scss_{$md5}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SCSS cache object
|
||||
*
|
||||
* Runs the compilation if needed
|
||||
*
|
||||
* Implements Leafo\ScssPhp\Server logic because we need to get parsed files without parsing actual content
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getCache()
|
||||
{
|
||||
// cache for single run
|
||||
// so that getLastModified and getContent in single request do not add additional cache roundtrips (i.e memcache)
|
||||
if (isset($this->parsed)) {
|
||||
return $this->parsed;
|
||||
}
|
||||
|
||||
// check from cache first
|
||||
$cache = null;
|
||||
$cacheId = $this->getCacheId();
|
||||
if ($this->cache->isValid($cacheId, 0)) {
|
||||
if ($cache = $this->cache->fetch($cacheId)) {
|
||||
$cache = unserialize($cache);
|
||||
}
|
||||
}
|
||||
|
||||
$input = $cache ? $cache : $this->filepath;
|
||||
|
||||
if ($this->cacheIsStale($cache)) {
|
||||
$cache = $this->compile($this->filepath);
|
||||
}
|
||||
|
||||
if (!is_array($input) || $cache['updated'] > $input['updated']) {
|
||||
$this->cache->store($cacheId, serialize($cache));
|
||||
}
|
||||
|
||||
return $this->parsed = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether .scss file needs to be re-compiled.
|
||||
*
|
||||
* @param array $cache Cache object
|
||||
*
|
||||
* @return boolean True if compile required.
|
||||
*/
|
||||
private function cacheIsStale($cache)
|
||||
{
|
||||
if (!$cache) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$updated = $cache['updated'];
|
||||
foreach ($cache['files'] as $import => $mtime) {
|
||||
$filemtime = filemtime($import);
|
||||
|
||||
if ($filemtime !== $mtime || $filemtime > $updated) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile .scss file
|
||||
*
|
||||
* @param string $filename Input path (.scss)
|
||||
*
|
||||
* @see Server::compile()
|
||||
* @return array meta data result of the compile
|
||||
*/
|
||||
private function compile($filename)
|
||||
{
|
||||
$start = microtime(true);
|
||||
$scss = new Compiler();
|
||||
|
||||
// set import path directory the input filename resides
|
||||
// otherwise @import statements will not find the files
|
||||
// and will treat the @import line as css import
|
||||
$scss->setImportPaths(dirname($filename));
|
||||
|
||||
$css = $scss->compileString(file_get_contents($filename), $filename)->getCss();
|
||||
$elapsed = round((microtime(true) - $start), 4);
|
||||
|
||||
$v = Version::VERSION;
|
||||
$ts = date('r', (int) $start);
|
||||
$css = "/* compiled by scssphp $v on $ts ({$elapsed}s) */\n\n" . $css;
|
||||
|
||||
$imports = $scss->getParsedFiles();
|
||||
|
||||
$updated = 0;
|
||||
foreach ($imports as $mtime) {
|
||||
$updated = max($updated, $mtime);
|
||||
}
|
||||
|
||||
return array(
|
||||
'elapsed' => $elapsed, // statistic, can be dropped
|
||||
'updated' => $updated,
|
||||
'content' => $css,
|
||||
'files' => $imports,
|
||||
);
|
||||
}
|
||||
}
|
71
lib/Minify/ServeConfiguration.php
Normal file
71
lib/Minify/ServeConfiguration.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_ServeConfiguration
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* A configuration for Minify::serve() determined by a controller
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
class Minify_ServeConfiguration
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Minify_SourceInterface[]
|
||||
*/
|
||||
protected $sources;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $selectionId = '';
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @param Minify_SourceInterface[] $sources
|
||||
* @param string $selectionId
|
||||
*/
|
||||
public function __construct(array $options, array $sources = array(), $selectionId = '')
|
||||
{
|
||||
$this->options = $options;
|
||||
$this->sources = $sources;
|
||||
$this->selectionId = $selectionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Minify_SourceInterface[]
|
||||
*/
|
||||
public function getSources()
|
||||
{
|
||||
return $this->sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Short name to place inside cache id
|
||||
*
|
||||
* The setupSources() method may choose to set this, making it easier to
|
||||
* recognize a particular set of sources/settings in the cache folder. It
|
||||
* will be filtered and truncated to make the final cache id <= 250 bytes.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSelectionId()
|
||||
{
|
||||
return $this->selectionId;
|
||||
}
|
||||
}
|
219
lib/Minify/Source.php
Normal file
219
lib/Minify/Source.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_Source
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* A content source to be minified by Minify.
|
||||
*
|
||||
* This allows per-source minification options and the mixing of files with
|
||||
* content from other sources.
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_Source implements Minify_SourceInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var int time of last modification
|
||||
*/
|
||||
protected $lastModified;
|
||||
|
||||
/**
|
||||
* @var callback minifier function specifically for this source.
|
||||
*/
|
||||
protected $minifier;
|
||||
|
||||
/**
|
||||
* @var array minification options specific to this source.
|
||||
*/
|
||||
protected $minifyOptions = array();
|
||||
|
||||
/**
|
||||
* @var string full path of file
|
||||
*/
|
||||
protected $filepath;
|
||||
|
||||
/**
|
||||
* @var string HTTP Content Type (Minify requires one of the constants Minify::TYPE_*)
|
||||
*/
|
||||
protected $contentType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $getContentFunc;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Create a Minify_Source
|
||||
*
|
||||
* In the $spec array(), you can either provide a 'filepath' to an existing
|
||||
* file (existence will not be checked!) or give 'id' (unique string for
|
||||
* the content), 'content' (the string content) and 'lastModified'
|
||||
* (unixtime of last update).
|
||||
*
|
||||
* @param array $spec options
|
||||
*/
|
||||
public function __construct($spec)
|
||||
{
|
||||
if (isset($spec['filepath'])) {
|
||||
$ext = pathinfo($spec['filepath'], PATHINFO_EXTENSION);
|
||||
switch ($ext) {
|
||||
case 'js': $this->contentType = Minify::TYPE_JS;
|
||||
break;
|
||||
case 'less': // fallthrough
|
||||
case 'scss': // fallthrough
|
||||
case 'css': $this->contentType = Minify::TYPE_CSS;
|
||||
break;
|
||||
case 'htm': // fallthrough
|
||||
case 'html': $this->contentType = Minify::TYPE_HTML;
|
||||
break;
|
||||
}
|
||||
$this->filepath = $spec['filepath'];
|
||||
$this->id = $spec['filepath'];
|
||||
|
||||
// TODO ideally not touch disk in constructor
|
||||
$this->lastModified = filemtime($spec['filepath']);
|
||||
|
||||
if (!empty($spec['uploaderHoursBehind'])) {
|
||||
// offset for Windows uploaders with out of sync clocks
|
||||
$this->lastModified += round($spec['uploaderHoursBehind'] * 3600);
|
||||
}
|
||||
} elseif (isset($spec['id'])) {
|
||||
$this->id = 'id::' . $spec['id'];
|
||||
if (isset($spec['content'])) {
|
||||
$this->content = $spec['content'];
|
||||
} else {
|
||||
$this->getContentFunc = $spec['getContentFunc'];
|
||||
}
|
||||
$this->lastModified = isset($spec['lastModified']) ? $spec['lastModified'] : time();
|
||||
}
|
||||
if (isset($spec['contentType'])) {
|
||||
$this->contentType = $spec['contentType'];
|
||||
}
|
||||
if (isset($spec['minifier'])) {
|
||||
$this->setMinifier($spec['minifier']);
|
||||
}
|
||||
if (isset($spec['minifyOptions'])) {
|
||||
$this->minifyOptions = $spec['minifyOptions'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
return $this->lastModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMinifier()
|
||||
{
|
||||
return $this->minifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMinifier($minifier = null)
|
||||
{
|
||||
if ($minifier === '') {
|
||||
error_log(__METHOD__ . " cannot accept empty string. Use 'Minify::nullMinifier' or 'trim'.");
|
||||
$minifier = 'Minify::nullMinifier';
|
||||
}
|
||||
if ($minifier !== null && !is_callable($minifier, true)) {
|
||||
throw new InvalidArgumentException('minifier must be null or a valid callable');
|
||||
}
|
||||
$this->minifier = $minifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMinifierOptions()
|
||||
{
|
||||
return $this->minifyOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMinifierOptions(array $options)
|
||||
{
|
||||
$this->minifyOptions = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContentType()
|
||||
{
|
||||
return $this->contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
if (null === $this->filepath) {
|
||||
if (null === $this->content) {
|
||||
$content = call_user_func($this->getContentFunc, $this->id);
|
||||
} else {
|
||||
$content = $this->content;
|
||||
}
|
||||
} else {
|
||||
$content = file_get_contents($this->filepath);
|
||||
}
|
||||
|
||||
// remove UTF-8 BOM if present
|
||||
if (strpos($content, "\xEF\xBB\xBF") === 0) {
|
||||
return substr($content, 3);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilePath()
|
||||
{
|
||||
return $this->filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setupUriRewrites()
|
||||
{
|
||||
if ($this->filepath
|
||||
&& !isset($this->minifyOptions['currentDir'])
|
||||
&& !isset($this->minifyOptions['prependRelativePath'])) {
|
||||
$this->minifyOptions['currentDir'] = dirname($this->filepath);
|
||||
}
|
||||
}
|
||||
}
|
197
lib/Minify/Source/Factory.php
Normal file
197
lib/Minify/Source/Factory.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
class Minify_Source_Factory
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
protected $handlers = array();
|
||||
|
||||
/**
|
||||
* @var Minify_Env
|
||||
*/
|
||||
protected $env;
|
||||
|
||||
/**
|
||||
* @param Minify_Env $env
|
||||
* @param array $options
|
||||
*
|
||||
* noMinPattern : Pattern matched against basename of the filepath (if present). If the pattern
|
||||
* matches, Minify will try to avoid re-compressing the resource.
|
||||
*
|
||||
* fileChecker : Callable responsible for verifying the existence of the file.
|
||||
*
|
||||
* resolveDocRoot : If true, a leading "//" will be replaced with the document root.
|
||||
*
|
||||
* checkAllowDirs : If true, the filepath will be verified to be within one of the directories
|
||||
* specified by allowDirs.
|
||||
*
|
||||
* allowDirs : Directory paths in which sources can be served.
|
||||
*
|
||||
* uploaderHoursBehind : How many hours behind are the file modification times of uploaded files?
|
||||
* If you upload files from Windows to a non-Windows server, Windows may report
|
||||
* incorrect mtimes for the files. Immediately after modifying and uploading a
|
||||
* file, use the touch command to update the mtime on the server. If the mtime
|
||||
* jumps ahead by a number of hours, set this variable to that number. If the mtime
|
||||
* moves back, this should not be needed.
|
||||
*
|
||||
* @param Minify_CacheInterface $cache Optional cache for handling .less files.
|
||||
*
|
||||
*/
|
||||
public function __construct(Minify_Env $env, array $options = array(), Minify_CacheInterface $cache = null)
|
||||
{
|
||||
$this->env = $env;
|
||||
$this->options = array_merge(array(
|
||||
'noMinPattern' => '@[-\\.]min\\.(?:[a-zA-Z]+)$@i', // matched against basename
|
||||
'fileChecker' => array($this, 'checkIsFile'),
|
||||
'resolveDocRoot' => true,
|
||||
'checkAllowDirs' => true,
|
||||
'allowDirs' => array('//'),
|
||||
'uploaderHoursBehind' => 0,
|
||||
), $options);
|
||||
|
||||
// resolve // in allowDirs
|
||||
$docRoot = $env->getDocRoot();
|
||||
foreach ($this->options['allowDirs'] as $i => $dir) {
|
||||
if (0 === strpos($dir, '//')) {
|
||||
$this->options['allowDirs'][$i] = $docRoot . substr($dir, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->options['fileChecker'] && !is_callable($this->options['fileChecker'])) {
|
||||
throw new InvalidArgumentException("fileChecker option is not callable");
|
||||
}
|
||||
|
||||
$this->setHandler('~\.less$~i', function ($spec) use ($cache) {
|
||||
return new Minify_LessCssSource($spec, $cache);
|
||||
});
|
||||
|
||||
$this->setHandler('~\.scss~i', function ($spec) use ($cache) {
|
||||
return new Minify_ScssCssSource($spec, $cache);
|
||||
});
|
||||
|
||||
$this->setHandler('~\.(js|css)$~i', function ($spec) {
|
||||
return new Minify_Source($spec);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $basenamePattern A pattern tested against basename. E.g. "~\.css$~"
|
||||
* @param callable $handler Function that recieves a $spec array and returns a Minify_SourceInterface
|
||||
*/
|
||||
public function setHandler($basenamePattern, $handler)
|
||||
{
|
||||
$this->handlers[$basenamePattern] = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return string
|
||||
*
|
||||
* @throws Minify_Source_FactoryException
|
||||
*/
|
||||
public function checkIsFile($file)
|
||||
{
|
||||
$realpath = realpath($file);
|
||||
if (!$realpath) {
|
||||
throw new Minify_Source_FactoryException("File failed realpath(): $file");
|
||||
}
|
||||
|
||||
$basename = basename($file);
|
||||
if (0 === strpos($basename, '.')) {
|
||||
throw new Minify_Source_FactoryException("Filename starts with period (may be hidden): $basename");
|
||||
}
|
||||
|
||||
if (!is_file($realpath) || !is_readable($realpath)) {
|
||||
throw new Minify_Source_FactoryException("Not a file or isn't readable: $file");
|
||||
}
|
||||
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $spec
|
||||
*
|
||||
* @return Minify_SourceInterface
|
||||
*
|
||||
* @throws Minify_Source_FactoryException
|
||||
*/
|
||||
public function makeSource($spec)
|
||||
{
|
||||
if (is_string($spec)) {
|
||||
$spec = array(
|
||||
'filepath' => $spec,
|
||||
);
|
||||
} elseif ($spec instanceof Minify_SourceInterface) {
|
||||
return $spec;
|
||||
}
|
||||
|
||||
$source = null;
|
||||
|
||||
if (empty($spec['filepath'])) {
|
||||
// not much we can check
|
||||
return new Minify_Source($spec);
|
||||
}
|
||||
|
||||
if ($this->options['resolveDocRoot'] && 0 === strpos($spec['filepath'], '//')) {
|
||||
$spec['filepath'] = $this->env->getDocRoot() . substr($spec['filepath'], 1);
|
||||
}
|
||||
|
||||
if (!empty($this->options['fileChecker'])) {
|
||||
$spec['filepath'] = call_user_func($this->options['fileChecker'], $spec['filepath']);
|
||||
}
|
||||
|
||||
if ($this->options['checkAllowDirs']) {
|
||||
$allowDirs = (array)$this->options['allowDirs'];
|
||||
$inAllowedDir = false;
|
||||
$filePath = $this->env->normalizePath($spec['filepath']);
|
||||
foreach ($allowDirs as $allowDir) {
|
||||
if (strpos($filePath, $this->env->normalizePath($allowDir)) === 0) {
|
||||
$inAllowedDir = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$inAllowedDir) {
|
||||
$allowDirsStr = implode(';', $allowDirs);
|
||||
throw new Minify_Source_FactoryException("File '{$spec['filepath']}' is outside \$allowDirs "
|
||||
. "($allowDirsStr). If the path is resolved via an alias/symlink, look into the "
|
||||
. "\$min_symlinks option.");
|
||||
}
|
||||
}
|
||||
|
||||
$basename = basename($spec['filepath']);
|
||||
|
||||
if ($this->options['noMinPattern'] && preg_match($this->options['noMinPattern'], $basename)) {
|
||||
if (preg_match('~\.(css|less)$~i', $basename)) {
|
||||
$spec['minifyOptions']['compress'] = false;
|
||||
// we still want URI rewriting to work for CSS
|
||||
} else {
|
||||
$spec['minifier'] = 'Minify::nullMinifier';
|
||||
}
|
||||
}
|
||||
|
||||
$hoursBehind = $this->options['uploaderHoursBehind'];
|
||||
if ($hoursBehind != 0) {
|
||||
$spec['uploaderHoursBehind'] = $hoursBehind;
|
||||
}
|
||||
|
||||
foreach ($this->handlers as $basenamePattern => $handler) {
|
||||
if (preg_match($basenamePattern, $basename)) {
|
||||
$source = call_user_func($handler, $spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$source) {
|
||||
throw new Minify_Source_FactoryException("Handler not found for file: $basename");
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
}
|
5
lib/Minify/Source/FactoryException.php
Normal file
5
lib/Minify/Source/FactoryException.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
class Minify_Source_FactoryException extends Exception
|
||||
{
|
||||
}
|
82
lib/Minify/SourceInterface.php
Normal file
82
lib/Minify/SourceInterface.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface Minify_SourceInterface
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* A content source to be minified by Minify.
|
||||
*
|
||||
* This allows per-source minification options and the mixing of files with
|
||||
* content from other sources.
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
interface Minify_SourceInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the minifier
|
||||
*
|
||||
* @return callable|null
|
||||
*/
|
||||
public function getMinifier();
|
||||
|
||||
/**
|
||||
* Set the minifier
|
||||
*
|
||||
* @param callable $minifier
|
||||
* @return void
|
||||
*/
|
||||
public function setMinifier($minifier = null);
|
||||
|
||||
/**
|
||||
* Get options for the minifier
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMinifierOptions();
|
||||
|
||||
/**
|
||||
* Set options for the minifier
|
||||
*
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
public function setMinifierOptions(array $options);
|
||||
|
||||
/**
|
||||
* Get the content type
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getContentType();
|
||||
|
||||
/**
|
||||
* Get content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent();
|
||||
|
||||
/**
|
||||
* Get last modified timestamp
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLastModified();
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getId();
|
||||
|
||||
/**
|
||||
* Get the path of the file that this source is based on (may be null)
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFilePath();
|
||||
}
|
31
lib/Minify/SourceSet.php
Normal file
31
lib/Minify/SourceSet.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_SourceSet
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* @package Minify
|
||||
*/
|
||||
class Minify_SourceSet
|
||||
{
|
||||
|
||||
/**
|
||||
* Get unique string for a set of sources
|
||||
*
|
||||
* @param Minify_SourceInterface[] $sources Minify_Source instances
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDigest($sources)
|
||||
{
|
||||
$info = array();
|
||||
foreach ($sources as $source) {
|
||||
$info[] = array(
|
||||
$source->getId(), $source->getMinifier(), $source->getMinifierOptions()
|
||||
);
|
||||
}
|
||||
|
||||
return md5(serialize($info));
|
||||
}
|
||||
}
|
@@ -1,17 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Minify_YUICompressor
|
||||
* Class Minify_YUICompressor
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compress Javascript/CSS using the YUI Compressor
|
||||
*
|
||||
*
|
||||
* You must set $jarFile and $tempDir before calling the minify functions.
|
||||
* Also, depending on your shell's environment, you may need to specify
|
||||
* the full path to java in $javaExecutable or use putenv() to setup the
|
||||
* Java environment.
|
||||
*
|
||||
*
|
||||
* <code>
|
||||
* Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.4.6.jar';
|
||||
* Minify_YUICompressor::$tempDir = '/tmp';
|
||||
@@ -25,11 +25,12 @@
|
||||
* array('stack-size' => '2048k')
|
||||
*
|
||||
* @todo unit tests, $options docs
|
||||
*
|
||||
*
|
||||
* @package Minify
|
||||
* @author Stephen Clay <steve@mrclay.org>
|
||||
*/
|
||||
class Minify_YUICompressor {
|
||||
class Minify_YUICompressor
|
||||
{
|
||||
|
||||
/**
|
||||
* Filepath of the YUI Compressor jar file. This must be set before
|
||||
@@ -37,106 +38,106 @@ class Minify_YUICompressor {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $jarFile = null;
|
||||
|
||||
public static $jarFile;
|
||||
|
||||
/**
|
||||
* Writable temp directory. This must be set before calling minifyJs()
|
||||
* or minifyCss().
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $tempDir = null;
|
||||
|
||||
public static $tempDir;
|
||||
|
||||
/**
|
||||
* Filepath of "java" executable (may be needed if not in shell's PATH)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $javaExecutable = 'java';
|
||||
|
||||
|
||||
/**
|
||||
* Minify a Javascript string
|
||||
*
|
||||
*
|
||||
* @param string $js
|
||||
*
|
||||
*
|
||||
* @param array $options (verbose is ignored)
|
||||
*
|
||||
*
|
||||
* @see http://www.julienlecomte.net/yuicompressor/README
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minifyJs($js, $options = array())
|
||||
{
|
||||
return self::_minify('js', $js, $options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Minify a CSS string
|
||||
*
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
*
|
||||
* @param array $options (verbose is ignored)
|
||||
*
|
||||
*
|
||||
* @see http://www.julienlecomte.net/yuicompressor/README
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function minifyCss($css, $options = array())
|
||||
{
|
||||
return self::_minify('css', $css, $options);
|
||||
}
|
||||
|
||||
|
||||
private static function _minify($type, $content, $options)
|
||||
{
|
||||
self::_prepare();
|
||||
if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) {
|
||||
throw new Exception('Minify_YUICompressor : could not create temp file in "'.self::$tempDir.'".');
|
||||
}
|
||||
|
||||
file_put_contents($tmpFile, $content);
|
||||
exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code);
|
||||
unlink($tmpFile);
|
||||
if ($result_code != 0) {
|
||||
throw new Exception('Minify_YUICompressor : YUI compressor execution failed.');
|
||||
}
|
||||
|
||||
return implode("\n", $output);
|
||||
}
|
||||
|
||||
|
||||
private static function _getCmd($userOptions, $type, $tmpFile)
|
||||
{
|
||||
$o = array_merge(
|
||||
array(
|
||||
'charset' => ''
|
||||
,'line-break' => 5000
|
||||
,'type' => $type
|
||||
,'nomunge' => false
|
||||
,'preserve-semi' => false
|
||||
,'disable-optimizations' => false
|
||||
,'stack-size' => ''
|
||||
)
|
||||
,$userOptions
|
||||
$defaults = array(
|
||||
'charset' => '',
|
||||
'line-break' => 5000,
|
||||
'type' => $type,
|
||||
'nomunge' => false,
|
||||
'preserve-semi' => false,
|
||||
'disable-optimizations' => false,
|
||||
'stack-size' => '',
|
||||
);
|
||||
$o = array_merge($defaults, $userOptions);
|
||||
|
||||
$cmd = self::$javaExecutable
|
||||
. (!empty($o['stack-size'])
|
||||
? ' -Xss' . $o['stack-size']
|
||||
: '')
|
||||
. ' -jar ' . escapeshellarg(self::$jarFile)
|
||||
. (!empty($o['stack-size']) ? ' -Xss' . $o['stack-size'] : '')
|
||||
. ' -jar ' . escapeshellarg(self::$jarFile)
|
||||
. " --type {$type}"
|
||||
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
|
||||
? " --charset {$o['charset']}"
|
||||
? " --charset {$o['charset']}"
|
||||
: '')
|
||||
. (is_numeric($o['line-break']) && $o['line-break'] >= 0
|
||||
? ' --line-break ' . (int)$o['line-break']
|
||||
: '');
|
||||
if ($type === 'js') {
|
||||
foreach (array('nomunge', 'preserve-semi', 'disable-optimizations') as $opt) {
|
||||
$cmd .= $o[$opt]
|
||||
$cmd .= $o[$opt]
|
||||
? " --{$opt}"
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
return $cmd . ' ' . escapeshellarg($tmpFile);
|
||||
}
|
||||
|
||||
|
||||
private static function _prepare()
|
||||
{
|
||||
if (! is_file(self::$jarFile)) {
|
||||
@@ -153,4 +154,3 @@ class Minify_YUICompressor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php
|
||||
|
||||
namespace MrClay;
|
||||
|
||||
@@ -18,16 +18,17 @@ use InvalidArgumentException;
|
||||
* @author Steve Clay <steve@mrclay.org>
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
class Cli {
|
||||
|
||||
class Cli
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array validation errors
|
||||
*/
|
||||
public $errors = array();
|
||||
|
||||
|
||||
/**
|
||||
* @var array option values available after validation.
|
||||
*
|
||||
*
|
||||
* E.g. array(
|
||||
* 'a' => false // option was missing
|
||||
* ,'b' => true // option was present
|
||||
@@ -67,7 +68,7 @@ class Cli {
|
||||
* @var resource
|
||||
*/
|
||||
protected $_stdout = null;
|
||||
|
||||
|
||||
/**
|
||||
* @param bool $exitIfNoStdin (default true) Exit() if STDIN is not defined
|
||||
*/
|
||||
@@ -116,6 +117,7 @@ class Cli {
|
||||
$arg = new Arg($required);
|
||||
}
|
||||
$this->_args[$letter] = $arg;
|
||||
|
||||
return $arg;
|
||||
}
|
||||
|
||||
@@ -130,7 +132,7 @@ class Cli {
|
||||
|
||||
/*
|
||||
* Read and validate options
|
||||
*
|
||||
*
|
||||
* @return bool true if all options are valid
|
||||
*/
|
||||
public function validate()
|
||||
@@ -139,17 +141,17 @@ class Cli {
|
||||
$this->errors = array();
|
||||
$this->values = array();
|
||||
$this->_stdin = null;
|
||||
|
||||
|
||||
if ($this->isHelpRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$lettersUsed = '';
|
||||
foreach ($this->_args as $letter => $arg) {
|
||||
/* @var Arg $arg */
|
||||
$options .= $letter;
|
||||
$lettersUsed .= $letter;
|
||||
|
||||
|
||||
if ($arg->mayHaveValue || $arg->mustHaveValue) {
|
||||
$options .= ($arg->mustHaveValue ? ':' : '::');
|
||||
}
|
||||
@@ -201,14 +203,15 @@ class Cli {
|
||||
array_splice($argvCopy, $k, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check that value isn't really another option
|
||||
if (strlen($lettersUsed) > 1) {
|
||||
$pattern = "/^-[" . str_replace($letter, '', $lettersUsed) . "]/i";
|
||||
if (preg_match($pattern, $v)) {
|
||||
$this->addError($letter, "Value was read as another option: %s", $v);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($arg->assertFile || $arg->assertDir) {
|
||||
if ($v[0] !== '/' && $v[0] !== '~') {
|
||||
@@ -249,6 +252,7 @@ class Cli {
|
||||
}
|
||||
$this->moreArgs = $argvCopy;
|
||||
reset($this->moreArgs);
|
||||
|
||||
return empty($this->errors);
|
||||
}
|
||||
|
||||
@@ -270,12 +274,13 @@ class Cli {
|
||||
$r[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a short list of errors with options
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getErrorReport()
|
||||
@@ -288,6 +293,7 @@ class Cli {
|
||||
$r .= " $letter : " . implode(', ', $arr) . "\n";
|
||||
}
|
||||
$r .= "\n";
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
@@ -318,9 +324,10 @@ class Cli {
|
||||
$desc = wordwrap($desc, 70);
|
||||
$r .= $flag . str_replace("\n", "\n ", $desc) . "\n\n";
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get resource of open input stream. May be STDIN or a file pointer
|
||||
* to the file specified by an option with 'STDIN'.
|
||||
@@ -333,17 +340,18 @@ class Cli {
|
||||
return STDIN;
|
||||
} else {
|
||||
$this->_stdin = fopen($this->_stdin, 'rb');
|
||||
|
||||
return $this->_stdin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function closeInput()
|
||||
{
|
||||
if (null !== $this->_stdin) {
|
||||
fclose($this->_stdin);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get resource of open output stream. May be STDOUT or a file pointer
|
||||
* to the file specified by an option with 'STDOUT'. The file will be
|
||||
@@ -357,10 +365,11 @@ class Cli {
|
||||
return STDOUT;
|
||||
} else {
|
||||
$this->_stdout = fopen($this->_stdout, 'wb');
|
||||
|
||||
return $this->_stdout;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function closeOutput()
|
||||
{
|
||||
if (null !== $this->_stdout) {
|
||||
@@ -381,4 +390,3 @@ class Cli {
|
||||
$this->errors[$letter][] = sprintf($msg, $value);
|
||||
}
|
||||
}
|
||||
|
@@ -43,7 +43,8 @@ use BadMethodCallException;
|
||||
* @author Steve Clay <steve@mrclay.org>
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
class Arg {
|
||||
class Arg
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -97,6 +98,7 @@ class Arg {
|
||||
public function useAsOutfile()
|
||||
{
|
||||
$this->spec['useAsOutfile'] = true;
|
||||
|
||||
return $this->assertFile()->assertWritable();
|
||||
}
|
||||
|
||||
@@ -109,6 +111,7 @@ class Arg {
|
||||
public function useAsInfile()
|
||||
{
|
||||
$this->spec['useAsInfile'] = true;
|
||||
|
||||
return $this->assertFile()->assertReadable();
|
||||
}
|
||||
|
||||
@@ -127,6 +130,7 @@ class Arg {
|
||||
public function setDescription($desc)
|
||||
{
|
||||
$this->description = $desc;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -164,6 +168,7 @@ class Arg {
|
||||
} else {
|
||||
throw new BadMethodCallException('Method does not exist');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -178,6 +183,7 @@ class Arg {
|
||||
if (array_key_exists($name, $this->spec)) {
|
||||
return $this->spec[$name];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
4
min/builder/jquery-1.6.3.min.js
vendored
4
min/builder/jquery-1.6.3.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Groups configuration for default Minify implementation
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
/**
|
||||
* You may wish to use the Minify URI Builder app to suggest
|
||||
* changes. http://yourdomain/min/builder/
|
||||
*
|
||||
* See http://code.google.com/p/minify/wiki/CustomSource for other ideas
|
||||
**/
|
||||
|
||||
return array(
|
||||
// 'js' => array('//js/file1.js', '//js/file2.js'),
|
||||
// 'css' => array('//css/file1.css', '//css/file2.css'),
|
||||
);
|
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Front controller for default Minify implementation
|
||||
*
|
||||
* DO NOT EDIT! Configure this utility via config.php and groupsConfig.php
|
||||
*
|
||||
* @package Minify
|
||||
*/
|
||||
|
||||
define('MINIFY_MIN_DIR', dirname(__FILE__));
|
||||
|
||||
// set config path defaults
|
||||
$min_configPaths = array(
|
||||
'base' => MINIFY_MIN_DIR . '/config.php',
|
||||
'test' => MINIFY_MIN_DIR . '/config-test.php',
|
||||
'groups' => MINIFY_MIN_DIR . '/groupsConfig.php'
|
||||
);
|
||||
|
||||
// check for custom config paths
|
||||
if (!empty($min_customConfigPaths) && is_array($min_customConfigPaths)) {
|
||||
$min_configPaths = array_merge($min_configPaths, $min_customConfigPaths);
|
||||
}
|
||||
|
||||
// load config
|
||||
require $min_configPaths['base'];
|
||||
|
||||
if (isset($_GET['test'])) {
|
||||
include $min_configPaths['test'];
|
||||
}
|
||||
|
||||
require "$min_libPath/Minify/Loader.php";
|
||||
Minify_Loader::register();
|
||||
|
||||
Minify::$uploaderHoursBehind = $min_uploaderHoursBehind;
|
||||
Minify::setCache(
|
||||
isset($min_cachePath) ? $min_cachePath : ''
|
||||
,$min_cacheFileLocking
|
||||
);
|
||||
|
||||
if ($min_documentRoot) {
|
||||
$_SERVER['DOCUMENT_ROOT'] = $min_documentRoot;
|
||||
Minify::$isDocRootSet = true;
|
||||
}
|
||||
|
||||
$min_serveOptions['minifierOptions']['text/css']['symlinks'] = $min_symlinks;
|
||||
// auto-add targets to allowDirs
|
||||
foreach ($min_symlinks as $uri => $target) {
|
||||
$min_serveOptions['minApp']['allowDirs'][] = $target;
|
||||
}
|
||||
|
||||
if ($min_allowDebugFlag) {
|
||||
$min_serveOptions['debug'] = Minify_DebugDetector::shouldDebugRequest($_COOKIE, $_GET, $_SERVER['REQUEST_URI']);
|
||||
}
|
||||
|
||||
if ($min_errorLogger) {
|
||||
if (true === $min_errorLogger) {
|
||||
$min_errorLogger = FirePHP::getInstance(true);
|
||||
}
|
||||
Minify_Logger::setLogger($min_errorLogger);
|
||||
}
|
||||
|
||||
// check for URI versioning
|
||||
if (preg_match('/&\\d/', $_SERVER['QUERY_STRING']) || isset($_GET['v'])) {
|
||||
$min_serveOptions['maxAge'] = 31536000;
|
||||
}
|
||||
|
||||
// need groups config?
|
||||
if (isset($_GET['g'])) {
|
||||
// well need groups config
|
||||
$min_serveOptions['minApp']['groups'] = (require $min_configPaths['groups']);
|
||||
}
|
||||
|
||||
// serve or redirect
|
||||
if (isset($_GET['f']) || isset($_GET['g'])) {
|
||||
if (! isset($min_serveController)) {
|
||||
$min_serveController = new Minify_Controller_MinApp();
|
||||
}
|
||||
Minify::serve($min_serveController, $min_serveOptions);
|
||||
|
||||
} elseif ($min_enableBuilder) {
|
||||
header('Location: builder/');
|
||||
exit();
|
||||
} else {
|
||||
header("Location: /");
|
||||
exit();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user