Localize records and update with DataHandler - typo3

It is possible to localize records by using the "localize" command of the DataHandler
$cmd[self::TABLE_NAME] = [
uid_of_the_original_record => [
'localize' => language_uid,
]
];
$this->dataHandler->start([], $cmd);
$this->dataHandler->process_cmdmap();
This correctly adds a translated record to the DB, but with the copied strings of the original record. How is it possible to edit the properties of the localized record? By using the update command afterwards? If so, how do I get the uid of the added localization?

You can actually create localizations using the data map instead:
$data[self::TABLE_NAME] = [
'NEW123' => [
'sys_language_uid' => <language-uid>,
'l10n_parent' => <original-record-uid>, // Optionally also l10n_source
// Other fields
],
];
$this->dataHandler->start($data, []);
$this->dataHandler->process_datamap();
This way you can directly set other fields when creating a localization.

Currently it's not possible to run localize and put the translated content in there at the same time.
There are two options I can see:
Get all translated elements (by the original UID you have) and set the translated content in each of them afterwards
Hook into processCmdmap_postProcess or processCmdmap_afterFinish and put the translated content into it
I had the same issue with an importer and used option 1.

Related

Move existing pages into newly created pages with TYPO3 DataHandler

I am using the DataHandler to create and move pages like in this snippet. While new pages are created fine, existing subpages are not moved into their newly created parents.
This creates the new page but does not move the existing page
$data = [
'pages' => [
'NEW_IT' => [
'pid' => 1,
'hidden' => false,
'title' => 'IT',
],
591 => [
// this is not set
'pid' => 'NEW_IT',
// but this is set
'title' => 'I am a child of IT',
],
]
];
I tried ['pages'][591]['move'] = 'NEW_IT' but also to no avail.
I also tried '591' instead of 591, dataHandler->reverseOrder = true and dataHandler->copyTree = true.
dataHandler->errorLog is empty.
In contrary, this works (new page into new page)
$data = [
'pages' => [
'NEW_IT' => [
'pid' => 1,
'hidden' => false,
'title' => 'IT',
],
'NEW_IT_SUB' => [
'pid' => 'NEW_IT',
],
]
];
Also I wonder which IDs (NEW<any string> vs. NEW<base64> etc.) are acceptable as I did not find anything in the documentation and the examples use different styles. The "must be unique" is obvious. But I don't get why some people generate UUIDs there.
References
https://docs.typo3.org/m/typo3/reference-coreapi/9.5/en-us/ApiOverview/Typo3CoreEngine/Database/Index.html
EDIT: I opened a forge ticket: https://forge.typo3.org/issues/90939
I have checked the code / DataHandler in v8/v9 and master. The corresponding logic in the DataHandler has not changed for that.
I have created a test case and xdebugged through it, also i was pretty sure after looking into the code, what happens. But have done this to validate that "it is working the way i think the code tells" - although it is not the way you expected. And I'm too. Remembering that I had such an issue last year through a migration script, but did not digged deeper into it (because of time), i changed it and made this as loops.
Creating page => retrieving the replaced id => using it for the other data commands.
The datahandler loops through each record for a table in the provided dataset array.
foreach ($this->datamap[$table] as $id => $incomingFieldArray) { ... }
First it prepares some stuff, and calling registered processDatamap_preProcessFieldArray hooks, it checks the given $id
// Is it a new record? (Then Id is a string)
if (!MathUtility::canBeInterpretedAsInteger($id)) { ... } else { ... }
If the id is not an integer or an integer string, it executed the true-branch, otherwise the false-branch. So long, so good.
The TRUE-Branch handles creating a new record (page, tt_content, ...), adding the replacement for the provided NEWxxx as substituion and so on.
It also checks, if a 'pid' field is in the record, and if it contains with NEW. If it is so, it replaces NEWxxx with the uid of the previously created record.
On the otherhand, if $id is not a integer or a integer string, it assumes it is an existing record. (FALSE-Branch). On this branch there is no checking for 'pid', if it contains NEW and looking up for replacement.
But .. why is there no error ? If it did not replace it, it should crash or someting like that ? - Yes, that is what we would assume - at least for that.
'pid' is always ignored in appling to the record ( see method fillInFieldArray() )
switch ($field) {
case 'uid':
case 'pid':
// Nothing happens, already set
break;
...
}
and so .. as long as you do not provide further fields/values in the second page array ( that with the integer id and the pid with NEW from the initial), it has nothing to do. Nothing to do is not an error - and so, it do nothing and even do not notify anything about it.
Is it a bug ? Don't know, personally I would say yes. But maybe this should be created as an issue, and eventually discussed with the core developer/other contributers.
Maybe there were/are reasons WHY it only do this one run.
Either the documentation should be changed to make clear, that 'pid' replacement only works, id $id => [] is also a new / playholder id .. or you have to do it in 2 rounds.
Or .. put as as feature/bug to the issue tracker, and let's discuss it. And maybe giv it a try to implement it in the FALSE/existing record branch too. But let's heare some other opionons about it.
If others thing this should be also be done for the existing records in the same round, I would take the time tommorrow/at the weekend to provide an patch for it / the issue. (Would be fun to fight with the datahandler ).

How to create inline-records (IRRE) using DataHandler in TYPO3?

In our case we have a table A which contains IRRE records using table B. Inside a backend module we import an XML file to import those records for table B.
All records/data for table A is available.
All data for table B is available, except the new uids/identifiers.
Based on https://docs.typo3.org/typo3cms/CoreApiReference/6.2/ApiOverview/Typo3CoreEngine/Database/ I have to set the identifier NEWxxxx for all new created records.
I'm importing a large number of records at once. Can I generate those identifiers in a loop and process all records at once or do I have to run the whole datamap-handling record by record ?
Besides the identifier, is there any field i have to set on the parent record which contains the IRRE record ?
No translations/workspaces/other relations are involved.
Thanks for your help.
The DataHandler in TYPO3 is using the following array structure to create new or update existing records - this is valid up to and including TYPO3 CMS 8:
$dataMap = ['<table-name>' => [
'<record-uid>' => ['<field-name>' => '<field-value>']
];
Existing records use the integer value of the record's uid field, e.g. 123, new records use some random but unique identifier that are prefixed with NEW, e.g. NEWa2b3c4f8 created by uniqid('NEW', true) - since TYPO3 CMS 7 StringUtility::getUniqueId('NEW') can and should be used for that.
Generic Example
Let's assume the following records shall be created:
a new content element in table tt_content
two new inline file references for table sys_file_reference for field tt_content.image
referencing to existing sys_file record with uid 123
referencing to existing sys_file record with uid 234
// generating unique identifiers for records to be created
$ttContentId = 'NEW58d5079c8741c822627844'; // StringUtility::getUniqueId('NEW')
$fileRefId1st = 'NEW58d506f3cd0c4159344142'; // StringUtility::getUniqueId('NEW')
$fileRefId2nd = 'NEW58d50714c1226092562338'; // StringUtility::getUniqueId('NEW')
Preparing data-map
Hava a close look to tt_content.image, this is actually defining the (new) inline references, defined by a comma separated values of new records or existing records - this could either be NEWabc,NEWdef, 123,234,345 or NEWabc,123,NEWdef, mixing new and existing record references.
$dataMap = [
'tt_content' => [
'NEW58d5079c8741c822627844' => [
'title' => 'My new content element',
'bodytext' => 'Look at the following images...',
'CType' => 'textpic',
// $fileRefId1st & $fileRefId2nd, the sorting order is defined by this as well
'image' => 'NEW58d506f3cd0c4159344142,NEW58d50714c1226092562338',
],
],
'sys_file_reference' => [
'NEW58d506f3cd0c4159344142' => [
'uid_local' => 123,
'title' => 'Image #123',
],
'NEW58d50714c1226092562338' => [
'uid_local' => 234,
'title' => 'Image #234',
],
]
];
Preparing command-map
// the command-maps is similar to the data-map to copy, localize, move records
// however, it's not required in this scenario and thus stays empty
$commandMap = [];
Executing DataHandler
$dataHandler = new \TYPO3\CMS\Core\DataHandling\DataHandler();
$dataHandler->start($dataMap, $commandMap);
$dataHandler->process_datamap();
// $dataHandler->process_cmdmap(); // if $commandMap should be processed as well
If you need the uid of the created records, this can be resolved from the internal DataHandler record mapping. For example, the following code resolves the new uid of the created tt_content record:
// fetching the actual record ID, e.g. results in 333
$ttContentId = $dataHandler->substNEWwithIDs['NEW58d5079c8741c822627844'];
Notes
Defining the references happens in the example above directly for the field tt_content.image, which can contain NEW... ids as well as existing integer ids. The behaviour for the is the same for all reference types in TYPO3:
TCA type inline, for all variants (plain, foreign_field, MM)
TCA type select, for all variants (plain, MM)
TCA type group, for all variants (plain, MM)
Passing data through DataHandler ensures that log entries are created, and the in most cases modifications can be reverted using TYPO3's history/rollback module.
Besides that, it's possible to execute mass actions - the invocation of DataHandler is not limited to just on aggregate (the tt_content record in the example above). However, the NEW... ids have to be unique and must not be re-used during mass-executions to avoid side-effects.
Transformed to table_a & table_b scenario
Transforming this to the table_a and table_b scenario of the initial question, the $dataMap might look like the following. Of course you have to determine which references to table_b are bound to table_a.
$dataMap = [
// existing records of table_a, thus using the real ids
'table_a' => [
'11' => [ 'reference_field' => 'NEWb1,NEWb2' ],
'22' => [ 'reference_field' => 'NEWb3,NEWb4' ],
'33' => [ 'reference_field' => 'NEWb5,NEWb6' ],
],
// new records to be references for table_b, thus using NEW... ids
'table_b' => [
'NEWb1' => [ ... field values of this particular table_b record ... ],
'NEWb2' => [ ... field values of this particular table_b record ... ],
'NEWb3' => [ ... field values of this particular table_b record ... ],
'NEWb4' => [ ... field values of this particular table_b record ... ],
'NEWb5' => [ ... field values of this particular table_b record ... ],
'NEWb6' => [ ... field values of this particular table_b record ... ],
],
];
Notes on identifiers
Identifiers like NEWb1 are kept simple intentionally - usually those identifiers are composed by prefix NEW and a (pseudo-)random hexadecimal string abdc....
The TYPO3 core is using $id = StringUtility::getUniqueId('NEW') to create those unique identifiers. However, that also can be achieved using $id = 'NEW' . bin2hex(random_bytes(10); - identifiers just have to be unique for this particular process.

How to use TCA Placeholder from parent records

Can anyone give me a hint how to use the TCA placeholder stuff correctly?
I got the example about sys_file_reference here, but it doesn't work if I adapt it onto my TCA table configuration
My config looks something like this:
tx_epproducts_domain_model_product => [
ctrl ...
columns => [
parent => [
label => ...
config => [
type => select
foreign_table => tx_epproducts_domain_model_product
foreign_table_where => AND tx_epproducts_domain_model_product.uid != ###THIS_UID###
]
],
name => [
label => ...
config => [
type => input
placeholder => __row|parent|name
mode => useOrOverridePlaceholder
]
]
]
]
I thought I had to write __row to reference the current record, then parent to reference the parent record by the value inside the current records parent field and then the field where the placeholder text is located, but this isn't working at all.
I even don't get the checkbox to override the value like in the sys_file_reference inline relations
EDIT:
I forgot to mention that I want to use this in columnsOverrides section of another type of the record.
Sadly, this configuration is not fixable, yet. The configuration works for the columns section but fails to work in the columnsOverrides section of the type. This probably has to do with this bug/feature: https://forge.typo3.org/issues/76671
Long story short: placholder configuration is possible, but not via columnsOverrides in another record type configuration.

How to unset field from Document

I have one document in which I want to remove one field. I got answer from Here. But, It is for core PHP code and I want in CakePHP how to unset field from document.
I tried following code in CakePHP.
$this->MyModal->id = $id;
$this->MyModal->updateAll(
array('myField' => array('$exists' => true)),
array('unset' => array('myField' => true))
);
Edit
I also tried
$this->MyModal->updateAll(
array('$unset' => array('myField' => 1))
);
But it does not work.How can unset field?
It looks like you're using wrong the update all method. First you must provide an array with the new values you want for your fields and then you should pass an array with the search criteria. So in order for it to work you could write something like
$conditions=array('myField'=>array('$exists'=>true));
$this->MyModal->updateAll(
array('$unset'=>array('myfield'=>1)),
$conditions//array of search conditions
)

SugarCRM restriction on custom field

i'm totally new to SugarCRM module development, however i have very good knowledge of PHP, ajax and database programming.
Here is my task:I have been asked to create a restriction on a custom field from the clients module.
There is a custom field called identification number, what i need to do is avoid a new client to be saved into the database based on that field, in other words the client has to be unique. It has to display a pop up window saying "that client already exists"
Copy editviewdefs.php of Accounts to the custom folder and change the custom field definition for id_number to this
array(
'name' => 'id_number',
'displayParams' =>
array (
'field' =>
array (
'onChange' => 'check_is_duplicate(this);',
),
),
),
Create a javascript function check_is_duplicate
function check_is_duplicate(obj) {
// Call a script via Ajax. Pass values for id and id_number with the request.
if (o.responseText > 0) {
alert('duplicate');
document.getElementById('SAVE').disabled = true;
} else {
document.getElementById('SAVE').disabled = false;
}
}
You would have to create the script which would be called via ajax request.
In that script you would have to run a query like -
SELECT COUNT(*) AS count FROM accounts
WHERE deleted = 0 AND id != {$record} AND id_number = {$id_number}
Execute the query and return the count.
Afterwards on saving check for duplicates from server side using the beforeSave logic hook.
You can create a custom duplicate check by the following code:
$dictionary['Account']['duplicate_check']['FilterDuplicateCheck']['filter_template'] = array(
array(
'$and' => array(
array('identification_number' => array('$equals' => '$identification_number')),
),
),
);
Add this code in the file: /custom/Extension/modules/Accounts/Ext/Vardefs/duplicate_check.php.
Then, perform a quick repair/rebuild.
How it works:
When you enter the identification number and press save, SugarCRM will perform a duplicate check. If duplicates are found, SugarCRM will list the duplicates and you can choose to use the duplicate or ignore.