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

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

223 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * JCH Optimize - Performs several front-end optimizations for fast downloads
5 *
6 * @package jchoptimize/core
7 * @author Samuel Marshall <samuel@jch-optimize.net>
8 * @copyright Copyright (c) 2022 Samuel Marshall / JCH Optimize
9 * @license GNU/GPLv3, or later. See LICENSE file
10 *
11 * If LICENSE file missing, see <http://www.gnu.org/licenses/>.
12 */
13
14 namespace JchOptimize\Core;
15
16 use _JchOptimizeVendor\V91\Joomla\Input\Input;
17 use _JchOptimizeVendor\V91\Laminas\Cache\Exception\ExceptionInterface;
18 use _JchOptimizeVendor\V91\Laminas\Cache\Storage\StorageInterface;
19 use DateInterval;
20 use DateTime;
21 use JchOptimize\Container\ContainerFactory;
22
23 use function apache_request_headers;
24 use function array_intersect;
25 use function array_keys;
26 use function array_map;
27 use function defined;
28 use function explode;
29 use function function_exists;
30 use function gzencode;
31 use function header;
32 use function is_null;
33 use function preg_match;
34 use function strtolower;
35 use function strtotime;
36
37 defined('_JCH_EXEC') or die('Restricted access');
38
39 class Output
40 {
41 /**
42 * @param array $vars
43 * @param bool $bSend True to output to browser otherwise return result
44 *
45 * @return bool|string|string[]|void
46 */
47 public static function getCombinedFile(array $vars = [], bool $bSend = true)
48 {
49 $container = ContainerFactory::getInstance();
50
51 /** @var StorageInterface $cache */
52 $cache = $container->get(StorageInterface::class);
53
54 $input = new Input();
55
56 if (empty($vars)) {
57 $vars = [
58 'f' => $input->getBase64('f'),
59 'type' => $input->getWord('type'),
60 'gz' => $input->getWord('gz', 'nz')
61 ];
62 }
63
64 try {
65 //Temporarily set lifetime to 0 and fetch cache
66 $lifetime = $cache->getOptions()->getTtl();
67 $cache->getOptions()->setTtl(0);
68
69 /** @var CacheObject[] $resultsObj */
70 $resultsObj = $cache->getItem($vars['f']);
71
72 $cache->getOptions()->setTtl($lifetime);
73 } catch (ExceptionInterface $e) {
74 $resultsObj = null;
75 }
76
77 if (is_null($resultsObj)) {
78 if ($bSend) {
79 header("HTTP/1.0 404 Not Found");
80
81 echo 'File not found';
82 }
83
84 return false;
85 }
86
87 $file = $resultsObj[0]->getContents();
88
89 //Return file if we're not outputting to browser
90 if (!$bSend) {
91 return $file;
92 }
93
94 $aTimeMFile = self::RFC1123DateAdd($resultsObj[0]->getFilemtime(), '1 year');
95
96 $timeMFile = $aTimeMFile['filemtime'] . ' GMT';
97 $expiryDate = $aTimeMFile['expiry'] . ' GMT';
98
99 $modifiedSinceTime = '';
100 $noneMatch = '';
101
102 if (function_exists('apache_request_headers')) {
103 $headers = apache_request_headers();
104
105 if (isset($headers['If-Modified-Since'])) {
106 $modifiedSinceTime = strtotime($headers['If-Modified-Since']);
107 }
108
109 if (isset($headers['If-None-Match'])) {
110 $noneMatch = $headers['If-None-Match'];
111 }
112 }
113
114 if ($modifiedSinceTime == '' && !is_null($input->server->getString('HTTP_IF_MODIFIED_SINCE'))) {
115 $modifiedSinceTime = strtotime($input->server->getString('HTTP_IF_MODIFIED_SINCE'));
116 }
117
118 if ($noneMatch == '' && !is_null($input->server->getString('HTTP_IF_NONE_MATCH'))) {
119 $noneMatch = $input->server->getString('HTTP_IF_NONE_MATCH');
120 }
121
122 $etag = $resultsObj[0]->getEtag();
123
124 if ($modifiedSinceTime == strtotime($timeMFile) || trim($noneMatch) == $etag) {
125 // Client's cache IS current, so we just respond '304 Not Modified'.
126 header('HTTP/1.1 304 Not Modified');
127 header('Content-Length: 0');
128
129 return;
130 } else {
131 header('Last-Modified: ' . $timeMFile);
132 }
133
134
135 if ($vars['type'] == 'css') {
136 header('Content-type: text/css');
137 } elseif ($vars['type'] == 'js') {
138 header('Content-type: text/javascript');
139 }
140
141 header('Expires: ' . $expiryDate);
142 header('Accept-Ranges: bytes');
143 header('Cache-Control: Public');
144 header('Vary: Accept-Encoding');
145 header('Etag: ' . $etag);
146
147 $gzip = true;
148
149 if (!is_null($input->server->getString('HTTP_USER_AGENT'))) {
150 /* Facebook User Agent
151 * facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)
152 * LinkedIn User Agent
153 * LinkedInBot/1.0 (compatible; Mozilla/5.0; Jakarta Commons-HttpClient/3.1 +http://www.linkedin.com)
154 */
155 $pattern = strtolower('/facebookexternalhit|LinkedInBot/x');
156
157 if (preg_match($pattern, strtolower($input->server->getString('HTTP_USER_AGENT')))) {
158 $gzip = false;
159 }
160 }
161
162 if (isset($vars['gz']) && $vars['gz'] == 'gz' && $gzip) {
163 $supported = [
164 'x-gzip' => 'gz',
165 'gzip' => 'gz',
166 'deflate' => 'deflate'
167 ];
168
169 if (!is_null($input->server->getString('HTTP_ACCEPT_ENCODING'))) {
170 $aAccepted = array_map('trim', (array)explode(',', $input->server->getString('HTTP_ACCEPT_ENCODING')));
171 $encodings = array_intersect($aAccepted, array_keys($supported));
172 } else {
173 $encodings = ['gzip'];
174 }
175
176 if (!empty($encodings)) {
177 foreach ($encodings as $encoding) {
178 if (($supported[$encoding] == 'gz') || ($supported[$encoding] == 'deflate')) {
179 $compressedFile = gzencode(
180 $file,
181 4,
182 ($supported[$encoding] == 'gz') ? FORCE_GZIP : FORCE_DEFLATE
183 );
184
185 if ($compressedFile === false) {
186 continue;
187 }
188
189 header('Content-Encoding: ' . $encoding);
190
191 $file = $compressedFile;
192
193 break;
194 }
195 }
196 }
197 }
198
199 echo $file;
200 }
201
202 /**
203 * @param int $fileMTime
204 * @param string $period
205 *
206 * @return array
207 */
208 public static function RFC1123DateAdd(int $fileMTime, string $period): array
209 {
210 $times = [];
211
212 $date = new DateTime();
213 $date->setTimestamp($fileMTime);
214
215 $times['filemtime'] = $date->format('D, d M Y H:i:s');
216
217 $date->add(DateInterval::createFromDateString($period));
218 $times['expiry'] = $date->format('D, d M Y H:i:s');
219
220 return $times;
221 }
222 }
223