Browse Source

[Uploadable] Added possibility to configure a filename generator

comfortablynumb 13 năm trước cách đây
mục cha
commit
dbdd6b024a

+ 4 - 0
lib/Gedmo/Mapping/Annotation/Uploadable.php

@@ -3,6 +3,7 @@
 namespace Gedmo\Mapping\Annotation;
 
 use Doctrine\Common\Annotations\Annotation;
+use Gedmo\Uploadable\Mapping\Validator;
 
 /**
  * Uploadable annotation for Uploadable behavioral extension
@@ -36,5 +37,8 @@ final class Uploadable extends Annotation
 
     /** @var string */
     public $callback = '';
+
+    /** @var string */
+    public $filenameGenerator = Validator::FILENAME_GENERATOR_NONE;
 }
 

+ 1 - 0
lib/Gedmo/Uploadable/FileInfo/FileInfoInterface.php

@@ -12,6 +12,7 @@ namespace Gedmo\Uploadable\FileInfo;
  * @link http://www.gediminasm.org
  * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  */
+
 interface FileInfoInterface
 {
     public function getTmpName();

+ 28 - 0
lib/Gedmo/Uploadable/FilenameGenerator/FilenameGeneratorAlphanumeric.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Gedmo\Uploadable\FilenameGenerator;
+
+/**
+ * FilenameGeneratorAlphanumeric
+ *
+ * This class generates a filename, leaving only lowercase
+ * alphanumeric characters
+ *
+ * @author Gustavo Falco <comfortablynumb84@gmail.com>
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Uploadable.FilenameGenerator
+ * @subpackage FilenameGeneratorAlphanumeric
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+class FilenameGeneratorAlphanumeric implements FilenameGeneratorInterface
+{
+    /**
+     * @inheritDoc
+     */
+    public static function generate($filename, $extension)
+    {
+        return preg_replace('/[^a-z0-9]+/', '-', strtolower($filename)).$extension;
+    }
+}

+ 27 - 0
lib/Gedmo/Uploadable/FilenameGenerator/FilenameGeneratorInterface.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Gedmo\Uploadable\FilenameGenerator;
+
+/**
+ * FilenameGeneratorInterface
+ *
+ * @author Gustavo Falco <comfortablynumb84@gmail.com>
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Uploadable.FilenameGenerator
+ * @subpackage FilenameGeneratorInterface
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+interface FilenameGeneratorInterface
+{
+    /**
+     * Generates a new filename
+     *
+     * @param string - Filename without extension
+     * @param string - Extension with dot: .jpg, .gif, etc
+     *
+     * @return string
+     */
+    public static function generate($filename, $extension);
+}

+ 25 - 0
lib/Gedmo/Uploadable/FilenameGenerator/FilenameGeneratorSha1.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Gedmo\Uploadable\FilenameGenerator;
+
+/**
+ * FilenameGeneratorSha1
+ *
+ * @author Gustavo Falco <comfortablynumb84@gmail.com>
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Uploadable.FilenameGenerator
+ * @subpackage FilenameGeneratorSha1
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+class FilenameGeneratorSha1 implements FilenameGeneratorInterface
+{
+    /**
+     * @inheritDoc
+     */
+    public static function generate($filename, $extension)
+    {
+        return sha1(uniqid($filename.$extension, true)).$extension;
+    }
+}

+ 1 - 0
lib/Gedmo/Uploadable/Mapping/Driver/Annotation.php

@@ -82,6 +82,7 @@ class Annotation implements AnnotationDriverInterface
             $config['fileSizeField'] = false;
             $config['fileInfoProperty'] = $annot->fileInfoProperty;
             $config['callback'] = $annot->callback;
+            $config['filenameGenerator'] = $annot->filenameGenerator;
 
             foreach ($class->getProperties() as $prop) {
                 if ($this->reader->getPropertyAnnotation($prop, self::UPLOADABLE_FILE_MIME_TYPE)) {

+ 3 - 0
lib/Gedmo/Uploadable/Mapping/Driver/Xml.php

@@ -52,6 +52,9 @@ class Xml extends BaseXml
                 $config['fileMimeTypeField'] = false;
                 $config['filePathField'] = false;
                 $config['fileSizeField'] = false;
+                $config['filenameGenerator'] = $this->_isAttributeSet($xmlUploadable, 'filename-generator') ?
+                    $this->_getAttribute($xml->{'uploadable'}, 'filename-generator') :
+                    Validator::FILENAME_GENERATOR_NONE;
 
                 if (isset($xmlDoctrine->field)) {
                     foreach ($xmlDoctrine->field as $mapping) {

+ 3 - 0
lib/Gedmo/Uploadable/Mapping/Driver/Yaml.php

@@ -53,6 +53,9 @@ class Yaml extends File implements Driver
                 $config['fileMimeTypeField'] = false;
                 $config['filePathField'] = false;
                 $config['fileSizeField'] = false;
+                $config['filenameGenerator'] = isset($uploadable['filenameGenerator']) ?
+                    $uploadable['filenameGenerator'] :
+                    Validator::FILENAME_GENERATOR_NONE;
 
                 if (isset($mapping['fields'])) {
                     foreach ($mapping['fields'] as $field => $info) {

+ 28 - 0
lib/Gedmo/Uploadable/Mapping/Validator.php

@@ -23,6 +23,9 @@ class Validator
     const UPLOADABLE_FILE_MIME_TYPE = 'UploadableFileMimeType';
     const UPLOADABLE_FILE_PATH = 'UploadableFilePath';
     const UPLOADABLE_FILE_SIZE = 'UploadableFileSize';
+    const FILENAME_GENERATOR_SHA1 = 'SHA1';
+    const FILENAME_GENERATOR_ALPHANUMERIC = 'ALPHANUMERIC';
+    const FILENAME_GENERATOR_NONE = 'NONE';
 
     /**
      * List of types which are valid for UploadableFileMimeType field
@@ -140,6 +143,31 @@ class Validator
             self::validateFileSizeField($meta, $config['fileSizeField']);
         }
 
+        switch ((string) $config['filenameGenerator']) {
+            case self::FILENAME_GENERATOR_ALPHANUMERIC:
+            case self::FILENAME_GENERATOR_SHA1:
+            case self::FILENAME_GENERATOR_NONE:
+                break;
+            default:
+                $ok = false;
+                if (is_class($config['filenameGenerator'])) {
+                    $refl = new \ReflectionClass($config['filenameGenerator']);
+
+                    if ($refl->implementsInterface('Gedmo\Uploadable\FilenameGeneratorInterface')) {
+                        $ok = true;
+                    }
+                }
+
+                if (!$ok) {
+                    $msg = 'Class "%s" needs a valid value for filenameGenerator. It can be: SHA1, ALPHANUMERIC, NONE or ';
+                    $msg .= 'a class implementing FileGeneratorInterface.';
+
+                    throw new InvalidMappingException(sprintf($msg,
+                        $meta->name
+                    ));
+                }
+        }
+
         self::validateFilePathField($meta, $config['filePathField']);
     }
 }

+ 59 - 18
lib/Gedmo/Uploadable/UploadableListener.php

@@ -223,7 +223,27 @@ class UploadableListener extends MappedEventSubscriber
             $this->pendingFileRemovals[] = $this->getFilePath($meta, $config, $object);
         }
 
-        $info = $this->moveFile($fileInfo, $path, $config['allowOverwrite'], $config['appendNumber']);
+        // We generate the filename based on configuration
+        $generatorNamespace = 'Gedmo\Uploadable\FilenameGenerator';
+
+        switch ($config['filenameGenerator']) {
+            case Validator::FILENAME_GENERATOR_ALPHANUMERIC:
+                $generatorClass = $generatorNamespace .'\FilenameGeneratorAlphanumeric';
+
+                break;
+            case Validator::FILENAME_GENERATOR_SHA1:
+                $generatorClass = $generatorNamespace .'\FilenameGeneratorSha1';
+
+                break;
+            case Validator::FILENAME_GENERATOR_NONE:
+                $generatorClass = false;
+
+                break;
+            default:
+                $generatorClass = $config['filenameGenerator'];
+        }
+
+        $info = $this->moveFile($fileInfo, $path, $generatorClass, $config['allowOverwrite'], $config['appendNumber']);
         $filePathField->setValue($object, $info['filePath']);
 
         if ($config['callback'] !== '') {
@@ -289,12 +309,16 @@ class UploadableListener extends MappedEventSubscriber
     /**
      * Moves the file to the specified path
      *
-     * @param array - File array
+
+     * @param FileInfoInterface
      * @param string - Path
-     * 
+     * @param string - FilenameGeneratorClass
+     * @param bool - Overwrite if file already exists?
+     * @param bool - Append a number if file already exists?
+     *
      * @return array - Information about the moved file
      */
-    public function moveFile(FileInfoInterface $fileInfo, $path, $overwrite = false, $appendNumber = false)
+    public function moveFile(FileInfoInterface $fileInfo, $path, $filenameGeneratorClass, $overwrite = false, $appendNumber = false)
     {
         if ($fileInfo->getError() > 0) {
             switch ($fileInfo->getError()) {
@@ -334,32 +358,49 @@ class UploadableListener extends MappedEventSubscriber
         }
 
         $info = array(
-            'fileName'      => '',
-            'filePath'      => '',
-            'fileMimeType'  => $fileInfo->getType(),
-            'fileSize'      => $fileInfo->getSize()
+            'fileName'          => '',
+            'fileExtension'     => '',
+            'fileWithoutExt'    => '',
+            'filePath'          => '',
+            'fileMimeType'      => $fileInfo->getType(),
+            'fileSize'          => $fileInfo->getSize()
         );
 
         $info['fileName'] = basename($fileInfo->getName());
         $info['filePath'] = $path.'/'.$info['fileName'];
 
+        $extensionPos = strrpos($info['filePath'], '.');
+
+        if ($extensionPos !== false) {
+            $info['fileExtension'] = substr($info['filePath'], $extensionPos);
+            $info['fileWithoutExt'] = substr($info['filePath'], 0, strrpos($info['filePath'], '.'));
+        } else {
+            $info['fileWithoutExt'] = $info['fileName'];
+        }
+
+        // Now we generate the filename using the configured class
+        if ($filenameGeneratorClass) {
+            $filename = $filenameGeneratorClass::generate(
+                str_replace($path.'/', '', $info['fileWithoutExt']),
+                $info['fileExtension']
+            );
+            $info['filePath'] = str_replace(
+                $info['fileName'],
+                $filename,
+                $info['filePath']
+            );
+            $info['fileName'] = $filename;
+        }
+
         if (is_file($info['filePath'])) {
             if ($overwrite) {
                 $this->removeFile($info['filePath']);
             } else if ($appendNumber) {
                 $counter = 1;
-                $extensionPos = strrpos($info['filePath'], '.');
-
-                if ($extensionPos !== false) {
-                    $extension = substr($info['filePath'], $extensionPos);
-
-                    $fileWithoutExt = substr($info['filePath'], 0, strrpos($info['filePath'], '.'));
-
-                    $info['filePath'] = $fileWithoutExt.'-'.$counter.$extension;
-                }
+                $info['filePath'] = $info['fileWithoutExt'].'-'.$counter.$info['fileExtension'];
 
                 do {
-                    $info['filePath'] = $fileWithoutExt.'-'.(++$counter).$extension;
+                    $info['filePath'] = $info['fileWithoutExt'].'-'.(++$counter).$info['fileExtension'];
                 } while (is_file($info['filePath']));
             } else {
                 throw new UploadableFileAlreadyExistsException(sprintf('File "%s" already exists!',

+ 1 - 0
schemas/orm/doctrine-extensions-mapping-2-2.xsd

@@ -140,6 +140,7 @@ people to push their own additional attributes/elements into the same field elem
     <xs:attribute name="path" type="xs:string" use="optional" />
     <xs:attribute name="file-info-property" type="xs:string" use="required" />
     <xs:attribute name="path-method" type="xs:string" use="optional" />
+    <xs:attribute name="filename-generator" type="xs:string" use="optional" />
   </xs:complexType>
 
 </xs:schema>

+ 2 - 1
tests/Gedmo/Mapping/Driver/Xml/Mapping.Fixture.Xml.Uploadable.dcm.xml

@@ -27,7 +27,8 @@
             path="/my/path"
             path-method="getPath"
             file-info-property="fileInfo"
-            callback="callbackMethod" />
+            callback="callbackMethod"
+            filename-generator="SHA1" />
 
     </entity>
 

+ 1 - 0
tests/Gedmo/Mapping/Driver/Yaml/Mapping.Fixture.Yaml.Uploadable.dcm.yml

@@ -10,6 +10,7 @@ Mapping\Fixture\Yaml\Uploadable:
       pathMethod: getPath
       fileInfoProperty: fileInfo
       callback: callbackMethod
+      filenameGenerator: SHA1
   id:
     id:
       type: integer

+ 1 - 0
tests/Gedmo/Mapping/UploadableMappingTest.php

@@ -68,5 +68,6 @@ class UploadableMappingTest extends BaseTestCaseOM
         $this->assertEquals('path', $config['filePathField']);
         $this->assertEquals('size', $config['fileSizeField']);
         $this->assertEquals('callbackMethod', $config['callback']);
+        $this->assertEquals('SHA1', $config['filenameGenerator']);
     }
 }

+ 1 - 0
tests/Gedmo/Mapping/Xml/UploadableMappingTest.php

@@ -68,5 +68,6 @@ class UploadableMappingTest extends BaseTestCaseOM
         $this->assertEquals('path', $config['filePathField']);
         $this->assertEquals('size', $config['fileSizeField']);
         $this->assertEquals('callbackMethod', $config['callback']);
+        $this->assertEquals('SHA1', $config['filenameGenerator']);
     }
 }

+ 27 - 0
tests/Gedmo/Uploadable/FilenameGenerator/FilenameGeneratorAlphanumericTest.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Gedmo\Uploadable;
+
+use Gedmo\Uploadable\FilenameGenerator\FilenameGeneratorAlphanumeric;
+
+/**
+ * These are tests for FilenameGeneratorAlphanumeric class
+ *
+ * @author Gustavo Falco <comfortablynumb84@gmail.com>
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ * @package Gedmo.Uploadable.FilenameGenerator
+ * @link http://www.gediminasm.org
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+class FilenameGeneratorAlphanumericTest extends \PHPUnit_Framework_TestCase
+{
+    public function testGenerator()
+    {
+        $generator = new FilenameGeneratorAlphanumeric();
+
+        $filename = 'MegaName_For_A_###$$$File$$$###';
+        $extension = '.exe';
+
+        $this->assertEquals('meganame-for-a-file-.exe', $generator->generate($filename, $extension));
+    }
+}

+ 60 - 0
tests/Gedmo/Uploadable/Fixture/Entity/FileWithSha1Name.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace Uploadable\Fixture\Entity;
+
+use Gedmo\Mapping\Annotation as Gedmo;
+use Doctrine\ORM\Mapping as ORM;
+use Doctrine\Common\Collections\ArrayCollection;
+
+/**
+ * @ORM\Entity
+ * @Gedmo\Uploadable(pathMethod="getPath", fileInfoProperty="fileInfo", filenameGenerator="SHA1")
+ */
+class FileWithSha1Name
+{
+    /**
+     * @ORM\Column(name="id", type="integer")
+     * @ORM\Id
+     * @ORM\GeneratedValue(strategy="IDENTITY")
+     */
+    private $id;
+
+    /**
+     * @ORM\Column(name="path", type="string", nullable=true)
+     * @Gedmo\UploadableFilePath
+     */
+    private $filePath;
+
+    private $fileInfo;
+
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setFilePath($filePath)
+    {
+        $this->filePath = $filePath;
+    }
+
+    public function getFilePath()
+    {
+        return $this->filePath;
+    }
+
+    public function setFileInfo(array $fileInfo)
+    {
+        $this->fileInfo = $fileInfo;
+    }
+
+    public function getFileInfo()
+    {
+        return $this->fileInfo;
+    }
+
+    public function getPath()
+    {
+        return __DIR__.'/../../../../temp/uploadable';
+    }
+}

+ 32 - 11
tests/Gedmo/Uploadable/UploadableEntityTest.php

@@ -9,6 +9,7 @@ use Tool\BaseTestCaseORM,
     Uploadable\Fixture\Entity\Article,
     Uploadable\Fixture\Entity\File,
     Uploadable\Fixture\Entity\FileWithoutPath,
+    Uploadable\Fixture\Entity\FileWithSha1Name,
     Gedmo\Uploadable\Stub\UploadableListenerStub,
     Gedmo\Uploadable\FileInfo\FileInfoArray;
 
@@ -27,6 +28,7 @@ class UploadableEntityTest extends BaseTestCaseORM
     const ARTICLE_CLASS = 'Uploadable\Fixture\Entity\Article';
     const FILE_CLASS = 'Uploadable\Fixture\Entity\File';
     const FILE_WITHOUT_PATH_CLASS = 'Uploadable\Fixture\Entity\FileWithoutPath';
+    const FILE_WITH_SHA1_NAME = 'Uploadable\Fixture\Entity\FileWithSha1Name';
 
     private $listener;
     private $testFile;
@@ -60,7 +62,9 @@ class UploadableEntityTest extends BaseTestCaseORM
 
         $this->clearFilesAndDirectories();
 
-        mkdir($this->destinationTestDir);
+        if (!is_dir($this->destinationTestDir)) {
+            mkdir($this->destinationTestDir);
+        };
     }
 
     public function tearDown()
@@ -252,6 +256,24 @@ class UploadableEntityTest extends BaseTestCaseORM
         $this->assertInstanceOf($fileInfoStubClass, $file->getFileInfo());
     }
 
+    public function testFileWithFilenameGenerator()
+    {
+        $file = new FileWithSha1Name();
+        $fileInfo = $this->generateUploadedFile();
+
+        $file->setFileInfo($fileInfo);
+
+        $this->em->persist($file);
+        $this->em->flush();
+
+        $this->em->refresh($file);
+
+        $sha1String = substr($file->getFilePath(), strrpos($file->getFilePath(), '/') + 1);
+        $sha1String = str_replace('.txt', '', $sha1String);
+
+        $this->assertRegExp('/[a-z0-9]{40}/', $sha1String);
+    }
+
 
 
     // Data Providers
@@ -294,22 +316,21 @@ class UploadableEntityTest extends BaseTestCaseORM
             self::IMAGE_CLASS,
             self::ARTICLE_CLASS,
             self::FILE_CLASS,
-            self::FILE_WITHOUT_PATH_CLASS
+            self::FILE_WITHOUT_PATH_CLASS,
+            self::FILE_WITH_SHA1_NAME
         );
     }
 
     private function clearFilesAndDirectories()
     {
-        if (is_file($this->destinationTestFile)) {
-            unlink($this->destinationTestFile);
-        }
-
-        if (is_file($this->destinationTestFile2)) {
-            unlink($this->destinationTestFile2);
-        }
-
         if (is_dir($this->destinationTestDir)) {
-            rmdir($this->destinationTestDir);
+            $iter = new \DirectoryIterator($this->destinationTestDir);
+
+            foreach ($iter as $fileInfo) {
+                if (!$fileInfo->isDot()) {
+                    @unlink($fileInfo->getPathname());
+                }
+            }
         }
     }
 }