Typo3 , extbase mapped entity return no results - typo3

First of all, i create a Extension with the "Extension Builder". I would like to extend the "tt_address" Extension.
What steps i do?
I create a Entity Adress and map this to existing table tt_address. In my Entity Address i create setter and getter for retrieving Addressinformations like 'city, zip and street'.
After this step i create a Repository AddressRepository which extends \TYPO3\CMS\Extbase\Persistence\Repository.
The extensionbuilder create a typoscript File ext_typoscript_setup.txt with this content:
config.tx_extbase{
persistence{
classes{
Mab\Oaaddress\Domain\Model\Address {
mapping {
tableName = tt_address
recordType = Tx_Oaaddress_Address
}
}
}
}}
I set the storagePid in my constants.txt
In the last step i would like to retrieve all Addresses from Database and show this in a list view.
class AddressController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* Events repository
*
* #var \Mab\Oaaddress\Domain\Repository\AddressRepository
* #inject
*/
protected $addressRepository;
/**
* action list
*
* #return void
*/
public function listAction() {
// Exists adress repository?
//var_dump($this->addressRepository);
$addresses = $this->addressRepository->findAll();
//var_dump(count($addresses));
$this->view->assign('addresses', $addresses);
}
The table tt_address contains more than ten results. But the controller show $addresses count always 0. I clear every Cache (empty Typo3Temp folder, empty Cache through Install Tool, emptyh Backend Cache) but nothing have a effect. Why my Controller return nothing? Can someone give me a tip?
Update
After i analyse the query log, i find that this query is executed:
SELECT tt_address.* FROM tt_address WHERE 1=1 AND (tt_address.tx_extbase_type='Tx_Oaaddress_Address') AND tt_address.pid IN (148) AND tt_address.deleted=0 AND tt_address.hidden=0
How can i remove this part
tt_address.tx_extbase_type='Tx_Oaaddress_Address' from the where part of the query?

You can add:
config.tx_extbase {
persistence {
classes {
FriendsOfTYPO3\TtAddress\Domain\Model\Address {
mapping {
tableName = tt_address
recordType = Tx_TtAddress_Listview
}
}
}
}
}
And add "Tx_TtAddress_Listview" to your tx_extbase_type like:
// Configuration/TCA/Overrides/tt_address.php
$tempColumnstx_yourext_tt_address[$GLOBALS['TCA']['tt_address']['ctrl']['type']] = [
'exclude' => true,
'label' => 'yourLabe for Type',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [
['Person','Tx_TtAddress_Listview'],
['MyLabel','Tx_YourExt_type']
],
'default' => 'Tx_TtAddress_Listview',
'size' => 1,
'maxitems' => 1,
]
];
This adds a or to the SQL statement like:
... WHERE ((`tt_address`.`t
x_extbase_type` = Tx_YourExt_type) OR (`tt_address`.`tx_extbase_type` = Tx_TtAddress_Listview) ...

Related

How can I save url and local images?

I'm working on a project that uses the Spotify API. I get all the top tracks and top artists from users. I save the information into my database, the image that I get from Spotify is a URL image. Like this https://i.scdn.co/image/ab67616d0000b27300d5163a674cf57fe79866a6
In my CMS I want to be able to change the image of an artist or track. For this, I copied the code from the docs https://backpackforlaravel.com/docs/4.1/crud-fields#image-1 "public function setImageAttribute($value) and put this in my model. Everything is working fine for this part, I can upload a new image and it saves into my public folder and gets displayed in my table.
The problem that I have is that when a new user logs in and the artist/track data needs to be saved in the database it goes through this setImageAttribute($value) in the model and doesn't add the URL into the database column.
Is there a way that I can add the URL image without going through the setImageAttribute function in the model?
The controller:
//get Top Artists
$client = new Client([]);
$topartists = $client->get('https://api.spotify.com/v1/me/top/artists?time_range=medium_term&limit=25&offset=5', [
'headers' => [
'Authorization' => 'Bearer ' . $token,
],
]);
$listtopartists = json_decode($topartists->getBody(), true);
$p = 25;
foreach ($listtopartists['items'] as $topartist) {
$artist = TopArtist::where('artist_id', $topartist['id'])->first();
$artist_genre = json_encode($topartist['genres']);
if ($artist === null) {
$new_artist = TopArtist::create([
'artist_id' => $topartist['id'],
'name' => $topartist['name'],
'genres' => $artist_genre,
'users_popularity' => $p,
'popularity' => $topartist['popularity'],
'image' => $topartist['images'][0]['url'],
'url' => $topartist['external_urls']['spotify'],
]);
$spotify_user->topArtists()->attach($new_artist->id);
} else {
$exists = $spotify_user->topArtists()->where('spotify_user_id', $spotify_user->id)->where('top_artist_id', $artist->id)->get();
if ($exists->isEmpty()) {
$spotify_user->topArtists()->attach($artist->id);
}
}
$p--;
}
The Model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class TopArtist extends Model
{
use \Backpack\CRUD\app\Models\Traits\CrudTrait;
use HasFactory;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'artist_id',
'name',
'popularity',
'users_popularity',
'url',
'image',
'genres',
'status',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'id' => 'integer',
'status' => 'boolean',
];
public function setImageAttribute($value)
{
$attribute_name = "image";
// or use your own disk, defined in config/filesystems.php
$disk = config('backpack.base.root_disk_name');
// destination path relative to the disk above
$destination_path = "public/storage/artists";
// if the image was erased
if ($value==null) {
// delete the image from disk
\Storage::disk($disk)->delete($this->{$attribute_name});
// set null in the database column
$this->attributes[$attribute_name] = null;
}
// if a base64 was sent, store it in the db
if (Str::startsWith($value, 'data:image'))
{
$image = \Image::make($value)->encode('jpg', 90);
$filename = md5($value.time()).'.jpg';
\Storage::disk($disk)->put($destination_path.'/'.$filename, $image->stream());
\Storage::disk($disk)->delete($this->{$attribute_name});
$public_destination_path = Str::replaceFirst('public/', '', $destination_path);
$this->attributes[$attribute_name] = $public_destination_path.'/'.$filename;
}
}
public function spotifyUsers()
{
return $this->belongsToMany(\App\Models\SpotifyUser::class);
}
}
I fixed it, I had to add an else statement after
if (Str::startsWith($value, 'data:image'))
in the model where I set $this->attributes[$attribute_name] = $value;.

TYPO3/Extbase How to create unique slug within create action?

I have slug field in my TCA and in general it works, when adding via Backend > List module, even if I won't input any value the unique eval ensures that slug will be unique, so when I'll create many rows with the same name Foo TYPO3 backend will enshure that it will resolve to unique slugs like foo, foo-1, foo-2, etc. Kudos!:
'slug' => [
'exclude' => true,
'label' => 'Slug',
'displayCond' => 'VERSION:IS:false',
'config' => [
'type' => 'slug',
'generatorOptions' => [
'fields' => ['name'],
'fieldSeparator' => '/',
'replacements' => [
'/' => '',
],
],
'fallbackCharacter' => '-',
'eval' => 'unique',
'default' => '',
'appearance' => [
'prefix' => \BIESIOR\Garage\UserFunctions\SlugPrefix::class . '->getPrefix'
],
],
],
However when creating a new object from my form within new/create actions (typical Extbase CRUD from extension_builder as you can see) like:
public function createAction(Car $newCar)
{
$this->addFlashMessage(
'The object was created. Please be aware that this action is publicly accessible unless you implement an access check. See https://docs.typo3.org/typo3cms/extensions/extension_builder/User/Index.html',
'',
\TYPO3\CMS\Core\Messaging\AbstractMessage::WARNING);
$this->carRepository->add($newCar);
$this->redirect('list');
}
of course slug is note set.
My first idea is to duplicate the logic of TCA type='slug' and just add this functionality with some own JS, AJAX and PHP, however that sounds as overload and time consumption. Especially that I don't want the user to care about slug part at all. Is there any simple API for lookup for a unique slug of the given table that can be used in custom action instead?
Note this question is not about how to handle it with JS, that's just concept. I would like to skip this part for FE user at all, he doesn't need to know what the slug is. Just during creating a new object, I want to get unique value like foo-123 instead.
In addition to Jonas Eberles answer here's another example which also respects the eval configuration of the slug field (can be uniqueInSite, uniqueInPid or simply unique).
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
use TYPO3\CMS\Core\DataHandling\SlugHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
...
public function createAction(Car $newCar)
{
$this->carRepository->add($newCar);
GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class)->persistAll();
$record = $this->carRepository->findByUidAssoc($newCar->getUid())[0];
$tableName = 'tx_garage_domain_model_car';
$slugFieldName = 'slug';
// Get field configuration
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$slugFieldName]['config'];
$evalInfo = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
// Initialize Slug helper
/** #var SlugHelper $slugHelper */
$slugHelper = GeneralUtility::makeInstance(
SlugHelper::class,
$tableName,
$slugFieldName,
$fieldConfig
);
// Generate slug
$slug = $slugHelper->generate($record, $record['pid']);
$state = RecordStateFactory::forName($tableName)
->fromArray($record, $record['pid'], $record['uid']);
// Build slug depending on eval configuration
if (in_array('uniqueInSite', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
} else if (in_array('uniqueInPid', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
} else if (in_array('unique', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInTable($slug, $state);
}
$newCar->setSlug($slug);
$this->carRepository->update($newCar);
}
with custom finder in the repository to fetch assoc array instead of the mapped object for $racord argument
public function findByUidAssoc($uid)
{
$query = $this->createQuery();
$query->matching(
$query->equals('uid', $uid)
);
return $query->execute(true)[0];
}
Note that the record needs to be persisted before executing above code.
References:
SlugHelper::generate
SlugHelper::buildSlugForUniqueInSite
SlugHelper::buildSlugForUniqueInPid
SlugHelper::buildSlugForUniqueInTable
According to answers from Elias and Jonas, I created a class which simplifies things especially when you have more models to handle
typo3conf/ext/sitepackage/Classes/Utility/SlugUtility.php
<?php
namespace VENDOR\Sitepackage\Utility; // <- to be replaced with your namespace
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
use TYPO3\CMS\Core\DataHandling\SlugHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/***
*
* This file is part of the "Sitepackage" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* (c) 2020 Marcus Biesioroff <biesior#gmail.com>
* Concept by: Elias Häußler
* Jonas Eberle
*
***/
class SlugUtility
{
/**
* #param int $uid UID of record saved in DB
* #param string $tableName Name of the table to lookup for uniques
* #param string $slugFieldName Name of the slug field
*
* #return string Resolved unique slug
* #throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
*/
public static function generateUniqueSlug(int $uid, string $tableName, string $slugFieldName): string
{
/** #var Connection $connection */
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
$queryBuilder = $connection->createQueryBuilder();
$record = $queryBuilder
->select('*')
->from($tableName)
->where('uid=:uid')
->setParameter(':uid', $uid)
->execute()
->fetch();
if (!$record) return false;
// Get field configuration
$fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$slugFieldName]['config'];
$evalInfo = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
// Initialize Slug helper
/** #var SlugHelper $slugHelper */
$slugHelper = GeneralUtility::makeInstance(
SlugHelper::class,
$tableName,
$slugFieldName,
$fieldConfig
);
// Generate slug
$slug = $slugHelper->generate($record, $record['pid']);
$state = RecordStateFactory::forName($tableName)
->fromArray($record, $record['pid'], $record['uid']);
// Build slug depending on eval configuration
if (in_array('uniqueInSite', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
} else if (in_array('uniqueInPid', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
} else if (in_array('unique', $evalInfo)) {
$slug = $slugHelper->buildSlugForUniqueInTable($slug, $state);
}
return $slug;
}
}
Usage in any place, like controller. Scheduler task, repository, etc. Keep in mind that record should be saved before (it may be created by Extbase, or just with plain SQL), just need to have created uid and be valid TYPO3 record.
use VENDOR\Sitepackage\Utility\SlugUtility;
use \TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
...
$pageSlug = SlugUtility::generateUniqueSlug(
5, // int $uid UID of record saved in DB
'pages', // string $tableName Name of the table to lookup for uniques
'slug' // string $slugFieldName Name of the slug field
)
// or
$uniqueSlug = SlugUtility::generateUniqueSlug(
123,
'tx_garage_domain_model_car',
'slug'
);
// or according to the original question,
// if you created new model object with Extbase,
// persist it, create unique slug with SlugUtility
// set the slug property to the created model object and finally update
public function createAction(Car $newCar)
{
$this->carRepository->add($newCar);
GeneralUtility::makeInstance(PersistenceManager::class)->persistAll();
$uniqueSlug = SlugUtility::generateUniqueSlug(
$newCar->getUid(),
'tx_garage_domain_model_car',
'slug'
);
if($uniqueSlug) {
$newCar->setSlug($uniqueSlug);
$this->carRepository->update($newCar);
}
$this->redirect('list');
}
// no need for second call to persistAll()
// as Extbase will call it at action's finalizing.
// etc.
You can use the SlugHelper directly. The API was obviously not made very fluent for that use case but it works...
$this->carRepository->add($newCar);
// probably you need to persist first - I am not sure if this is really necessary
$this->objectManager()->get(
\TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager::class
)->persistAll();
$table = 'tx_garage_domain_model_car';
$field = 'slug';
// a stripped down record with just the necessary fields is enough
$record = ['name' => $newCar->getName()];
$pid = $this->settings->persistence->...
$slugHelper = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
\TYPO3\CMS\Core\DataHandling\SlugHelper::class,
$table,
$field,
$GLOBALS['TCA'][$table]['columns'][$field]['config']
);
$newCar->slug = $slugHelper->generate($record, $pid);

Restrict page media allowedExtensions TYPO3 9.5.x

How can I restrict allowedExtention just for $GLOBALS['TCA']['pages']['columns']['media']? But not using $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] as it will add restriction for all FAL fields.
I found class
class ImageManipulationElement extends AbstractFormElement
{
/**
* Default element configuration
*
* #var array
*/
protected static $defaultConfig = [
'file_field' => 'uid_local',
'allowedExtensions' => null, // default: $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
Looks like should be something like:
$GLOBALS['TCA']['pages']['columns']['media']['config']['overrideChildTca']['allowedExtensions'] = 'jpg, jpeg';
Hard way will drope all usefull things for this field.
$GLOBALS['TCA']['pages']['columns']['media'] = [
'exclude' => true,
'label' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.media',
'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
'media',
[], 'jpg, jpeg'
)
];
So not is our way. I need just some override like
$GLOBALS['TCA']['pages']['columns']['media']['config']['overrideChildTca']['columns']...['allowedExtention'] = 'jpg, jpeg';
Who know knows how to do this?
After small searching I found this example:
$allowExtensions = 'jpg,jpeg';
$GLOBALS['TCA']['pages']['columns']['media']['config']['filter'][0]['parameters']['allowedFileExtensions'] = '$allowExtensions;
$GLOBALS['TCA']['pages']['columns']['media']['config']['overrideChildTca']['columns']['uid_local']['config']['appearance']['elementBrowserAllowed']= $allowExtensions;
added to typo3conf/ext/myext/Configuration/TCA/Overrides/pages.php

Getting Unsupported or non-existing property name Exception in TYPO3

I am trying to add sys_category in my own custom modal by using this code in my setup.txt
config.tx_extbase {
objects {
TYPO3\CMS\Extbase\Domain\Model\Category {
className = ABC\MyExt\Domain\Model\Category
}
TYPO3\CMS\Extbase\Domain\Repository\CategoryRepository {
className = ABC\MyExt\Domain\Repository\CategoryRepository
}
}
}
plugin.tx_myext {
persistence {
classes {
ABC\MyExt\Domain\Model\Category {
mapping {
tableName = sys_category
}
}
}
}
}
but I am getting this exception Unsupported or non-existing property name \"categories\" used in relation matching
and in my model I am using this
/**
* categories
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\ABC\MyExt\Domain\Model\Category>
*/
protected $categories = null;
what am I doing wrong in making the relation?
My TCA I am using this in the ext_tables.php
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::makeCategorizable(
'myext',
'tx_myext_domain_model_tablename,
// Do not use the default field name ("categories") for pages, tt_content, sys_file_metadata, which is already used
'categories',
array(
// Set a custom label
'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang.xlf:additional_categories',
// This field should not be an exclude-field
'exclude' => false,
// Override generic configuration, e.g. sort by title rather than by sorting
'fieldConfiguration' => array(
'foreign_table_where' => ' AND sys_category.sys_language_uid IN (-1, 0) ORDER BY sys_category.title ASC',
),
// string (keyword), see TCA reference for details
'l10n_mode' => 'exclude',
// list of keywords, see TCA reference for details
'l10n_display' => 'hideDiff',
)
);
and in the table I have this categories int(11) DEFAULT '0' NOT NULL,
Frontend requests do not load ext_tables.php in requests anymore since TYPO3 8.5 (TYPO3 CMS Core ChangeLog).
The call of makeCategorizable() has to be moved to Configuration/TCA/Overrides/<table>.php.

TYPO3 Extbase how to empty ObjectStorage

I want to "empty" an ObjectStorage when updating a Object:
It's TYPO3 4.6 with a Extbase Extension which allows you to show/add/edit/delete datasets in the frontend. At first sight everything looks good.
I have one field referencing another table:
TCA:
'partner' => array(
'exclude' => 0,
'label' => 'LLL:EXT:toco3_marketingdb/Resources/Private/Language/locallang_db.xlf:tx_toco3marketingdb_domain_model_firma.partner',
'config' => array(
'type' => 'select',
'size' => 5,
'foreign_table' => 'tx_toco3marketingdb_domain_model_partner',
'foreign_table_where' => 'ORDER BY tx_toco3marketingdb_domain_model_partner.partnerpkey',
'minitems' => 0,
'maxitems' => 20,
),
),
Model:
/**
* Partner
*
* #var Tx_Extbase_Persistence_ObjectStorage<Tx_Toco3Marketingdb_Domain_Model_Partner>
* #lazy
*/
protected $partner;
/**
* Sets the partner
*
* #param Tx_Extbase_Persistence_ObjectStorage<Tx_Toco3Marketingdb_Domain_Model_Partner> $partner
* #return void
*/
public function setPartner(Tx_Extbase_Persistence_ObjectStorage $partner) {
$this->partner = $partner;
}
Controller:
$partner = new Tx_Extbase_Persistence_ObjectStorage();
if (count($partnerarr) > 0){
foreach($partnerarr as $p){
$partner->attach( $this->partnerRepository->findByUid($p));
}
}
$organisation = $this->organisationRepository->findByUid($uid)
$organisation->setPartner($partner);
This is working as long there is an Object in the ObjectStorage. So I can add/delete/change relations. But when $partnerarr is empty an no objects get attached an empty Tx_Extbase_Persistence_ObjectStorage is assigned, the old values do not get "deleted". I also tried to assign null or "" but the an error occures because an ObjectStorage is needed. If I assign the empty ObjectStorage I don't get an error, but the old values still maintain :(
Any idea?
Thank you
Christian
Call the detach or removeAll methods to remove certain or all objects of the storage.
/** #var \Tx_Extbase_Persistence_ObjectStorage $organisationPartners */
$organisationPartners = $organisation->getPartner();
foreach ($organisationPartners as $partner) {
$organisationPartners->detach($partner);
}
Thank you #Wolfgang for your message.
I added the following function to my model:
/**
* detach Partner
*
* #param Tx_Toco3Marketingdb_Domain_Model_Partner $partner
* #return void
*/
public function detachPartner($partner) {
$this->partner->detach($partner);
}
In the controller I added:
$persistanceManager = t3lib_div::makeInstance('Tx_Extbase_Persistence_Manager');
$organisation = $this->firmaRepository->findByUid($uid);
$organisationPartners = $organisation->getPartner();
foreach ($organisationPartners as $organisationPartner) {
$organisation->detachPartner($organisationPartner);
}
$persistanceManager->persistAll();
$organisation->setPartner($partner);
It is important to persist before setting the new (empty) value...