TYPO3 11 TCA field 'type' => 'user' - Fetch child objects like inline - typo3

I'm trying to create my own TCA field user type to simply display some data in the typo3 backend of an objects child/children.
The thing is that when I fetch the children by query builder I would be returned an array with id's for its children instead of e.g. the title as defined in the tca of that object. As I want to display not the id but e.g. the title of that childs child I am stuck with a problem I don't know how to solve.
I've tried looking at the classes for the select elements (inline too) but when copying the beginning of the render function of it I already run into an "array key not defined" error for "items" (which is optional if a foreign_table is defined). The class I copied it from was \TYPO3\CMS\Backend\Form\Element\SelectSingleElement which I found by searching for AbstractFormElement which my class extends from too.
To make it clearer on what I want to achieve there's an example (I only used the parts that are important):
object1_tca.php:
'columns' => [
'object1' => [
'label' => 'object1',
'config' => [
'type' => 'user',
'renderType' => 'showChildData',
'foreign_table' => 'tx_ext_domain_model_object2',
'foreign_field' => 'object1',
'render_settings' => [
'childFields' => 'object3'
],
],
]
]
object2_tca.php:
'ctrl' => [
'title' => 'object2',
'label' => 'name',
'label_alt' => 'object3',
'label_alt_force' => true,
],
'columns' => [
'object3' => [
'label' => 'object3',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'foreign_table' => 'tx_ext_domain_model_object3',
'foreign_field' => 'object2',
],
],
]
object3_tca.php:
'ctrl' => [
'title' => 'object3',
'label' => 'name',
]
So the objects relations are as follows:
------------ ------------
| object 1 | ----- 1:m ----→ | object 2 |
------------ ------------
------------ ------------
| object 2 | ----- m:1 ----→ | object 3 |
------------ ------------
So, object2 will display as title its own name and the name of object3 that is selected. If I would change the field object2 of object1 configuration to a single select, I would also get that title for that child.
But I don't know how to fetch that title or that object2 data in my user type field.
In the end I want to do more with the data, group them by e.g. some field aso. This should also be applicable for different objects, so it's not the same repository or objects for the fields I want to use them for.
Can anyone help me? Is this even possible without having to fetch the children and the childrens children aso. from the database?
If you need more information I will provide whatever I can :) Any help is welcome. I feel like this should be easy but I'm stuck with even knowing where to look anymore. Thanks.

Related

TCA field of type group doesn't show records if there are too many items

In my own extension I have extended fe_user table to add some relations for users to sysfolders (pages with doktype 254). I did it with common TCA override and adding the group type. In general that works for normal amount of records (like several tens or so) but in some cases some users can have chosen up to 3000+ relations.
Unfortunately, in such case TYPO3 BE doesn't render these relations in form properly (it's empty, although database contains a list of 3000 comma-separated UIDs and FE plugins displays these relations properly).
Of course in such case after saving the form in BE causes that the DB column is cleared.
As you can see, I tried to increase the maxitems without result. Is there some other config option that I missed, or for such huge amount I should use another type of form field? Any advice will be highly appreciated.
File typo3conf/ext/myext/Configuration/TCA/Overrides/fe_users.php
'tx_myext_points' => [
'exclude' => 0,
'label' => 'Sale points for user',
'config' => [
'type' => 'group',
'internal_type' => 'db',
'allowed' => 'pages',
'maxitems' => 999999,
'suggestOptions' => [
'default' => [
'searchWholePhrase' => 1
],
'pages' => [
'searchCondition' => 'doktype = 254'
]
]
]
],

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',
],
],

Different value_options in Form Collection

I hava a Collection, in which a field User (a multiselect) depends on a previous Select, the Department. Therefore each User select contain a different "value_options".
How can I set different "value_options" when retrieving the form for each row of the Collection?
You have different options:
You create an API endpoint to retrieve form options
You make this into two different pages, the first one you choose the department and the second one you choose the user (ew)
You populate the form in server-side and you filter the selects on client side
I personally discourage the second option. Is there just to say that it is a possible solution, but NO.
The first option, the API, is interesting, but requires actually more work, especially if it is the only endpoint in your application.
The third option is the one I always use, since it requires the less code and it quite simple to implement.
In your form, you have your two elements:
$this->add([
'name' => 'department_id',
'type' => 'Select',
'attributes' => [
'id' => 'department_id'
],
'options' => [
'value_options' => [
1 => 'Marketing',
2 => 'IT',
3 => 'Logistic'
]
]
]);
$this->add([
'name' => 'user_id',
'type' => 'Select',
'attributes' => [
'id' => 'user_id',
'multiple' => true
],
'options' => [
'value_options' => [
[
'value' => 1,
'label' => 'John Doe - Marketing',
'attributes' => ['data-department-id' => 1]
],
[
'value' => 2,
'label' => 'Jane Doe - Marketing',
'attributes' => ['data-department-id' => 1]
],
[
'value' => 3,
'label' => 'Jack Doe - IT',
'attributes' => ['data-department-id' => 2]
],
[
'value' => 4,
'label' => 'Dana Doe - IT',
'attributes' => ['data-department-id' => 2]
],
[
'value' => 5,
'label' => 'Frank Doe - Logistic',
'attributes' => ['data-department-id' => 3]
],
[
'value' => 6,
'label' => 'Lara Doe - Logistic',
'attributes' => ['data-department-id' => 3]
]
]
]
]);
As you can see, all the users are put in the value_options. Keep in mind that this is just an example, you should use custom elements to populate this kind of selects ;)
Then, in your view, you render the elements:
<?= $this->formElement($this->form->get('department_id')); ?>
<?= $this->formElement($this->form->get('user_id')); ?>
And you finally add the JS code to handle the filter. Here I use jQuery, but it's not necessary to use it:
$(function () {
// Filter users when you load the page
filterUsers(false);
// Filter users when you change value on multiselect
$('#department_id').on('change', function () {
filterUsers(true);
});
});
function filterUsers(resetValue) {
var departmentId = $('#department_id').val();
// Remove previous value only when filter is changed
if (resetValue) {
$('#user_id').val(null);
}
// Disable all options
$('#user_id option').attr('disabled', 'disabled').attr('hidden', true);
// Enable only those that respect the criteria
$('#user_id option[data-department-id=' + departmentId + ']').attr('disabled', false).attr('hidden', false);
}
Final tip: don't forget to create and add to the form a Validator to check the couple department_id - user_id is correct, just to avoid (on my example) to accept Lara Doe (logistic) with IT department ;)

Get Disabled/Hidden Relation Table Records using TYPO3 Repository Query

I've a requirement in my custom extension where there are records, lets say which are projects saved in table name: tx_myextension_domain_model_projects and which are categorized using custom category table tx_myextension_domain_model_categories the relation which is mapped in model is m:n and saved in mm table tx_myextension_category_record_mm.
This is the TCA mapping for the category field:
products_category' => [
'exclude' => true,
'label' => 'Category Label',
'config' => [
'type' => 'select',
'renderType' => 'selectTree',
'foreign_table' => 'tx_myextension_domain_model_categories',
'foreign_table_where' => 'ORDER BY tx_myextension_domain_model_categories.sorting',
'MM' => 'tx_myextension_category_record_mm',
'MM_match_fields' => [
'tablenames' => 'tx_myextension_domain_model_projects',
],
'MM_opposite_field' => 'items',
'size' => 20,
'treeConfig' => [
'parentField' => 'parent',
'appearance' => [
'showHeader' => true,
'expandAll' => true,
'maxLevels' => 99,
],
],
],
],
So in one of my use cases I need to get the projects which are categorized with disabled and active categories.
For example this is the categories :
categories = [
A => [
A1 => [
A1.2
]
A2 => [
A2.2
]
],
B => [
B1 => [
B1.2
]
],
];
Lets say A1 is disabled/hidden record and I want to get the projects which are assigned to A1 and its child A1.2. Is there any possibilities to get the project records using TYPO3 Repository query.
Note: I couldn't disable the enable fields using $querySettings->setIgnoreEnableFields(true); because I don't want to disable it for the entire repository of category and also I need to disable it from the Project repository while query the project records. I hope my question is clear. Please let me know the solution for this.
Solution Currently I choosed: I used TYPO3 Connection Pool Query instead of repository query.
Why isnt $query->getQuerySettings()->setIgnoreEnableFields(true); an option for you? You can build the query exactly the way you need it like you would do with the Connection Pool.

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

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.