Skip to content

Commit 60ed28d

Browse files
committed
[TASK] Add example extension
Adds the EXT:example for testing purposes.
1 parent 9844ec3 commit 60ed28d

10 files changed

Lines changed: 400 additions & 1 deletion

File tree

Build/composer.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"typo3/cms-info": "^13.4",
1818
"typo3/cms-lowlevel": "^13.4",
1919
"typo3/cms-extensionmanager": "^13.4",
20-
"b13/db-file-storage": "@dev"
20+
"b13/db-file-storage": "@dev",
21+
"db-store/example": "@dev"
2122
},
2223
"require-dev": {
2324
"typo3/testing-framework": "^9.0",
@@ -30,6 +31,13 @@
3031
"options": {
3132
"symlink": true
3233
}
34+
},
35+
{
36+
"type": "path",
37+
"url": "../Tests/example",
38+
"options": {
39+
"symlink": true
40+
}
3341
}
3442
],
3543
"config": {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DbStore\Example\Controller;
6+
7+
use B13\DbFileStorage\Domain\Repository\StoredFileReferenceRepository;
8+
use B13\DbFileStorage\Service\DatabaseFileStorage;
9+
use Psr\Http\Message\ResponseInterface;
10+
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
11+
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
12+
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
13+
14+
final class ExtbaseFileController extends ActionController
15+
{
16+
public function __construct(
17+
private readonly DatabaseFileStorage $storage,
18+
private readonly StoredFileReferenceRepository $repository,
19+
private readonly ModuleTemplateFactory $moduleTemplateFactory,
20+
) {}
21+
22+
public function initializeAction(): void
23+
{
24+
$querySettings = $this->repository->createQuery()->getQuerySettings();
25+
$querySettings->setRespectStoragePage(false);
26+
$this->repository->setDefaultQuerySettings($querySettings);
27+
}
28+
29+
public function listAction(): ResponseInterface
30+
{
31+
$view = $this->moduleTemplateFactory->create($this->request);
32+
$view->assign('files', $this->repository->findAll());
33+
$view->setFlashMessageQueue($this->getFlashMessageQueue());
34+
return $view->renderResponse('ExtbaseFile/List');
35+
}
36+
37+
public function uploadAction(): ResponseInterface
38+
{
39+
$uploadedFiles = $this->request->getUploadedFiles();
40+
$uploadedFile = $uploadedFiles['file'] ?? null;
41+
if ($uploadedFile === null) {
42+
$this->addFlashMessage('No file uploaded.', 'Error', ContextualFeedbackSeverity::ERROR);
43+
return $this->redirect('list');
44+
}
45+
46+
$stored = $this->storage->store($uploadedFile);
47+
$this->addFlashMessage(
48+
sprintf('File "%s" uploaded (%d bytes).', $stored->filename, $stored->size),
49+
'Upload successful',
50+
);
51+
return $this->redirect('list');
52+
}
53+
54+
public function downloadAction(int $file): ResponseInterface
55+
{
56+
return $this->storage->createResponse($file, forceDownload: true);
57+
}
58+
59+
public function deleteAction(int $file): ResponseInterface
60+
{
61+
$this->storage->delete($file);
62+
$this->addFlashMessage('File deleted.');
63+
return $this->redirect('list');
64+
}
65+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DbStore\Example\Controller;
6+
7+
use B13\DbFileStorage\Service\DatabaseFileStorage;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use TYPO3\CMS\Backend\Attribute\AsController;
11+
use TYPO3\CMS\Backend\Routing\UriBuilder;
12+
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
13+
use TYPO3\CMS\Core\Database\ConnectionPool;
14+
use TYPO3\CMS\Core\Http\RedirectResponse;
15+
use TYPO3\CMS\Core\Messaging\FlashMessage;
16+
use TYPO3\CMS\Core\Messaging\FlashMessageService;
17+
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
18+
19+
#[AsController]
20+
final class NativeFileController
21+
{
22+
public function __construct(
23+
private readonly DatabaseFileStorage $storage,
24+
private readonly ModuleTemplateFactory $moduleTemplateFactory,
25+
private readonly ConnectionPool $connectionPool,
26+
private readonly UriBuilder $uriBuilder,
27+
private readonly FlashMessageService $flashMessageService,
28+
) {}
29+
30+
public function listAction(ServerRequestInterface $request): ResponseInterface
31+
{
32+
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(DatabaseFileStorage::TABLE_NAME);
33+
$rows = $queryBuilder
34+
->select('uid', 'filename', 'mime_type', 'size', 'crdate')
35+
->from(DatabaseFileStorage::TABLE_NAME)
36+
->orderBy('uid', 'DESC')
37+
->executeQuery()
38+
->fetchAllAssociative();
39+
40+
$view = $this->moduleTemplateFactory->create($request);
41+
$view->assign('files', $rows);
42+
$view->assign('uploadUri', (string)$this->uriBuilder->buildUriFromRoute('example_native.upload'));
43+
$view->assign('deleteUri', (string)$this->uriBuilder->buildUriFromRoute('example_native.delete'));
44+
45+
return $view->renderResponse('NativeFile/List');
46+
}
47+
48+
public function uploadAction(ServerRequestInterface $request): ResponseInterface
49+
{
50+
$uploadedFiles = $request->getUploadedFiles();
51+
$uploadedFile = $uploadedFiles['file'] ?? null;
52+
53+
if ($uploadedFile !== null) {
54+
$stored = $this->storage->store($uploadedFile);
55+
$this->enqueueFlashMessage(
56+
sprintf('File "%s" uploaded (%d bytes).', $stored->filename, $stored->size),
57+
'Upload successful',
58+
);
59+
} else {
60+
$this->enqueueFlashMessage('No file uploaded.', 'Error', ContextualFeedbackSeverity::ERROR);
61+
}
62+
63+
return new RedirectResponse($this->uriBuilder->buildUriFromRoute('example_native'));
64+
}
65+
66+
public function downloadAction(ServerRequestInterface $request): ResponseInterface
67+
{
68+
$fileUid = (int)($request->getQueryParams()['file'] ?? 0);
69+
return $this->storage->createResponse($fileUid, forceDownload: true);
70+
}
71+
72+
public function deleteAction(ServerRequestInterface $request): ResponseInterface
73+
{
74+
$fileUid = (int)($request->getParsedBody()['file'] ?? 0);
75+
$this->storage->delete($fileUid);
76+
$this->enqueueFlashMessage('File deleted.');
77+
78+
return new RedirectResponse($this->uriBuilder->buildUriFromRoute('example_native'));
79+
}
80+
81+
private function enqueueFlashMessage(
82+
string $body,
83+
string $title = '',
84+
ContextualFeedbackSeverity $severity = ContextualFeedbackSeverity::OK,
85+
): void {
86+
$this->flashMessageService
87+
->getMessageQueueByIdentifier()
88+
->enqueue(new FlashMessage($body, $title, $severity, true));
89+
}
90+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use DbStore\Example\Controller\ExtbaseFileController;
6+
use DbStore\Example\Controller\NativeFileController;
7+
8+
return [
9+
'example_extbase' => [
10+
'parent' => 'file',
11+
'access' => 'user',
12+
'path' => '/module/file/example-extbase',
13+
'iconIdentifier' => 'module-filelist',
14+
'labels' => 'LLL:EXT:example/Resources/Private/Language/locallang_mod_extbase.xlf',
15+
'extensionName' => 'Example',
16+
'inheritNavigationComponentFromMainModule' => false,
17+
'navigationComponent' => false,
18+
'controllerActions' => [
19+
ExtbaseFileController::class => ['list', 'upload', 'download', 'delete'],
20+
],
21+
],
22+
'example_native' => [
23+
'parent' => 'file',
24+
'access' => 'user',
25+
'path' => '/module/file/example-native',
26+
'iconIdentifier' => 'module-filelist',
27+
'labels' => 'LLL:EXT:example/Resources/Private/Language/locallang_mod_native.xlf',
28+
'navigationComponent' => false,
29+
'inheritNavigationComponentFromMainModule' => false,
30+
'routes' => [
31+
'_default' => [
32+
'target' => NativeFileController::class . '::listAction',
33+
],
34+
'upload' => [
35+
'target' => NativeFileController::class . '::uploadAction',
36+
'methods' => ['POST'],
37+
],
38+
'download' => [
39+
'target' => NativeFileController::class . '::downloadAction',
40+
],
41+
'delete' => [
42+
'target' => NativeFileController::class . '::deleteAction',
43+
'methods' => ['POST'],
44+
],
45+
],
46+
],
47+
];
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
services:
2+
_defaults:
3+
autowire: true
4+
autoconfigure: true
5+
public: false
6+
7+
DbStore\Example\:
8+
resource: '../Classes/*'
9+
10+
DbStore\Example\Controller\NativeFileController:
11+
public: true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
3+
<file source-language="en" datatype="plaintext" original="messages" date="2025-01-01T00:00:00Z" product-name="example">
4+
<body>
5+
<trans-unit id="mlang_tabs_tab" resname="mlang_tabs_tab">
6+
<source>DB Extbase</source>
7+
</trans-unit>
8+
<trans-unit id="mlang_labels_tabdescr" resname="mlang_labels_tabdescr">
9+
<source>Extbase-based DB file storage demo</source>
10+
</trans-unit>
11+
</body>
12+
</file>
13+
</xliff>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
3+
<file source-language="en" datatype="plaintext" original="messages" date="2025-01-01T00:00:00Z" product-name="example">
4+
<body>
5+
<trans-unit id="mlang_tabs_tab" resname="mlang_tabs_tab">
6+
<source>DB non-Extbase</source>
7+
</trans-unit>
8+
<trans-unit id="mlang_labels_tabdescr" resname="mlang_labels_tabdescr">
9+
<source>PSR-7 based DB file storage demo</source>
10+
</trans-unit>
11+
</body>
12+
</file>
13+
</xliff>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<html
2+
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
3+
data-namespace-typo3-fluid="true"
4+
>
5+
6+
<f:layout name="Module" />
7+
8+
<f:section name="Content">
9+
<h2>DB File Storage — Extbase Module</h2>
10+
11+
<h3>Upload a file</h3>
12+
<f:form action="upload" method="post" enctype="multipart/form-data">
13+
<div class="mb-3">
14+
<input type="file" name="file" class="form-control" required="required" />
15+
</div>
16+
<f:form.submit value="Upload" class="btn btn-primary" />
17+
</f:form>
18+
19+
<h3 class="mt-4">Stored files</h3>
20+
<f:if condition="{files -> f:count()} > 0">
21+
<f:then>
22+
<div class="table-fit">
23+
<table class="table table-striped table-hover">
24+
<thead>
25+
<tr>
26+
<th>UID</th>
27+
<th>Filename</th>
28+
<th>MIME type</th>
29+
<th>Size</th>
30+
<th>Actions</th>
31+
</tr>
32+
</thead>
33+
<tbody>
34+
<f:for each="{files}" as="file">
35+
<tr>
36+
<td>{file.uid}</td>
37+
<td>{file.filename}</td>
38+
<td>{file.mimeType}</td>
39+
<td>{file.size}</td>
40+
<td>
41+
<f:link.action action="download" arguments="{file: file.uid}" class="btn btn-sm btn-default">
42+
Download
43+
</f:link.action>
44+
<f:form action="delete" arguments="{file: file.uid}" method="post" style="display:inline">
45+
<f:form.submit value="Delete" class="btn btn-sm btn-danger" />
46+
</f:form>
47+
</td>
48+
</tr>
49+
</f:for>
50+
</tbody>
51+
</table>
52+
</div>
53+
</f:then>
54+
<f:else>
55+
<f:be.infobox title="No files" state="{f:constant(name: 'TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper::STATE_INFO')}">
56+
No files stored yet. Upload one above.
57+
</f:be.infobox>
58+
</f:else>
59+
</f:if>
60+
</f:section>
61+
62+
</html>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<html
2+
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
3+
xmlns:be="http://typo3.org/ns/TYPO3/CMS/Backend/ViewHelpers"
4+
data-namespace-typo3-fluid="true"
5+
>
6+
7+
<f:layout name="Module" />
8+
9+
<f:section name="Content">
10+
<h2>DB File Storage — Native (PSR-7) Module</h2>
11+
12+
<h3>Upload a file</h3>
13+
<form action="{uploadUri}" method="post" enctype="multipart/form-data">
14+
<div class="mb-3">
15+
<input type="file" name="file" class="form-control" required="required" />
16+
</div>
17+
<button type="submit" class="btn btn-primary">Upload</button>
18+
</form>
19+
20+
<h3 class="mt-4">Stored files</h3>
21+
<f:if condition="{files}">
22+
<f:then>
23+
<div class="table-fit">
24+
<table class="table table-striped table-hover">
25+
<thead>
26+
<tr>
27+
<th>UID</th>
28+
<th>Filename</th>
29+
<th>MIME type</th>
30+
<th>Size</th>
31+
<th>Created</th>
32+
<th>Actions</th>
33+
</tr>
34+
</thead>
35+
<tbody>
36+
<f:for each="{files}" as="row">
37+
<tr>
38+
<td>{row.uid}</td>
39+
<td>{row.filename}</td>
40+
<td>{row.mime_type}</td>
41+
<td>{row.size}</td>
42+
<td>{row.crdate -> f:format.date(format: 'Y-m-d H:i')}</td>
43+
<td>
44+
<a href="{be:moduleLink(route:'example_native.download', arguments:{file: row.uid})}" class="btn btn-sm btn-default">
45+
Download
46+
</a>
47+
<form action="{deleteUri}" method="post" style="display:inline">
48+
<input type="hidden" name="file" value="{row.uid}" />
49+
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
50+
</form>
51+
</td>
52+
</tr>
53+
</f:for>
54+
</tbody>
55+
</table>
56+
</div>
57+
</f:then>
58+
<f:else>
59+
<f:be.infobox title="No files" state="{f:constant(name: 'TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper::STATE_INFO')}">
60+
No files stored yet. Upload one above.
61+
</f:be.infobox>
62+
</f:else>
63+
</f:if>
64+
</f:section>
65+
66+
</html>

0 commit comments

Comments
 (0)