* @copyright Copyright (c) 2022 Samuel Marshall / JCH Optimize * @license GNU/GPLv3, or later. See LICENSE file * * If LICENSE file missing, see . */ namespace JchOptimize\Core; use _JchOptimizeVendor\V91\Joomla\Input\Input; use _JchOptimizeVendor\V91\Laminas\Cache\Exception\ExceptionInterface; use _JchOptimizeVendor\V91\Laminas\Cache\Storage\StorageInterface; use DateInterval; use DateTime; use JchOptimize\Container\ContainerFactory; use function apache_request_headers; use function array_intersect; use function array_keys; use function array_map; use function defined; use function explode; use function function_exists; use function gzencode; use function header; use function is_null; use function preg_match; use function strtolower; use function strtotime; defined('_JCH_EXEC') or die('Restricted access'); class Output { /** * @param array $vars * @param bool $bSend True to output to browser otherwise return result * * @return bool|string|string[]|void */ public static function getCombinedFile(array $vars = [], bool $bSend = true) { $container = ContainerFactory::getInstance(); /** @var StorageInterface $cache */ $cache = $container->get(StorageInterface::class); $input = new Input(); if (empty($vars)) { $vars = [ 'f' => $input->getBase64('f'), 'type' => $input->getWord('type'), 'gz' => $input->getWord('gz', 'nz') ]; } try { //Temporarily set lifetime to 0 and fetch cache $lifetime = $cache->getOptions()->getTtl(); $cache->getOptions()->setTtl(0); /** @var CacheObject[] $resultsObj */ $resultsObj = $cache->getItem($vars['f']); $cache->getOptions()->setTtl($lifetime); } catch (ExceptionInterface $e) { $resultsObj = null; } if (is_null($resultsObj)) { if ($bSend) { header("HTTP/1.0 404 Not Found"); echo 'File not found'; } return false; } $file = $resultsObj[0]->getContents(); //Return file if we're not outputting to browser if (!$bSend) { return $file; } $aTimeMFile = self::RFC1123DateAdd($resultsObj[0]->getFilemtime(), '1 year'); $timeMFile = $aTimeMFile['filemtime'] . ' GMT'; $expiryDate = $aTimeMFile['expiry'] . ' GMT'; $modifiedSinceTime = ''; $noneMatch = ''; if (function_exists('apache_request_headers')) { $headers = apache_request_headers(); if (isset($headers['If-Modified-Since'])) { $modifiedSinceTime = strtotime($headers['If-Modified-Since']); } if (isset($headers['If-None-Match'])) { $noneMatch = $headers['If-None-Match']; } } if ($modifiedSinceTime == '' && !is_null($input->server->getString('HTTP_IF_MODIFIED_SINCE'))) { $modifiedSinceTime = strtotime($input->server->getString('HTTP_IF_MODIFIED_SINCE')); } if ($noneMatch == '' && !is_null($input->server->getString('HTTP_IF_NONE_MATCH'))) { $noneMatch = $input->server->getString('HTTP_IF_NONE_MATCH'); } $etag = $resultsObj[0]->getEtag(); if ($modifiedSinceTime == strtotime($timeMFile) || trim($noneMatch) == $etag) { // Client's cache IS current, so we just respond '304 Not Modified'. header('HTTP/1.1 304 Not Modified'); header('Content-Length: 0'); return; } else { header('Last-Modified: ' . $timeMFile); } if ($vars['type'] == 'css') { header('Content-type: text/css'); } elseif ($vars['type'] == 'js') { header('Content-type: text/javascript'); } header('Expires: ' . $expiryDate); header('Accept-Ranges: bytes'); header('Cache-Control: Public'); header('Vary: Accept-Encoding'); header('Etag: ' . $etag); $gzip = true; if (!is_null($input->server->getString('HTTP_USER_AGENT'))) { /* Facebook User Agent * facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php) * LinkedIn User Agent * LinkedInBot/1.0 (compatible; Mozilla/5.0; Jakarta Commons-HttpClient/3.1 +http://www.linkedin.com) */ $pattern = strtolower('/facebookexternalhit|LinkedInBot/x'); if (preg_match($pattern, strtolower($input->server->getString('HTTP_USER_AGENT')))) { $gzip = false; } } if (isset($vars['gz']) && $vars['gz'] == 'gz' && $gzip) { $supported = [ 'x-gzip' => 'gz', 'gzip' => 'gz', 'deflate' => 'deflate' ]; if (!is_null($input->server->getString('HTTP_ACCEPT_ENCODING'))) { $aAccepted = array_map('trim', (array)explode(',', $input->server->getString('HTTP_ACCEPT_ENCODING'))); $encodings = array_intersect($aAccepted, array_keys($supported)); } else { $encodings = ['gzip']; } if (!empty($encodings)) { foreach ($encodings as $encoding) { if (($supported[$encoding] == 'gz') || ($supported[$encoding] == 'deflate')) { $compressedFile = gzencode( $file, 4, ($supported[$encoding] == 'gz') ? FORCE_GZIP : FORCE_DEFLATE ); if ($compressedFile === false) { continue; } header('Content-Encoding: ' . $encoding); $file = $compressedFile; break; } } } } echo $file; } /** * @param int $fileMTime * @param string $period * * @return array */ public static function RFC1123DateAdd(int $fileMTime, string $period): array { $times = []; $date = new DateTime(); $date->setTimestamp($fileMTime); $times['filemtime'] = $date->format('D, d M Y H:i:s'); $date->add(DateInterval::createFromDateString($period)); $times['expiry'] = $date->format('D, d M Y H:i:s'); return $times; } }