flysystem_drupal_cache-8.x-1.0-alpha3/src/StackMiddleware/Flysystem.php
src/StackMiddleware/Flysystem.php
<?php
namespace Drupal\flysystem_drupal_cache\StackMiddleware;
use DateTime;
use Drupal\Component\Datetime\DateTimePlus;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\CacheFactoryInterface;
use Drupal\Core\File\FileSystem;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Drupal\flysystem\FlysystemFactory;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Mime\MimeTypeGuesserInterface;
/**
* Executes the page caching before the main kernel takes over the request.
*/
class Flysystem implements HttpKernelInterface {
/**
* The wrapped HTTP kernel.
*
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
protected $httpKernel;
/**
* @var \Drupal\Core\Cache\CacheFactoryInterface
*/
protected $cacheFactory;
/**
* A policy rule determining the cacheability of a request.
*
* @var \Drupal\Core\PageCache\RequestPolicyInterface
*/
protected $requestPolicy;
/**
* Mime Type Guesser.
*
* @var \Symfony\Component\Mime\MimeTypeGuesserInterface
*/
protected $mimeTypeGuesser;
/**
* Flysystem Factory.
*
* @var \Drupal\flysystem\FlysystemFactory
*/
protected $flysystemFactory;
/**
* Filesystem.
*
* @var \Drupal\Core\File\FileSystem
*/
protected $filesystem;
/**
* Constructs a PageCache object.
*
* @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
* The decorated kernel.
* @param \Drupal\Core\Cache\CacheFactoryInterface $cacheFactory
* The cache bin.
* @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy
* A policy rule determining the cacheability of a request.
* @param \Symfony\Component\Mime\MimeTypeGuesserInterface $mimeTypeGuesser
* Mime Type Guesser.
* @param \Drupal\flysystem\FlysystemFactory $flysystemFactory
* Flysystem Factory.
* @param \Drupal\Core\File\FileSystem $filesystem
* Filesystem.
*/
public function __construct(HttpKernelInterface $http_kernel, CacheFactoryInterface $cacheFactory, RequestPolicyInterface $request_policy, MimeTypeGuesserInterface $mimeTypeGuesser, FlysystemFactory $flysystemFactory, FileSystem $filesystem) {
$this->httpKernel = $http_kernel;
$this->cacheFactory = $cacheFactory;
$this->requestPolicy = $request_policy;
$this->mimeTypeGuesser = $mimeTypeGuesser;
$this->flysystemFactory = $flysystemFactory;
$this->filesystem = $filesystem;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true): Response {
// Only allow page caching on master request.
if ($type !== static::MAIN_REQUEST || $this->requestPolicy->check($request) !== RequestPolicyInterface::ALLOW) {
return $this->pass($request, $type, $catch);
}
// Stream wrapper protocols must conform to /^[a-zA-Z0-9+.-]+$/
// Via php_stream_wrapper_scheme_validate() in the PHP source.
if (!preg_match('|^/_flysystem/([a-zA-Z0-9+.-]+)/|', $request->getRequestUri(), $matches)) {
return $this->pass($request, $type, $catch);
}
if (empty($matches[1]) || !in_array($matches[1], $this->flysystemFactory->getSchemes(), TRUE)) {
return $this->pass($request, $type, $catch);
}
$scheme = $matches[1];
$settings = $this->flysystemFactory->getSettings($scheme);
if ($settings['driver'] !== 'drupal_cache') {
return $this->pass($request, $type, $catch);
}
$cid = substr($request->getRequestUri(), strlen($matches[0]));
$cid = UrlHelper::parse($cid)['path'];
try {
return $this->getResponse($request, $scheme, $cid);
}
catch (NotFoundHttpException $exception) {
return $this->pass($request, $type, $catch);
}
}
/**
* Sidesteps the page cache and directly forwards a request to the backend.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* A request object.
* @param int $type
* The type of the request (one of HttpKernelInterface::MAIN_REQUEST or
* HttpKernelInterface::SUB_REQUEST)
* @param bool $catch
* Whether to catch exceptions or not
*
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Exception
*/
protected function pass(Request $request, $type = self::MAIN_REQUEST, $catch = TRUE): Response {
return $this->httpKernel->handle($request, $type, $catch);
}
/**
* Get the response object.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* @param string $scheme
* The scheme for the file.
* @param string $cid
* The cid/path of the file.
*
* @return \Symfony\Component\HttpFoundation\Response
* @throws \Exception
*/
private function getResponse(Request $request, $scheme, $cid): Response {
$adapter = $this->flysystemFactory->getPlugin($scheme)->getAdapter();
$data = $adapter->read($cid);
$metadata = $adapter->getMetadata($cid);
$uri = $scheme . '://' . $cid;
$response = new Response();
$response->headers->set('Content-type', $metadata['mimetype'] );
$response->headers->set('Content-length', strlen($data['contents']));
$response->setLastModified(new DateTime(gmdate(DateTimePlus::RFC7231, (int) $metadata['timestamp'])));
$date = new DateTime(gmdate(DateTimePlus::RFC7231, (int) $metadata['timestamp']));
$response->setExpires($date->modify('+1 year'));
$response->setPublic();
$response->setMaxAge(31556926);
$response->setEtag(md5($data['contents']), TRUE);
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, pathinfo($uri, PATHINFO_BASENAME));
$response->headers->set('Content-Disposition', $disposition);
$response->setContent($data['contents']);
$response->isNotModified($request);
return $response;
}
}
