File.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Eav\Model\Attribute\Data;
  7. use Magento\Framework\App\Filesystem\DirectoryList;
  8. use Magento\Framework\App\RequestInterface;
  9. /**
  10. * EAV Entity Attribute File Data Model
  11. *
  12. * @api
  13. * @since 100.0.2
  14. */
  15. class File extends \Magento\Eav\Model\Attribute\Data\AbstractData
  16. {
  17. /**
  18. * Validator for check not protected extensions
  19. *
  20. * @var \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension
  21. */
  22. protected $_validatorNotProtectedExtensions;
  23. /**
  24. * @var \Magento\Framework\Url\EncoderInterface
  25. */
  26. protected $urlEncoder;
  27. /**
  28. * @var \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension
  29. */
  30. protected $_fileValidator;
  31. /**
  32. * @var \Magento\Framework\Filesystem\Directory\Write
  33. */
  34. protected $_directory;
  35. /**
  36. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
  37. * @param \Psr\Log\LoggerInterface $logger
  38. * @param \Magento\Framework\Locale\ResolverInterface $localeResolver
  39. * @param \Magento\Framework\Url\EncoderInterface $urlEncoder
  40. * @param \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $fileValidator
  41. * @param \Magento\Framework\Filesystem $filesystem
  42. * @codeCoverageIgnore
  43. */
  44. public function __construct(
  45. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  46. \Psr\Log\LoggerInterface $logger,
  47. \Magento\Framework\Locale\ResolverInterface $localeResolver,
  48. \Magento\Framework\Url\EncoderInterface $urlEncoder,
  49. \Magento\MediaStorage\Model\File\Validator\NotProtectedExtension $fileValidator,
  50. \Magento\Framework\Filesystem $filesystem
  51. ) {
  52. parent::__construct($localeDate, $logger, $localeResolver);
  53. $this->urlEncoder = $urlEncoder;
  54. $this->_fileValidator = $fileValidator;
  55. $this->_directory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
  56. }
  57. /**
  58. * Extract data from request and return value
  59. *
  60. * @param RequestInterface $request
  61. * @return array|string|bool
  62. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  63. */
  64. public function extractValue(RequestInterface $request)
  65. {
  66. if ($this->getIsAjaxRequest()) {
  67. return false;
  68. }
  69. $extend = $this->_getRequestValue($request);
  70. $attrCode = $this->getAttribute()->getAttributeCode();
  71. if ($this->_requestScope) {
  72. $value = [];
  73. if (strpos($this->_requestScope, '/') !== false) {
  74. $scopes = explode('/', $this->_requestScope);
  75. $mainScope = array_shift($scopes);
  76. } else {
  77. $mainScope = $this->_requestScope;
  78. $scopes = [];
  79. }
  80. if (!empty($_FILES[$mainScope])) {
  81. foreach ($_FILES[$mainScope] as $fileKey => $scopeData) {
  82. foreach ($scopes as $scopeName) {
  83. if (isset($scopeData[$scopeName])) {
  84. $scopeData = $scopeData[$scopeName];
  85. } else {
  86. $scopeData[$scopeName] = [];
  87. }
  88. }
  89. if (isset($scopeData[$attrCode])) {
  90. $value[$fileKey] = $scopeData[$attrCode];
  91. }
  92. }
  93. } else {
  94. $value = [];
  95. }
  96. } else {
  97. if (isset($_FILES[$attrCode])) {
  98. $value = $_FILES[$attrCode];
  99. } else {
  100. $value = [];
  101. }
  102. }
  103. if (!empty($extend['delete'])) {
  104. $value['delete'] = true;
  105. }
  106. return $value;
  107. }
  108. /**
  109. * Validate file by attribute validate rules and return array of errors
  110. *
  111. * @param array $value
  112. * @return string[]
  113. */
  114. protected function _validateByRules($value)
  115. {
  116. $label = $this->getAttribute()->getStoreLabel();
  117. $rules = $this->getAttribute()->getValidateRules();
  118. $extension = pathinfo($value['name'], PATHINFO_EXTENSION);
  119. if (!empty($rules['file_extensions'])) {
  120. $extensions = explode(',', $rules['file_extensions']);
  121. $extensions = array_map('trim', $extensions);
  122. if (!in_array($extension, $extensions)) {
  123. return [__('"%1" is not a valid file extension.', $label)];
  124. }
  125. }
  126. /**
  127. * Check protected file extension
  128. */
  129. if (!$this->_fileValidator->isValid($extension)) {
  130. return $this->_fileValidator->getMessages();
  131. }
  132. if (empty($value['tmp_name'])) {
  133. return [__('"%1" is not a valid file.', $label)];
  134. }
  135. if (!empty($rules['max_file_size'])) {
  136. $size = $value['size'];
  137. if ($rules['max_file_size'] < $size) {
  138. return [__('"%1" exceeds the allowed file size.', $label)];
  139. }
  140. }
  141. return [];
  142. }
  143. /**
  144. * Validate data
  145. *
  146. * @param array|string $value
  147. * @return bool
  148. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  149. * @SuppressWarnings(PHPMD.NPathComplexity)
  150. */
  151. public function validateValue($value)
  152. {
  153. if ($this->getIsAjaxRequest()) {
  154. return true;
  155. }
  156. $fileData = $value;
  157. if (is_string($value) && !empty($value)) {
  158. $dir = $this->_directory->getAbsolutePath($this->getAttribute()->getEntityType()->getEntityTypeCode());
  159. $fileData = [
  160. 'size' => filesize($dir . $value),
  161. 'name' => $value,
  162. 'tmp_name' => $dir . $value
  163. ];
  164. }
  165. $errors = [];
  166. $attribute = $this->getAttribute();
  167. $toDelete = !empty($value['delete']) ? true : false;
  168. $toUpload = !empty($value['tmp_name']) || is_string($value) && !empty($value) ? true : false;
  169. if (!$toUpload && !$toDelete && $this->getEntity()->getData($attribute->getAttributeCode())) {
  170. return true;
  171. }
  172. if (!$attribute->getIsRequired() && !$toUpload) {
  173. return true;
  174. }
  175. if ($attribute->getIsRequired() && !$toUpload) {
  176. $label = __($attribute->getStoreLabel());
  177. $errors[] = __('"%1" is a required value.', $label);
  178. }
  179. if ($toUpload) {
  180. $errors = array_merge($errors, $this->_validateByRules($fileData));
  181. }
  182. if (count($errors) == 0) {
  183. return true;
  184. } elseif (is_string($value) && !empty($value)) {
  185. $this->_directory->delete($dir . $value);
  186. }
  187. return $errors;
  188. }
  189. /**
  190. * Export attribute value to entity model
  191. *
  192. * @param array|string $value
  193. * @return $this
  194. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  195. */
  196. public function compactValue($value)
  197. {
  198. if ($this->getIsAjaxRequest()) {
  199. return $this;
  200. }
  201. $attribute = $this->getAttribute();
  202. $original = $this->getEntity()->getData($attribute->getAttributeCode());
  203. $toDelete = false;
  204. if ($original) {
  205. if (!$attribute->getIsRequired() && !empty($value['delete'])) {
  206. $toDelete = true;
  207. }
  208. if (!empty($value['tmp_name'])) {
  209. $toDelete = true;
  210. }
  211. }
  212. $destinationFolder = $attribute->getEntityType()->getEntityTypeCode();
  213. // unlink entity file
  214. if ($toDelete) {
  215. $this->getEntity()->setData($attribute->getAttributeCode(), '');
  216. $file = $destinationFolder . $original;
  217. if ($this->_directory->isExist($file)) {
  218. $this->_directory->delete($file);
  219. }
  220. }
  221. if (!empty($value['tmp_name'])) {
  222. try {
  223. $uploader = new \Magento\Framework\File\Uploader($value);
  224. $uploader->setFilesDispersion(true);
  225. $uploader->setFilenamesCaseSensitivity(false);
  226. $uploader->setAllowRenameFiles(true);
  227. $uploader->save($this->_directory->getAbsolutePath($destinationFolder), $value['name']);
  228. $fileName = $uploader->getUploadedFileName();
  229. $this->getEntity()->setData($attribute->getAttributeCode(), $fileName);
  230. } catch (\Exception $e) {
  231. $this->_logger->critical($e);
  232. }
  233. }
  234. return $this;
  235. }
  236. /**
  237. * Restore attribute value from SESSION to entity model
  238. *
  239. * @param array|string $value
  240. * @return $this
  241. *
  242. * @codeCoverageIgnore
  243. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  244. */
  245. public function restoreValue($value)
  246. {
  247. return $this;
  248. }
  249. /**
  250. * Return formatted attribute value from entity model
  251. *
  252. * @param string $format
  253. * @return string|array
  254. */
  255. public function outputValue($format = \Magento\Eav\Model\AttributeDataFactory::OUTPUT_FORMAT_TEXT)
  256. {
  257. $output = '';
  258. $value = $this->getEntity()->getData($this->getAttribute()->getAttributeCode());
  259. if ($value) {
  260. switch ($format) {
  261. case \Magento\Eav\Model\AttributeDataFactory::OUTPUT_FORMAT_JSON:
  262. $output = ['value' => $value, 'url_key' => $this->urlEncoder->encode($value)];
  263. break;
  264. }
  265. }
  266. return $output;
  267. }
  268. }