Delete file when deleting sys_file_reference - typo3

I am writing an extension which allows to upload files in the frontend and backend of a TYPO3 instance. The upload works in both views but if the admin wants to delete an upload in the backend in list view, the "physical" file, which is located on the harddisk of the webserver, will not be deleted, only the sys_file_reference record.
Is there a possibility to tell the tca that in case of a deletion of the upload record the associated file should also be deleted? I've also tried to implement a slot with the following code but nothing happens:
ext_localconf.php:
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher')->connect(
'TYPO3\CMS\Extbase\Persistence\Generic\Backend',
'afterRemoveObject',
'Kmi\feupload\Slots\MyAfterRemoveObjectSlot',
'myAfterRemoveObjectMethod'
);
Classes/Slots/MyAfterRemoveObjectSlot.php:
namespace Kmi\feupload\Slots;
class MyAfterRemoveObjectSlot {
public function myAfterRemoveObjectMethod($object) {
// do something
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($object);
}
}
Has anyone an idea how to solve this? There will be many uploads and if the admin deletes one, the associated file should also be deleted...
Thank you in advance for your help :)

Unfortunately I don't have time to create a complete, tested answer ATM but I'm putting together the steps needed and hope that you can work a solution and complete my answer then.
Every manipulation done through a TCEFORM is saved with the DataHandler (formerly called TCEmain). The DataHandler has numerous hooks. I assume that your model "Upload" has a property file which is of type (or extends) \TYPO3\CMS\Extbase\Domain\Model\FileReference.
File references in TCEFORM are added as IRRE elements. So when you remove the file reference and save the Upload object, the following data is (amogst others) sent to DataHandler:
cmd[sys_file_reference][15011][delete]=1
This means that the file reference with uid 15011 must be deleted. I suggest to implement the processCmdmap_deleteAction hook for this.
So you must also check the datamap to find out if the command was executed through a manipulation of an "Upload" record.
ext_localconf.php:
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['your_extension'] = 'My\\Extension\\Hook\\DataHandler';
EXT:your_extension/Classes/Hook/DataHandler.php
This code is untested!
<?php
namespace My\Extension\Hook
class DataHandler {
/**
* #param string $table
* #param int $id
* #param array $recordToDelete
* #param $parentObject \TYPO3\CMS\Core\DataHandling\DataHandler
*/
public function processCmdmap_deleteAction($table, $id, $recordToDelete, $parentObject) {
if (array_key_exists('tx_myext_domain_model_upload', $parentObject->datamap)) {
// Parent record of record to delete is of type "tx_myext_domain_model_upload"
if ($table === 'sys_file_reference' && is_integer($id)) {
// A file reference was requested to delete
// Get an instance of the ResourceFactory
/** #var $resourceFactory \TYPO3\CMS\Core\Resource\ResourceFactory */
$resourceFactory = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory');
// We get the FileReference object for the given id
$fileReferenceObject = $resourceFactory->getFileReferenceObject($id);
// Delete the original file of the file reference
$fileWasDeleted = $fileReferenceObject->getOriginalFile()->delete();
// #TODO throw a warning if $fileWasDeleted is false
}
}
}
}
I commented the code so you know which checks are necessary for what.
Don't forget to clear the system cache after defining the hook in ext_localconf.php.

// delete video or image from sys_file table and sys_file_reference
// table (here videourl - sys_file_reference fieldname)
$fileRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\FileRepository::class);
$fileObjects = $fileRepository->findByRelation('tablename', 'videourl', $id);
foreach ($fileObjects as $fileKey => $fileValue) {
$delete= $fileValue->getOriginalFile()->delete();
}

Related

Fileupload TYPO3 getting null on findAll()

Hi have a backend module extension to upload files. Im using helhum fileupload for reference. File upload is successful. But the file filed of table updates the uid of sys_file_reference instead of no of files. Why it happens?
<f:form.upload property="file" />
my reference is this Where can I set the table name and no_files in my table and sys_file reference
The property "file" I assume is an 1:1 relation which is why the UID of the file reference is what gets written in to the field.
Had the property been a M:N or 1:N table you would see the number of files, as you expect - and Extbase would need to know you want an ObjectStorage containing FileReference objects on your property.
Regarding the subject, if your Repository returns NULL when you do findAll, this is almost always because of storage page restrictions. To overcome it, override either createQuery and manipulate QuerySettings on the query before it gets returned, setting respectStoragePageUids(false).
I got the solution for my problem. my model was
/**
* Sets the file
*
* #param \TYPO3\CMS\Extbase\Domain\Model\FileReference $file
* #return void
*/
public function setFile(\TYPO3\CMS\Extbase\Domain\Model\FileReference $file = NULL)
{
$this->file = $file;
}
I removed the type from argument list .Now its working fine.My updated code is below
/**
* Sets the file
*
* #param \TYPO3\CMS\Extbase\Domain\Model\FileReference $file
* #return void
*/
public function setFile($file = NULL)
{
$this->file = $file;
}

Edit hidden records in frontend

I am building an extension to edit tt_news records in frontend.
I set setIgnoreEnableFields(TRUE) in my repository.
But if I try edit a hidden record, I get the error
Object with identity „12345" not found.
Any solution for this?
I am guessing you are using an action like
/**
* Single view of a news record
*
* #param \Vendor\Ext\Domain\Model\News $news news item
*/
public function detailAction(\Vendor\Ext\Domain\Model\News $news = null)
Your problem is, that the Repository is not used to fetch the record.
As a solution, remove the argument, clear the caches and try something like that
/**
* Single view of a news record
*
* #param \Vendor\Ext\Domain\Model\News $news news item
*/
public function detailAction() {
$id = (int)$this->request->getArgument('news');
if ($id) {
$news = $this->newsRepository->findByUid($previewNewsId);
}
}
Now you can manipulate the QuerySettings and use those.
The problem is the PropertyMapping. If extbase try to assign an uid (12345) to an Domain Object (tt_news) the "setEnableFields" setting of the Repository isn't respected. So you must fetch the object by yourself.
the simple solution is to do this in an initialize*Action for each "show" action. For editAction an example:
public function initializeEditAction() {
if ($this->request->hasArgument('news')) {
$newsUid = $this->request->getArgument('news');
if (!$this->newsRepository->findByUid($newsUid)) {
$defaultQuerySettings = $this->newsRepository->createQuery()->getQuerySettings();
$defaultQuerySettings->setIgnoreEnableFields(TRUE);
$this->newsRepository->setDefaultQuerySettings($defaultQuerySettings);
if ($news = $this->newsRepository->findByUid($newsUid)) {
$this->request->setArgument('news', $news);
}
}
}
}
The Hard Part is to get the object to update. As I never try this I have found an TypeConverter to fetch also hidden Records at https://gist.github.com/helhum/58a406fbb846b56a8b50
Maybe Instead to register the TypeConverter for everything (like the example in ext_localconf.php) you can try to assign it only in the initializeUpdateAction
public function initializeUpdateAction() {
if ($this->arguments->hasArgument('news')) {
$this->arguments->getArgument('news')->getPropertyMappingConfiguration()
->setTypeConverter('MyVendor\\MyExtension\\Property\\TypeConverters\\MyPersistenObjectConverter')
}
}

TYPO3 Hook for Page/ Content

I found this Answer here on stackoverflow.
I need a Hook, which is executed when a page and content is created, deleted, moved or updated.
I only found this hook processDatamap_postProcessFieldArray but it will not be executed if the content is created, deleted, moved or updated. It is executed only when a page is created or deleted.
I'm on TYPO3 Version 7.6.9.
Is there a list of all available hooks?
Greetings.
Check out this answer. It has a detailed explanation on how to set up a hook that executes upon record deletion and will certainly help you out.
To sum it up, you need to register your hook in an ext_tables.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['YourHook'][] = 'YourVendor\\YourExt\\Hooks\\YourHook';
And declare the hook itself in:
/ext/your_ext/Classes/Hooks/yourHook.php
Here is a partial list of available hooks from the official Docs.
Edit
Your are looking for the correct Member Function.
To be honest, I am not sure if you need to hook multiple of them or if using processCmdmap_afterFinish will do what you need:
<?php
namespace YourVendor\YourExt\Hooks;
class ProcessCmdmap {
/**
* hook that is called when an element shall get deleted
*
* #param string $table the table of the record
* #param integer $id the ID of the record
* #param array $record The accordant database record
* #param boolean $recordWasDeleted can be set so that other hooks or
* #param DataHandler $tcemainObj reference to the main tcemain object
* #return void
*/
function processCmdmap_postProcess($command, $table, $id, $value, $dataHandler) {
/* Does this trigger at all for the actions you need? */
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($command);
die();
if ($command == 'delete' ||
$command == 'update' ||
$command == 'move' ||
$table == 'tx_yourext_domain_model_something') {
}
}
}
large portions of this code come from this answer

Extbase property mapping for deleted record

I would like to build a preview page for a create form. I set "deleted" property of the record to "1" when in previewAction because in the BE the list module is used to approve the inserted records - so if the record was never finally saved its deleted anyway.
Problem: I can create the record (deleted=1) - I can jump back to the form (no history back for I have to keep the created object). But if I submit again the property mapping tells me
Object of type MyModel with identity "3" not found.
Of course that's because its deleted. The settings in the Repository to ignore deleted are not taking action here.
Yes I could bypass the Extbase magic by filling up everything manually, but this is not what I want.
Here is the action to get an idea what I'm trying
/**
* action preview
*
* #param MyModel
* #return void
*/
public function previewAction(MyModel $newModel)
{
//check if model was already saved
$uid = $this->request->hasArgument('uid') ? this->request->getArgument('uid') : 0;
if($uid){
$newModel = $this->myRepository->findDeletedByUid($uid);
$this->myRepository->update($newModel);
}
else{
$newModel->setDeleted(true);
$this->myRepository->add($newModel);
}
$this->view->assign('ad', $newModel);
$this->persistenceManager->persistAll();
$uid = $this->persistenceManager->getIdentifierByObject($newModel);
$this->view->assign('uid', $uid);
}
Any ideas?
The Extbase default query settings suppress deleted objects.
Since you've already stated the custom query findDeletedByUid() in your repository, you just need to set it to include deleted records. It is important, however, that if you want to call your controller action using the object, you'll have to retrieve it before calling the action. Use an initialization action for that. The initializaton will be called automatically before the action.
If you want to set wether the object is deleted, you'll also going to need to define a property, getter and setter in your Domain Model and a proper definition in your tca to enable the data mapper to access the column.
In the repository:
public function findDeletedByUid($uid) {
$query = $this->createQuery();
$query->getQuerySettings()->setIncludeDeleted(true);
$query->matching(
$query->equals('uid',$uid)
);
return $query->execute();
}
In your Controller class:
/**
* initialize action previewAction
* Overrides the default initializeAction with one that can retrieve deleted objects
*/
public function initializePreviewAction(){
if( $this->request->hasArgument('mymodel') ){
$uid = $this->request->getArgument('mymodel');
if( $mymodel = $this->mymodelRepository->findDeletedByUid($uid) ){
$this->request->setArgument($mymodel);
} else {
// handle non retrievable object here
}
} else {
// handle missing argument here
}
}
In your Domain Model:
...
/**
* #var bool
*/
protected $deleted;
/**
* #return bool
*/
public function getDeleted() {
return $this->deleted;
}
/**
* #param bool $deleted
*/
public function setDeleted($deleted) {
$this->deleted = $deleted;
}
In your tca.php
...
'deleted' => array(
'exclude' => 1,
'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.deleted',
'config' => array(
'type' => 'check',
),
),
Instead of doing any magic with deleted, you should use the hidden field to allow editors to preview documents.
You can tell your query to include hidden records inside the repository.
Your findDeletedByUid($uid) function caught my eye. If it's not a custom function, should it use something like findByDeleted(TRUE) or findByDeleted(1) in combination with ->getFirst() or ->findByUid()? You can find discussions in the Extbase manual reference and the Repository __call() function API sections.
Thanks for all hints.
I think depending to the answers its not possible without bypass extbase property-mapping magic. So I think in general its not a good idea to do it like that.
So I put now my own flag "stored" to the model.
In BE List-Module the not "stored" objects are still visible, but using an own BE Module or deleting the not "stored" object by a cron-job should do the job.
If anyone has a bedder idea feel free to share it :-)

TYPO3 Extbase individual code on backend-deletion of an object

I would like to execute some individual code when one of my Extbase domain objects is deleted from the list view in TYPO3 backend.
Thought that it could / would work by overwriting the remove( $o ) method in the according repository like
public function remove( $object ) {
parent::__remove( $object );
do_something_i_want();
}
, but that won't work I guess. Looks like the repository-methods are only called / used by actions of my extension (e.g. if I had some delete-action in a FE- or BE-plugin) but not when the object is just deleted from list view in the backend? I don't use (up to now) any FE/BE-plugin / -actions - only the simple add/edit/delete functions in the backends list view of my storage folder.
Background: I have e.g. two models with some 1:n relation (let's say singer and song), where one object includes some uploaded file (album_cover > pointing to the file's URL in /uploads/myext/ folder); using #cascade works fine for deleting every song belonging to a singer that is deleted, but it won't touch the file uploaded (only) for song.album_cover - leading to quite some waste over time. So I would love to do some sort of onDeletionOfSinger() { deleteAllFilesForHisSongs(); }-thing.
(Same thing would apply on deletion of let's say a single song and it's album_cover-file.)
Sounds quite easy & common, but I just don't get behind it and found nothing useful - would love some hint / pointing to the right direction :-).
List view uses TCEmain hooks during its operations, so you can use one of them to intersect delete action, i.e.: processCmdmap_deleteAction
Register your hooks class in typo3conf/ext/your_ext/ext_tables.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][] = 'VENDORNAME\\YourExt\\Hooks\\ProcessCmdmap';
Create a class with valid namespace and path (according to previous step)
file: typo3conf/ext/your_ext/Classes/Hooks/ProcessCmdmap.php
<?php
namespace VENDORNAME\YourExt\Hooks;
class ProcessCmdmap {
/**
* hook that is called when an element shall get deleted
*
* #param string $table the table of the record
* #param integer $id the ID of the record
* #param array $record The accordant database record
* #param boolean $recordWasDeleted can be set so that other hooks or
* #param DataHandler $tcemainObj reference to the main tcemain object
* #return void
*/
function processCmdmap_postProcess($command, $table, $id, $value, $dataHandler) {
if ($command == 'delete' && $table == 'tx_yourext_domain_model_something') {
// Perform something before real delete
// You don't need to delete the record here it will be deleted by CMD after the hook
}
}
}
Don't forget to clear system cache after registering new hook's class
In addition to biesiors answer I want to point out, that there is also a signalSlot for this. So you can rather register on that signal than hooking into tcemain.
in your ext_localconf.php put:
$signalSlotDispatcher =
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\SignalSlot\\Dispatcher');
$signalSlotDispatcher->connect(
'TYPO3\CMS\Extbase\Persistence\Generic\Backend',
'afterRemoveObject',
'Vendor\MxExtension\Slots\MyAfterRemoveObjectSlot',
'myAfterRemoveObjectMethod'
);
So in your Slot you have this PHP file:
namespace Vendor\MxExtension\Slots;
class MyAfterRemoveObjectSlot {
public function myAfterRemoveObjectMethod($object) {
// do something
}
}
Note thet $object will be the $object that was just removed from the DB.
For more information, see https://usetypo3.com/signals-and-hooks-in-typo3.html