I'm trying to create nested record structures using TYPO3's DataHandler data structures (tested with TYPO3 v7). However relations are not created as expected. Consider the following data structure:
$data = array(
'sys_category' =>
array(
'NEW_1' =>
array(
'title' => 'Category 1',
'pid' => $pid,
),
'NEW_2' =>
array(
'title' => 'Category 3',
'pid' => $pid,
),
'NEW_3' =>
array(
'title' => 'Category 2',
'pid' => $pid,
),
'NEW_4' =>
array(
'title' => 'Category 1.1',
'pid' => $pid,
'parent' => 'NEW_1',
),
'NEW_5' =>
array(
'title' => 'Category 1.2',
'pid' => $pid,
'parent' => 'NEW_1',
),
'NEW_6' =>
array(
'title' => 'Category 3.1',
'pid' => $pid,
'parent' => 'NEW_2',
),
),
);
This gives the following result in the database:
uid title parent
1 Category 1 0
2 Category 3 0
3 Category 2 0
4 Category 1.1 0
5 Category 1.2 0
6 Category 3.1 0
Note the "0" value for all "parent" fields. Why is it that the "NEW_*" values are not interpreted for the "parent" fields set in the data structure?
As mentioned in a comment above, the situation changed between TYPO3 6.2 and 7.6. The difference lies in \TYPO3\CMS\Core\DataHandling\DataHandler::processRemapStack(). Starting with TYPO3 7.6, it checks if the "NEW*" placeholders contain a low dash (_). If yes, the placeholder is split on that character and the first part of the string is considered to be the related table name.
This is a change from before, where the low dash had no special meaning. Indeed, the documentation mentions examples using a low dash.
So the above code works fine with just removing the low dash from all the placeholders.
As far as I see only the pid-value is checked for the "NEW"-keyword.
But you could use some of the included hooks for enabling the assignment for "parent" too.
EDIT:
I'm referring to TYPO3\CMS\Core\DataHandling\DataHandler::process_datamap()
Related
I am trying to figure it out how can I display field in TCA when two values of other fields are same?
My configuration is that I have two fields new and old and one field second. I would like to reach that field second is displayed when new and old are same or new=1 (this is working).
$fields[] = array(
'new' => array(
'label' => "New ID",
'exclude' => 1,
'config' => array(
'type' => 'input'
)
),
);
$fields[] = array(
'old' => array(
'label' => "old ID",
'exclude' => 1,
'config' => array(
'type' => 'input'
)
),
);
$fields[] = array(
'second' => array(
'exclude' => 1,
'displayCond' => array(
'OR' => array(
'FIELD:new:=:1',
'FIELD:new:=FIELD:old'
)
),
'config' => array(
'type' => 'input',
'size' => '255',
)
),
);
The syntax ''FIELD:new:=FIELD:old' is not allowed by the display condition parser (which i rewrote in core v8). Thus, you can not compare the values of two different fields directly, and you are not able to solve your issue on a display condition level.
You may solve your issue by adding a new data provider (probably after the EvaluateDisplayConditionDataProvider) that removes your column in your special case, see https://docs.typo3.org/typo3cms/CoreApiReference/ApiOverview/FormEngine/Index.html for more docs.
I need a backend field where users can enter a time (ie "23:13:46", hours, minutes, seconds). But it should also be possible to not enter a time at all (which should of course be different from 00:00:00). Is it possible to make a field nullable?
What I have so far is this:
$GLOBALS['TCA']['tx_something_domain_model_delivery'] = array(
'columns' => array(
'deadline' => array(
'exclude' => 1,
'label' => 'Deadline',
'config' => array(
'dbType' => 'time',
'type' => 'input',
),
),
...
),
);
But if I enter nothing in the Deadline field, it stores "00:00:00" to the database. The database looks like this:
CREATE TABLE IF NOT EXISTS `tx_something_domain_model_delivery` (
`uid` int(11) NOT NULL,
`pid` int(11) NOT NULL DEFAULT '0',
`deadline` time DEFAULT NULL,
...
);
so deadline is nullable, but I don't know how to achieve this.
BTW: I'm a newbie at typo3, so if you think the way I create the field is retarded or there is a much better way, I'd be thankful for any suggestions.
First: You are creating the field exactly in the intended way :-)
Second: You need to use the eval property in the configuration of the field. Add the setting null to it. Your config should then look like this:
$GLOBALS['TCA']['tx_something_domain_model_delivery'] = array(
'columns' => array(
'deadline' => array(
'exclude' => 1,
'label' => 'Deadline',
'config' => array(
'dbType' => 'time',
'type' => 'input',
'eval' => 'null',
),
),
...
),
);
There are more settings for the eval setting that might be of interest to you.
This is what I have:
$server->wsdl->addComplexType('ResultObject',
'complexType',
'struct',
'all',
'',
array(
'Result' => array('name' => 'Result', 'type' => 'xsd:string'),
'Action' => array('name' => 'Action', 'type' => 'xsd:string')
)
);
And in register nusoap function output param is as below:
array('ResultObject' => 'tns:ResultObject') // output parameters
The question I have is that result is now array of 3 elements NOT xsd:string:
Continue
End
AbortReason
Each one of the above has address(xsd:anyURI), code(xsd:int). How to implement this structure as a complexType?
EDIT:
Here you can see that Abort Reason has Reason part type, and reason has its own structure.
The reason structure has code, description fields.
I want to implement this structure for the output with addComplexType
I "kickstarted" an extension with the extbase extension builder that contains some 1:1 and 1:n relations. It automatically set the field types to 'inline' and displayed a nice IRRE UI in the backend.
But by default, there is no way to select an existing record, just create new ones.
I found various explanations on how to achieve this with 'foreign_selector', but all of them very sketchy. The feature itself should be working, see https://forge.typo3.org/issues/43239
Can someone walk me through this or point to a working example in the TER? I could create a step-by-step tutorial from the example, once I get it to work.
PS The field's TCA config as generated by extension_builder:
'myfield' => array(
'exclude' => 1,
'label' => 'LLL:EXT:myextension/Resources/Private/Language/locallang_db.xlf:tx_myextension_domain_model_myitem.myfield',
'config' => array(
'type' => 'inline',
'foreign_table' => 'tx_myextension_domain_model_myfield',
'foreign_field' => 'myitem',
'maxitems' => 9999,
'appearance' => array(
'collapseAll' => 0,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => 1,
'showPossibleLocalizationRecords' => 1,
'showAllLocalizationLink' => 1
),
),
),
The main problem is that IRRE relations of type 1:n work like this: A child record holds the uid of its parent. So your table tx_myext_domain_model_city holds the UID of your (imaginary) tx_myext_domain_model_address.
Therefore with the default configuration you will not be able to select a city multiple times as it can only have exactly one parent.
So you will need to use a relation table for this field. This table needs to contain a uid field for both the address (uid_address) and the city (uid_city):
CREATE TABLE tx_irreforeignselectordemo_address_city_mm (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
uid_address int(11) unsigned DEFAULT '0' NOT NULL,
uid_city int(11) unsigned DEFAULT '0' NOT NULL,
sorting int(11) unsigned DEFAULT '0' NOT NULL,
PRIMARY KEY (uid),
KEY parent (pid)
);
And it needs to have a TCA configuration for these fields (while the table itself can be hidden):
return array(
'ctrl' => array(
'title' => 'Relation table',
'hideTable' => TRUE,
'sortby' => 'sorting',
),
'columns' => array(
'uid_address' => Array(
'label' => 'Address',
'config' => Array(
'type' => 'select',
'foreign_table' => 'tx_irreforeignselectordemo_domain_model_address',
'size' => 1,
'minitems' => 0,
'maxitems' => 1,
),
),
'uid_city' => Array(
'label' => 'City',
'config' => Array(
'type' => 'select',
'foreign_table' => 'tx_irreforeignselectordemo_domain_model_city',
'foreign_table_where' => ' AND sys_language_uid IN (0,-1)',
'size' => 1,
'minitems' => 0,
'maxitems' => 1,
),
),
),
'types' => array(
'0' => array('showitem' => 'uid_address,uid_city')
),
'palettes' => array()
);
You can then configure the TCA of your address to make it an IRRE field:
'type' => 'inline',
'foreign_table' => 'tx_yourext_address_city_mm',
'foreign_field' => 'uid_address',
'foreign_label' => 'uid_city',
'foreign_selector' => 'uid_city',
'foreign_unique' => 'uid_city',
'foreign_sortby' => 'sorting',
Note that foreign_unique tells TYPO3 that a city can only selected once.
And you need to define the relation from the other side (from your city TCA):
'addresses' => array(
'exclude' => 1,
'label' => 'Addresses',
'config' => array(
'type' => 'inline',
'foreign_table' => 'tx_irreforeignselectordemo_address_city_mm',
'foreign_field' => 'uid_city',
'foreign_label' => 'uid_address',
),
),
Once your configuration is complete, you will be able to use this in the Backend.
Since this is a non-standard MM relation, Extbase will not be able to deal with it by default. But we can compare this to the sys_file_reference table that was introduced in TYPO3 6. So we build an Extbase model for the CityRelation with the properties "address" and "city" and map this model to our mm table:
config.tx_extbase.persistence.classes {
Visol\Irreforeignselectordemo\Domain\Model\CityRelation {
mapping {
tableName = tx_irreforeignselectordemo_address_city_mm
columns {
uid_address.mapOnProperty = address
uid_city.mapOnProperty = city
}
}
}
}
Now in our address model, we define the city (or cities - of you allow more than one choice) as ObjectStorage of type CityRelation:
/**
* Cities
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Visol\Irreforeignselectordemo\Domain\Model\CityRelation>
*/
protected $cities = NULL;
We now have a property "cities" that contains the references to all selected cities. You can iterate through them and use them:
<f:for each="{address.cities}" as="cityRelation">
<li>{cityRelation.city.name}</li>
</f:for>
Since I couldn't find an all-in-one demo for this and was interested in the topic, I created a demo extension that does what I just described - based on the Core and two extensions that deal with the topic: https://github.com/lorenzulrich/irreforeignselectordemo
The solution is an m:n approach anyway (because 1:n wouldn't work for the reasons stated above) so I decided to use "cities" instead of "city". While this might not make sense for selecting a city (as suggested by your post), it might make sense for other opportunities. Feel free to replace "cities" by "city" and set maxItems in the inline configuration to one - then you have kind of an 1:n.
I've encoutered problem with filtering pages to display in WP query.
I use Codex WP query reference for custom fields with ACF (Advanced Custom Fields plugin - but it doesn't matter, couse it works same as WP custom field) parameters to filter pages.
In "Multiple Custom Field Handling" paragraph, Codex got an example with 2 conditions. We can use OR or AND relation. I works for both until you have 3rd condition (array).
They use example:
'relation' => 'OR',
array(
'key' => 'color',
'value' => 'blue',
'compare' => 'NOT LIKE'
),
array(
'key' => 'price',
'value' => array( 20, 100 ),
'type' => 'numeric',
'compare' => 'BETWEEN'
)
It has only 2 arrays. When I put 3rd, nothing shows. In debug mode I can see an error:
WordPress database error: [Lost connection to MySQL server during query]
When I use AND it works, but I got to use OR. Unfortunately it makes MySQL disconnect.
I've tryed asking phpMySQL for same query WP does. It couses problem - phpMyAdmin says same:
Lost connection
Any Idea?
Maybe I should try different aproach for filtering? (maybe I should use taxonomy?)
Here is code I use:
$query_array = array('relation' => 'OR');
array_push($query_array,
array(
'key' => 'filter1',
'value' => 'value1',
'compare' => 'LIKE'
),
array(
'key' => 'filter1',
'value' => 'value2',
'compare' => 'LIKE'
),
array(
'key' => 'filter1',
'value' => 'value3',
'compare' => 'LIKE'
)
);
$args = array(
'order' => $order_array,
'meta_key' => $meta_key,
'orderby' => $orderby,
'post_type' => 'page',
'paged' => $paged,
'post__in' => $postIDs,
'posts_per_page' => 12,
'paged' => get_query_var('paged'),
'meta_query' => $query_array
);
query_posts($args);
?>
(variables for $args are set of course)
I don't know why I can't use
'compare' => '='
but probably it is why I can't use:
$query_array = array('relation' => 'OR');
array_push($query_array,
array(
'key' => 'filter1',
'value' => array('value1', 'value2', 'value3'),
'compare' => 'IN'
)
);
Just wanted to say that your comment helped me; I'd been butting my head against a very similar problem for a while. I'm using ACF too, and using it to attach items of one post type to another custom post type was easy - for instance, to Attach Person_1 and Person_3 to "Project_A".
This made it easy to list out which users were attached to specific projects. But when it came to do the same in reverse - to show which projects were attached to which users - it became a massive headache.
I finally figured it out, in part thanks to your comment - I'll post my solution here in case someone else comes along with the same problem:
$args = array(
'numberposts' => -1,
'post_type' => 'project',
'meta_query' => array(
'relation' => 'IN',
array(
'key' => 'people',
'value' => ';s:1:"' . $person->ID . '";',
'compare' => 'LIKE'
)
)
);
In short: because the ACF values in repeater fields et cetera are serialized, the compare keyword has to be "LIKE", and I added some context to the value to eliminate false returns - just searching for an ID like "1" would match a lot of the (wrong) posts, but the ";s1;" part ensures that the given value is at index 1, which in my case is the correct index.
So it would need tweaking from case to case. Inspecting what you're trying to match up with var_dumping "get_post_meta($post->ID, 'people')" is helpful for getting the value correct.