Skip to content

Commit

Permalink
[BACKPORT] [TASK] Backport Extbase FileUpload for v12 (#4808) (#4825)
Browse files Browse the repository at this point in the history
* [BACKPORT] [TASK] Backport Extbase FileUpload for v12 (#4808)

This backports PR #4808 for TYPO3 v12 and hints at the v13
changes only.

* [TASK] CGL

* [TASK] Use a "hint" instead of "versionchanged"
  • Loading branch information
garvinhicking authored Oct 14, 2024
1 parent 7c8f77a commit 9ed8d84
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 2 deletions.
4 changes: 2 additions & 2 deletions Documentation/ApiOverview/Fal/UsingFal/ExamplesFileFolder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ The simplest solution is to create a database entry into table
using the :ref:`database connection <database-connection>` class or the
:ref:`query builder <database-query-builder>` provided by TYPO3.

A cleaner solution using :ref:`Extbase <extbase>` requires far more work. An
example can be found here: https://github.com/helhum/upload_example
See :ref:`Extbase file upload <extbase_fileupload>` for details on how
to achieve this using :ref:`Extbase <extbase>`.


.. _fal-using-fal-examples-file-folder-get-references:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
.. include:: /Includes.rst.txt

.. index:: Extbase, FileUpload

.. _extbase_fileupload:

===========
File upload
===========

Implementing file uploads / attachments to Extbase domain models
has always been a bit of a challenge.

While it is straight-forward to access an existing file reference in a domain model,
writing new files to the :ref:`FAL (File Access Layer) <t3coreapi:using-fal>`
takes more effort.

.. _extbase_fileupload_accessing:

Accessing a file reference in an Extbase domain model
-----------------------------------------------------

You need two components for the structural information: the Domain
Model definition and the TCA entry.

The domain model definition:

.. literalinclude:: _FileUpload/_Blog.php
:caption: EXT:my_extension/Classes/Domain/Model/Blog.php

and the TCA definition:

.. literalinclude:: _FileUpload/_tx_myextension_domain_model_blog.php
:caption: EXT:my_extension/Configuration/TCA/tx_myextension_domain_model_blog.php

Once this is set up, you can create/edit records through the TYPO3
backend (for example via :guilabel:`Web > List`), attach a single or
multiple files in it. Then using a normal
controller and Fluid template, you can display an image.

The relevant Extbase controller part:

.. literalinclude:: _FileUpload/_BlogController.php
:caption: EXT:my_extension/Classes/Controller/BlogController.php

and the corresponding Fluid template:

.. literalinclude:: _FileUpload/_Show.html
:caption: EXT:my_extension/Resources/Private/Templates/Blog/Show.html

On the PHP side within controllers, you can use the usual
:php:`$blogItem->getSingleFile()` and :php:`$blogItem->getMultipleFiles()`
Extbase getters to retrieve the FileReference object.

.. _extbase_fileupload_writing:

Writing FileReference entries
-----------------------------

.. _extbase_fileupload_writing-manual:

Manual handling
...............

.. hint::
With TYPO3 v13.3
:ref:`Feature: #103511 - Introduce Extbase file upload and deletion handling <changelog:feature-103511-1711894330>`
was introduced and allows a simplified file upload handling. See
:ref:`<t3coreapi/13:extbase_fileupload_writing-manual>` for details.

With TYPO3 versions 12.4 and below, attaching files to an Extbase domain model
is possible by either:

* Manually evaluating the :php:`$_FILES` data, process and validate the data,
use raw QueryBuilder write actions on :sql:`sys_file` and :sql:`sys_file_reference`
to persist the files quickly, or use at least some API methods:

.. literalinclude:: _FileUpload/_ApiUpload.php
:caption: EXT:my_extension/Classes/Controller/BlogController.php, excerpt

Instead of raw access to :php:`$_FILES`, starting with TYPO3 v12 the recommendation
is to utilize the :ref:`UploadedFile objects instead of $_FILES <changelog:breaking-97214>`.
In that case, validators can be used for custom UploadedFile objects to specify restrictions
on file types, file sizes and image dimensions.

* Using (or better: adapting) a more complex implementation by using Extbase TypeConverters,
as provided by `Helmut Hummel's EXT:upload_example <https://github.com/helhum/upload_example>`__.
This extension is no longer maintained and will not work without larger adaptation for
TYPO3 v12 compatibility.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Extbase reference
TypoScriptConfiguration
Annotations
Validation
FileUpload
Caching
Localization
UriArguments
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Model\Blog;
use MyVendor\MyExtension\Domain\Repository\BlogRepository;
use TYPO3\CMS\Core\Resource\DuplicationBehavior;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
public function __construct(
protected ResourceFactory $resourceFactory,
protected BlogRepository $blogRepository,
) {}

public function attachFileUpload(Blog $blog): void
{
$falIdentifier = '1:/your_storage';
$yourFile = '/path/to/uploaded/file.jpg';

// Attach the file to the wanted storage
$falFolder = $this->resourceFactory->retrieveFileOrFolderObject($falIdentifier);
$fileObject = $falFolder->addFile(
$yourFile,
basename($yourFile),
DuplicationBehavior::REPLACE,
);

// Initialize a new storage object
$newObject = [
'uid_local' => $fileObject->getUid(),
'uid_foreign' => StringUtility::getUniqueId('NEW'),
'uid' => StringUtility::getUniqueId('NEW'),
'crop' => null,
];

// Create the FileReference Object
$fileReference = $this->resourceFactory->createFileReferenceObject($newObject);

// Port the FileReference Object to an Extbase FileReference
$fileReferenceObject = GeneralUtility::makeInstance(FileReference::class);
$fileReferenceObject->setOriginalResource($fileReference);

// Persist the created file reference object to our Blog model
$blog->setSingleFile($fileReferenceObject);
$this->blogRepository->update($blog);

// Note: For multiple files, a wrapping ObjectStorage would be needed
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model;

use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;

class Blog extends AbstractEntity
{
// A single file
protected ?FileReference $singleFile = null;

/**
* A collection of files.
* @var ObjectStorage<FileReference>
*/
protected ObjectStorage $multipleFiles;

// When using ObjectStorages, it is vital to initialize these.
public function __construct()
{
$this->multipleFiles = new ObjectStorage();
}

/**
* Called again with initialize object, as fetching an entity from the DB does not use the constructor
*/
public function initializeObject(): void
{
$this->multipleFiles = $this->multipleFiles ?? new ObjectStorage();
}

// Typical getters
public function getSingleFile(): ?FileReference
{
return $this->singleFile;
}

/**
* @return ObjectStorage|FileReference[]
*/
public function getMultipleFiles(): ObjectStorage
{
return $this->multipleFiles;
}

// For later examples, the setters:
public function setSingleFile(?FileReference $singleFile): void
{
$this->singleFile = $singleFile;
}

public function setMultipleFiles(ObjectStorage $files): void
{
$this->multipleFiles = $files;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Model\Blog;
use MyVendor\MyExtension\Domain\Repository\BlogRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
public function __construct(protected readonly BlogRepository $blogRepository)
{
// Note: The repository is a standard extbase repository, nothing specific
// to this example.
}

public function showAction(Blog $blog): ResponseInterface
{
$this->view->assign('blog', $blog);

return $this->htmlResponse();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
data-namespace-typo3-fluid="true">

<f:layout name="Default" />

<f:section name="main">
<p>Single image:</p>
<f:image image="{blog.singleFile.originalFile}" />

<p>Multiple images:</p>
<f:for each="{blog.multipleFiles}" as="image">
<f:image image="{image.originalFile}" />
</f:for>

<p>Access first image of multiple images:</p>
<f:image image="{blog.multipleFiles[0].originalFile}" />
</f:section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

return [
'ctrl' => [
// .. usual TCA fields
],
'columns' => [
// ... usual TCA columns
'singleFile' => [
'exclude' => true,
'label' => 'Single file',
'config' => [
'type' => 'file',
'maxitems' => 1,
'allowed' => 'common-image-types',
],
],
'multipleFiles' => [
'exclude' => true,
'label' => 'Multiple files',
'config' => [
'type' => 'file',
'allowed' => 'common-image-types',
],
],
],
];

0 comments on commit 9ed8d84

Please sign in to comment.