bixbite 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.markdown +49 -0
- data/VERSION +1 -0
- data/bin/bixbite +73 -0
- data/lib/bixbite.rb +13 -0
- data/lib/bixbite/command.rb +14 -0
- data/lib/bixbite/create.rb +76 -0
- data/template/Rakefile +25 -0
- data/template/assets/bixbite/Rakefile.rb +297 -0
- data/template/assets/naturaldocs/NaturalDocs/Config/Languages.txt +286 -0
- data/template/assets/naturaldocs/NaturalDocs/Config/Topics.txt +382 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/customizinglanguages.html +52 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/customizingtopics.html +74 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/documenting.html +58 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/documenting/reference.html +146 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/documenting/walkthrough.html +180 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/example/Default.css +528 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/example/NaturalDocs.js +204 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/examples.css +90 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/background.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/leftside.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/logo.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overbody.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overbodybg.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overleftmargin.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overmenu.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/overmenubg.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/header/rightside.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/logo.gif +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/about.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/background.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/bottomleft.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/bottomright.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/community.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/customizing.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/images/menu/using.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/index.html +9 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/javascript/BrowserStyles.js +77 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/javascript/PNGHandling.js +72 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/keywords.html +38 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/languages.html +32 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/menu.html +79 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/output.html +84 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/running.html +40 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/styles.css +290 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/styles.html +52 -0
- data/template/assets/naturaldocs/NaturalDocs/Help/troubleshooting.html +18 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/CSSGuide.txt +947 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/File Parsing.txt +83 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/HTMLTestCases.pm +269 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/Languages.txt +107 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/NDMarkup.txt +91 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/Symbol Management.txt +59 -0
- data/template/assets/naturaldocs/NaturalDocs/Info/images/Logo.png +0 -0
- data/template/assets/naturaldocs/NaturalDocs/JavaScript/NaturalDocs.js +836 -0
- data/template/assets/naturaldocs/NaturalDocs/License-GPL.txt +341 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/BinaryFile.pm +294 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder.pm +280 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder/Base.pm +348 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder/FramedHTML.pm +345 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder/HTML.pm +398 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Builder/HTMLBase.pm +3693 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ClassHierarchy.pm +860 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ClassHierarchy/Class.pm +412 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ClassHierarchy/File.pm +157 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ConfigFile.pm +497 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Constants.pm +165 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/DefineMembers.pm +100 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Error.pm +305 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/File.pm +540 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ImageReferenceTable.pm +383 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ImageReferenceTable/Reference.pm +44 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ImageReferenceTable/String.pm +110 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages.pm +1475 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/ActionScript.pm +1473 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Ada.pm +38 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Advanced.pm +828 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Advanced/Scope.pm +95 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Advanced/ScopeChange.pm +70 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Base.pm +832 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/CSharp.pm +1484 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/PLSQL.pm +319 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Pascal.pm +143 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Perl.pm +1370 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Prototype.pm +92 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Prototype/Parameter.pm +87 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Simple.pm +503 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Languages/Tcl.pm +219 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Menu.pm +3406 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Menu/Entry.pm +201 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/NDMarkup.pm +76 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Parser.pm +1331 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Parser/JavaDoc.pm +464 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Parser/Native.pm +1060 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Parser/ParsedTopic.pm +253 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Project.pm +1402 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Project/ImageFile.pm +160 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Project/SourceFile.pm +113 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/ReferenceString.pm +334 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Settings.pm +1418 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Settings/BuildTarget.pm +66 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB.pm +678 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/Extension.pm +84 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/File.pm +129 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/Item.pm +201 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/ItemDefinition.pm +45 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SourceDB/WatchedFileDefinitions.pm +159 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/StatusMessage.pm +102 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolString.pm +212 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable.pm +1984 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/File.pm +186 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/IndexElement.pm +522 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/Reference.pm +273 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/ReferenceTarget.pm +97 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/Symbol.pm +428 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/SymbolTable/SymbolDefinition.pm +96 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Topics.pm +1319 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Topics/Type.pm +151 -0
- data/template/assets/naturaldocs/NaturalDocs/Modules/NaturalDocs/Version.pm +384 -0
- data/template/assets/naturaldocs/NaturalDocs/NaturalDocs +400 -0
- data/template/assets/naturaldocs/NaturalDocs/NaturalDocs.bat +17 -0
- data/template/assets/naturaldocs/NaturalDocs/Styles/Default.css +767 -0
- data/template/assets/naturaldocs/NaturalDocs/Styles/Roman.css +765 -0
- data/template/assets/naturaldocs/NaturalDocs/Styles/Small.css +763 -0
- data/template/assets/utilities/pngout +0 -0
- data/template/deploy/public_html/.htaccess +0 -0
- data/template/documentation/js/.htaccess +0 -0
- data/template/src/html/.htaccess +76 -0
- data/template/src/html/css/cmn/global.css +96 -0
- data/template/src/html/css/cmn/ie.css +15 -0
- data/template/src/html/css/cmn/ie6.css +15 -0
- data/template/src/html/images/cmn/.htaccess +0 -0
- data/template/src/html/images/tmp/.htaccess +0 -0
- data/template/src/html/includes/debug.inc +5 -0
- data/template/src/html/includes/footer.inc +52 -0
- data/template/src/html/includes/header.inc +61 -0
- data/template/src/html/includes/html.inc +3 -0
- data/template/src/html/includes/namespace.inc +19 -0
- data/template/src/html/includes/page.inc +151 -0
- data/template/src/html/index.html +35 -0
- data/template/src/html/js/cmn/bootstrap.js +74 -0
- data/template/src/html/js/cmn/global.js +142 -0
- data/template/src/html/js/cmn/lib/LAB.js +348 -0
- data/template/src/html/min/.htaccess +4 -0
- data/template/src/html/min/MinifyCLI.php +19 -0
- data/template/src/html/min/README.txt +132 -0
- data/template/src/html/min/builder/_index.js +242 -0
- data/template/src/html/min/builder/bm.js +36 -0
- data/template/src/html/min/builder/index.php +182 -0
- data/template/src/html/min/builder/ocCheck.php +36 -0
- data/template/src/html/min/builder/rewriteTest.js +1 -0
- data/template/src/html/min/config.php +187 -0
- data/template/src/html/min/groupsConfig.php +34 -0
- data/template/src/html/min/index.php +66 -0
- data/template/src/html/min/lib/FirePHP.php +1370 -0
- data/template/src/html/min/lib/HTTP/ConditionalGet.php +348 -0
- data/template/src/html/min/lib/HTTP/Encoder.php +326 -0
- data/template/src/html/min/lib/JSMin.php +314 -0
- data/template/src/html/min/lib/JSMinPlus.php +1872 -0
- data/template/src/html/min/lib/Minify.php +532 -0
- data/template/src/html/min/lib/Minify/Build.php +103 -0
- data/template/src/html/min/lib/Minify/CSS.php +83 -0
- data/template/src/html/min/lib/Minify/CSS/Compressor.php +250 -0
- data/template/src/html/min/lib/Minify/CSS/UriRewriter.php +270 -0
- data/template/src/html/min/lib/Minify/Cache/APC.php +130 -0
- data/template/src/html/min/lib/Minify/Cache/File.php +125 -0
- data/template/src/html/min/lib/Minify/Cache/Memcache.php +137 -0
- data/template/src/html/min/lib/Minify/ClosureCompiler.php +85 -0
- data/template/src/html/min/lib/Minify/CommentPreserver.php +90 -0
- data/template/src/html/min/lib/Minify/Controller/Base.php +202 -0
- data/template/src/html/min/lib/Minify/Controller/Files.php +78 -0
- data/template/src/html/min/lib/Minify/Controller/Groups.php +94 -0
- data/template/src/html/min/lib/Minify/Controller/MinApp.php +132 -0
- data/template/src/html/min/lib/Minify/Controller/Page.php +82 -0
- data/template/src/html/min/lib/Minify/Controller/Version1.php +118 -0
- data/template/src/html/min/lib/Minify/HTML.php +245 -0
- data/template/src/html/min/lib/Minify/ImportProcessor.php +157 -0
- data/template/src/html/min/lib/Minify/Lines.php +131 -0
- data/template/src/html/min/lib/Minify/Logger.php +45 -0
- data/template/src/html/min/lib/Minify/Packer.php +37 -0
- data/template/src/html/min/lib/Minify/Source.php +187 -0
- data/template/src/html/min/lib/Minify/YUICompressor.php +139 -0
- data/template/src/html/min/lib/Solar/Dir.php +199 -0
- data/template/src/html/min/lib/closure-compiler.jar +0 -0
- data/template/src/html/min/lib/yuicompressor-2.4.2.jar +0 -0
- data/template/src/html/min/utils.php +90 -0
- data/template/src/templates/css/template.css +7 -0
- data/template/src/templates/js/template.js +72 -0
- data/template/src/templates/template.html +18 -0
- data/template/src/yaml/config.yml +46 -0
- data/template/src/yaml/deploy.yml +35 -0
- data/test/bixbite_test.rb +7 -0
- data/test/test_helper.rb +10 -0
- metadata +278 -0
@@ -0,0 +1,348 @@
|
|
1
|
+
<?php
|
2
|
+
/**
|
3
|
+
* Class HTTP_ConditionalGet
|
4
|
+
* @package Minify
|
5
|
+
* @subpackage HTTP
|
6
|
+
*/
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Implement conditional GET via a timestamp or hash of content
|
10
|
+
*
|
11
|
+
* E.g. Content from DB with update time:
|
12
|
+
* <code>
|
13
|
+
* list($updateTime, $content) = getDbUpdateAndContent();
|
14
|
+
* $cg = new HTTP_ConditionalGet(array(
|
15
|
+
* 'lastModifiedTime' => $updateTime
|
16
|
+
* ,'isPublic' => true
|
17
|
+
* ));
|
18
|
+
* $cg->sendHeaders();
|
19
|
+
* if ($cg->cacheIsValid) {
|
20
|
+
* exit();
|
21
|
+
* }
|
22
|
+
* echo $content;
|
23
|
+
* </code>
|
24
|
+
*
|
25
|
+
* E.g. Shortcut for the above
|
26
|
+
* <code>
|
27
|
+
* HTTP_ConditionalGet::check($updateTime, true); // exits if client has cache
|
28
|
+
* echo $content;
|
29
|
+
* </code>
|
30
|
+
*
|
31
|
+
* E.g. Content from DB with no update time:
|
32
|
+
* <code>
|
33
|
+
* $content = getContentFromDB();
|
34
|
+
* $cg = new HTTP_ConditionalGet(array(
|
35
|
+
* 'contentHash' => md5($content)
|
36
|
+
* ));
|
37
|
+
* $cg->sendHeaders();
|
38
|
+
* if ($cg->cacheIsValid) {
|
39
|
+
* exit();
|
40
|
+
* }
|
41
|
+
* echo $content;
|
42
|
+
* </code>
|
43
|
+
*
|
44
|
+
* E.g. Static content with some static includes:
|
45
|
+
* <code>
|
46
|
+
* // before content
|
47
|
+
* $cg = new HTTP_ConditionalGet(array(
|
48
|
+
* 'lastUpdateTime' => max(
|
49
|
+
* filemtime(__FILE__)
|
50
|
+
* ,filemtime('/path/to/header.inc')
|
51
|
+
* ,filemtime('/path/to/footer.inc')
|
52
|
+
* )
|
53
|
+
* ));
|
54
|
+
* $cg->sendHeaders();
|
55
|
+
* if ($cg->cacheIsValid) {
|
56
|
+
* exit();
|
57
|
+
* }
|
58
|
+
* </code>
|
59
|
+
* @package Minify
|
60
|
+
* @subpackage HTTP
|
61
|
+
* @author Stephen Clay <steve@mrclay.org>
|
62
|
+
*/
|
63
|
+
class HTTP_ConditionalGet {
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Does the client have a valid copy of the requested resource?
|
67
|
+
*
|
68
|
+
* You'll want to check this after instantiating the object. If true, do
|
69
|
+
* not send content, just call sendHeaders() if you haven't already.
|
70
|
+
*
|
71
|
+
* @var bool
|
72
|
+
*/
|
73
|
+
public $cacheIsValid = null;
|
74
|
+
|
75
|
+
/**
|
76
|
+
* @param array $spec options
|
77
|
+
*
|
78
|
+
* 'isPublic': (bool) if true, the Cache-Control header will contain
|
79
|
+
* "public", allowing proxies to cache the content. Otherwise "private" will
|
80
|
+
* be sent, allowing only browser caching. (default false)
|
81
|
+
*
|
82
|
+
* 'lastModifiedTime': (int) if given, both ETag AND Last-Modified headers
|
83
|
+
* will be sent with content. This is recommended.
|
84
|
+
*
|
85
|
+
* 'encoding': (string) if set, the header "Vary: Accept-Encoding" will
|
86
|
+
* always be sent and a truncated version of the encoding will be appended
|
87
|
+
* to the ETag. E.g. "pub123456;gz". This will also trigger a more lenient
|
88
|
+
* checking of the client's If-None-Match header, as the encoding portion of
|
89
|
+
* the ETag will be stripped before comparison.
|
90
|
+
*
|
91
|
+
* 'contentHash': (string) if given, only the ETag header can be sent with
|
92
|
+
* content (only HTTP1.1 clients can conditionally GET). The given string
|
93
|
+
* should be short with no quote characters and always change when the
|
94
|
+
* resource changes (recommend md5()). This is not needed/used if
|
95
|
+
* lastModifiedTime is given.
|
96
|
+
*
|
97
|
+
* 'eTag': (string) if given, this will be used as the ETag header rather
|
98
|
+
* than values based on lastModifiedTime or contentHash. Also the encoding
|
99
|
+
* string will not be appended to the given value as described above.
|
100
|
+
*
|
101
|
+
* 'invalidate': (bool) if true, the client cache will be considered invalid
|
102
|
+
* without testing. Effectively this disables conditional GET.
|
103
|
+
* (default false)
|
104
|
+
*
|
105
|
+
* 'maxAge': (int) if given, this will set the Cache-Control max-age in
|
106
|
+
* seconds, and also set the Expires header to the equivalent GMT date.
|
107
|
+
* After the max-age period has passed, the browser will again send a
|
108
|
+
* conditional GET to revalidate its cache.
|
109
|
+
*
|
110
|
+
* @return null
|
111
|
+
*/
|
112
|
+
public function __construct($spec)
|
113
|
+
{
|
114
|
+
$scope = (isset($spec['isPublic']) && $spec['isPublic'])
|
115
|
+
? 'public'
|
116
|
+
: 'private';
|
117
|
+
$maxAge = 0;
|
118
|
+
// backwards compatibility (can be removed later)
|
119
|
+
if (isset($spec['setExpires'])
|
120
|
+
&& is_numeric($spec['setExpires'])
|
121
|
+
&& ! isset($spec['maxAge'])) {
|
122
|
+
$spec['maxAge'] = $spec['setExpires'] - $_SERVER['REQUEST_TIME'];
|
123
|
+
}
|
124
|
+
if (isset($spec['maxAge'])) {
|
125
|
+
$maxAge = $spec['maxAge'];
|
126
|
+
$this->_headers['Expires'] = self::gmtDate(
|
127
|
+
$_SERVER['REQUEST_TIME'] + $spec['maxAge']
|
128
|
+
);
|
129
|
+
}
|
130
|
+
$etagAppend = '';
|
131
|
+
if (isset($spec['encoding'])) {
|
132
|
+
$this->_stripEtag = true;
|
133
|
+
$this->_headers['Vary'] = 'Accept-Encoding';
|
134
|
+
if ('' !== $spec['encoding']) {
|
135
|
+
if (0 === strpos($spec['encoding'], 'x-')) {
|
136
|
+
$spec['encoding'] = substr($spec['encoding'], 2);
|
137
|
+
}
|
138
|
+
$etagAppend = ';' . substr($spec['encoding'], 0, 2);
|
139
|
+
}
|
140
|
+
}
|
141
|
+
if (isset($spec['lastModifiedTime'])) {
|
142
|
+
$this->_setLastModified($spec['lastModifiedTime']);
|
143
|
+
if (isset($spec['eTag'])) { // Use it
|
144
|
+
$this->_setEtag($spec['eTag'], $scope);
|
145
|
+
} else { // base both headers on time
|
146
|
+
$this->_setEtag($spec['lastModifiedTime'] . $etagAppend, $scope);
|
147
|
+
}
|
148
|
+
} elseif (isset($spec['eTag'])) { // Use it
|
149
|
+
$this->_setEtag($spec['eTag'], $scope);
|
150
|
+
} elseif (isset($spec['contentHash'])) { // Use the hash as the ETag
|
151
|
+
$this->_setEtag($spec['contentHash'] . $etagAppend, $scope);
|
152
|
+
}
|
153
|
+
$this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}";
|
154
|
+
// invalidate cache if disabled, otherwise check
|
155
|
+
$this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate'])
|
156
|
+
? false
|
157
|
+
: $this->_isCacheValid();
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Get array of output headers to be sent
|
162
|
+
*
|
163
|
+
* In the case of 304 responses, this array will only contain the response
|
164
|
+
* code header: array('_responseCode' => 'HTTP/1.0 304 Not Modified')
|
165
|
+
*
|
166
|
+
* Otherwise something like:
|
167
|
+
* <code>
|
168
|
+
* array(
|
169
|
+
* 'Cache-Control' => 'max-age=0, public'
|
170
|
+
* ,'ETag' => '"foobar"'
|
171
|
+
* )
|
172
|
+
* </code>
|
173
|
+
*
|
174
|
+
* @return array
|
175
|
+
*/
|
176
|
+
public function getHeaders()
|
177
|
+
{
|
178
|
+
return $this->_headers;
|
179
|
+
}
|
180
|
+
|
181
|
+
/**
|
182
|
+
* Set the Content-Length header in bytes
|
183
|
+
*
|
184
|
+
* With most PHP configs, as long as you don't flush() output, this method
|
185
|
+
* is not needed and PHP will buffer all output and set Content-Length for
|
186
|
+
* you. Otherwise you'll want to call this to let the client know up front.
|
187
|
+
*
|
188
|
+
* @param int $bytes
|
189
|
+
*
|
190
|
+
* @return int copy of input $bytes
|
191
|
+
*/
|
192
|
+
public function setContentLength($bytes)
|
193
|
+
{
|
194
|
+
return $this->_headers['Content-Length'] = $bytes;
|
195
|
+
}
|
196
|
+
|
197
|
+
/**
|
198
|
+
* Send headers
|
199
|
+
*
|
200
|
+
* @see getHeaders()
|
201
|
+
*
|
202
|
+
* Note this doesn't "clear" the headers. Calling sendHeaders() will
|
203
|
+
* call header() again (but probably have not effect) and getHeaders() will
|
204
|
+
* still return the headers.
|
205
|
+
*
|
206
|
+
* @return null
|
207
|
+
*/
|
208
|
+
public function sendHeaders()
|
209
|
+
{
|
210
|
+
$headers = $this->_headers;
|
211
|
+
if (array_key_exists('_responseCode', $headers)) {
|
212
|
+
header($headers['_responseCode']);
|
213
|
+
unset($headers['_responseCode']);
|
214
|
+
}
|
215
|
+
foreach ($headers as $name => $val) {
|
216
|
+
header($name . ': ' . $val);
|
217
|
+
}
|
218
|
+
}
|
219
|
+
|
220
|
+
/**
|
221
|
+
* Exit if the client's cache is valid for this resource
|
222
|
+
*
|
223
|
+
* This is a convenience method for common use of the class
|
224
|
+
*
|
225
|
+
* @param int $lastModifiedTime if given, both ETag AND Last-Modified headers
|
226
|
+
* will be sent with content. This is recommended.
|
227
|
+
*
|
228
|
+
* @param bool $isPublic (default false) if true, the Cache-Control header
|
229
|
+
* will contain "public", allowing proxies to cache the content. Otherwise
|
230
|
+
* "private" will be sent, allowing only browser caching.
|
231
|
+
*
|
232
|
+
* @param array $options (default empty) additional options for constructor
|
233
|
+
*
|
234
|
+
* @return null
|
235
|
+
*/
|
236
|
+
public static function check($lastModifiedTime = null, $isPublic = false, $options = array())
|
237
|
+
{
|
238
|
+
if (null !== $lastModifiedTime) {
|
239
|
+
$options['lastModifiedTime'] = (int)$lastModifiedTime;
|
240
|
+
}
|
241
|
+
$options['isPublic'] = (bool)$isPublic;
|
242
|
+
$cg = new HTTP_ConditionalGet($options);
|
243
|
+
$cg->sendHeaders();
|
244
|
+
if ($cg->cacheIsValid) {
|
245
|
+
exit();
|
246
|
+
}
|
247
|
+
}
|
248
|
+
|
249
|
+
|
250
|
+
/**
|
251
|
+
* Get a GMT formatted date for use in HTTP headers
|
252
|
+
*
|
253
|
+
* <code>
|
254
|
+
* header('Expires: ' . HTTP_ConditionalGet::gmtdate($time));
|
255
|
+
* </code>
|
256
|
+
*
|
257
|
+
* @param int $time unix timestamp
|
258
|
+
*
|
259
|
+
* @return string
|
260
|
+
*/
|
261
|
+
public static function gmtDate($time)
|
262
|
+
{
|
263
|
+
return gmdate('D, d M Y H:i:s \G\M\T', $time);
|
264
|
+
}
|
265
|
+
|
266
|
+
protected $_headers = array();
|
267
|
+
protected $_lmTime = null;
|
268
|
+
protected $_etag = null;
|
269
|
+
protected $_stripEtag = false;
|
270
|
+
|
271
|
+
protected function _setEtag($hash, $scope)
|
272
|
+
{
|
273
|
+
$this->_etag = '"' . substr($scope, 0, 3) . $hash . '"';
|
274
|
+
$this->_headers['ETag'] = $this->_etag;
|
275
|
+
}
|
276
|
+
|
277
|
+
protected function _setLastModified($time)
|
278
|
+
{
|
279
|
+
$this->_lmTime = (int)$time;
|
280
|
+
$this->_headers['Last-Modified'] = self::gmtDate($time);
|
281
|
+
}
|
282
|
+
|
283
|
+
/**
|
284
|
+
* Determine validity of client cache and queue 304 header if valid
|
285
|
+
*/
|
286
|
+
protected function _isCacheValid()
|
287
|
+
{
|
288
|
+
if (null === $this->_etag) {
|
289
|
+
// lmTime is copied to ETag, so this condition implies that the
|
290
|
+
// server sent neither ETag nor Last-Modified, so the client can't
|
291
|
+
// possibly has a valid cache.
|
292
|
+
return false;
|
293
|
+
}
|
294
|
+
$isValid = ($this->resourceMatchedEtag() || $this->resourceNotModified());
|
295
|
+
if ($isValid) {
|
296
|
+
$this->_headers['_responseCode'] = 'HTTP/1.0 304 Not Modified';
|
297
|
+
}
|
298
|
+
return $isValid;
|
299
|
+
}
|
300
|
+
|
301
|
+
protected function resourceMatchedEtag()
|
302
|
+
{
|
303
|
+
if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
|
304
|
+
return false;
|
305
|
+
}
|
306
|
+
$clientEtagList = get_magic_quotes_gpc()
|
307
|
+
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
|
308
|
+
: $_SERVER['HTTP_IF_NONE_MATCH'];
|
309
|
+
$clientEtags = explode(',', $clientEtagList);
|
310
|
+
|
311
|
+
$compareTo = $this->normalizeEtag($this->_etag);
|
312
|
+
foreach ($clientEtags as $clientEtag) {
|
313
|
+
if ($this->normalizeEtag($clientEtag) === $compareTo) {
|
314
|
+
// respond with the client's matched ETag, even if it's not what
|
315
|
+
// we would've sent by default
|
316
|
+
$this->_headers['ETag'] = trim($clientEtag);
|
317
|
+
return true;
|
318
|
+
}
|
319
|
+
}
|
320
|
+
return false;
|
321
|
+
}
|
322
|
+
|
323
|
+
protected function normalizeEtag($etag) {
|
324
|
+
$etag = trim($etag);
|
325
|
+
return $this->_stripEtag
|
326
|
+
? preg_replace('/;\\w\\w"$/', '"', $etag)
|
327
|
+
: $etag;
|
328
|
+
}
|
329
|
+
|
330
|
+
protected function resourceNotModified()
|
331
|
+
{
|
332
|
+
if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
333
|
+
return false;
|
334
|
+
}
|
335
|
+
$ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
|
336
|
+
if (false !== ($semicolon = strrpos($ifModifiedSince, ';'))) {
|
337
|
+
// IE has tacked on extra data to this header, strip it
|
338
|
+
$ifModifiedSince = substr($ifModifiedSince, 0, $semicolon);
|
339
|
+
}
|
340
|
+
if ($ifModifiedSince == self::gmtDate($this->_lmTime)) {
|
341
|
+
// Apache 2.2's behavior. If there was no ETag match, send the
|
342
|
+
// non-encoded version of the ETag value.
|
343
|
+
$this->_headers['ETag'] = $this->normalizeEtag($this->_etag);
|
344
|
+
return true;
|
345
|
+
}
|
346
|
+
return false;
|
347
|
+
}
|
348
|
+
}
|
@@ -0,0 +1,326 @@
|
|
1
|
+
<?php
|
2
|
+
/**
|
3
|
+
* Class HTTP_Encoder
|
4
|
+
* @package Minify
|
5
|
+
* @subpackage HTTP
|
6
|
+
*/
|
7
|
+
|
8
|
+
/**
|
9
|
+
* Encode and send gzipped/deflated content
|
10
|
+
*
|
11
|
+
* The "Vary: Accept-Encoding" header is sent. If the client allows encoding,
|
12
|
+
* Content-Encoding and Content-Length are added.
|
13
|
+
*
|
14
|
+
* <code>
|
15
|
+
* // Send a CSS file, compressed if possible
|
16
|
+
* $he = new HTTP_Encoder(array(
|
17
|
+
* 'content' => file_get_contents($cssFile)
|
18
|
+
* ,'type' => 'text/css'
|
19
|
+
* ));
|
20
|
+
* $he->encode();
|
21
|
+
* $he->sendAll();
|
22
|
+
* </code>
|
23
|
+
*
|
24
|
+
* <code>
|
25
|
+
* // Shortcut to encoding output
|
26
|
+
* header('Content-Type: text/css'); // needed if not HTML
|
27
|
+
* HTTP_Encoder::output($css);
|
28
|
+
* </code>
|
29
|
+
*
|
30
|
+
* <code>
|
31
|
+
* // Just sniff for the accepted encoding
|
32
|
+
* $encoding = HTTP_Encoder::getAcceptedEncoding();
|
33
|
+
* </code>
|
34
|
+
*
|
35
|
+
* For more control over headers, use getHeaders() and getData() and send your
|
36
|
+
* own output.
|
37
|
+
*
|
38
|
+
* Note: If you don't need header mgmt, use PHP's native gzencode, gzdeflate,
|
39
|
+
* and gzcompress functions for gzip, deflate, and compress-encoding
|
40
|
+
* respectively.
|
41
|
+
*
|
42
|
+
* @package Minify
|
43
|
+
* @subpackage HTTP
|
44
|
+
* @author Stephen Clay <steve@mrclay.org>
|
45
|
+
*/
|
46
|
+
class HTTP_Encoder {
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Should the encoder allow HTTP encoding to IE6?
|
50
|
+
*
|
51
|
+
* If you have many IE6 users and the bandwidth savings is worth troubling
|
52
|
+
* some of them, set this to true.
|
53
|
+
*
|
54
|
+
* By default, encoding is only offered to IE7+. When this is true,
|
55
|
+
* getAcceptedEncoding() will return an encoding for IE6 if its user agent
|
56
|
+
* string contains "SV1". This has been documented in many places as "safe",
|
57
|
+
* but there seem to be remaining, intermittent encoding bugs in patched
|
58
|
+
* IE6 on the wild web.
|
59
|
+
*
|
60
|
+
* @var bool
|
61
|
+
*/
|
62
|
+
public static $encodeToIe6 = false;
|
63
|
+
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Default compression level for zlib operations
|
67
|
+
*
|
68
|
+
* This level is used if encode() is not given a $compressionLevel
|
69
|
+
*
|
70
|
+
* @var int
|
71
|
+
*/
|
72
|
+
public static $compressionLevel = 6;
|
73
|
+
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Get an HTTP Encoder object
|
77
|
+
*
|
78
|
+
* @param array $spec options
|
79
|
+
*
|
80
|
+
* 'content': (string required) content to be encoded
|
81
|
+
*
|
82
|
+
* 'type': (string) if set, the Content-Type header will have this value.
|
83
|
+
*
|
84
|
+
* 'method: (string) only set this if you are forcing a particular encoding
|
85
|
+
* method. If not set, the best method will be chosen by getAcceptedEncoding()
|
86
|
+
* The available methods are 'gzip', 'deflate', 'compress', and '' (no
|
87
|
+
* encoding)
|
88
|
+
*
|
89
|
+
* @return null
|
90
|
+
*/
|
91
|
+
public function __construct($spec)
|
92
|
+
{
|
93
|
+
$this->_content = $spec['content'];
|
94
|
+
$this->_headers['Content-Length'] = (string)strlen($this->_content);
|
95
|
+
if (isset($spec['type'])) {
|
96
|
+
$this->_headers['Content-Type'] = $spec['type'];
|
97
|
+
}
|
98
|
+
if (isset($spec['method'])
|
99
|
+
&& in_array($spec['method'], array('gzip', 'deflate', 'compress', '')))
|
100
|
+
{
|
101
|
+
$this->_encodeMethod = array($spec['method'], $spec['method']);
|
102
|
+
} else {
|
103
|
+
$this->_encodeMethod = self::getAcceptedEncoding();
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Get content in current form
|
109
|
+
*
|
110
|
+
* Call after encode() for encoded content.
|
111
|
+
*
|
112
|
+
* return string
|
113
|
+
*/
|
114
|
+
public function getContent()
|
115
|
+
{
|
116
|
+
return $this->_content;
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Get array of output headers to be sent
|
121
|
+
*
|
122
|
+
* E.g.
|
123
|
+
* <code>
|
124
|
+
* array(
|
125
|
+
* 'Content-Length' => '615'
|
126
|
+
* ,'Content-Encoding' => 'x-gzip'
|
127
|
+
* ,'Vary' => 'Accept-Encoding'
|
128
|
+
* )
|
129
|
+
* </code>
|
130
|
+
*
|
131
|
+
* @return array
|
132
|
+
*/
|
133
|
+
public function getHeaders()
|
134
|
+
{
|
135
|
+
return $this->_headers;
|
136
|
+
}
|
137
|
+
|
138
|
+
/**
|
139
|
+
* Send output headers
|
140
|
+
*
|
141
|
+
* You must call this before headers are sent and it probably cannot be
|
142
|
+
* used in conjunction with zlib output buffering / mod_gzip. Errors are
|
143
|
+
* not handled purposefully.
|
144
|
+
*
|
145
|
+
* @see getHeaders()
|
146
|
+
*
|
147
|
+
* @return null
|
148
|
+
*/
|
149
|
+
public function sendHeaders()
|
150
|
+
{
|
151
|
+
foreach ($this->_headers as $name => $val) {
|
152
|
+
header($name . ': ' . $val);
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
/**
|
157
|
+
* Send output headers and content
|
158
|
+
*
|
159
|
+
* A shortcut for sendHeaders() and echo getContent()
|
160
|
+
*
|
161
|
+
* You must call this before headers are sent and it probably cannot be
|
162
|
+
* used in conjunction with zlib output buffering / mod_gzip. Errors are
|
163
|
+
* not handled purposefully.
|
164
|
+
*
|
165
|
+
* @return null
|
166
|
+
*/
|
167
|
+
public function sendAll()
|
168
|
+
{
|
169
|
+
$this->sendHeaders();
|
170
|
+
echo $this->_content;
|
171
|
+
}
|
172
|
+
|
173
|
+
/**
|
174
|
+
* Determine the client's best encoding method from the HTTP Accept-Encoding
|
175
|
+
* header.
|
176
|
+
*
|
177
|
+
* If no Accept-Encoding header is set, or the browser is IE before v6 SP2,
|
178
|
+
* this will return ('', ''), the "identity" encoding.
|
179
|
+
*
|
180
|
+
* A syntax-aware scan is done of the Accept-Encoding, so the method must
|
181
|
+
* be non 0. The methods are favored in order of gzip, deflate, then
|
182
|
+
* compress. Deflate is always smallest and generally faster, but is
|
183
|
+
* rarely sent by servers, so client support could be buggier.
|
184
|
+
*
|
185
|
+
* @param bool $allowCompress allow the older compress encoding
|
186
|
+
*
|
187
|
+
* @param bool $allowDeflate allow the more recent deflate encoding
|
188
|
+
*
|
189
|
+
* @return array two values, 1st is the actual encoding method, 2nd is the
|
190
|
+
* alias of that method to use in the Content-Encoding header (some browsers
|
191
|
+
* call gzip "x-gzip" etc.)
|
192
|
+
*/
|
193
|
+
public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
|
194
|
+
{
|
195
|
+
// @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
196
|
+
|
197
|
+
if (! isset($_SERVER['HTTP_ACCEPT_ENCODING'])
|
198
|
+
|| self::_isBuggyIe())
|
199
|
+
{
|
200
|
+
return array('', '');
|
201
|
+
}
|
202
|
+
$ae = $_SERVER['HTTP_ACCEPT_ENCODING'];
|
203
|
+
// gzip checks (quick)
|
204
|
+
if (0 === strpos($ae, 'gzip,') // most browsers
|
205
|
+
|| 0 === strpos($ae, 'deflate, gzip,') // opera
|
206
|
+
) {
|
207
|
+
return array('gzip', 'gzip');
|
208
|
+
}
|
209
|
+
// gzip checks (slow)
|
210
|
+
if (preg_match(
|
211
|
+
'@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
|
212
|
+
,$ae
|
213
|
+
,$m)) {
|
214
|
+
return array('gzip', $m[1]);
|
215
|
+
}
|
216
|
+
if ($allowDeflate) {
|
217
|
+
// deflate checks
|
218
|
+
$aeRev = strrev($ae);
|
219
|
+
if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit
|
220
|
+
|| 0 === strpos($aeRev, 'etalfed,') // gecko
|
221
|
+
|| 0 === strpos($ae, 'deflate,') // opera
|
222
|
+
// slow parsing
|
223
|
+
|| preg_match(
|
224
|
+
'@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) {
|
225
|
+
return array('deflate', 'deflate');
|
226
|
+
}
|
227
|
+
}
|
228
|
+
if ($allowCompress && preg_match(
|
229
|
+
'@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
|
230
|
+
,$ae
|
231
|
+
,$m)) {
|
232
|
+
return array('compress', $m[1]);
|
233
|
+
}
|
234
|
+
return array('', '');
|
235
|
+
}
|
236
|
+
|
237
|
+
/**
|
238
|
+
* Encode (compress) the content
|
239
|
+
*
|
240
|
+
* If the encode method is '' (none) or compression level is 0, or the 'zlib'
|
241
|
+
* extension isn't loaded, we return false.
|
242
|
+
*
|
243
|
+
* Then the appropriate gz_* function is called to compress the content. If
|
244
|
+
* this fails, false is returned.
|
245
|
+
*
|
246
|
+
* The header "Vary: Accept-Encoding" is added. If encoding is successful,
|
247
|
+
* the Content-Length header is updated, and Content-Encoding is also added.
|
248
|
+
*
|
249
|
+
* @param int $compressionLevel given to zlib functions. If not given, the
|
250
|
+
* class default will be used.
|
251
|
+
*
|
252
|
+
* @return bool success true if the content was actually compressed
|
253
|
+
*/
|
254
|
+
public function encode($compressionLevel = null)
|
255
|
+
{
|
256
|
+
$this->_headers['Vary'] = 'Accept-Encoding';
|
257
|
+
if (null === $compressionLevel) {
|
258
|
+
$compressionLevel = self::$compressionLevel;
|
259
|
+
}
|
260
|
+
if ('' === $this->_encodeMethod[0]
|
261
|
+
|| ($compressionLevel == 0)
|
262
|
+
|| !extension_loaded('zlib'))
|
263
|
+
{
|
264
|
+
return false;
|
265
|
+
}
|
266
|
+
if ($this->_encodeMethod[0] === 'deflate') {
|
267
|
+
$encoded = gzdeflate($this->_content, $compressionLevel);
|
268
|
+
} elseif ($this->_encodeMethod[0] === 'gzip') {
|
269
|
+
$encoded = gzencode($this->_content, $compressionLevel);
|
270
|
+
} else {
|
271
|
+
$encoded = gzcompress($this->_content, $compressionLevel);
|
272
|
+
}
|
273
|
+
if (false === $encoded) {
|
274
|
+
return false;
|
275
|
+
}
|
276
|
+
$this->_headers['Content-Length'] = strlen($encoded);
|
277
|
+
$this->_headers['Content-Encoding'] = $this->_encodeMethod[1];
|
278
|
+
$this->_content = $encoded;
|
279
|
+
return true;
|
280
|
+
}
|
281
|
+
|
282
|
+
/**
|
283
|
+
* Encode and send appropriate headers and content
|
284
|
+
*
|
285
|
+
* This is a convenience method for common use of the class
|
286
|
+
*
|
287
|
+
* @param string $content
|
288
|
+
*
|
289
|
+
* @param int $compressionLevel given to zlib functions. If not given, the
|
290
|
+
* class default will be used.
|
291
|
+
*
|
292
|
+
* @return bool success true if the content was actually compressed
|
293
|
+
*/
|
294
|
+
public static function output($content, $compressionLevel = null)
|
295
|
+
{
|
296
|
+
if (null === $compressionLevel) {
|
297
|
+
$compressionLevel = self::$compressionLevel;
|
298
|
+
}
|
299
|
+
$he = new HTTP_Encoder(array('content' => $content));
|
300
|
+
$ret = $he->encode($compressionLevel);
|
301
|
+
$he->sendAll();
|
302
|
+
return $ret;
|
303
|
+
}
|
304
|
+
|
305
|
+
protected $_content = '';
|
306
|
+
protected $_headers = array();
|
307
|
+
protected $_encodeMethod = array('', '');
|
308
|
+
|
309
|
+
/**
|
310
|
+
* Is the browser an IE version earlier than 6 SP2?
|
311
|
+
*/
|
312
|
+
protected static function _isBuggyIe()
|
313
|
+
{
|
314
|
+
$ua = $_SERVER['HTTP_USER_AGENT'];
|
315
|
+
// quick escape for non-IEs
|
316
|
+
if (0 !== strpos($ua, 'Mozilla/4.0 (compatible; MSIE ')
|
317
|
+
|| false !== strpos($ua, 'Opera')) {
|
318
|
+
return false;
|
319
|
+
}
|
320
|
+
// no regex = faaast
|
321
|
+
$version = (float)substr($ua, 30);
|
322
|
+
return self::$encodeToIe6
|
323
|
+
? ($version < 6 || ($version == 6 && false === strpos($ua, 'SV1')))
|
324
|
+
: ($version < 7);
|
325
|
+
}
|
326
|
+
}
|