Skip to content

Instantly share code, notes, and snippets.

@fprochazka
Created October 6, 2012 16:22
Show Gist options
  • Save fprochazka/3845361 to your computer and use it in GitHub Desktop.
Save fprochazka/3845361 to your computer and use it in GitHub Desktop.
Image pipe for #nettefw templates
<?php
/**
* @author Filip Procházka <[email protected]>
*/
abstract class BasePresenter extends Nette\Application\UI\Presenter
{
/**
* @var \Img\ImagePipe
*/
private $imgPipe;
/**
* @param \Img\ImagePipe $imgPipe
*/
public function injectImgPipe(\Img\ImagePipe $imgPipe)
{
$this->imgPipe = $imgPipe;
}
/**
* @param string $class
*
* @return Nette\Templating\FileTemplate
*/
protected function createTemplate($class = NULL)
{
$tmp = parent::createTemplate($class);
/** @var \Nette\Templating\FileTemplate|\stdClass $tmp */
$tmp->_imagePipe = $this->imgPipe;
return $tmp;
}
}
nette:
latte:
macros: [Img\ImgMacro]
services:
imageStorage: Img\ImageStorage(%tempDir%)
imagePipe: Img\ImagePipe(%wwwDir%)
<img n:tempImg="$uploadedImg" alt="" /> nebo <img n:img="$uploadedImg" alt="" />
<?php
/**
* @author Filip Procházka <[email protected]>
*/
class FooPresenter extends BasePresenter
{
/**
* @var \Img\ImageStorage
*/
private $imageStorage;
/**
* @param \Img\ImageStorage $storage
*/
public function injectImageStorage(\Img\ImageStorage $storage)
{
$this->imageStorage = $storage;
}
public function renderDefault()
{
$this->template->uploadedImg = new Img\Image(...); // z databáze
}
/**
* @param string $name
* @return \Nette\Application\UI\Form
*/
protected function createComponentUpload($name)
{
$form = new UI\Form;
$form->addUpload('file')
->addRule(UI\Form::IMAGE, 'Obrázek musí být JPEG, PNG nebo GIF.')
->addRule(UI\Form::FILLED, 'Zvolte prosím obrázek.');
$form->addSubmit('upload', 'Upload image')
->setValidationScope(FALSE)
->onClick[] = $this->processUpload;
return $form;
}
/**
* @param \Nette\Forms\Controls\SubmitButton $button
*/
public function processUpload(SubmitButton $button)
{
try {
$image = $this->imageStorage->upload($button->form['file']->value);
} catch (\Exception $e) {
$button->form->addError($e->getMessage());
return;
}
// $this->flashMessage('Huray!');
// $this->redirect('this');
}
}
<?php
namespace Img;
use Nette;
/**
* @author Filip Procházka <[email protected]>
*
* @property-read string $file
* @property \Size $size
* @property-read \Size $size
*/
class Image extends Nette\Object
{
/**
* @var string
*/
private $file;
/**
* @var \Size
*/
private $size;
/**
* @param string $file
*/
public function __construct($file)
{
$this->file = $file;
$this->size = Size::fromFile($file);
}
/**
* @return bool
*/
public function exists()
{
return file_exists($this->file);
}
/**
* @return float|int
*/
public function getFile()
{
return $this->file;
}
/**
* @return \Size
*/
public function getSize()
{
return $this->size;
}
}
<?php
namespace Img;
use Kdyby;
use Nette\Http\Request;
use Nette;
/**
* @author Filip Procházka <[email protected]>
*/
class ImagePipe extends Nette\Object
{
const TEMP = 1;
/**
* @var string
*/
private $assetsDir;
/**
* @var string
*/
private $wwwDir;
/**
* @var string
*/
private $baseUrl;
/**
* @param string $wwwDir
* @param \Nette\Http\Request $httpRequest
* @param string $assetsDir
*
* @throws \Nette\IOException
*/
public function __construct($wwwDir, Request $httpRequest, $assetsDir = 'assets')
{
// public assets dir
$assetsDir = $wwwDir . '/' . $assetsDir;
if (!is_dir($assetsDir) || !is_writable($assetsDir)) {
throw new Nette\IOException("Directory $assetsDir is not writable.");
}
$this->wwwDir = $wwwDir;
$this->assetsDir = $assetsDir;
// for temporary files
if (!is_dir($tempDir = ($this->assetsDir . '/temp'))) {
self::mkdir($tempDir);
}
// base of public url
$this->baseUrl = rtrim($httpRequest->url->baseUrl, '/');
}
/**
* @param string|Image $sourceImage
* @param array $size
* @param int $flags
*
* @return string
* @throws \Nette\IOException
*/
public function request($sourceImage, array $size = array(), $flags = 0)
{
if (!$sourceImage instanceof Image) {
$sourceImage = new Image($sourceImage);
}
$image = NULL;
/** @var Nette\Image $image */
if ($sourceImage->size->width > 500 || $sourceImage->size->height > 500) { // w & h
$image = Nette\Image::fromFile($sourceImage->file)->resize(500, 500);
} // elseif($size) todo: zpracování velikosti
// figure out public directory
$dir = $flags & self::TEMP ? $this->assetsDir . '/temp' : $this->assetsDir;
$dir .= '/' . (implode('x', $size) ? : 'original');
if (!is_dir($dir)) {
self::mkdir($dir);
}
// filename
$targetPath = $dir . '/' . basename($sourceImage->file);
// continue processing only if newer version is available
if (file_exists($targetPath) && filectime($targetPath) < filectime($sourceImage->file)) {
return $this->publicPath($targetPath);
}
// copy source file
if ($image) {
if (!$image->save($targetPath)) {
throw new Nette\IOException("Cannot resize $sourceImage->file to $targetPath");
}
} else {
if (!@copy($sourceImage->file, $targetPath)) {
throw new Nette\IOException("Cannot copy $sourceImage->file to $targetPath");
}
}
return $this->publicPath($targetPath);
}
/**
* @param string $file
* @return string
*/
private function publicPath($file)
{
return $this->baseUrl . str_replace($this->wwwDir, '', $file);
}
/**
* @param string $dir
*
* @throws \Nette\IOException
* @return void
*/
private static function mkdir($dir)
{
$oldMask = umask(0);
@mkdir($dir, 0777);
@chmod($dir, 0777);
umask($oldMask);
if (!is_dir($dir) || !is_writable($dir)) {
throw new Nette\IOException("Please create writable directory $dir.");
}
}
}
<?php
namespace Img;
use Kdyby;
use Nette;
use Nette\Http\FileUpload;
use Nette\Utils\Finder;
use Nette\Utils\Strings;
/**
* @author Filip Procházka <[email protected]>
*/
class ImageStorage extends Nette\Object
{
/**
* @var string
*/
private $imagesDir;
/**
* @param string $dir
*/
public function __construct($dir)
{
$dir .= '/images';
if (!is_dir($dir)) {
umask(0);
mkdir($dir, 0777);
}
$this->imagesDir = $dir;
}
/**
* @param FileUpload $file
* @return Image
* @throws \Nette\InvalidArgumentException
*/
public function upload(FileUpload $file)
{
if (!$file->isOk() || !$file->isImage()) {
throw new Nette\InvalidArgumentException;
}
do {
$name = Strings::random(10) . '.' . $file->getSanitizedName();
} while (file_exists($path = $this->imagesDir . DIRECTORY_SEPARATOR . $name));
$file->move($path);
return new Image($path);
}
/**
* @param string $content
* @param string $filename
* @return Image
*/
public function save($content, $filename)
{
do {
$name = Strings::random(10) . '.' . $filename;
} while (file_exists($path = $this->imagesDir . DIRECTORY_SEPARATOR . $name));
file_put_contents($path, $content);
return new Image($path);
}
/**
* @return string
*/
public function getImagesDir()
{
return $this->imagesDir;
}
/**
* @param $param
* @throws FileNotFoundException
* @return string
*/
public function find($param)
{
foreach (Finder::findFiles($param)->from($this->imagesDir) as $file) {
/** @var \SplFileInfo $file */
return $file->getPathname();
}
throw new FileNotFoundException("File $param not found.");
}
}
/**
* @author Filip Procházka <[email protected]>
*/
class FileNotFoundException extends \RuntimeException
{
}
<?php
namespace Img;
use Kdyby;
use Nette;
use Nette\Latte\PhpWriter;
use Nette\Latte\MacroNode;
use Nette\Latte\Compiler;
/**
* @author Filip Procházka <[email protected]>
*/
class ImgMacro extends Nette\Latte\Macros\MacroSet
{
/**
* @var bool
*/
private $isUsed = FALSE;
/**
* @param \Nette\Latte\Compiler $compiler
*
* @return ImgMacro|\Nette\Latte\Macros\MacroSet
*/
public static function install(Compiler $compiler)
{
$me = new static($compiler);
// todo: předání parametrů s velikostí
/**
* {img} je obecně pro jakýkoliv obrázek, který je veřejný na webu.
*/
$me->addMacro('img', array($me, 'macroImg'), NULL, array($me, 'macroAttrImg'));
/**
* {tempImg} je kvůli obrázkům, které nejsou "schválené" a je pravděpodobné,
* že se budou mazat. Tak aby byly ve zvláštní složce, kterou může promazávat robot.
* Pokud nic takového nepotřebujete, používejte prostě {img}.
*/
$me->addMacro('tempImg', array($me, 'macroImgTemp'), NULL, array($me, 'macroAttrImgTemp'));
return $me;
}
/**
* @param \Nette\Latte\MacroNode $node
* @param \Nette\Latte\PhpWriter $writer
* @return string
*/
public function macroImg(MacroNode $node, PhpWriter $writer)
{
$this->isUsed = TRUE;
return $writer->write('echo %escape($_imagePipe->request(%node.word))');
}
/**
* @param \Nette\Latte\MacroNode $node
* @param \Nette\Latte\PhpWriter $writer
* @return string
*/
public function macroAttrImg(MacroNode $node, PhpWriter $writer)
{
$this->isUsed = TRUE;
return $writer->write('?>src="<?php echo %escape($_imagePipe->request(%node.word))?>" <?php');
}
/**
* @param \Nette\Latte\MacroNode $node
* @param \Nette\Latte\PhpWriter $writer
* @return string
*/
public function macroImgTemp(MacroNode $node, PhpWriter $writer)
{
$this->isUsed = TRUE;
return $writer->write('echo %escape($_imagePipe->request(%node.word, array(), $_imagePipe::TEMP))');
}
/**
* @param \Nette\Latte\MacroNode $node
* @param \Nette\Latte\PhpWriter $writer
* @return string
*/
public function macroAttrImgTemp(MacroNode $node, PhpWriter $writer)
{
$this->isUsed = TRUE;
return $writer->write('?>src="<?php echo %escape($_imagePipe->request(%node.word, array(), $_imagePipe::TEMP))?>" <?php');
}
/**
*/
public function initialize()
{
$this->isUsed = FALSE;
}
/**
* Finishes template parsing.
* @return array(prolog, epilog)
*/
public function finalize()
{
if (!$this->isUsed) {
return array();
}
return array(
get_called_class() . '::validateTemplateParams($template);',
NULL
);
}
/**
* @param \Nette\Templating\Template $template
* @throws \Nette\InvalidStateException
*/
public static function validateTemplateParams(Nette\Templating\Template $template)
{
$params = $template->getParameters();
if (!isset($params['_imagePipe']) || !$params['_imagePipe'] instanceof ImagePipe) {
$where = isset($params['control']) ?
" of component " . get_class($params['control']) . '(' . $params['control']->getName() . ')'
: NULL;
throw new Nette\InvalidStateException(
'Please provide an instanceof Img\\ImagePipe ' .
'as a parameter $_imagePipe to template' . $where
);
}
}
}
<?php
namespace Img;
use Nette;
/**
* @author Filip Procházka <[email protected]>
*
* @property float|int $width
* @property float|int $height
* @property-read float|int $height
*/
class Size extends Nette\Object
{
/**
* @var float|int
*/
private $width;
/**
* @var float|int
*/
private $height;
/**
* @param float|int $width
* @param float|int $height
*/
public function __construct($width, $height)
{
$this->width = $width;
$this->height = $height;
}
/**
* @return float|int
*/
public function getHeight()
{
return $this->height;
}
/**
* @return float|int
*/
public function getWidth()
{
return $this->width;
}
/**
* @param string $file
*
* @return Size
*/
public static function fromFile($file)
{
list($width, $height) = @getimagesize($file);
return new Size($width, $height);
}
}
@fprochazka
Copy link
Author

Berte v úvahu, že dané řešení dává smysl v konkrétní aplikaci a proto tam mám například natvrdo maximální velikost obrázku 500x500 a jiné neřeším. Tyhle drobnůstky je třeba doladit ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment