diff --git a/bin/setup b/bin/setup
index 8ed77ab5b8..a96f724d73 100755
--- a/bin/setup
+++ b/bin/setup
@@ -21,6 +21,8 @@ use Alchemy\Phrasea\Command\UpgradeDBDatas;
use Alchemy\Phrasea\Command\Setup\Install;
use Alchemy\Phrasea\Command\Setup\LessCompiler;
use Alchemy\Phrasea\Command\Setup\JavascriptBuilder;
+use Alchemy\Phrasea\Command\Setup\XSendFileMappingNginxDumper;
+use Alchemy\Phrasea\Command\Setup\XSendFileMappingApacheDumper;
use Alchemy\Phrasea\CLI;
use Alchemy\Phrasea\Command\Setup\CheckEnvironment;
@@ -66,6 +68,8 @@ try {
$app->command(new Install('system:install'));
$app->command(new LessCompiler());
$app->command(new JavascriptBuilder());
+ $app->command(new XSendFileMappingNginxDumper());
+ $app->command(new XSendFileMappingApacheDumper());
$result_code = is_int($app->run()) ? : 1;
} catch (\Exception $e) {
diff --git a/config/configuration.sample.yml b/config/configuration.sample.yml
index e151fe0734..6e233eb492 100644
--- a/config/configuration.sample.yml
+++ b/config/configuration.sample.yml
@@ -130,3 +130,9 @@ registration-fields:
-
name: geonameid
required: true
+xsendfile:
+ enable: false
+ mapping:
+ -
+ directory: ''
+ mount-point: ''
diff --git a/hudson/_GV.php b/hudson/_GV.php
index c4a14423c3..b9011abf85 100644
--- a/hudson/_GV.php
+++ b/hudson/_GV.php
@@ -26,9 +26,6 @@ define('GV_dailymotion_client_secret', '');
define('GV_client_navigator', false);
define('GV_base_datapath_noweb', '/tmp/');
define('GV_phrasea_sort', '');
-define('GV_modxsendfile', false);
-define('GV_X_Accel_Redirect', '');
-define('GV_X_Accel_Redirect_mount_point', 'noweb');
define('GV_h264_streaming', false);
define('GV_mod_auth_token_directory', '');
define('GV_mod_auth_token_directory_path', '');
diff --git a/lib/Alchemy/Phrasea/Application.php b/lib/Alchemy/Phrasea/Application.php
index a0f3c3a6de..ed1bf00209 100644
--- a/lib/Alchemy/Phrasea/Application.php
+++ b/lib/Alchemy/Phrasea/Application.php
@@ -99,6 +99,7 @@ use Alchemy\Phrasea\Core\Provider\TaskManagerServiceProvider;
use Alchemy\Phrasea\Core\Provider\TemporaryFilesystemServiceProvider;
use Alchemy\Phrasea\Core\Provider\TokensServiceProvider;
use Alchemy\Phrasea\Core\Provider\UnicodeServiceProvider;
+use Alchemy\Phrasea\Core\Provider\XSendFileMappingServiceProvider;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Twig\JSUniqueID;
use Alchemy\Phrasea\Twig\Camelize;
@@ -306,6 +307,12 @@ class Application extends SilexApplication
$this->register(new ValidatorServiceProvider());
$this->register(new XPDFServiceProvider());
+ $this->register(new XSendFileMappingServiceProvider(), array(
+ 'xsendfile.mapping' => array(
+ $this['root.path'] . '/tmp/download/' => '/download/',
+ $this['root.path'] . '/tmp/lazaret/' => '/lazaret/'
+ )
+ ));
$this->register(new FileServeServiceProvider());
$this['phraseanet.exception_handler'] = $this->share(function ($app) {
diff --git a/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingApacheDumper.php b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingApacheDumper.php
new file mode 100644
index 0000000000..5010efd61a
--- /dev/null
+++ b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingApacheDumper.php
@@ -0,0 +1,51 @@
+setDescription('Dump XSendFile mapping for Apache web server');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output)
+ {
+ $mapper = $this->container['phraseanet.xsendfile-mapping'];
+
+ $output->writeln('Apache XSendfile configuration');
+ $output->writeln('');
+ $output->writeln('');
+ $output->writeln(' ');
+ $output->writeln(' XSendFile on');
+ foreach ($this->container['phraseanet.xsendfile-mapping']->getMapping() as $entry) {
+ $output->writeln(' XSendFilePath ' . $mapper->sanitizePath($entry['directory']));
+ }
+ $output->writeln(' ');
+ $output->writeln('');
+ $output->writeln('');
+
+ return 1;
+ }
+}
diff --git a/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingNginxDumper.php b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingNginxDumper.php
new file mode 100644
index 0000000000..91ffca120d
--- /dev/null
+++ b/lib/Alchemy/Phrasea/Command/Setup/XSendFileMappingNginxDumper.php
@@ -0,0 +1,48 @@
+setDescription('Dump xsendfile mapping for Nginx and Apache web server');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function doExecute(InputInterface $input, OutputInterface $output)
+ {
+ $mapper = $this->container['phraseanet.xsendfile-mapping'];
+ $output->writeln('Nginx XSendfile configuration');
+ $output->writeln('');
+ foreach ($this->container['phraseanet.xsendfile-mapping']->getMapping() as $entry) {
+ $output->writeln(' location ' . $mapper->sanitizeMountPoint($entry['mount-point']) . ' {');
+ $output->writeln(' internal;');
+ $output->writeln(' alias ' . $mapper->sanitizePath($entry['directory']));
+ $output->writeln(' }');
+ $output->writeln('');
+ }
+
+ return 1;
+ }
+}
diff --git a/lib/Alchemy/Phrasea/Core/Event/Subscriber/XSendFileSuscriber.php b/lib/Alchemy/Phrasea/Core/Event/Subscriber/XSendFileSuscriber.php
new file mode 100644
index 0000000000..2cbe9ea688
--- /dev/null
+++ b/lib/Alchemy/Phrasea/Core/Event/Subscriber/XSendFileSuscriber.php
@@ -0,0 +1,43 @@
+app = $app;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ KernelEvents::REQUEST => array('applyHeaders', 16),
+ );
+ }
+
+ public function applyHeaders(GetResponseEvent $event)
+ {
+ if ($this->app['phraseanet.configuration']['xsendfile']['enable']) {
+ $request = $event->getRequest();
+ $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect');
+ $request->headers->set('X-Accel-Mapping', (string) $this->app['phraseanet.xsendfile-mapping']);
+ }
+ }
+}
diff --git a/lib/Alchemy/Phrasea/Core/Provider/XSendFileMappingServiceProvider.php b/lib/Alchemy/Phrasea/Core/Provider/XSendFileMappingServiceProvider.php
new file mode 100644
index 0000000000..48cda29b10
--- /dev/null
+++ b/lib/Alchemy/Phrasea/Core/Provider/XSendFileMappingServiceProvider.php
@@ -0,0 +1,47 @@
+share(function($app) {
+ $mapping = array();
+ foreach($app['xsendfile.mapping'] as $path => $mountPoint) {
+ $mapping[] = array(
+ 'directory' => $path,
+ 'mount-point' => $mountPoint,
+ );
+ }
+
+ return Mapping::create($app, $mapping);
+ });
+ }
+
+ public function boot(Application $app)
+ {
+ }
+}
diff --git a/lib/Alchemy/Phrasea/Response/ServeFileResponseFactory.php b/lib/Alchemy/Phrasea/Response/ServeFileResponseFactory.php
index ce53c6d324..980686c7ef 100644
--- a/lib/Alchemy/Phrasea/Response/ServeFileResponseFactory.php
+++ b/lib/Alchemy/Phrasea/Response/ServeFileResponseFactory.php
@@ -13,32 +13,17 @@ namespace Alchemy\Phrasea\Response;
use Alchemy\Phrasea\Response\DeliverDataInterface;
use Alchemy\Phrasea\Application;
-use Psr\Log\LoggerInterface,
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ServeFileResponseFactory implements DeliverDataInterface
{
private $xSendFileEnable = false;
- private $mappings;
private $unicode;
- private $logger;
- public function __construct($enableXSendFile, $xAccelMappings, \unicode $unicode, LoggerInterface $logger = null)
+ public function __construct($enableXSendFile, \unicode $unicode)
{
- $this->logger = $logger;
-
$this->xSendFileEnable = $enableXSendFile;
-
- $mappings = array();
-
- foreach ($xAccelMappings as $path => $mountPoint) {
- if (is_dir($path) && '' !== $mountPoint) {
- $mappings[$this->sanitizeXAccelPath($path)] = $this->sanitizeXAccelMountPoint($mountPoint);
- }
- }
-
- $this->mappings = $mappings;
$this->unicode = $unicode;
}
@@ -49,12 +34,9 @@ class ServeFileResponseFactory implements DeliverDataInterface
public static function create(Application $app)
{
return new self(
- $app['phraseanet.registry']->get('GV_modxsendfile'),
- array(
- $app['phraseanet.registry']->get('GV_X_Accel_Redirect') => $app['phraseanet.registry']->get('GV_X_Accel_Redirect_mount_point'),
- $app['root.path'] . '/tmp/download/' => '/download/',
- $app['root.path'] . '/tmp/lazaret/' => '/lazaret/'
- ), new \unicode(), $app['logger']);
+ $app['phraseanet.configuration']['xsendfile']['enable'],
+ $app['unicode']
+ );
}
/**
@@ -66,11 +48,7 @@ class ServeFileResponseFactory implements DeliverDataInterface
$response->setContentDisposition($disposition, $this->sanitizeFilename($filename), $this->sanitizeFilenameFallback($filename));
if ($this->isXSendFileEnable()) {
- if ($this->isMappedFile($file)) {
- $response->headers->set('X-Accel-Redirect', $this->xAccelRedirectMapping($file));
- } else if (null !== $this->logger) {
- $this->logger->warning(sprintf('%s is not located under a nginx xAccelPath'));
- }
+ BinaryFileResponse::trustXSendfileTypeHeader();
}
if (null !== $mimeType) {
@@ -104,16 +82,6 @@ class ServeFileResponseFactory implements DeliverDataInterface
return $this->xSendFileEnable;
}
- private function sanitizeXAccelPath($path)
- {
- return sprintf('%s/', rtrim($path, '/'));
- }
-
- private function sanitizeXAccelMountPoint($mountPoint)
- {
- return sprintf('/%s/', rtrim(ltrim($mountPoint, '/'), '/'));
- }
-
private function sanitizeFilename($filename)
{
return str_replace(array('/', '\\'), '', $filename);
@@ -123,20 +91,4 @@ class ServeFileResponseFactory implements DeliverDataInterface
{
return $this->unicode->remove_nonazAZ09($filename, true, true, true);
}
-
- private function xAccelRedirectMapping($file)
- {
- return str_replace(array_keys($this->mappings), array_values($this->mappings), $file);
- }
-
- private function isMapped($file)
- {
- foreach (array_keys($this->mappings) as $path) {
- if (false !== strpos($file, $path)) {
- return true;
- }
- }
-
- return false;
- }
}
diff --git a/lib/Alchemy/Phrasea/XSendFile/Mapping.php b/lib/Alchemy/Phrasea/XSendFile/Mapping.php
new file mode 100644
index 0000000000..882a82fc04
--- /dev/null
+++ b/lib/Alchemy/Phrasea/XSendFile/Mapping.php
@@ -0,0 +1,91 @@
+validate($mapping);
+
+ $this->mapping = $mapping;
+ }
+
+ public function __toString()
+ {
+ $final = array();
+
+ foreach($this->mapping as $entry) {
+ if (!is_dir($entry['directory']) || '' === $entry['mount-point']) {
+ continue;
+ }
+
+ $final[] = sprintf('%s=%s', $this->sanitizeMountPoint($entry['mount-point']), $this->sanitizePath(realpath($entry['directory'])));
+ }
+
+ return implode(',', $final);
+ }
+
+ public function getMapping()
+ {
+ return $this->mapping;
+ }
+
+ public static function create(Application $app, array $mapping = array())
+ {
+ if (isset($app['phraseanet.configuration']['xsendfile']['mapping'])) {
+ $confMapping = $app['phraseanet.configuration']['xsendfile']['mapping'];
+
+ if (!is_array($confMapping)) {
+ throw new InvalidArgumentException('XSendFile mapping configuration must be an array');
+ }
+
+ foreach($confMapping as $entry) {
+ $mapping[] = $entry;
+ }
+ }
+
+ return new Mapping($mapping);
+ }
+
+ public function sanitizePath($path)
+ {
+ return sprintf('/%s', rtrim(ltrim($path, '/'),'/'));
+ }
+
+ public function sanitizeMountPoint($mountPoint)
+ {
+ return sprintf('/%s', rtrim(ltrim($mountPoint, '/'), '/'));
+ }
+
+ private function validate(array $mapping)
+ {
+ foreach($mapping as $entry) {
+ if (!is_array($entry)) {
+ throw new \InvalidArgumentException('XSendFile mapping entry must be an array');
+ }
+
+ if (!isset($entry['directory']) && !isset($entry['mount-point'])) {
+ throw new \InvalidArgumentException('XSendFile mapping entry must contain at least two keys "directory" and "mounbt-point"');
+ }
+ }
+ }
+}
diff --git a/lib/classes/API/V1/adapter.php b/lib/classes/API/V1/adapter.php
index 51203dcd18..5e8b847a14 100644
--- a/lib/classes/API/V1/adapter.php
+++ b/lib/classes/API/V1/adapter.php
@@ -351,9 +351,8 @@ class API_V1_adapter extends API_V1_Abstract
'defaultLanguage' => $app['phraseanet.registry']->get('id_GV_default_lng'),
'allowIndexing' => $app['phraseanet.registry']->get('GV_allow_search_engine'),
'modes' => array(
- 'XsendFile' => $app['phraseanet.registry']->get('GV_modxsendfile'),
- 'nginxXAccelRedirect' => $app['phraseanet.registry']->get('GV_X_Accel_Redirect'),
- 'nginxXAccelRedirectMountPoint' => $app['phraseanet.registry']->get('GV_X_Accel_Redirect_mount_point'),
+ 'XsendFile' => $app['phraseanet.configuration']['xsendfile']['enable'],
+ 'XsendFileMapping' => $app['phraseanet.configuration']['xsendfile']['mapping'],
'h264Streaming' => $app['phraseanet.registry']->get('GV_h264_streaming'),
'authTokenDirectory' => $app['phraseanet.registry']->get('GV_mod_auth_token_directory'),
'authTokenDirectoryPath' => $app['phraseanet.registry']->get('GV_mod_auth_token_directory_path'),
diff --git a/lib/conf.d/_GV_template.inc b/lib/conf.d/_GV_template.inc
index 30f715203a..68634f4338 100644
--- a/lib/conf.d/_GV_template.inc
+++ b/lib/conf.d/_GV_template.inc
@@ -196,26 +196,6 @@ return call_user_func_array(function(Application $app) {
), array(
'section' => _('GV::section:: Executables externes'),
'vars' => array(
- array(
- 'type' => \registry::TYPE_BOOLEAN,
- 'name' => 'GV_modxsendfile',
- 'comment' => _('reglages:: mod_xsendfileapache active'),
- 'default' => false
- ),
- array(
- 'type' => \registry::TYPE_STRING,
- 'name' => 'GV_X_Accel_Redirect',
- 'comment' => _('reglages:: Path en acces pour X-Accel-Redirect (NginX Uniquement)'),
- 'default' => '',
- 'end_slash' => true
- ),
- array(
- 'type' => \registry::TYPE_STRING,
- 'name' => 'GV_X_Accel_Redirect_mount_point',
- 'comment' => _('reglages:: Point de montage pour X-Accel-Redirect (NginX Uniquement)'),
- 'default' => 'noweb',
- 'end_slash' => false
- ),
array(
'type' => \registry::TYPE_BOOLEAN,
'name' => 'GV_h264_streaming',
diff --git a/lib/conf.d/configuration.yml b/lib/conf.d/configuration.yml
index e1ad1c38f0..fb52350f7d 100644
--- a/lib/conf.d/configuration.yml
+++ b/lib/conf.d/configuration.yml
@@ -133,3 +133,9 @@ registration-fields:
-
name: geonameid
required: true
+xsendfile:
+ enable: false
+ mapping:
+ -
+ directory: ''
+ mount-point: ''
diff --git a/tests/Alchemy/Tests/Phrasea/Core/Provider/XSendFileMappingServiceProviderTest.php b/tests/Alchemy/Tests/Phrasea/Core/Provider/XSendFileMappingServiceProviderTest.php
new file mode 100644
index 0000000000..db61b4f9c8
--- /dev/null
+++ b/tests/Alchemy/Tests/Phrasea/Core/Provider/XSendFileMappingServiceProviderTest.php
@@ -0,0 +1,16 @@
+factory = new ServeFileResponseFactory(
- false,
- array(
- __DIR__ . '/../../../../files/' => '/protected/'
- ), new \unicode());
+ $this->factory = new ServeFileResponseFactory(false, new \unicode());
$response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg');
@@ -24,11 +22,7 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract
public function testDeliverFileWithFilename()
{
- $this->factory = new ServeFileResponseFactory(
- false,
- array(
- __DIR__ . '/../../../../files/' => '/protected/'
- ), new \unicode());
+ $this->factory = new ServeFileResponseFactory(false, new \unicode());
$response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'toto.jpg');
@@ -38,11 +32,7 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract
public function testDeliverFileWithFilenameAndDisposition()
{
- $this->factory = new ServeFileResponseFactory(
- false,
- array(
- __DIR__ . '/../../../../files/' => '/protected/'
- ), new \unicode());
+ $this->factory = new ServeFileResponseFactory(false, new \unicode());
$response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'toto.jpg', 'attachment');
@@ -52,13 +42,18 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract
public function testDeliverFileWithFilenameAndDispositionAndXSendFile()
{
- $this->factory = new ServeFileResponseFactory(
- true,
+ $this->factory = new ServeFileResponseFactory(true, new \unicode());
+ $request = Request::create('/');
+ $request->headers->set('X-SendFile-Type', 'X-Accel-Redirect');
+ $request->headers->set('X-Accel-Mapping', (string) new Mapping(array(
array(
- __DIR__ . '/../../../../files/' => '/protected/'
- ), new \unicode());
+ 'directory' => __DIR__ . '/../../../../files/',
+ 'mount-point' => '/protected/'
+ )
+ )));
$response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'toto.jpg', 'attachment');
+ $response->prepare($request);
$this->assertInstanceOf("Symfony\Component\HttpFoundation\Response", $response);
$this->assertEquals('attachment; filename="toto.jpg"', $response->headers->get('content-disposition'));
@@ -67,13 +62,18 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract
public function testDeliverFileWithFilenameAndDispositionAndXSendFileAndNoTrailingSlashes()
{
- $this->factory = new ServeFileResponseFactory(
- true,
+ $this->factory = new ServeFileResponseFactory(true, new \unicode());
+ $request = Request::create('/');
+ $request->headers->set('X-SendFile-Type', 'X-Accel-Redirect');
+ $request->headers->set('X-Accel-Mapping', (string) new Mapping(array(
array(
- __DIR__ . '/../../../../files' => 'protected'
- ), new \unicode());
+ 'directory' => __DIR__ . '/../../../../files/',
+ 'mount-point' => '/protected/'
+ )
+ )));
$response = $this->factory->deliverFile(__DIR__ . '/../../../../files/cestlafete.jpg', 'toto.jpg', 'attachment');
+ $response->prepare($request);
$this->assertInstanceOf("Symfony\Component\HttpFoundation\Response", $response);
$this->assertEquals('attachment; filename="toto.jpg"', $response->headers->get('content-disposition'));
@@ -85,37 +85,36 @@ class ServeFileResponseFactoryTest extends \PhraseanetWebTestCaseAbstract
*/
public function testDeliverUnexistingFile()
{
- $this->factory = new ServeFileResponseFactory(
- true,
- array(
- __DIR__ . '/../../../../files' => 'protected'
- ), new \unicode());
+ $this->factory = new ServeFileResponseFactory(true, new \unicode());
$this->factory->deliverFile(__DIR__ . '/../../../../files/does_not_exists.jpg', 'toto.jpg', 'attachment');
}
public function testDeliverFileWithFilenameAndDispositionAndXSendFileButFileNotInXAccelMapping()
{
- $this->factory = new ServeFileResponseFactory(
- true,
+ $this->factory = new ServeFileResponseFactory(true, new \unicode());
+ $request = Request::create('/');
+ $request->headers->set('X-SendFile-Type', 'X-Accel-Redirect');
+ $request->headers->set('X-Accel-Mapping', (string) new Mapping(array(
array(
- __DIR__ . '/../../../../files' => 'protected'
- ), new \unicode());
+ 'directory' => __DIR__ . '/../../../../files/',
+ 'mount-point' => '/protected/'
+ )
+ )));
- $response = $this->factory->deliverFile(__DIR__ . '/../../../../classes/PhraseanetPHPUnitAbstract.php', 'PhraseanetPHPUnitAbstract.php', 'attachment');
+ $file = __DIR__ . '/../../../../classes/PhraseanetPHPUnitAbstract.php';
+
+ $response = $this->factory->deliverFile($file, 'PhraseanetPHPUnitAbstract.php', 'attachment');
+ $response->prepare($request);
$this->assertInstanceOf("Symfony\Component\HttpFoundation\Response", $response);
$this->assertEquals('attachment; filename="PhraseanetPHPUnitAbstract.php"', $response->headers->get('content-disposition'));
- $this->assertEquals(null, $response->headers->get('x-accel-redirect'));
+ $this->assertEquals(realpath($file), $response->headers->get('x-accel-redirect'));
}
public function testDeliverDatas()
{
- $this->factory = new ServeFileResponseFactory(
- false,
- array(
- __DIR__ . '/../../../../files/' => '/protected/'
- ), new \unicode());
+ $this->factory = new ServeFileResponseFactory(false, new \unicode());
$data = 'Sex,Name,Birthday
M,Alphonse,1932
diff --git a/tests/Alchemy/Tests/Phrasea/XSendFile/MappingTest.php b/tests/Alchemy/Tests/Phrasea/XSendFile/MappingTest.php
new file mode 100644
index 0000000000..1bfdd43214
--- /dev/null
+++ b/tests/Alchemy/Tests/Phrasea/XSendFile/MappingTest.php
@@ -0,0 +1,59 @@
+ __DIR__ . '/../../../../files/',
+ 'mount-point' => '/protected/'
+ )
+ ));
+
+ $this->assertEquals('/protected/=/home/nlegoff/workspace/Phraseanet/tests/files/', (string) $mapping);
+ }
+
+ public function testMultiMapping()
+ {
+ $mapping = new Mapping(array(
+ array(
+ 'directory' => __DIR__ . '/../../../../files/',
+ 'mount-point' => '/protected/'
+ ),
+ array(
+ 'directory' => __DIR__ . '/../../../../',
+ 'mount-point' => '/uploads/'
+ ),
+ ));
+
+ $this->assertEquals('/protected/=/home/nlegoff/workspace/Phraseanet/tests/files/,/uploads/=/home/nlegoff/workspace/Phraseanet/tests/', (string) $mapping);
+ }
+
+ public function testMultiMappingWithANotExsistingDir()
+ {
+ $mapping = new Mapping(array(
+ array(
+ 'directory' => __DIR__ . '/../../../../files/',
+ 'mount-point' => '/protected/'
+ ),
+ array(
+ 'directory' => __DIR__ . '/../../../../do_not_exists',
+ 'mount-point' => '/test/'
+ ),
+ ));
+
+ $this->assertEquals('/protected/=/home/nlegoff/workspace/Phraseanet/tests/files/', (string) $mapping);
+ }
+
+ public function testEmptyMapping()
+ {
+ $mapping = new Mapping(array());
+
+ $this->assertEquals('', (string) $mapping);
+ }
+}
\ No newline at end of file