Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
63 changes: 58 additions & 5 deletions src/Controller/Backend/FileEditController.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ class FileEditController extends TwigAwareController implements BackendZoneInter
/** @var EntityManagerInterface */
private $em;

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

public function __construct(CsrfTokenManagerInterface $csrfTokenManager, MediaRepository $mediaRepository, EntityManagerInterface $em)
{
$this->csrfTokenManager = $csrfTokenManager;
$this->mediaRepository = $mediaRepository;
$this->em = $em;
$this->filesystem = new Filesystem();
}

/**
Expand Down Expand Up @@ -109,12 +113,12 @@ public function save(Request $request, UrlGeneratorInterface $urlGenerator): Res
}

/**
* @Route("/delete", name="bolt_file_delete", methods={"POST", "GET"})
* @Route("/file-delete/", name="bolt_file_delete", methods={"POST", "GET"})
*/
public function handleDelete(Request $request): Response
{
try {
$this->validateCsrf($request, 'delete');
$this->validateCsrf($request, 'file-delete');
} catch (InvalidCsrfTokenException $e) {
return new JsonResponse([
'error' => [
Expand All @@ -123,8 +127,6 @@ public function handleDelete(Request $request): Response
], Response::HTTP_FORBIDDEN);
}

$filesystem = new Filesystem();

$locationName = $request->get('location', '');
$path = $request->get('path', '');

Expand All @@ -139,7 +141,40 @@ public function handleDelete(Request $request): Response
$filePath = Path::canonicalize($locationName . '/' . $path);

try {
$filesystem->remove($filePath);
$this->filesystem->remove($filePath);
} catch (\Throwable $e) {
// something wrong happened, we don't need the uploaded files anymore
throw $e;
}

$this->addFlash('success', 'file.delete_success');
return $this->redirectToRoute('bolt_filemanager', ['location' => $locationName]);
}

/**
* @Route("/file-duplicate/", name="bolt_file_duplicate", methods={"POST", "GET"})
*/
public function handleDuplicate(Request $request): Response
{
try {
$this->validateCsrf($request, 'file-duplicate');
} catch (InvalidCsrfTokenException $e) {
return new JsonResponse([
'error' => [
'message' => 'Invalid CSRF token',
],
], Response::HTTP_FORBIDDEN);
}

$locationName = $request->get('location', '');
$path = $request->get('path', '');

$originalFilepath = Path::canonicalize($locationName . '/' . $path);

$copyFilePath = $this->getCopyFilepath($originalFilepath);

try {
$this->filesystem->copy($originalFilepath, $copyFilePath);
} catch (\Throwable $e) {
// something wrong happened, we don't need the uploaded files anymore
throw $e;
Expand All @@ -149,6 +184,24 @@ public function handleDelete(Request $request): Response
return $this->redirectToRoute('bolt_filemanager', ['location' => $locationName]);
}

/**
* @return string Returns the copy file path. E.g. 'files/foal.jpg' -> 'files/foal (1).jpg'
*/
private function getCopyFilepath(string $path): string
{
$copyPath = $path;

$i = 1;
while ($this->filesystem->exists($copyPath)) {
$pathinfo = pathinfo($path);
$basename = basename($pathinfo['basename'], '.' . $pathinfo['extension']) . ' (' . $i . ')';
$copyPath = Path::canonicalize($pathinfo['dirname'] . '/' . $basename . '.' . $pathinfo['extension']);
$i++;
}

return $copyPath;
}

private function verifyYaml(string $yaml): bool
{
$yamlParser = new Parser();
Expand Down
6 changes: 4 additions & 2 deletions templates/finder/_files_actions.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@
<i class="fas fa-w fa-external-link-square-alt"></i>
{{ 'files_cards.action_view_original'|trans }}
</a>
<a class="dropdown-item" href="#">
<a class="dropdown-item"
href="{{ path('bolt_file_duplicate',
{'location': location.key|default('files'), 'path': file.getRelativePathname, '_csrf_token': csrf_token('file-duplicate')}) }}">
<i class="far fa-w fa-copy"></i>
{{ 'files_cards.action_duplicate'|trans }} {{ file.getRelativePathname|excerpt(22) }}
</a>

<a class="dropdown-item delete" ,
href="{{ path('bolt_file_delete',
{'location': location.key|default('files'), 'path': file.getRelativePathname, '_csrf_token': csrf_token('delete')}) }}"
{'location': location.key|default('files'), 'path': file.getRelativePathname, '_csrf_token': csrf_token('file-delete')}) }}"
data-confirmation="{{ 'file.delete_confirm'|trans }}">
<i class="fas fa-w fa-trash"></i>
{{ 'files_cards.action_delete'|trans }} {{ file.getRelativePathname|excerpt(22) }}
Expand Down
38 changes: 37 additions & 1 deletion tests/e2e/filemanager.feature
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,40 @@ Feature: Filemanager
When I press "Cancel"

Then I should not see "File deleted successfully"
And I should see "_a-sunrise.jpeg" in the 2nd ".admin__body--main tr" element
And I should see "_a-sunrise.jpeg" in the 2nd ".admin__body--main tr" element

@javascript
@testme
Scenario: As an Admin I want to duplicate a file
Given I am logged in as "admin"
And I am on "/bolt/filemanager/files"

Then I should see "_a-sunrise.jpeg" in the 2nd ".admin__body--main tr" element
And I should not see "I should see _a-sunrise (1).jpeg"

When I click the 1st ".edit-actions__dropdown-toggler"
And I follow "Duplicate _a-sunrise.jpeg"

Then I should be on "/bolt/filemanager/files"
And I should see "_a-sunrise (1).jpeg"
And I should see "_a-sunrise.jpeg"

When I click the 2nd ".edit-actions__dropdown-toggler"
And I follow "Duplicate _a-sunrise.jpeg"

Then I should be on "/bolt/filemanager/files"
And I should see "_a-sunrise (2).jpeg"
And I should see "_a-sunrise (1).jpeg"
And I should see "_a-sunrise.jpeg"

# This is the end of the test. Below is cleanup.
Then I click the 2nd ".edit-actions__dropdown-toggler"
And I follow "Delete _a-sunrise (2).jpeg"
And I wait for ".modal-dialog"
And I press "OK"

Then I click the 1st ".edit-actions__dropdown-toggler"
And I follow "Delete _a-sunrise (1).jpeg"
And I wait for ".modal-dialog"
And I press "OK"