class BackgroundImageCssController in Background Image 2.0.x
Same name and namespace in other branches
- 8 src/Controller/BackgroundImageCssController.php \Drupal\background_image\Controller\BackgroundImageCssController
- 2.x src/Controller/BackgroundImageCssController.php \Drupal\background_image\Controller\BackgroundImageCssController
Defines a controller to serve image styles.
Hierarchy
- class \Drupal\Core\Controller\ControllerBase implements ContainerInjectionInterface uses LoggerChannelTrait, MessengerTrait, RedirectDestinationTrait, StringTranslationTrait
- class \Drupal\system\FileDownloadController
- class \Drupal\background_image\Controller\BackgroundImageCssController
- class \Drupal\system\FileDownloadController
Expanded class hierarchy of BackgroundImageCssController
1 file declares its use of BackgroundImageCssController
File
- src/
Controller/ BackgroundImageCssController.php, line 26
Namespace
Drupal\background_image\ControllerView source
class BackgroundImageCssController extends FileDownloadController {
/**
* @var \Drupal\background_image\BackgroundImageManagerInterface
*/
protected $backgroundImageManager;
/**
* @var \Drupal\breakpoint\BreakpointManagerInterface
*/
protected $breakpointManager;
/**
* A cache array of CSS template filenames, keyed by theme name.
*
* @var array
*/
protected $cssTemplates;
/**
* The File System service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* Flag indicating whether to gzip files.
*
* @var bool
*/
protected $gzip = FALSE;
/**
* @var \Drupal\Core\Image\ImageFactory
*/
protected $imageFactory;
/**
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* A logger instance.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* @var \Twig_Environment
*/
protected $twig;
/**
* Constructs a BackgroundImageCssController object.
*
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
* The Stream Wrapper Manager service.
* @param \Drupal\background_image\BackgroundImageManagerInterface $background_image_manager
* The Background Image Manager service.
* @param \Drupal\Core\File\FileSystemInterface $fileSystem
* The File System service.
* @param \Drupal\Core\Image\ImageFactory $image_factory
* The Image Factory service.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The Lock service.
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The Theme Manager service.
* @param \Twig_Environment $twig
* The Twig service.
*/
public function __construct(StreamWrapperManagerInterface $streamWrapperManager, BackgroundImageManagerInterface $background_image_manager, FileSystemInterface $fileSystem, ImageFactory $image_factory, LockBackendInterface $lock, ThemeManagerInterface $theme_manager, \Twig_Environment $twig) {
parent::__construct($streamWrapperManager);
$this->backgroundImageManager = $background_image_manager;
$this->fileSystem = $fileSystem;
$this->gzip = extension_loaded('zlib') && \Drupal::config('system.performance')
->get('css.gzip');
$this->imageFactory = $image_factory;
$this->lock = $lock;
$this->logger = $this
->getLogger('background_image');
$this->themeManager = $theme_manager;
$this->twig = $twig;
if ($this
->moduleHandler()
->moduleExists('responsive_image')) {
$this->breakpointManager = \Drupal::service('breakpoint.manager');
}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container
->get('stream_wrapper_manager'), $container
->get('background_image.manager'), $container
->get('file_system'), $container
->get('image.factory'), $container
->get('lock'), $container
->get('theme.manager'), $container
->get('twig'));
}
/**
* Generates the necessary CSS for a background image.
*
* @param \Drupal\background_image\BackgroundImageInterface $background_image
* A background image entity.
* @param string $uri
* The URI path on where to store the generated CSS.
*
* @return bool
* TRUE or FALSE
*/
public function buildCss(BackgroundImageInterface $background_image, $uri) {
// Immediately return if there is is no image file.
if (!$background_image
->getImageFile()) {
$this->logger
->error('Background image does not have a valid image file: background_image:@id', [
'@id' => $background_image
->id(),
]);
return FALSE;
}
// Build the destination folder tree if it doesn't already exist.
$directory = $this->fileSystem
->dirname($uri);
if (!$this->fileSystem
->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
$this->logger
->error('Failed to create background image directory: %directory', [
'%directory' => $directory,
]);
return FALSE;
}
$variables = [
'base_class' => $this->backgroundImageManager
->getBaseClass(),
'background_image_class' => $background_image
->getCssClass(),
'settings' => $background_image
->getSettings()
->get(),
'preload_url' => $background_image
->getImageUrl($this->backgroundImageManager
->getPreloadImageStyle()),
'fallback_url' => $background_image
->getImageUrl($this->backgroundImageManager
->getFallbackImageStyle()),
'media_queries' => $this
->buildMediaQueries($background_image),
];
$cssTemplate = $this
->getCssTemplate();
$this
->moduleHandler()
->alter('background_image_css_template', $variables, $cssTemplate, $background_image);
$this->themeManager
->alter('background_image_css_template', $variables, $cssTemplate, $background_image);
// Render the template.
try {
$data = $this->twig
->loadTemplate($cssTemplate)
->render($variables);
// Minify the CSS if necessary.
if (preg_match('/\\.min\\.css$/', $uri) && ($css_minifier = $this->backgroundImageManager
->getCssMinifier())) {
$data = $css_minifier
->optimize($data, [], []);
$css_minifier
->addLicense($data, preg_replace('/\\.min\\.css$/', '.css', file_create_url($uri)));
}
if (!$this
->dump($data, $uri)) {
return FALSE;
}
} catch (\Exception $e) {
$previous_exception = $e
->getPrevious();
$this->logger
->error($previous_exception ? $previous_exception
->getMessage() : $e
->getMessage());
return FALSE;
}
return TRUE;
}
/**
* Build a list of media queries.
*
* @param \Drupal\background_image\BackgroundImageInterface $background_image
* The background image entity.
*
* @return array
* An sorted indexed array of associative arrays.
*/
protected function buildMediaQueries(BackgroundImageInterface $background_image) {
$responsive_image_style = $this->backgroundImageManager
->getResponsiveImageStyle();
// Immediately return if there is no responsive image style.
if (!$this->breakpointManager || !$responsive_image_style) {
return [];
}
$mediaQueries = [];
// Get the necessary variables.
$breakpoints = $this->breakpointManager
->getBreakpointsByGroup($responsive_image_style
->getBreakpointGroup());
$keyed_image_style_mappings = $responsive_image_style
->getKeyedImageStyleMappings();
$retinaRules = $this->backgroundImageManager
->getRetinaRules();
// Retrieve the responsive image sources.
$i = 0;
foreach ($breakpoints as $breakpoint_id => $breakpoint) {
if (isset($keyed_image_style_mappings[$breakpoint_id])) {
$mediaQuery = trim($breakpoint
->getMediaQuery());
foreach ($keyed_image_style_mappings[$breakpoint_id] as $multiplier => $image_style_mapping) {
if ($image_style_mapping['image_mapping_type'] !== 'image_style') {
continue;
}
// Use multiplier as a key so it can be sorted in the array later.
$key = intval(mb_substr($multiplier, 0, -1) * 100) + $i++;
$image_style = $image_style_mapping['image_mapping'];
// Merge the multiplier with retina rules.
if ($multiplier === "2x") {
$rules = [];
foreach ($retinaRules as $retinaRule) {
$rules[] = trim($retinaRule) . ' and ' . trim(preg_replace('/^\\s*(only )?(all|print|screen)\\s?(and)?/', '', $mediaQuery));
}
$mediaQueries[$key] = [
'image_style' => $image_style,
'multiplier' => $multiplier,
'query' => implode(',', $rules),
'url' => $background_image
->getImageUrl($image_style),
];
}
else {
$mediaQueries[$key] = [
'image_style' => $image_style,
'multiplier' => $multiplier,
'query' => $mediaQuery,
'url' => $background_image
->getImageUrl($image_style),
];
}
}
}
}
// Sort the the media queries so the multipliers are aft
ksort($mediaQueries);
return $mediaQueries;
}
/**
* {@inheritdoc}
*/
protected function dump($data, $uri) {
// Save the file.
$this->fileSystem
->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
try {
$this->fileSystem
->saveData($data, $uri, FileSystemInterface::EXISTS_REPLACE);
// Create gzipped file.
if ($this->gzip) {
$this->fileSystem
->saveData(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FileSystemInterface::EXISTS_REPLACE);
}
} catch (FileWriteException $e) {
$this->logger
->error($this
->t('Unable to create save the CSS file: @uri', [
'@uri' => $uri,
]));
return FALSE;
} catch (FileException $e) {
return FALSE;
}
return $uri;
}
/**
* Duplication of _responsive_image_image_style_url().
*
* This is needed so this module does not have to provide a hard dependency
* on the responsive_image module.
*
* @param string $style_name
* The style name to use.
* @param string $path
* The path of the image file.
*
* @return string
* The image style URL.
*
* @deprecated in 8.x-1.4, will be removed in 8.x-2.0. Use
* \Drupal\background_image\BackgroundImageInterface::getImageUrl() instead.
*/
public static function imageStyleUrl($style_name, $path) {
if ($style_name == '_empty image_') {
// The smallest data URI for a 1px square transparent GIF image.
// http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
return 'data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
}
$entity = ImageStyle::load($style_name);
if ($entity instanceof ImageStyle) {
return file_url_transform_relative($entity
->buildUrl($path));
}
return file_url_transform_relative(file_create_url($path));
}
/**
* Generates a background CSS file.
*
* After generating an image, transfer it to the requesting agent.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Drupal\background_image\BackgroundImageInterface $background_image
* The background image entity.
* @param string $scheme
* The file scheme, defaults to 'private'.
* @param string $file
* The file name to generate.
*
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response
* The transferred file as response or some error response.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* Thrown when the user does not have access to the file.
* @throws \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException
* Thrown when the file is still being generated.
*/
public function deliver(Request $request, BackgroundImageInterface $background_image, $scheme, $file = NULL) {
// Ensure this is a valid background image, scheme and user has permission.
$valid = !empty($background_image) && $this->streamWrapperManager
->isValidScheme($scheme) && $background_image
->access('view');
if (!$valid) {
throw new AccessDeniedHttpException();
}
$uri = "{$scheme}://background_image/css/{$background_image->id()}/{$scheme}/{$file}";
// If using the private scheme, let other modules provide headers and
// control access to the file.
$headers = [];
if ($scheme == 'private') {
$headers = $this
->moduleHandler()
->invokeAll('file_download', [
$uri,
]);
if (in_array(-1, $headers) || empty($headers)) {
throw new AccessDeniedHttpException();
}
}
// Don't start generating the image if the derivative already exists or if
// generation is in progress in another thread.
if (!file_exists($uri)) {
$lock_name = 'background_image_css_deliver:' . $background_image
->id() . ':' . $background_image
->getImageHash();
$lock_acquired = $this->lock
->acquire($lock_name);
if (!$lock_acquired) {
// Tell client to retry again in 3 seconds. Currently no browsers are
// known to support Retry-After.
throw new ServiceUnavailableHttpException(3, $this
->t('Background Image CSS generation in progress. Try again shortly.'));
}
}
// Try to generate the image, unless another thread just did it while we
// were acquiring the lock.
$success = file_exists($uri) || $this
->buildCss($background_image, $uri);
if (!empty($lock_acquired)) {
$this->lock
->release($lock_name);
}
if ($success) {
$headers += [
'Content-Type' => 'text/css',
'Content-Length' => filesize($uri),
];
// \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond()
// sets response as not cacheable if the Cache-Control header is not
// already modified. We pass in FALSE for non-private schemes for the
// $public parameter to make sure we don't change the headers.
return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');
}
else {
$this->logger
->notice('Unable to generate the background image CSS located at %path.', [
'%path' => $uri,
]);
return new Response($this
->t('Error generating image.'), 500);
}
}
/**
* Retrieves the Twig template filename used to generate the necessary CSS.
*
* @return string
*/
protected function getCssTemplate() {
if (!isset($this->cssTemplates)) {
$cache = \Drupal::cache()
->get('background_image_css_templates');
$this->cssTemplates = $cache && is_array($cache->data) ? $cache->data : [];
}
$activeTheme = $this->themeManager
->getActiveTheme();
$activeThemeName = $activeTheme
->getName();
if (!isset($this->cssTemplates[$activeThemeName])) {
// Search for a theme based template file.
$templatePaths = [
$activeTheme
->getPath() . '/templates',
];
foreach ($activeTheme
->getBaseThemeExtensions() as $name => $extension) {
$templatePaths[$name] = $extension
->getPath() . '/templates';
}
$mask = '/' . preg_quote('background_image.css.twig', '/') . '$/';
foreach (array_unique($templatePaths) as $path) {
try {
if (is_dir($path) && ($file = current($this->fileSystem
->scanDirectory($path, $mask)))) {
$cssTemplate = str_replace(\Drupal::root() . '/', '', $file->uri);
break;
}
} catch (FileException $e) {
// Intentionally ignored, do nothing.
}
}
// Default to this module's template if none was found.
if (!isset($cssTemplate)) {
$cssTemplate = drupal_get_path('module', 'background_image') . '/templates/background_image.css.twig';
}
$this->cssTemplates[$activeThemeName] = $cssTemplate;
\Drupal::cache()
->set('background_image_css_templates', $this->cssTemplates);
}
return $this->cssTemplates[$activeThemeName];
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
BackgroundImageCssController:: |
protected | property | ||
BackgroundImageCssController:: |
protected | property | ||
BackgroundImageCssController:: |
protected | property | A cache array of CSS template filenames, keyed by theme name. | |
BackgroundImageCssController:: |
protected | property | The File System service. | |
BackgroundImageCssController:: |
protected | property | Flag indicating whether to gzip files. | |
BackgroundImageCssController:: |
protected | property | ||
BackgroundImageCssController:: |
protected | property | ||
BackgroundImageCssController:: |
protected | property | A logger instance. | |
BackgroundImageCssController:: |
protected | property | ||
BackgroundImageCssController:: |
protected | property | ||
BackgroundImageCssController:: |
public | function | Generates the necessary CSS for a background image. | |
BackgroundImageCssController:: |
protected | function | Build a list of media queries. | |
BackgroundImageCssController:: |
public static | function |
Instantiates a new instance of this class. Overrides FileDownloadController:: |
|
BackgroundImageCssController:: |
public | function | Generates a background CSS file. | |
BackgroundImageCssController:: |
protected | function | ||
BackgroundImageCssController:: |
protected | function | Retrieves the Twig template filename used to generate the necessary CSS. | |
BackgroundImageCssController:: |
public static | function | Duplication of _responsive_image_image_style_url(). | |
BackgroundImageCssController:: |
public | function |
Constructs a BackgroundImageCssController object. Overrides FileDownloadController:: |
|
ControllerBase:: |
protected | property | The configuration factory. | |
ControllerBase:: |
protected | property | The current user service. | 1 |
ControllerBase:: |
protected | property | The entity form builder. | |
ControllerBase:: |
protected | property | The entity type manager. | |
ControllerBase:: |
protected | property | The form builder. | 2 |
ControllerBase:: |
protected | property | The key-value storage. | 1 |
ControllerBase:: |
protected | property | The language manager. | 1 |
ControllerBase:: |
protected | property | The module handler. | 2 |
ControllerBase:: |
protected | property | The state service. | |
ControllerBase:: |
protected | function | Returns the requested cache bin. | |
ControllerBase:: |
protected | function | Retrieves a configuration object. | |
ControllerBase:: |
private | function | Returns the service container. | |
ControllerBase:: |
protected | function | Returns the current user. | 1 |
ControllerBase:: |
protected | function | Retrieves the entity form builder. | |
ControllerBase:: |
protected | function | Retrieves the entity type manager. | |
ControllerBase:: |
protected | function | Returns the form builder service. | 2 |
ControllerBase:: |
protected | function | Returns a key/value storage collection. | 1 |
ControllerBase:: |
protected | function | Returns the language manager service. | 1 |
ControllerBase:: |
protected | function | Returns the module handler. | 2 |
ControllerBase:: |
protected | function | Returns a redirect response object for the specified route. | |
ControllerBase:: |
protected | function | Returns the state storage service. | |
FileDownloadController:: |
protected | property | The stream wrapper manager. | |
FileDownloadController:: |
public | function | Handles private file transfers. | |
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 27 |
MessengerTrait:: |
public | function | Gets the messenger. | 27 |
MessengerTrait:: |
public | function | Sets the messenger. | |
RedirectDestinationTrait:: |
protected | property | The redirect destination service. | 1 |
RedirectDestinationTrait:: |
protected | function | Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url. | |
RedirectDestinationTrait:: |
protected | function | Returns the redirect destination service. | |
RedirectDestinationTrait:: |
public | function | Sets the redirect destination service. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 4 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |