TYPO3: Uploads in backend module - typo3

I have to implement a "Book" management in the Backend. Each book has a PDF preview, a title, description and so on...
The BE user should be able to upload a PDF and set a title, description etc. through a Backend module.
The created Book should be selectable in a plugin (or content element ?) so it can be display in the frontend.
Also, the uploaded PDF should only be downloadable by a certain group of FE users.
I don't know how to handle the upload part in the backend. I didn't find much info online beside this upload example: https://github.com/helhum/upload_example it seems quite complex and I'm not sure if it's the best solution for me.
What would be the best way to proceed for my task ?

Use File Abstraction Layer (FAL). You don't need the example in backend, but it's great for frontend upload.
Domain/Model/Book.php
...
/**
* File (file references)
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference>
* #lazy
*/
protected $files = NULL;
/**
* Construct
*
*
*/
public function __construct() {
//Do not remove the next line: It would break the functionality
$this->initStorageObjects();
}
/**
* Initializes all ObjectStorage properties
* Do not modify this method!
* It will be rewritten on each save in the extension builder
* You may modify the constructor of this class instead
*
* #return void
*/
protected function initStorageObjects() {
$this->files = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Set files (file references)
*
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $files
* #return void
*/
public function setFiles(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $files) {
$this->files = $files;
}
/**
* Get files (file references)
*
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\FileReference> $files
*/
public function getFiles() {
return $this->files;
}
...
TCA/tx_yourextension_domain_model_book.php
...
'files' => [
'label' => 'LLL:EXT:werkhandkunst/Resources/Private/Language/locallang_db.xlf:file',
'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
'files', ['
maxitems' => 25,
],
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
),
],
...
ext_tables.sql
CREATE TABLE tx_yourextension_domain_model_book (
...
files int(11) unsigned DEFAULT '0' NOT NULL,
...
)

Related

TYPO3 FileReference does not save the tablename on the DB. Uploading file from frontend on TYPO3

In my custom extension on TYPO3 10.4 I'm trying to upload a file (image) from the frontend. The file gets uploaded just fine, the rows on the DB seemed to be inserted just fine but there is some data missing.
This is my form:
<f:form method="post" action="create" name="blackboard"
object="{blackboard}" enctype="multipart/form-data">
<f:form.textfield placeholder="Titel*" required="true" property="title"></f:form.textfield>
<f:form.upload property="image" name="image" />
<f:form.submit class="btn btn-primary" value="{f:translate(key: 'submit', default: 'Absenden')}"></f:form.submit>
</f:form>
The model:
/**
* image
*
* #var \TYPO3\CMS\Extbase\Domain\Model\FileReference
* #TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
*/
protected $image = null;
/**
* Returns the image
*
* #return \TYPO3\CMS\Extbase\Domain\Model\FileReference $image
*/
public function getImage()
{
return $this->image;
}
/**
* Sets the image
*
* #param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image
* #return void
*/
public function setImage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $image)
{
$this->image = $image;
}
The controller:
/**
* action create
* #param Blackboard
*/
public function createAction(Blackboard $blackboard)
{
$blackboard->setPid($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['hebo_blackboards']['BlackboardsStoragePId']);
$blackboard->setUser($GLOBALS['TSFE']->fe_user->user['uid']);
$this->blackboardRepository->add($blackboard);
}
Surprisingly, just that easy, this seems to work just fine. I get the image uploaded on the server, the correct UID of that sys_file_reference on my custom table, the sys_file_reference gets the correct UID of that sys_file... but as you can see in the pic that follows there are a few data missing, "tablename" and "table_local" and as soon as I add that data manually the relationships work (the first rows, where this data is not missing is from rows created from the backend, working fine)
My question is, why? What do I do to fix that?
The problem is that extbase doesn't know those values, therefore you need to state those in the TCA. Given this example
'extra_files' => [
'label' => 'A file',
'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
'extra_files',
[
'foreign_match_fields' => [
'tablenames' => 'tx_yourtable_domain_model_fo',
'table_local' => 'sys_file'
]
],
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
),
],
The foreign_match_fields part is the relevant one which is not needed if you don't handle file uploads in the Frontend.

Symfony2 mapping file not found

I have a little form, with just a file field to upload a document. I'm using Symfony2.6 with mongodb annotation. I get a "No mapping found for field 'file'" exception.
My document class :
namespace My\Bundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #MongoDB\Document
*/
class ImportFile
{
[...]
/**
* #Assert\File(maxSize = "5M", mimeTypes = {"image/jpeg", "image/gif", "image/png"})
*/
protected $file;
[...]
/**
* #return $file
*/
public function getFile()
{
return $this->file;
}
/**
* #param $file
*/
public function setFile($file)
{
$this->file = $file;
}
(the namespace is correct, i changed it for the post)
Here is my controller with my form builder :
$importFile = new ImportFile();
$form = $this->createFormBuilder($importFile)
->add('file')
->getForm();
The file field should be automatically detected (as it mentioned here http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html) but it doesn't work. If i add:
$form = $this->createFormBuilder($importFile)
->add('file','file')
->getForm();
it works, but i should'nt have to add the file type as it should be automatically detected. what am i doing wrong ?
I think the problem is in your annotation. You added just an assert but no field definition:
/**
* #Field(type="file")
* #Assert\File(maxSize = "5M", mimeTypes = {"image/jpeg", "image/gif", "image/png"})
*/
I hope it helps.
As you mention here:
I don't want this field to be mapped in the database
You should use mapped property at form field definition:
mapped => false
So, this should resolved your problem ( but remeber that after submit, value from file property will not be mapped with your odm Document ):
$form = $this->createFormBuilder($importFile)
->add('file', 'file', array(
'mapped' => false
))
->getForm();
More here: http://symfony.com/doc/current/reference/forms/types/text.html#mapped

Typo3 Extbase AJAX without page typenum

Is there any way to create AJAX calls in Extbase extension without using of page typeNum?
Edit:
Helmut Hummel, a member of the TYPO3 CMS team, measured that using EID with Extbase is slower than using the typeNum approach. But since the typeNum approach is cumbersome to configure, there is a third way developed by him.
The extension typoscript_rendering provides a way to call Extbase actions directly without additional configuration. It contains a ViewHelper that generates such links and can be used like this in a Fluid template:
{namespace h=Helhum\TyposcriptRendering\ViewHelpers}
<script>
var getParticipationsUri = '<h:uri.ajaxAction controller="Participation" action="listByCompetition" arguments="{competition:competition}" />';
</script>
This generates an URI that calls the action "listByCompetition" of my "ParticipationController". You can pass arguments normally.
The only downside is that for security reasons, the extension uses the cHash to validate the request arguments. The cHash is submitted by GET but you cannot pass additional arguments by GET at the same time because it would invalidate the cHash. So if you want to pass form data in such a request, you need to mix GET (for a valid AJAX call) and POST (for submitting user data):
<script>
var createAddressUri = '<h:uri.ajaxAction controller="Address" action="create" />';
$body.on('submit', '#myForm', function(e) {
e.preventDefault();
emailAddress = $('#myForm').find('#email');
if (typeof(emailAddress) === 'string') {
$.ajax({
url: createAddressUri,
type: 'POST',
data: { 'tx_myext_pluginname[address][email]' : emailAddress},
success: function() {
// things to do on success
}
})
}
});
</script>
(Of course this is only a very basic example. You might post whole models etc.)
The EID way:
Yes, you can use the EID (Extension ID) mechanism for that. There is no official statement which way (pageType or eID) should be used for Extbase AJAX calls and it seems to be just a matter of taste.
There is a nice tutorial that can be found here and I copy the source code in here:
<?php
/** *************************************************************
*
* Extbase Dispatcher for Ajax Calls TYPO3 6.1 namespaces
*
* IMPORTANT Use this script only in Extensions with namespaces
*
* Klaus Heuer <klaus.heuer#t3-developer.com>
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
* ************************************************************* */
/** ************************************************************
* Usage of this script:
*
* - Copy this script in your Extension Dir in the Folder Classes
* - Set the Vendor and Extension Name in Line 82 + 83
* - Include the next line in the ext_localconf.php, change the ext name!
* - $TYPO3_CONF_VARS['FE']['eID_include']['ajaxDispatcher'] = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('myExtension').'Classes/EidDispatcher.php';
*
* Use for Ajax Calls in your jQuery Code:
*
* $('.jqAjax').click(function(e) {
* var uid = $(this).find('.uid').html();
* var storagePid = '11';
*
* $.ajax({
* async: 'true',
* url: 'index.php',
* type: 'POST',
*
* data: {
* eID: "ajaxDispatcher",
* request: {
* pluginName: 'patsystem',
* controller: 'Todo',
* action: 'findTodoByAjax',
* arguments: {
* 'uid': uid,
* 'storagePid': storagePid
* }
* }
* },
* dataType: "json",
*
* success: function(result) {
* console.log(result);
* },
* error: function(error) {
* console.log(error);
* }
* });
*************************************************************** */
/**
* Gets the Ajax Call Parameters
*/
$ajax = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('request');
/**
* Set Vendor and Extension Name
*
* Vendor Name like your Vendor Name in namespaces
* ExtensionName in upperCamelCase
*/
$ajax['vendor'] = 'T3Developer';
$ajax['extensionName'] = 'ProjectsAndTasks';
/**
* #var $TSFE \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
*/
$TSFE = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController', $TYPO3_CONF_VARS, 0, 0);
\TYPO3\CMS\Frontend\Utility\EidUtility::initLanguage();
// Get FE User Information
$TSFE->initFEuser();
// Important: no Cache for Ajax stuff
$TSFE->set_no_cache();
//$TSFE->checkAlternativCoreMethods();
$TSFE->checkAlternativeIdMethods();
$TSFE->determineId();
$TSFE->initTemplate();
$TSFE->getConfigArray();
\TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadConfigurationAndInitialize();
$TSFE->cObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer');
$TSFE->settingLanguage();
$TSFE->settingLocale();
/**
* Initialize Database
*/
\TYPO3\CMS\Frontend\Utility\EidUtility::connectDB();
/**
* #var $objectManager \TYPO3\CMS\Extbase\Object\ObjectManager
*/
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
/**
* Initialize Extbase bootstap
*/
$bootstrapConf['extensionName'] = $ajax['extensionName'];
$bootstrapConf['pluginName'] = $ajax['pluginName'];
$bootstrap = new TYPO3\CMS\Extbase\Core\Bootstrap();
$bootstrap->initialize($bootstrapConf);
$bootstrap->cObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('tslib_cObj');
/**
* Build the request
*/
$request = $objectManager->get('TYPO3\CMS\Extbase\Mvc\Request');
$request->setControllerVendorName($ajax['vendor']);
$request->setcontrollerExtensionName($ajax['extensionName']);
$request->setPluginName($ajax['pluginName']);
$request->setControllerName($ajax['controller']);
$request->setControllerActionName($ajax['action']);
$request->setArguments($ajax['arguments']);
$response = $objectManager->create('TYPO3\CMS\Extbase\Mvc\ResponseInterface');
$dispatcher = $objectManager->get('TYPO3\CMS\Extbase\Mvc\Dispatcher');
$dispatcher->dispatch($request, $response);
echo $response->getContent();
//die();
?>
Have a look at the "usage of this script" section that explains how to register the eID. The script works with TYPO3 6.1 and higher.
For TYPO3 6.2 change the following line:
\TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadConfigurationAndInitialize();
to
\TYPO3\CMS\Core\Core\Bootstrap::getInstance()
For Test Extension.
Include EID in the ext_localconf.php file
## Ajax configuration
$TYPO3_CONF_VARS['FE']['eID_include']['Test'] = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('test').'Classes/Ajax/EidDispatcher.php';
Create directory in classes – Classes/Ajax/EidDispatcher.php
namespace TYPO3\Test\Ajax;
class EidDispatcher {
/**
* #var \array
*/
protected $configuration;
/**
* #var \array
*/
protected $bootstrap;
/**
* The main Method
*
* #return \string
*/
public function run() {
return $this->bootstrap->run( '', $this->configuration );
}
/**
* Initialize Extbase
*
* #param \array $TYPO3_CONF_VARS
*/
public function __construct($TYPO3_CONF_VARS) {
$ajaxRequest = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('tx_Test_addhours');
// create bootstrap
$this->bootstrap = new \TYPO3\CMS\Extbase\Core\Bootstrap();
// get User
$feUserObj = \TYPO3\CMS\Frontend\Utility\EidUtility::initFeUser();
// set PID
$pid = (\TYPO3\CMS\Core\Utility\GeneralUtility::_GET( 'id' )) ? \TYPO3\CMS\Core\Utility\GeneralUtility::_GET('id') : 1;
// Create and init Frontend
$GLOBALS['TSFE'] = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( 'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController', $TYPO3_CONF_VARS, $pid, 0, TRUE );
$GLOBALS['TSFE']->connectToDB();
$GLOBALS['TSFE']->fe_user = $feUserObj;
$GLOBALS['TSFE']->id = $pid;
$GLOBALS['TSFE']->determineId();
$GLOBALS['TSFE']->getCompressedTCarray(); //Comment this line when used for TYPO3 7.6.0 on wards
$GLOBALS['TSFE']->initTemplate();
$GLOBALS['TSFE']->getConfigArray();
$GLOBALS['TSFE']->includeTCA(); //Comment this line when used for TYPO3 7.6.0 on wards
// Get Plugins TypoScript
$TypoScriptService = new \TYPO3\CMS\Extbase\Service\TypoScriptService();
$pluginConfiguration = $TypoScriptService->convertTypoScriptArrayToPlainArray($GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_Test.']);
// Set configuration to call the plugin
$this->configuration = array (
'pluginName' => $ajaxRequest['pluginName'],
'vendorName' => 'TYPO3',
'extensionName' => 'Test',
'controller' => $ajaxRequest['controller'],
'action' => $ajaxRequest['action'],
'mvc' => array (
'requestHandlers' => array (
'TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler' => 'TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler'
)
),
'settings' => $pluginConfiguration['settings'],
'persistence' => array (
'storagePid' => $pluginConfiguration['persistence']['storagePid']
)
);
}
}
global $TYPO3_CONF_VARS;
// make instance of bootstrap and run
$eid = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( 'TYPO3\Test\Ajax\EidDispatcher', $TYPO3_CONF_VARS );
echo $eid->run();
Call From Script
$.ajax({
async: 'true',
url: 'index.php',
type: 'GET',
data: {
eID: "Test",
tx_ExtName_PluginName: {
pluginName: 'Plugin_Name',
controller: 'Controller_Name',
action: 'Action_Name',
}
},
success:function(data){
// code
}
});
I had to change the first 0 of the makeInstance to the id of the page for it to work.
$id = \TYPO3\CMS\Core\Utility\GeneralUtility::_GP('id');
$TSFE = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController', $TYPO3_CONF_VARS, $id, 0);
I used a quick and probably dirty way without typeNum.
I used jQuery ajax call the common way.
My target action ended with the following.
$headers = DivUtilities::createHeader();
foreach ($headers as $header => $data) {
$this->response->setHeader($header, $data);
}
$this->response->sendHeaders();
echo $this->view->render();
exit;
The createHeader Method
/**
*
* #param string $type
* #return array
*/
public static function createHeader($type = 'html')
{
switch ($type) {
case 'txt':
$cType = 'text/plain';
break;
case 'html':
$cType = 'text/html';
break;
case 'json':
$cType = 'application/json';
break;
case 'pdf':
$cType = 'application/pdf';
break;
}
$headers = array(
'Pragma' => 'public',
'Expires' => 0,
'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
'Cache-Control' => 'public',
'Content-Type' => $cType
);
return $headers;
}
The output is the template of the called action. This can be html, json or whatever u need.

Persisting language fields

In my extbase model, I created getters and setters for the fields t3_origuid, sys_language_uid, and l10n_parent.
When setting these fields and persisting, only the l10n_parent field is updated in the database. Is it possible to change the other fields, without manually querying the database of course.
To set sys_language_uid through extbase you need to use the internal API AbstractDomainObject::_setProperty() with $entity->_setProperty('_languageUid', $languageUid) where $languageUid would be the id of your langage record. You will find a more complete example on forge: https://forge.typo3.org/issues/61722.
You could also use this small service I've written to translate any domain object easily:
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Provides services to translate domain objects
*/
class TranslationService implements SingletonInterface {
/**
* #var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
* #inject
*/
protected $dataMapper;
/**
* Translates a domain object
*
* #param DomainObjectInterface $origin
* #param DomainObjectInterface $translation
* #param int $language
* #throws \Exception
* #return void
*/
public function translate(DomainObjectInterface $origin, DomainObjectInterface $translation, $language) {
if (get_class($origin) !== get_class($translation)) {
throw new \Exception('Origin and translation must be the same type.', 1432499926);
}
$dataMap = $this->dataMapper->getDataMap(get_class($origin));
if (!$dataMap->getTranslationOriginColumnName()) {
throw new \Exception('The type is not translatable.', 1432500079);
}
if ($origin === $translation) {
throw new \Exception('Origin can\'t be translation of its own.', 1432502696);
}
$propertyName = GeneralUtility::underscoredToLowerCamelCase($dataMap->getTranslationOriginColumnName());
if ($translation->_setProperty($propertyName, $origin) === FALSE) {
$columnMap = $dataMap->getColumnMap($propertyName);
$columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
$columnMap->setType($dataMap->getClassName());
$columnMap->setChildTableName($dataMap->getTableName());
$translation->{$propertyName} = $origin;
}
$translation->_setProperty('_languageUid', $language);
}
}
You have define your fields in the TCA.
'sys_language_uid' => array(
'config' => array(
'type' => 'passthrough'
)
)
And the same for the other fields...
there are some tutorials in the web for mapping tables of typo3 with extbase.
this tutorial is in german, but the code speaks for itself.

Zend Controller Action: _redirect() vs getHelper('Redirector')->gotoUrl()

I've read that $this->getHelper('[helper_name]') is preferable to $this->_helper->[helper_name]. What I haven't been able to find any documentation of is which of these is better/preferred: $this->_redirect($url) or $this->getHelper('Redirector')->gotoUrl($url).
Use whatever one suits you, they do exactly the same thing:
/**
* Redirect to another URL
*
* Proxies to {#link Zend_Controller_Action_Helper_Redirector::gotoUrl()}.
*
* #param string $url
* #param array $options Options to be used when redirecting
* #return void
*/
protected function _redirect($url, array $options = array())
{
$this->_helper->redirector->gotoUrl($url, $options);
}