Move existing pages into newly created pages with TYPO3 DataHandler - typo3

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

Related

Localize records and update with DataHandler

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.

Symfony (3.2)'s ChoiceType - flipping key/values?

I've been wrecking my brain, going from 2.7.x docs all up onto 3.2 which I am on and I fail to see why they removed choices_as_values. It is exactly what I require.
At this time I've got an input (don't ask why) with 3 values, 2 of which have the same name/label. It's what the customer wants and after much discussing I simply had to agree. So, i.e. I've got the following:
1 => Name
2 => Name
3 => Other Name
However, Symfony wants it received like this:
Name => 1
Name => 2
Other Name => 3
You can see the problem, the resulting array would become:
Name => 2
Other Name => 3
I'd be missing ONE value. So, what is the right way to go about this? I've tried numerous solutions, none of them worked. Keep in mind that the resulting value after submit must still be 1, 2 or 3.
The reason for flipping was that PHP only allows integers and strings to be array keys while actual choice values could be arbitrary PHP types. Usually, labels however are unique as duplicate keys will likely confuse users so it's easy to have them as the keys of the choices option value.
Anyway, if you need labels to be duplicated, you can pass a callback to the choice_label that will return the label to be displayed:
$builder->add(ChoiceType::class, null, [
'choices' => [
1,
2,
3,
],
'choice_label' => function ($value) {
switch ($value) {
case 1:
return 'Name';
case 2:
return 'Name';
case 3:
return 'Other Name';
default:
return '';
}
},
]);

Edit form dynamically through subscriber

// buildForm
...
->add('book', 'entity', [
'class' => 'MyBundle\Entity\Book',
'choices' => [],
])
->addEventSubscriber(new MySubscriber());
The field book gets filled through javascript and gets the title of the book.
What I need to do is check if the book already exists in my db, otherwise I create it. I created a subscriber for that works well.
The problem is that I couldn't get rid of the error emitted by $form->handleRequest($request)->isValid(), Which is weird because I edited data in the request this way in my subscriber:
public function preSetData(FormEvent $event)
{
...
$author = $event->getData();
$requestForm = $this->request->request->get('mybundle_author');
$bookTitle = $requestForm['book'];
// if this book title doesn't exist -> create it
...
$requestForm['book] = (string) $book->getId();
$this->request->request->set('mybundle_author', $requestForm);
}
No matter what FormEvents I used, it emits the error that book value is not valid
I crossed a similar problem with the entity type.
The problem is that the new Entity is not marked as managed, and the entity type is focused on selecting existing entities. You could either pass the ObjectManager to the subscriber and set the entity as managed (with persist), or get rid of the validation error yourself. The latter is cleaner, but may require more work.
Removing the option choices fixed the problem.
My subscriber is correct but in my form I had to edit the field
// buildForm
...
->add('book', 'entity', [
'class' => 'MyBundle\Entity\Book',
//'choices' => [], // removing this fixed the problem
])
->addEventSubscriber(new MySubscriber());

Perl Catalyst: Resultset and Relations

I have two tables in my database and one of the tables is associated with my Accounts table.
So in my Schema Result for Account.pm I added the following line.
__PACKAGE__->has_many('subjects', 'MyApp::DBIC::Schema::Subject', {'foreight.account_id' => 'self.account_id'});
Then in my controller I make a search like this.
$c->stash->{search_results} = $c->model('DB::Account')->search(
{ -or => [
firstname => {like => '%'.$search_term.'%'},
'subjects.subject_title' => {like => '%'.$search_term.'%'},
]
},
{
join => 'subjects',
rows => '3',
},
{
order_by => 'first name ASC',
page => 1,
rows => 10,
}
);
It does not output any errors, but I can't figure out how to output the results on my view file. Is this a correct method of making relations between two tables?
My goal: provided a search_term, search two tables and output the result in view file. My SQL would look something like this:
SELECT FROM Accounts,Subjects WHERE Accounts.firstname=$search_term OR Subjects.subject_title=$search_term LEFT JOIN Subjects ON Accounts.account_id=Subject.account_id
And would want to output the result in view file, as I stated above.
I am fairly new to Perl and some of the documentations don't make that much sense to me, still. So any help and tips are appreciated.
The join looks OK to me, but it would make sense to try a simplified version without the join to check that everything else is OK.
The behaviour of DBIx::Class::ResultSet::search differs depending on the context in which it's called. If it's called in list context then it executes the database query and returns an array of MyApp::DBIC::Schema::Account objects. For example:
my #accounts = $c->model('DB::Account')->search();
In your case you're calling search in scalar context, which means that rather than returning an array it will return a DBIx::Class::ResultSet object (or a subclass thereof), and crucially it won't actually execute a db query. For that to happen you need to call the all method on your resultset. So, assuming you're using the default template toolkit view you probably want something like this:
[% FOREACH search_result IN search_results.all %]
[% search_result.first_name %]
[% END %]
This 'lazy' behaviour of DBIx::Class is actually very useful, and in my opinion somewhat undersold in the documentation. It means you can keep a resultset in a variable and keep executing different search calls on it without actually hitting the DB, it can allow much nicer code in cases where you want to conditionally build up a complex query. See the DBIx::Class::Resultset documentation for further details.
You have error in your query:
Try:
$c->stash->{search_results} = $c->model('DB::Account')->search(
{ -or => [
firstname => {like => '%'.$search_term.'%'},
'subjects.subject_title' => {like => '%'.$search_term.'%'},
]
},
{
join => 'subjects',
order_by => 'firstname ASC',
page => 1,
rows => 10,
}
);

Symfony2 entityaudit: Add field on revision table

I have 2 entities in my bundle that are audited via simplethings/entity-audit.
I would like to add a field to REVISIONS table call "reason". Every time a user updates or delete an entity, he/she needs to especicify an reason for doing that (why updating/deleting) via form, and this reason should be associated to the entity revision.
How would you guys do it? I dont have much experience in OOP.
Thank you very much in advance.
for adding field you need to add field in your database like 'ip' next you change your bundle in file "logRevisionsListener.php"
private function getRevisionId()
{
if ($this->revisionId === null) {
$this->conn->insert($this->config->getRevisionTableName(), array(
'timestamp' => date_create('now'),
'username' => $this->config->getCurrentUsername(),
'ip' => $this->config->getCurrentUsername(),(not correct just for test it give me the user name)
), array(
Type::DATETIME,
Type::STRING,
Type::STRING
));
.
.
}
I added here the ip field and change your Revision.php file by adding your field with the getter method