pathResolver = $pathResolver; $this->allow_attachments_downloads = $allow_attachments_downloads; //The mapping used to determine which folder will be used for an attachment type $this->folder_mapping = [PartAttachment::class => 'part', AttachmentTypeAttachment::class => 'attachment_type', CategoryAttachment::class => 'category', CurrencyAttachment::class => 'currency', DeviceAttachment::class => 'device', FootprintAttachment::class => 'footprint', GroupAttachment::class => 'group', ManufacturerAttachment::class => 'manufacturer', MeasurementUnitAttachment::class => 'measurement_unit', StorelocationAttachment::class => 'storelocation', SupplierAttachment::class => 'supplier', UserAttachment::class => 'user']; } protected function configureOptions(OptionsResolver $resolver) : void { $resolver->setDefaults([ //If no preview image was set yet, the new uploaded file will become the preview image 'become_preview_if_empty' => true, //When an URL is given download the URL 'download_url' => false, 'secure_attachment' => false, ]); } /** * Generates a filename for the given attachment and extension. * The filename contains a random id, so every time this function is called you get an unique name. * @param Attachment $attachment The attachment that should be used for generating an attachment * @param string $extension The extension that the new file should have (must only contain chars allowed in pathes) * @return string The new filename. */ public function generateAttachmentFilename(Attachment $attachment, string $extension) : string { //Normalize extension $extension = transliterator_transliterate( 'Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $extension ); //Use the (sanatized) attachment name as an filename part $safeName = transliterator_transliterate( 'Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $attachment->getName() ); return $safeName . '-' . uniqid('', false) . '.' . $extension; } /** * Generates an (absolute) path to a folder where the given attachment should be stored. * @param Attachment $attachment The attachment that should be used for * @param bool $secure_upload True if the file path should be located in a safe location * @return string The absolute path for the attachment folder. */ public function generateAttachmentPath(Attachment $attachment, bool $secure_upload = false) : string { if ($secure_upload) { $base_path = $this->pathResolver->getSecurePath(); } else { $base_path = $this->pathResolver->getMediaPath(); } //Ensure the given attachment class is known to mapping if (!isset($this->folder_mapping[get_class($attachment)])) { throw new \InvalidArgumentException( 'The given attachment class is not known! The passed class was: ' . get_class($attachment) ); } //Ensure the attachment has an assigned element if ($attachment->getElement() === null) { throw new \InvalidArgumentException( 'The given attachment is not assigned to an element! An element is needed to generate a path!' ); } //Build path return $base_path . DIRECTORY_SEPARATOR //Base path . $this->folder_mapping[get_class($attachment)] . DIRECTORY_SEPARATOR . $attachment->getElement()->getID(); } /** * Handle the submit of an attachment form. * This function will move the uploaded file or download the URL file to server, if needed. * @param Attachment $attachment The attachment that should be used for handling. * @param UploadedFile|null $file If given, that file will be moved to the right location * @param array $options The options to use with the upload. Here you can specify that an URL should be downloaded, * or an file should be moved to a secure location. * @return Attachment The attachment with the new filename (same instance as passed $attachment) */ public function handleFormSubmit(Attachment $attachment, ?UploadedFile $file, array $options = []) : Attachment { $resolver = new OptionsResolver(); $this->configureOptions($resolver); $options = $resolver->resolve($options); //When a file is given then upload it, otherwise check if we need to download the URL if ($file) { $this->upload($attachment, $file, $options); } elseif ($options['download_url'] && $attachment->isExternal()) { $this->downloadURL($attachment, $options); } //Check if we should assign this attachment to master picture //this is only possible if the attachment is new (not yet persisted to DB) if ($options['become_preview_if_empty'] && $attachment->getID() === null && $attachment->isPicture()) { $element = $attachment->getElement(); if ($element instanceof AttachmentContainingDBElement && $element->getMasterPictureAttachment() === null) { $element->setMasterPictureAttachment($attachment); } } return $attachment; } /** * Download the URL set in the attachment and save it on the server * @param Attachment $attachment * @param array $options The options from the handleFormSubmit function * @return Attachment The attachment with the new filepath */ protected function downloadURL(Attachment $attachment, array $options) : Attachment { //Check if we are allowed to download files if (!$this->allow_attachments_downloads) { throw new \RuntimeException('Download of attachments is not allowed!'); } } /** * Moves the given uploaded file to a permanent place and saves it into the attachment * @param Attachment $attachment The attachment in which the file should be saved * @param UploadedFile $file The file which was uploaded * @param array $options The options from the handleFormSubmit function * @return Attachment The attachment with the new filepath */ protected function upload(Attachment $attachment, UploadedFile $file, array $options) : Attachment { //Move our temporay attachment to its final location $file_path = $file->move( $this->generateAttachmentPath($attachment, $options['secure_attachment']), $this->generateAttachmentFilename($attachment, $file->getClientOriginalExtension()) )->getRealPath(); //Make our file path relative to %BASE% $file_path = $this->pathResolver->realPathToPlaceholder($file_path); //Save the path to the attachment $attachment->setPath($file_path); //And save original filename $attachment->setFilename($file->getClientOriginalName()); return $attachment; } }