Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-kuendig committed Nov 28, 2018
2 parents cb188e2 + 0944e0a commit a1a472b
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Plugin.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php namespace OFFLINE\ResponsiveImages;

use OFFLINE\ResponsiveImages\Classes\ImagePreloader;
use OFFLINE\ResponsiveImages\Console\GenerateResizedImages;
use System\Classes\PluginBase;

/**
Expand Down Expand Up @@ -69,4 +71,8 @@ public function registerSettings()
],
];
}

public function register(){
$this->registerConsoleCommand('responsiveimages:generate', GenerateResizedImages::class);
}
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ If you want to add a class to every processed image you can configure this via t

This is useful if you want to add Bootstrap's `img-responsive` class to all images on your website.

## Pre-generate images

You can use the `php artisan responsive-images:generate` command to pre-generate responsive images. The command uses
October's `pages.menuitem.*` events to build a list of all available URLs and pre-generates all images used on these
pages.

## Test results

I have tested this plugin on a page with 20 hd wallpapers from pixabay.
Expand Down
229 changes: 229 additions & 0 deletions classes/ImagePreloader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<?php

namespace OFFLINE\ResponsiveImages\Classes;


use Backend\Facades\Backend;
use Cms\Classes\Controller;
use Cms\Classes\Theme;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Event;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;

class ImagePreloader
{
/**
* The code of the theme to use.
* @var string
*/
protected $theme;
/**
* Generated URLs.
* @var Collection
*/
protected $urls;
/**
* The base URL of this installation.
* @var string
*/
protected $baseUrl;

public function __construct($theme, OutputInterface $output = null)
{
$this->baseUrl = Backend::baseUrl();
$this->theme = Theme::load($theme);
$this->urls = collect();
$this->output = $output ?? new NullOutput();
}

public function preload()
{
$definitions = $this->getTypeOptions()->mapWithKeys(function ($name, $type) {
return [$type => $this->getTypeInfo($type)];
});

$urls = $this->getPageUrls($definitions);


foreach ($urls->sort() as $url) {
$urlParts = parse_url($url);
$path = data_get($urlParts, 'path', '/');
$query = array_key_exists('query', $urlParts) ? '?' . $urlParts['query'] : '';
$fragment = array_key_exists('fragment', $urlParts) ? '#' . $urlParts['fragment'] : '';

$relative = implode('', [$path, $query, $fragment]);

try {
$response = App::make(Controller::class)->run($relative);

$content = $response->getContent();
if(empty($content)){
$this->output->writeln("Skipping empty page: \t$relative");
continue;
}

$this->output->writeln("Processing page: \t$relative");

(new ResponsiveImageService($content))->process();

} catch (\Exception $e) {
$this->output->writeln("Failed to process page: \t$relative \t".$e->getMessage());
}
}
}

/**
* Returns a list of registered item types.
*
* @return Collection
*/
public function getTypeOptions(): Collection
{
$result = collect();
$apiResult = Event::fire('pages.menuitem.listTypes');

if ( ! \is_array($apiResult)) {
return $result;
}

foreach ($apiResult as $typeList) {
if ( ! \is_array($typeList)) {
continue;
}

foreach ($typeList as $typeCode => $typeName) {
$result->put($typeCode, $typeName);
}
}

return $result;
}

/**
* Get all available info for a certain item type.
*
* @param $type
*
* @return array
*/
public function getTypeInfo($type): array
{
$result = [];
$apiResult = Event::fire('pages.menuitem.getTypeInfo', [$type]);

if ( ! \is_array($apiResult)) {
return $result;
}

foreach ($apiResult as $typeInfo) {
if ( ! is_array($typeInfo)) {
continue;
}

foreach ($typeInfo as $name => $value) {
if ($name !== 'cmsPages') {
$result[$name] = $value;
continue;
}

// CMS pages are special
$cmsPages = [];
foreach ($value as $page) {
$baseName = $page->getBaseFileName();
$pos = strrpos($baseName, '/');

$dir = $pos !== false ? substr($baseName, 0, $pos) . ' / ' : null;
$cmsPages[$baseName] = $page->title !== '' ? $dir . $page->title : $baseName;
}

$result[$name] = $cmsPages;
}
}

return $result;

}

/**
* Generate the page URLs for every definition.
*
* @param Collection $definitions
*
* @return Collection
*/
public function getPageUrls(Collection $definitions): Collection
{
return $definitions->flatMap(function ($definition, $type) {
$urls = new Collection();
$references = data_get($definition, 'references', []);
foreach ($references as $reference => $name) {
if ( ! isset($definition['cmsPages'])) {
$item = (object)[
'type' => $type,
'reference' => $reference,
'nesting' => data_get($definition, 'nesting', false),
];
$urls = $urls->concat($this->getUrlsForItem($type, $item));

continue;
}

foreach ($definition['cmsPages'] as $cmsPage => $name) {
$item = (object)[
'type' => $type,
'reference' => $reference,
'cmsPage' => $cmsPage,
'nesting' => data_get($definition, 'nesting', false),
];
$urls = $urls->concat($this->getUrlsForItem($type, $item));
}
}

return $urls;
});
}

/**
* Get all URLs for a single menu item.
*
* @param $type
* @param $item
*
* @return array
*/
public function getUrlsForItem($type, $item): array
{
$urls = [];
$apiResult = Event::fire('pages.menuitem.resolveItem', [$type, $item, $this->baseUrl, $this->theme]);

if ( ! \is_array($apiResult)) {
return [];
}

foreach ($apiResult as $itemInfo) {
if (isset($itemInfo['url'])) {
$urls[] = $itemInfo['url'];
}

if (isset($itemInfo['items'])) {
$itemIterator = function ($items) use (&$itemIterator, &$urls) {
foreach ($items as $item) {
if (isset($item['url'])) {
$urls[] = $item['url'];
}

if (isset($item['items'])) {
$itemIterator($item['items']);
}
}
};

$itemIterator($itemInfo['items']);
}
}

return $urls;
}
}
32 changes: 32 additions & 0 deletions console/GenerateResizedImages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace OFFLINE\ResponsiveImages\Console;


use Cms\Classes\Theme;
use Illuminate\Console\Command;
use OFFLINE\ResponsiveImages\Classes\ImagePreloader;
use Symfony\Component\Console\Input\InputArgument;

class GenerateResizedImages extends Command
{
protected $name = 'responsive-images:generate';
protected $description = 'Tries to resize all used images.';

protected function getArguments()
{
return [
['theme', InputArgument::OPTIONAL, 'The theme to use.'],
];
}

public function handle()
{
$theme = $this->argument('theme');
if ( ! $theme) {
$theme = Theme::getActiveThemeCode();
}

(new ImagePreloader($theme, $this->getOutput()))->preload();
}
}
1 change: 1 addition & 0 deletions updates/version.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
1.1.7: Optimized support for multi-byte character strings (thanks to sergei3456)
1.1.8: Optimized support for installations that serve multiple domains
1.1.9: Fixed resizing of image paths that contain spaces (thanks to adamo)
1.2.0: Added `responsive-images:generate` artisan command to pre-generate all image sizes (thanks to kiselli)

0 comments on commit a1a472b

Please sign in to comment.