From a3ef1b606c71492c8e62144ef201c2cbd53ec48f Mon Sep 17 00:00:00 2001 From: Nicolas Le Goff Date: Wed, 10 Dec 2014 18:27:35 +0100 Subject: [PATCH] Makes static thumbnails delivery as default delivery && set symlink dir in www directory --- .gitignore | 3 + bin/console | 5 - config/configuration.sample.yml | 4 - lib/Alchemy/Phrasea/Application.php | 2 + .../Setup/StaticConfigurationDumper.php | 56 ---------- .../Command/Setup/StaticMappingGenerator.php | 64 ----------- .../Provider/FileServeServiceProvider.php | 10 +- .../Phrasea/Http/StaticFile/Apache.php | 52 --------- lib/Alchemy/Phrasea/Http/StaticFile/Nginx.php | 50 --------- .../Phrasea/Http/StaticFile/NullMode.php | 30 ----- .../Http/StaticFile/StaticFileFactory.php | 104 ------------------ .../StaticFile/StaticFileModeInterface.php | 25 ----- ...{AbstractStaticMode.php => StaticMode.php} | 8 +- .../Http/StaticFile/Symlink/SymLinker.php | 12 +- lib/classes/databox.php | 15 --- lib/classes/media/subdef.php | 5 +- lib/classes/patch/390alpha21a.php | 67 +++++++++++ lib/classes/record/adapter.php | 2 +- lib/conf.d/configuration.yml | 4 - .../Http/StaticFile/ApacheModeTest.php | 37 ------- .../Phrasea/Http/StaticFile/NginxModeTest.php | 37 ------- .../Http/StaticFile/StaticFileFactoryTest.php | 54 --------- www/thumbnails/.gitkeep | 0 23 files changed, 85 insertions(+), 561 deletions(-) delete mode 100644 lib/Alchemy/Phrasea/Command/Setup/StaticConfigurationDumper.php delete mode 100644 lib/Alchemy/Phrasea/Command/Setup/StaticMappingGenerator.php delete mode 100644 lib/Alchemy/Phrasea/Http/StaticFile/Apache.php delete mode 100644 lib/Alchemy/Phrasea/Http/StaticFile/Nginx.php delete mode 100644 lib/Alchemy/Phrasea/Http/StaticFile/NullMode.php delete mode 100644 lib/Alchemy/Phrasea/Http/StaticFile/StaticFileFactory.php delete mode 100644 lib/Alchemy/Phrasea/Http/StaticFile/StaticFileModeInterface.php rename lib/Alchemy/Phrasea/Http/StaticFile/{AbstractStaticMode.php => StaticMode.php} (74%) create mode 100644 lib/classes/patch/390alpha21a.php delete mode 100644 tests/Alchemy/Tests/Phrasea/Http/StaticFile/ApacheModeTest.php delete mode 100644 tests/Alchemy/Tests/Phrasea/Http/StaticFile/NginxModeTest.php delete mode 100644 tests/Alchemy/Tests/Phrasea/Http/StaticFile/StaticFileFactoryTest.php create mode 100644 www/thumbnails/.gitkeep diff --git a/.gitignore b/.gitignore index a039125aae..d25188dda0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ /www/assets /tmp-assets +# Exclude thumbnails folder +/www/thumbnails + # Exclude node.js dependencies folder /node_modules diff --git a/bin/console b/bin/console index d47a71301e..7125fcf91b 100755 --- a/bin/console +++ b/bin/console @@ -15,7 +15,6 @@ use Alchemy\Phrasea\Command\BuildSubdefs; use Alchemy\Phrasea\Command\Plugin\ListPlugin; use Alchemy\Phrasea\Command\Setup\H264ConfigurationDumper; use Alchemy\Phrasea\Command\Setup\H264MappingGenerator; -use Alchemy\Phrasea\Command\Setup\StaticConfigurationDumper; use Alchemy\Phrasea\Core\Version; use Alchemy\Phrasea\Command\BuildMissingSubdefs; use Alchemy\Phrasea\Command\CreateCollection; @@ -30,7 +29,6 @@ use Alchemy\Phrasea\Command\Plugin\RemovePlugin; use Alchemy\Phrasea\Command\CheckConfig; use Alchemy\Phrasea\Command\Setup\XSendFileMappingGenerator; use Alchemy\Phrasea\Command\Setup\XSendFileConfigurationDumper; -use Alchemy\Phrasea\Command\Setup\StaticMappingGenerator; use Alchemy\Phrasea\Command\Task\SchedulerResumeTasks; use Alchemy\Phrasea\Command\Task\SchedulerState; use Alchemy\Phrasea\Command\Task\SchedulerPauseTasks; @@ -117,9 +115,6 @@ $cli->command(new H264MappingGenerator()); $cli->command(new XSendFileConfigurationDumper()); $cli->command(new XSendFileMappingGenerator()); -$cli->command(new StaticConfigurationDumper()); -$cli->command(new StaticMappingGenerator()); - $cli->loadPlugins(); exit(is_int($cli->run()) ? : 1); diff --git a/config/configuration.sample.yml b/config/configuration.sample.yml index eae521e4e9..4a830d8405 100644 --- a/config/configuration.sample.yml +++ b/config/configuration.sample.yml @@ -206,10 +206,6 @@ api_cors: session: idle: 0 lifetime: 604800 # 1 week -static-file: - enabled: false - type: nginx - symlink-directory: '' crossdomain: site-control: 'master-only' allow-access-from: diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php index 819089a497..2c8dcced55 100644 --- a/lib/Alchemy/Phrasea/Application.php +++ b/lib/Alchemy/Phrasea/Application.php @@ -702,6 +702,8 @@ class Application extends SilexApplication $this['tmp.path'] = $this['root.path'].'/tmp'; // plugin path $this['plugin.path'] = $dir = $this['root.path'].'/plugins'; + // thumbnails path + $this['thumbnail.path'] = $dir = $this['root.path'].'/www/thumbnails'; // cache path for dev env $this['cache.dev.path'] = $this->share(function() { diff --git a/lib/Alchemy/Phrasea/Command/Setup/StaticConfigurationDumper.php b/lib/Alchemy/Phrasea/Command/Setup/StaticConfigurationDumper.php deleted file mode 100644 index de5b0bffca..0000000000 --- a/lib/Alchemy/Phrasea/Command/Setup/StaticConfigurationDumper.php +++ /dev/null @@ -1,56 +0,0 @@ -setDescription('Dump the virtual host configuration depending on Phraseanet configuration'); - } - - /** - * {@inheritdoc} - */ - protected function doExecute(InputInterface $input, OutputInterface $output) - { - $output->writeln(''); - - if (!$this->container['phraseanet.static-file-factory']->isStaticFileModeEnabled()) { - $output->writeln('Static file support is disabled'); - $ret = 1; - } else { - $output->writeln('Static file support is enabled'); - $ret = 0; - } - - try { - $configuration = $this->container['phraseanet.static-file-factory']->getMode(true, true)->getVirtualHostConfiguration(); - $output->writeln('Static file configuration seems OK'); - $output->writeln($configuration); - } catch (RuntimeException $e) { - $output->writeln('Static file configuration seems invalid'); - $ret = 1; - } - - $output->writeln(''); - - return $ret; - } -} diff --git a/lib/Alchemy/Phrasea/Command/Setup/StaticMappingGenerator.php b/lib/Alchemy/Phrasea/Command/Setup/StaticMappingGenerator.php deleted file mode 100644 index c09b1a3e33..0000000000 --- a/lib/Alchemy/Phrasea/Command/Setup/StaticMappingGenerator.php +++ /dev/null @@ -1,64 +0,0 @@ -addOption('write', 'w', null, 'Writes the configuration') - ->addOption('enabled', 'e', null, 'Set the enable toggle to `true`') - ->addArgument('type', InputArgument::REQUIRED, 'The configuration type, either `nginx` or `apache`') - ->setDescription('Generates Phraseanet Static file configuration'); - } - - /** - * {@inheritdoc} - */ - protected function doExecute(InputInterface $input, OutputInterface $output) - { - $enabled = $input->getOption('enabled'); - $type = strtolower($input->getArgument('type')); - - $factory = new StaticFileFactory($this->container['monolog'], true, $type, $this->container['phraseanet.thumb-symlinker']); - $mode = $factory->getMode(true); - - $conf = array( - 'enabled' => $enabled, - 'type' => $type, - 'mapping' => $mode->getMapping(), - ); - - if ($input->getOption('write')) { - $output->write("Writing configuration ..."); - $this->container['phraseanet.configuration']['static-file'] = $conf; - $output->writeln(" OK"); - $output->writeln(""); - $output->write("It is now strongly recommended to use static-file:dump-configuration command to upgrade your virtual-host"); - } else { - $output->writeln("Configuration will not be written, use --write option to write it"); - $output->writeln(""); - $output->writeln(Yaml::dump(array('static-file' => $conf), 4)); - } - - return 0; - } -} diff --git a/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php index 82c9983af7..d110c2f9bb 100644 --- a/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php +++ b/lib/Alchemy/Phrasea/Core/Provider/FileServeServiceProvider.php @@ -15,6 +15,8 @@ use Alchemy\Phrasea\Core\Event\Subscriber\XSendFileSubscriber; use Alchemy\Phrasea\Http\H264PseudoStreaming\H264Factory; use Alchemy\Phrasea\Http\ServeFileResponseFactory; use Alchemy\Phrasea\Http\StaticFile\StaticFileFactory; +use Alchemy\Phrasea\Http\StaticFile\StaticMode; +use Alchemy\Phrasea\Http\StaticFile\Symlink\SymLinker; use Alchemy\Phrasea\Http\XSendFile\XSendFileFactory; use Silex\Application; use Silex\ServiceProviderInterface; @@ -34,16 +36,12 @@ class FileServeServiceProvider implements ServiceProviderInterface return H264Factory::create($app); }); - $app['phraseanet.static-file-factory'] = $app->share(function ($app) { - return StaticFileFactory::create($app); - }); - $app['phraseanet.h264'] = $app->share(function ($app) { return $app['phraseanet.h264-factory']->createMode(false); }); - $app['phraseanet.static-file'] = $app->share(function ($app) { - return $app['phraseanet.static-file-factory']->getMode(false); + $app['phraseanet.static-file'] = $app->share(function (Application $app) { + return new StaticMode(SymLinker::create($app)); }); $app['phraseanet.file-serve'] = $app->share(function (Application $app) { diff --git a/lib/Alchemy/Phrasea/Http/StaticFile/Apache.php b/lib/Alchemy/Phrasea/Http/StaticFile/Apache.php deleted file mode 100644 index 10b97d41bb..0000000000 --- a/lib/Alchemy/Phrasea/Http/StaticFile/Apache.php +++ /dev/null @@ -1,52 +0,0 @@ -mapping = $mapping; - } - - /** - * {@inheritdoc} - */ - public function getVirtualHostConfiguration() - { - $output = "\n"; - $output .= " Alias ".$this->mapping['mount-point']." ".$this->mapping['directory']."\n"; - $output .= "\n"; - $output .= " mapping['directory'].">\n"; - $output .= " Order allow,deny\n"; - $output .= " Allow from all\n"; - $output .= " \n"; - $output .= "\n"; - - return $output; - } -} diff --git a/lib/Alchemy/Phrasea/Http/StaticFile/Nginx.php b/lib/Alchemy/Phrasea/Http/StaticFile/Nginx.php deleted file mode 100644 index 31960dbfbe..0000000000 --- a/lib/Alchemy/Phrasea/Http/StaticFile/Nginx.php +++ /dev/null @@ -1,50 +0,0 @@ -mapping = $mapping; - } - - /** - * {@inheritdoc} - */ - public function getVirtualHostConfiguration() - { - $output = "\n"; - $output .= " location " . $this->mapping['mount-point']. " {\n"; - $output .= " alias ".$this->mapping['directory'].";\n"; - $output .= " types { }"; - $output .= " default_type image/jpeg;"; - $output .= " }\n"; - - return $output; - } -} diff --git a/lib/Alchemy/Phrasea/Http/StaticFile/NullMode.php b/lib/Alchemy/Phrasea/Http/StaticFile/NullMode.php deleted file mode 100644 index fc021e75fe..0000000000 --- a/lib/Alchemy/Phrasea/Http/StaticFile/NullMode.php +++ /dev/null @@ -1,30 +0,0 @@ -logger = $logger; - $this->enabled = (Boolean) $enabled; - $this->type = strtolower($type); - $this->symlinker = $symlinker; - - $this->mapping = array( - 'mount-point' => $symlinker->getDefaultAlias(), - 'directory' => $symlinker->getSymlinkDir() - ); - } - - /** - * Creates a new instance of StaticFileFactory Factory according to the application - * configuration. - * - * @param Application $app - * @return StaticFileFactory - */ - public static function create(Application $app) - { - $conf = $app['phraseanet.configuration']['static-file']; - - return new self($app['monolog'], $conf['enabled'], $conf['type'], $app['phraseanet.thumb-symlinker']); - } - - /** - * Returns a new instance of ModeInterface - * - * @param bool $throwException - * @param bool $forceMode - * - * @return Apache|Nginx|NullMode - * @throws InvalidArgumentException - */ - public function getMode($throwException = false, $forceMode = false) - { - if (false === $this->enabled && true !== $forceMode) { - return new NullMode(); - } - - switch ($this->type) { - case 'nginx': - return new Nginx($this->mapping, $this->symlinker); - break; - case 'apache': - case 'apache2': - return new Apache($this->mapping, $this->symlinker); - default: - $this->logger->error('Invalid static file configuration.'); - if ($throwException) { - throw new InvalidArgumentException(sprintf( - 'Invalid static file type value "%s"', - $this->type - )); - } - - return new NullMode(); - } - } - - /** - * @return bool - */ - public function isStaticFileModeEnabled() - { - return $this->enabled; - } -} diff --git a/lib/Alchemy/Phrasea/Http/StaticFile/StaticFileModeInterface.php b/lib/Alchemy/Phrasea/Http/StaticFile/StaticFileModeInterface.php deleted file mode 100644 index 96f5773475..0000000000 --- a/lib/Alchemy/Phrasea/Http/StaticFile/StaticFileModeInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -symlinker = $symlinker; - - parent::__construct($mapping); } /** @@ -33,7 +31,7 @@ abstract class AbstractStaticMode extends AbstractServerMode { $this->ensureSymlink($pathFile); - return Url::factory(sprintf('%s/%s', $this->mapping['mount-point'], $this->symlinker->getSymlinkBasePath($pathFile))); + return Url::factory(sprintf('/thumbnails/%s', $this->symlinker->getSymlinkBasePath($pathFile))); } /** diff --git a/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php b/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php index 99f8ad9a34..9141c6e7f1 100644 --- a/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php +++ b/lib/Alchemy/Phrasea/Http/StaticFile/Symlink/SymLinker.php @@ -21,9 +21,6 @@ use Guzzle\Http\Url; */ class SymLinker { - /** Mount Point Alias Name */ - const ALIAS = 'thumb'; - protected $encoder; protected $fs; protected $symlinkDir; @@ -33,7 +30,7 @@ class SymLinker return new SymLinker( $app['phraseanet.thumb-symlinker-encoder'], $app['filesystem'], - isset($app['phraseanet.configuration']['static-file']['symlink-directory']) ? $app['phraseanet.configuration']['static-file']['symlink-directory'] : $app['root.path'] . '/tmp/symlinks' + $app['thumbnail.path'] ); } @@ -49,11 +46,6 @@ class SymLinker return $this->symlinkDir; } - public function getDefaultAlias() - { - return sprintf('/%s', self::ALIAS); - } - public function symlink($pathFile) { $this->fs->symlink($pathFile, $this->getSymlinkPath($pathFile)) ; @@ -68,7 +60,7 @@ class SymLinker { $symlinkName = $this->getSymlink($pathFile); - return sprintf('%s/%s/%s', + return sprintf('%s/%s/%s.jpg', substr($symlinkName, 0, 2), substr($symlinkName, 2, 2), substr($symlinkName, 4) diff --git a/lib/classes/databox.php b/lib/classes/databox.php index cd699f51d6..de124ee17c 100644 --- a/lib/classes/databox.php +++ b/lib/classes/databox.php @@ -451,21 +451,6 @@ class databox extends base public function unmount_databox() { - if ($this->app['phraseanet.static-file-factory']->isStaticFileModeEnabled()) { - $sql = "SELECT path, file FROM subdef WHERE `name`='thumbnail'"; - $stmt = $this->get_connection()->prepare($sql); - $stmt->execute(); - $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC); - $stmt->closeCursor(); - foreach ($rows as $row) { - $pathfile = $this->app['phraseanet.thumb-symlinker']->getSymlinkPath(sprintf( - '%s/%s', - rtrim($row['path'], '/'), - $row['file'] - )); - $this->app['filesystem']->remove($pathfile); - } - } foreach ($this->get_collections() as $collection) { $collection->unmount_collection($this->app); } diff --git a/lib/classes/media/subdef.php b/lib/classes/media/subdef.php index a49ac8a8ec..c2b0f7750a 100644 --- a/lib/classes/media/subdef.php +++ b/lib/classes/media/subdef.php @@ -735,7 +735,7 @@ class media_subdef extends media_abstract implements cache_cacheableInterface $subdef->get_permalink()->delete_data_from_cache(); } - if ($name === 'thumbnail' && $app['phraseanet.static-file-factory']->isStaticFileModeEnabled()) { + if ($name === 'thumbnail') { $app['phraseanet.thumb-symlinker']->symlink($subdef->get_pathfile()); } @@ -755,8 +755,9 @@ class media_subdef extends media_abstract implements cache_cacheableInterface return; } + // serve thumbnails using static file service if ($this->get_name() === 'thumbnail') { - if ($this->app['phraseanet.static-file-factory']->isStaticFileModeEnabled() && null !== $url = $this->app['phraseanet.static-file']->getUrl($this->get_pathfile())) { + if (null !== $url = $this->app['phraseanet.static-file']->getUrl($this->get_pathfile())) { $this->url = $url. "?etag=".$this->getEtag(); return; diff --git a/lib/classes/patch/390alpha21a.php b/lib/classes/patch/390alpha21a.php new file mode 100644 index 0000000000..f530f70307 --- /dev/null +++ b/lib/classes/patch/390alpha21a.php @@ -0,0 +1,67 @@ +release; + } + + /** + * {@inheritdoc} + */ + public function require_all_upgrades() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function concern() + { + return $this->concern; + } + + /** + * {@inheritdoc} + */ + public function getDoctrineMigrations() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function apply(base $appbox, Application $app) + { + $main = $app['conf']->get(['main']); + if (isset($main['static-file'])) { + unset($main['static-file']); + } + $app['conf']->set(['main'], $main); + + return true; + } +} diff --git a/lib/classes/record/adapter.php b/lib/classes/record/adapter.php index d0295e4b06..92350ed7b5 100644 --- a/lib/classes/record/adapter.php +++ b/lib/classes/record/adapter.php @@ -1564,7 +1564,7 @@ class record_adapter implements record_Interface, cache_cacheableInterface if (!$subdef->is_physically_present()) continue; - if ($subdef->get_name() === 'thumbnail' && $this->app['phraseanet.static-file-factory']->isStaticFileModeEnabled()) { + if ($subdef->get_name() === 'thumbnail') { $this->app['filesystem']->remove($this->app['phraseanet.thumb-symlinker']->getSymlinkPath($subdef->get_pathfile())); } diff --git a/lib/conf.d/configuration.yml b/lib/conf.d/configuration.yml index ee77da7513..355ae28797 100644 --- a/lib/conf.d/configuration.yml +++ b/lib/conf.d/configuration.yml @@ -203,10 +203,6 @@ session: idle: 0 # 1 week lifetime: 604800 -static-file: - enabled: false - type: nginx - symlink-directory: '' crossdomain: allow-access-from: - diff --git a/tests/Alchemy/Tests/Phrasea/Http/StaticFile/ApacheModeTest.php b/tests/Alchemy/Tests/Phrasea/Http/StaticFile/ApacheModeTest.php deleted file mode 100644 index 6ab211fc19..0000000000 --- a/tests/Alchemy/Tests/Phrasea/Http/StaticFile/ApacheModeTest.php +++ /dev/null @@ -1,37 +0,0 @@ - __DIR__, - 'mount-point' => '/thumbs' - ), self::$DI['app']['phraseanet.thumb-symlinker']); - $conf = $mode->getVirtualHostConfiguration(); - $this->assertRegExp('#'.__DIR__ . '#', $conf); - } - - /** - * @dataProvider provideMappings - * @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException - */ - public function testInvalidMapping($mapping) - { - new Apache($mapping, self::$DI['app']['phraseanet.thumb-symlinker']); - } - - public function provideMappings() - { - return array( - array(array('Directory' => __DIR__)), - array(array('wrong-key' => __DIR__, 'mount-point' => '/')), - array(array('directory' => __DIR__, 'wrong-key' => '/')), - ); - } -} diff --git a/tests/Alchemy/Tests/Phrasea/Http/StaticFile/NginxModeTest.php b/tests/Alchemy/Tests/Phrasea/Http/StaticFile/NginxModeTest.php deleted file mode 100644 index ad8b948f9e..0000000000 --- a/tests/Alchemy/Tests/Phrasea/Http/StaticFile/NginxModeTest.php +++ /dev/null @@ -1,37 +0,0 @@ - __DIR__, - 'mount-point' => '/thumbs' - ), self::$DI['app']['phraseanet.thumb-symlinker']); - $conf = $mode->getVirtualHostConfiguration(); - $this->assertRegExp('#'.__DIR__ . '#', $conf); - } - - /** - * @dataProvider provideMappings - * @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException - */ - public function testInvalidMapping($mapping) - { - new Nginx($mapping, self::$DI['app']['phraseanet.thumb-symlinker']); - } - - public function provideMappings() - { - return array( - array(array('Directory' => __DIR__)), - array(array('wrong-key' => __DIR__, 'mount-point' => '/')), - array(array('directory' => __DIR__, 'wrong-key' => '/')), - ); - } -} diff --git a/tests/Alchemy/Tests/Phrasea/Http/StaticFile/StaticFileFactoryTest.php b/tests/Alchemy/Tests/Phrasea/Http/StaticFile/StaticFileFactoryTest.php deleted file mode 100644 index e0e8d39838..0000000000 --- a/tests/Alchemy/Tests/Phrasea/Http/StaticFile/StaticFileFactoryTest.php +++ /dev/null @@ -1,54 +0,0 @@ -assertInstanceOf('Alchemy\Phrasea\Http\StaticFile\StaticFileFactory', $factory); - } - - public function testFactoryWithStaticFileEnable() - { - $logger = $this->getMock('Psr\Log\LoggerInterface'); - - $factory = new StaticFileFactory($logger, true, 'nginx', self::$DI['app']['phraseanet.thumb-symlinker']); - $this->assertInstanceOf('Alchemy\Phrasea\Http\StaticFile\AbstractStaticMode', $factory->getMode()); - } - - public function testFactoryWithStaticFileDisabled() - { - $logger = $this->getMock('Psr\Log\LoggerInterface'); - - $factory = new StaticFileFactory($logger, false, 'nginx', self::$DI['app']['phraseanet.thumb-symlinker']); - $this->assertInstanceOf('Alchemy\Phrasea\Http\StaticFile\NullMode', $factory->getMode()); - $this->assertFalse($factory->isStaticFileModeEnabled()); - } - - /** - * @expectedException Alchemy\Phrasea\Exception\InvalidArgumentException - */ - public function testFactoryWithWrongTypeThrowsAnExceptionIfRequired() - { - $logger = $this->getMock('Psr\Log\LoggerInterface'); - - $factory = new StaticFileFactory($logger, true, 'wrong-type', self::$DI['app']['phraseanet.thumb-symlinker']); - $factory->getMode(true); - } - - public function testFactoryWithWrongTypeDoesNotThrowsAnException() - { - $logger = $this->getMock('Psr\Log\LoggerInterface'); - - $logger->expects($this->once()) - ->method('error') - ->with($this->isType('string')); - - $factory = new StaticFileFactory($logger, true, 'wrong-type', self::$DI['app']['phraseanet.thumb-symlinker']); - $this->assertInstanceOf('Alchemy\Phrasea\Http\StaticFile\NullMode', $factory->getMode(false)); - } -} diff --git a/www/thumbnails/.gitkeep b/www/thumbnails/.gitkeep new file mode 100644 index 0000000000..e69de29bb2