Getting Unsupported or non-existing property name Exception in TYPO3 - 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.

Related

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);

How to configure extbase to fetch inline / IRRE relations to tt_content?

I've built a extension where I can assign content elements (tt_content) inline (IRRE) to my entries in the TYPO3 Backend.
But I can't figure out how I can resolve the relations so that I can display the assigned content elements in the frontend.
I've created the extension with the 'Extension Builder' in TYPO3 9.5.5.
I created a Model Object 'Content' and selected 'Map to existing table' in the 'Domain object settings' and choosed 'tt_content'.
In my model 'ProductTab' I configured a 1:n relation of type 'Inline (IRRE)' with the name 'content' to my 'Content' model.
If I add content elements to my model entries in the Backend, it works like expected. The relational fields in the DB are mapped correctly. With the 'Extbase Variable Dump' I can see that all my relations are passed to the front end plugin, but my 'content' relation is an empty 'ObjectStorage'.
What am I missing so that the extbase persistence fetches my content relations?
ext_tables.sql excerpts
CREATE TABLE tx_myext_domain_model_producttab (
product int(11) unsigned DEFAULT '0' NOT NULL,
title varchar(255) DEFAULT '' NOT NULL,
content int(11) unsigned DEFAULT '0' NOT NULL,
);
CREATE TABLE tt_content (
producttab int(11) unsigned DEFAULT '0' NOT NULL,
);
TCA excerpts
'content' => [
'exclude' => false,
'label' => 'producttab.content',
'config' => [
'type' => 'inline',
'foreign_table' => 'tt_content',
'foreign_field' => 'producttab',
'maxitems' => 9999,
'appearance' => [
'collapseAll' => 0,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => 1,
'showPossibleLocalizationRecords' => 1,
'showAllLocalizationLink' => 1
],
],
],
Since I can add and edit the content elements in the Backend, I think the DB and TCA relation is fine.
But something is still missing, so that the relation is fetched and provided to the FE template so that I can display the assigned content elements with a ViewHelper.
ProductTab.php excerpts
class ProductTab extends \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject
{
protected $content = null;
protected function initStorageObjects()
{
$this->content = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
public function addContent(\MyExt\Domain\Model\Content $content)
{
$this->content->attach($content);
}
public function removeContent(\MyExt\Domain\Model\Content $contentToRemove)
{
$this->content->detach($contentToRemove);
}
public function getContent()
{
return $this->content;
}
public function setContent(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $content)
{
$this->content = $content;
}
}
Extbase Variable Dump
Screenshot of debug output
On the screenshot you can see the fetched products (one item in this case) and the producttabs (also 1 item). There is one content element assigned, but the property content of the ProductTab with uid=1 is empty.
Update
Since I'm still not able to solve this issue, I've created a minimalistic extension with the Extension Builder to demonstrate the issue: https://github.com/apiening/demo_irrecontent
The extension is the most simple one I could create with one model holding only one property and the relation to tt_content. It has a plugin list which also shows the debug output.
I've put some more details in the README.mdof the GitHub project.

Typo3 , extbase mapped entity return no results

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) ...

Repository/controller: How can I force TYPO3 to load the field "sorting"?

In a controller/template I'd like to have access to the field sorting of an entity.
I've tried to access it like:
$category->getSorting();
But it fails, as the method does not exist. When I dump the entity, all those meta fields, like hidden, starttime etc. aren't listed at all.
How can I tell TYPO3 to load those fields along with the other fields of the entitiy?
Since you are in Extbase context, you have to add the property to your model or (if you use the model of another extension) extend it and add the property. In both cases a getter and a setter method is needed if you want to access and edit the properties value:
/**
* #var integer
*/
protected $sorting;
public function setSorting($sorting) {
$this->sorting = $sorting;
}
public function getSorting() {
return $this->sorting;
}
Make sure you have that field configured in the TCA as well:
...
'columns' => array(
'sorting' => array(
'label' => 'sorting',
'config' => array(
'type' => 'passthrough'
)
),
...
After this you should be able to access the sorting property.

How to do a select with Zend Framework from a form?

im trying make a form with zend, and i do know how to do a select from a form
public function init()
{
$this->addElement("text","titulo",array(
"label" => "Titulo"
));
$this->setAttrib("id", "enviarNoticia");
$this->setAttrib("class", "FormEnviarNoticia");
$this->setMethod("post");
$this->addElement("textarea","noticia",array());
$this->addElement("submit","Enviar",array());
$this->addElement("multiselect", "categories",array(
"label" => "Categories",
"required" => false,
));
}
How to add options and item selected?
Instead of trying to get the data from the form itself, you should get the data from the model/database in your controller and assign the values to the form from the controller.
// In a controller
// get the options from your model or database into an array
$options = array('name' => 'value', 'name2' => 'value2', 'name3' => 'value3');
$form = new Application_Form_Form();
$form->getElement('categories')->setMultiOptions($options); // set the $options as the options for the categories multiselect
if ($this->getRequest()->isPost()) {
if ($this->form->isValid($this->getRequest()->getPost())) {
// form passed validation
}
} else { // form was not submitted
// to set default value(s) for the select
$form->getElement('categories')->setValue(array('name2', 'name3'));
}