TYPO3 Extbase TCA MM-relation filter results by site-defined constant "newRecordStoragePid" - typo3

TLDR:
I've got two models with MM-Relations with different StoragePids defined via Constants in my template.
I don't know how to filter results while querying my data regarding my configured storagePids for my related Model.
Long version:
In my multisite-TYPO3 installation I've got two models "Person" and "PersonalInformation". These models have a MM-Relation defined via TCA.
"Person" contains all general data, stored in a global RecordStore. "PersonalInformation" contains editable Data i.e. images to be editable for each site separately. These data are stored in seperate RecordStores under each site.
That means within each site-template->Constants I've defined the extension-storagePid i.e.: $plugin.tx_myext.persistence.storagePid = 1
This config is on all sites the same, to be able to access the same RecordStore from each Site.
The RecordStore for "PersonalInformation" should be different for each site. So my setup.txt of my extension looks like:
persistence {
storagePid = {$plugin.tx_myext.persistence.storagePid},
{$plugin.tx_tx_myext.persistence.personalInformationStoragePid}
classes {
TYPO3\T3myext\Domain\Model\PersonalInformation {
newRecordStoragePid = {$plugin.tx_myext.persistence.personalInformationStoragePid}
}
}
}
And in my root-site-template under Constants I've defined plugin.tx_myext.persistence.personalInformationStoragePid for each site individually.
My TCA MM-Relation defined for PersonalInformation:
'person' => array(
'exclude' => 1,
'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_person',
'config' => array(
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_myext_domain_model_person',
'foreign_table_where' => 'AND 1=1 ORDER BY last_name ASC',
'MM' => 'tx_myext_person_personalinformation_mm',
'size' => 10,
'autoSizeMax' => 30,
'maxitems' => 1,
'minitems' => 0,
'multiple' => 0,
),
),
My TCA MM-Relation defined for Person:
'personalinformation' => array(
'exclude' => 1,
'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_person.personalinformation',
'config' => array(
'type' => 'none',
'readonly' => 1,
'foreign_table' => 'tx_myext_domain_model_personalinformation',
'MM_opposite_field' => 'personalinformation',
'MM' => 'tx_myext_person_personalinformation_mm',
'foreign_table_where' => 'AND tx_myext_domain_model_personalinformation.pid=###The-PID-defined-in-my-site-Const-for-personalInformationStoragePid###'
),
),
If I var_dump my Person in the Frontend all Person.PersonaInformation of all RecordStores are displayed. But I what to show only PersonalInformation Records of the current Site.

The field in the model will always give back all relations, independent of the storage pid. The foreign_table_where in TCA is only for the backend, so this will do nothing for the frontend.
If you want to only get relations from a certain pid, there are several solutions:
Filter it yourself, either in your template, model or controller. Just loop through the relations and check the pid. This option is easiest, but will be slow if you have a lot of relations.
Select the PersonalInformation records separately in your controller using a PersonalInformationRepository with a findByPerson function. This will respect the storagePid set in TypoScript. This will work fine if you only need the information for 1 person. If you need it for multiple persons on 1 page (in a list view for example) you can do this in a custom getPersonalInformation function in your Person model. If it's not cached it could also be slow for lists (depending on the amount of records).
Use a completely custom query using QueryBuilder (https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html). This way you can do it in 1 query with joins.
What is the best solution depends on your exact situation and the number of records.

Related

TYPO3 - How can I sort a record by a field of a foreign_table and not by it's uid?

'passwordtype' => [
'exclude' => false,
'label' => '###label####',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'passwordtypes',
'foreign_sortby' => 'name',
],
],
That part of the tca works like a charm. The passwordtypes are getting sorted by name when I open the record of a password in the backend.
But I want the sorting of the passwordrecords also happening by the passwordtypes. So I tried 'sortby' => 'passwordtype' under 'ctrl' but that only sorts it by the uid, not by the name of the passwordtypes.
I also tried 'sortby' => 'passwordtype.name' but that creates an error. Is it possible to sort it by the name of passwordtypes instead of the uid? It clearly has the name of the passwordtypes already in the passwordlist because when I change the 'label' under 'ctrl' to 'passwordtype' than I can see the correct names and not uids.
I'm open for every idea even if I have to change my database-structure.
Let's dive into the core...
The record lists are generated via \TYPO3\CMS\Recordlist\Controller\RecordListController::main(). Fot getting the list itself, the method calls
\TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList::generateList().
generateList() does select data only from the actual table itself. No relations are resolved here. Then it becomes clearer why sorting by fields of a relation is not possible: they are not part of the selected data.
But... The labels of foreign records are shown.
Yes, that's done while rendering a concrete row of the list. At that
moment, no sorting can be applied anymore.
But... It works for sorting by UIDs.
Yes and no. You have a 1:n relation, so the UID of the foreign record
is saved as foreign_key in your (passwordrecords) table. The
'sortby' => 'passwordtype' is not applied to the table
passwordtype but to the column passwordrecords.passwordtype containing these UIDs.
Conclusion:
Out of the box, there seems not to be an option for sorting by a foreign table field.
Maybe, you can hook into the recordlist by \TYPO3\CMS\Backend\RecordList\RecordListGetTableHookInterface::getDBlistQuery() (called in \TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList::getTable) and modify the query parts to meet your needs.
Simply put the order in are foreign_table_where:
'passwordtype' => [
'exclude' => false,
'label' => '###label####',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'passwordtypes',
'foreign_table_where' => 'AND 1=1 ORDER BY name',
],
],

Typo3 - How to add dynamic value in TCA MM table

I'm trying to add a column to the relation table (MM table) in Typo3. Let's say I have my tables user, wanted_car and wanted_car_mm which is my relation table. So in wanted_car_mm I'll know what user want which car, but I want to add the column need_faster in wanted_car_mm, which is a boolean, that tells me who should have it faster. A lot of users could need it faster, there's no order.
How do I add this column, map the value and correctly retrieve this information when I get all my users?
I currently have this in my TCA config for user:
'wanted_cars' => array(
'exclude' => 1,
'label' => 'LLL:EXT:caa_my_plugin/Resources/Private/Language/locallang_db.xlf:my_title',
'l10n_mode' => 'exclude',
'config' => array(
'type' => 'select',
'foreign_table' => 'wanted_car',
'MM' => 'wanted_car_mm',
'MM_insert_fields' => array('need_faster' => '???'),
'MM_table_where' => ' AND wanted_car_mm.need_faster = ???',
'maxitems' => 9999,
'multiple' => 0,
'renderType' => 'selectCheckBox',
),
),
I feel like this is how I should add the column and retrieve it correctly, but I have no clue how to map the correct value.. that I should also put in my where clause.. I don't know how this can be possible going like this, but I can't find any other way of doing it.
The value must be specified when creating a user.
You need an intermediate table for that. The full docs for that can be found on https://docs.typo3.org/m/typo3/reference-tca/master/en-us/ColumnsConfig/Type/Inline.html
a screenshot and old example which still should work can also be found on https://wiki.typo3.org/Inline_Relational_Record_Editing_Attributes

Extend TYPO3 News Repository with M:M Relation

I've been trying to extend tx_news with a m:m relation with not much luck so far. Anything I can find online is just for a regular relation, where the ID is saved directly into the same table, not an extra _mm column.
The backend looks fine so far, the way I want it with a selectMultipleSideBySide renderType. It also saves the relations to the database.
Extending the News TCA with this, works fine:
$extendArtistId = array(
'artist_id' => array (
'exclude' => 0,
'l10n_mode' => 'exclude',
'label' => 'Künstler',
'config' => array(
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'enableMultiSelectFilterTextfield' => TRUE,
'foreign_table' => 'tx_bfartistmanagement_domain_model_kuenstler',
'foreign_table_where' => 'AND tx_bfartistmanagement_domain_model_kuenstler.sys_language_uid = 0 AND tx_bfartistmanagement_domain_model_kuenstler.pid = 63 ORDER BY tx_bfartistmanagement_domain_model_kuenstler.name ASC',
'MM' => 'tx_news_domain_model_news_artist_id_mm',
'minitems' => 0,
'maxitems' => 99
),
),
);
And the entries are saved the way I expected:
The problem I'm having now is getting these relations from a different extension. News has something called "Hooks", but I'm not sure I want/need that for my case.
The query inside my other extension currently looks like this, newsRepository being injected:
$news = $this->newsRepository->findByArtistId(intval($userID));
I struggle with the next step(s). The function findByArtistId was working before as I was saving the artistId directly inside the news table, but that only works with a direct relation, not an M:M one.
How can I get the News that are associated with that artist, with an m:m relation?

TYPO3 Extbase m:m relation within TCA

I'm currently struggling with a m:m relation within a simple custom TYPO3 extension.
There are multiple filters and about a dozen of categories a filter can be related to. The relation consists of the category being selected within the filter:
Now, there are about 150 filters and all categories show all filters when assigning them to a product, but should only show those filters that are associated to that category.
I could not find any documentation about this, my current TCA setup looks like this:
'filter_bauwerkszustand' => array(
'exclude' => 0,
'label' => 'Bauwerkszustand',
'config' => array(
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_produkte_domain_model_filter',
'foreign_table_where' => ' AND tx_produkte_domain_model_filter.filterkategorie = 1 AND tx_produkte_domain_model_filter.sys_language_uid=###REC_FIELD_sys_language_uid### ORDER BY tx_produkte_domain_model_filter.titel ASC',
'MM' => 'tx_produkte_filter_filterkategorie_mm',
'size' => 10,
'autoSizeMax' => 10,
'maxitems' => 9999,
'multiple' => 0,
),
),
If its a MM-relation thats the problem: the relation colum in filter doesnt acutally hold the categories, just the count of relations. The actual relations are reflected in a MM-table. The backend incorporates the categories in a SELECT statement with JOINs, first to the MM-table, and from there to the actual categories, like so:
tx_produkte_domain_model_filter.uid =
tx_produkte_filter_filterkategorie_mm.uid_local
tx_produkte_filter_filterkategorie_mm.uid_foreign =
tx_produkte_domain_model_filterkategorie.uid
Try relacing the tx_produkte_domain_model_filter.filterkategorie = 1 part in the query with tx_produkte_filter_filterkategorie_mm.uid_foreign = 1.

How to realize inheritance in Typo3 6.2 Extension?

My goal is being able to:
Create Expertise Entries in the backend (already accomplished)
Create SubExpertise Entries in the backend
(same props as Expertise but
they belong to one or many Expertise)
Create AdditionalInfoTitles Entries in the backend
(they can belong to one or many Expertise OR SubExpertise)
I want to be able to choose Objects from all Expertise AND SubExpertise when creating a new entry
Right now I can only choose between all Expertise-Entries:
That's why I thought about inheritance since then SubExpertise would be of the same type as Expertise and therefore automatically displayed in the Expertise list in a AdditionalInfoTitles entry. But that's just my theory and I'm kinda stuck in reality with typo3 TCA and other knowledge that I'm lacking...
In my extension builder I made following (don't mind the subExpertises property)
Then I added expertise to the Overrides folder, because I'm trying to extend it with subexpertise:
<?php
if (!defined('TYPO3_MODE')) {
die ('Access denied.');
}
$temporaryColumns = array (
'expertise' => array(
'exclude' => 1,
'label' => 'LLL:EXT:appoints/Resources/Private/Language/locallang_db.xlf:tx_appoints_domain_model_subexpertise.expertise',
'config' => array(
'type' => 'select',
'foreign_table' => 'tx_appoints_domain_model_subexpertise',
'MM' => 'tx_appoints_subexpertise_expertise_mm',
'size' => 10,
'autoSizeMax' => 30,
'maxitems' => 9999,
'multiple' => 0,
'wizards' => array(
'_PADDING' => 1,
'_VERTICAL' => 1,
'edit' => array(
'module' => array(
'name' => 'wizard_edit',
),
'type' => 'popup',
'title' => 'Edit',
'icon' => 'edit2.gif',
'popup_onlyOpenIfSelected' => 1,
'JSopenParams' => 'height=350,width=580,status=0,menubar=0,scrollbars=1',
),
'add' => Array(
'module' => array(
'name' => 'wizard_add',
),
'type' => 'script',
'title' => 'Create new',
'icon' => 'add.gif',
'params' => array(
'table' => 'tx_appoints_domain_model_expertise',
'pid' => '###CURRENT_PID###',
'setValue' => 'prepend'
),
),
),
),
),
);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns(
'tx_appoints_domain_model_expertise',
$temporaryColumns
);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes(
'tx_appoints_domain_model_expertise',
'expertise'
);
But I don't think I'm going into the right direction with this -
Because I think this way I'm not gonna be able to add a SubExpertise in the backend separately from an Expertise - I already have the same problem with my Objects that extend fe_user because when creating them I usually have to go through a new User and then set the extension type - but this way I don't have separate listings of the different entities that extend fe_user.
I would get rid of the separation between Expertise and SubExpertise for the most part. According to your description a SubExpertise cannot have another SubExpertise as its parent, so you can adapt the select field that it only lists Expertises which have an empty parent field.
By removing the difference the problem of selecting (Sub)Expertise's in AdditionalInfoTitles is removed; it's just one and the same type of objects.
If you need to differentiate in the presentation in the BE forms there are plenty of options to adjust the labels of the listed items, use a function of your own to build the list or even a custom form element.
In Extbase you can simply write a few functions in your repository to fetch Expertise's, SubExpertise's or both.
If the entity SubExpertise does not have a meaning in your domain model, Jigal's answer is perfect for your scenario. If it does have a meaning, you can achieve that using single table inheritance in Extbase.
class Expertise extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
{
// all common properties
}
class SubExpertise extends Expertise
{
/**
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\[YourVendorName]\Appoints\Domain\Model\Expertise>
*/
protected $expertises;
public function __construct()
{
$this->expertises = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
public function getExpertises() {}
public function setExpertises($expertises) {}
}
Via TypoScript then you have to define mapping rules, since both Expertise and SubExpertise would be stored in the same table tx_appoints_domain_model_subexpertise.
You'll find more details on single table inheritance in the Extbase book.