├── .htaccess ├── README.md ├── css ├── bootstrap.min.css ├── stylesheet.css └── vendor.css ├── images ├── body-bg.png ├── forkme_right_red_aa0000.png ├── highlight-bg.jpg ├── hr.png ├── logo-trans.png └── ui.totop.png ├── index.html ├── js ├── jquery.js ├── navbar2.js └── waypoints.min.js └── min ├── .htaccess ├── builder ├── .htaccess ├── _index.js ├── bm.js ├── bm2.js ├── index.php ├── jquery-1.6.3.min.js ├── ocCheck.php ├── rewriteTest.js └── test.php ├── config-test.php ├── config.php ├── groupsConfig.php ├── index.php ├── lib ├── CSSmin.php ├── DooDigestAuth.php ├── FirePHP.php ├── HTTP │ ├── ConditionalGet.php │ └── Encoder.php ├── JSMin.php ├── JSMinPlus.php ├── Minify.php ├── Minify │ ├── Build.php │ ├── CSS.php │ ├── CSS │ │ ├── Compressor.php │ │ └── UriRewriter.php │ ├── Cache │ │ ├── APC.php │ │ ├── File.php │ │ ├── Memcache.php │ │ ├── XCache.php │ │ └── ZendPlatform.php │ ├── ClosureCompiler.php │ ├── CommentPreserver.php │ ├── Controller │ │ ├── Base.php │ │ ├── Files.php │ │ ├── Groups.php │ │ ├── MinApp.php │ │ ├── Page.php │ │ └── Version1.php │ ├── DebugDetector.php │ ├── HTML.php │ ├── HTML │ │ └── Helper.php │ ├── ImportProcessor.php │ ├── JS │ │ └── ClosureCompiler.php │ ├── Lines.php │ ├── Loader.php │ ├── Logger.php │ ├── Packer.php │ ├── Source.php │ ├── YUI │ │ ├── CssCompressor.java │ │ └── CssCompressor.php │ └── YUICompressor.php └── MrClay │ ├── Cli.php │ └── Cli │ └── Arg.php ├── quick-test.css ├── quick-test.js └── utils.php /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | deny from all 3 | 4 | RewriteEngine on 5 | RewriteCond %{HTTP_HOST} ^(www.phpdoc2cheatsheet\.com)(:80)? [NC] 6 | RewriteRule ^(.*) http://phpdoc2cheatsheet.com/$1 [R=301,L] 7 | order deny,allow 8 | 9 | # BEGIN Compress text files 10 | 11 | AddOutputFilterByType DEFLATE text/html text/xml text/css text/plain 12 | AddOutputFilterByType DEFLATE image/svg+xml application/xhtml+xml application/xml 13 | AddOutputFilterByType DEFLATE application/rdf+xml application/rss+xml application/atom+xml 14 | AddOutputFilterByType DEFLATE text/javascript application/javascript application/x-javascript application/json 15 | AddOutputFilterByType DEFLATE application/x-font-ttf application/x-font-otf 16 | AddOutputFilterByType DEFLATE font/truetype font/opentype 17 | 18 | # END Compress text files 19 | 20 | # BEGIN Expire headers 21 | 22 | ExpiresActive On 23 | ExpiresDefault "access plus 5 seconds" 24 | ExpiresByType image/x-icon "access plus 2592000 seconds" 25 | ExpiresByType image/jpeg "access plus 2592000 seconds" 26 | ExpiresByType image/png "access plus 2592000 seconds" 27 | ExpiresByType image/gif "access plus 2592000 seconds" 28 | ExpiresByType application/x-shockwave-flash "access plus 2592000 seconds" 29 | ExpiresByType text/css "access plus 604800 seconds" 30 | ExpiresByType text/javascript "access plus 216000 seconds" 31 | ExpiresByType application/javascript "access plus 216000 seconds" 32 | ExpiresByType application/x-javascript "access plus 216000 seconds" 33 | ExpiresByType text/html "access plus 600 seconds" 34 | ExpiresByType application/xhtml+xml "access plus 600 seconds" 35 | 36 | # END Expire headers 37 | 38 | # BEGIN Cache-Control Headers 39 | 40 | 41 | Header set Cache-Control "public" 42 | 43 | 44 | Header set Cache-Control "public" 45 | 46 | 47 | Header set Cache-Control "private" 48 | 49 | 50 | Header set Cache-Control "private, must-revalidate" 51 | 52 | 53 | # END Cache-Control Headers 54 | 55 | # BEGIN Turn ETags Off 56 | FileETag None 57 | # END Turn ETags Off 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PhpDoc2Cheatsheet 2 | ================= 3 | 4 | https://phpdoc2cheatsheet.joseruzafa.com 5 | 6 | [File](#file) 7 | [Variable](#variable) 8 | [Class](#class) 9 | [Function](#function) 10 | [Tags](#tags) 11 | [Arrays](#arrays) 12 | [Multiple Types](#multiple_types) 13 | 14 | File 15 | ==== 16 | 17 | ```php 18 | /** File docBlock description 19 | * 20 | * This block demonstrates the rich 21 | * information that can be included 22 | * in in-code documentation through 23 | * DocBlocks and tags 24 | * @author Jane Doe 25 | * @version 1.0 26 | * @package sample 27 | */ 28 | ``` 29 | 30 | Variable 31 | ======== 32 | 33 | ```php 34 | /** 35 | * Global variable declaration docBlock 36 | * @global integer $GLOBALS['_myvar'] 37 | * @name $_myvar 38 | */ 39 | $GLOBALS['_myvar'] = 6; 40 | ``` 41 | 42 | Class 43 | ===== 44 | 45 | ```php 46 | /** 47 | * An example class, this is grouped with 48 | * other classes in the "sample" package and 49 | * is part of "classes" subpackage 50 | * @package sample 51 | * @subpackage classes 52 | */ 53 | class myclass 54 | { 55 | ... 56 | } 57 | ``` 58 | 59 | Function 60 | ======== 61 | 62 | ```php 63 | /** 64 | * A sample function docblock 65 | * @global string document the use $_myvar 66 | * @staticvar integer $staticvar is returned 67 | * @param string $param1 name to declare 68 | * @param string $param2 value of the name 69 | * @return integer 70 | */ 71 | function aFunc($param1, $param2 = 'optional') 72 | { 73 | static $staticvar = 7; 74 | global $_myvar; 75 | return $staticvar; 76 | } 77 | ``` 78 | 79 | Tags 80 | ==== 81 | 82 | @api 83 | 84 | ```php 85 | /** 86 | * This method will not change until a major release. 87 | * 88 | * @api 89 | * 90 | * @return void 91 | */ 92 | function showVersion() 93 | { 94 | ... 95 | } 96 | ``` 97 | 98 | @author 99 | 100 | ```php 101 | /** 102 | * @author My Name 103 | * @author My Name 104 | */ 105 | ``` 106 | 107 | @category 108 | 109 | ```php 110 | /** 111 | * Page-Level DocBlock 112 | * 113 | * @category MyCategory 114 | * @package MyPackage 115 | */ 116 | ``` 117 | 118 | @copyright 119 | 120 | ```php 121 | /** 122 | * @copyright 1997-2005 The PHP Group 123 | */ 124 | ``` 125 | 126 | @deprecated 127 | 128 | ```php 129 | /** 130 | * @deprecated 131 | * @deprecated 1.0.0 132 | * @deprecated No longer used by internal code and not recommended. 133 | * @deprecated 1.0.0 No longer used by internal code and not recommended. 134 | */ 135 | function count() 136 | { 137 | ... 138 | } 139 | ``` 140 | 141 | @example 142 | 143 | ```php 144 | /** 145 | * @example example1.php Counting in action. 146 | * @example http://example.com/example2.phps Counting in action by a 3rd party. 147 | * @example "My Own Example.php" My counting. 148 | */ 149 | function count() 150 | { 151 | ... 152 | } 153 | ``` 154 | 155 | @filesource 156 | 157 | ```php 158 | /** 159 | * @filesource 160 | */ 161 | ``` 162 | 163 | @global 164 | 165 | This tag is not included in phpDocumentor 2.0 166 | 167 | ```php 168 | /** 169 | *@global [Type] [name] @global [Type] [description] 170 | */ 171 | ``` 172 | 173 | @ignore 174 | 175 | ```php 176 | if($ostest) 177 | { 178 | /** 179 | * This define will either be 'Unix' or 'Windows' 180 | */ 181 | define("OS","Unix"); 182 | } 183 | else 184 | { 185 | /** 186 | * @ignore 187 | */ 188 | define("OS","Windows"); 189 | } 190 | ``` 191 | 192 | @internal 193 | 194 | ```php 195 | /** 196 | * @internal 197 | * 198 | * @return integer Indicates the number of items. 199 | */ 200 | function count() 201 | { 202 | ... 203 | } 204 | ``` 205 | 206 | @link 207 | 208 | Normal tag: 209 | 210 | ```php 211 | /** 212 | * @link http://example.com/my/bar Documentation of Foo. 213 | * 214 | * @return integer Indicates the number of items. 215 | */ 216 | function count() 217 | { 218 | ... 219 | } 220 | ``` 221 | 222 | Inline tag: 223 | 224 | ```php 225 | /** 226 | * This method counts the occurrences of Foo. 227 | * 228 | * When no more Foo ({@link http://example.com/my/bar}) are given this 229 | * function will add one as there must always be one Foo. 230 | * 231 | * @return integer Indicates the number of items. 232 | */ 233 | function count() 234 | { 235 | ... 236 | } 237 | ``` 238 | 239 | @license 240 | 241 | ```php 242 | /** 243 | * @license GPL 244 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License 245 | */ 246 | ``` 247 | 248 | @method 249 | 250 | ```php 251 | class Parent 252 | { 253 | public function __call() 254 | { 255 | ... 256 | } 257 | } 258 | 259 | /** 260 | * @method string getString() 261 | * @method void setInteger(integer $integer) 262 | * @method setString(integer $integer) 263 | */ 264 | class Child extends Parent 265 | { 266 | ... 267 | } 268 | ``` 269 | 270 | @package 271 | 272 | ```php 273 | /** 274 | * @package PSR\Documentation\API 275 | */ 276 | ``` 277 | 278 | @param 279 | 280 | ```php 281 | /** 282 | * Counts the number of items in the provided array. 283 | * 284 | * @param mixed[] $array Array structure to count the elements of. 285 | * 286 | * @return int Returns the number of elements. 287 | */ 288 | function count(array $items) 289 | { 290 | ... 291 | } 292 | ``` 293 | 294 | @property 295 | 296 | ```php 297 | class Parent 298 | { 299 | public function __get() 300 | { 301 | ... 302 | } 303 | } 304 | 305 | /** 306 | * @property string $myProperty 307 | */ 308 | class Child extends Parent 309 | { 310 | ... 311 | } 312 | ``` 313 | 314 | @property-read 315 | 316 | ```php 317 | class Parent 318 | { 319 | public function __get() 320 | { 321 | ... 322 | } 323 | } 324 | 325 | /** 326 | * @property-read string $myProperty 327 | */ 328 | class Child extends Parent 329 | { 330 | ... 331 | } 332 | ``` 333 | 334 | @property-write 335 | 336 | ```php 337 | class Parent 338 | { 339 | public function __set() 340 | { 341 | ... 342 | } 343 | } 344 | 345 | /** 346 | * @property-write string $myProperty 347 | */ 348 | class Child extends Parent 349 | { 350 | ... 351 | } 352 | ``` 353 | 354 | @return (Singular type) 355 | 356 | ```php 357 | /** 358 | * @return integer Indicates the number of items. 359 | */ 360 | function count() 361 | { 362 | ... 363 | } 364 | ``` 365 | 366 | @return (two types) 367 | 368 | ```php 369 | /** 370 | * @return string|null The label's text or null if none provided. 371 | */ 372 | function getLabel() 373 | { 374 | ... 375 | } 376 | ``` 377 | 378 | @see 379 | 380 | ```php 381 | /** 382 | * @see http://example.com/my/bar Documentation of Foo. 383 | * @see MyClass::$items for the property whose items are counted 384 | * @see MyClass::setItems() to set the items for this collection. 385 | * 386 | * @return integer Indicates the number of items. 387 | */ 388 | function count() 389 | { 390 | ... 391 | } 392 | ``` 393 | 394 | @since 395 | 396 | ```php 397 | /** 398 | * @since 1.0.1 First time this was introduced. 399 | * 400 | * @return integer Indicates the number of items. 401 | */ 402 | function count() 403 | { 404 | ... 405 | } 406 | 407 | /** 408 | * @since 1.0.2 Added the $b argument. 409 | * @since 1.0.1 Added the $a argument. 410 | * @since 1.0.0 411 | * 412 | * @return void 413 | */ 414 | function dump($a, $b) 415 | { 416 | ... 417 | } 418 | ``` 419 | 420 | @source 421 | 422 | ```php 423 | /** 424 | * @source 2 1 Check that ensures lazy counting. 425 | */ 426 | function count() 427 | { 428 | if(null === $this->count) 429 | { 430 | ... 431 | } 432 | } 433 | ``` 434 | 435 | @subpackage 436 | 437 | ```php 438 | /** 439 | * @package PSR 440 | * @subpackage Documentation\API 441 | */ 442 | ``` 443 | 444 | @throws 445 | 446 | ```php 447 | /** 448 | * Counts the number of items in the provided array. 449 | * 450 | * @param mixed[] $array Array structure to count the elements of. 451 | * 452 | * @throws InvalidArgumentException if the provided argument is not of type 453 | * 'array'. 454 | * 455 | * @return int Returns the number of elements. 456 | */ 457 | function count($items) 458 | { 459 | ... 460 | } 461 | ``` 462 | 463 | @todo 464 | 465 | ```php 466 | /** 467 | * Counts the number of items in the provided array. 468 | * 469 | * @todo add an array parameter to count 470 | * 471 | * @return int Returns the number of elements. 472 | */ 473 | function count() 474 | { 475 | ... 476 | } 477 | ``` 478 | 479 | @uses & @used-by 480 | 481 | ```php 482 | /** 483 | * @uses MyClass::$items to retrieve the count from. 484 | * 485 | * @return integer Indicates the number of items. 486 | */ 487 | function count() 488 | { 489 | ... 490 | } 491 | ``` 492 | 493 | @version 494 | 495 | ```php 496 | /** 497 | * @version 1.0.1 498 | */ 499 | class Counter 500 | { 501 | ... 502 | } 503 | 504 | /** 505 | * @version GIT: $Id$ In development. Very unstable. 506 | */ 507 | class NeoCounter 508 | { 509 | ... 510 | } 511 | ``` 512 | 513 | Arrays 514 | ====== 515 | 516 | unspecified: 517 | 518 | ```php 519 | /* 520 | * @return array 521 | */ 522 | ``` 523 | 524 | specified containing a single type: 525 | 526 | ```php 527 | /* 528 | * @return int[] 529 | */ 530 | ``` 531 | 532 | specified containing multiple types: 533 | 534 | ```php 535 | /* 536 | * @return (int|string)[] 537 | */ 538 | ``` 539 | 540 | Multiple Types 541 | ============== 542 | 543 | ```php 544 | /* 545 | * @return int|null 546 | */ 547 | ``` 548 | -------------------------------------------------------------------------------- /css/stylesheet.css: -------------------------------------------------------------------------------- 1 | #downloads:after{content:".";display:block;height:0;clear:both;visibility:hidden;font-size:0}#downloads{display:inline-block}.small{margin-left: 10px;}#downloads{display:block;-height:1px}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}body{font-size:1em;line-height:1.5;background:#e7e7e7 url(../images/body-bg.png) repeat 0 0;font-family:"Helvetica Neue",Helvetica,Arial,serif;text-shadow:0 1px 0 rgba(255,255,255,.8);color:#6d6d6d}a{color:#d5000d}a:hover{color:#c5000c}header{padding-top:35px;padding-bottom:7px}#title{font-family:MuseoSlab500,Helvetica,Arial;font-weight:900;letter-spacing:-1px;font-size:48px;color:#c92434;line-height:1.2}header h1{letter-spacing:-1px;font-family:MuseoSlab500,Helvetica,Arial;font-weight:400;font-size:24px;color:#bd8a5d;font-weight:400;line-height:1.3}#container{background:transparent url(../images/highlight-bg.jpg) no-repeat 50% 0;min-height:595px}#container .inner img{max-width:100%}#downloads{margin-bottom:40px}a.button{-moz-border-radius:30px;-webkit-border-radius:30px;border-radius:30px;border-top:1px solid #cbcbcb;border-left:1px solid #b7b7b7;border-right:1px solid #b7b7b7;border-bottom:1px solid #b3b3b3;color:#303030;line-height:25px;font-weight:700;font-size:15px;padding:12px 8px;display:block;float:left;width:179px;margin-right:14px;background:#fdfdfd;background:-moz-linear-gradient(top,#fdfdfd 0,#f2f2f2 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fdfdfd),color-stop(100%,#f2f2f2));background:-webkit-linear-gradient(top,#fdfdfd 0,#f2f2f2 100%);background:-o-linear-gradient(top,#fdfdfd 0,#f2f2f2 100%);background:-ms-linear-gradient(top,#fdfdfd 0,#f2f2f2 100%);background:linear-gradient(top,#fdfdfd 0,#f2f2f2 100%);filter:progid:dximagetransform.Microsoft.gradient(startcolorstr='#fdfdfd',endcolorstr='#f2f2f2',gradienttype=0);-webkit-box-shadow:10px 10px 5px #888;-moz-box-shadow:10px 10px 5px #888;box-shadow:0 1px 5px #e8e8e8}a.button:hover{border-top:1px solid #b7b7b7;border-left:1px solid #b3b3b3;border-right:1px solid #b3b3b3;border-bottom:1px solid #b3b3b3;background:#fafafa;background:-moz-linear-gradient(top,#fdfdfd 0,#f6f6f6 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fdfdfd),color-stop(100%,#f6f6f6));background:-webkit-linear-gradient(top,#fdfdfd 0,#f6f6f6 100%);background:-o-linear-gradient(top,#fdfdfd 0,#f6f6f6 100%);background:-ms-linear-gradient(top,#fdfdfd 0,#f6f6f6 100%);background:linear-gradient(top,#fdfdfd 0,#f6f6f6,100%);filter:progid:dximagetransform.Microsoft.gradient(startcolorstr='#fdfdfd',endcolorstr='#f6f6f6',gradienttype=0)}a.button span{padding-left:50px;display:block;height:23px}#download-zip span{background:transparent url(../images/zip-icon.png) no-repeat 12px 50%}#download-tar-gz span{background:transparent url(../images/tar-gz-icon.png) no-repeat 12px 50%}#view-on-github span{background:transparent url(../images/octocat-icon.png) no-repeat 12px 50%}#donate{width:72px;height:0;margin:0 0 0 -52px;padding:0}#donate span{padding-left:101px}code,pre{font-family:Monaco,"Bitstream Vera Sans Mono","Lucida Console",Terminal;color:#222;margin-bottom:30px;font-size:14px}code{background-color:#f2f2f2;border:1px solid #ddd;padding:0 3px}pre{padding:20px;background:#303030;color:#f2f2f2;text-shadow:none;overflow:auto}pre code{color:#f2f2f2;background-color:#303030;border:0;padding:0}ul,ol,dl{margin-bottom:20px}hr{height:1px;line-height:1px;margin-top:1em;padding-bottom:1em;border:0;background:transparent url(../images/hr.png) no-repeat 50% 0}strong{font-weight:700}em{font-style:italic}table{width:100%;border:1px solid #ebebeb}th{font-weight:500}td{border:1px solid #ebebeb;text-align:center;font-weight:300}form{background:#f2f2f2;padding:20px}h1{font-size:32px}h2{font-size:22px;font-weight:700;color:#303030;margin-bottom:8px}h3{color:#d5000d;font-size:18px;font-weight:700;margin-bottom:8px}h4{font-size:16px;color:#303030;font-weight:700}h5{font-size:1em;color:#303030}h6{font-size:.8em;color:#303030}p{font-weight:300;margin-bottom:20px}a{text-decoration:none}p a{font-weight:400}blockquote{font-size:1.6em;border-left:10px solid #e9e9e9;margin-bottom:20px;padding:0 0 0 30px}ul li{list-style:disc inside;padding-left:20px}ol li{list-style:decimal inside;padding-left:3px}dl dt{color:#303030}footer{background:transparent url(../images/hr.png) no-repeat 0 0;margin-top:40px;padding-top:20px;padding-bottom:30px;font-size:13px;color:#aaa}footer a{color:#666}footer a:hover{color:#444}.clearfix:after{clear:both;content:'.';display:block;visibility:hidden;height:0}.clearfix{display:inline-block}* html .clearfix{height:1%}.clearfix{display:block}@media only screen and (max-width:767px){header{padding-top:10px;padding-bottom:10px}}#downloads{position:relative}#downloads a#download-zip{position:absolute;right:-36px;top:-83px}@media only screen and (max-width:767px){#download-zip,#download-tar-gz{display:none}} -------------------------------------------------------------------------------- /css/vendor.css: -------------------------------------------------------------------------------- 1 | #contactable #contactable_inner{background-image:url(vendor/contactable/images/feeback.png);color:#fff;background-color:#333;cursor:pointer;height:102px;left:0;margin-left:-5px;*margin-left:-5px;overflow:hidden;position:fixed;*position:absolute;text-indent:-100000px;top:102px;*margin-top:10px;width:44px;z-index:100000}#contactable #contactForm{background-color:#333;border:2px solid #fff;color:#fff;height:450px;left:0;margin-left:-400px;*margin-left:-434px;margin-top:-160px;overflow:hidden;padding-left:30px;position:fixed;top:200px;width:360px;*width:394px;z-index:99}#contactable form#contactForm input,textarea{background:#fff none repeat scroll 0 0;outline-style:none;outline-width:medium;width:325px;padding:5px;border:1px solid #dfdfdf;font-family:georgia;font-size:1em;margin-bottom:10px}#contactable form#contactForm .submit{background:#f5410f none repeat scroll 0 0;outline-style:none;outline-width:medium;width:325px;padding:5px;border:3px solid #f52d0f;outline-color:-moz-use-text-color;font-family:georgia;font-size:1em;cursor:pointer;color:#fff;text-transform:uppercase;font-weight:bolder;font-family:Helvetica;margin-top:10px}#contactable form#contactForm p{width:325px;font-size:.9em}#contactable form#contactForm .disclaimer{*margin-left:20px}#contactable #contactForm .red{color:#f5410f}#contactable #overlay{background-color:#666;display:none;height:100%;left:0;margin:0;padding:0;position:absolute;top:0;width:100%;z-index:0}#contactable .error{background-color:#edbe9c}#contactable #name.error{background-color:#edbe9c}#contactable #email.error{background-color:#edbe9c}#contactable #comment.error{background-color:#edbe9c}#contactable form#contactForm label{*margin-left:20px}#contactable form#contactForm #loading{background:url(vendor/contactable/images/ajax-loader.gif) no-repeat;width:55px;height:55px;margin:100px auto;display:none}#contactable #callback{font-family:georgia;font-size:1.1em;color:#fff;width:325px;margin:100px auto;display:none}#contactable .holder{margin:0 auto;*margin-left:20px;padding-top:20px}#downloads:after{content:".";display:block;height:0;clear:both;visibility:hidden;font-size:0}#downloads{display:inline-block}#downloads{display:block;-height:1px}ul,li,ul li{list-style:none;margin:0;padding:0}a{color:#ae090a}a:hover{color:#ae090a}a.small{font-size:12px}a.button#donate{margin-right:0}a.button{margin-right:50px}h2 small{font-size:13px;letter-spacing:0;font-weight:700}#downloads{position:relative;margin:0}#downloads a#download-zip{position:absolute;right:-46px;top:-91px}.nav-pills{width:970px;position:relative;left:-7px;top:0}.nav-pills li.active a{background-color:#303030;text-shadow:none}.nav-pills li.active a:hover{background-color:#ae090a}.nav-pills li a{color:#417282;font-weight:700}.nav-pills li a:hover{background-color:#417282;color:#f5f5f5;text-shadow:none}table{margin-bottom:22px;-moz-border-radius:5px;-webkit-border-radius:5px;-khtml-border-radius:5px;border-radius:5px}table td{text-align:left;border:1px solid #d0d0d0;padding-left:7px;padding-right:7px;padding-top:3px;padding-bottom:3px;background-color:#f5f5f5}table tr th{text-align:left;display:none}ul li ul{margin-top:3px;font-weight:400;margin-bottom:16px;margin-left:16px}ul.left li{float:left;position:relative;left:0;top:0;width:109px}#contactable *{text-shadow:none}ul.indent li img,ul.indent li p{margin-left:30px}ul.indent li h4{margin-bottom:10px;margin-top:20px}#ribbon{margin:0;padding:0;width:149px;height:149px;overflow:hidden;position:fixed;top:0;right:0}#ribbon *{margin:0;padding:0}header{position:relative}header .version{position:absolute;left:515px;top:48px;background-color:#417282;-moz-border-radius:5px;-webkit-border-radius:5px;-khtml-border-radius:5px;border-radius:5px;color:#f5f5f5;-moz-text-shadow:none;-webkit-text-shadow:none;-o-text-shadow:none;text-shadow:none;padding-left:6px;padding-right:6px;padding-top:3px;padding-bottom:3px}nav{position:relative}.sticky{position:fixed;top:0}nav.sticky{background-color:#333;padding-top:3px;padding-left:10px;padding-right:10px;margin-left:-10px;height:40px;margin-top:-1px;-moz-border-radius:0 0 10px 10px;-webkit-border-radius:0 0 10px 10px;-khtml-border-radius:0 0 10px 10px;border-radius:0 0 10px 10px;width:945px}nav.sticky *{-moz-text-shadow:none;-webkit-text-shadow:none;-o-text-shadow:none;text-shadow:none}nav.sticky a{color:#f5f5f5}#main_content h2{border-top:60px solid transparent}#main_content h2#controller{border-top:0 solid #000}#social{position:absolute;right:154px;top:-4px}#social li{float:left;position:relative;left:0;top:0}#toTop{display:none;text-decoration:none;position:fixed;bottom:10px;right:10px;overflow:hidden;width:51px;height:51px;border:0;text-indent:100%;background:url(../images/ui.totop.png) no-repeat left top}#toTopHover{background:url(vendor/uItoTop/img/ui.totop.png) no-repeat left -51px;width:51px;height:51px;display:block;overflow:hidden;float:left;opacity:0;-moz-opacity:0;filter:alpha(opacity=0)}#toTop:active,#toTop:focus{outline:0} -------------------------------------------------------------------------------- /images/body-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jruzafa/PhpDoc2Cheatsheet/4700b316b79b63893c640298a9f73e52702b9356/images/body-bg.png -------------------------------------------------------------------------------- /images/forkme_right_red_aa0000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jruzafa/PhpDoc2Cheatsheet/4700b316b79b63893c640298a9f73e52702b9356/images/forkme_right_red_aa0000.png -------------------------------------------------------------------------------- /images/highlight-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jruzafa/PhpDoc2Cheatsheet/4700b316b79b63893c640298a9f73e52702b9356/images/highlight-bg.jpg -------------------------------------------------------------------------------- /images/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jruzafa/PhpDoc2Cheatsheet/4700b316b79b63893c640298a9f73e52702b9356/images/hr.png -------------------------------------------------------------------------------- /images/logo-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jruzafa/PhpDoc2Cheatsheet/4700b316b79b63893c640298a9f73e52702b9356/images/logo-trans.png -------------------------------------------------------------------------------- /images/ui.totop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jruzafa/PhpDoc2Cheatsheet/4700b316b79b63893c640298a9f73e52702b9356/images/ui.totop.png -------------------------------------------------------------------------------- /js/navbar2.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // Do our DOM lookups beforehand 4 | var nav_container = $(".nav-container"); 5 | var nav = $("nav"); 6 | 7 | var top_spacing = 0; 8 | var waypoint_offset = 300; 9 | 10 | nav_container.waypoint({ 11 | handler: function(event, direction) { 12 | 13 | if (direction == 'down') { 14 | 15 | nav_container.css({ 'height':nav.outerHeight() }); 16 | nav.stop().addClass("sticky").css("top",-nav.outerHeight()).animate({"top":top_spacing}); 17 | 18 | } else { 19 | 20 | nav_container.css({ 'height':'auto' }); 21 | nav.stop().removeClass("sticky").css("top",nav.outerHeight()+waypoint_offset).animate({"top":""}); 22 | 23 | } 24 | 25 | }, 26 | offset: function() { 27 | return -nav.outerHeight()-waypoint_offset; 28 | } 29 | }); 30 | 31 | var sections = $("section"); 32 | var navigation_links = $("nav a"); 33 | 34 | sections.waypoint({ 35 | handler: function(event, direction) { 36 | 37 | var active_section; 38 | active_section = $(this); 39 | if (direction === "up") active_section = active_section.prev(); 40 | 41 | var active_link = $('nav a[href="#' + active_section.attr("id") + '"]'); 42 | navigation_links.removeClass("selected"); 43 | active_link.addClass("selected"); 44 | 45 | }, 46 | offset: '25%' 47 | }) 48 | 49 | 50 | // navigation_links.click( function(event) { 51 | 52 | // $.scrollTo('#validation',500); 53 | // $.scrollTo( 54 | // $(this).attr("href"), 55 | // { 56 | // duration: 200, 57 | // offset: { 'left':0, 'top':-0.15*$(window).height() } 58 | // } 59 | // ); 60 | // }); 61 | 62 | 63 | }); -------------------------------------------------------------------------------- /js/waypoints.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Waypoints - v1.1.4 3 | Copyright (c) 2011-2012 Caleb Troughton 4 | Dual licensed under the MIT license and GPL license. 5 | https://github.com/imakewebthings/jquery-waypoints/blob/master/MIT-license.txt 6 | https://github.com/imakewebthings/jquery-waypoints/blob/master/GPL-license.txt 7 | */ 8 | (function($,k,m,i,d){var e=$(i),g="waypoint.reached",b=function(o,n){o.element.trigger(g,n);if(o.options.triggerOnce){o.element[k]("destroy")}},h=function(p,o){var n=o.waypoints.length-1;while(n>=0&&o.waypoints[n].element[0]!==p[0]){n-=1}return n},f=[],l=function(n){$.extend(this,{element:$(n),oldScroll:0,waypoints:[],didScroll:false,didResize:false,doScroll:$.proxy(function(){var q=this.element.scrollTop(),p=q>this.oldScroll,s=this,r=$.grep(this.waypoints,function(u,t){return p?(u.offset>s.oldScroll&&u.offset<=q):(u.offset<=s.oldScroll&&u.offset>q)}),o=r.length;if(!this.oldScroll||!q){$[m]("refresh")}this.oldScroll=q;if(!o){return}if(!p){r.reverse()}$.each(r,function(u,t){if(t.options.continuous||u===o-1){b(t,[p?"down":"up"])}})},this)});$(n).scroll($.proxy(function(){if(!this.didScroll){this.didScroll=true;i.setTimeout($.proxy(function(){this.doScroll();this.didScroll=false},this),$[m].settings.scrollThrottle)}},this)).resize($.proxy(function(){if(!this.didResize){this.didResize=true;i.setTimeout($.proxy(function(){$[m]("refresh");this.didResize=false},this),$[m].settings.resizeThrottle)}},this));e.load($.proxy(function(){this.doScroll()},this))},j=function(n){var o=null;$.each(f,function(p,q){if(q.element[0]===n){o=q;return false}});return o},c={init:function(o,n){this.each(function(){var u=$.fn[k].defaults.context,q,t=$(this);if(n&&n.context){u=n.context}if(!$.isWindow(u)){u=t.closest(u)[0]}q=j(u);if(!q){q=new l(u);f.push(q)}var p=h(t,q),s=p<0?$.fn[k].defaults:q.waypoints[p].options,r=$.extend({},s,n);r.offset=r.offset==="bottom-in-view"?function(){var v=$.isWindow(u)?$[m]("viewportHeight"):$(u).height();return v-$(this).outerHeight()}:r.offset;if(p<0){q.waypoints.push({element:t,offset:null,options:r})}else{q.waypoints[p].options=r}if(o){t.bind(g,o)}if(n&&n.handler){t.bind(g,n.handler)}});$[m]("refresh");return this},remove:function(){return this.each(function(o,p){var n=$(p);$.each(f,function(r,s){var q=h(n,s);if(q>=0){s.waypoints.splice(q,1)}})})},destroy:function(){return this.unbind(g)[k]("remove")}},a={refresh:function(){$.each(f,function(r,s){var q=$.isWindow(s.element[0]),n=q?0:s.element.offset().top,p=q?$[m]("viewportHeight"):s.element.height(),o=q?0:s.element.scrollTop();$.each(s.waypoints,function(u,x){if(!x){return}var t=x.options.offset,w=x.offset;if(typeof x.options.offset==="function"){t=x.options.offset.apply(x.element)}else{if(typeof x.options.offset==="string"){var v=parseFloat(x.options.offset);t=x.options.offset.indexOf("%")?Math.ceil(p*(v/100)):v}}x.offset=x.element.offset().top-n+o-t;if(x.options.onlyOnScroll){return}if(w!==null&&s.oldScroll>w&&s.oldScroll<=x.offset){b(x,["up"])}else{if(w!==null&&s.oldScroll=x.offset){b(x,["down"])}else{if(!w&&o>x.offset){b(x,["down"])}}}});s.waypoints.sort(function(u,t){return u.offset-t.offset})})},viewportHeight:function(){return(i.innerHeight?i.innerHeight:e.height())},aggregate:function(){var n=$();$.each(f,function(o,p){$.each(p.waypoints,function(q,r){n=n.add(r.element)})});return n}};$.fn[k]=function(n){if(c[n]){return c[n].apply(this,Array.prototype.slice.call(arguments,1))}else{if(typeof n==="function"||!n){return c.init.apply(this,arguments)}else{if(typeof n==="object"){return c.init.apply(this,[null,n])}else{$.error("Method "+n+" does not exist on jQuery "+k)}}}};$.fn[k].defaults={continuous:true,offset:0,triggerOnce:false,context:i};$[m]=function(n){if(a[n]){return a[n].apply(this)}else{return a.aggregate()}};$[m].settings={resizeThrottle:200,scrollThrottle:100};e.load(function(){$[m]("refresh")})})(jQuery,"waypoint","waypoints",this); -------------------------------------------------------------------------------- /min/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine on 3 | 4 | # You may need RewriteBase on some servers 5 | #RewriteBase /personal/phpdoc2cheatsheet/min 6 | 7 | # rewrite URLs like "/min/f=..." to "/min/?f=..." 8 | RewriteRule ^([bfg]=.*) index.php?$1 [L,NE] 9 | 10 | 11 | # In case AddOutputFilterByType has been added 12 | SetEnv no-gzip 13 | 14 | -------------------------------------------------------------------------------- /min/builder/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine on 3 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] 4 | -------------------------------------------------------------------------------- /min/builder/_index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Minify URI Builder 3 | */ 4 | var MUB = { 5 | _uid : 0, 6 | _minRoot : '/min/?', 7 | checkRewrite : function () { 8 | var testUri = location.pathname.replace(/\/[^\/]*$/, '/rewriteTest.js').substr(1); 9 | function fail() { 10 | $('#minRewriteFailed')[0].className = 'topNote'; 11 | } 12 | $.ajax({ 13 | url : '../f=' + testUri + '&' + (new Date()).getTime(), 14 | success : function (data) { 15 | if (data === '1') { 16 | MUB._minRoot = '/min/'; 17 | $('span.minRoot').html('/min/'); 18 | } else 19 | fail(); 20 | }, 21 | error : fail 22 | }); 23 | }, 24 | /** 25 | * Get markup for new source LI element 26 | */ 27 | newLi : function () { 28 | return '
  • http://' + location.host + '/' + 29 | ' ' + 30 | '
  • '; 31 | }, 32 | /** 33 | * Add new empty source LI and attach handlers to buttons 34 | */ 35 | addLi : function () { 36 | $('#sources').append(MUB.newLi()); 37 | var li = $('#li' + MUB._uid)[0]; 38 | $('button[title=Remove]', li).click(function () { 39 | $('#results').hide(); 40 | var hadValue = !!$('input', li)[0].value; 41 | $(li).remove(); 42 | }); 43 | $('button[title$=Earlier]', li).click(function () { 44 | $(li).prev('li').find('input').each(function () { 45 | $('#results').hide(); 46 | // this = previous li input 47 | var tmp = this.value; 48 | this.value = $('input', li).val(); 49 | $('input', li).val(tmp); 50 | MUB.updateAllTestLinks(); 51 | }); 52 | }); 53 | $('button[title$=Later]', li).click(function () { 54 | $(li).next('li').find('input').each(function () { 55 | $('#results').hide(); 56 | // this = next li input 57 | var tmp = this.value; 58 | this.value = $('input', li).val(); 59 | $('input', li).val(tmp); 60 | MUB.updateAllTestLinks(); 61 | }); 62 | }); 63 | ++MUB._uid; 64 | }, 65 | /** 66 | * In the context of a source LI element, this will analyze the URI in 67 | * the INPUT and check the URL on the site. 68 | */ 69 | liUpdateTestLink : function () { // call in context of li element 70 | if (! $('input', this)[0].value) 71 | return; 72 | var li = this; 73 | $('span', this).html(''); 74 | var url = location.protocol + '//' + location.host + '/' + 75 | $('input', this)[0].value.replace(/^\//, ''); 76 | $.ajax({ 77 | url : url, 78 | complete : function (xhr, stat) { 79 | if ('success' === stat) 80 | $('span', li).html('✓'); 81 | else { 82 | $('span', li).html('') 83 | .find('button').click(function () { 84 | MUB.liUpdateTestLink.call(li); 85 | }); 86 | } 87 | }, 88 | dataType : 'text' 89 | }); 90 | }, 91 | /** 92 | * Check all source URLs 93 | */ 94 | updateAllTestLinks : function () { 95 | $('#sources li').each(MUB.liUpdateTestLink); 96 | }, 97 | /** 98 | * In a given array of strings, find the character they all have at 99 | * a particular index 100 | * @param Array arr array of strings 101 | * @param Number pos index to check 102 | * @return mixed a common char or '' if any do not match 103 | */ 104 | getCommonCharAtPos : function (arr, pos) { 105 | var i, 106 | l = arr.length, 107 | c = arr[0].charAt(pos); 108 | if (c === '' || l === 1) 109 | return c; 110 | for (i = 1; i < l; ++i) 111 | if (arr[i].charAt(pos) !== c) 112 | return ''; 113 | return c; 114 | }, 115 | /** 116 | * Get the shortest URI to minify the set of source files 117 | * @param Array sources URIs 118 | */ 119 | getBestUri : function (sources) { 120 | var pos = 0, 121 | base = '', 122 | c; 123 | while (true) { 124 | c = MUB.getCommonCharAtPos(sources, pos); 125 | if (c === '') 126 | break; 127 | else 128 | base += c; 129 | ++pos; 130 | } 131 | base = base.replace(/[^\/]+$/, ''); 132 | var uri = MUB._minRoot + 'f=' + sources.join(','); 133 | if (base.charAt(base.length - 1) === '/') { 134 | // we have a base dir! 135 | var basedSources = sources, 136 | i, 137 | l = sources.length; 138 | for (i = 0; i < l; ++i) { 139 | basedSources[i] = sources[i].substr(base.length); 140 | } 141 | base = base.substr(0, base.length - 1); 142 | var bUri = MUB._minRoot + 'b=' + base + '&f=' + basedSources.join(','); 143 | //window.console && console.log([uri, bUri]); 144 | uri = uri.length < bUri.length ? uri : bUri; 145 | } 146 | return uri; 147 | }, 148 | /** 149 | * Create the Minify URI for the sources 150 | */ 151 | update : function () { 152 | MUB.updateAllTestLinks(); 153 | var sources = [], 154 | ext = false, 155 | fail = false, 156 | markup; 157 | $('#sources input').each(function () { 158 | var m, val; 159 | if (! fail && this.value && (m = this.value.match(/\.(css|js)$/))) { 160 | var thisExt = m[1]; 161 | if (ext === false) 162 | ext = thisExt; 163 | else if (thisExt !== ext) { 164 | fail = true; 165 | return alert('extensions must match!'); 166 | } 167 | this.value = this.value.replace(/^\//, ''); 168 | if (-1 !== $.inArray(this.value, sources)) { 169 | fail = true; 170 | return alert('duplicate file!'); 171 | } 172 | sources.push(this.value); 173 | } 174 | }); 175 | if (fail || ! sources.length) 176 | return; 177 | $('#groupConfig').val(" 'keyName' => array('//" + sources.join("', '//") + "'),"); 178 | var uri = MUB.getBestUri(sources), 179 | uriH = uri.replace(//, '>').replace(/&/, '&'); 180 | $('#uriA').html(uriH)[0].href = uri; 181 | if (ext === 'js') { 182 | markup = ''; 183 | } else { 184 | markup = ''; 185 | } 186 | $('#uriHtml').val(markup); 187 | $('#results').show(); 188 | }, 189 | /** 190 | * Handler for the "Add file +" button 191 | */ 192 | addButtonClick : function () { 193 | $('#results').hide(); 194 | MUB.addLi(); 195 | MUB.updateAllTestLinks(); 196 | $('#update').show().click(MUB.update); 197 | $('#sources li:last input')[0].focus(); 198 | }, 199 | /** 200 | * Runs on DOMready 201 | */ 202 | init : function () { 203 | $('#jsDidntLoad').remove(); 204 | $('#app').show(); 205 | $('#sources').html(''); 206 | $('#add button').click(MUB.addButtonClick); 207 | // make easier to copy text out of 208 | $('#uriHtml, #groupConfig, #symlinkOpt').click(function () { 209 | this.select(); 210 | }).focus(function () { 211 | this.select(); 212 | }); 213 | $('a.ext').attr({target:'_blank'}); 214 | if (location.hash) { 215 | // make links out of URIs from bookmarklet 216 | $('#getBm').hide(); 217 | var i = 0, found = location.hash.substr(1).split(','), l = found.length; 218 | $('#bmUris').html('

    Found by bookmarklet: /

    '); 219 | var $p = $('#bmUris p'); 220 | for (; i < l; i++) { 221 | $p.append($('').text(found[i])[0]); 222 | if (i < (l - 1)) { 223 | $p.append(', /'); 224 | } 225 | } 226 | $('#bmUris a').click(function () { 227 | MUB.addButtonClick(); 228 | $('#sources li:last input').val(this.innerHTML); 229 | MUB.liUpdateTestLink.call($('#sources li:last')[0]); 230 | $('#results').hide(); 231 | return false; 232 | }).attr({title:'Add file +'}); 233 | } else { 234 | // setup bookmarklet 1 235 | $.ajax({ 236 | url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm.js').substr(1), 237 | success : function (code) { 238 | $('#bm')[0].href = code 239 | .replace('%BUILDER_URL%', location.href) 240 | .replace(/\n/g, ' '); 241 | }, 242 | dataType : 'text' 243 | }); 244 | if ($.browser.msie) { 245 | $('#getBm p:last').append(' Sorry, not supported in MSIE!'); 246 | } 247 | MUB.addButtonClick(); 248 | } 249 | // setup bookmarklet 2 250 | $.ajax({ 251 | url : '../?f=' + location.pathname.replace(/\/[^\/]*$/, '/bm2.js').substr(1), 252 | success : function (code) { 253 | $('#bm2')[0].href = code.replace(/\n/g, ' '); 254 | }, 255 | dataType : 'text' 256 | }); 257 | MUB.checkRewrite(); 258 | } 259 | }; 260 | $(MUB.init); 261 | -------------------------------------------------------------------------------- /min/builder/bm.js: -------------------------------------------------------------------------------- 1 | javascript:(function() { 2 | var d = document 3 | ,uris = [] 4 | ,i = 0 5 | ,o 6 | ,home = (location + '').split('/').splice(0, 3).join('/') + '/'; 7 | function add(uri) { 8 | return (0 === uri.indexOf(home)) 9 | && (!/[\?&]/.test(uri)) 10 | && uris.push(escape(uri.substr(home.length))); 11 | }; 12 | function sheet(ss) { 13 | // we must check the domain with add() before accessing ss.cssRules 14 | // otherwise a security exception will be thrown 15 | if (ss.href && add(ss.href) && ss.cssRules) { 16 | var i = 0, r; 17 | while (r = ss.cssRules[i++]) 18 | r.styleSheet && sheet(r.styleSheet); 19 | } 20 | }; 21 | while (o = d.getElementsByTagName('script')[i++]) 22 | o.src && !(o.type && /vbs/i.test(o.type)) && add(o.src); 23 | i = 0; 24 | while (o = d.styleSheets[i++]) 25 | /* http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-DocumentStyle-styleSheets 26 | document.styleSheet is a list property where [0] accesses the 1st element and 27 | [outOfRange] returns null. In IE, styleSheets is a function, and also throws an 28 | exception when you check the out of bounds index. (sigh) */ 29 | sheet(o); 30 | if (uris.length) 31 | window.open('%BUILDER_URL%#' + uris.join(',')); 32 | else 33 | alert('No js/css files found with URLs within "' 34 | + home.split('/')[2] 35 | + '".\n(This tool is limited to URLs with the same domain.)'); 36 | })(); -------------------------------------------------------------------------------- /min/builder/bm2.js: -------------------------------------------------------------------------------- 1 | javascript:(function(){ 2 | var d = document 3 | ,c = d.cookie 4 | ,m = c.match(/\bminifyDebug=([^; ]+)/) 5 | ,v = m ? decodeURIComponent(m[1]) : '' 6 | ,p = prompt('Debug Minify URIs on ' + location.hostname + ' which contain:' 7 | + '\n(empty for none, space = OR, * = any string, ? = any char)', v) 8 | ; 9 | if (p === null) return; 10 | p = p.replace(/^\s+|\s+$/, ''); 11 | v = (p === '') 12 | ? 'minifyDebug=; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/' 13 | : 'minifyDebug=' + encodeURIComponent(p) + '; path=/'; 14 | d.cookie = v; 15 | })(); -------------------------------------------------------------------------------- /min/builder/index.php: -------------------------------------------------------------------------------- 1 | $min_builderPassword)); 39 | } 40 | 41 | $cachePathCode = ''; 42 | if (! isset($min_cachePath) && ! function_exists('sys_get_temp_dir')) { 43 | $detectedTmp = Minify_Cache_File::tmp(); 44 | $cachePathCode = "\$min_cachePath = " . var_export($detectedTmp, 1) . ';'; 45 | } 46 | 47 | ob_start(); 48 | ?> 49 | 50 | Minify URI Builder 51 | 52 | 71 | 72 | 73 |
    Note: It looks like you're running Minify in a user 74 | directory. You may need the following option in /min/config.php to have URIs 75 | correctly rewritten in CSS output: 76 |
    77 |
    78 | 79 | 80 |

    Uh Oh. Minify was unable to 81 | serve Javascript for this app. To troubleshoot this, 82 | enable FirePHP debugging 83 | and request the Minify URL directly. Hopefully the 84 | FirePHP console will report the cause of the error. 85 |

    86 | 87 | 88 |

    Note: was discovered as a usable temp directory.
    To 90 | slightly improve performance you can hardcode this in /min/config.php: 91 |

    92 | 93 | 94 |

    Note: Your webserver does not seem to 95 | support mod_rewrite (used in /min/.htaccess). Your Minify URIs will contain "?", which 96 | may reduce the benefit of proxy cache servers.

    98 | 99 |

    Minify URI Builder

    100 | 101 | 103 | 104 |
    105 | 106 |

    Create a list of Javascript or CSS files (or 1 is fine) you'd like to combine 107 | and click [Update].

    108 | 109 |
    110 |
    111 | 112 |
    113 | 114 |

    115 | 116 |
    117 | 118 |

    Minify URI

    119 |

    Place this URI in your HTML to serve the files above combined, minified, compressed and 120 | with cache headers.

    121 | 122 | 123 | 124 |
    URI/min (opens in new window)
    HTML
    125 | 126 |

    How to serve these files as a group

    127 |

    For the best performance you can serve these files as a pre-defined group with a URI 128 | like: /min/?g=keyName

    129 |

    To do this, add a line like this to /min/groupsConfig.php:

    130 | 131 |
    return array(
    132 |     ... your existing groups here ...
    133 | 
    134 | );
    135 | 136 |

    Make sure to replace keyName with a unique key for this group.

    137 |
    138 | 139 |
    140 |

    Find URIs on a Page

    141 |

    You can use the bookmarklet below to fetch all CSS & Javascript URIs from a page 142 | on your site. When you active it, this page will open in a new window with a list of 143 | available URIs to add.

    144 | 145 |

    Create Minify URIs (right-click, add to bookmarks)

    146 |
    147 | 148 |

    Combining CSS files that contain @import

    149 |

    If your CSS files contain @import declarations, Minify will not 150 | remove them. Therefore, you will want to remove those that point to files already 151 | in your list, and move any others to the top of the first file in your list 152 | (imports below any styles will be ignored by browsers as invalid).

    153 |

    If you desire, you can use Minify URIs in imports and they will not be touched 154 | by Minify. E.g. @import "/min/?g=css2";

    155 | 156 |

    Debug Mode

    157 |

    When /min/config.php has $min_allowDebugFlag = true; 158 | you can get debug output by appending &debug to a Minify URL, or 159 | by sending the cookie minDebug=<match>, where <match> 160 | should be a string in the Minify URIs you'd like to debug. This bookmarklet will allow you to 161 | set this cookie.

    162 |

    Minify Debug (right-click, add to bookmarks)

    163 | 164 |
    165 | 166 |
    167 |

    Need help? Check the wiki, 168 | or post to the discussion 169 | list.

    170 |

    Powered by Minify

    171 | 172 | 173 | 174 | 218 | 219 | $content 231 | ,'id' => __FILE__ 232 | ,'lastModifiedTime' => max( 233 | // regenerate cache if any of these change 234 | filemtime(__FILE__) 235 | ,filemtime(dirname(__FILE__) . '/../config.php') 236 | ,filemtime(dirname(__FILE__) . '/../lib/Minify.php') 237 | ) 238 | ,'minifyAll' => true 239 | ,'encodeOutput' => $encodeOutput 240 | )); 241 | -------------------------------------------------------------------------------- /min/builder/ocCheck.php: -------------------------------------------------------------------------------- 1 | 'World!' 27 | ,'method' => 'deflate' 28 | )); 29 | $he->encode(); 30 | $he->sendAll(); 31 | 32 | } else { 33 | // echo status "0" or "1" 34 | header('Content-Type: text/plain'); 35 | echo (int)$_oc; 36 | } 37 | -------------------------------------------------------------------------------- /min/builder/rewriteTest.js: -------------------------------------------------------------------------------- 1 | 1 -------------------------------------------------------------------------------- /min/builder/test.php: -------------------------------------------------------------------------------- 1 | realpath(DOCUMENT_ROOT) failed. You may need " 29 | . "to set \$min_documentRoot manually (hopefully realpath() is not " 30 | . "broken in your environment).

    "; 31 | } 32 | if (0 !== strpos(realpath(__FILE__), realpath($_SERVER['DOCUMENT_ROOT']))) { 33 | echo "

    DOCUMENT_ROOT doesn't contain this file. You may " 34 | . " need to set \$min_documentRoot manually

    "; 35 | } 36 | if (isset($_SERVER['SUBDOMAIN_DOCUMENT_ROOT'])) { 37 | echo "

    \$_SERVER['SUBDOMAIN_DOCUMENT_ROOT'] is set. " 38 | . "You may need to set \$min_documentRoot to this in config.php

    "; 39 | } 40 | 41 | } 42 | 43 | //*/ -------------------------------------------------------------------------------- /min/config-test.php: -------------------------------------------------------------------------------- 1 | 150 | * array('//symlink' => '/real/target/path') // unix 151 | * array('//static' => 'D:\\staticStorage') // Windows 152 | * 153 | */ 154 | $min_symlinks = array(); 155 | 156 | 157 | /** 158 | * If you upload files from Windows to a non-Windows server, Windows may report 159 | * incorrect mtimes for the files. This may cause Minify to keep serving stale 160 | * cache files when source file changes are made too frequently (e.g. more than 161 | * once an hour). 162 | * 163 | * Immediately after modifying and uploading a file, use the touch command to 164 | * update the mtime on the server. If the mtime jumps ahead by a number of hours, 165 | * set this variable to that number. If the mtime moves back, this should not be 166 | * needed. 167 | * 168 | * In the Windows SFTP client WinSCP, there's an option that may fix this 169 | * issue without changing the variable below. Under login > environment, 170 | * select the option "Adjust remote timestamp with DST". 171 | * @link http://winscp.net/eng/docs/ui_login_environment#daylight_saving_time 172 | */ 173 | $min_uploaderHoursBehind = 0; 174 | 175 | 176 | $min_serveOptions['contentTypeCharset'] = 'utf-8'; 177 | 178 | /** 179 | * Path to Minify's lib folder. If you happen to move it, change 180 | * this accordingly. 181 | */ 182 | $min_libPath = dirname(__FILE__) . '/lib'; 183 | 184 | 185 | // try to disable output_compression (may not have an effect) 186 | ini_set('zlib.output_compression', '1'); 187 | -------------------------------------------------------------------------------- /min/groupsConfig.php: -------------------------------------------------------------------------------- 1 | array('//js/file1.js', '//js/file2.js'), 16 | // 'css' => array('//css/file1.css', '//css/file2.css'), 17 | ); -------------------------------------------------------------------------------- /min/index.php: -------------------------------------------------------------------------------- 1 | $target) { 36 | $min_serveOptions['minApp']['allowDirs'][] = $target; 37 | } 38 | 39 | if ($min_allowDebugFlag) { 40 | $min_serveOptions['debug'] = Minify_DebugDetector::shouldDebugRequest($_COOKIE, $_GET, $_SERVER['REQUEST_URI']); 41 | } 42 | 43 | if ($min_errorLogger) { 44 | if (true === $min_errorLogger) { 45 | $min_errorLogger = FirePHP::getInstance(true); 46 | } 47 | Minify_Logger::setLogger($min_errorLogger); 48 | } 49 | 50 | // check for URI versioning 51 | if (preg_match('/&\\d/', $_SERVER['QUERY_STRING'])) { 52 | $min_serveOptions['maxAge'] = 31536000; 53 | } 54 | if (isset($_GET['g'])) { 55 | // well need groups config 56 | $min_serveOptions['minApp']['groups'] = (require MINIFY_MIN_DIR . '/groupsConfig.php'); 57 | } 58 | if (isset($_GET['f']) || isset($_GET['g'])) { 59 | // serve! 60 | 61 | if (! isset($min_serveController)) { 62 | $min_serveController = new Minify_Controller_MinApp(); 63 | } 64 | Minify::serve($min_serveController, $min_serveOptions); 65 | 66 | } elseif ($min_enableBuilder) { 67 | header('Location: builder/'); 68 | exit(); 69 | } else { 70 | header("Location: /"); 71 | exit(); 72 | } -------------------------------------------------------------------------------- /min/lib/DooDigestAuth.php: -------------------------------------------------------------------------------- 1 | 6 | * @link http://www.doophp.com/ 7 | * @copyright Copyright © 2009 Leng Sheng Hong 8 | * @license http://www.doophp.com/license 9 | */ 10 | 11 | /** 12 | * Handles HTTP digest authentication 13 | * 14 | *

    HTTP digest authentication can be used with the URI router. 15 | * HTTP digest is much more recommended over the use of HTTP Basic auth which doesn't provide any encryption. 16 | * If you are running PHP on Apache in CGI/FastCGI mode, you would need to 17 | * add the following line to your .htaccess for digest auth to work correctly.

    18 | * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] 19 | * 20 | *

    This class is tested under Apache 2.2 and Cherokee web server. It should work in both mod_php and cgi mode.

    21 | * 22 | * @author Leng Sheng Hong 23 | * @version $Id: DooDigestAuth.php 1000 2009-07-7 18:27:22 24 | * @package doo.auth 25 | * @since 1.0 26 | */ 27 | class DooDigestAuth{ 28 | 29 | /** 30 | * Authenticate against a list of username and passwords. 31 | * 32 | *

    HTTP Digest Authentication doesn't work with PHP in CGI mode, 33 | * you have to add this into your .htaccess RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

    34 | * 35 | * @param string $realm Name of the authentication session 36 | * @param array $users An assoc array of username and password: array('uname1'=>'pwd1', 'uname2'=>'pwd2') 37 | * @param string $fail_msg Message to be displayed if the User cancel the login 38 | * @param string $fail_url URL to be redirect if the User cancel the login 39 | * @return string The username if login success. 40 | */ 41 | public static function http_auth($realm, $users, $fail_msg=NULL, $fail_url=NULL){ 42 | $realm = "Restricted area - $realm"; 43 | 44 | //user => password 45 | //$users = array('admin' => '1234', 'guest' => 'guest'); 46 | if(!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Digest')===0){ 47 | $_SERVER['PHP_AUTH_DIGEST'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; 48 | } 49 | 50 | if (empty($_SERVER['PHP_AUTH_DIGEST'])) { 51 | header('WWW-Authenticate: Digest realm="'.$realm. 52 | '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); 53 | header('HTTP/1.1 401 Unauthorized'); 54 | if($fail_msg!=NULL) 55 | die($fail_msg); 56 | if($fail_url!=NULL) 57 | die(""); 58 | exit; 59 | } 60 | 61 | // analyze the PHP_AUTH_DIGEST variable 62 | if (!($data = self::http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || !isset($users[$data['username']])){ 63 | header('WWW-Authenticate: Digest realm="'.$realm. 64 | '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); 65 | header('HTTP/1.1 401 Unauthorized'); 66 | if($fail_msg!=NULL) 67 | die($fail_msg); 68 | if($fail_url!=NULL) 69 | die(""); 70 | exit; 71 | } 72 | 73 | // generate the valid response 74 | $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]); 75 | $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']); 76 | $valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2); 77 | 78 | if ($data['response'] != $valid_response){ 79 | header('HTTP/1.1 401 Unauthorized'); 80 | header('WWW-Authenticate: Digest realm="'.$realm. 81 | '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); 82 | if($fail_msg!=NULL) 83 | die($fail_msg); 84 | if($fail_url!=NULL) 85 | die(""); 86 | exit; 87 | } 88 | 89 | // ok, valid username & password 90 | return $data['username']; 91 | } 92 | 93 | /** 94 | * Method to parse the http auth header, works with IE. 95 | * 96 | * Internet Explorer returns a qop="xxxxxxxxxxx" in the header instead of qop=xxxxxxxxxxx as most browsers do. 97 | * 98 | * @param string $txt header string to parse 99 | * @return array An assoc array of the digest auth session 100 | */ 101 | private static function http_digest_parse($txt) 102 | { 103 | $res = preg_match("/username=\"([^\"]+)\"/i", $txt, $match); 104 | $data['username'] = (isset($match[1]))?$match[1]:null; 105 | $res = preg_match('/nonce=\"([^\"]+)\"/i', $txt, $match); 106 | $data['nonce'] = $match[1]; 107 | $res = preg_match('/nc=([0-9]+)/i', $txt, $match); 108 | $data['nc'] = $match[1]; 109 | $res = preg_match('/cnonce=\"([^\"]+)\"/i', $txt, $match); 110 | $data['cnonce'] = $match[1]; 111 | $res = preg_match('/qop=([^,]+)/i', $txt, $match); 112 | $data['qop'] = str_replace('"','',$match[1]); 113 | $res = preg_match('/uri=\"([^\"]+)\"/i', $txt, $match); 114 | $data['uri'] = $match[1]; 115 | $res = preg_match('/response=\"([^\"]+)\"/i', $txt, $match); 116 | $data['response'] = $match[1]; 117 | return $data; 118 | } 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /min/lib/Minify/Build.php: -------------------------------------------------------------------------------- 1 | 12 | * // in config file 13 | * $groupSources = array( 14 | * 'js' => array('file1.js', 'file2.js') 15 | * ,'css' => array('file1.css', 'file2.css', 'file3.css') 16 | * ) 17 | * 18 | * // during HTML generation 19 | * $jsBuild = new Minify_Build($groupSources['js']); 20 | * $cssBuild = new Minify_Build($groupSources['css']); 21 | * 22 | * $script = ""; 24 | * $link = ""; 26 | * 27 | * // in min.php 28 | * Minify::serve('Groups', array( 29 | * 'groups' => $groupSources 30 | * ,'setExpires' => (time() + 86400 * 365) 31 | * )); 32 | * 33 | * 34 | * @package Minify 35 | * @author Stephen Clay 36 | */ 37 | class Minify_Build { 38 | 39 | /** 40 | * Last modification time of all files in the build 41 | * 42 | * @var int 43 | */ 44 | public $lastModified = 0; 45 | 46 | /** 47 | * String to use as ampersand in uri(). Set this to '&' if 48 | * you are not HTML-escaping URIs. 49 | * 50 | * @var string 51 | */ 52 | public static $ampersand = '&'; 53 | 54 | /** 55 | * Get a time-stamped URI 56 | * 57 | * 58 | * echo $b->uri('/site.js'); 59 | * // outputs "/site.js?1678242" 60 | * 61 | * echo $b->uri('/scriptaculous.js?load=effects'); 62 | * // outputs "/scriptaculous.js?load=effects&1678242" 63 | * 64 | * 65 | * @param string $uri 66 | * @param boolean $forceAmpersand (default = false) Force the use of ampersand to 67 | * append the timestamp to the URI. 68 | * @return string 69 | */ 70 | public function uri($uri, $forceAmpersand = false) { 71 | $sep = ($forceAmpersand || strpos($uri, '?') !== false) 72 | ? self::$ampersand 73 | : '?'; 74 | return "{$uri}{$sep}{$this->lastModified}"; 75 | } 76 | 77 | /** 78 | * Create a build object 79 | * 80 | * @param array $sources array of Minify_Source objects and/or file paths 81 | * 82 | * @return null 83 | */ 84 | public function __construct($sources) 85 | { 86 | $max = 0; 87 | foreach ((array)$sources as $source) { 88 | if ($source instanceof Minify_Source) { 89 | $max = max($max, $source->lastModified); 90 | } elseif (is_string($source)) { 91 | if (0 === strpos($source, '//')) { 92 | $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1); 93 | } 94 | if (is_file($source)) { 95 | $max = max($max, filemtime($source)); 96 | } 97 | } 98 | } 99 | $this->lastModified = $max; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /min/lib/Minify/CSS.php: -------------------------------------------------------------------------------- 1 | 15 | * @author http://code.google.com/u/1stvamp/ (Issue 64 patch) 16 | */ 17 | class Minify_CSS { 18 | 19 | /** 20 | * Minify a CSS string 21 | * 22 | * @param string $css 23 | * 24 | * @param array $options available options: 25 | * 26 | * 'preserveComments': (default true) multi-line comments that begin 27 | * with "/*!" will be preserved with newlines before and after to 28 | * enhance readability. 29 | * 30 | * 'removeCharsets': (default true) remove all @charset at-rules 31 | * 32 | * 'prependRelativePath': (default null) if given, this string will be 33 | * prepended to all relative URIs in import/url declarations 34 | * 35 | * 'currentDir': (default null) if given, this is assumed to be the 36 | * directory of the current CSS file. Using this, minify will rewrite 37 | * all relative URIs in import/url declarations to correctly point to 38 | * the desired files. For this to work, the files *must* exist and be 39 | * visible by the PHP process. 40 | * 41 | * 'symlinks': (default = array()) If the CSS file is stored in 42 | * a symlink-ed directory, provide an array of link paths to 43 | * target paths, where the link paths are within the document root. Because 44 | * paths need to be normalized for this to work, use "//" to substitute 45 | * the doc root in the link paths (the array keys). E.g.: 46 | * 47 | * array('//symlink' => '/real/target/path') // unix 48 | * array('//static' => 'D:\\staticStorage') // Windows 49 | * 50 | * 51 | * 'docRoot': (default = $_SERVER['DOCUMENT_ROOT']) 52 | * see Minify_CSS_UriRewriter::rewrite 53 | * 54 | * @return string 55 | */ 56 | public static function minify($css, $options = array()) 57 | { 58 | $options = array_merge(array( 59 | 'compress' => true, 60 | 'removeCharsets' => true, 61 | 'preserveComments' => true, 62 | 'currentDir' => null, 63 | 'docRoot' => $_SERVER['DOCUMENT_ROOT'], 64 | 'prependRelativePath' => null, 65 | 'symlinks' => array(), 66 | ), $options); 67 | 68 | if ($options['removeCharsets']) { 69 | $css = preg_replace('/@charset[^;]+;\\s*/', '', $css); 70 | } 71 | if ($options['compress']) { 72 | if (! $options['preserveComments']) { 73 | $css = Minify_CSS_Compressor::process($css, $options); 74 | } else { 75 | $css = Minify_CommentPreserver::process( 76 | $css 77 | ,array('Minify_CSS_Compressor', 'process') 78 | ,array($options) 79 | ); 80 | } 81 | } 82 | if (! $options['currentDir'] && ! $options['prependRelativePath']) { 83 | return $css; 84 | } 85 | if ($options['currentDir']) { 86 | return Minify_CSS_UriRewriter::rewrite( 87 | $css 88 | ,$options['currentDir'] 89 | ,$options['docRoot'] 90 | ,$options['symlinks'] 91 | ); 92 | } else { 93 | return Minify_CSS_UriRewriter::prepend( 94 | $css 95 | ,$options['prependRelativePath'] 96 | ); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /min/lib/Minify/CSS/Compressor.php: -------------------------------------------------------------------------------- 1 | 19 | * @author http://code.google.com/u/1stvamp/ (Issue 64 patch) 20 | */ 21 | class Minify_CSS_Compressor { 22 | 23 | /** 24 | * Minify a CSS string 25 | * 26 | * @param string $css 27 | * 28 | * @param array $options (currently ignored) 29 | * 30 | * @return string 31 | */ 32 | public static function process($css, $options = array()) 33 | { 34 | $obj = new Minify_CSS_Compressor($options); 35 | return $obj->_process($css); 36 | } 37 | 38 | /** 39 | * @var array 40 | */ 41 | protected $_options = null; 42 | 43 | /** 44 | * Are we "in" a hack? I.e. are some browsers targetted until the next comment? 45 | * 46 | * @var bool 47 | */ 48 | protected $_inHack = false; 49 | 50 | 51 | /** 52 | * Constructor 53 | * 54 | * @param array $options (currently ignored) 55 | */ 56 | private function __construct($options) { 57 | $this->_options = $options; 58 | } 59 | 60 | /** 61 | * Minify a CSS string 62 | * 63 | * @param string $css 64 | * 65 | * @return string 66 | */ 67 | protected function _process($css) 68 | { 69 | $css = str_replace("\r\n", "\n", $css); 70 | 71 | // preserve empty comment after '>' 72 | // http://www.webdevout.net/css-hacks#in_css-selectors 73 | $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css); 74 | 75 | // preserve empty comment between property and value 76 | // http://css-discuss.incutio.com/?page=BoxModelHack 77 | $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css); 78 | $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css); 79 | 80 | // apply callback to all valid comments (and strip out surrounding ws 81 | $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@' 82 | ,array($this, '_commentCB'), $css); 83 | 84 | // remove ws around { } and last semicolon in declaration block 85 | $css = preg_replace('/\\s*{\\s*/', '{', $css); 86 | $css = preg_replace('/;?\\s*}\\s*/', '}', $css); 87 | 88 | // remove ws surrounding semicolons 89 | $css = preg_replace('/\\s*;\\s*/', ';', $css); 90 | 91 | // remove ws around urls 92 | $css = preg_replace('/ 93 | url\\( # url( 94 | \\s* 95 | ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis) 96 | \\s* 97 | \\) # ) 98 | /x', 'url($1)', $css); 99 | 100 | // remove ws between rules and colons 101 | $css = preg_replace('/ 102 | \\s* 103 | ([{;]) # 1 = beginning of block or rule separator 104 | \\s* 105 | ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter) 106 | \\s* 107 | : 108 | \\s* 109 | (\\b|[#\'"-]) # 3 = first character of a value 110 | /x', '$1$2:$3', $css); 111 | 112 | // remove ws in selectors 113 | $css = preg_replace_callback('/ 114 | (?: # non-capture 115 | \\s* 116 | [^~>+,\\s]+ # selector part 117 | \\s* 118 | [,>+~] # combinators 119 | )+ 120 | \\s* 121 | [^~>+,\\s]+ # selector part 122 | { # open declaration block 123 | /x' 124 | ,array($this, '_selectorsCB'), $css); 125 | 126 | // minimize hex colors 127 | $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i' 128 | , '$1#$2$3$4$5', $css); 129 | 130 | // remove spaces between font families 131 | $css = preg_replace_callback('/font-family:([^;}]+)([;}])/' 132 | ,array($this, '_fontFamilyCB'), $css); 133 | 134 | $css = preg_replace('/@import\\s+url/', '@import url', $css); 135 | 136 | // replace any ws involving newlines with a single newline 137 | $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css); 138 | 139 | // separate common descendent selectors w/ newlines (to limit line lengths) 140 | $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css); 141 | 142 | // Use newline after 1st numeric value (to limit line lengths). 143 | $css = preg_replace('/ 144 | ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value 145 | \\s+ 146 | /x' 147 | ,"$1\n", $css); 148 | 149 | // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/ 150 | $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css); 151 | 152 | return trim($css); 153 | } 154 | 155 | /** 156 | * Replace what looks like a set of selectors 157 | * 158 | * @param array $m regex matches 159 | * 160 | * @return string 161 | */ 162 | protected function _selectorsCB($m) 163 | { 164 | // remove ws around the combinators 165 | return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]); 166 | } 167 | 168 | /** 169 | * Process a comment and return a replacement 170 | * 171 | * @param array $m regex matches 172 | * 173 | * @return string 174 | */ 175 | protected function _commentCB($m) 176 | { 177 | $hasSurroundingWs = (trim($m[0]) !== $m[1]); 178 | $m = $m[1]; 179 | // $m is the comment content w/o the surrounding tokens, 180 | // but the return value will replace the entire comment. 181 | if ($m === 'keep') { 182 | return '/**/'; 183 | } 184 | if ($m === '" "') { 185 | // component of http://tantek.com/CSS/Examples/midpass.html 186 | return '/*" "*/'; 187 | } 188 | if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) { 189 | // component of http://tantek.com/CSS/Examples/midpass.html 190 | return '/*";}}/* */'; 191 | } 192 | if ($this->_inHack) { 193 | // inversion: feeding only to one browser 194 | if (preg_match('@ 195 | ^/ # comment started like /*/ 196 | \\s* 197 | (\\S[\\s\\S]+?) # has at least some non-ws content 198 | \\s* 199 | /\\* # ends like /*/ or /**/ 200 | @x', $m, $n)) { 201 | // end hack mode after this comment, but preserve the hack and comment content 202 | $this->_inHack = false; 203 | return "/*/{$n[1]}/**/"; 204 | } 205 | } 206 | if (substr($m, -1) === '\\') { // comment ends like \*/ 207 | // begin hack mode and preserve hack 208 | $this->_inHack = true; 209 | return '/*\\*/'; 210 | } 211 | if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */ 212 | // begin hack mode and preserve hack 213 | $this->_inHack = true; 214 | return '/*/*/'; 215 | } 216 | if ($this->_inHack) { 217 | // a regular comment ends hack mode but should be preserved 218 | $this->_inHack = false; 219 | return '/**/'; 220 | } 221 | // Issue 107: if there's any surrounding whitespace, it may be important, so 222 | // replace the comment with a single space 223 | return $hasSurroundingWs // remove all other comments 224 | ? ' ' 225 | : ''; 226 | } 227 | 228 | /** 229 | * Process a font-family listing and return a replacement 230 | * 231 | * @param array $m regex matches 232 | * 233 | * @return string 234 | */ 235 | protected function _fontFamilyCB($m) 236 | { 237 | // Issue 210: must not eliminate WS between words in unquoted families 238 | $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 239 | $out = 'font-family:'; 240 | while (null !== ($piece = array_shift($pieces))) { 241 | if ($piece[0] !== '"' && $piece[0] !== "'") { 242 | $piece = preg_replace('/\\s+/', ' ', $piece); 243 | $piece = preg_replace('/\\s?,\\s?/', ',', $piece); 244 | } 245 | $out .= $piece; 246 | } 247 | return $out . $m[2]; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /min/lib/Minify/CSS/UriRewriter.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Minify_CSS_UriRewriter { 14 | 15 | /** 16 | * rewrite() and rewriteRelative() append debugging information here 17 | * 18 | * @var string 19 | */ 20 | public static $debugText = ''; 21 | 22 | /** 23 | * In CSS content, rewrite file relative URIs as root relative 24 | * 25 | * @param string $css 26 | * 27 | * @param string $currentDir The directory of the current CSS file. 28 | * 29 | * @param string $docRoot The document root of the web site in which 30 | * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']). 31 | * 32 | * @param array $symlinks (default = array()) If the CSS file is stored in 33 | * a symlink-ed directory, provide an array of link paths to 34 | * target paths, where the link paths are within the document root. Because 35 | * paths need to be normalized for this to work, use "//" to substitute 36 | * the doc root in the link paths (the array keys). E.g.: 37 | * 38 | * array('//symlink' => '/real/target/path') // unix 39 | * array('//static' => 'D:\\staticStorage') // Windows 40 | * 41 | * 42 | * @return string 43 | */ 44 | public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array()) 45 | { 46 | self::$_docRoot = self::_realpath( 47 | $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT'] 48 | ); 49 | self::$_currentDir = self::_realpath($currentDir); 50 | self::$_symlinks = array(); 51 | 52 | // normalize symlinks 53 | foreach ($symlinks as $link => $target) { 54 | $link = ($link === '//') 55 | ? self::$_docRoot 56 | : str_replace('//', self::$_docRoot . '/', $link); 57 | $link = strtr($link, '/', DIRECTORY_SEPARATOR); 58 | self::$_symlinks[$link] = self::_realpath($target); 59 | } 60 | 61 | self::$debugText .= "docRoot : " . self::$_docRoot . "\n" 62 | . "currentDir : " . self::$_currentDir . "\n"; 63 | if (self::$_symlinks) { 64 | self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n"; 65 | } 66 | self::$debugText .= "\n"; 67 | 68 | $css = self::_trimUrls($css); 69 | 70 | // rewrite 71 | $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/' 72 | ,array(self::$className, '_processUriCB'), $css); 73 | $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/' 74 | ,array(self::$className, '_processUriCB'), $css); 75 | 76 | return $css; 77 | } 78 | 79 | /** 80 | * In CSS content, prepend a path to relative URIs 81 | * 82 | * @param string $css 83 | * 84 | * @param string $path The path to prepend. 85 | * 86 | * @return string 87 | */ 88 | public static function prepend($css, $path) 89 | { 90 | self::$_prependPath = $path; 91 | 92 | $css = self::_trimUrls($css); 93 | 94 | // append 95 | $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/' 96 | ,array(self::$className, '_processUriCB'), $css); 97 | $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/' 98 | ,array(self::$className, '_processUriCB'), $css); 99 | 100 | self::$_prependPath = null; 101 | return $css; 102 | } 103 | 104 | /** 105 | * Get a root relative URI from a file relative URI 106 | * 107 | * 108 | * Minify_CSS_UriRewriter::rewriteRelative( 109 | * '../img/hello.gif' 110 | * , '/home/user/www/css' // path of CSS file 111 | * , '/home/user/www' // doc root 112 | * ); 113 | * // returns '/img/hello.gif' 114 | * 115 | * // example where static files are stored in a symlinked directory 116 | * Minify_CSS_UriRewriter::rewriteRelative( 117 | * 'hello.gif' 118 | * , '/var/staticFiles/theme' 119 | * , '/home/user/www' 120 | * , array('/home/user/www/static' => '/var/staticFiles') 121 | * ); 122 | * // returns '/static/theme/hello.gif' 123 | * 124 | * 125 | * @param string $uri file relative URI 126 | * 127 | * @param string $realCurrentDir realpath of the current file's directory. 128 | * 129 | * @param string $realDocRoot realpath of the site document root. 130 | * 131 | * @param array $symlinks (default = array()) If the file is stored in 132 | * a symlink-ed directory, provide an array of link paths to 133 | * real target paths, where the link paths "appear" to be within the document 134 | * root. E.g.: 135 | * 136 | * array('/home/foo/www/not/real/path' => '/real/target/path') // unix 137 | * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows 138 | * 139 | * 140 | * @return string 141 | */ 142 | public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array()) 143 | { 144 | // prepend path with current dir separator (OS-independent) 145 | $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR) 146 | . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR); 147 | 148 | self::$debugText .= "file-relative URI : {$uri}\n" 149 | . "path prepended : {$path}\n"; 150 | 151 | // "unresolve" a symlink back to doc root 152 | foreach ($symlinks as $link => $target) { 153 | if (0 === strpos($path, $target)) { 154 | // replace $target with $link 155 | $path = $link . substr($path, strlen($target)); 156 | 157 | self::$debugText .= "symlink unresolved : {$path}\n"; 158 | 159 | break; 160 | } 161 | } 162 | // strip doc root 163 | $path = substr($path, strlen($realDocRoot)); 164 | 165 | self::$debugText .= "docroot stripped : {$path}\n"; 166 | 167 | // fix to root-relative URI 168 | $uri = strtr($path, '/\\', '//'); 169 | $uri = self::removeDots($uri); 170 | 171 | self::$debugText .= "traversals removed : {$uri}\n\n"; 172 | 173 | return $uri; 174 | } 175 | 176 | /** 177 | * Remove instances of "./" and "../" where possible from a root-relative URI 178 | * 179 | * @param string $uri 180 | * 181 | * @return string 182 | */ 183 | public static function removeDots($uri) 184 | { 185 | $uri = str_replace('/./', '/', $uri); 186 | // inspired by patch from Oleg Cherniy 187 | do { 188 | $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed); 189 | } while ($changed); 190 | return $uri; 191 | } 192 | 193 | /** 194 | * Defines which class to call as part of callbacks, change this 195 | * if you extend Minify_CSS_UriRewriter 196 | * 197 | * @var string 198 | */ 199 | protected static $className = 'Minify_CSS_UriRewriter'; 200 | 201 | /** 202 | * Get realpath with any trailing slash removed. If realpath() fails, 203 | * just remove the trailing slash. 204 | * 205 | * @param string $path 206 | * 207 | * @return mixed path with no trailing slash 208 | */ 209 | protected static function _realpath($path) 210 | { 211 | $realPath = realpath($path); 212 | if ($realPath !== false) { 213 | $path = $realPath; 214 | } 215 | return rtrim($path, '/\\'); 216 | } 217 | 218 | /** 219 | * Directory of this stylesheet 220 | * 221 | * @var string 222 | */ 223 | private static $_currentDir = ''; 224 | 225 | /** 226 | * DOC_ROOT 227 | * 228 | * @var string 229 | */ 230 | private static $_docRoot = ''; 231 | 232 | /** 233 | * directory replacements to map symlink targets back to their 234 | * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath' 235 | * 236 | * @var array 237 | */ 238 | private static $_symlinks = array(); 239 | 240 | /** 241 | * Path to prepend 242 | * 243 | * @var string 244 | */ 245 | private static $_prependPath = null; 246 | 247 | /** 248 | * @param string $css 249 | * 250 | * @return string 251 | */ 252 | private static function _trimUrls($css) 253 | { 254 | return preg_replace('/ 255 | url\\( # url( 256 | \\s* 257 | ([^\\)]+?) # 1 = URI (assuming does not contain ")") 258 | \\s* 259 | \\) # ) 260 | /x', 'url($1)', $css); 261 | } 262 | 263 | /** 264 | * @param array $m 265 | * 266 | * @return string 267 | */ 268 | private static function _processUriCB($m) 269 | { 270 | // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/' 271 | $isImport = ($m[0][0] === '@'); 272 | // determine URI and the quote character (if any) 273 | if ($isImport) { 274 | $quoteChar = $m[1]; 275 | $uri = $m[2]; 276 | } else { 277 | // $m[1] is either quoted or not 278 | $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"') 279 | ? $m[1][0] 280 | : ''; 281 | $uri = ($quoteChar === '') 282 | ? $m[1] 283 | : substr($m[1], 1, strlen($m[1]) - 2); 284 | } 285 | // analyze URI 286 | if ('/' !== $uri[0] // root-relative 287 | && false === strpos($uri, '//') // protocol (non-data) 288 | && 0 !== strpos($uri, 'data:') // data protocol 289 | ) { 290 | // URI is file-relative: rewrite depending on options 291 | if (self::$_prependPath === null) { 292 | $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks); 293 | } else { 294 | $uri = self::$_prependPath . $uri; 295 | if ($uri[0] === '/') { 296 | $root = ''; 297 | $rootRelative = $uri; 298 | $uri = $root . self::removeDots($rootRelative); 299 | } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) { 300 | $root = $m[1]; 301 | $rootRelative = substr($uri, strlen($root)); 302 | $uri = $root . self::removeDots($rootRelative); 303 | } 304 | } 305 | } 306 | return $isImport 307 | ? "@import {$quoteChar}{$uri}{$quoteChar}" 308 | : "url({$quoteChar}{$uri}{$quoteChar})"; 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /min/lib/Minify/Cache/APC.php: -------------------------------------------------------------------------------- 1 | 11 | * Minify::setCache(new Minify_Cache_APC()); 12 | * 13 | * 14 | * @package Minify 15 | * @author Chris Edwards 16 | **/ 17 | class Minify_Cache_APC { 18 | 19 | /** 20 | * Create a Minify_Cache_APC object, to be passed to 21 | * Minify::setCache(). 22 | * 23 | * 24 | * @param int $expire seconds until expiration (default = 0 25 | * meaning the item will not get an expiration date) 26 | * 27 | * @return null 28 | */ 29 | public function __construct($expire = 0) 30 | { 31 | $this->_exp = $expire; 32 | } 33 | 34 | /** 35 | * Write data to cache. 36 | * 37 | * @param string $id cache id 38 | * 39 | * @param string $data 40 | * 41 | * @return bool success 42 | */ 43 | public function store($id, $data) 44 | { 45 | return apc_store($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp); 46 | } 47 | 48 | /** 49 | * Get the size of a cache entry 50 | * 51 | * @param string $id cache id 52 | * 53 | * @return int size in bytes 54 | */ 55 | public function getSize($id) 56 | { 57 | if (! $this->_fetch($id)) { 58 | return false; 59 | } 60 | return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) 61 | ? mb_strlen($this->_data, '8bit') 62 | : strlen($this->_data); 63 | } 64 | 65 | /** 66 | * Does a valid cache entry exist? 67 | * 68 | * @param string $id cache id 69 | * 70 | * @param int $srcMtime mtime of the original source file(s) 71 | * 72 | * @return bool exists 73 | */ 74 | public function isValid($id, $srcMtime) 75 | { 76 | return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); 77 | } 78 | 79 | /** 80 | * Send the cached content to output 81 | * 82 | * @param string $id cache id 83 | */ 84 | public function display($id) 85 | { 86 | echo $this->_fetch($id) 87 | ? $this->_data 88 | : ''; 89 | } 90 | 91 | /** 92 | * Fetch the cached content 93 | * 94 | * @param string $id cache id 95 | * 96 | * @return string 97 | */ 98 | public function fetch($id) 99 | { 100 | return $this->_fetch($id) 101 | ? $this->_data 102 | : ''; 103 | } 104 | 105 | private $_exp = null; 106 | 107 | // cache of most recently fetched id 108 | private $_lm = null; 109 | private $_data = null; 110 | private $_id = null; 111 | 112 | /** 113 | * Fetch data and timestamp from apc, store in instance 114 | * 115 | * @param string $id 116 | * 117 | * @return bool success 118 | */ 119 | private function _fetch($id) 120 | { 121 | if ($this->_id === $id) { 122 | return true; 123 | } 124 | $ret = apc_fetch($id); 125 | if (false === $ret) { 126 | $this->_id = null; 127 | return false; 128 | } 129 | list($this->_lm, $this->_data) = explode('|', $ret, 2); 130 | $this->_id = $id; 131 | return true; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /min/lib/Minify/Cache/File.php: -------------------------------------------------------------------------------- 1 | _locking = $fileLocking; 15 | $this->_path = $path; 16 | } 17 | 18 | /** 19 | * Write data to cache. 20 | * 21 | * @param string $id cache id (e.g. a filename) 22 | * 23 | * @param string $data 24 | * 25 | * @return bool success 26 | */ 27 | public function store($id, $data) 28 | { 29 | $flag = $this->_locking 30 | ? LOCK_EX 31 | : null; 32 | $file = $this->_path . '/' . $id; 33 | if (! @file_put_contents($file, $data, $flag)) { 34 | $this->_log("Minify_Cache_File: Write failed to '$file'"); 35 | } 36 | // write control 37 | if ($data !== $this->fetch($id)) { 38 | @unlink($file); 39 | $this->_log("Minify_Cache_File: Post-write read failed for '$file'"); 40 | return false; 41 | } 42 | return true; 43 | } 44 | 45 | /** 46 | * Get the size of a cache entry 47 | * 48 | * @param string $id cache id (e.g. a filename) 49 | * 50 | * @return int size in bytes 51 | */ 52 | public function getSize($id) 53 | { 54 | return filesize($this->_path . '/' . $id); 55 | } 56 | 57 | /** 58 | * Does a valid cache entry exist? 59 | * 60 | * @param string $id cache id (e.g. a filename) 61 | * 62 | * @param int $srcMtime mtime of the original source file(s) 63 | * 64 | * @return bool exists 65 | */ 66 | public function isValid($id, $srcMtime) 67 | { 68 | $file = $this->_path . '/' . $id; 69 | return (is_file($file) && (filemtime($file) >= $srcMtime)); 70 | } 71 | 72 | /** 73 | * Send the cached content to output 74 | * 75 | * @param string $id cache id (e.g. a filename) 76 | */ 77 | public function display($id) 78 | { 79 | if ($this->_locking) { 80 | $fp = fopen($this->_path . '/' . $id, 'rb'); 81 | flock($fp, LOCK_SH); 82 | fpassthru($fp); 83 | flock($fp, LOCK_UN); 84 | fclose($fp); 85 | } else { 86 | readfile($this->_path . '/' . $id); 87 | } 88 | } 89 | 90 | /** 91 | * Fetch the cached content 92 | * 93 | * @param string $id cache id (e.g. a filename) 94 | * 95 | * @return string 96 | */ 97 | public function fetch($id) 98 | { 99 | if ($this->_locking) { 100 | $fp = fopen($this->_path . '/' . $id, 'rb'); 101 | flock($fp, LOCK_SH); 102 | $ret = stream_get_contents($fp); 103 | flock($fp, LOCK_UN); 104 | fclose($fp); 105 | return $ret; 106 | } else { 107 | return file_get_contents($this->_path . '/' . $id); 108 | } 109 | } 110 | 111 | /** 112 | * Fetch the cache path used 113 | * 114 | * @return string 115 | */ 116 | public function getPath() 117 | { 118 | return $this->_path; 119 | } 120 | 121 | /** 122 | * Get a usable temp directory 123 | * 124 | * Adapted from Solar/Dir.php 125 | * @author Paul M. Jones 126 | * @license http://opensource.org/licenses/bsd-license.php BSD 127 | * @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php 128 | * 129 | * @return string 130 | */ 131 | public static function tmp() 132 | { 133 | static $tmp = null; 134 | if (! $tmp) { 135 | $tmp = function_exists('sys_get_temp_dir') 136 | ? sys_get_temp_dir() 137 | : self::_tmp(); 138 | $tmp = rtrim($tmp, DIRECTORY_SEPARATOR); 139 | } 140 | return $tmp; 141 | } 142 | 143 | /** 144 | * Returns the OS-specific directory for temporary files 145 | * 146 | * @author Paul M. Jones 147 | * @license http://opensource.org/licenses/bsd-license.php BSD 148 | * @link http://solarphp.com/trac/core/browser/trunk/Solar/Dir.php 149 | * 150 | * @return string 151 | */ 152 | protected static function _tmp() 153 | { 154 | // non-Windows system? 155 | if (strtolower(substr(PHP_OS, 0, 3)) != 'win') { 156 | $tmp = empty($_ENV['TMPDIR']) ? getenv('TMPDIR') : $_ENV['TMPDIR']; 157 | if ($tmp) { 158 | return $tmp; 159 | } else { 160 | return '/tmp'; 161 | } 162 | } 163 | // Windows 'TEMP' 164 | $tmp = empty($_ENV['TEMP']) ? getenv('TEMP') : $_ENV['TEMP']; 165 | if ($tmp) { 166 | return $tmp; 167 | } 168 | // Windows 'TMP' 169 | $tmp = empty($_ENV['TMP']) ? getenv('TMP') : $_ENV['TMP']; 170 | if ($tmp) { 171 | return $tmp; 172 | } 173 | // Windows 'windir' 174 | $tmp = empty($_ENV['windir']) ? getenv('windir') : $_ENV['windir']; 175 | if ($tmp) { 176 | return $tmp; 177 | } 178 | // final fallback for Windows 179 | return getenv('SystemRoot') . '\\temp'; 180 | } 181 | 182 | /** 183 | * Send message to the Minify logger 184 | * @param string $msg 185 | * @return null 186 | */ 187 | protected function _log($msg) 188 | { 189 | Minify_Logger::log($msg); 190 | } 191 | 192 | private $_path = null; 193 | private $_locking = null; 194 | } 195 | -------------------------------------------------------------------------------- /min/lib/Minify/Cache/Memcache.php: -------------------------------------------------------------------------------- 1 | 11 | * // fall back to disk caching if memcache can't connect 12 | * $memcache = new Memcache; 13 | * if ($memcache->connect('localhost', 11211)) { 14 | * Minify::setCache(new Minify_Cache_Memcache($memcache)); 15 | * } else { 16 | * Minify::setCache(); 17 | * } 18 | * 19 | **/ 20 | class Minify_Cache_Memcache { 21 | 22 | /** 23 | * Create a Minify_Cache_Memcache object, to be passed to 24 | * Minify::setCache(). 25 | * 26 | * @param Memcache $memcache already-connected instance 27 | * 28 | * @param int $expire seconds until expiration (default = 0 29 | * meaning the item will not get an expiration date) 30 | * 31 | * @return null 32 | */ 33 | public function __construct($memcache, $expire = 0) 34 | { 35 | $this->_mc = $memcache; 36 | $this->_exp = $expire; 37 | } 38 | 39 | /** 40 | * Write data to cache. 41 | * 42 | * @param string $id cache id 43 | * 44 | * @param string $data 45 | * 46 | * @return bool success 47 | */ 48 | public function store($id, $data) 49 | { 50 | return $this->_mc->set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", 0, $this->_exp); 51 | } 52 | 53 | 54 | /** 55 | * Get the size of a cache entry 56 | * 57 | * @param string $id cache id 58 | * 59 | * @return int size in bytes 60 | */ 61 | public function getSize($id) 62 | { 63 | if (! $this->_fetch($id)) { 64 | return false; 65 | } 66 | return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) 67 | ? mb_strlen($this->_data, '8bit') 68 | : strlen($this->_data); 69 | } 70 | 71 | /** 72 | * Does a valid cache entry exist? 73 | * 74 | * @param string $id cache id 75 | * 76 | * @param int $srcMtime mtime of the original source file(s) 77 | * 78 | * @return bool exists 79 | */ 80 | public function isValid($id, $srcMtime) 81 | { 82 | return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); 83 | } 84 | 85 | /** 86 | * Send the cached content to output 87 | * 88 | * @param string $id cache id 89 | */ 90 | public function display($id) 91 | { 92 | echo $this->_fetch($id) 93 | ? $this->_data 94 | : ''; 95 | } 96 | 97 | /** 98 | * Fetch the cached content 99 | * 100 | * @param string $id cache id 101 | * 102 | * @return string 103 | */ 104 | public function fetch($id) 105 | { 106 | return $this->_fetch($id) 107 | ? $this->_data 108 | : ''; 109 | } 110 | 111 | private $_mc = null; 112 | private $_exp = null; 113 | 114 | // cache of most recently fetched id 115 | private $_lm = null; 116 | private $_data = null; 117 | private $_id = null; 118 | 119 | /** 120 | * Fetch data and timestamp from memcache, store in instance 121 | * 122 | * @param string $id 123 | * 124 | * @return bool success 125 | */ 126 | private function _fetch($id) 127 | { 128 | if ($this->_id === $id) { 129 | return true; 130 | } 131 | $ret = $this->_mc->get($id); 132 | if (false === $ret) { 133 | $this->_id = null; 134 | return false; 135 | } 136 | list($this->_lm, $this->_data) = explode('|', $ret, 2); 137 | $this->_id = $id; 138 | return true; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /min/lib/Minify/Cache/XCache.php: -------------------------------------------------------------------------------- 1 | 14 | * Minify::setCache(new Minify_Cache_XCache()); 15 | * 16 | * 17 | * @package Minify 18 | * @author Elan Ruusamäe 19 | **/ 20 | class Minify_Cache_XCache { 21 | 22 | /** 23 | * Create a Minify_Cache_XCache object, to be passed to 24 | * Minify::setCache(). 25 | * 26 | * @param int $expire seconds until expiration (default = 0 27 | * meaning the item will not get an expiration date) 28 | */ 29 | public function __construct($expire = 0) 30 | { 31 | $this->_exp = $expire; 32 | } 33 | 34 | /** 35 | * Write data to cache. 36 | * 37 | * @param string $id cache id 38 | * @param string $data 39 | * @return bool success 40 | */ 41 | public function store($id, $data) 42 | { 43 | return xcache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp); 44 | } 45 | 46 | /** 47 | * Get the size of a cache entry 48 | * 49 | * @param string $id cache id 50 | * @return int size in bytes 51 | */ 52 | public function getSize($id) 53 | { 54 | if (! $this->_fetch($id)) { 55 | return false; 56 | } 57 | return (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) 58 | ? mb_strlen($this->_data, '8bit') 59 | : strlen($this->_data); 60 | } 61 | 62 | /** 63 | * Does a valid cache entry exist? 64 | * 65 | * @param string $id cache id 66 | * @param int $srcMtime mtime of the original source file(s) 67 | * @return bool exists 68 | */ 69 | public function isValid($id, $srcMtime) 70 | { 71 | return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); 72 | } 73 | 74 | /** 75 | * Send the cached content to output 76 | * 77 | * @param string $id cache id 78 | */ 79 | public function display($id) 80 | { 81 | echo $this->_fetch($id) 82 | ? $this->_data 83 | : ''; 84 | } 85 | 86 | /** 87 | * Fetch the cached content 88 | * 89 | * @param string $id cache id 90 | * @return string 91 | */ 92 | public function fetch($id) 93 | { 94 | return $this->_fetch($id) 95 | ? $this->_data 96 | : ''; 97 | } 98 | 99 | private $_exp = null; 100 | 101 | // cache of most recently fetched id 102 | private $_lm = null; 103 | private $_data = null; 104 | private $_id = null; 105 | 106 | /** 107 | * Fetch data and timestamp from xcache, store in instance 108 | * 109 | * @param string $id 110 | * @return bool success 111 | */ 112 | private function _fetch($id) 113 | { 114 | if ($this->_id === $id) { 115 | return true; 116 | } 117 | $ret = xcache_get($id); 118 | if (false === $ret) { 119 | $this->_id = null; 120 | return false; 121 | } 122 | list($this->_lm, $this->_data) = explode('|', $ret, 2); 123 | $this->_id = $id; 124 | return true; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /min/lib/Minify/Cache/ZendPlatform.php: -------------------------------------------------------------------------------- 1 | 14 | * Minify::setCache(new Minify_Cache_ZendPlatform()); 15 | * 16 | * 17 | * @package Minify 18 | * @author Patrick van Dissel 19 | */ 20 | class Minify_Cache_ZendPlatform { 21 | 22 | 23 | /** 24 | * Create a Minify_Cache_ZendPlatform object, to be passed to 25 | * Minify::setCache(). 26 | * 27 | * @param int $expire seconds until expiration (default = 0 28 | * meaning the item will not get an expiration date) 29 | * 30 | * @return null 31 | */ 32 | public function __construct($expire = 0) 33 | { 34 | $this->_exp = $expire; 35 | } 36 | 37 | 38 | /** 39 | * Write data to cache. 40 | * 41 | * @param string $id cache id 42 | * 43 | * @param string $data 44 | * 45 | * @return bool success 46 | */ 47 | public function store($id, $data) 48 | { 49 | return output_cache_put($id, "{$_SERVER['REQUEST_TIME']}|{$data}"); 50 | } 51 | 52 | 53 | /** 54 | * Get the size of a cache entry 55 | * 56 | * @param string $id cache id 57 | * 58 | * @return int size in bytes 59 | */ 60 | public function getSize($id) 61 | { 62 | return $this->_fetch($id) 63 | ? strlen($this->_data) 64 | : false; 65 | } 66 | 67 | 68 | /** 69 | * Does a valid cache entry exist? 70 | * 71 | * @param string $id cache id 72 | * 73 | * @param int $srcMtime mtime of the original source file(s) 74 | * 75 | * @return bool exists 76 | */ 77 | public function isValid($id, $srcMtime) 78 | { 79 | $ret = ($this->_fetch($id) && ($this->_lm >= $srcMtime)); 80 | return $ret; 81 | } 82 | 83 | 84 | /** 85 | * Send the cached content to output 86 | * 87 | * @param string $id cache id 88 | */ 89 | public function display($id) 90 | { 91 | echo $this->_fetch($id) 92 | ? $this->_data 93 | : ''; 94 | } 95 | 96 | 97 | /** 98 | * Fetch the cached content 99 | * 100 | * @param string $id cache id 101 | * 102 | * @return string 103 | */ 104 | public function fetch($id) 105 | { 106 | return $this->_fetch($id) 107 | ? $this->_data 108 | : ''; 109 | } 110 | 111 | 112 | private $_exp = null; 113 | 114 | 115 | // cache of most recently fetched id 116 | private $_lm = null; 117 | private $_data = null; 118 | private $_id = null; 119 | 120 | 121 | /** 122 | * Fetch data and timestamp from ZendPlatform, store in instance 123 | * 124 | * @param string $id 125 | * 126 | * @return bool success 127 | */ 128 | private function _fetch($id) 129 | { 130 | if ($this->_id === $id) { 131 | return true; 132 | } 133 | $ret = output_cache_get($id, $this->_exp); 134 | if (false === $ret) { 135 | $this->_id = null; 136 | return false; 137 | } 138 | list($this->_lm, $this->_data) = explode('|', $ret, 2); 139 | $this->_id = $id; 140 | return true; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /min/lib/Minify/ClosureCompiler.php: -------------------------------------------------------------------------------- 1 | 16 | * Minify_ClosureCompiler::$jarFile = '/path/to/closure-compiler-20120123.jar'; 17 | * Minify_ClosureCompiler::$tempDir = '/tmp'; 18 | * $code = Minify_ClosureCompiler::minify( 19 | * $code, 20 | * array('compilation_level' => 'SIMPLE_OPTIMIZATIONS') 21 | * ); 22 | * 23 | * --compilation_level WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS 24 | * 25 | * 26 | * 27 | * @todo unit tests, $options docs 28 | * @todo more options support (or should just passthru them all?) 29 | * 30 | * @package Minify 31 | * @author Stephen Clay 32 | * @author Elan Ruusamäe 33 | */ 34 | class Minify_ClosureCompiler { 35 | 36 | /** 37 | * Filepath of the Closure Compiler jar file. This must be set before 38 | * calling minifyJs(). 39 | * 40 | * @var string 41 | */ 42 | public static $jarFile = null; 43 | 44 | /** 45 | * Writable temp directory. This must be set before calling minifyJs(). 46 | * 47 | * @var string 48 | */ 49 | public static $tempDir = null; 50 | 51 | /** 52 | * Filepath of "java" executable (may be needed if not in shell's PATH) 53 | * 54 | * @var string 55 | */ 56 | public static $javaExecutable = 'java'; 57 | 58 | /** 59 | * Minify a Javascript string 60 | * 61 | * @param string $js 62 | * 63 | * @param array $options (verbose is ignored) 64 | * 65 | * @see https://code.google.com/p/closure-compiler/source/browse/trunk/README 66 | * 67 | * @return string 68 | */ 69 | public static function minify($js, $options = array()) 70 | { 71 | self::_prepare(); 72 | if (! ($tmpFile = tempnam(self::$tempDir, 'cc_'))) { 73 | throw new Exception('Minify_ClosureCompiler : could not create temp file.'); 74 | } 75 | file_put_contents($tmpFile, $js); 76 | exec(self::_getCmd($options, $tmpFile), $output, $result_code); 77 | unlink($tmpFile); 78 | if ($result_code != 0) { 79 | throw new Exception('Minify_ClosureCompiler : Closure Compiler execution failed.'); 80 | } 81 | return implode("\n", $output); 82 | } 83 | 84 | private static function _getCmd($userOptions, $tmpFile) 85 | { 86 | $o = array_merge( 87 | array( 88 | 'charset' => 'utf-8', 89 | 'compilation_level' => 'SIMPLE_OPTIMIZATIONS', 90 | ), 91 | $userOptions 92 | ); 93 | $cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile) 94 | . (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset']) 95 | ? " --charset {$o['charset']}" 96 | : ''); 97 | 98 | foreach (array('compilation_level') as $opt) { 99 | if ($o[$opt]) { 100 | $cmd .= " --{$opt} ". escapeshellarg($o[$opt]); 101 | } 102 | } 103 | return $cmd . ' ' . escapeshellarg($tmpFile); 104 | } 105 | 106 | private static function _prepare() 107 | { 108 | if (! is_file(self::$jarFile)) { 109 | throw new Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not a valid file.'); 110 | } 111 | if (! is_readable(self::$jarFile)) { 112 | throw new Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not readable.'); 113 | } 114 | if (! is_dir(self::$tempDir)) { 115 | throw new Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not a valid direcotry.'); 116 | } 117 | if (! is_writable(self::$tempDir)) { 118 | throw new Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not writable.'); 119 | } 120 | } 121 | } 122 | 123 | /* vim:ts=4:sw=4:et */ 124 | -------------------------------------------------------------------------------- /min/lib/Minify/CommentPreserver.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Minify_CommentPreserver { 14 | 15 | /** 16 | * String to be prepended to each preserved comment 17 | * 18 | * @var string 19 | */ 20 | public static $prepend = "\n"; 21 | 22 | /** 23 | * String to be appended to each preserved comment 24 | * 25 | * @var string 26 | */ 27 | public static $append = "\n"; 28 | 29 | /** 30 | * Process a string outside of C-style comments that begin with "/*!" 31 | * 32 | * On each non-empty string outside these comments, the given processor 33 | * function will be called. The comments will be surrounded by 34 | * Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append. 35 | * 36 | * @param string $content 37 | * @param callback $processor function 38 | * @param array $args array of extra arguments to pass to the processor 39 | * function (default = array()) 40 | * @return string 41 | */ 42 | public static function process($content, $processor, $args = array()) 43 | { 44 | $ret = ''; 45 | while (true) { 46 | list($beforeComment, $comment, $afterComment) = self::_nextComment($content); 47 | if ('' !== $beforeComment) { 48 | $callArgs = $args; 49 | array_unshift($callArgs, $beforeComment); 50 | $ret .= call_user_func_array($processor, $callArgs); 51 | } 52 | if (false === $comment) { 53 | break; 54 | } 55 | $ret .= $comment; 56 | $content = $afterComment; 57 | } 58 | return $ret; 59 | } 60 | 61 | /** 62 | * Extract comments that YUI Compressor preserves. 63 | * 64 | * @param string $in input 65 | * 66 | * @return array 3 elements are returned. If a YUI comment is found, the 67 | * 2nd element is the comment and the 1st and 3rd are the surrounding 68 | * strings. If no comment is found, the entire string is returned as the 69 | * 1st element and the other two are false. 70 | */ 71 | private static function _nextComment($in) 72 | { 73 | if ( 74 | false === ($start = strpos($in, '/*!')) 75 | || false === ($end = strpos($in, '*/', $start + 3)) 76 | ) { 77 | return array($in, false, false); 78 | } 79 | $ret = array( 80 | substr($in, 0, $start) 81 | ,self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append 82 | ); 83 | $endChars = (strlen($in) - $end - 2); 84 | $ret[] = (0 === $endChars) 85 | ? '' 86 | : substr($in, -$endChars); 87 | return $ret; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /min/lib/Minify/Controller/Base.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class Minify_Controller_Base { 18 | 19 | /** 20 | * Setup controller sources and set an needed options for Minify::source 21 | * 22 | * You must override this method in your subclass controller to set 23 | * $this->sources. If the request is NOT valid, make sure $this->sources 24 | * is left an empty array. Then strip any controller-specific options from 25 | * $options and return it. To serve files, $this->sources must be an array of 26 | * Minify_Source objects. 27 | * 28 | * @param array $options controller and Minify options 29 | * 30 | * @return array $options Minify::serve options 31 | */ 32 | abstract public function setupSources($options); 33 | 34 | /** 35 | * Get default Minify options for this controller. 36 | * 37 | * Override in subclass to change defaults 38 | * 39 | * @return array options for Minify 40 | */ 41 | public function getDefaultMinifyOptions() { 42 | return array( 43 | 'isPublic' => true 44 | ,'encodeOutput' => function_exists('gzdeflate') 45 | ,'encodeMethod' => null // determine later 46 | ,'encodeLevel' => 9 47 | ,'minifierOptions' => array() // no minifier options 48 | ,'contentTypeCharset' => 'utf-8' 49 | ,'maxAge' => 1800 // 30 minutes 50 | ,'rewriteCssUris' => true 51 | ,'bubbleCssImports' => false 52 | ,'quiet' => false // serve() will send headers and output 53 | ,'debug' => false 54 | 55 | // if you override these, the response codes MUST be directly after 56 | // the first space. 57 | ,'badRequestHeader' => 'HTTP/1.0 400 Bad Request' 58 | ,'errorHeader' => 'HTTP/1.0 500 Internal Server Error' 59 | 60 | // callback function to see/modify content of all sources 61 | ,'postprocessor' => null 62 | // file to require to load preprocessor 63 | ,'postprocessorRequire' => null 64 | ); 65 | } 66 | 67 | /** 68 | * Get default minifiers for this controller. 69 | * 70 | * Override in subclass to change defaults 71 | * 72 | * @return array minifier callbacks for common types 73 | */ 74 | public function getDefaultMinifers() { 75 | $ret[Minify::TYPE_JS] = array('JSMin', 'minify'); 76 | $ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify'); 77 | $ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify'); 78 | return $ret; 79 | } 80 | 81 | /** 82 | * Is a user-given file within an allowable directory, existing, 83 | * and having an extension js/css/html/txt ? 84 | * 85 | * This is a convenience function for controllers that have to accept 86 | * user-given paths 87 | * 88 | * @param string $file full file path (already processed by realpath()) 89 | * 90 | * @param array $safeDirs directories where files are safe to serve. Files can also 91 | * be in subdirectories of these directories. 92 | * 93 | * @return bool file is safe 94 | * 95 | * @deprecated use checkAllowDirs, checkNotHidden instead 96 | */ 97 | public static function _fileIsSafe($file, $safeDirs) 98 | { 99 | $pathOk = false; 100 | foreach ((array)$safeDirs as $safeDir) { 101 | if (strpos($file, $safeDir) === 0) { 102 | $pathOk = true; 103 | break; 104 | } 105 | } 106 | $base = basename($file); 107 | if (! $pathOk || ! is_file($file) || $base[0] === '.') { 108 | return false; 109 | } 110 | list($revExt) = explode('.', strrev($base)); 111 | return in_array(strrev($revExt), array('js', 'css', 'html', 'txt')); 112 | } 113 | 114 | /** 115 | * @param string $file 116 | * @param array $allowDirs 117 | * @param string $uri 118 | * @return bool 119 | * @throws Exception 120 | */ 121 | public static function checkAllowDirs($file, $allowDirs, $uri) 122 | { 123 | foreach ((array)$allowDirs as $allowDir) { 124 | if (strpos($file, $allowDir) === 0) { 125 | return true; 126 | } 127 | } 128 | throw new Exception("File '$file' is outside \$allowDirs. If the path is" 129 | . " resolved via an alias/symlink, look into the \$min_symlinks option." 130 | . " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';"); 131 | } 132 | 133 | /** 134 | * @param string $file 135 | * @throws Exception 136 | */ 137 | public static function checkNotHidden($file) 138 | { 139 | $b = basename($file); 140 | if (0 === strpos($b, '.')) { 141 | throw new Exception("Filename '$b' starts with period (may be hidden)"); 142 | } 143 | } 144 | 145 | /** 146 | * instances of Minify_Source, which provide content and any individual minification needs. 147 | * 148 | * @var array 149 | * 150 | * @see Minify_Source 151 | */ 152 | public $sources = array(); 153 | 154 | /** 155 | * Short name to place inside cache id 156 | * 157 | * The setupSources() method may choose to set this, making it easier to 158 | * recognize a particular set of sources/settings in the cache folder. It 159 | * will be filtered and truncated to make the final cache id <= 250 bytes. 160 | * 161 | * @var string 162 | */ 163 | public $selectionId = ''; 164 | 165 | /** 166 | * Mix in default controller options with user-given options 167 | * 168 | * @param array $options user options 169 | * 170 | * @return array mixed options 171 | */ 172 | public final function mixInDefaultOptions($options) 173 | { 174 | $ret = array_merge( 175 | $this->getDefaultMinifyOptions(), $options 176 | ); 177 | if (! isset($options['minifiers'])) { 178 | $options['minifiers'] = array(); 179 | } 180 | $ret['minifiers'] = array_merge( 181 | $this->getDefaultMinifers(), $options['minifiers'] 182 | ); 183 | return $ret; 184 | } 185 | 186 | /** 187 | * Analyze sources (if there are any) and set $options 'contentType' 188 | * and 'lastModifiedTime' if they already aren't. 189 | * 190 | * @param array $options options for Minify 191 | * 192 | * @return array options for Minify 193 | */ 194 | public final function analyzeSources($options = array()) 195 | { 196 | if ($this->sources) { 197 | if (! isset($options['contentType'])) { 198 | $options['contentType'] = Minify_Source::getContentType($this->sources); 199 | } 200 | // last modified is needed for caching, even if setExpires is set 201 | if (! isset($options['lastModifiedTime'])) { 202 | $max = 0; 203 | foreach ($this->sources as $source) { 204 | $max = max($source->lastModified, $max); 205 | } 206 | $options['lastModifiedTime'] = $max; 207 | } 208 | } 209 | return $options; 210 | } 211 | 212 | /** 213 | * Send message to the Minify logger 214 | * 215 | * @param string $msg 216 | * 217 | * @return null 218 | */ 219 | public function log($msg) { 220 | Minify_Logger::log($msg); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /min/lib/Minify/Controller/Files.php: -------------------------------------------------------------------------------- 1 | 12 | * Minify::serve('Files', array( 13 | * 'files' => array( 14 | * '//js/jquery.js' 15 | * ,'//js/plugins.js' 16 | * ,'/home/username/file.js' 17 | * ) 18 | * )); 19 | * 20 | * 21 | * As a shortcut, the controller will replace "//" at the beginning 22 | * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'. 23 | * 24 | * @package Minify 25 | * @author Stephen Clay 26 | */ 27 | class Minify_Controller_Files extends Minify_Controller_Base { 28 | 29 | /** 30 | * Set up file sources 31 | * 32 | * @param array $options controller and Minify options 33 | * @return array Minify options 34 | * 35 | * Controller options: 36 | * 37 | * 'files': (required) array of complete file paths, or a single path 38 | */ 39 | public function setupSources($options) { 40 | // strip controller options 41 | 42 | $files = $options['files']; 43 | // if $files is a single object, casting will break it 44 | if (is_object($files)) { 45 | $files = array($files); 46 | } elseif (! is_array($files)) { 47 | $files = (array)$files; 48 | } 49 | unset($options['files']); 50 | 51 | $sources = array(); 52 | foreach ($files as $file) { 53 | if ($file instanceof Minify_Source) { 54 | $sources[] = $file; 55 | continue; 56 | } 57 | if (0 === strpos($file, '//')) { 58 | $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); 59 | } 60 | $realPath = realpath($file); 61 | if (is_file($realPath)) { 62 | $sources[] = new Minify_Source(array( 63 | 'filepath' => $realPath 64 | )); 65 | } else { 66 | $this->log("The path \"{$file}\" could not be found (or was not a file)"); 67 | return $options; 68 | } 69 | } 70 | if ($sources) { 71 | $this->sources = $sources; 72 | } 73 | return $options; 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /min/lib/Minify/Controller/Groups.php: -------------------------------------------------------------------------------- 1 | 12 | * Minify::serve('Groups', array( 13 | * 'groups' => array( 14 | * 'css' => array('//css/type.css', '//css/layout.css') 15 | * ,'js' => array('//js/jquery.js', '//js/site.js') 16 | * ) 17 | * )); 18 | * 19 | * 20 | * If the above code were placed in /serve.php, it would enable the URLs 21 | * /serve.php/js and /serve.php/css 22 | * 23 | * As a shortcut, the controller will replace "//" at the beginning 24 | * of a filename with $_SERVER['DOCUMENT_ROOT'] . '/'. 25 | * 26 | * @package Minify 27 | * @author Stephen Clay 28 | */ 29 | class Minify_Controller_Groups extends Minify_Controller_Base { 30 | 31 | /** 32 | * Set up groups of files as sources 33 | * 34 | * @param array $options controller and Minify options 35 | * 36 | * 'groups': (required) array mapping PATH_INFO strings to arrays 37 | * of complete file paths. @see Minify_Controller_Groups 38 | * 39 | * @return array Minify options 40 | */ 41 | public function setupSources($options) { 42 | // strip controller options 43 | $groups = $options['groups']; 44 | unset($options['groups']); 45 | 46 | // mod_fcgid places PATH_INFO in ORIG_PATH_INFO 47 | $pi = isset($_SERVER['ORIG_PATH_INFO']) 48 | ? substr($_SERVER['ORIG_PATH_INFO'], 1) 49 | : (isset($_SERVER['PATH_INFO']) 50 | ? substr($_SERVER['PATH_INFO'], 1) 51 | : false 52 | ); 53 | if (false === $pi || ! isset($groups[$pi])) { 54 | // no PATH_INFO or not a valid group 55 | $this->log("Missing PATH_INFO or no group set for \"$pi\""); 56 | return $options; 57 | } 58 | $sources = array(); 59 | 60 | $files = $groups[$pi]; 61 | // if $files is a single object, casting will break it 62 | if (is_object($files)) { 63 | $files = array($files); 64 | } elseif (! is_array($files)) { 65 | $files = (array)$files; 66 | } 67 | foreach ($files as $file) { 68 | if ($file instanceof Minify_Source) { 69 | $sources[] = $file; 70 | continue; 71 | } 72 | if (0 === strpos($file, '//')) { 73 | $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); 74 | } 75 | $realPath = realpath($file); 76 | if (is_file($realPath)) { 77 | $sources[] = new Minify_Source(array( 78 | 'filepath' => $realPath 79 | )); 80 | } else { 81 | $this->log("The path \"{$file}\" could not be found (or was not a file)"); 82 | return $options; 83 | } 84 | } 85 | if ($sources) { 86 | $this->sources = $sources; 87 | } 88 | return $options; 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /min/lib/Minify/Controller/MinApp.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Minify_Controller_MinApp extends Minify_Controller_Base { 14 | 15 | /** 16 | * Set up groups of files as sources 17 | * 18 | * @param array $options controller and Minify options 19 | * 20 | * @return array Minify options 21 | */ 22 | public function setupSources($options) { 23 | // PHP insecure by default: realpath() and other FS functions can't handle null bytes. 24 | foreach (array('g', 'b', 'f') as $key) { 25 | if (isset($_GET[$key])) { 26 | $_GET[$key] = str_replace("\x00", '', (string)$_GET[$key]); 27 | } 28 | } 29 | 30 | // filter controller options 31 | $cOptions = array_merge( 32 | array( 33 | 'allowDirs' => '//' 34 | ,'groupsOnly' => false 35 | ,'groups' => array() 36 | ,'noMinPattern' => '@[-\\.]min\\.(?:js|css)$@i' // matched against basename 37 | ) 38 | ,(isset($options['minApp']) ? $options['minApp'] : array()) 39 | ); 40 | unset($options['minApp']); 41 | $sources = array(); 42 | $this->selectionId = ''; 43 | $firstMissingResource = null; 44 | if (isset($_GET['g'])) { 45 | // add group(s) 46 | $this->selectionId .= 'g=' . $_GET['g']; 47 | $keys = explode(',', $_GET['g']); 48 | if ($keys != array_unique($keys)) { 49 | $this->log("Duplicate group key found."); 50 | return $options; 51 | } 52 | $keys = explode(',', $_GET['g']); 53 | foreach ($keys as $key) { 54 | if (! isset($cOptions['groups'][$key])) { 55 | $this->log("A group configuration for \"{$key}\" was not found"); 56 | return $options; 57 | } 58 | $files = $cOptions['groups'][$key]; 59 | // if $files is a single object, casting will break it 60 | if (is_object($files)) { 61 | $files = array($files); 62 | } elseif (! is_array($files)) { 63 | $files = (array)$files; 64 | } 65 | foreach ($files as $file) { 66 | if ($file instanceof Minify_Source) { 67 | $sources[] = $file; 68 | continue; 69 | } 70 | if (0 === strpos($file, '//')) { 71 | $file = $_SERVER['DOCUMENT_ROOT'] . substr($file, 1); 72 | } 73 | $realpath = realpath($file); 74 | if ($realpath && is_file($realpath)) { 75 | $sources[] = $this->_getFileSource($realpath, $cOptions); 76 | } else { 77 | $this->log("The path \"{$file}\" (realpath \"{$realpath}\") could not be found (or was not a file)"); 78 | if (null === $firstMissingResource) { 79 | $firstMissingResource = basename($file); 80 | continue; 81 | } else { 82 | $secondMissingResource = basename($file); 83 | $this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource'"); 84 | return $options; 85 | } 86 | } 87 | } 88 | if ($sources) { 89 | try { 90 | $this->checkType($sources[0]); 91 | } catch (Exception $e) { 92 | $this->log($e->getMessage()); 93 | return $options; 94 | } 95 | } 96 | } 97 | } 98 | if (! $cOptions['groupsOnly'] && isset($_GET['f'])) { 99 | // try user files 100 | // The following restrictions are to limit the URLs that minify will 101 | // respond to. 102 | if (// verify at least one file, files are single comma separated, 103 | // and are all same extension 104 | ! preg_match('/^[^,]+\\.(css|js)(?:,[^,]+\\.\\1)*$/', $_GET['f'], $m) 105 | // no "//" 106 | || strpos($_GET['f'], '//') !== false 107 | // no "\" 108 | || strpos($_GET['f'], '\\') !== false 109 | ) { 110 | $this->log("GET param 'f' was invalid"); 111 | return $options; 112 | } 113 | $ext = ".{$m[1]}"; 114 | try { 115 | $this->checkType($m[1]); 116 | } catch (Exception $e) { 117 | $this->log($e->getMessage()); 118 | return $options; 119 | } 120 | $files = explode(',', $_GET['f']); 121 | if ($files != array_unique($files)) { 122 | $this->log("Duplicate files were specified"); 123 | return $options; 124 | } 125 | if (isset($_GET['b'])) { 126 | // check for validity 127 | if (preg_match('@^[^/]+(?:/[^/]+)*$@', $_GET['b']) 128 | && false === strpos($_GET['b'], '..') 129 | && $_GET['b'] !== '.') { 130 | // valid base 131 | $base = "/{$_GET['b']}/"; 132 | } else { 133 | $this->log("GET param 'b' was invalid"); 134 | return $options; 135 | } 136 | } else { 137 | $base = '/'; 138 | } 139 | $allowDirs = array(); 140 | foreach ((array)$cOptions['allowDirs'] as $allowDir) { 141 | $allowDirs[] = realpath(str_replace('//', $_SERVER['DOCUMENT_ROOT'] . '/', $allowDir)); 142 | } 143 | $basenames = array(); // just for cache id 144 | foreach ($files as $file) { 145 | $uri = $base . $file; 146 | $path = $_SERVER['DOCUMENT_ROOT'] . $uri; 147 | $realpath = realpath($path); 148 | if (false === $realpath || ! is_file($realpath)) { 149 | $this->log("The path \"{$path}\" (realpath \"{$realpath}\") could not be found (or was not a file)"); 150 | if (null === $firstMissingResource) { 151 | $firstMissingResource = $uri; 152 | continue; 153 | } else { 154 | $secondMissingResource = $uri; 155 | $this->log("More than one file was missing: '$firstMissingResource', '$secondMissingResource`'"); 156 | return $options; 157 | } 158 | } 159 | try { 160 | parent::checkNotHidden($realpath); 161 | parent::checkAllowDirs($realpath, $allowDirs, $uri); 162 | } catch (Exception $e) { 163 | $this->log($e->getMessage()); 164 | return $options; 165 | } 166 | $sources[] = $this->_getFileSource($realpath, $cOptions); 167 | $basenames[] = basename($realpath, $ext); 168 | } 169 | if ($this->selectionId) { 170 | $this->selectionId .= '_f='; 171 | } 172 | $this->selectionId .= implode(',', $basenames) . $ext; 173 | } 174 | if ($sources) { 175 | if (null !== $firstMissingResource) { 176 | array_unshift($sources, new Minify_Source(array( 177 | 'id' => 'missingFile' 178 | // should not cause cache invalidation 179 | ,'lastModified' => 0 180 | // due to caching, filename is unreliable. 181 | ,'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n" 182 | ,'minifier' => '' 183 | ))); 184 | } 185 | $this->sources = $sources; 186 | } else { 187 | $this->log("No sources to serve"); 188 | } 189 | return $options; 190 | } 191 | 192 | /** 193 | * @param string $file 194 | * 195 | * @param array $cOptions 196 | * 197 | * @return Minify_Source 198 | */ 199 | protected function _getFileSource($file, $cOptions) 200 | { 201 | $spec['filepath'] = $file; 202 | if ($cOptions['noMinPattern'] && preg_match($cOptions['noMinPattern'], basename($file))) { 203 | if (preg_match('~\.css$~i', $file)) { 204 | $spec['minifyOptions']['compress'] = false; 205 | } else { 206 | $spec['minifier'] = ''; 207 | } 208 | } 209 | return new Minify_Source($spec); 210 | } 211 | 212 | protected $_type = null; 213 | 214 | /** 215 | * Make sure that only source files of a single type are registered 216 | * 217 | * @param string $sourceOrExt 218 | * 219 | * @throws Exception 220 | */ 221 | public function checkType($sourceOrExt) 222 | { 223 | if ($sourceOrExt === 'js') { 224 | $type = Minify::TYPE_JS; 225 | } elseif ($sourceOrExt === 'css') { 226 | $type = Minify::TYPE_CSS; 227 | } elseif ($sourceOrExt->contentType !== null) { 228 | $type = $sourceOrExt->contentType; 229 | } else { 230 | return; 231 | } 232 | if ($this->_type === null) { 233 | $this->_type = $type; 234 | } elseif ($this->_type !== $type) { 235 | throw new Exception('Content-Type mismatch'); 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /min/lib/Minify/Controller/Page.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Minify_Controller_Page extends Minify_Controller_Base { 15 | 16 | /** 17 | * Set up source of HTML content 18 | * 19 | * @param array $options controller and Minify options 20 | * @return array Minify options 21 | * 22 | * Controller options: 23 | * 24 | * 'content': (required) HTML markup 25 | * 26 | * 'id': (required) id of page (string for use in server-side caching) 27 | * 28 | * 'lastModifiedTime': timestamp of when this content changed. This 29 | * is recommended to allow both server and client-side caching. 30 | * 31 | * 'minifyAll': should all CSS and Javascript blocks be individually 32 | * minified? (default false) 33 | * 34 | * @todo Add 'file' option to read HTML file. 35 | */ 36 | public function setupSources($options) { 37 | if (isset($options['file'])) { 38 | $sourceSpec = array( 39 | 'filepath' => $options['file'] 40 | ); 41 | $f = $options['file']; 42 | } else { 43 | // strip controller options 44 | $sourceSpec = array( 45 | 'content' => $options['content'] 46 | ,'id' => $options['id'] 47 | ); 48 | $f = $options['id']; 49 | unset($options['content'], $options['id']); 50 | } 51 | // something like "builder,index.php" or "directory,file.html" 52 | $this->selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,'); 53 | 54 | if (isset($options['minifyAll'])) { 55 | // this will be the 2nd argument passed to Minify_HTML::minify() 56 | $sourceSpec['minifyOptions'] = array( 57 | 'cssMinifier' => array('Minify_CSS', 'minify') 58 | ,'jsMinifier' => array('JSMin', 'minify') 59 | ); 60 | unset($options['minifyAll']); 61 | } 62 | $this->sources[] = new Minify_Source($sourceSpec); 63 | 64 | $options['contentType'] = Minify::TYPE_HTML; 65 | return $options; 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /min/lib/Minify/Controller/Version1.php: -------------------------------------------------------------------------------- 1 | 11 | * Minify::serve('Version1'); 12 | * 13 | * 14 | * @package Minify 15 | * @author Stephen Clay 16 | */ 17 | class Minify_Controller_Version1 extends Minify_Controller_Base { 18 | 19 | /** 20 | * Set up groups of files as sources 21 | * 22 | * @param array $options controller and Minify options 23 | * @return array Minify options 24 | * 25 | */ 26 | public function setupSources($options) { 27 | // PHP insecure by default: realpath() and other FS functions can't handle null bytes. 28 | if (isset($_GET['files'])) { 29 | $_GET['files'] = str_replace("\x00", '', (string)$_GET['files']); 30 | } 31 | 32 | self::_setupDefines(); 33 | if (MINIFY_USE_CACHE) { 34 | $cacheDir = defined('MINIFY_CACHE_DIR') 35 | ? MINIFY_CACHE_DIR 36 | : ''; 37 | Minify::setCache($cacheDir); 38 | } 39 | $options['badRequestHeader'] = 'HTTP/1.0 404 Not Found'; 40 | $options['contentTypeCharset'] = MINIFY_ENCODING; 41 | 42 | // The following restrictions are to limit the URLs that minify will 43 | // respond to. Ideally there should be only one way to reference a file. 44 | if (! isset($_GET['files']) 45 | // verify at least one file, files are single comma separated, 46 | // and are all same extension 47 | || ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m) 48 | // no "//" (makes URL rewriting easier) 49 | || strpos($_GET['files'], '//') !== false 50 | // no "\" 51 | || strpos($_GET['files'], '\\') !== false 52 | // no "./" 53 | || preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files']) 54 | ) { 55 | return $options; 56 | } 57 | 58 | $files = explode(',', $_GET['files']); 59 | if (count($files) > MINIFY_MAX_FILES) { 60 | return $options; 61 | } 62 | 63 | // strings for prepending to relative/absolute paths 64 | $prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME']) 65 | . DIRECTORY_SEPARATOR; 66 | $prependAbsPaths = $_SERVER['DOCUMENT_ROOT']; 67 | 68 | $goodFiles = array(); 69 | $hasBadSource = false; 70 | 71 | $allowDirs = isset($options['allowDirs']) 72 | ? $options['allowDirs'] 73 | : MINIFY_BASE_DIR; 74 | 75 | foreach ($files as $file) { 76 | // prepend appropriate string for abs/rel paths 77 | $file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file; 78 | // make sure a real file! 79 | $file = realpath($file); 80 | // don't allow unsafe or duplicate files 81 | if (parent::_fileIsSafe($file, $allowDirs) 82 | && !in_array($file, $goodFiles)) 83 | { 84 | $goodFiles[] = $file; 85 | $srcOptions = array( 86 | 'filepath' => $file 87 | ); 88 | $this->sources[] = new Minify_Source($srcOptions); 89 | } else { 90 | $hasBadSource = true; 91 | break; 92 | } 93 | } 94 | if ($hasBadSource) { 95 | $this->sources = array(); 96 | } 97 | if (! MINIFY_REWRITE_CSS_URLS) { 98 | $options['rewriteCssUris'] = false; 99 | } 100 | return $options; 101 | } 102 | 103 | private static function _setupDefines() 104 | { 105 | $defaults = array( 106 | 'MINIFY_BASE_DIR' => realpath($_SERVER['DOCUMENT_ROOT']) 107 | ,'MINIFY_ENCODING' => 'utf-8' 108 | ,'MINIFY_MAX_FILES' => 16 109 | ,'MINIFY_REWRITE_CSS_URLS' => true 110 | ,'MINIFY_USE_CACHE' => true 111 | ); 112 | foreach ($defaults as $const => $val) { 113 | if (! defined($const)) { 114 | define($const, $val); 115 | } 116 | } 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /min/lib/Minify/DebugDetector.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class Minify_DebugDetector { 10 | public static function shouldDebugRequest($cookie, $get, $requestUri) 11 | { 12 | if (isset($get['debug'])) { 13 | return true; 14 | } 15 | if (! empty($cookie['minifyDebug'])) { 16 | foreach (preg_split('/\\s+/', $cookie['minifyDebug']) as $debugUri) { 17 | $pattern = '@' . preg_quote($debugUri, '@') . '@i'; 18 | $pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern); 19 | if (preg_match($pattern, $requestUri)) { 20 | return true; 21 | } 22 | } 23 | } 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /min/lib/Minify/HTML.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Minify_HTML { 20 | /** 21 | * @var boolean 22 | */ 23 | protected $_jsCleanComments = true; 24 | 25 | /** 26 | * "Minify" an HTML page 27 | * 28 | * @param string $html 29 | * 30 | * @param array $options 31 | * 32 | * 'cssMinifier' : (optional) callback function to process content of STYLE 33 | * elements. 34 | * 35 | * 'jsMinifier' : (optional) callback function to process content of SCRIPT 36 | * elements. Note: the type attribute is ignored. 37 | * 38 | * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If 39 | * unset, minify will sniff for an XHTML doctype. 40 | * 41 | * @return string 42 | */ 43 | public static function minify($html, $options = array()) { 44 | $min = new self($html, $options); 45 | return $min->process(); 46 | } 47 | 48 | 49 | /** 50 | * Create a minifier object 51 | * 52 | * @param string $html 53 | * 54 | * @param array $options 55 | * 56 | * 'cssMinifier' : (optional) callback function to process content of STYLE 57 | * elements. 58 | * 59 | * 'jsMinifier' : (optional) callback function to process content of SCRIPT 60 | * elements. Note: the type attribute is ignored. 61 | * 62 | * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block 63 | * 64 | * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If 65 | * unset, minify will sniff for an XHTML doctype. 66 | * 67 | * @return null 68 | */ 69 | public function __construct($html, $options = array()) 70 | { 71 | $this->_html = str_replace("\r\n", "\n", trim($html)); 72 | if (isset($options['xhtml'])) { 73 | $this->_isXhtml = (bool)$options['xhtml']; 74 | } 75 | if (isset($options['cssMinifier'])) { 76 | $this->_cssMinifier = $options['cssMinifier']; 77 | } 78 | if (isset($options['jsMinifier'])) { 79 | $this->_jsMinifier = $options['jsMinifier']; 80 | } 81 | if (isset($options['jsCleanComments'])) { 82 | $this->_jsCleanComments = (bool)$options['jsCleanComments']; 83 | } 84 | } 85 | 86 | 87 | /** 88 | * Minify the markeup given in the constructor 89 | * 90 | * @return string 91 | */ 92 | public function process() 93 | { 94 | if ($this->_isXhtml === null) { 95 | $this->_isXhtml = (false !== strpos($this->_html, '_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']); 99 | $this->_placeholders = array(); 100 | 101 | // replace SCRIPTs (and minify) with placeholders 102 | $this->_html = preg_replace_callback( 103 | '/(\\s*)]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i' 104 | ,array($this, '_removeScriptCB') 105 | ,$this->_html); 106 | 107 | // replace STYLEs (and minify) with placeholders 108 | $this->_html = preg_replace_callback( 109 | '/\\s*]*>)([\\s\\S]*?)<\\/style>\\s*/i' 110 | ,array($this, '_removeStyleCB') 111 | ,$this->_html); 112 | 113 | // remove HTML comments (not containing IE conditional comments). 114 | $this->_html = preg_replace_callback( 115 | '//' 116 | ,array($this, '_commentCB') 117 | ,$this->_html); 118 | 119 | // replace PREs with placeholders 120 | $this->_html = preg_replace_callback('/\\s*]*?>[\\s\\S]*?<\\/pre>)\\s*/i' 121 | ,array($this, '_removePreCB') 122 | ,$this->_html); 123 | 124 | // replace TEXTAREAs with placeholders 125 | $this->_html = preg_replace_callback( 126 | '/\\s*]*?>[\\s\\S]*?<\\/textarea>)\\s*/i' 127 | ,array($this, '_removeTextareaCB') 128 | ,$this->_html); 129 | 130 | // trim each line. 131 | // @todo take into account attribute values that span multiple lines. 132 | $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html); 133 | 134 | // remove ws around block/undisplayed elements 135 | $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body' 136 | .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form' 137 | .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta' 138 | .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)' 139 | .'|ul)\\b[^>]*>)/i', '$1', $this->_html); 140 | 141 | // remove ws outside of all elements 142 | $this->_html = preg_replace( 143 | '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?$1$2$3<' 145 | ,$this->_html); 146 | 147 | // use newlines before 1st attribute in open tags (to limit line lengths) 148 | $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html); 149 | 150 | // fill placeholders 151 | $this->_html = str_replace( 152 | array_keys($this->_placeholders) 153 | ,array_values($this->_placeholders) 154 | ,$this->_html 155 | ); 156 | // issue 229: multi-pass to catch scripts that didn't get replaced in textareas 157 | $this->_html = str_replace( 158 | array_keys($this->_placeholders) 159 | ,array_values($this->_placeholders) 160 | ,$this->_html 161 | ); 162 | return $this->_html; 163 | } 164 | 165 | protected function _commentCB($m) 166 | { 167 | return (0 === strpos($m[1], '[') || false !== strpos($m[1], '_replacementHash . count($this->_placeholders) . '%'; 175 | $this->_placeholders[$placeholder] = $content; 176 | return $placeholder; 177 | } 178 | 179 | protected $_isXhtml = null; 180 | protected $_replacementHash = null; 181 | protected $_placeholders = array(); 182 | protected $_cssMinifier = null; 183 | protected $_jsMinifier = null; 184 | 185 | protected function _removePreCB($m) 186 | { 187 | return $this->_reservePlace("_reservePlace("\\s*$)/', '', $css); 201 | 202 | // remove CDATA section markers 203 | $css = $this->_removeCdata($css); 204 | 205 | // minify 206 | $minifier = $this->_cssMinifier 207 | ? $this->_cssMinifier 208 | : 'trim'; 209 | $css = call_user_func($minifier, $css); 210 | 211 | return $this->_reservePlace($this->_needsCdata($css) 212 | ? "{$openStyle}/**/" 213 | : "{$openStyle}{$css}" 214 | ); 215 | } 216 | 217 | protected function _removeScriptCB($m) 218 | { 219 | $openScript = "_jsCleanComments) { 228 | $js = preg_replace('/(?:^\\s*\\s*$)/', '', $js); 229 | } 230 | 231 | // remove CDATA section markers 232 | $js = $this->_removeCdata($js); 233 | 234 | // minify 235 | $minifier = $this->_jsMinifier 236 | ? $this->_jsMinifier 237 | : 'trim'; 238 | $js = call_user_func($minifier, $js); 239 | 240 | return $this->_reservePlace($this->_needsCdata($js) 241 | ? "{$ws1}{$openScript}/**/{$ws2}" 242 | : "{$ws1}{$openScript}{$js}{$ws2}" 243 | ); 244 | } 245 | 246 | protected function _removeCdata($str) 247 | { 248 | return (false !== strpos($str, ''), '', $str) 250 | : $str; 251 | } 252 | 253 | protected function _needsCdata($str) 254 | { 255 | return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str)); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /min/lib/Minify/HTML/Helper.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Minify_HTML_Helper { 14 | public $rewriteWorks = true; 15 | public $minAppUri = '/min'; 16 | public $groupsConfigFile = ''; 17 | 18 | /** 19 | * Get an HTML-escaped Minify URI for a group or set of files 20 | * 21 | * @param string|array $keyOrFiles a group key or array of filepaths/URIs 22 | * @param array $opts options: 23 | * 'farExpires' : (default true) append a modified timestamp for cache revving 24 | * 'debug' : (default false) append debug flag 25 | * 'charset' : (default 'UTF-8') for htmlspecialchars 26 | * 'minAppUri' : (default '/min') URI of min directory 27 | * 'rewriteWorks' : (default true) does mod_rewrite work in min app? 28 | * 'groupsConfigFile' : specify if different 29 | * @return string 30 | */ 31 | public static function getUri($keyOrFiles, $opts = array()) 32 | { 33 | $opts = array_merge(array( // default options 34 | 'farExpires' => true 35 | ,'debug' => false 36 | ,'charset' => 'UTF-8' 37 | ,'minAppUri' => '/min' 38 | ,'rewriteWorks' => true 39 | ,'groupsConfigFile' => '' 40 | ), $opts); 41 | $h = new self; 42 | $h->minAppUri = $opts['minAppUri']; 43 | $h->rewriteWorks = $opts['rewriteWorks']; 44 | $h->groupsConfigFile = $opts['groupsConfigFile']; 45 | if (is_array($keyOrFiles)) { 46 | $h->setFiles($keyOrFiles, $opts['farExpires']); 47 | } else { 48 | $h->setGroup($keyOrFiles, $opts['farExpires']); 49 | } 50 | $uri = $h->getRawUri($opts['farExpires'], $opts['debug']); 51 | return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']); 52 | } 53 | 54 | /** 55 | * Get non-HTML-escaped URI to minify the specified files 56 | * 57 | * @param bool $farExpires 58 | * @param bool $debug 59 | * @return string 60 | */ 61 | public function getRawUri($farExpires = true, $debug = false) 62 | { 63 | $path = rtrim($this->minAppUri, '/') . '/'; 64 | if (! $this->rewriteWorks) { 65 | $path .= '?'; 66 | } 67 | if (null === $this->_groupKey) { 68 | // @todo: implement shortest uri 69 | $path = self::_getShortestUri($this->_filePaths, $path); 70 | } else { 71 | $path .= "g=" . $this->_groupKey; 72 | } 73 | if ($debug) { 74 | $path .= "&debug"; 75 | } elseif ($farExpires && $this->_lastModified) { 76 | $path .= "&" . $this->_lastModified; 77 | } 78 | return $path; 79 | } 80 | 81 | /** 82 | * Set the files that will comprise the URI we're building 83 | * 84 | * @param array $files 85 | * @param bool $checkLastModified 86 | */ 87 | public function setFiles($files, $checkLastModified = true) 88 | { 89 | $this->_groupKey = null; 90 | if ($checkLastModified) { 91 | $this->_lastModified = self::getLastModified($files); 92 | } 93 | // normalize paths like in /min/f= 94 | foreach ($files as $k => $file) { 95 | if (0 === strpos($file, '//')) { 96 | $file = substr($file, 2); 97 | } elseif (0 === strpos($file, '/') 98 | || 1 === strpos($file, ':\\')) { 99 | $file = substr($file, strlen($_SERVER['DOCUMENT_ROOT']) + 1); 100 | } 101 | $file = strtr($file, '\\', '/'); 102 | $files[$k] = $file; 103 | } 104 | $this->_filePaths = $files; 105 | } 106 | 107 | /** 108 | * Set the group of files that will comprise the URI we're building 109 | * 110 | * @param string $key 111 | * @param bool $checkLastModified 112 | */ 113 | public function setGroup($key, $checkLastModified = true) 114 | { 115 | $this->_groupKey = $key; 116 | if ($checkLastModified) { 117 | if (! $this->groupsConfigFile) { 118 | $this->groupsConfigFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/groupsConfig.php'; 119 | } 120 | if (is_file($this->groupsConfigFile)) { 121 | $gc = (require $this->groupsConfigFile); 122 | $keys = explode(',', $key); 123 | foreach ($keys as $key) { 124 | if (isset($gc[$key])) { 125 | $this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified); 126 | } 127 | } 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Get the max(lastModified) of all files 134 | * 135 | * @param array|string $sources 136 | * @param int $lastModified 137 | * @return int 138 | */ 139 | public static function getLastModified($sources, $lastModified = 0) 140 | { 141 | $max = $lastModified; 142 | foreach ((array)$sources as $source) { 143 | if (is_object($source) && isset($source->lastModified)) { 144 | $max = max($max, $source->lastModified); 145 | } elseif (is_string($source)) { 146 | if (0 === strpos($source, '//')) { 147 | $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1); 148 | } 149 | if (is_file($source)) { 150 | $max = max($max, filemtime($source)); 151 | } 152 | } 153 | } 154 | return $max; 155 | } 156 | 157 | protected $_groupKey = null; // if present, URI will be like g=... 158 | protected $_filePaths = array(); 159 | protected $_lastModified = null; 160 | 161 | 162 | /** 163 | * In a given array of strings, find the character they all have at 164 | * a particular index 165 | * 166 | * @param array $arr array of strings 167 | * @param int $pos index to check 168 | * @return mixed a common char or '' if any do not match 169 | */ 170 | protected static function _getCommonCharAtPos($arr, $pos) { 171 | if (!isset($arr[0][$pos])) { 172 | return ''; 173 | } 174 | $c = $arr[0][$pos]; 175 | $l = count($arr); 176 | if ($l === 1) { 177 | return $c; 178 | } 179 | for ($i = 1; $i < $l; ++$i) { 180 | if ($arr[$i][$pos] !== $c) { 181 | return ''; 182 | } 183 | } 184 | return $c; 185 | } 186 | 187 | /** 188 | * Get the shortest URI to minify the set of source files 189 | * 190 | * @param array $paths root-relative URIs of files 191 | * @param string $minRoot root-relative URI of the "min" application 192 | * @return string 193 | */ 194 | protected static function _getShortestUri($paths, $minRoot = '/min/') { 195 | $pos = 0; 196 | $base = ''; 197 | while (true) { 198 | $c = self::_getCommonCharAtPos($paths, $pos); 199 | if ($c === '') { 200 | break; 201 | } else { 202 | $base .= $c; 203 | } 204 | ++$pos; 205 | } 206 | $base = preg_replace('@[^/]+$@', '', $base); 207 | $uri = $minRoot . 'f=' . implode(',', $paths); 208 | 209 | if (substr($base, -1) === '/') { 210 | // we have a base dir! 211 | $basedPaths = $paths; 212 | $l = count($paths); 213 | for ($i = 0; $i < $l; ++$i) { 214 | $basedPaths[$i] = substr($paths[$i], strlen($base)); 215 | } 216 | $base = substr($base, 0, strlen($base) - 1); 217 | $bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths); 218 | 219 | $uri = strlen($uri) < strlen($bUri) 220 | ? $uri 221 | : $bUri; 222 | } 223 | return $uri; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /min/lib/Minify/ImportProcessor.php: -------------------------------------------------------------------------------- 1 | 19 | * @author Simon Schick 20 | */ 21 | class Minify_ImportProcessor { 22 | 23 | public static $filesIncluded = array(); 24 | 25 | public static function process($file) 26 | { 27 | self::$filesIncluded = array(); 28 | self::$_isCss = (strtolower(substr($file, -4)) === '.css'); 29 | $obj = new Minify_ImportProcessor(dirname($file)); 30 | return $obj->_getContent($file); 31 | } 32 | 33 | // allows callback funcs to know the current directory 34 | private $_currentDir = null; 35 | 36 | // allows callback funcs to know the directory of the file that inherits this one 37 | private $_previewsDir = null; 38 | 39 | // allows _importCB to write the fetched content back to the obj 40 | private $_importedContent = ''; 41 | 42 | private static $_isCss = null; 43 | 44 | /** 45 | * @param String $currentDir 46 | * @param String $previewsDir Is only used internally 47 | */ 48 | private function __construct($currentDir, $previewsDir = "") 49 | { 50 | $this->_currentDir = $currentDir; 51 | $this->_previewsDir = $previewsDir; 52 | } 53 | 54 | private function _getContent($file, $is_imported = false) 55 | { 56 | $file = realpath($file); 57 | if (! $file 58 | || in_array($file, self::$filesIncluded) 59 | || false === ($content = @file_get_contents($file)) 60 | ) { 61 | // file missing, already included, or failed read 62 | return ''; 63 | } 64 | self::$filesIncluded[] = realpath($file); 65 | $this->_currentDir = dirname($file); 66 | 67 | // remove UTF-8 BOM if present 68 | if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) { 69 | $content = substr($content, 3); 70 | } 71 | // ensure uniform EOLs 72 | $content = str_replace("\r\n", "\n", $content); 73 | 74 | // process @imports 75 | $content = preg_replace_callback( 76 | '/ 77 | @import\\s+ 78 | (?:url\\(\\s*)? # maybe url( 79 | [\'"]? # maybe quote 80 | (.*?) # 1 = URI 81 | [\'"]? # maybe end quote 82 | (?:\\s*\\))? # maybe ) 83 | ([a-zA-Z,\\s]*)? # 2 = media list 84 | ; # end token 85 | /x' 86 | ,array($this, '_importCB') 87 | ,$content 88 | ); 89 | 90 | // You only need to rework the import-path if the script is imported 91 | if (self::$_isCss && $is_imported) { 92 | // rewrite remaining relative URIs 93 | $content = preg_replace_callback( 94 | '/url\\(\\s*([^\\)\\s]+)\\s*\\)/' 95 | ,array($this, '_urlCB') 96 | ,$content 97 | ); 98 | } 99 | 100 | return $this->_importedContent . $content; 101 | } 102 | 103 | private function _importCB($m) 104 | { 105 | $url = $m[1]; 106 | $mediaList = preg_replace('/\\s+/', '', $m[2]); 107 | 108 | if (strpos($url, '://') > 0) { 109 | // protocol, leave in place for CSS, comment for JS 110 | return self::$_isCss 111 | ? $m[0] 112 | : "/* Minify_ImportProcessor will not include remote content */"; 113 | } 114 | if ('/' === $url[0]) { 115 | // protocol-relative or root path 116 | $url = ltrim($url, '/'); 117 | $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR 118 | . strtr($url, '/', DIRECTORY_SEPARATOR); 119 | } else { 120 | // relative to current path 121 | $file = $this->_currentDir . DIRECTORY_SEPARATOR 122 | . strtr($url, '/', DIRECTORY_SEPARATOR); 123 | } 124 | $obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir); 125 | $content = $obj->_getContent($file, true); 126 | if ('' === $content) { 127 | // failed. leave in place for CSS, comment for JS 128 | return self::$_isCss 129 | ? $m[0] 130 | : "/* Minify_ImportProcessor could not fetch '{$file}' */"; 131 | } 132 | return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList)) 133 | ? $content 134 | : "@media {$mediaList} {\n{$content}\n}\n"; 135 | } 136 | 137 | private function _urlCB($m) 138 | { 139 | // $m[1] is either quoted or not 140 | $quote = ($m[1][0] === "'" || $m[1][0] === '"') 141 | ? $m[1][0] 142 | : ''; 143 | $url = ($quote === '') 144 | ? $m[1] 145 | : substr($m[1], 1, strlen($m[1]) - 2); 146 | if ('/' !== $url[0]) { 147 | if (strpos($url, '//') > 0) { 148 | // probably starts with protocol, do not alter 149 | } else { 150 | // prepend path with current dir separator (OS-independent) 151 | $path = $this->_currentDir 152 | . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR); 153 | // update the relative path by the directory of the file that imported this one 154 | $url = self::getPathDiff(realpath($this->_previewsDir), $path); 155 | } 156 | } 157 | return "url({$quote}{$url}{$quote})"; 158 | } 159 | 160 | /** 161 | * @param string $from 162 | * @param string $to 163 | * @param string $ps 164 | * @return string 165 | */ 166 | private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR) 167 | { 168 | $realFrom = $this->truepath($from); 169 | $realTo = $this->truepath($to); 170 | 171 | $arFrom = explode($ps, rtrim($realFrom, $ps)); 172 | $arTo = explode($ps, rtrim($realTo, $ps)); 173 | while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) 174 | { 175 | array_shift($arFrom); 176 | array_shift($arTo); 177 | } 178 | return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo); 179 | } 180 | 181 | /** 182 | * This function is to replace PHP's extremely buggy realpath(). 183 | * @param string $path The original path, can be relative etc. 184 | * @return string The resolved path, it might not exist. 185 | * @see http://stackoverflow.com/questions/4049856/replace-phps-realpath 186 | */ 187 | function truepath($path) 188 | { 189 | // whether $path is unix or not 190 | $unipath = strlen($path) == 0 || $path{0} != '/'; 191 | // attempts to detect if path is relative in which case, add cwd 192 | if (strpos($path, ':') === false && $unipath) 193 | $path = $this->_currentDir . DIRECTORY_SEPARATOR . $path; 194 | 195 | // resolve path parts (single dot, double dot and double delimiters) 196 | $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path); 197 | $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); 198 | $absolutes = array(); 199 | foreach ($parts as $part) { 200 | if ('.' == $part) 201 | continue; 202 | if ('..' == $part) { 203 | array_pop($absolutes); 204 | } else { 205 | $absolutes[] = $part; 206 | } 207 | } 208 | $path = implode(DIRECTORY_SEPARATOR, $absolutes); 209 | // resolve any symlinks 210 | if (file_exists($path) && linkinfo($path) > 0) 211 | $path = readlink($path); 212 | // put initial separator that could have been lost 213 | $path = !$unipath ? '/' . $path : $path; 214 | return $path; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /min/lib/Minify/JS/ClosureCompiler.php: -------------------------------------------------------------------------------- 1 | 13 | * 14 | * @todo can use a stream wrapper to unit test this? 15 | */ 16 | class Minify_JS_ClosureCompiler { 17 | const URL = 'http://closure-compiler.appspot.com/compile'; 18 | 19 | /** 20 | * Minify Javascript code via HTTP request to the Closure Compiler API 21 | * 22 | * @param string $js input code 23 | * @param array $options unused at this point 24 | * @return string 25 | */ 26 | public static function minify($js, array $options = array()) 27 | { 28 | $obj = new self($options); 29 | return $obj->min($js); 30 | } 31 | 32 | /** 33 | * 34 | * @param array $options 35 | * 36 | * fallbackFunc : default array($this, 'fallback'); 37 | */ 38 | public function __construct(array $options = array()) 39 | { 40 | $this->_fallbackFunc = isset($options['fallbackMinifier']) 41 | ? $options['fallbackMinifier'] 42 | : array($this, '_fallback'); 43 | } 44 | 45 | public function min($js) 46 | { 47 | $postBody = $this->_buildPostBody($js); 48 | $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) 49 | ? mb_strlen($postBody, '8bit') 50 | : strlen($postBody); 51 | if ($bytes > 200000) { 52 | throw new Minify_JS_ClosureCompiler_Exception( 53 | 'POST content larger than 200000 bytes' 54 | ); 55 | } 56 | $response = $this->_getResponse($postBody); 57 | if (preg_match('/^Error\(\d\d?\):/', $response)) { 58 | if (is_callable($this->_fallbackFunc)) { 59 | $response = "/* Received errors from Closure Compiler API:\n$response" 60 | . "\n(Using fallback minifier)\n*/\n"; 61 | $response .= call_user_func($this->_fallbackFunc, $js); 62 | } else { 63 | throw new Minify_JS_ClosureCompiler_Exception($response); 64 | } 65 | } 66 | if ($response === '') { 67 | $errors = $this->_getResponse($this->_buildPostBody($js, true)); 68 | throw new Minify_JS_ClosureCompiler_Exception($errors); 69 | } 70 | return $response; 71 | } 72 | 73 | protected $_fallbackFunc = null; 74 | 75 | protected function _getResponse($postBody) 76 | { 77 | $allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen')); 78 | if ($allowUrlFopen) { 79 | $contents = file_get_contents(self::URL, false, stream_context_create(array( 80 | 'http' => array( 81 | 'method' => 'POST', 82 | 'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n", 83 | 'content' => $postBody, 84 | 'max_redirects' => 0, 85 | 'timeout' => 15, 86 | ) 87 | ))); 88 | } elseif (defined('CURLOPT_POST')) { 89 | $ch = curl_init(self::URL); 90 | curl_setopt($ch, CURLOPT_POST, true); 91 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 92 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded')); 93 | curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody); 94 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); 95 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); 96 | $contents = curl_exec($ch); 97 | curl_close($ch); 98 | } else { 99 | throw new Minify_JS_ClosureCompiler_Exception( 100 | "Could not make HTTP request: allow_url_open is false and cURL not available" 101 | ); 102 | } 103 | if (false === $contents) { 104 | throw new Minify_JS_ClosureCompiler_Exception( 105 | "No HTTP response from server" 106 | ); 107 | } 108 | return trim($contents); 109 | } 110 | 111 | protected function _buildPostBody($js, $returnErrors = false) 112 | { 113 | return http_build_query(array( 114 | 'js_code' => $js, 115 | 'output_info' => ($returnErrors ? 'errors' : 'compiled_code'), 116 | 'output_format' => 'text', 117 | 'compilation_level' => 'SIMPLE_OPTIMIZATIONS' 118 | ), null, '&'); 119 | } 120 | 121 | /** 122 | * Default fallback function if CC API fails 123 | * @param string $js 124 | * @return string 125 | */ 126 | protected function _fallback($js) 127 | { 128 | return JSMin::minify($js); 129 | } 130 | } 131 | 132 | class Minify_JS_ClosureCompiler_Exception extends Exception {} 133 | -------------------------------------------------------------------------------- /min/lib/Minify/Lines.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Adam Pedersen (Issue 55 fix) 13 | */ 14 | class Minify_Lines { 15 | 16 | /** 17 | * Add line numbers in C-style comments 18 | * 19 | * This uses a very basic parser easily fooled by comment tokens inside 20 | * strings or regexes, but, otherwise, generally clean code will not be 21 | * mangled. URI rewriting can also be performed. 22 | * 23 | * @param string $content 24 | * 25 | * @param array $options available options: 26 | * 27 | * 'id': (optional) string to identify file. E.g. file name/path 28 | * 29 | * 'currentDir': (default null) if given, this is assumed to be the 30 | * directory of the current CSS file. Using this, minify will rewrite 31 | * all relative URIs in import/url declarations to correctly point to 32 | * the desired files, and prepend a comment with debugging information about 33 | * this process. 34 | * 35 | * @return string 36 | */ 37 | public static function minify($content, $options = array()) 38 | { 39 | $id = (isset($options['id']) && $options['id']) 40 | ? $options['id'] 41 | : ''; 42 | $content = str_replace("\r\n", "\n", $content); 43 | 44 | // Hackily rewrite strings with XPath expressions that are 45 | // likely to throw off our dumb parser (for Prototype 1.6.1). 46 | $content = str_replace('"/*"', '"/"+"*"', $content); 47 | $content = preg_replace('@([\'"])(\\.?//?)\\*@', '$1$2$1+$1*', $content); 48 | 49 | $lines = explode("\n", $content); 50 | $numLines = count($lines); 51 | // determine left padding 52 | $padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits 53 | $inComment = false; 54 | $i = 0; 55 | $newLines = array(); 56 | while (null !== ($line = array_shift($lines))) { 57 | if (('' !== $id) && (0 == $i % 50)) { 58 | if ($inComment) { 59 | array_push($newLines, '', "/* {$id} *|", ''); 60 | } else { 61 | array_push($newLines, '', "/* {$id} */", ''); 62 | } 63 | } 64 | ++$i; 65 | $newLines[] = self::_addNote($line, $i, $inComment, $padTo); 66 | $inComment = self::_eolInComment($line, $inComment); 67 | } 68 | $content = implode("\n", $newLines) . "\n"; 69 | 70 | // check for desired URI rewriting 71 | if (isset($options['currentDir'])) { 72 | Minify_CSS_UriRewriter::$debugText = ''; 73 | $content = Minify_CSS_UriRewriter::rewrite( 74 | $content 75 | ,$options['currentDir'] 76 | ,isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT'] 77 | ,isset($options['symlinks']) ? $options['symlinks'] : array() 78 | ); 79 | $content = "/* Minify_CSS_UriRewriter::\$debugText\n\n" 80 | . Minify_CSS_UriRewriter::$debugText . "*/\n" 81 | . $content; 82 | } 83 | 84 | return $content; 85 | } 86 | 87 | /** 88 | * Is the parser within a C-style comment at the end of this line? 89 | * 90 | * @param string $line current line of code 91 | * 92 | * @param bool $inComment was the parser in a comment at the 93 | * beginning of the line? 94 | * 95 | * @return bool 96 | */ 97 | private static function _eolInComment($line, $inComment) 98 | { 99 | // crude way to avoid things like // */ 100 | $line = preg_replace('~//.*?(\\*/|/\\*).*~', '', $line); 101 | 102 | while (strlen($line)) { 103 | $search = $inComment 104 | ? '*/' 105 | : '/*'; 106 | $pos = strpos($line, $search); 107 | if (false === $pos) { 108 | return $inComment; 109 | } else { 110 | if ($pos == 0 111 | || ($inComment 112 | ? substr($line, $pos, 3) 113 | : substr($line, $pos-1, 3)) != '*/*') 114 | { 115 | $inComment = ! $inComment; 116 | } 117 | $line = substr($line, $pos + 2); 118 | } 119 | } 120 | return $inComment; 121 | } 122 | 123 | /** 124 | * Prepend a comment (or note) to the given line 125 | * 126 | * @param string $line current line of code 127 | * 128 | * @param string $note content of note/comment 129 | * 130 | * @param bool $inComment was the parser in a comment at the 131 | * beginning of the line? 132 | * 133 | * @param int $padTo minimum width of comment 134 | * 135 | * @return string 136 | */ 137 | private static function _addNote($line, $note, $inComment, $padTo) 138 | { 139 | return $inComment 140 | ? '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line 141 | : '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /min/lib/Minify/Loader.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Minify_Loader { 14 | public function loadClass($class) 15 | { 16 | $file = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR; 17 | $file .= strtr($class, "\\_", DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . '.php'; 18 | if (is_readable($file)) { 19 | require $file; 20 | } 21 | } 22 | 23 | static public function register() 24 | { 25 | $inst = new self(); 26 | spl_autoload_register(array($inst, 'loadClass')); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /min/lib/Minify/Logger.php: -------------------------------------------------------------------------------- 1 | 12 | * 13 | * @todo lose this singleton! pass log object in Minify::serve and distribute to others 14 | */ 15 | class Minify_Logger { 16 | 17 | /** 18 | * Set logger object. 19 | * 20 | * The object should have a method "log" that accepts a value as 1st argument and 21 | * an optional string label as the 2nd. 22 | * 23 | * @param mixed $obj or a "falsey" value to disable 24 | * @return null 25 | */ 26 | public static function setLogger($obj = null) { 27 | self::$_logger = $obj 28 | ? $obj 29 | : null; 30 | } 31 | 32 | /** 33 | * Pass a message to the logger (if set) 34 | * 35 | * @param string $msg message to log 36 | * @return null 37 | */ 38 | public static function log($msg, $label = 'Minify') { 39 | if (! self::$_logger) return; 40 | self::$_logger->log($msg, $label); 41 | } 42 | 43 | /** 44 | * @var mixed logger object (like FirePHP) or null (i.e. no logger available) 45 | */ 46 | private static $_logger = null; 47 | } 48 | -------------------------------------------------------------------------------- /min/lib/Minify/Packer.php: -------------------------------------------------------------------------------- 1 | pack()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /min/lib/Minify/Source.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class Minify_Source { 17 | 18 | /** 19 | * @var int time of last modification 20 | */ 21 | public $lastModified = null; 22 | 23 | /** 24 | * @var callback minifier function specifically for this source. 25 | */ 26 | public $minifier = null; 27 | 28 | /** 29 | * @var array minification options specific to this source. 30 | */ 31 | public $minifyOptions = null; 32 | 33 | /** 34 | * @var string full path of file 35 | */ 36 | public $filepath = null; 37 | 38 | /** 39 | * @var string HTTP Content Type (Minify requires one of the constants Minify::TYPE_*) 40 | */ 41 | public $contentType = null; 42 | 43 | /** 44 | * Create a Minify_Source 45 | * 46 | * In the $spec array(), you can either provide a 'filepath' to an existing 47 | * file (existence will not be checked!) or give 'id' (unique string for 48 | * the content), 'content' (the string content) and 'lastModified' 49 | * (unixtime of last update). 50 | * 51 | * As a shortcut, the controller will replace "//" at the beginning 52 | * of a filepath with $_SERVER['DOCUMENT_ROOT'] . '/'. 53 | * 54 | * @param array $spec options 55 | */ 56 | public function __construct($spec) 57 | { 58 | if (isset($spec['filepath'])) { 59 | if (0 === strpos($spec['filepath'], '//')) { 60 | $spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1); 61 | } 62 | $segments = explode('.', $spec['filepath']); 63 | $ext = strtolower(array_pop($segments)); 64 | switch ($ext) { 65 | case 'js' : $this->contentType = 'application/x-javascript'; 66 | break; 67 | case 'css' : $this->contentType = 'text/css'; 68 | break; 69 | case 'htm' : // fallthrough 70 | case 'html' : $this->contentType = 'text/html'; 71 | break; 72 | } 73 | $this->filepath = $spec['filepath']; 74 | $this->_id = $spec['filepath']; 75 | $this->lastModified = filemtime($spec['filepath']) 76 | // offset for Windows uploaders with out of sync clocks 77 | + round(Minify::$uploaderHoursBehind * 3600); 78 | } elseif (isset($spec['id'])) { 79 | $this->_id = 'id::' . $spec['id']; 80 | if (isset($spec['content'])) { 81 | $this->_content = $spec['content']; 82 | } else { 83 | $this->_getContentFunc = $spec['getContentFunc']; 84 | } 85 | $this->lastModified = isset($spec['lastModified']) 86 | ? $spec['lastModified'] 87 | : time(); 88 | } 89 | if (isset($spec['contentType'])) { 90 | $this->contentType = $spec['contentType']; 91 | } 92 | if (isset($spec['minifier'])) { 93 | $this->minifier = $spec['minifier']; 94 | } 95 | if (isset($spec['minifyOptions'])) { 96 | $this->minifyOptions = $spec['minifyOptions']; 97 | } 98 | } 99 | 100 | /** 101 | * Get content 102 | * 103 | * @return string 104 | */ 105 | public function getContent() 106 | { 107 | $content = (null !== $this->filepath) 108 | ? file_get_contents($this->filepath) 109 | : ((null !== $this->_content) 110 | ? $this->_content 111 | : call_user_func($this->_getContentFunc, $this->_id) 112 | ); 113 | // remove UTF-8 BOM if present 114 | return (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) 115 | ? substr($content, 3) 116 | : $content; 117 | } 118 | 119 | /** 120 | * Get id 121 | * 122 | * @return string 123 | */ 124 | public function getId() 125 | { 126 | return $this->_id; 127 | } 128 | 129 | /** 130 | * Verifies a single minification call can handle all sources 131 | * 132 | * @param array $sources Minify_Source instances 133 | * 134 | * @return bool true iff there no sources with specific minifier preferences. 135 | */ 136 | public static function haveNoMinifyPrefs($sources) 137 | { 138 | foreach ($sources as $source) { 139 | if (null !== $source->minifier 140 | || null !== $source->minifyOptions) { 141 | return false; 142 | } 143 | } 144 | return true; 145 | } 146 | 147 | /** 148 | * Get unique string for a set of sources 149 | * 150 | * @param array $sources Minify_Source instances 151 | * 152 | * @return string 153 | */ 154 | public static function getDigest($sources) 155 | { 156 | foreach ($sources as $source) { 157 | $info[] = array( 158 | $source->_id, $source->minifier, $source->minifyOptions 159 | ); 160 | } 161 | return md5(serialize($info)); 162 | } 163 | 164 | /** 165 | * Get content type from a group of sources 166 | * 167 | * This is called if the user doesn't pass in a 'contentType' options 168 | * 169 | * @param array $sources Minify_Source instances 170 | * 171 | * @return string content type. e.g. 'text/css' 172 | */ 173 | public static function getContentType($sources) 174 | { 175 | foreach ($sources as $source) { 176 | if ($source->contentType !== null) { 177 | return $source->contentType; 178 | } 179 | } 180 | return 'text/plain'; 181 | } 182 | 183 | protected $_content = null; 184 | protected $_getContentFunc = null; 185 | protected $_id = null; 186 | } 187 | 188 | -------------------------------------------------------------------------------- /min/lib/Minify/YUI/CssCompressor.php: -------------------------------------------------------------------------------- 1 | +\\(\\)\\],])@", "$1", $css); 53 | $css = str_replace("___PSEUDOCLASSCOLON___", ":", $css); 54 | 55 | // Remove the spaces after the things that should not have spaces after them. 56 | $css = preg_replace("@([!{}:;>+\\(\\[,])\\s+@", "$1", $css); 57 | 58 | // Add the semicolon where it's missing. 59 | $css = preg_replace("@([^;\\}])}@", "$1;}", $css); 60 | 61 | // Replace 0(px,em,%) with 0. 62 | $css = preg_replace("@([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)@", "$1$2", $css); 63 | 64 | // Replace 0 0 0 0; with 0. 65 | $css = str_replace(":0 0 0 0;", ":0;", $css); 66 | $css = str_replace(":0 0 0;", ":0;", $css); 67 | $css = str_replace(":0 0;", ":0;", $css); 68 | 69 | // Replace background-position:0; with background-position:0 0; 70 | $css = str_replace("background-position:0;", "background-position:0 0;", $css); 71 | 72 | // Replace 0.6 to .6, but only when preceded by : or a white-space 73 | $css = preg_replace("@(:|\\s)0+\\.(\\d+)@", "$1.$2", $css); 74 | 75 | // Shorten colors from rgb(51,102,153) to #336699 76 | // This makes it more likely that it'll get further compressed in the next step. 77 | $css = preg_replace_callback("@rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)@", array($this, '_shortenRgbCB'), $css); 78 | 79 | // Shorten colors from #AABBCC to #ABC. Note that we want to make sure 80 | // the color is not preceded by either ", " or =. Indeed, the property 81 | // filter: chroma(color="#FFFFFF"); 82 | // would become 83 | // filter: chroma(color="#FFF"); 84 | // which makes the filter break in IE. 85 | $css = preg_replace_callback("@([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])@", array($this, '_shortenHexCB'), $css); 86 | 87 | // Remove empty rules. 88 | $css = preg_replace("@[^\\}]+\\{;\\}@", "", $css); 89 | 90 | $linebreakpos = isset($this->_options['linebreakpos']) 91 | ? $this->_options['linebreakpos'] 92 | : 0; 93 | 94 | if ($linebreakpos > 0) { 95 | // Some source control tools don't like it when files containing lines longer 96 | // than, say 8000 characters, are checked in. The linebreak option is used in 97 | // that case to split long lines after a specific column. 98 | $i = 0; 99 | $linestartpos = 0; 100 | $sb = $css; 101 | 102 | // make sure strlen returns byte count 103 | $mbIntEnc = null; 104 | if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { 105 | $mbIntEnc = mb_internal_encoding(); 106 | mb_internal_encoding('8bit'); 107 | } 108 | $sbLength = strlen($css); 109 | while ($i < $sbLength) { 110 | $c = $sb[$i++]; 111 | if ($c === '}' && $i - $linestartpos > $linebreakpos) { 112 | $sb = substr_replace($sb, "\n", $i, 0); 113 | $sbLength++; 114 | $linestartpos = $i; 115 | } 116 | } 117 | $css = $sb; 118 | 119 | // undo potential mb_encoding change 120 | if ($mbIntEnc !== null) { 121 | mb_internal_encoding($mbIntEnc); 122 | } 123 | } 124 | 125 | // Replace the pseudo class for the Box Model Hack 126 | $css = str_replace("___PSEUDOCLASSBMH___", "\"\\\\\"}\\\\\"\"", $css); 127 | 128 | // Replace multiple semi-colons in a row by a single one 129 | // See SF bug #1980989 130 | $css = preg_replace("@;;+@", ";", $css); 131 | 132 | // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/ 133 | $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css); 134 | 135 | // Trim the final string (for any leading or trailing white spaces) 136 | $css = trim($css); 137 | 138 | return $css; 139 | } 140 | 141 | protected function _removeSpacesCB($m) 142 | { 143 | return str_replace(':', '___PSEUDOCLASSCOLON___', $m[0]); 144 | } 145 | 146 | protected function _shortenRgbCB($m) 147 | { 148 | $rgbcolors = explode(',', $m[1]); 149 | $hexcolor = '#'; 150 | for ($i = 0; $i < count($rgbcolors); $i++) { 151 | $val = round($rgbcolors[$i]); 152 | if ($val < 16) { 153 | $hexcolor .= '0'; 154 | } 155 | $hexcolor .= dechex($val); 156 | } 157 | return $hexcolor; 158 | } 159 | 160 | protected function _shortenHexCB($m) 161 | { 162 | // Test for AABBCC pattern 163 | if ((strtolower($m[3])===strtolower($m[4])) && 164 | (strtolower($m[5])===strtolower($m[6])) && 165 | (strtolower($m[7])===strtolower($m[8]))) { 166 | return $m[1] . $m[2] . "#" . $m[3] . $m[5] . $m[7]; 167 | } else { 168 | return $m[0]; 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /min/lib/Minify/YUICompressor.php: -------------------------------------------------------------------------------- 1 | 16 | * Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.4.6.jar'; 17 | * Minify_YUICompressor::$tempDir = '/tmp'; 18 | * $code = Minify_YUICompressor::minifyJs( 19 | * $code 20 | * ,array('nomunge' => true, 'line-break' => 1000) 21 | * ); 22 | * 23 | * 24 | * Note: In case you run out stack (default is 512k), you may increase stack size in $options: 25 | * array('stack-size' => '2048k') 26 | * 27 | * @todo unit tests, $options docs 28 | * 29 | * @package Minify 30 | * @author Stephen Clay 31 | */ 32 | class Minify_YUICompressor { 33 | 34 | /** 35 | * Filepath of the YUI Compressor jar file. This must be set before 36 | * calling minifyJs() or minifyCss(). 37 | * 38 | * @var string 39 | */ 40 | public static $jarFile = null; 41 | 42 | /** 43 | * Writable temp directory. This must be set before calling minifyJs() 44 | * or minifyCss(). 45 | * 46 | * @var string 47 | */ 48 | public static $tempDir = null; 49 | 50 | /** 51 | * Filepath of "java" executable (may be needed if not in shell's PATH) 52 | * 53 | * @var string 54 | */ 55 | public static $javaExecutable = 'java'; 56 | 57 | /** 58 | * Minify a Javascript string 59 | * 60 | * @param string $js 61 | * 62 | * @param array $options (verbose is ignored) 63 | * 64 | * @see http://www.julienlecomte.net/yuicompressor/README 65 | * 66 | * @return string 67 | */ 68 | public static function minifyJs($js, $options = array()) 69 | { 70 | return self::_minify('js', $js, $options); 71 | } 72 | 73 | /** 74 | * Minify a CSS string 75 | * 76 | * @param string $css 77 | * 78 | * @param array $options (verbose is ignored) 79 | * 80 | * @see http://www.julienlecomte.net/yuicompressor/README 81 | * 82 | * @return string 83 | */ 84 | public static function minifyCss($css, $options = array()) 85 | { 86 | return self::_minify('css', $css, $options); 87 | } 88 | 89 | private static function _minify($type, $content, $options) 90 | { 91 | self::_prepare(); 92 | if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) { 93 | throw new Exception('Minify_YUICompressor : could not create temp file.'); 94 | } 95 | file_put_contents($tmpFile, $content); 96 | exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code); 97 | unlink($tmpFile); 98 | if ($result_code != 0) { 99 | throw new Exception('Minify_YUICompressor : YUI compressor execution failed.'); 100 | } 101 | return implode("\n", $output); 102 | } 103 | 104 | private static function _getCmd($userOptions, $type, $tmpFile) 105 | { 106 | $o = array_merge( 107 | array( 108 | 'charset' => '' 109 | ,'line-break' => 5000 110 | ,'type' => $type 111 | ,'nomunge' => false 112 | ,'preserve-semi' => false 113 | ,'disable-optimizations' => false 114 | ,'stack-size' => '' 115 | ) 116 | ,$userOptions 117 | ); 118 | $cmd = self::$javaExecutable 119 | . (!empty($o['stack-size']) 120 | ? ' -Xss' . $o['stack-size'] 121 | : '') 122 | . ' -jar ' . escapeshellarg(self::$jarFile) 123 | . " --type {$type}" 124 | . (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset']) 125 | ? " --charset {$o['charset']}" 126 | : '') 127 | . (is_numeric($o['line-break']) && $o['line-break'] >= 0 128 | ? ' --line-break ' . (int)$o['line-break'] 129 | : ''); 130 | if ($type === 'js') { 131 | foreach (array('nomunge', 'preserve-semi', 'disable-optimizations') as $opt) { 132 | $cmd .= $o[$opt] 133 | ? " --{$opt}" 134 | : ''; 135 | } 136 | } 137 | return $cmd . ' ' . escapeshellarg($tmpFile); 138 | } 139 | 140 | private static function _prepare() 141 | { 142 | if (! is_file(self::$jarFile)) { 143 | throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.'); 144 | } 145 | if (! is_readable(self::$jarFile)) { 146 | throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not readable.'); 147 | } 148 | if (! is_dir(self::$tempDir)) { 149 | throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.'); 150 | } 151 | if (! is_writable(self::$tempDir)) { 152 | throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.'); 153 | } 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /min/lib/MrClay/Cli/Arg.php: -------------------------------------------------------------------------------- 1 | values['f.raw'] 23 | * 24 | * Use assertReadable()/assertWritable() to cause the validator to test the file/dir for 25 | * read/write permissions respectively. 26 | * 27 | * @method \MrClay\Cli\Arg mayHaveValue() Assert that the argument, if present, may receive a string value 28 | * @method \MrClay\Cli\Arg mustHaveValue() Assert that the argument, if present, must receive a string value 29 | * @method \MrClay\Cli\Arg assertFile() Assert that the argument's value must specify a file 30 | * @method \MrClay\Cli\Arg assertDir() Assert that the argument's value must specify a directory 31 | * @method \MrClay\Cli\Arg assertReadable() Assert that the specified file/dir must be readable 32 | * @method \MrClay\Cli\Arg assertWritable() Assert that the specified file/dir must be writable 33 | * 34 | * @property-read bool mayHaveValue 35 | * @property-read bool mustHaveValue 36 | * @property-read bool assertFile 37 | * @property-read bool assertDir 38 | * @property-read bool assertReadable 39 | * @property-read bool assertWritable 40 | * @property-read bool useAsInfile 41 | * @property-read bool useAsOutfile 42 | * 43 | * @author Steve Clay 44 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 45 | */ 46 | class Arg { 47 | /** 48 | * @return array 49 | */ 50 | public function getDefaultSpec() 51 | { 52 | return array( 53 | 'mayHaveValue' => false, 54 | 'mustHaveValue' => false, 55 | 'assertFile' => false, 56 | 'assertDir' => false, 57 | 'assertReadable' => false, 58 | 'assertWritable' => false, 59 | 'useAsInfile' => false, 60 | 'useAsOutfile' => false, 61 | ); 62 | } 63 | 64 | /** 65 | * @var array 66 | */ 67 | protected $spec = array(); 68 | 69 | /** 70 | * @var bool 71 | */ 72 | protected $required = false; 73 | 74 | /** 75 | * @var string 76 | */ 77 | protected $description = ''; 78 | 79 | /** 80 | * @param bool $isRequired 81 | */ 82 | public function __construct($isRequired = false) 83 | { 84 | $this->spec = $this->getDefaultSpec(); 85 | $this->required = (bool) $isRequired; 86 | if ($isRequired) { 87 | $this->spec['mustHaveValue'] = true; 88 | } 89 | } 90 | 91 | /** 92 | * Assert that the argument's value points to a writable file. When 93 | * Cli::openOutput() is called, a write pointer to this file will 94 | * be provided. 95 | * @return Arg 96 | */ 97 | public function useAsOutfile() 98 | { 99 | $this->spec['useAsOutfile'] = true; 100 | return $this->assertFile()->assertWritable(); 101 | } 102 | 103 | /** 104 | * Assert that the argument's value points to a readable file. When 105 | * Cli::openInput() is called, a read pointer to this file will 106 | * be provided. 107 | * @return Arg 108 | */ 109 | public function useAsInfile() 110 | { 111 | $this->spec['useAsInfile'] = true; 112 | return $this->assertFile()->assertReadable(); 113 | } 114 | 115 | /** 116 | * @return array 117 | */ 118 | public function getSpec() 119 | { 120 | return $this->spec; 121 | } 122 | 123 | /** 124 | * @param string $desc 125 | * @return Arg 126 | */ 127 | public function setDescription($desc) 128 | { 129 | $this->description = $desc; 130 | return $this; 131 | } 132 | 133 | /** 134 | * @return string 135 | */ 136 | public function getDescription() 137 | { 138 | return $this->description; 139 | } 140 | 141 | /** 142 | * @return bool 143 | */ 144 | public function isRequired() 145 | { 146 | return $this->required; 147 | } 148 | 149 | /** 150 | * Note: magic methods declared in class PHPDOC 151 | * 152 | * @param string $name 153 | * @param array $args 154 | * @return Arg 155 | * @throws BadMethodCallException 156 | */ 157 | public function __call($name, array $args = array()) 158 | { 159 | if (array_key_exists($name, $this->spec)) { 160 | $this->spec[$name] = true; 161 | if ($name === 'assertFile' || $name === 'assertDir') { 162 | $this->spec['mustHaveValue'] = true; 163 | } 164 | } else { 165 | throw new BadMethodCallException('Method does not exist'); 166 | } 167 | return $this; 168 | } 169 | 170 | /** 171 | * Note: magic properties declared in class PHPDOC 172 | * 173 | * @param string $name 174 | * @return bool|null 175 | */ 176 | public function __get($name) 177 | { 178 | if (array_key_exists($name, $this->spec)) { 179 | return $this->spec[$name]; 180 | } 181 | return null; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /min/quick-test.css: -------------------------------------------------------------------------------- 1 | /*! This file exists only for testing a Minify installation. It's content is not used. 2 | * 3 | * http://example.org/min/f=min/quick-test.css 4 | */ 5 | 6 | @import url( /more.css ); 7 | 8 | body, td, th { 9 | font-family: Verdana , "Bitstream Vera Sans" , Arial Narrow, sans-serif ; 10 | 11 | font-size : 12px; 12 | } 13 | 14 | .nav { 15 | margin-left: 20%; 16 | } 17 | #main-nav { 18 | background-color: red; 19 | border: 1px solid #00ff77; 20 | } 21 | 22 | div#content 23 | h1 + p { 24 | padding-top: 0; 25 | margin-top: 0; 26 | } 27 | 28 | @media all and (min-width: 640px) { 29 | #media-queries-1 { background-color: #0f0; } 30 | } 31 | -------------------------------------------------------------------------------- /min/quick-test.js: -------------------------------------------------------------------------------- 1 | /*! This file exists only for testing a Minify installation. It's content is not used. 2 | * 3 | * http://example.org/min/f=min/quick-test.js 4 | */ 5 | 6 | /* Finds the lowest common multiple of two numbers */ 7 | function LCMCalculator(x, y) { // constructor function 8 | var checkInt = function (x) { // inner function 9 | if (x % 1 !== 0) { 10 | throw new TypeError(x + " is not an integer"); // throw an exception 11 | } 12 | return x; 13 | }; 14 | this.a = checkInt(x); 15 | // ^ semicolons are optional 16 | this.b = checkInt(y); 17 | } 18 | // The prototype of object instances created by a constructor is 19 | // that constructor's "prototype" property. 20 | LCMCalculator.prototype = { // object literal 21 | constructor: LCMCalculator, // when reassigning a prototype, set the constructor property appropriately 22 | gcd: function () { // method that calculates the greatest common divisor 23 | // Euclidean algorithm: 24 | var a = Math.abs(this.a), b = Math.abs(this.b), t; 25 | if (a < b) { 26 | // swap variables 27 | t = b; 28 | b = a; 29 | a = t; 30 | } 31 | while (b !== 0) { 32 | t = b; 33 | b = a % b; 34 | a = t; 35 | } 36 | // Only need to calculate GCD once, so "redefine" this method. 37 | // (Actually not redefinition - it's defined on the instance itself, 38 | // so that this.gcd refers to this "redefinition" instead of LCMCalculator.prototype.gcd.) 39 | // Also, 'gcd' === "gcd", this['gcd'] === this.gcd 40 | this['gcd'] = function () { 41 | return a; 42 | }; 43 | return a; 44 | }, 45 | // Object property names can be specified by strings delimited by double (") or single (') quotes. 46 | "lcm" : function () { 47 | // Variable names don't collide with object properties, e.g. |lcm| is not |this.lcm|. 48 | // not using |this.a * this.b| to avoid FP precision issues 49 | var lcm = this.a / this.gcd() * this.b; 50 | // Only need to calculate lcm once, so "redefine" this method. 51 | this.lcm = function () { 52 | return lcm; 53 | }; 54 | return lcm; 55 | }, 56 | toString: function () { 57 | return "LCMCalculator: a = " + this.a + ", b = " + this.b; 58 | } 59 | }; 60 | 61 | //define generic output function; this implementation only works for web browsers 62 | function output(x) { 63 | document.write(x + "
    "); 64 | } 65 | 66 | // Note: Array's map() and forEach() are defined in JavaScript 1.6. 67 | // They are used here to demonstrate JavaScript's inherent functional nature. 68 | [[25, 55], [21, 56], [22, 58], [28, 56]].map(function (pair) { // array literal + mapping function 69 | return new LCMCalculator(pair[0], pair[1]); 70 | }).sort(function (a, b) { // sort with this comparative function 71 | return a.lcm() - b.lcm(); 72 | }).forEach(function (obj) { 73 | output(obj + ", gcd = " + obj.gcd() + ", lcm = " + obj.lcm()); 74 | }); -------------------------------------------------------------------------------- /min/utils.php: -------------------------------------------------------------------------------- 1 | 23 | * 24 | * 25 | * 29 | * 30 | * 31 | * @param mixed $keyOrFiles a group key or array of file paths/URIs 32 | * @param array $opts options: 33 | * 'farExpires' : (default true) append a modified timestamp for cache revving 34 | * 'debug' : (default false) append debug flag 35 | * 'charset' : (default 'UTF-8') for htmlspecialchars 36 | * 'minAppUri' : (default '/min') URI of min directory 37 | * 'rewriteWorks' : (default true) does mod_rewrite work in min app? 38 | * 'groupsConfigFile' : specify if different 39 | * @return string 40 | */ 41 | function Minify_getUri($keyOrFiles, $opts = array()) 42 | { 43 | return Minify_HTML_Helper::getUri($keyOrFiles, $opts); 44 | } 45 | 46 | 47 | /** 48 | * Get the last modification time of several source js/css files. If you're 49 | * caching the output of Minify_getUri(), you might want to know if one of the 50 | * dependent source files has changed so you can update the HTML. 51 | * 52 | * Since this makes a bunch of stat() calls, you might not want to check this 53 | * on every request. 54 | * 55 | * @param array $keysAndFiles group keys and/or file paths/URIs. 56 | * @return int latest modification time of all given keys/files 57 | */ 58 | function Minify_mtime($keysAndFiles, $groupsConfigFile = null) 59 | { 60 | $gc = null; 61 | if (! $groupsConfigFile) { 62 | $groupsConfigFile = dirname(__FILE__) . '/groupsConfig.php'; 63 | } 64 | $sources = array(); 65 | foreach ($keysAndFiles as $keyOrFile) { 66 | if (is_object($keyOrFile) 67 | || 0 === strpos($keyOrFile, '/') 68 | || 1 === strpos($keyOrFile, ':\\')) { 69 | // a file/source obj 70 | $sources[] = $keyOrFile; 71 | } else { 72 | if (! $gc) { 73 | $gc = (require $groupsConfigFile); 74 | } 75 | foreach ($gc[$keyOrFile] as $source) { 76 | $sources[] = $source; 77 | } 78 | } 79 | } 80 | return Minify_HTML_Helper::getLastModified($sources); 81 | } 82 | --------------------------------------------------------------------------------