Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement flysystem #3454

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,5 @@ MAILER_DSN=smtp://localhost
###> php-translation/loco-adapter ###
LOCO_PROJECT_API_KEY=
###< php-translation/loco-adapter ###

FILES_STORAGE_SOURCE=files.storage.local
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "🧿 Bolt 5 Core",
"license": "MIT",
"require": {
"php": ">=7.2.9",
"php": "^8.0",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to bump this in order to install flysystem v3

"ext-json": "*",
"ext-mbstring": "*",
"ext-pdo": "*",
Expand All @@ -29,6 +29,7 @@
"jasny/twig-extensions": "^1.3",
"knplabs/doctrine-behaviors": "^2.1",
"knplabs/knp-menu-bundle": "^3.1",
"league/flysystem-bundle": "^3.1",
"league/glide-symfony": "^1.0.4",
"miljar/php-exif": "^0.6.4",
"nelexa/zip": "^3.3 || ^4.0",
Expand Down
1 change: 1 addition & 0 deletions config/bundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
Translation\Bundle\TranslationBundle::class => ['all' => true],
Translation\PlatformAdapter\Loco\Bridge\Symfony\TranslationAdapterLocoBundle::class => ['dev' => true, 'local' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
League\FlysystemBundle\FlysystemBundle::class => ['all' => true],
];
1 change: 0 additions & 1 deletion config/packages/extension_reference-extension.yaml

This file was deleted.

41 changes: 41 additions & 0 deletions config/packages/flysystem.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Read the documentation at https://github.com/thephpleague/flysystem-bundle/blob/master/docs/1-getting-started.md
flysystem:
storages:
files.storage.local:
adapter: 'local'
options:
directory: '%kernel.project_dir%/public/files'
permissions:
file:
public: 0o644
private: 0o600
dir:
public: 0o775
private: 0o700
visibility: public
files.storage:
adapter: 'lazy'
options:
source: '%env(FILES_STORAGE_SOURCE)%'
themes.storage:
adapter: 'local'
options:
directory: '%kernel.project_dir%/public/theme'
permissions:
file:
public: 0o644
private: 0o600
dir:
public: 0o775
private: 0o700
visibility: public
config.storage:
adapter: 'local'
options:
directory: '%kernel.project_dir%/config'
visibility: public
extension_config.storage:
adapter: 'local'
options:
directory: '%kernel.project_dir%/config/extensions'
visibility: public
1 change: 0 additions & 1 deletion config/routes/extension_reference-extension.yaml

This file was deleted.

4 changes: 3 additions & 1 deletion config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,6 @@ services:

Bolt\Api\ContentDataPersister:
decorates: 'api_platform.doctrine.orm.data_persister'


Bolt\Utils\FilesystemManager:
arguments: [ { files: '@files.storage', themes: '@themes.storage', config: '@config.storage', extension_config: '@extension_config.storage' }]
82 changes: 40 additions & 42 deletions src/Controller/Backend/FilemanagerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@
namespace Bolt\Controller\Backend;

use Bolt\Common\Str;
use Bolt\Configuration\FileLocation;
use Bolt\Configuration\FileLocations;
use Bolt\Controller\CsrfTrait;
use Bolt\Controller\TwigAwareController;
use Bolt\Repository\MediaRepository;
use Bolt\Utils\Excerpt;
use Bolt\Utils\PathCanonicalize;
use Bolt\Utils\FilesystemManager;
use League\Flysystem\DirectoryAttributes;
use League\Flysystem\DirectoryListing;
use League\Flysystem\FileAttributes;
use Pagerfanta\Adapter\ArrayAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Path;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;

Expand All @@ -39,14 +40,14 @@ class FilemanagerController extends TwigAwareController implements BackendZoneIn

private const PAGESIZE = 60;

/** @var Filesystem */
private $filesystem;
/** @var FilesystemManager */
private $filesystemManager;

public function __construct(FileLocations $fileLocations, MediaRepository $mediaRepository, RequestStack $requestStack, Filesystem $filesystem)
public function __construct(FileLocations $fileLocations, MediaRepository $mediaRepository, RequestStack $requestStack, FilesystemManager $filesystemManager)
{
$this->fileLocations = $fileLocations;
$this->mediaRepository = $mediaRepository;
$this->filesystem = $filesystem;
$this->filesystemManager = $filesystemManager;
$this->requestStack = $requestStack;
}

Expand Down Expand Up @@ -76,8 +77,8 @@ public function filemanager(string $location): Response

$location = $this->fileLocations->get($location);

$finder = $this->findFiles($location->getBasepath(), $path);
$folders = $this->findFolders($location->getBasepath(), $path);
$finder = $this->findFiles($location, $path);
$folders = $this->findFolders($location, $path);

$currentPage = (int) $this->getFromRequest('page', '1');
$pager = $this->createPaginator($finder, $currentPage);
Expand All @@ -91,7 +92,7 @@ public function filemanager(string $location): Response
'folders' => $folders,
'parent' => $parent,
'media' => $this->mediaRepository->findAll(),
'allfiles' => $location->isShowAll() ? $this->buildIndex($location->getBasepath()) : false,
'allfiles' => $location->isShowAll() ? $this->buildIndex('/', $location) : false,
'view' => $view,
]);
}
Expand All @@ -117,14 +118,14 @@ public function delete(): Response
$this->denyAccessUnlessGranted('managefiles:' . $location);

$location = $this->fileLocations->get($location);
$folder = '/' . $path;
$filesystem = $this->filesystemManager->get($location->getKey());

$folder = Path::canonicalize($location->getBasepath() . '/' . $path);

if (! $this->filesystem->exists($folder)) {
if (! $filesystem->directoryExists($folder)) {
$this->addFlash('warning', 'filemanager.delete_folder_missing');
} else {
try {
$this->filesystem->remove($folder);
$filesystem->deleteDirectory($folder);
$this->addFlash('success', 'filemanager.delete_folder_successful');
} catch (IOException $e) {
$this->addFlash('danger', 'filemanager.delete_folder_error');
Expand Down Expand Up @@ -158,15 +159,15 @@ public function create(): Response
$this->denyAccessUnlessGranted('managefiles:' . $location);

$location = $this->fileLocations->get($location);
$folder = '/' . $path;
$filesystem = $this->filesystemManager->get($location->getKey());

$folder = Path::canonicalize($location->getBasepath() . '/' . $path);

if ($this->filesystem->exists($folder)) {
if ($filesystem->directoryExists($folder)) {
$this->addFlash('warning', 'filemanager.create_folder_already_exists');
$this->addFlash('danger', 'filemanager.create_folder_error');
} else {
try {
$this->filesystem->mkdir($folder);
$filesystem->createDirectory($folder, ['visibility' => 'public']);
$this->addFlash('success', 'filemanager.create_folder_success');
} catch (IOException $exception) {
$this->addFlash('danger', 'filemanager.create_folder_error');
Expand All @@ -179,48 +180,45 @@ public function create(): Response
]);
}

private function findFiles(string $base, string $path): Finder
private function findFiles(FileLocation $location, string $path): DirectoryListing
{
$fullpath = PathCanonicalize::canonicalize($base, $path);

$finder = new Finder();
$finder->in($fullpath)->depth('== 0')->files()->sortByName();

return $finder;
return $this->filesystemManager->get($location->getKey())
->listContents($path, false)
->filter(fn($item) => $item instanceof FileAttributes);
}

private function findFolders(string $base, string $path): Finder
private function findFolders(FileLocation $location, string $path): DirectoryListing
{
$fullpath = PathCanonicalize::canonicalize($base, $path);

$finder = new Finder();
$finder->in($fullpath)->depth('== 0')->directories()->sortByName();

return $finder;
return $this->filesystemManager->get($location->getKey())
->listContents($path, false)
->filter(fn($item) => $item instanceof DirectoryAttributes);
}

private function createPaginator(Finder $finder, int $page): Pagerfanta
private function createPaginator(DirectoryListing $listing, int $page): Pagerfanta
{
$paginator = new Pagerfanta(new ArrayAdapter(iterator_to_array($finder, true)));
$paginator = new Pagerfanta(new ArrayAdapter(iterator_to_array($listing->getIterator(), true)));
$paginator->setMaxPerPage(self::PAGESIZE);
$paginator->setCurrentPage($page);

return $paginator;
}

private function buildIndex(string $base)
private function buildIndex(string $base, FileLocation $location)
{
$fullpath = Path::canonicalize($base);
$filesystem = $this->filesystemManager->get($location->getKey());

$finder = new Finder();
$finder->in($fullpath)->depth('< 5')->sortByName()->files();
$files = $filesystem
->listContents($base, true)
->filter(fn($item) => $item instanceof FileAttributes);

$index = [];

foreach ($finder as $file) {
$contents = $this->getFileSummary($file->getContents());
foreach ($files as $file) {
$contents = $this->getFileSummary(
$filesystem->read($file->path())
);
$index[] = [
'filename' => $file->getRelativePathname(),
'filename' => $file->path(),
'description' => $contents,
];
}
Expand Down
20 changes: 11 additions & 9 deletions src/Controller/ImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
namespace Bolt\Controller;

use Bolt\Configuration\Config;
use Bolt\Utils\FilesystemManager;
use League\Glide\Responses\SymfonyResponseFactory;
use League\Glide\Server;
use League\Glide\ServerFactory;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;

class ImageController
Expand All @@ -23,16 +23,20 @@ class ImageController
/** @var Server */
private $server;

/** @var FilesystemManager */
private $filesystemManager;

/** @var array */
private $parameters = [];

/** @var Request */
private $request;

public function __construct(Config $config, RequestStack $requestStack)
public function __construct(Config $config, RequestStack $requestStack, FilesystemManager $filesystemManager)
{
$this->config = $config;
$this->request = $requestStack->getCurrentRequest();
$this->filesystemManager = $filesystemManager;
}

/**
Expand All @@ -55,7 +59,7 @@ private function createServer(): void
{
$this->server = ServerFactory::create([
'response' => new SymfonyResponseFactory(),
'source' => $this->getPath(),
'source' => $this->filesystemManager->get('files'),
'cache' => $this->getPath('cache', true, 'thumbnails'),
]);
}
Expand All @@ -80,7 +84,7 @@ private function saveAsFile(string $paramString, string $filename): void
return;
}

$filesystem = new Filesystem();
$filesystem = new SymfonyFilesystem();
$filePath = sprintf('%s%s%s%s%s', $this->getPath('thumbs'), DIRECTORY_SEPARATOR, $paramString, DIRECTORY_SEPARATOR, $filename);
$folderMode = $this->config->get('general/filepermissions/folders', 0775);
$fileMode = $this->config->get('general/filepermissions/files', 0664);
Expand Down Expand Up @@ -115,17 +119,15 @@ private function buildImage(string $filename): string

private function buildResponse(string $filename): Response
{
$filepath = $this->getPath(null, false, $filename);

if (! (new Filesystem())->exists($filepath)) {
if (! $this->filesStorage->fileExists($filename)) {
// $notice = sprintf("The file '%s' does not exist.", $filepath);

return $this->sendErrorImage();
}

// In case we're trying to "thumbnail" an svg, just return the whole thing.
if ($this->isSvg($filename)) {
$response = new Response(file_get_contents($filepath));
$response = new Response(file_get_contents($this->filesStorage->read($filename)));
$response->headers->set('Content-Type', 'image/svg+xml');

return $response;
Expand Down
Loading