You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

259 lines
10KB

  1. <?php
  2. namespace Lc\SovBundle\Form\Common;
  3. use EasyCorp\Bundle\EasyAdminBundle\Form\DataTransformer\StringToFileTransformer;
  4. use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Model\FileUploadState;
  5. use Lc\SovBundle\Model\File\FileInterface;
  6. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  7. use Symfony\Component\Form\DataMapperInterface;
  8. use Symfony\Component\Form\DataTransformerInterface;
  9. use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
  10. use Symfony\Component\Form\FormInterface;
  11. use Symfony\Component\HttpFoundation\File\File;
  12. use Lc\SovBundle\Doctrine\EntityManager;
  13. use Symfony\Component\Form\AbstractType;
  14. use Symfony\Component\Form\Extension\Core\Type\FileType;
  15. use Symfony\Component\Form\Extension\Core\Type\TextType;
  16. use Symfony\Component\Form\FormBuilderInterface;
  17. use Symfony\Component\HttpFoundation\File\UploadedFile;
  18. use Symfony\Component\OptionsResolver\Exception\InvalidArgumentException;
  19. use Symfony\Component\OptionsResolver\Options;
  20. use Symfony\Component\OptionsResolver\OptionsResolver;
  21. use Symfony\Component\String\Slugger\AsciiSlugger;
  22. use Symfony\Component\Uid\Ulid;
  23. use Symfony\Component\Uid\Uuid;
  24. class FileUploadType extends AbstractType implements DataMapperInterface, DataTransformerInterface
  25. {
  26. protected string $projectDir;
  27. protected EntityManager $entityManager;
  28. public function __construct(EntityManager $entityManager, ParameterBagInterface $parameterBag)
  29. {
  30. $this->projectDir = $parameterBag->get('kernel.project_dir');
  31. $this->entityManager = $entityManager;
  32. }
  33. public function buildForm(FormBuilderInterface $builder, array $options)
  34. {
  35. $uploadDir = $options['upload_dir'];
  36. $uploadFilename = $options['upload_filename'];
  37. $uploadValidate = $options['upload_validate'];
  38. $allowAdd = $options['allow_add'];
  39. unset($options['upload_dir'], $options['upload_new'], $options['upload_delete'], $options['upload_filename'], $options['upload_validate'], $options['download_path'], $options['allow_add'], $options['allow_delete'], $options['compound']);
  40. $builder->add('path', FileType::class, $options);
  41. $builder->add('legend', TextType::class, array(
  42. 'attr' => [
  43. "placeholder" => 'Légende'
  44. ],
  45. 'label' => false
  46. ));
  47. $builder->add('delete', CheckboxType::class, ['required' => false, 'mapped' => false]);
  48. $builder->setDataMapper($this);
  49. $builder->setAttribute('state', new FileUploadState($allowAdd));
  50. $builder->addModelTransformer(new StringToFileTransformer($uploadDir, $uploadFilename, $uploadValidate, $options['multiple']));
  51. /*
  52. $builder->add('path', FileType::class, array(
  53. 'block_prefix' => 'file_manager_image',
  54. 'label' => false
  55. ));
  56. $builder->add('legend', TextType::class, array(
  57. 'block_prefix' => 'file_upload_legend',
  58. 'attr' => array(
  59. "placeholder" => 'Légende'
  60. ),
  61. 'label' => false
  62. ));
  63. $builder->add('position', HiddenType::class, array(
  64. 'block_prefix' => 'file_upload_position',
  65. 'empty_data' => 0,
  66. 'required' => true,
  67. 'attr' => array(
  68. 'class' => 'field-position'
  69. ),
  70. 'label' => false
  71. ));
  72. */
  73. }
  74. /**
  75. * {@inheritdoc}
  76. */
  77. public function configureOptions(OptionsResolver $resolver): void
  78. {
  79. $uploadNew = static function (UploadedFile $file, string $uploadDir, string $fileName) {
  80. $file->move($uploadDir, $fileName);
  81. };
  82. $uploadDelete = static function (File $file) {
  83. unlink($file->getPathname());
  84. };
  85. $uploadFilename = static function (UploadedFile $file): string {
  86. return $file->getClientOriginalName();
  87. };
  88. $uploadValidate = static function (string $filename): string {
  89. if (!file_exists($filename)) {
  90. return $filename;
  91. }
  92. $index = 1;
  93. $pathInfo = pathinfo($filename);
  94. while (file_exists($filename = sprintf('%s/%s_%d.%s', $pathInfo['dirname'], $pathInfo['filename'], $index, $pathInfo['extension']))) {
  95. ++$index;
  96. }
  97. return $filename;
  98. };
  99. $downloadPath = function (Options $options) {
  100. return mb_substr($options['upload_dir'], mb_strlen($this->projectDir.'/public/'));
  101. };
  102. $allowAdd = static function (Options $options) {
  103. return $options['multiple'];
  104. };
  105. $dataClass = static function (Options $options) {
  106. return $options['multiple'] ? null : File::class;
  107. };
  108. $emptyData = static function (Options $options) {
  109. return $options['multiple'] ? [] : null;
  110. };
  111. $resolver->setDefaults([
  112. 'upload_dir' => $this->projectDir.'/public/uploads/files/',
  113. 'upload_new' => $uploadNew,
  114. 'upload_delete' => $uploadDelete,
  115. 'upload_filename' => $uploadFilename,
  116. 'upload_validate' => $uploadValidate,
  117. 'download_path' => $downloadPath,
  118. 'allow_add' => $allowAdd,
  119. 'allow_delete' => true,
  120. //'data_class' => $dataClass,
  121. 'data_class' => $this->entityManager->getEntityName(FileInterface::class),
  122. 'empty_data' => $emptyData,
  123. 'multiple' => false,
  124. 'required' => false,
  125. 'error_bubbling' => false,
  126. 'allow_file_upload' => true,
  127. ]);
  128. $resolver->setAllowedTypes('upload_dir', 'string');
  129. $resolver->setAllowedTypes('upload_new', 'callable');
  130. $resolver->setAllowedTypes('upload_delete', 'callable');
  131. $resolver->setAllowedTypes('upload_filename', ['string', 'callable']);
  132. $resolver->setAllowedTypes('upload_validate', 'callable');
  133. $resolver->setAllowedTypes('download_path', ['null', 'string']);
  134. $resolver->setAllowedTypes('allow_add', 'bool');
  135. $resolver->setAllowedTypes('allow_delete', 'bool');
  136. $resolver->setNormalizer('upload_dir', function (Options $options, string $value): string {
  137. if (\DIRECTORY_SEPARATOR !== mb_substr($value, -1)) {
  138. $value .= \DIRECTORY_SEPARATOR;
  139. }
  140. if (0 !== mb_strpos($value, $this->projectDir)) {
  141. $value = $this->projectDir.'/'.$value;
  142. }
  143. if ('' !== $value && (!is_dir($value) || !is_writable($value))) {
  144. throw new InvalidArgumentException(sprintf('Invalid upload directory "%s" it does not exist or is not writable.', $value));
  145. }
  146. return $value;
  147. });
  148. $resolver->setNormalizer('upload_filename', static function (Options $options, $fileNamePatternOrCallable) {
  149. if (\is_callable($fileNamePatternOrCallable)) {
  150. return $fileNamePatternOrCallable;
  151. }
  152. return static function (UploadedFile $file) use ($fileNamePatternOrCallable) {
  153. return strtr($fileNamePatternOrCallable, [
  154. '[contenthash]' => sha1_file($file->getRealPath()),
  155. '[day]' => date('d'),
  156. '[extension]' => $file->guessClientExtension(),
  157. '[month]' => date('m'),
  158. '[name]' => pathinfo($file->getClientOriginalName(), \PATHINFO_FILENAME),
  159. '[randomhash]' => bin2hex(random_bytes(20)),
  160. '[slug]' => (new AsciiSlugger())
  161. ->slug(pathinfo($file->getClientOriginalName(), \PATHINFO_FILENAME))
  162. ->lower()
  163. ->toString(),
  164. '[timestamp]' => time(),
  165. '[uuid]' => Uuid::v4()->toRfc4122(),
  166. '[ulid]' => new Ulid(),
  167. '[year]' => date('Y'),
  168. ]);
  169. };
  170. });
  171. $resolver->setNormalizer('allow_add', static function (Options $options, string $value): bool {
  172. if ($value && !$options['multiple']) {
  173. throw new InvalidArgumentException('Setting "allow_add" option to "true" when "multiple" option is "false" is not supported.');
  174. }
  175. return $value;
  176. });
  177. }
  178. /**
  179. * {@inheritdoc}
  180. */
  181. public function mapDataToForms($currentFiles, $forms): void
  182. {
  183. /** @var FormInterface $fileForm */
  184. $fileForm = current(iterator_to_array($forms));
  185. $fileForm->setData($currentFiles);
  186. }
  187. /**
  188. * {@inheritdoc}
  189. */
  190. public function mapFormsToData($forms, &$currentFiles): void
  191. {
  192. /** @var FormInterface[] $children */
  193. $children = iterator_to_array($forms);
  194. $uploadedFiles = $children['path']->getData();
  195. /** @var FileUploadState $state */
  196. $state = $children['path']->getParent()->getConfig()->getAttribute('state');
  197. $state->setCurrentFiles($currentFiles);
  198. $state->setUploadedFiles($uploadedFiles);
  199. $state->setDelete($children['delete']->getData());
  200. if (!$state->isModified()) {
  201. return;
  202. }
  203. if ($state->isAddAllowed() && !$state->isDelete()) {
  204. $currentFiles = array_merge($currentFiles, $uploadedFiles);
  205. } else {
  206. $currentFiles = $uploadedFiles;
  207. }
  208. }
  209. /**
  210. * {@inheritdoc}
  211. */
  212. public function transform($data)
  213. {
  214. return $data;
  215. }
  216. /**
  217. * {@inheritdoc}
  218. */
  219. public function reverseTransform($data)
  220. {
  221. return null === $data ? '' : $data;
  222. }
  223. }