diff --git a/config/packages/oneup_flysystem.yaml b/config/packages/oneup_flysystem.yaml index 4fa26cc02f..b3fe011b1f 100644 --- a/config/packages/oneup_flysystem.yaml +++ b/config/packages/oneup_flysystem.yaml @@ -2,8 +2,10 @@ oneup_flysystem: cache: my_cache: memory: ~ - adapters: + asset_adapter: + local: + directory: '%kernel.project_dir%/var/upload/assets' resource_adapter: local: directory: '%kernel.project_dir%/var/upload/resource' @@ -11,6 +13,9 @@ oneup_flysystem: local: directory: '%kernel.project_dir%/var/cache/resource' filesystems: + assets: + adapter: asset_adapter + mount: assets_fs resources: adapter: resource_adapter mount: resources_fs diff --git a/config/packages/vich_uploader.yaml b/config/packages/vich_uploader.yaml index 70ac3d9239..462047cae0 100644 --- a/config/packages/vich_uploader.yaml +++ b/config/packages/vich_uploader.yaml @@ -9,7 +9,17 @@ vich_uploader: namer: Vich\UploaderBundle\Naming\SmartUniqueNamer directory_namer: service: Vich\UploaderBundle\Naming\SubdirDirectoryNamer - options: {chars_per_dir: 1, dirs: 3} + options: {chars_per_dir: 1, dirs: 3} # will create directory "a/b/c" for "abcdef.jpg" + inject_on_load: true + delete_on_update: true + delete_on_remove: true + assets: + uri_prefix: '' + upload_destination: assets_fs + namer: Vich\UploaderBundle\Naming\SmartUniqueNamer + directory_namer: + service: Chamilo\CoreBundle\Component\Utils\AssetDirectoryNamer + options: {property: 'category'} inject_on_load: true delete_on_update: true delete_on_remove: true diff --git a/config/services.yaml b/config/services.yaml index c58d1175d5..5580e559a8 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -44,6 +44,8 @@ services: # fetching services directly from the container via $container->get() won't work. # The best practice is to be explicit about your dependencies anyway. + Chamilo\CoreBundle\Component\Utils\AssetDirectoryNamer: + Chamilo\CoreBundle\Component\Utils\Glide: arguments: - {source: '@oneup_flysystem.resources_filesystem', cache: '@oneup_flysystem.cache_resources_filesystem'} diff --git a/src/CoreBundle/Component/Utils/AssetDirectoryNamer.php b/src/CoreBundle/Component/Utils/AssetDirectoryNamer.php new file mode 100644 index 0000000000..7d56b1238f --- /dev/null +++ b/src/CoreBundle/Component/Utils/AssetDirectoryNamer.php @@ -0,0 +1,78 @@ +propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); + $this->transliterator = $transliterator; + } + + + /** + * @param array $options Options for this namer. The following options are accepted: + * - chars_per_dir: how many chars use for each dir. + * - dirs: how many dirs create + */ + public function configure(array $options): void + { + if (empty($options['property'])) { + throw new \InvalidArgumentException('Option "property" is missing or empty.'); + } + + $this->propertyPath = $options['property']; + + $options = \array_merge(['chars_per_dir' => $this->charsPerDir, 'dirs' => $this->dirs], $options); + + $this->charsPerDir = $options['chars_per_dir']; + $this->dirs = $options['dirs']; + } + + public function directoryName($object, PropertyMapping $mapping): string + { + $fileName = $mapping->getFileName($object); + + $category = $this->propertyAccessor->getValue($object, $this->propertyPath); + + $parts[] = $category; + /*for ($i = 0, $start = 0; $i < $this->dirs; $i++, $start += $this->charsPerDir) { + $parts[] = \substr($fileName, $start, $this->charsPerDir); + }*/ + $parts[] = $fileName; + + return \implode('/', $parts); + } +} diff --git a/src/CoreBundle/Entity/Asset.php b/src/CoreBundle/Entity/Asset.php new file mode 100644 index 0000000000..c4eb7ce7fc --- /dev/null +++ b/src/CoreBundle/Entity/Asset.php @@ -0,0 +1,383 @@ +metadata = []; + $this->size = 0; + $this->compressed = false; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + public function __toString(): string + { + return $this->getOriginalName(); + } + + public function isImage(): bool + { + $mimeType = $this->getMimeType(); + if (false !== strpos($mimeType, 'image')) { + return true; + } + + return false; + } + + public function isVideo(): bool + { + $mimeType = $this->getMimeType(); + if (false !== strpos($mimeType, 'video')) { + return true; + } + + return false; + } + + /** + * @return string + */ + public function getCrop() + { + return $this->crop; + } + + /** + * @param string $crop + * + * @return $this + */ + public function setCrop($crop): self + { + $this->crop = $crop; + + return $this; + } + + public function getSize(): int + { + return (int) $this->size; + } + + /** + * @param int $size + */ + public function setSize($size): self + { + $this->size = $size; + + return $this; + } + + /*public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + }*/ + + public function getMimeType(): string + { + return $this->mimeType; + } + + /** + * @param string $mimeType + */ + public function setMimeType($mimeType): self + { + $this->mimeType = $mimeType; + + return $this; + } + + public function getOriginalName(): string + { + return (string) $this->originalName; + } + + /** + * @param string $originalName + */ + public function setOriginalName($originalName): self + { + $this->originalName = $originalName; + + return $this; + } + + public function getDimensions(): array + { + return $this->dimensions; + } + + /** + * @param $dimensions + */ + public function setDimensions($dimensions): self + { + $this->dimensions = $dimensions; + + return $this; + } + + public function getWidth(): int + { + $data = $this->getDimensions(); + if ($data) { + //$data = explode(',', $data); + + return (int) $data[0]; + } + + return 0; + } + + public function getHeight(): int + { + $data = $this->getDimensions(); + + if ($data) { + //$data = explode(',', $data); + + return (int) $data[1]; + } + + return 0; + } + + public function getMetadata(): array + { + return $this->metadata; + } + + public function setMetadata(array $metadata): self + { + $this->metadata = $metadata; + + return $this; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + + return $this; + } + + public function getFile(): ?File + { + return $this->file; + } + + public function hasFile() + { + return null !== $this->file; + } + + /** + * @param File|UploadedFile $file + */ + public function setFile(File $file = null): self + { + $this->file = $file; + + if (null !== $file) { + // It is required that at least one field changes if you are using doctrine + // otherwise the event listeners won't be called and the file is lost + $this->updatedAt = new \DateTimeImmutable(); + } + + return $this; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + * + * @return Asset + */ + public function setTitle(string $title): Asset + { + $this->title = $title; + + return $this; + } + + /** + * @return string + */ + public function getCategory(): string + { + return $this->category; + } + + /** + * @param string $category + * + * @return Asset + */ + public function setCategory(string $category): Asset + { + $this->category = $category; + + return $this; + } + + public function getCompressed(): bool + { + return $this->compressed; + } + + public function setCompressed(bool $compressed) + { + $this->compressed = $compressed; + + return $this; + } +} diff --git a/src/CoreBundle/Framework/Container.php b/src/CoreBundle/Framework/Container.php index 6a0458186d..fb1766fb40 100644 --- a/src/CoreBundle/Framework/Container.php +++ b/src/CoreBundle/Framework/Container.php @@ -6,6 +6,7 @@ namespace Chamilo\CoreBundle\Framework; use Chamilo\CoreBundle\Component\Editor\Editor; use Chamilo\CoreBundle\Manager\SettingsManager; +use Chamilo\CoreBundle\Repository\AssetRepository; use Chamilo\CoreBundle\Repository\CourseCategoryRepository; use Chamilo\CoreBundle\Repository\Node\AccessUrlRepository; use Chamilo\CoreBundle\Repository\Node\CourseRepository; @@ -52,8 +53,10 @@ use Chamilo\CourseBundle\Repository\CThematicPlanRepository; use Chamilo\CourseBundle\Repository\CThematicRepository; use Chamilo\CourseBundle\Repository\CWikiRepository; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Form\FormFactory; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Mailer\Mailer; use Symfony\Component\Routing\Router; use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; use Symfony\Component\Security\Core\Role\RoleHierarchy; @@ -132,18 +135,12 @@ class Container return self::$container->get('security.role_hierarchy'); } - /** - * @return string - */ - public static function getLogDir() + public static function getLogDir(): string { return self::$container->get('kernel')->getLogDir(); } - /** - * @return string - */ - public static function getCacheDir() + public static function getCacheDir(): string { return self::$container->get('kernel')->getCacheDir().'/'; } @@ -260,9 +257,9 @@ class Container return false; } - public static function getMailer() + public static function getMailer(): Mailer { - return self::$container->get('Symfony\Component\Mailer\Mailer'); + return self::$container->get(Mailer::class); } public static function getSettingsManager(): SettingsManager @@ -283,7 +280,7 @@ class Container return \Database::getManager(); } - public static function getUserManager() + public static function getUserManager(): UserRepository { return self::$container->get(UserRepository::class); } @@ -508,10 +505,7 @@ class Container return self::$container->get(CWikiRepository::class); } - /** - * @return \Symfony\Component\Form\FormFactory - */ - public static function getFormFactory() + public static function getFormFactory(): FormFactory { return self::$container->get('form.factory'); } @@ -526,27 +520,22 @@ class Container $session->getFlashBag()->add($type, $message); } - /** - * @return Router - */ - public static function getRouter() + public static function getRouter(): Router { return self::$container->get('router'); } - /** - * @return ToolChain - */ - public static function getToolChain() + public static function getToolChain(): ToolChain { return self::$container->get(ToolChain::class); } - /** - * @param ContainerInterface $container - * @param bool $setSession - */ - public static function setLegacyServices($container, $setSession = true) + public static function getAssetRepository(): AssetRepository + { + return self::$container->get(AssetRepository::class); + } + + public static function setLegacyServices(ContainerInterface $container, bool $setSession = true) { \Database::setConnection($container->get('doctrine.dbal.default_connection')); $em = $container->get('doctrine.orm.entity_manager'); diff --git a/src/CoreBundle/Repository/AssetRepository.php b/src/CoreBundle/Repository/AssetRepository.php new file mode 100644 index 0000000000..85bc5ec33a --- /dev/null +++ b/src/CoreBundle/Repository/AssetRepository.php @@ -0,0 +1,103 @@ +storage = $storage; + $this->mountManager = $mountManager; + } + + public function getFilename(ResourceFile $resourceFile) + { + return $this->storage->resolveUri($resourceFile); + } + + /** + * @return FilesystemInterface + */ + public function getFileSystem() + { + // Flysystem mount name is saved in config/packages/oneup_flysystem.yaml + return $this->mountManager->getFilesystem('assets_fs'); + } + + public function unZipFile(Asset $asset, ZipArchiveAdapter $zipArchiveAdapter) + { + $folder = '/'.$asset->getCategory().'/'.$asset->getTitle(); + + $fs = $this->getFileSystem(); + if ($fs->has($folder)) { + $contents = $zipArchiveAdapter->listContents(); + foreach ($contents as $data) { + if ($fs->has($folder.'/'.$data['path'])) { + continue; + } + + if ('dir' === $data['type']) { + $fs->createDir($folder.'/'.$data['path']); + continue; + } + + $fs->write($folder.'/'.$data['path'], $zipArchiveAdapter->read($data['path'])); + } + } + } + + public function getFileContent(Asset $asset): string + { + try { + if ($asset->hasFile()) { + $file = $asset->getFile(); + $fileName = $this->getFilename($file); + + return $this->getFileSystem()->read($fileName); + } + + return ''; + } catch (\Throwable $exception) { + throw new FileNotFoundException($asset); + } + } + + public function getFileStream(Asset $asset) + { + try { + if ($asset->hasFile()) { + $file = $asset->getFile(); + $fileName = $this->getFilename($file); + + return $this->getFileSystem()->readStream($fileName); + } + + return ''; + } catch (\Throwable $exception) { + throw new FileNotFoundException($asset); + } + } +}