diff --git a/.travis.yml b/.travis.yml index ea2c758..a42973a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ php: env: - SYMFONY_VERSION=2.2.* - SYMFONY_VERSION=2.3.* + - SYMFONY_VERSION=2.4.* before_script: @@ -15,6 +16,7 @@ before_script: - cp .travis.composer.config.json ~/.composer/config.json - composer self-update - composer require symfony/framework-bundle:${SYMFONY_VERSION} + - composer require symfony/templating:${SYMFONY_VERSION} - composer install --dev notifications: diff --git a/Controller/ProtectedController.php b/Controller/ProtectedController.php new file mode 100644 index 0000000..6b7333d --- /dev/null +++ b/Controller/ProtectedController.php @@ -0,0 +1,79 @@ +get('iphp.filestore.mapping.factory') + ->getMappingConfig($request->get('_iphpfilestore_mapping')); + + + if (!$mappingConfig) throw $this->createNotFoundException('File not found'); + + + $protectedFileName = $mappingConfig['protected_dir'] . '/' . $path; + + + if (!file_exists($protectedFileName)) + throw $this->createNotFoundException('File ' . $mappingConfig['upload_path'] . '/' . $path . ' not found'); + + + if (!is_readable($protectedFileName)) + throw $this->createNotFoundException('File ' . $mappingConfig['upload_path'] . '/' . $path . ' not readable'); + + + + if ($this->getUser() && array_intersect($mappingConfig['protected_roles'],$this->getUser()->getRoles())) + { + $response = new BinaryFileResponse($protectedFileName); + return $response; + } + else + { + + + return $this->noAccessToFileAction(); + + + } + + + + } + + + + + + protected function noAccessToFileAction () + { + if (!$this->getUser()) return $this->unauthorizedAction(); + + else return $this->insufficientRightsAction(); + } + + + + protected function unauthorizedAction() + { + throw new HttpException(401, 'Unauthorized access.' ); + + return array(); + } + + + protected function insufficientRightsAction() + { + return $this->render ("IphpFileStoreBundle:Protected:insufficient.html.twig", array ())->setStatusCode(Response::HTTP_FORBIDDEN); + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 1f2381a..1b346b7 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -32,6 +32,16 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('upload_dir')->end() ->scalarNode('upload_path')->end() + + ->scalarNode('protected')->defaultFalse()->end() + ->scalarNode('protected_dir')->defaultNull()->end() + ->scalarNode('protected_controller')->defaultNull()->end() + ->scalarNode('protected_authredirect')->defaultTrue()->end() + ->arrayNode ('protected_roles') + ->prototype('scalar')->end() + ->defaultValue(array ('ROLE_ADMIN')) + ->end() + ->scalarNode('delete_on_remove')->defaultTrue()->end() ->scalarNode('overwrite_duplicates')->defaultFalse()->end() diff --git a/EventListener/UploaderListener.php b/EventListener/UploaderListener.php index 335ad2d..c48b80f 100644 --- a/EventListener/UploaderListener.php +++ b/EventListener/UploaderListener.php @@ -145,7 +145,7 @@ public function postFlush(EventArgs $args) if (!$this->deferredFiles[$obj]) continue; foreach ($this->deferredFiles[$obj] as $mapping) { - $fileData = $this->fileStorage->upload($mapping, $this->deferredFiles[$obj][$mapping]); + $fileData = $this->fileStorage->saveFile($mapping, $this->deferredFiles[$obj][$mapping]); $mapping->setFileDataPropertyValue($fileData); } @@ -170,7 +170,9 @@ public function preUpdate(\Doctrine\Common\EventArgs $args) $file = $mapping->getFileUploadPropertyValue(); $currentFileData = $this->dataStorage->currentFieldData($mapping->getFileDataPropertyName(), $args); - $currentFileName = $currentFileData ? $mapping->resolveFileName($currentFileData['fileName']) : null; + $currentFileName = $currentFileData ? + $mapping->resolveFileName($currentFileData['fileName'], + isset($currentFileData['protected']) && $currentFileData['protected'] ? true : false) : null; //If no new file @@ -179,6 +181,8 @@ public function preUpdate(\Doctrine\Common\EventArgs $args) if ($currentFileData) { if (!$this->fileStorage->fileExists($currentFileName)) { + + //try to restore file by web path $fileNameByWebDir = $_SERVER['DOCUMENT_ROOT'].$currentFileData['path']; if ($this->fileStorage->fileExists($fileNameByWebDir)) @@ -199,14 +203,37 @@ public function preUpdate(\Doctrine\Common\EventArgs $args) } //uploaded file has deleted status else if ($file instanceof \Iphp\FileStoreBundle\File\File && $file->isDeleted()) { if ($this->fileStorage->removeFile($currentFileName)) $mapping->setFileDataPropertyValue(null); - } else { + } + + + //changed protect attribute +/* else if ($file instanceof \Iphp\FileStoreBundle\File\File && $file->isProtected()) { + + + $fileData = $this->fileStorage->move($mapping, $file); + $mapping->setFileDataPropertyValue($fileData); + + die ('Protect in action!'); + //if ($this->fileStorage->removeFile($currentFileName)) $mapping->setFileDataPropertyValue(null); + + + + + }*/ + + + else { //Old value (file) exits and uploaded new file if ($currentFileData && !$this->fileStorage->isSameFile($file, $currentFileName)) //before upload new file delete old file $this->fileStorage->removeFile($currentFileName); - $fileData = $this->fileStorage->upload($mapping, $file); + $fileData = $this->fileStorage->saveFile ($mapping, $file); + + + + $mapping->setFileDataPropertyValue($fileData); } } diff --git a/File/File.php b/File/File.php index 6785f18..83ddbc7 100644 --- a/File/File.php +++ b/File/File.php @@ -8,12 +8,18 @@ namespace Iphp\FileStoreBundle\File; -use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\File\File as BaseFile; -class File extends UploadedFile +class File extends BaseFile implements LocalFileInterface { protected $deleted = false; + protected $protected = false; + + protected $originalName; + + protected $saveSource = true; + static function createEmpty() { return new File(); @@ -22,14 +28,14 @@ static function createEmpty() public function __construct($path = null, $originalName = null, $mimeType = null, $size = null, $error = null, $test = false) { - if ($path !== null) - parent::__construct($path, $originalName, $mimeType,$size, $error, $test); + if ($path !== null) + parent::__construct($path, $originalName, $mimeType, $size, $error, $test); } - public function delete () + public function delete() { - // parent::__construct (__FILE__,'just for pass validator'); + // parent::__construct (__FILE__,'just for pass validator'); $this->deleted = true; return $this; } @@ -40,8 +46,58 @@ public function isDeleted() } - function isValid() + /** + * @param boolean $protected + */ + public function setProtected($protected) + { + $this->protected = $protected; + return $this; + } + + /** + * @return boolean + */ + public function isProtected() { - return true; + return $this->protected; } + + /** + * @param mixed $originalName + */ + public function setOriginalName($originalName) + { + $this->originalName = $originalName; + return $this; + } + + /** + * @return mixed + */ + public function getOriginalName() + { + return $this->originalName; + } + + /** + * @param boolean $saveSource + */ + public function setSaveSource($saveSource) + { + $this->saveSource = $saveSource; + return $this; + } + + /** + * @return boolean + */ + public function getSaveSource() + { + return $this->saveSource; + } + + + + } diff --git a/File/FileInterface.php b/File/FileInterface.php new file mode 100644 index 0000000..e8c7468 --- /dev/null +++ b/File/FileInterface.php @@ -0,0 +1,14 @@ + + */ + +namespace Iphp\FileStoreBundle\File; + + +interface FileInterface +{ + function isProtected(); + function setProtected($protected); + +} \ No newline at end of file diff --git a/File/FormFile.php b/File/FormFile.php new file mode 100644 index 0000000..ed89272 --- /dev/null +++ b/File/FormFile.php @@ -0,0 +1,11 @@ + + */ + +namespace Iphp\FileStoreBundle\File; + + +class FormFile { + +} \ No newline at end of file diff --git a/File/LocalFileInterface.php b/File/LocalFileInterface.php new file mode 100644 index 0000000..281946b --- /dev/null +++ b/File/LocalFileInterface.php @@ -0,0 +1,23 @@ + + */ + +namespace Iphp\FileStoreBundle\File; + + +interface LocalFileInterface extends FileInterface +{ + + function isDeleted(); + + function delete(); + + function getFilename(); + + function getMimeType(); + + function getOriginalName(); + + function getSaveSource(); +} \ No newline at end of file diff --git a/File/UploadedFile.php b/File/UploadedFile.php new file mode 100644 index 0000000..7cfcabb --- /dev/null +++ b/File/UploadedFile.php @@ -0,0 +1,48 @@ + + */ +class UploadedFile extends BaseUploadedFile implements UploadedFileInterface +{ + + + protected $protected = false; + + + static function createFrom(BaseUploadedFile $baseUploadedFile, $test = false) + { + return new UploadedFile ( + $baseUploadedFile->getPathname(), + $baseUploadedFile->getClientOriginalName(), + $baseUploadedFile->getClientMimeType(), + $baseUploadedFile->getClientSize(), + $baseUploadedFile->getError(), + $test); + + + } + + /** + * @param boolean $protected + */ + public function setProtected($protected) + { + $this->protected = $protected; + return $this; + } + + /** + * @return boolean + */ + public function isProtected() + { + return $this->protected; + } + + +} \ No newline at end of file diff --git a/File/UploadedFileInterface.php b/File/UploadedFileInterface.php new file mode 100644 index 0000000..5ef246a --- /dev/null +++ b/File/UploadedFileInterface.php @@ -0,0 +1,17 @@ + + */ + +namespace Iphp\FileStoreBundle\File; + + +interface UploadedFileInterface extends FileInterface +{ + function isProtected(); + function setProtected($protected); + + function getClientOriginalName(); + function getClientMimeType(); + +} \ No newline at end of file diff --git a/FileStorage/FileStorageInterface.php b/FileStorage/FileStorageInterface.php index 09275b2..5789eb4 100644 --- a/FileStorage/FileStorageInterface.php +++ b/FileStorage/FileStorageInterface.php @@ -2,8 +2,12 @@ namespace Iphp\FileStoreBundle\FileStorage; +use Iphp\FileStoreBundle\File\FileInterface; +use Iphp\FileStoreBundle\File\LocalFileInterface; +use Iphp\FileStoreBundle\File\UploadedFileInterface; use Iphp\FileStoreBundle\Mapping\PropertyMapping; use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\UploadedFile; /** * StorageInterface. @@ -20,7 +24,12 @@ interface FileStorageInterface * * @param object $obj The object. */ - public function upload(PropertyMapping $mapping, File $file); + public function saveUploadedFile (PropertyMapping $mapping, UploadedFile $file); + + public function saveLocalFile (PropertyMapping $mapping, File $file); + + + public function saveFile (PropertyMapping $mapping, File $file); /** diff --git a/FileStorage/FileSystemStorage.php b/FileStorage/FileSystemStorage.php index d351d46..62b39da 100644 --- a/FileStorage/FileSystemStorage.php +++ b/FileStorage/FileSystemStorage.php @@ -2,6 +2,9 @@ namespace Iphp\FileStoreBundle\FileStorage; +use Iphp\FileStoreBundle\File\FileInterface; +use Iphp\FileStoreBundle\File\LocalFileInterface; +use Iphp\FileStoreBundle\File\UploadedFileInterface; use Iphp\FileStoreBundle\FileStorage\FileStorageInterface; use Iphp\FileStoreBundle\Mapping\PropertyMapping; @@ -57,22 +60,6 @@ public function setSameFileChecker (\Closure $checker) } - - - protected function getOriginalName(File $file) - { - return $file instanceof UploadedFile ? - $file->getClientOriginalName() : $file->getFilename(); - } - - - protected function getMimeType(File $file) - { - return $file instanceof UploadedFile ? - $file->getClientMimeType() : $file->getMimeType(); - } - - public function isSameFile (File $file, $fullFileName) { return call_user_func( @@ -123,38 +110,70 @@ protected function checkDirectory ($directory) } - /** - * {@inheritDoc} - * File may be \Symfony\Component\HttpFoundation\File\File or \Symfony\Component\HttpFoundation\File\UploadedFile - */ - public function upload(PropertyMapping $mapping, File $file) + + + public function saveLocalFile(PropertyMapping $mapping, File $file) { - $originalName = $this->getOriginalName($file); - $mimeType = $this->getMimeType($file); + $originalName = $file instanceof FileInterface && $file->getOriginalName() ? + $file->getOriginalName() : $file->getFilename(); + $mimeType = $file->getMimeType(); + + $fileProtected = $file instanceof FileInterface && $file->isProtected(); //transform filename and directory name if namer exists in mapping definition - list ($fileName, $webPath) = $mapping->prepareFileName($originalName, $this); - $fullFileName = $mapping->resolveFileName($fileName); + list ($fileName, $webPath) = $mapping->prepareFileName($originalName, $this, $fileProtected ); + + $fullFileName = $mapping->resolveFileName($fileName, $fileProtected ); + //check if file already placed in needed position if (!$this->isSameFile($file, $fullFileName)) { $fileInfo = pathinfo($fullFileName); + $this->checkDirectory($fileInfo['dirname']); - if ($file instanceof UploadedFile) + if ( $file instanceof FileInterface && $file->getSaveSource() == false) $file->move($fileInfo['dirname'], $fileInfo['basename']); else { - $this->checkDirectory($fileInfo['dirname']); - $file->move($fileInfo['dirname'], $fileInfo['basename']); + $this->copyFile($file->getPathname(), $fileInfo['dirname'], $fileInfo['basename']); } - else $this->copyFile($file->getPathname(), $fileInfo['dirname'], $fileInfo['basename']); } + return $this->prepareFileData($fileName, $originalName, $mimeType, $fullFileName, $webPath, $fileProtected); + } + + + public function saveUploadedFile(PropertyMapping $mapping, UploadedFile $file) + { + $originalName = $file->getClientOriginalName(); + $mimeType = $file->getClientMimeType(); + + //transform filename and directory name if namer exists in mapping definition + list ($fileName, $webPath) = $mapping->prepareFileName($originalName, $this); + + $fileProtected = $file instanceof UploadedFileInterface && $file->isProtected(); + + $fullFileName = $mapping->resolveFileName($fileName, $fileProtected ); + + + $fileInfo = pathinfo($fullFileName); + + $this->checkDirectory($fileInfo['dirname']); + $file->move($fileInfo['dirname'], $fileInfo['basename']); + + return $this->prepareFileData($fileName, $originalName, $mimeType, $fullFileName, $webPath, $fileProtected); + } + + + + protected function prepareFileData ($fileName, $originalName, $mimeType, $fullFileName, $webPath, $protected) + { $fileData = array( 'fileName' => $fileName, 'originalName' => $originalName, 'mimeType' => $mimeType, 'size' => filesize($fullFileName), - 'path' => $webPath + 'path' => $webPath, + 'protected' => $protected ? true : false ); if (!$fileData['path']) @@ -164,7 +183,7 @@ public function upload(PropertyMapping $mapping, File $file) $ext = substr($originalName,strrpos ($originalName,'.')+1); if ((in_array($fileData['mimeType'], array('image/png', 'image/jpeg', 'image/pjpeg')) || - in_array ($ext,array ('jpeg','jpg','png'))) + in_array ($ext,array ('jpeg','jpg','png'))) && function_exists('getimagesize') ) { list($width, $height, $type) = @getimagesize($fullFileName); @@ -177,6 +196,17 @@ public function upload(PropertyMapping $mapping, File $file) } + /** + * {@inheritDoc} + * File may be \Symfony\Component\HttpFoundation\File\File or \Symfony\Component\HttpFoundation\File\UploadedFile + */ + public function saveFile (PropertyMapping $mapping, File $file) + { + if ($file instanceof UploadedFile ) return $this->saveUploadedFile($mapping, $file); + else return $this->saveLocalFile($mapping, $file); + } + + /** * {@inheritDoc} */ @@ -198,10 +228,4 @@ public function fileExists($fullFileName) { return file_exists($fullFileName); } - - - - - - } diff --git a/Form/DataTransformer/FileDataTransformer.php b/Form/DataTransformer/FileDataTransformer.php index 2fe654f..3b0ec11 100644 --- a/Form/DataTransformer/FileDataTransformer.php +++ b/Form/DataTransformer/FileDataTransformer.php @@ -2,6 +2,7 @@ namespace Iphp\FileStoreBundle\Form\DataTransformer; +use Iphp\FileStoreBundle\File\File; use Symfony\Component\Form\DataTransformerInterface; use Iphp\FileStoreBundle\FileStorage\FileStorageInterface; use Iphp\FileStoreBundle\Mapping\PropertyMapping; @@ -38,9 +39,20 @@ public function setMapping(PropertyMapping $mapping) return $this; } + /** + * @return \Iphp\FileStoreBundle\Mapping\PropertyMapping + */ + public function getMapping() + { + return $this->mapping; + } + public function transform($fileDataFromDb) { + if (is_array($fileDataFromDb) && isset($fileDataFromDb['fileName'])) + $fileDataFromDb['protected'] = $fileDataFromDb['isprotected'] = isset($fileDataFromDb['protected']) && $fileDataFromDb['protected']; + return $fileDataFromDb; } @@ -52,16 +64,86 @@ public function transform($fileDataFromDb) */ public function reverseTransform($fileDataFromForm) { - if ($this->mapping && $fileDataFromForm['delete']) { + if (!$this->mapping) return $fileDataFromForm['file']; + + if ($fileDataFromForm['delete']) { + + $existFileProtected = isset($fileDataFromForm['protected']) && $fileDataFromForm['protected']; + //File may no exists try { - $this->fileStorage->removeFile($this->mapping->resolveFileName($fileDataFromForm['fileName'])); - + $this->fileStorage->removeFile( + $this->mapping->resolveFileName($fileDataFromForm['fileName'], $existFileProtected)); } catch (\Exception $e) { } } + + + if ($this->mapping->getProtected() == 'ondemand') { + + + $protected = isset($fileDataFromForm['isprotected']) && $fileDataFromForm['isprotected']; + + //transform to \Iphp\FileStoreBundle\File\UploadedFile with attribute protected = true + if ($fileDataFromForm['file']) { + $fileDataFromForm['file'] = \Iphp\FileStoreBundle\File\UploadedFile::createFrom($fileDataFromForm['file']); + $fileDataFromForm['file']->setProtected($protected); + + } else { + + //cur file data exists + if (isset($fileDataFromForm['fileName'])) { + + //print_r($fileDataFromForm); + + $setFileProtected = isset($fileDataFromForm['isprotected']) && $fileDataFromForm['isprotected']; + $existFileProtected = isset($fileDataFromForm['protected']) && $fileDataFromForm['protected']; + + + //change status of exists file + if ($setFileProtected != $existFileProtected) { + $existFileName = $this->mapping->resolveFileName($fileDataFromForm['fileName'], + $existFileProtected); + + + if ($this->fileStorage->fileExists($existFileName)) { + // print $existFileName.' '.$setFileProtected; + $fileDataFromForm['file'] = new File($existFileName); + $fileDataFromForm['file'] + ->setProtected($setFileProtected) + ->setOriginalName($fileDataFromForm['originalName']) + ->setSaveSource(false); + + + } + //exit(); + + } + //exit(); + + } + + + // + + + /* print_r($fileDataFromForm); + + print $fileDataFromForm['fileName'] . '-' . $protected; + exit(); + + + // Данные из текущего положения файла + $fileDataFromForm['file'] = new File($this->mapping->resolveFileName($fileDataFromForm['fileName'], + + isset($fileDataFromForm['fileName']) + )); + $fileDataFromForm['file']->setProtected($protected);*/ + } + } + return $fileDataFromForm['file']; } } diff --git a/Form/Type/FileType.php b/Form/Type/FileType.php index 2494823..15d0cc0 100644 --- a/Form/Type/FileType.php +++ b/Form/Type/FileType.php @@ -38,13 +38,15 @@ class FileType extends AbstractType /** * @var \Iphp\FileStoreBundle\DataStorage\DataStorageInterface */ - protected $dataStorage; + protected $dataStorage; /** * @var \Iphp\FileStoreBundle\FileStorage\FileStorageInterface */ protected $fileStorage; + protected $transformer; + public function __construct(PropertyMappingFactory $mappingFactory, DataStorageInterface $dataStorage, FileStorageInterface $fileStorage) @@ -52,6 +54,7 @@ public function __construct(PropertyMappingFactory $mappingFactory, $this->mappingFactory = $mappingFactory; $this->dataStorage = $dataStorage; $this->fileStorage = $fileStorage; + $this->transformer = new FileDataTransformer($this->fileStorage); } public function setDefaultOptions(OptionsResolverInterface $resolver) @@ -65,20 +68,38 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) } + public function buildView(FormView $view, FormInterface $form, array $options) + { + + $mapping = $this->transformer->getMapping(); + $view->vars['is_mapped'] = $mapping ? true : false; + + if (!$mapping) return; + + + // $view->vars['mappingProtected'] = $mapping->getProtected(); + } + /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { - - $transformer = new FileDataTransformer($this->fileStorage); - $subscriber = new FileTypeBindSubscriber($this->mappingFactory,$this->dataStorage, $transformer); + $subscriber = new FileTypeBindSubscriber( + $this->mappingFactory, + $this->dataStorage, + $this->transformer, + $builder->getFormFactory() + ); $builder->addEventSubscriber($subscriber); $builder->add('file', 'file', array('required' => false)) ->add('delete', 'checkbox', array('required' => false)) - ->addViewTransformer($transformer); + ->addViewTransformer($this->transformer); + + + //for sonata admin // ->addViewTransformer(new FileDataViewTransformer()); diff --git a/Form/Type/FileTypeBindSubscriber.php b/Form/Type/FileTypeBindSubscriber.php index 3e13d4b..6e7bd38 100644 --- a/Form/Type/FileTypeBindSubscriber.php +++ b/Form/Type/FileTypeBindSubscriber.php @@ -1,11 +1,13 @@ @@ -29,21 +31,72 @@ class FileTypeBindSubscriber implements EventSubscriberInterface */ private $dataStorage; + /** + * @var \Symfony\Component\Form\FormFactoryInterface + */ + private $formFactory; + + public function __construct(PropertyMappingFactory $mappingFactory, DataStorageInterface $dataStorage, - FileDataTransformer $transformer) + FileDataTransformer $transformer, + FormFactoryInterface $formFactory) { $this->mappingFactory = $mappingFactory; $this->dataStorage = $dataStorage; $this->transformer = $transformer; + $this->formFactory = $formFactory; } public static function getSubscribedEvents() { - return array(FormEvents::PRE_BIND => 'preBind'); + return array( + FormEvents::PRE_BIND => 'preBind', + FormEvents::POST_SET_DATA => 'postSet' + ); } + public function postSet(FormEvent $event) + { + $data = $event->getData(); + $form = $event->getForm(); + + //for first call event parent is null, cause of setData(null) in Form constructor. + if (!$form->getParent()) return; + $obj = $form->getParent()->getData(); + if (!$obj) return; + + $mapping = $this->mappingFactory->getMappingFromField($obj, + $this->dataStorage->getReflectionClass($obj), + $form->getName()); + if ($mapping) { + $this->transformer->setMapping($mapping); + + + + //print '---'.$mapping->getProtected(); + + if ($mapping->getProtected() == 'ondemand') { + + // print 55; + + $form->add($this->formFactory->createNamed('isprotected', 'checkbox', null, array( + + 'label' => 'Protected file', + 'required' => false + ))); + + /* $form->add ('isprotected', 'checkbox', array( + + 'label' => 'Protected file', + 'required' => false + ));*/ + } + } + + } + public function preBind(FormEvent $event) { @@ -54,7 +107,7 @@ public function preBind(FormEvent $event) //For oneToMany at SonataAdmin if (!$obj) return; - $mapping = $this->mappingFactory->getMappingFromField($obj, + $mapping = $this->mappingFactory->getMappingFromField($obj, $this->dataStorage->getReflectionClass($obj), $form->getName()); if ($mapping) $this->transformer->setMapping($mapping); diff --git a/Locator/FileLocator.php b/Locator/FileLocator.php new file mode 100644 index 0000000..86f5272 --- /dev/null +++ b/Locator/FileLocator.php @@ -0,0 +1,40 @@ + + */ +class FileLocator +{ + + + /** + * @var \Iphp\FileStoreBundle\Mapping\PropertyMappingFactory + */ + protected $propertyMappingFactory; + + function __construct(PropertyMappingFactory $propertyMappingFactory) + { + $this->propertyMappingFactory = $propertyMappingFactory; + } + + + function getFileFromEntity($entity, $field) + { + $mapping = $this->propertyMappingFactory->getMappingFromField($entity, new \ReflectionClass ($entity), $field); + if (!$mapping) return null; + + $fileName = $mapping->resolveFileName(); + if (!$fileName ) return null; + + return new File ($fileName); + } + + +} \ No newline at end of file diff --git a/Mapping/PropertyMapping.php b/Mapping/PropertyMapping.php index 482d724..6586193 100644 --- a/Mapping/PropertyMapping.php +++ b/Mapping/PropertyMapping.php @@ -16,8 +16,6 @@ */ class PropertyMapping { - - protected $obj; /** @@ -152,13 +150,6 @@ public function useDirectoryNamer($fileName, $clientOriginalName) isset($namer['params']) ? $namer['params'] : array()); -/* $subPath = call_user_func( - array($this->container->get($namer['service']), $method . 'Rename'), - $this, - $replaceMode ? $path : $fileName, - isset($namer['params']) ? $namer['params'] : array());*/ - - if ($replaceMode) $path = $subPath; else $path .= ($subPath ? '/' : '') . $subPath; } @@ -169,10 +160,9 @@ public function useDirectoryNamer($fileName, $clientOriginalName) } - public function needResolveCollision($fileName, FileStorageInterface $fileStorage) + public function needResolveCollision($fileName, FileStorageInterface $fileStorage, $protected = false) { - //print "\n -->".$fileName; - return !$this->isOverwriteDuplicates() && $fileStorage->fileExists($this->resolveFileName($fileName)); + return !$this->isOverwriteDuplicates() && $fileStorage->fileExists($this->resolveFileName($fileName,$protected)); } @@ -182,22 +172,23 @@ public function needResolveCollision($fileName, FileStorageInterface $fileStorag * @return array relative or full fileName and file path at web * @throws \Exception */ - public function prepareFileName($originalName, FileStorageInterface $fileStorage) + public function prepareFileName($originalName, FileStorageInterface $fileStorage, $protected = false) { + $fileName = $origName = $this->useFileNamer($originalName); $dirName = $this->useDirectoryNamer($fileName, $originalName); if (substr($dirName,-1) != '/') $dirName.='/'; $try = 0; - while ($this->needResolveCollision( $dirName . $fileName , $fileStorage)) { + while ($this->needResolveCollision( $dirName . $fileName , $fileStorage, $protected)) { + if ($try > 15) throw new \Exception ("Can't resolve collision for file " . $fileName); $fileName = $this->resolveFileCollision($origName, $originalName, ++$try); } - return array( //file system path ($this->isStoreFullDir() ? $this->getUploadDir() : '') . $dirName. $fileName , @@ -215,11 +206,9 @@ public function prepareFileName($originalName, FileStorageInterface $fileStorage */ public function resolveFileCollision($fileName, $clientOriginalName, $attempt = 1) { - if ($this->hasNamer()) { $firstNamer = current($this->config['namer']); - return $this->namerServiceInvoker->resolveCollision ($firstNamer['service'], $fileName, $attempt); } @@ -233,12 +222,35 @@ public function getUploadDir() } + public function getStoreDir ($protected = false) + { + return $this->getProtected() === true || $this->getProtected() == 'ondemand' && $protected ? + $this->getProtectedDir() : $this->getUploadDir(); + } + + public function getUploadPath() { return $this->config['upload_path']; } + public function getProtected() + { + return isset($this->config['protected']) ? $this->config['protected'] : null; + } + + + + public function getProtectedDir() + { + return $this->config['protected_dir']; + } + + + + + /** * Sets the configured configuration mapping. * @@ -344,15 +356,20 @@ public function getObj() } - public function resolveFileName($fileName = null) + public function resolveFileName($fileName = null, $protected = null) { if (!$fileName) { $fileData = $this->getFileDataPropertyValue(); - if ($fileData) $fileName = $fileData['fileName']; + if ($fileData) + { + $fileName = $fileData['fileName']; + $protected = isset($fileData['protected']) && $fileData['protected']; + } } if (!$fileName) return null; - $dir = $this->isStoreFullDir() ? '' : $this->getUploadDir(); + $dir = $this->isStoreFullDir() ? '' : $this->getStoreDir($protected); + return $dir . (substr($dir,-1) != '/' && substr($fileName,0,1) != '/' ? '/' : ''). $fileName; } @@ -363,4 +380,4 @@ public function getConfig() { return $this->config; } -} +} \ No newline at end of file diff --git a/Mapping/PropertyMappingFactory.php b/Mapping/PropertyMappingFactory.php index ebfb198..7c7f2e6 100644 --- a/Mapping/PropertyMappingFactory.php +++ b/Mapping/PropertyMappingFactory.php @@ -126,5 +126,11 @@ protected function createMapping($obj, \ReflectionClass $class, UploadableField } + function getMappingConfig ($mappingName) + { + return isset($this->mappingsConfig[$mappingName]) ? $this->mappingsConfig[$mappingName] : null; + } + + } diff --git a/Resources/config/routing.xml b/Resources/config/routing.xml deleted file mode 100644 index 4c9969a..0000000 --- a/Resources/config/routing.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - IphpFilestoreBundle:Default:index - - diff --git a/Resources/config/routing.yml b/Resources/config/routing.yml new file mode 100644 index 0000000..c09c46e --- /dev/null +++ b/Resources/config/routing.yml @@ -0,0 +1,3 @@ +_iphp_filestore: + resource: . + type: iphp_filestore diff --git a/Resources/config/services.xml b/Resources/config/services.xml index d48280f..a5ddc9d 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -6,16 +6,35 @@ + + + Iphp\FileStoreBundle\EventListener\UploaderListener + Iphp\FileStoreBundle\Mapping\PropertyMappingFactory + Iphp\FileStoreBundle\Form\Type\FileType Iphp\FileStoreBundle\Naming\DefaultNamer Iphp\FileStoreBundle\Naming\DefaultDirectoryNamer Iphp\FileStoreBundle\Naming\NamerServiceInvoker + Iphp\FileStoreBundle\Routing\ProtectFilesLoader + Iphp\FileStoreBundle\Driver\AnnotationDriver + Iphp\FileStoreBundle\FileStorage\FileSystemStorage + Iphp\FileStoreBundle\Locator\FileLocator + + + + + + %iphp.filestore.mappings% + + + + - + @@ -37,7 +56,7 @@ - @@ -45,7 +64,7 @@ - @@ -54,15 +73,22 @@ - + - + + + + + + + diff --git a/Resources/translations/messages.ru.xliff b/Resources/translations/messages.ru.xliff index 70ef4a9..22c1a42 100644 --- a/Resources/translations/messages.ru.xliff +++ b/Resources/translations/messages.ru.xliff @@ -11,6 +11,12 @@ Delete Удалить + + + Protected file + Защищенный файл + + diff --git a/Resources/views/Form/base_fields.html.twig b/Resources/views/Form/base_fields.html.twig new file mode 100644 index 0000000..303c3b4 --- /dev/null +++ b/Resources/views/Form/base_fields.html.twig @@ -0,0 +1,99 @@ +{% block iphp_file_widget %} + + {% set fileData = value %} + +
+ + {% block iphp_file_widget_file %} +
{{ form_widget(form.file) }} + + + {% if form.isprotected is defined %} + {{ form_row (form.isprotected, {'label_attr' : { 'style' :'width:auto;padding-right:10px' }})}} + {% endif %} + +
+ {% endblock iphp_file_widget_file %} + + + {% if not is_mapped %} + Mapping for this field is not defined + {% else %} + +
+ + + {# if was upload error value is uploadedFile and originalName no exists#} + {% if fileData and fileData.originalName is defined %} + + {% block iphp_file_widget_filedata %} + + {% set fileUrl = fileData.path %} + + {# |url_encode() #} + + + + + + + {% if fileData.width is defined %} + + + + + {% endif %} + + + +
+ {% set imageSrc = fileUrl %} + {% set isProtected = form.vars.value.protected %} + {% block iphp_file_widget_filedata_image %} + + {% endblock iphp_file_widget_filedata_image %} + +     + + + {% block iphp_file_widget_filedata_info %} + + + {{ fileData.fileName }} +
{{ fileData.size /1000 }} Kb + {{ fileData.width is defined ? ', ' ~ fileData.width ~ 'x' ~ fileData.height : "" }} + + {% if fileData.originalName != fileData.fileName %} +
{% trans %}Original file name{% endtrans %}: {{ fileData.originalName }} + {% endif %} + +

+ {% endblock iphp_file_widget_filedata_info %} + + + + + {% block iphp_file_widget_filedata_delete %} + {{ form_row (form.delete, {'label_attr' : { 'style' :'width:auto;padding-right:10px' }})}} + {% endblock iphp_file_widget_filedata_delete %} + +
+ + + {% endblock iphp_file_widget_filedata %} + + {% endif %} + +
+ {% endif %} + + + {# dump( value) #} +
+ + +{% endblock iphp_file_widget %} \ No newline at end of file diff --git a/Resources/views/Form/fields.html.twig b/Resources/views/Form/fields.html.twig index e224510..4fb5829 100644 --- a/Resources/views/Form/fields.html.twig +++ b/Resources/views/Form/fields.html.twig @@ -1,53 +1 @@ -{% block iphp_file_widget %} - - -{% set fileData = value %} -
- -
{{ form_widget(form.file) }}
- - {# if was upload error value is uploadedFile and originalName no exists#} - {% if fileData and fileData.originalName is defined %} - {% set fileUrl = fileData.path %} - {# |url_encode() #} - - - -
- - - {% if fileData.width is defined %} - - - {% endif %} - - - -
-     - - {{ fileData.fileName }} -
{{ fileData.size /1000}} Kb - {{ fileData.width is defined ? ', ' ~ fileData.width ~ 'x' ~ fileData.height : ""}} - - {% if fileData.originalName != fileData.fileName %} -
{% trans %}Original file name{% endtrans %}: {{ fileData.originalName }} - {% endif %} - -

- - - {{ form_row (form.delete, {'label_attr' : { 'style' :'width:auto;padding-right:10px'}})}} - -
-
- - {% endif %} - - - {# dump( value) #} -
- - -{% endblock iphp_file_widget%} \ No newline at end of file +{% extends 'IphpFileStoreBundle:Form:base_fields.html.twig' %} \ No newline at end of file diff --git a/Resources/views/Protected/insufficient.html.twig b/Resources/views/Protected/insufficient.html.twig new file mode 100644 index 0000000..14bc3ca --- /dev/null +++ b/Resources/views/Protected/insufficient.html.twig @@ -0,0 +1 @@ +{{ app.user.username }} has insufficient rights to access file {{ app.request.uri }} \ No newline at end of file diff --git a/Routing/ProtectFilesLoader.php b/Routing/ProtectFilesLoader.php new file mode 100644 index 0000000..4ce42c3 --- /dev/null +++ b/Routing/ProtectFilesLoader.php @@ -0,0 +1,56 @@ +mappings = $mappings; + } + + public function supports($resource, $type = null) + { + return $type === 'iphp_filestore'; + } + + public function load($resource, $type = null) + { + + $requirements = array('_method' => 'GET', /*'filter' => '[A-z0-9_\-]*',*/ + 'path' => '.+'); + $routes = new RouteCollection(); + foreach ($this->mappings as $name => $mapping) { + if (!$mapping['protected']) continue; + + $pattern = $mapping['upload_path']; + + $defaults = array( + '_iphpfilestore_mapping' => $name, + '_controller' => empty($mapping['protected_contriller']) ? + $this->defaultController : $mapping['protected_contriller'] + /*'filter' => $filter,*/ + ); + + + $routes->add('_iphpfilestore_' . $name, new Route( + $pattern . '/{path}', + $defaults, + $requirements/*, + $routeOptions*/ + )); + } + + return $routes; + + } +} diff --git a/Tests/DummyEntityProxyORM.php b/Tests/DummyEntityProxyORM.php index 7454ea0..477fc30 100644 --- a/Tests/DummyEntityProxyORM.php +++ b/Tests/DummyEntityProxyORM.php @@ -2,6 +2,7 @@ namespace Iphp\FileStoreBundle\Tests; +use Closure; use Doctrine\ORM\Proxy\Proxy; /** @@ -14,4 +15,81 @@ class DummyEntityProxyORM extends DummyEntity implements Proxy public function __load() { } public function __isInitialized() { } + + /** + * Sets the callback to be used when cloning the proxy. That initializer should accept + * a single parameter, which is the cloned proxy instance itself. + * + * @param Closure|null $cloner + * + * @return void + */ + public function __setCloner(Closure $cloner = null) + { + // TODO: Implement __setCloner() method. + } + + /** + * Retrieves the callback to be used when cloning the proxy. + * + * @see __setCloner + * + * @return Closure|null + */ + public function __getCloner() + { + // TODO: Implement __getCloner() method. + } + + /** + * Marks the proxy as initialized or not. + * + * @param boolean $initialized + * + * @return void + */ + public function __setInitialized($initialized) + { + // TODO: Implement __setInitialized() method. + } + + /** + * Retrieves the list of lazy loaded properties for a given proxy + * + * @return array Keys are the property names, and values are the default values + * for those properties. + */ + public function __getLazyProperties() + { + // TODO: Implement __getLazyProperties() method. + } + + /** + * Retrieves the initializer callback used to initialize the proxy. + * + * @see __setInitializer + * + * @return Closure|null + */ + public function __getInitializer() + { + // TODO: Implement __getInitializer() method. + } + + /** + * Sets the initializer callback to be used when initializing the proxy. That + * initializer should accept 3 parameters: $proxy, $method and $params. Those + * are respectively the proxy object that is being initialized, the method name + * that triggered initialization and the parameters passed to that method. + * + * @param Closure|null $initializer + * + * @return void + */ + public function __setInitializer(Closure $initializer = null) + { + // TODO: Implement __setInitializer() method. + } + + } diff --git a/Tests/EventListener/UploaderListenerTest.php b/Tests/EventListener/UploaderListenerTest.php index 46a8ed6..30f1f48 100644 --- a/Tests/EventListener/UploaderListenerTest.php +++ b/Tests/EventListener/UploaderListenerTest.php @@ -210,7 +210,7 @@ public function testPreUpdateNoFileNoCurrent() $this->dataStorage->expects($this->once())->method('recomputeChangeSet')->with($args); - $this->fileStorage->expects($this->never())->method('upload'); + $this->fileStorage->expects($this->never())->method('saveFile'); $this->fileStorage->expects($this->never())->method('removeFile'); @@ -263,7 +263,7 @@ public function testPreUpdateNoFileWithCurrentFileExists() /** - * Test the preUpdate method - With file, no current file + * Test the preUpdate method - With setted file, no current file */ public function testPreUpdateWithFileNoCurrent() { @@ -277,7 +277,7 @@ public function testPreUpdateWithFileNoCurrent() $this->fileStorage ->expects($this->once()) - ->method('upload') + ->method('saveFile') ->with($propertyMapping, $file) ->will($this->returnValue(array('fileName' => '111'))); @@ -293,6 +293,8 @@ public function testPreUpdateWithFileNoCurrent() $listener->preUpdate($args); $this->assertEquals($listener->getDeferredObjectNum(), 0); + + } @@ -314,7 +316,7 @@ public function testPreUpdateWithFileWithCurrentNoSame() $this->fileStorage ->expects($this->once()) - ->method('upload') + ->method('saveFile') ->with($propertyMapping, $file) ->will($this->returnValue(array('fileName' => 'NEW_NAME'))); @@ -374,7 +376,7 @@ public function testPreUpdateWithFileWithCurrentIsSame() $this->fileStorage ->expects($this->once()) - ->method('upload') + ->method('saveFile') ->with($propertyMapping, $file) ->will($this->returnValue(array('fileName' => 'CURRENT_NAME'))); @@ -440,7 +442,7 @@ public function testPreUpdateWithIphpDeletedFile() ->method('setFileDataPropertyValue') ->with(null); - $this->fileStorage->expects($this->never())->method('upload'); + $this->fileStorage->expects($this->never())->method('saveFile'); $this->dataStorage->expects($this->once())->method('recomputeChangeSet')->with($args); $listener = $this->getUploaderListener(); @@ -457,7 +459,7 @@ public function testPostFlushNoDeferred() { $args = Mocks::getEventArgsMock($this); - $this->fileStorage->expects($this->never())->method('upload'); + $this->fileStorage->expects($this->never())->method('saveFile'); $this->dataStorage->expects($this->never())->method('postFlush'); $listener = $this->getUploaderListener(); @@ -485,7 +487,7 @@ public function testPostFlushHasDeferred() $this->fileStorage->expects($this->once()) - ->method('upload') + ->method('saveFile') ->with($propertyMapping, $file) ->will($this->returnValue(array('fileName' => 'NEW_NAME'))); diff --git a/Tests/File/FileTest.php b/Tests/File/FileTest.php index 3c9ed4a..17332eb 100644 --- a/Tests/File/FileTest.php +++ b/Tests/File/FileTest.php @@ -23,7 +23,7 @@ function testCreateEmpty() $this->assertTrue ($file instanceof File); $this->assertSame ($file->isDeleted(), false); $this->assertSame ($file->getPath(), ''); - $this->assertSame ($file->isValid(), true); + //$this->assertSame ($file->isValid(), true); } diff --git a/Tests/FileStorage/FileSystemStorageTest.php b/Tests/FileStorage/FileSystemStorageTest.php index df9b441..3bf6f20 100644 --- a/Tests/FileStorage/FileSystemStorageTest.php +++ b/Tests/FileStorage/FileSystemStorageTest.php @@ -131,7 +131,7 @@ public function testUploadImageFileToExistingDir() $this->assertFileNotExists($this->targetImageFileExistingDir); - $fileData = $this->storage->upload($propertyMapping, $file); + $fileData = $this->storage->saveFile($propertyMapping, $file); $this->assertFileExists($this->uploadedImageFile); @@ -160,7 +160,7 @@ public function testUploadUploadedImageFileToExistingDir() $this->assertFileExists(dirname($this->targetImageFileExistingDir)); $filesize = filesize($this->uploadedImageFile); - $fileData = $this->storage->upload($propertyMapping, $uploadedFile); + $fileData = $this->storage->saveFile($propertyMapping, $uploadedFile); $this->assertFileExists($this->targetImageFileExistingDir); $this->assertFileNotExists($this->uploadedImageFile); $this->assertTrue(filesize($this->targetImageFileExistingDir) == $filesize); @@ -172,7 +172,8 @@ public function testUploadUploadedImageFileToExistingDir() 'originalName' => '123.jpg', 'mimeType' => 'image/jpeg', 'size' => $filesize, - 'path' => '/images/123.jpg' + 'path' => '/images/123.jpg', + 'protected' => false ); if (function_exists('getimagesize')) { @@ -205,7 +206,7 @@ public function testUploadImageFileToNewDir() $this->assertFileNotExists(dirname($this->targetImageFileNewDir)); $filesize = filesize($this->uploadedImageFile); - $fileData = $this->storage->upload($propertyMapping, $file); + $fileData = $this->storage->saveFile($propertyMapping, $file); $this->assertFileExists(dirname($this->targetImageFileNewDir)); $this->assertFileExists($this->targetImageFileNewDir); @@ -217,7 +218,8 @@ public function testUploadImageFileToNewDir() 'originalName' => '123.jpg', 'mimeType' => 'image/jpeg', 'size' => $filesize, - 'path' => '/images/new/123.jpg' + 'path' => '/images/new/123.jpg', + 'protected' => false ); if (function_exists('getimagesize')) { @@ -259,7 +261,7 @@ public function testUploadUploadedImageFileToExistingReadonlyDir() return false; }); - $fileData = $this->storage->upload($propertyMapping, $uploadedFile); + $fileData = $this->storage->saveFile($propertyMapping, $uploadedFile); } @@ -289,7 +291,7 @@ public function testUploadImageFileToExistingReadonlyDir() return false; }); - $fileData = $this->storage->upload($propertyMapping, $uploadedFile); + $fileData = $this->storage->saveFile($propertyMapping, $uploadedFile); } diff --git a/Tests/Form/Type/FileTypeBindSubscriberTest.php b/Tests/Form/Type/FileTypeBindSubscriberTest.php index 4d80f5c..c1fd946 100644 --- a/Tests/Form/Type/FileTypeBindSubscriberTest.php +++ b/Tests/Form/Type/FileTypeBindSubscriberTest.php @@ -18,7 +18,7 @@ class FileTypeBindSubscriberTest extends \PHPUnit_Framework_TestCase /** - * @var \Iphp\FileStoreBundle\Mapping\PropertyMappingFactory $propertyMappingfactory + * @var \Iphp\FileStoreBundle\Mapping\PropertyMappingFactory $propertyMappingfactory */ protected $propertyMappingFactory; @@ -46,12 +46,21 @@ class FileTypeBindSubscriberTest extends \PHPUnit_Framework_TestCase protected $transformer; + /** + * @var \Symfony\Component\Form\FormFactoryInterface + */ + protected $formFactory; + + public function setUp() { $this->dataStorage = Mocks::getDataStorageMock($this); $this->driver = Mocks::getAnnotationDriverMock($this); $this->namerServiceInvoker = Mocks::getNamerServiceInvokerMock($this); + $this->formFactory = $this->getMockBuilder('Symfony\Component\Form\FormFactoryInterface') + ->disableOriginalConstructor() + ->getMock(); $this->propertyMappingFactory = Mocks::getPropertyMappingFactoryMock($this, $this->namerServiceInvoker, $this->driver); @@ -61,13 +70,17 @@ public function setUp() ->disableOriginalConstructor() ->getMock(); - $this->subscriber = new FileTypeBindSubscriber ($this->propertyMappingFactory, $this->dataStorage, $this->transformer); + $this->subscriber = new FileTypeBindSubscriber ( + $this->propertyMappingFactory, $this->dataStorage, $this->transformer, $this->formFactory); } function testSubscribedEvents() { - $this->assertSame(FileTypeBindSubscriber::getSubscribedEvents(), array(FormEvents::PRE_BIND => 'preBind')); + $this->assertSame(FileTypeBindSubscriber::getSubscribedEvents(), array( + FormEvents::PRE_BIND => 'preBind', + FormEvents::POST_SET_DATA => 'postSet' + )); } diff --git a/Tests/Form/Type/FileTypeTest.php b/Tests/Form/Type/FileTypeTest.php index 69e7577..848df78 100644 --- a/Tests/Form/Type/FileTypeTest.php +++ b/Tests/Form/Type/FileTypeTest.php @@ -46,6 +46,11 @@ class FileTypeTest extends \PHPUnit_Framework_TestCase */ protected $fileStorage; + /** + * @var \Symfony\Component\Form\FormFactoryInterface + */ + protected $formFactory; + function setUp() { @@ -55,11 +60,15 @@ function setUp() $this->namerServiceInvoker = Mocks::getNamerServiceInvokerMock($this); + $this->formFactory = $this->getMockBuilder('Symfony\Component\Form\FormFactoryInterface') + ->disableOriginalConstructor() + ->getMock(); $this->propertyMappingfactory = Mocks::getPropertyMappingFactoryMock($this, $this->namerServiceInvoker, $this->driver /*, DummyEntity::getDefaultFileStoreConfig()*/); - $this->fileType = new FileType ($this->propertyMappingfactory, $this->dataStorage, $this->fileStorage); + $this->fileType = new FileType ( + $this->propertyMappingfactory, $this->dataStorage, $this->fileStorage, $this->formFactory ); } @@ -93,7 +102,8 @@ function testBuildForm() $transformer = new FileDataTransformer($this->fileStorage); - $subscriber = new FileTypeBindSubscriber($this->propertyMappingfactory, $this->dataStorage, $transformer); + $subscriber = new FileTypeBindSubscriber( + $this->propertyMappingfactory, $this->dataStorage, $transformer, $this->formFactory); $formBuilder->expects($this->once()) @@ -110,6 +120,12 @@ function testBuildForm() ->method('add') ->will($this->returnValue($formBuilder)); + $formBuilder->expects($this->once()) + ->method('getFormFactory') + ->will($this->returnValue($this->formFactory)); + + + $this->fileType->buildForm($formBuilder, array()); } diff --git a/Tests/Functional/AppKernel.php b/Tests/Functional/AppKernel.php index e2ddfbe..ac4ff32 100644 --- a/Tests/Functional/AppKernel.php +++ b/Tests/Functional/AppKernel.php @@ -38,6 +38,7 @@ public function registerBundles() new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \Doctrine\Bundle\DoctrineBundle\DoctrineBundle(), new \Symfony\Bundle\TwigBundle\TwigBundle(), + new \Symfony\Bundle\SecurityBundle\SecurityBundle(), new \Iphp\FileStoreBundle\IphpFileStoreBundle(), new \Iphp\FileStoreBundle\Tests\Functional\TestBundle\TestBundle(), diff --git a/Tests/Functional/FileProtectedOnDemandSaveTest.php b/Tests/Functional/FileProtectedOnDemandSaveTest.php new file mode 100644 index 0000000..8b771b1 --- /dev/null +++ b/Tests/Functional/FileProtectedOnDemandSaveTest.php @@ -0,0 +1,101 @@ + + */ +class FileProtectedOnDemandSaveTest extends BaseTestCase +{ + + + /** + * test saving entity with file property from parent abstract uploadable class + */ + public function testFileSaveUpload() + { + $client = $this->createClient(); + $this->importDatabaseSchema(); + + + $file = new FileProtected(); + + //create not protected file (ondemand by default - not protected ) + $existsFile = new \Symfony\Component\HttpFoundation\File\File (__DIR__ . '/../Fixtures/files/text.txt'); + + $file->setTitle('new unprotected file') + ->setDate(new \DateTime('2013-04-04')) + ->setFileOndemand($existsFile); + + $this->getEntityManager()->persist($file); + $this->getEntityManager()->flush(); + + + $mappingData = $this->getContainer()->get("iphp.filestore.mapping.factory")->getMappingConfig('file_protected_ondemand'); + + $uploadedFileData = $file->getFileOndemand(); + + + $this->assertSame($uploadedFileData, array( + 'fileName' => '/FileProtected/fileOndemand/2013/1.txt', + 'originalName' => 'text.txt', + 'mimeType' => 'text/plain', + 'size' => 9, + 'path' => '/file/FileProtected/fileOndemand/2013/1.txt', + 'protected' => false + )); + + + $this->assertFileExists($mappingData['upload_dir'] . $uploadedFileData['fileName']); + $this->assertFileNotExists($mappingData['protected_dir'] . $uploadedFileData['fileName']); + + + unset($file); + + + //create protected + $protectedFile = new \Iphp\FileStoreBundle\File\File(__DIR__ . '/../Fixtures/files/text.txt'); + $protectedFile->setProtected(true); + + $file = new FileProtected(); + $file->setTitle('new protected file') + ->setDate(new \DateTime('2013-04-04')) + ->setFileOndemand( $protectedFile ); + + $this->getEntityManager()->persist($file); + $this->getEntityManager()->flush(); + + + $uploadedFileData = $file->getFileOndemand(); + + $this->assertSame($uploadedFileData, array( + 'fileName' => '/FileProtected/fileOndemand/2013/2.txt', + 'originalName' => 'text.txt', + 'mimeType' => 'text/plain', + 'size' => 9, + 'path' => '/file/FileProtected/fileOndemand/2013/2.txt', + 'protected' => true + )); + + + $this->assertFileNotExists($mappingData['upload_dir'] . $uploadedFileData['fileName']); + $this->assertFileExists($mappingData['protected_dir'] . $uploadedFileData['fileName']); + + + $this->getEntityManager()->clear(); + $this->getKernel()->shutdown(); + + + + + } + +} \ No newline at end of file diff --git a/Tests/Functional/FileProtectedSaveTest.php b/Tests/Functional/FileProtectedSaveTest.php new file mode 100644 index 0000000..9027c80 --- /dev/null +++ b/Tests/Functional/FileProtectedSaveTest.php @@ -0,0 +1,114 @@ + + */ +class FileProtectedSaveTest extends BaseTestCase +{ + + + /** + * test saving entity with file property from parent abstract uploadable class + */ + public function testFileSaveUpload() + { + $client = $this->createClient(); + $this->importDatabaseSchema(); + + + $file = new FileProtected(); + + $existsFile = new File( + __DIR__ . '/../Fixtures/files/text.txt'); + + + + $file->setTitle('new protected file') + ->setDate(new \DateTime('2013-04-04')) + ->setFile($existsFile); + + + $this->getEntityManager()->persist($file); + $this->getEntityManager()->flush(); + + + $mappingData = $this->getContainer()->get ("iphp.filestore.mapping.factory")->getMappingConfig ('file_protected'); + + + + $uploadedFileData = $file->getFile(); + + $this->assertSame($uploadedFileData, array( + 'fileName' => '/FileProtected/file/2013/1.txt', + 'originalName' => 'text.txt', + 'mimeType' => 'text/plain', + 'size' => 9, + 'path' => '/file/FileProtected/file/2013/1.txt', + 'protected' => false + )); + + + $this->assertFileExists($mappingData['protected_dir'].$uploadedFileData['fileName']); + + + + unset($file); + $this->getEntityManager()->clear(); + $this->getKernel()->shutdown(); + + + + //request protected file, client has no authorization + $crawler = $client->request('GET', $uploadedFileData['path'] ); + + + // print_r ($client->getResponse()); + + $this->assertEquals( + 401, + $client->getResponse()->getStatusCode() + ); + + + + + + + + //client with admin auth (ROLE_ADMIN) + $adminClient = $this->createClient(array(), array( + 'PHP_AUTH_USER' => 'admin', + 'PHP_AUTH_PW' => 'adminpass')); + + + $adminClient->request('GET', $uploadedFileData['path'] ); + + $this->assertTrue ( $adminClient->getResponse()->isOk() ); + $this->assertInstanceOf('Symfony\\Component\\HttpFoundation\\BinaryFileResponse', $adminClient->getResponse()); + + + $file = $adminClient->getResponse()->getFile(); + $this->assertEquals($mappingData['protected_dir'].$uploadedFileData['fileName'], $file->getPathname()); + + + //client without admin auth (NO ROLE_ADMIN ) + $userClient = $this->createClient(array(), array( + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'userpass')); + + $userClient->request('GET', $uploadedFileData['path'] ); + $this->assertTrue ( $userClient->getResponse()->isForbidden() ); + + //createFromprint_r ($userClient->getResponse()); + + } + +} \ No newline at end of file diff --git a/Tests/Functional/FileSaveTest.php b/Tests/Functional/FileSaveTest.php index d19e9da..e963ccb 100644 --- a/Tests/Functional/FileSaveTest.php +++ b/Tests/Functional/FileSaveTest.php @@ -45,6 +45,7 @@ public function testFileSaveUpload() 'mimeType' => 'text/plain', 'size' => 9, 'path' => '/file/File/file/2013/1.txt', + 'protected' => false )); unset($file); @@ -83,6 +84,7 @@ public function testFileSaveUpload() 'mimeType' => 'text/plain', 'size' => 9, 'path' => '/other/uploads/1/new-file.txt', + 'protected' => false )); diff --git a/Tests/Functional/ImageEditTest.php b/Tests/Functional/ImageEditTest.php index 32cb029..bf6d6c1 100644 --- a/Tests/Functional/ImageEditTest.php +++ b/Tests/Functional/ImageEditTest.php @@ -38,8 +38,10 @@ public function testImageEdit() 'mimeType' => 'image/jpeg', 'size' => $existsFile->getSize(), 'path' => '/photo/2013/04/front-images-list.jpeg', + 'protected' => false, 'width' => 445, 'height' => 531 + )); $crawler = $client->request('GET', '/edit/' . $photoLoaded->getId() . '/'); diff --git a/Tests/Functional/ImageUploadProtectedTest.php b/Tests/Functional/ImageUploadProtectedTest.php new file mode 100644 index 0000000..b7d4f99 --- /dev/null +++ b/Tests/Functional/ImageUploadProtectedTest.php @@ -0,0 +1,87 @@ + + */ + +use Iphp\FileStoreBundle\Tests\Functional\TestBundle\Entity\PhotoProtected; + +class ImageUploadProtectedTest extends BaseTestCase +{ + + + public function testImageUpload() + { + $client = $this->createClient(); + $this->importDatabaseSchema(); + + $client->enableProfiler(); + $crawler = $client->request('GET', '/list-protected/'); + + + // print_r ($client->getResponse()); + + // exit(); + $this->assertTrue($client->getResponse()->isSuccessful()); + //Photos not uploaded yet + $this->assertSame($crawler->filter('div.photo')->count(), 0); + + + $client->enableProfiler(); + + $fileToUpload = new \Symfony\Component\HttpFoundation\File\UploadedFile( + __DIR__ . '/../Fixtures/images/sonata-admin-iphpfile.jpeg', 'sonata-admin-iphpfile.jpeg'); + + + $client->submit($crawler->selectButton('Upload')->form(), array( + 'title' => 'Some protected title', + 'photo' => $fileToUpload, + 'date[year]' => '2013', + 'date[month]' => '3', + 'date[day]' => '15' + )); + + + $crawler = $client->followRedirect(); + + //added 1 photo + $this->assertSame($crawler->filter('div.photo')->count(), 1); + + + $photos = $this->getEntityManager()->getRepository('TestBundle:PhotoProtected')->findAll(); + $this->assertSame(sizeof($photos), 1); + $photo = $photos[0]; + $this->assertSame($photo->getTitle(), 'Some protected title'); + + //path to images dir and directory naming config in Tests/Functional/config/default.yml + $this->assertSame($photo->getPhoto(), array( + + 'fileName' => '/PhotoProtected/photo/sonata-admin-iphpfile.jpeg', + 'originalName' => 'sonata-admin-iphpfile.jpeg', + 'mimeType' => 'application/octet-stream', + 'size' => $fileToUpload->getSize(), + 'path' => '/file/PhotoProtected/photo/sonata-admin-iphpfile.jpeg', + 'protected' => false, + 'width' => 671, + 'height' => 487 + + )); + + + $uploadedFile = $this->getContainer()->get("iphp.filestore.file_locator")->getFileFromEntity($photo, 'photo'); + + + // print_r ( $uploadedFile ); + + $mappingData = $this->getContainer()->get("iphp.filestore.mapping.factory")->getMappingConfig('photo_protected'); + + $this->assertSame(substr($uploadedFile->getPathname(), 0, strlen($mappingData['protected_dir'])), + $mappingData['protected_dir']); + + // $this->assertSame ($uploadedFile->getPathname(), + } + + +} \ No newline at end of file diff --git a/Tests/Functional/ImageUploadTest.php b/Tests/Functional/ImageUploadTest.php index 598bf3e..98a1ee5 100644 --- a/Tests/Functional/ImageUploadTest.php +++ b/Tests/Functional/ImageUploadTest.php @@ -18,7 +18,7 @@ public function testImageUpload() $this->importDatabaseSchema(); $client->enableProfiler(); - $crawler = $client->request('GET', '/'); + $crawler = $client->request('GET', '/list/'); $this->assertTrue($client->getResponse()->isSuccessful()); //Photos not uploaded yet @@ -58,8 +58,10 @@ public function testImageUpload() 'mimeType' => 'application/octet-stream', 'size' => $fileToUpload->getSize(), 'path' => '/photo/2013/03/sonata-admin-iphpfile.jpeg', + 'protected' => false, 'width' => 671, 'height' => 487 + )); } diff --git a/Tests/Functional/TestBundle/Controller/DefaultController.php b/Tests/Functional/TestBundle/Controller/DefaultController.php index c309784..23ace3b 100644 --- a/Tests/Functional/TestBundle/Controller/DefaultController.php +++ b/Tests/Functional/TestBundle/Controller/DefaultController.php @@ -2,6 +2,7 @@ namespace Iphp\FileStoreBundle\Tests\Functional\TestBundle\Controller; +use Iphp\FileStoreBundle\Tests\Functional\TestBundle\Entity\PhotoProtected; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Iphp\FileStoreBundle\Tests\Functional\TestBundle\Entity\Photo; @@ -73,4 +74,38 @@ public function editAction(Request $request, $id) } + + + public function indexProtectedAction(Request $request) + { + $em = $this->getDoctrine()->getManager(); + $photo = new PhotoProtected(); + + $uploadForm = $this->get('form.factory')->createNamedBuilder(null, 'form', $photo, + array('csrf_protection' => false)) + ->add('title') + ->add('date', 'date') + //Using standart field type + ->add('photo', 'file') + // ->add ('photoOndemand','iphp_file') + ->getForm(); + + + if ($request->isMethod('POST')) { + $uploadForm->bind($request); + + if ($uploadForm->isValid()) { + + $em->persist($photo); + $em->flush(); + return $this->redirect($this->generateUrl('photo_index_protected')); + } else print "\n\n" . $uploadForm->getErrorsAsString(); + } + + return $this->render('TestBundle:Photo:index.html.twig', array( + 'uploadForm' => $uploadForm->createView(), + 'photos' => $em->getRepository('TestBundle:PhotoProtected')->findAll() + )); + } + } diff --git a/Tests/Functional/TestBundle/Entity/Photo.php b/Tests/Functional/TestBundle/Entity/Photo.php index 5672dae..a4694aa 100644 --- a/Tests/Functional/TestBundle/Entity/Photo.php +++ b/Tests/Functional/TestBundle/Entity/Photo.php @@ -21,27 +21,27 @@ class Photo * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") */ - private $id; + protected $id; /** * @ORM\Column(type="string") * @var string */ - private $title; + protected $title; /** * @var \Datetime * @ORM\Column(type="datetime") */ - private $date; + protected $date; /** * @ORM\Column(type="array") * @Assert\Image( maxSize="20M") * @FileStore\UploadableField(mapping="photo") **/ - private $photo; + protected $photo; /** * @return integer diff --git a/Tests/Functional/TestBundle/Entity/PhotoProtected.php b/Tests/Functional/TestBundle/Entity/PhotoProtected.php new file mode 100644 index 0000000..ce0ed44 --- /dev/null +++ b/Tests/Functional/TestBundle/Entity/PhotoProtected.php @@ -0,0 +1,143 @@ + + **/ + +class PhotoProtected +{ + + /** + * @var integer + * @ORM\Id + * @ORM\Column(type="integer") + * @ORM\GeneratedValue(strategy="AUTO") + */ + protected $id; + + /** + * @ORM\Column(type="string") + * @var string + */ + protected $title; + + + /** + * @var \Datetime + * @ORM\Column(type="datetime") + */ + protected $date; + + /** + * @ORM\Column(type="array") + * @Assert\Image( maxSize="20M") + * @FileStore\UploadableField(mapping="photo_protected") + **/ + private $photo; + + + + /** + * @ORM\Column(type="array") + * @Assert\Image( maxSize="20M") + * @FileStore\UploadableField(mapping="photo_protected_ondemand") + **/ + private $photoOndemand; + + + + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + + /** + * @param string $title + */ + public function setTitle($title) + { + $this->title = $title; + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + + /** + * @param \Datetime $date + */ + public function setDate($date) + { + $this->date = $date; + return $this; + } + + /** + * @return \Datetime + */ + public function getDate() + { + return $this->date; + } + + + + /** + * @param mixed $photo + */ + public function setPhoto($photo) + { + $this->photo = $photo; + return $this; + } + + /** + * @return mixed + */ + public function getPhoto() + { + return $this->photo; + } + + /** + * @param mixed $photoOndemand + */ + public function setPhotoOndemand($photoOndemand) + { + $this->photoOndemand = $photoOndemand; + return $this; + } + + /** + * @return mixed + */ + public function getPhotoOndemand() + { + return $this->photoOndemand; + } + + + + + +} \ No newline at end of file diff --git a/Tests/Functional/TestXmlConfigBundle/Entity/File.php b/Tests/Functional/TestXmlConfigBundle/Entity/File.php index 07cd35b..de79f5f 100644 --- a/Tests/Functional/TestXmlConfigBundle/Entity/File.php +++ b/Tests/Functional/TestXmlConfigBundle/Entity/File.php @@ -46,7 +46,7 @@ public function getId() /** * @param string $title - * @return Photo + * @return File */ public function setTitle($title) { diff --git a/Tests/Functional/TestXmlConfigBundle/Entity/FileProtected.php b/Tests/Functional/TestXmlConfigBundle/Entity/FileProtected.php new file mode 100644 index 0000000..b610816 --- /dev/null +++ b/Tests/Functional/TestXmlConfigBundle/Entity/FileProtected.php @@ -0,0 +1,126 @@ +file = $file; + return $this; + } + + public function getFile() + { + return $this->file; + } + + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @param string $title + * @return File + */ + public function setTitle($title) + { + $this->title = $title; + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + + /** + * @param \Datetime $date + */ + public function setDate($date) + { + $this->date = $date; + return $this; + } + + /** + * @return \Datetime + */ + public function getDate() + { + return $this->date; + } + + /** + * @param mixed $fileOndemand + */ + public function setFileOndemand($fileOndemand) + { + $this->fileOndemand = $fileOndemand; + return $this; + } + + /** + * @return mixed + */ + public function getFileOndemand() + { + return $this->fileOndemand; + } + + +} diff --git a/Tests/Functional/TestXmlConfigBundle/Resources/config/doctrine/FileProtected.orm.xml b/Tests/Functional/TestXmlConfigBundle/Resources/config/doctrine/FileProtected.orm.xml new file mode 100644 index 0000000..465a81c --- /dev/null +++ b/Tests/Functional/TestXmlConfigBundle/Resources/config/doctrine/FileProtected.orm.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Functional/config/default.yml b/Tests/Functional/config/default.yml index 4a1eb44..3147bb1 100644 --- a/Tests/Functional/config/default.yml +++ b/Tests/Functional/config/default.yml @@ -2,6 +2,7 @@ imports: - { resource: framework.yml } - { resource: doctrine.yml } - { resource: twig.yml } + - { resource: security.yml } - { resource: database.php } @@ -31,3 +32,71 @@ iphp_file_store: #naming file by id property: ~ + + + file_protected: + protected: true + protected_dir: %kernel.test_env_dir%/protected-files/protected + upload_path: /file + + #chain of directory namers + directory_namer: + entityName: ~ + property: + params: { use_field_name : true } + date: + params: { field : date, depth : year } + namer: + #naming file by id + property: ~ + + + + + + file_protected_ondemand: + protected: ondemand + protected_dir: %kernel.test_env_dir%/protected-files/protected + upload_path: /file + upload_dir: %kernel.test_env_dir%/web/file + + #chain of directory namers + directory_namer: + entityName: ~ + property: + params: { use_field_name : true } + date: + params: { field : date, depth : year } + namer: + #naming file by id + property: ~ + + + photo_protected: + upload_path: /file + protected: true + protected_dir: %kernel.test_env_dir%/protected-files/protected + + + #chain of directory namers + directory_namer: + entityName: ~ + property: + params: { use_field_name : true } + namer: ~ + + + photo_protected_ondemand: + upload_dir: %kernel.test_env_dir%/web/file + upload_path: /file + + protected: ondemand + protected_dir: %kernel.test_env_dir%/protected-files/protected + + + #chain of directory namers + directory_namer: + entityName: ~ + property: + params: { use_field_name : true } + namer: ~ \ No newline at end of file diff --git a/Tests/Functional/config/routing.yml b/Tests/Functional/config/routing.yml index ec3ee01..e325a92 100644 --- a/Tests/Functional/config/routing.yml +++ b/Tests/Functional/config/routing.yml @@ -1,7 +1,18 @@ photo_index: - pattern: / + pattern: /list/ defaults: { _controller: TestBundle:Default:index } photo_edit: pattern: /edit/{id}/ - defaults: { _controller: TestBundle:Default:edit } \ No newline at end of file + defaults: { _controller: TestBundle:Default:edit } + + +photo_index_protected: + pattern: /list-protected/ + defaults: { _controller: TestBundle:Default:indexProtected } + + +#routing for protected files +_iphp_filestore: + resource: . + type: iphp_filestore \ No newline at end of file diff --git a/Tests/Functional/config/security.yml b/Tests/Functional/config/security.yml new file mode 100644 index 0000000..3d8d856 --- /dev/null +++ b/Tests/Functional/config/security.yml @@ -0,0 +1,24 @@ +security: + encoders: + Symfony\Component\Security\Core\User\User: plaintext + + role_hierarchy: + ROLE_ADMIN: ROLE_USER + ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] + + providers: + in_memory: + memory: + users: + user: { password: userpass, roles: [ 'ROLE_USER' ] } + admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } + + firewalls: + secured_area: + pattern: ^/ + anonymous: ~ + http_basic: ~ + + access_control: + #- { path: ^/demo/secured/hello/admin/, roles: ROLE_ADMIN } + #- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https } \ No newline at end of file diff --git a/Tests/Mapping/PropertyMappingFactoryTest.php b/Tests/Mapping/PropertyMappingFactoryTest.php index 719e93a..06fcbbd 100644 --- a/Tests/Mapping/PropertyMappingFactoryTest.php +++ b/Tests/Mapping/PropertyMappingFactoryTest.php @@ -320,4 +320,24 @@ public function testConfiguredNamerRetrievedFromInvoker() } + function testGetMappingConfig() + { + + $mappingData = array( + 'upload_dir' => '/some/dir', + 'upload_path' => '/some-path/' + ); + + $mappingsConfig = array( + 'dummy_file' => $mappingData + ); + + $factory = new PropertyMappingFactory($this->namerServiceInvoker, $this->driver, $mappingsConfig); + + + $this->assertEquals($factory->getMappingConfig('dummy_file'), $mappingData); + $this->assertEquals($factory->getMappingConfig('another_file'), null); + } + + } diff --git a/composer.json b/composer.json index e26c92d..69e198c 100644 --- a/composer.json +++ b/composer.json @@ -13,14 +13,17 @@ ], "require":{ - "symfony/framework-bundle":">=2.2,<2.4-dev@stable" + "symfony/framework-bundle":"@stable" }, "require-dev":{ - + "symfony/class-loader": "@stable", "symfony/twig-bundle": "@stable", + "symfony/security-bundle": "@stable", + "symfony/security": "@stable", + "symfony/expression-language": "@stable", "symfony/finder": "@stable", "symfony/browser-kit": "@stable", "symfony/css-selector": "@stable", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c4291e6..8540067 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,10 +5,12 @@ ./Tests + + ./