PageRenderTime 27ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/core/modules/media_library/src/Form/FileUploadForm.php

http://github.com/drupal/drupal
PHP | 380 lines | 188 code | 38 blank | 154 comment | 16 complexity | 65e3742b5c777cd8e6a6b0785d330c98 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\media_library\Form;
  3. use Drupal\Core\Entity\EntityStorageInterface;
  4. use Drupal\Core\Entity\EntityTypeManagerInterface;
  5. use Drupal\Core\Field\FieldStorageDefinitionInterface;
  6. use Drupal\Core\Field\TypedData\FieldItemDataDefinition;
  7. use Drupal\Core\File\Exception\FileWriteException;
  8. use Drupal\Core\File\FileSystemInterface;
  9. use Drupal\Core\Form\FormBuilderInterface;
  10. use Drupal\Core\Form\FormStateInterface;
  11. use Drupal\Core\Render\ElementInfoManagerInterface;
  12. use Drupal\Core\Render\RendererInterface;
  13. use Drupal\Core\Url;
  14. use Drupal\file\FileInterface;
  15. use Drupal\file\FileUsage\FileUsageInterface;
  16. use Drupal\file\Plugin\Field\FieldType\FileFieldItemList;
  17. use Drupal\file\Plugin\Field\FieldType\FileItem;
  18. use Drupal\media\MediaInterface;
  19. use Drupal\media\MediaTypeInterface;
  20. use Drupal\media_library\MediaLibraryUiBuilder;
  21. use Drupal\media_library\OpenerResolverInterface;
  22. use Symfony\Component\DependencyInjection\ContainerInterface;
  23. /**
  24. * Creates a form to create media entities from uploaded files.
  25. *
  26. * @internal
  27. * Form classes are internal.
  28. */
  29. class FileUploadForm extends AddFormBase {
  30. /**
  31. * The element info manager.
  32. *
  33. * @var \Drupal\Core\Render\ElementInfoManagerInterface
  34. */
  35. protected $elementInfo;
  36. /**
  37. * The renderer service.
  38. *
  39. * @var \Drupal\Core\Render\ElementInfoManagerInterface
  40. */
  41. protected $renderer;
  42. /**
  43. * The file system service.
  44. *
  45. * @var \Drupal\Core\File\FileSystemInterface
  46. */
  47. protected $fileSystem;
  48. /**
  49. * The file usage service.
  50. *
  51. * @var \Drupal\file\FileUsage\FileUsageInterface
  52. */
  53. protected $fileUsage;
  54. /**
  55. * Constructs a new FileUploadForm.
  56. *
  57. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  58. * The entity type manager.
  59. * @param \Drupal\media_library\MediaLibraryUiBuilder $library_ui_builder
  60. * The media library UI builder.
  61. * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
  62. * The element info manager.
  63. * @param \Drupal\Core\Render\RendererInterface $renderer
  64. * The renderer service.
  65. * @param \Drupal\Core\File\FileSystemInterface $file_system
  66. * The file system service.
  67. * @param \Drupal\media_library\OpenerResolverInterface $opener_resolver
  68. * The opener resolver.
  69. * @param \Drupal\file\FileUsage\FileUsageInterface $file_usage
  70. * The file usage service.
  71. */
  72. public function __construct(EntityTypeManagerInterface $entity_type_manager, MediaLibraryUiBuilder $library_ui_builder, ElementInfoManagerInterface $element_info, RendererInterface $renderer, FileSystemInterface $file_system, OpenerResolverInterface $opener_resolver = NULL, FileUsageInterface $file_usage = NULL) {
  73. parent::__construct($entity_type_manager, $library_ui_builder, $opener_resolver);
  74. $this->elementInfo = $element_info;
  75. $this->renderer = $renderer;
  76. $this->fileSystem = $file_system;
  77. if (!$file_usage) {
  78. @trigger_error('Calling FileUploadForm::__construct() without the `file.usage` service is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Pass the `file.usage` service to the constructor. See https://www.drupal.org/node/3075165', E_USER_DEPRECATED);
  79. $file_usage = \Drupal::service('file.usage');
  80. }
  81. $this->fileUsage = $file_usage;
  82. }
  83. /**
  84. * {@inheritdoc}
  85. */
  86. public static function create(ContainerInterface $container) {
  87. return new static(
  88. $container->get('entity_type.manager'),
  89. $container->get('media_library.ui_builder'),
  90. $container->get('element_info'),
  91. $container->get('renderer'),
  92. $container->get('file_system'),
  93. $container->get('media_library.opener_resolver'),
  94. $container->get('file.usage')
  95. );
  96. }
  97. /**
  98. * {@inheritdoc}
  99. */
  100. public function getFormId() {
  101. return $this->getBaseFormId() . '_upload';
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. protected function getMediaType(FormStateInterface $form_state) {
  107. if ($this->mediaType) {
  108. return $this->mediaType;
  109. }
  110. $media_type = parent::getMediaType($form_state);
  111. // The file upload form only supports media types which use a file field as
  112. // a source field.
  113. $field_definition = $media_type->getSource()->getSourceFieldDefinition($media_type);
  114. if (!is_a($field_definition->getClass(), FileFieldItemList::class, TRUE)) {
  115. throw new \InvalidArgumentException('Can only add media types which use a file field as a source field.');
  116. }
  117. return $media_type;
  118. }
  119. /**
  120. * {@inheritdoc}
  121. */
  122. protected function buildInputElement(array $form, FormStateInterface $form_state) {
  123. // Create a file item to get the upload validators.
  124. $media_type = $this->getMediaType($form_state);
  125. $item = $this->createFileItem($media_type);
  126. /** @var \Drupal\media_library\MediaLibraryState $state */
  127. $state = $this->getMediaLibraryState($form_state);
  128. if (!$state->hasSlotsAvailable()) {
  129. return $form;
  130. }
  131. $slots = $state->getAvailableSlots();
  132. // Add a container to group the input elements for styling purposes.
  133. $form['container'] = [
  134. '#type' => 'container',
  135. ];
  136. $process = (array) $this->elementInfo->getInfoProperty('managed_file', '#process', []);
  137. $form['container']['upload'] = [
  138. '#type' => 'managed_file',
  139. '#title' => $this->formatPlural($slots, 'Add file', 'Add files'),
  140. // @todo Move validation in https://www.drupal.org/node/2988215
  141. '#process' => array_merge(['::validateUploadElement'], $process, ['::processUploadElement']),
  142. '#upload_validators' => $item->getUploadValidators(),
  143. '#multiple' => $slots > 1 || $slots === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
  144. '#cardinality' => $slots,
  145. '#remaining_slots' => $slots,
  146. ];
  147. $file_upload_help = [
  148. '#theme' => 'file_upload_help',
  149. '#upload_validators' => $form['container']['upload']['#upload_validators'],
  150. '#cardinality' => $slots,
  151. ];
  152. // The file upload help needs to be rendered since the description does not
  153. // accept render arrays. The FileWidget::formElement() method adds the file
  154. // upload help in the same way, so any theming improvements made to file
  155. // fields would also be applied to this upload field.
  156. // @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::formElement()
  157. $form['container']['upload']['#description'] = $this->renderer->renderPlain($file_upload_help);
  158. return $form;
  159. }
  160. /**
  161. * Validates the upload element.
  162. *
  163. * @param array $element
  164. * The upload element.
  165. * @param \Drupal\Core\Form\FormStateInterface $form_state
  166. * The form state.
  167. *
  168. * @return array
  169. * The processed upload element.
  170. */
  171. public function validateUploadElement(array $element, FormStateInterface $form_state) {
  172. if ($form_state::hasAnyErrors()) {
  173. // When an error occurs during uploading files, remove all files so the
  174. // user can re-upload the files.
  175. $element['#value'] = [];
  176. }
  177. $values = $form_state->getValue('upload', []);
  178. if (count($values['fids']) > $element['#cardinality'] && $element['#cardinality'] !== FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
  179. $form_state->setError($element, $this->t('A maximum of @count files can be uploaded.', [
  180. '@count' => $element['#cardinality'],
  181. ]));
  182. $form_state->setValue('upload', []);
  183. $element['#value'] = [];
  184. }
  185. return $element;
  186. }
  187. /**
  188. * Processes an upload (managed_file) element.
  189. *
  190. * @param array $element
  191. * The upload element.
  192. * @param \Drupal\Core\Form\FormStateInterface $form_state
  193. * The form state.
  194. *
  195. * @return array
  196. * The processed upload element.
  197. */
  198. public function processUploadElement(array $element, FormStateInterface $form_state) {
  199. $element['upload_button']['#submit'] = ['::uploadButtonSubmit'];
  200. // Limit the validation errors to make sure
  201. // FormValidator::handleErrorsWithLimitedValidation doesn't remove the
  202. // current selection from the form state.
  203. // @see Drupal\Core\Form\FormValidator::handleErrorsWithLimitedValidation()
  204. $element['upload_button']['#limit_validation_errors'] = [
  205. ['upload'],
  206. ['current_selection'],
  207. ];
  208. $element['upload_button']['#ajax'] = [
  209. 'callback' => '::updateFormCallback',
  210. 'wrapper' => 'media-library-wrapper',
  211. // Add a fixed URL to post the form since AJAX forms are automatically
  212. // posted to <current> instead of $form['#action'].
  213. // @todo Remove when https://www.drupal.org/project/drupal/issues/2504115
  214. // is fixed.
  215. 'url' => Url::fromRoute('media_library.ui'),
  216. 'options' => [
  217. 'query' => $this->getMediaLibraryState($form_state)->all() + [
  218. FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
  219. ],
  220. ],
  221. ];
  222. return $element;
  223. }
  224. /**
  225. * {@inheritdoc}
  226. */
  227. protected function buildEntityFormElement(MediaInterface $media, array $form, FormStateInterface $form_state, $delta) {
  228. $element = parent::buildEntityFormElement($media, $form, $form_state, $delta);
  229. $source_field = $this->getSourceFieldName($media->bundle->entity);
  230. if (isset($element['fields'][$source_field])) {
  231. $element['fields'][$source_field]['widget'][0]['#process'][] = [static::class, 'hideExtraSourceFieldComponents'];
  232. }
  233. return $element;
  234. }
  235. /**
  236. * Processes an image or file source field element.
  237. *
  238. * @param array $element
  239. * The entity form source field element.
  240. * @param \Drupal\Core\Form\FormStateInterface $form_state
  241. * The current form state.
  242. * @param $form
  243. * The complete form.
  244. *
  245. * @return array
  246. * The processed form element.
  247. */
  248. public static function hideExtraSourceFieldComponents($element, FormStateInterface $form_state, $form) {
  249. // Remove original button added by ManagedFile::processManagedFile().
  250. if (!empty($element['remove_button'])) {
  251. $element['remove_button']['#access'] = FALSE;
  252. }
  253. // Remove preview added by ImageWidget::process().
  254. if (!empty($element['preview'])) {
  255. $element['preview']['#access'] = FALSE;
  256. }
  257. $element['#title_display'] = 'none';
  258. $element['#description_display'] = 'none';
  259. // Remove the filename display.
  260. foreach ($element['#files'] as $file) {
  261. $element['file_' . $file->id()]['filename']['#access'] = FALSE;
  262. }
  263. return $element;
  264. }
  265. /**
  266. * Submit handler for the upload button, inside the managed_file element.
  267. *
  268. * @param array $form
  269. * The form render array.
  270. * @param \Drupal\Core\Form\FormStateInterface $form_state
  271. * The form state.
  272. */
  273. public function uploadButtonSubmit(array $form, FormStateInterface $form_state) {
  274. $files = $this->entityTypeManager
  275. ->getStorage('file')
  276. ->loadMultiple($form_state->getValue('upload', []));
  277. $this->processInputValues($files, $form, $form_state);
  278. }
  279. /**
  280. * {@inheritdoc}
  281. */
  282. protected function createMediaFromValue(MediaTypeInterface $media_type, EntityStorageInterface $media_storage, $source_field_name, $file) {
  283. if (!($file instanceof FileInterface)) {
  284. throw new \InvalidArgumentException('Cannot create a media item without a file entity.');
  285. }
  286. // Create a file item to get the upload location.
  287. $item = $this->createFileItem($media_type);
  288. $upload_location = $item->getUploadLocation();
  289. if (!$this->fileSystem->prepareDirectory($upload_location, FileSystemInterface::CREATE_DIRECTORY)) {
  290. throw new FileWriteException("The destination directory '$upload_location' is not writable");
  291. }
  292. $file = file_move($file, $upload_location);
  293. if (!$file) {
  294. throw new \RuntimeException("Unable to move file to '$upload_location'");
  295. }
  296. return parent::createMediaFromValue($media_type, $media_storage, $source_field_name, $file);
  297. }
  298. /**
  299. * Create a file field item.
  300. *
  301. * @param \Drupal\media\MediaTypeInterface $media_type
  302. * The media type of the media item.
  303. *
  304. * @return \Drupal\file\Plugin\Field\FieldType\FileItem
  305. * A created file item.
  306. */
  307. protected function createFileItem(MediaTypeInterface $media_type) {
  308. $field_definition = $media_type->getSource()->getSourceFieldDefinition($media_type);
  309. $data_definition = FieldItemDataDefinition::create($field_definition);
  310. return new FileItem($data_definition);
  311. }
  312. /**
  313. * {@inheritdoc}
  314. */
  315. protected function prepareMediaEntityForSave(MediaInterface $media) {
  316. /** @var \Drupal\file\FileInterface $file */
  317. $file = $media->get($this->getSourceFieldName($media->bundle->entity))->entity;
  318. $file->setPermanent();
  319. $file->save();
  320. }
  321. /**
  322. * Submit handler for the remove button.
  323. *
  324. * @param array $form
  325. * The form render array.
  326. * @param \Drupal\Core\Form\FormStateInterface $form_state
  327. * The form state.
  328. */
  329. public function removeButtonSubmit(array $form, FormStateInterface $form_state) {
  330. // Retrieve the delta of the media item from the parents of the remove
  331. // button.
  332. $triggering_element = $form_state->getTriggeringElement();
  333. $delta = array_slice($triggering_element['#array_parents'], -2, 1)[0];
  334. /** @var \Drupal\media\MediaInterface $removed_media */
  335. $removed_media = $form_state->get(['media', $delta]);
  336. $file = $removed_media->get($this->getSourceFieldName($removed_media->bundle->entity))->entity;
  337. if ($file instanceof FileInterface && empty($this->fileUsage->listUsage($file))) {
  338. $file->delete();
  339. }
  340. parent::removeButtonSubmit($form, $form_state);
  341. }
  342. }