progressive_image_loading-8.x-1.x-dev/src/ProgressiveImageLoadingManager.php
src/ProgressiveImageLoadingManager.php
<?php
namespace Drupal\progressive_image_loading;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Path\AliasManagerInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PrivateStream;
use Drupal\Core\StreamWrapper\PublicStream;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* The Progressive Image Loading manager service.
*/
class ProgressiveImageLoadingManager implements ProgressiveImageLoadingManagerInterface {
/**
* An alias manager to find the alias for the current system path.
*
* @var \Drupal\Core\Path\AliasManagerInterface
*/
protected $aliasManager;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The current path.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* The file system.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The image style storage.
*
* @var \Drupal\image\ImageStyleStorageInterface
*/
protected $imageStyleStorage;
/**
* The path matcher.
*
* @var \Drupal\Core\Path\PathMatcherInterface
*/
protected $pathMatcher;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The app root.
*
* @var string
*/
protected $root;
/**
* Constructs a new ProgressiveImageLoadingManager object.
*
* @param string $root
* The app root.
* @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
* An alias manager to find the alias for the current system path.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Path\CurrentPathStack $current_path
* The current path.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
* The path matcher service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function __construct($root, AliasManagerInterface $alias_manager, ConfigFactoryInterface $config_factory, CurrentPathStack $current_path, EntityTypeManagerInterface $entity_type_manager, PathMatcherInterface $path_matcher, RequestStack $request_stack, FileSystemInterface $file_system) {
$this->aliasManager = $alias_manager;
$this->configFactory = $config_factory;
$this->currentPath = $current_path;
$this->fileSystem = $file_system;
$this->imageStyleStorage = $entity_type_manager->getStorage('image_style');
$this->pathMatcher = $path_matcher;
$this->requestStack = $request_stack;
$this->root = $root;
}
/**
* {@inheritdoc}
*/
public function createPlaceholder(string $url, $width = 1, $height = 1) {
try {
// Skip inline images or unsupported extensions.
if (strpos($url, 'data:') === FALSE && $this->isSupportedExtension($url)) {
$original_uri = $this->buildUri($url);
// If is an internal uri and exists.
if (!UrlHelper::isExternal($original_uri) && file_exists($original_uri)) {
$image_style = $this->imageStyleStorage->load($this->configFactory->get('progressive_image_loading.settings')->get('low_quality_image_style'));
$derivative_uri = $image_style->buildUri($original_uri);
// Make sure image style exists.
if (file_exists($derivative_uri) ||
(!file_exists($derivative_uri) && $image_style->createDerivative($original_uri, $derivative_uri))) {
// Returns base64 encoded image or URL.
if ($this->configFactory->get('progressive_image_loading.settings')->get('inline_image')) {
$mime = getimagesize($derivative_uri)['mime'];
$data = base64_encode(file_get_contents($derivative_uri));
return "data:$mime;base64,$data";
}
return file_url_transform_relative($image_style->buildUrl($original_uri));
}
}
}
elseif (strpos($url, 'data:') !== FALSE) {
return $url;
}
}
catch (\Exception $exception) {
// Nothing to do.
}
// If we can't generate a placeholder, just return base64 encoded
// transparent svg.
return "data:image/svg+xml;charset=utf8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 1 1%22 width=%22$width%22 height=%22$height%22%3E%3C/svg%3E";
}
/**
* {@inheritdoc}
*/
public function currentPathIsExcluded() {
// Convert path to lowercase. This allows comparison of the same path
// with different case. Ex: /Page, /page, /PAGE.
$paths = mb_strtolower($this->configFactory->get('progressive_image_loading.settings')->get('excluded_paths'));
if (empty($paths)) {
return FALSE;
}
$request = $this->requestStack->getCurrentRequest();
// Compare the lowercase path alias (if any) and internal path.
$path = $this->currentPath->getPath($request);
// Do not trim a trailing slash if that is the complete path.
$path = $path === '/' ? $path : rtrim($path, '/');
$path_alias = mb_strtolower($this->aliasManager->getAliasByPath($path));
return ($this->pathMatcher->matchPath($path_alias, $paths) || (($path != $path_alias) && $this->pathMatcher->matchPath($path, $paths)));
}
/**
* Returns the URI from the given image URL, relevant for un-managed files.
*
* @param string $image_url
* The URL to convert into uri.
*
* @return string|false
* True converted uri.
*/
protected function buildUri($image_url) {
// Check if the URL is a valid URI and return it.
if (preg_match('/public:\/\/|private:\/\//', $image_url)) {
return $image_url;
}
// Convert our own external urls into relative urls.
if (UrlHelper::isExternal($image_url) && $this->isInternalDomain($image_url)) {
$image_url = parse_url($image_url)['path'];
}
if (!UrlHelper::isExternal($image_url) && $normal_path = parse_url($image_url)['path']) {
$public_path = Settings::get('file_public_path', PublicStream::basePath());
$private_path = Settings::get('file_private_path', PrivateStream::basePath());
if ($public_path[0] != '/') {
$public_path = '/' . $public_path;
}
if ($private_path[0] != '/') {
$private_path = '/' . $private_path;
}
// Only concerns for the correct URI, not image URL which is already being
// displayed via SRC attribute.
if ($public_path && strpos($normal_path, $public_path) !== FALSE) {
$rel_path = str_replace($public_path, '', $normal_path);
return file_build_uri($rel_path);
}
elseif ($private_path && strpos($normal_path, $private_path) !== FALSE) {
$uri = 'private://' . str_replace($private_path, '', $normal_path);
return file_stream_wrapper_uri_normalize($uri);
}
// We can't generate image styles of theme or module image, so if we
// return a base64 encoded image, we can copy this image into the
// temporary directory and return its uri, otherwise we can't guarantee
// the existence of the image.
elseif ($this->configFactory->get('progressive_image_loading.settings')->get('inline_image')) {
return $this->fileSystem->copy($this->root . $normal_path, 'temporary://');
}
}
return FALSE;
}
/**
* Checks if an URL has an internal domain.
*
* @param string $url
* The URL to check.
*
* @return bool
* True if the URL is internal.
*/
protected function isInternalDomain(string $url) {
$domains_pattern = $this->configFactory->get('progressive_image_loading.settings')->get('internal_paths');
return (bool) empty($domains_pattern) ? FALSE : preg_match($domains_pattern, $url);
}
/**
* Checks if an URL has a valid extension.
*
* @param string $url
* The URL to check.
*
* @return bool
* True if the URL is supported.
*/
protected function isSupportedExtension(string $url) {
$allowed_extension = ['png', 'gif', 'jpg', 'jpeg'];
$url = UrlHelper::parse($url)['path'];
$extension = pathinfo($url, PATHINFO_EXTENSION);
return (bool) empty($extension) ? FALSE : in_array($extension, $allowed_extension);
}
}
