PageRenderTime 26ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/app/Services/UploadService.php

https://bitbucket.org/lfalmeida/filestorage
PHP | 290 lines | 166 code | 33 blank | 91 comment | 16 complexity | bffa82b11a92de33e06b3ca16f6e4150 MD5 | raw file
  1. <?php
  2. namespace App\Services;
  3. use App\Contracts\DocumentRepositoryInterface as Repository;
  4. use App\Contracts\UploadServiceInterface;
  5. use App\Jobs\ImageAnalysisJob;
  6. use App\Libraries\Formatter;
  7. use App\Models\Document;
  8. use Illuminate\Http\UploadedFile;
  9. use Illuminate\Support\Facades\App;
  10. use Illuminate\Support\Facades\Config;
  11. use Illuminate\Support\Facades\Storage;
  12. use Mockery\Exception;
  13. /**
  14. * Class Uploader
  15. * @package App\Libraries
  16. */
  17. class UploadService implements UploadServiceInterface
  18. {
  19. const EXIF_DATE_TIME_KEY = 'DateTime';
  20. /**
  21. * @var string $resource Indica o Subdiretório onde será armazenado o arquivo
  22. */
  23. public $resource = 'warehouse';
  24. /**
  25. * @var Repository $repository Repositório que irá armazenar as informações sobre os arquivos recebidos
  26. */
  27. protected $repository;
  28. /**
  29. * @var mixed
  30. */
  31. protected $validationService;
  32. /**
  33. * @var array $fileList Armazena os arquivos que foram recebidos por esta instância
  34. */
  35. private $fileList = [];
  36. /**
  37. * Uploader constructor.
  38. *
  39. * @param Repository $repository
  40. */
  41. public function __construct(Repository $repository)
  42. {
  43. $this->repository = $repository;
  44. $this->validationService = App::make(UploadValidationService::class);
  45. }
  46. /**
  47. * Determina qual será o subdiretório utilizado para armazenar o arquivo
  48. * @param string $resource
  49. */
  50. public function setResource($resource)
  51. {
  52. $this->resource = rtrim($resource, '/');
  53. }
  54. /**
  55. * Recebe os dados e determina delega a ação para o método que gerencia um ou para o método
  56. * que gerencia vários arquivos, dependendo do tipo de dados recebidos.
  57. *
  58. * @param mixed $files Dados recebidos no request contendo os arquivos
  59. * @return Document|array Um objeto Documento ou um array de objetos Documento
  60. * @throws \Exception Lança uma Exception caso os dados recebidos sejam inválidos
  61. */
  62. public function handle($files)
  63. {
  64. if (is_array($files)) {
  65. return $this->handleMultipleFiles($files);
  66. }
  67. if (is_object($files)) {
  68. return $this->handleSingleFile($files);
  69. }
  70. throw new \InvalidArgumentException("Dados inválidos para upload");
  71. }
  72. /**
  73. * Trata o caso do recebimento de vários arquivos no mesmo Request
  74. * @param array $files Arquivos enviados no Request
  75. * @return array Arquivos recebidos e tratados
  76. * @throws \Exception
  77. */
  78. public function handleMultipleFiles(array $files)
  79. {
  80. foreach ($files as $file) {
  81. $this->handleSingleFile($file);
  82. }
  83. return $this->getUploadedFilesList();
  84. }
  85. /**
  86. * Realiza o upload de um único arquivo e retorna um objeto Document com as informações
  87. * sobre o arquivo que foi recebido
  88. *
  89. * @param $file UploadedFile Arquivo recebido no request
  90. * @return Document Dados gerados pelo upload
  91. * @throws \Exception Uma Exception é lançada caso os dados recebidos sejam inválidos
  92. */
  93. public function handleSingleFile($file)
  94. {
  95. if (!$file instanceof UploadedFile) {
  96. throw new \InvalidArgumentException("Dados inválidos para upload");
  97. }
  98. $this->validate($file);
  99. $document = $this->createDocument($file);
  100. $disk = Storage::disk()->getDriver();
  101. $disk->put($document->getPath(), fopen($file, 'r+'), [
  102. 'visibility' => 'public',
  103. 'ContentType' => $document->getMimeType()
  104. ]);
  105. $this->saveDocument($document);
  106. $this->addFileToList($document);
  107. return $document;
  108. }
  109. /**
  110. * @param $file UploadedFile
  111. * @return bool
  112. */
  113. private function validate(UploadedFile $file)
  114. {
  115. if ($this->validationService->validateWhitelisted($file)) {
  116. return $this->validationService->validateResourceUpload($file, $this->resource);
  117. }
  118. return false;
  119. }
  120. /**
  121. * Gera um objeto Document a partir de um arquivo recebido
  122. *
  123. * @param $file UploadedFile Arquivo tratado no upload
  124. * @return Document
  125. * @throws \Exception
  126. */
  127. private function createDocument(UploadedFile $file)
  128. {
  129. $data = [
  130. 'id' => $this->getId(),
  131. 'originalName' => $file->getClientOriginalName(),
  132. 'extension' => strtolower($file->getClientOriginalExtension()),
  133. 'size' => $file->getSize(),
  134. 'mimeType' => $file->getClientMimeType(),
  135. 'storageDisk' => Config::get('filesystems.default'),
  136. 'hash' => $file->getRealPath()
  137. ];
  138. $data['path'] = sprintf('%s/%s.%s', $this->resource, $data['id'], $data['extension']);
  139. $data['url'] = implode('/', [env('APP_URL'), $data['path']]);
  140. try {
  141. /**
  142. * se for uma imagem jpg, tentar extrair exif e verificar a orientação
  143. */
  144. if ($this->validationService->validateMimeType($file, ['image/jpeg'])) {
  145. $data['exif'] = $this->extractExifData($file);
  146. $this->checkAndFixImageOrientation($file);
  147. }
  148. } catch (\Exception $e) {
  149. }
  150. return new Document($data);
  151. }
  152. /**
  153. * Gera uma string parcialmente randômica para que seja atribuida ao arquivo recebido
  154. * O primeiro segmento da string é o unix timestamp do momento do upload, os outros
  155. * segmentos, são randômicos.
  156. *
  157. * @return string
  158. * @example: 1494190264.590f88b83cbca4.57186700
  159. */
  160. private function getId()
  161. {
  162. return (string)uniqid(sprintf('%s.', time()), true);
  163. }
  164. /**
  165. * @param UploadedFile $file
  166. * @return array
  167. */
  168. private function extractExifData(UploadedFile $file)
  169. {
  170. if (!function_exists('exif_read_data')) {
  171. throw new Exception(trans('messages.error.EXIF_EXT_NOT_INSTALLED'));
  172. }
  173. $exif = exif_read_data($file->path());
  174. $formatter = new Formatter();
  175. $data = [];
  176. if (isset($exif["GPSLongitude"]) && isset($exif["GPSLatitude"])) {
  177. $data['latitude'] = $formatter->gpsDegreesToCoordinates(
  178. $exif["GPSLatitude"],
  179. $exif["GPSLatitudeRef"]
  180. );
  181. $data['longitude'] = $formatter->gpsDegreesToCoordinates(
  182. $exif["GPSLongitude"],
  183. $exif["GPSLongitudeRef"]
  184. );
  185. }
  186. if (isset($exif[self::EXIF_DATE_TIME_KEY]) && !empty($exif[self::EXIF_DATE_TIME_KEY])) {
  187. $data['dateTimeTaken'] = $exif[self::EXIF_DATE_TIME_KEY];
  188. }
  189. return $data;
  190. }
  191. /**
  192. * @param UploadedFile $file
  193. */
  194. private function checkAndFixImageOrientation(UploadedFile $file)
  195. {
  196. if (!function_exists('exif_read_data')) {
  197. throw new Exception(trans('messages.error.EXIF_EXT_NOT_INSTALLED'));
  198. }
  199. $exif = exif_read_data($file->path());
  200. if (!empty($exif['Orientation'])) {
  201. $imageResource = imagecreatefromjpeg($file->path());
  202. switch ($exif['Orientation']) {
  203. case 3:
  204. $image = imagerotate($imageResource, 180, 0);
  205. break;
  206. case 6:
  207. $image = imagerotate($imageResource, -90, 0);
  208. break;
  209. case 8:
  210. $image = imagerotate($imageResource, 90, 0);
  211. break;
  212. default:
  213. $image = $imageResource;
  214. }
  215. imagejpeg($image, $file->path(), 90);
  216. if ($imageResource) {
  217. imagedestroy($imageResource);
  218. }
  219. if ($image) {
  220. imagedestroy($image);
  221. }
  222. }
  223. }
  224. /**
  225. * Salva os dados de um Document utilizando o Repository
  226. *
  227. * @param $document
  228. */
  229. private function saveDocument(Document $document)
  230. {
  231. $entity = $document->toArray();
  232. //não persistir a url web para evitar acoplamento
  233. unset($entity['url']);
  234. $this->repository->save($entity);
  235. if (env('ENABLE_IMAGE_ANALYSIS', false) &&
  236. in_array($document->getMimeType(), config('upload.default.images'))) {
  237. // Se for uma imagem, e a análise estiver habilitada, disparar job de análise
  238. dispatch(new ImageAnalysisJob($document));
  239. }
  240. }
  241. /**
  242. * Adiciona na lista de arquivos desta instância um Document representando um arquivo
  243. * @param $document Document
  244. */
  245. public function addFileToList(Document $document)
  246. {
  247. $this->fileList[] = $document;
  248. }
  249. /**
  250. * Retorna a lista de de objetos Document desta instância
  251. * @return array
  252. */
  253. public function getUploadedFilesList()
  254. {
  255. return $this->fileList;
  256. }
  257. }