diff --git a/config/services.yaml b/config/services.yaml index 9e158ff6..2ce270f7 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -42,6 +42,11 @@ services: tags: - { name: doctrine.orm.entity_listener } + attachment_delete_listener: + class: App\EntityListeners\AttachmentDeleteListener + tags: + - name: doctrine.orm.entity_listener + App\Command\UpdateExchangeRatesCommand: arguments: diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index d3508c35..00daf724 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -37,6 +37,7 @@ use Symfony\Component\Validator\Constraints as Assert; * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="class_name", type="string") * @ORM\DiscriminatorMap({"PartDB\Part" = "PartAttachment", "Part" = "PartAttachment"}) + * @ORM\EntityListeners({"App\EntityListeners\AttachmentDeleteListener"}) * */ abstract class Attachment extends NamedDBElement @@ -124,17 +125,6 @@ abstract class Attachment extends NamedDBElement return $this->element; } - /** - * Checks if the file in this attachement is existing. This works for files on the HDD, and for URLs - * (it's not checked if the ressource behind the URL is really existing). - * - * @return bool True if the file is existing. - */ - public function isFileExisting(): bool - { - return file_exists($this->getPath()) || static::isURL($this->getPath()); - } - /** * The URL to the external file. * Returns null, if the file is not external. @@ -273,11 +263,11 @@ abstract class Attachment extends NamedDBElement { //Only set if the URL is not empty if (!empty($url)) { - $this->path = $url; - } + if (strpos($url, '%BASE%') !== false || strpos($url, '%MEDIA%') !== false) { + throw new \InvalidArgumentException("You can not reference internal files via the url field! But nice try!"); + } - if (strpos($url, '%BASE%') !== false || strpos($url, '%MEDIA%') !== false) { - throw new \InvalidArgumentException("You can not reference internal files via the url field! But nice try!"); + $this->path = $url; } return $this; diff --git a/src/EntityListeners/AttachmentDeleteListener.php b/src/EntityListeners/AttachmentDeleteListener.php new file mode 100644 index 00000000..239ef65e --- /dev/null +++ b/src/EntityListeners/AttachmentDeleteListener.php @@ -0,0 +1,91 @@ +attachmentReverseSearch = $attachmentReverseSearch; + $this->attachmentHelper = $attachmentHelper; + } + + /** + * Removes the file associated with the attachment, if the file associated with the attachment changes. + * @param Attachment $attachment + * @param PreUpdateEventArgs $event + * + * @PreUpdate + */ + public function preUpdateHandler(Attachment $attachment, PreUpdateEventArgs $event) + { + if ($event->hasChangedField('path')) { + $file = new \SplFileInfo($this->attachmentHelper->placeholderToRealPath($event->getOldValue('path'))); + $this->attachmentReverseSearch->deleteIfNotUsed($file); + } + } + + /** + * Removes the file associated with the attachment, after the attachment was deleted. + * + * @param Attachment $attachment + * @param LifecycleEventArgs $event + * + * @PostRemove + */ + public function postRemoveHandler(Attachment $attachment, LifecycleEventArgs $event) + { + $file = $this->attachmentHelper->attachmentToFile($attachment); + //Only delete if the attachment has a valid file. + if ($file !== null) { + $this->attachmentReverseSearch->deleteIfNotUsed($file); + } + } + +} \ No newline at end of file diff --git a/src/Services/AttachmentHelper.php b/src/Services/AttachmentHelper.php index 3530da19..ae8ac403 100644 --- a/src/Services/AttachmentHelper.php +++ b/src/Services/AttachmentHelper.php @@ -34,10 +34,8 @@ namespace App\Services; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\PartAttachment; -use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Filesystem\Filesystem; -use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpKernel\KernelInterface; @@ -71,6 +69,21 @@ class AttachmentHelper return $this->base_path; } + /** + * Gets an SPLFileInfo object representing the file associated with the attachment. + * @param Attachment $attachment The attachment for which the file should be generated + * @return \SplFileInfo|null The fileinfo for the attachment file. Null, if the attachment is external or has + * invalid file. + */ + public function attachmentToFile(Attachment $attachment) : ?\SplFileInfo + { + if ($attachment->isExternal() || !$this->isFileExisting($attachment)) { + return null; + } + + return new \SplFileInfo($this->toAbsoluteFilePath($attachment)); + } + /** * Converts an relative placeholder filepath (with %MEDIA% or older %BASE%) to an absolute filepath on disk. * @param string $placeholder_path The filepath with placeholder for which the real path should be determined. diff --git a/src/Services/AttachmentReverseSearch.php b/src/Services/AttachmentReverseSearch.php index 3d69d303..d80ac220 100644 --- a/src/Services/AttachmentReverseSearch.php +++ b/src/Services/AttachmentReverseSearch.php @@ -33,6 +33,7 @@ namespace App\Services; use App\Entity\Attachments\Attachment; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\File\File; /** @@ -66,4 +67,22 @@ class AttachmentReverseSearch return $repo->findBy(['path' => [$relative_path_new, $relative_path_old]]); } + /** + * Deletes the given file if it is not used by more than $threshold attachments + * @param \SplFileInfo $file The file that should be removed + * @param int $threshold The threshold used, to determine if a file should be deleted or not. + * @return bool True, if the file was delete. False if not. + */ + public function deleteIfNotUsed(\SplFileInfo $file, int $threshold = 0) : bool + { + /* When the file is used more then $threshold times, don't delete it */ + if (count($this->findAttachmentsByFile($file)) > $threshold) { + return false; + } + + $fs = new Filesystem(); + $fs->remove($file); + + return true; + } } \ No newline at end of file