spicywebau / craft-neo

A Matrix-like field type for Craft CMS that uses existing fields
Other
403 stars 63 forks source link

Change section and site in content migration: Neo blocks lost #877

Closed hiasl closed 2 months ago

hiasl commented 2 months ago

What question would you like to ask?

Hi We make extensive use of Neo in a setup with multiple sites and sections. Due to some changes in requirements we need to move many entries between both sites and sections at the same time. We are trying this using content migrations according to the docs (https://craftcms.com/knowledge-base/bulk-resaving-elements#content-migrations).

Below is our internal testing migration code for a single entry:


    public function safeUp()
    {
        $elementsService = Craft::$app->getElements();
        $entryQuery = Entry::find()->siteId(11)->id(118460);

        $elementsService->on(
            \craft\services\Elements::EVENT_BEFORE_RESAVE_ELEMENT,
            static function(\craft\events\BatchElementActionEvent $e) use ($entryQuery) {
                if ($e->query === $entryQuery) {
                    $element = $e->element;
                    $element->siteId = 53;
                    $element->sectionId = 36;
                }
            }
        );

        $elementsService->resaveElements(
            $entryQuery,
            true,
            true
        );

    }

After applying the migration, the entry correctly appears in the new section/site but its Neo field is missing all blocks.

Before we tried content migrations, we tested Craft's resave/entries command to move the entry, which works (including the neo blocks). However we can only change one property (in our tests the site) at a time, so we first need to enable the source section in the target site because we could not change it at the same time, which is not handy and we actually also do not want to go the manual way through commands but stay with content migrations instead.

We assume that the problem is caused by our approach not correctly changing the elements2sites (elements_sites table) relation in the content migration. Is there a trick to get the resave command or Neo to do this?

Versions:

Thanks, Matthias

ttempleton commented 2 months ago

As well as updating the elements_sites table, you will also need to change the site ID for the relevant Neo block structure(s). Since you're running Craft 3, this will involve updating the ownerSiteId value for the relevant rows in the neoblockstructures table (on Craft 4 and later, the column is just called siteId).

Both of these could be achieved using code based on the following as part of your event handling code:

$blockIds = \benf\neo\elements\Block::find()
  ->ownerId($element->id)
  ->siteId(11)
  ->ids();

$this->update(
  '{{%elements_sites}}',
  [
    'siteId' => 53,
  ],
  [
    'siteId' => 11,
    'elementId' => $blockIds,
  ]
);

$this->update(
  '{{%neoblockstructures}}',
  [
    'ownerSiteId' => 53,
  ],
  [
    'ownerSiteId' => 11,
    'ownerId' => $element->id,
  ]
);

Please let me know if you have any issues while making these changes.

hiasl commented 2 months ago

Thanks for your answer @ttempleton !

1.) I tried it, both table rows in elements_sites and neoblockstructures are updated in the way I would expected it, but still the Neo blocks do not appear when I edit the owner entry in the CMS. The Neo field does not contain any blocks in edit mode. 2.) And because I would like to understand it: Why does moving the blocks work with Neo if I resave via Craft's console command? Isn't this the same as the resave service I use in the migration?

Below is the adapted content migration with your code, slightly modified with $this / $thisClone:

    public function safeUp()
    {
        $elementsService = Craft::$app->getElements();
        $entryQuery = Entry::find()->siteId(11)->id(118464); //
        $thisClone = $this;

        $elementsService->on(
            \craft\services\Elements::EVENT_BEFORE_RESAVE_ELEMENT,
            static function(\craft\events\BatchElementActionEvent $e) use ($entryQuery, $thisClone) {
                if ($e->query === $entryQuery) {
                    $element = $e->element;
                    $element->siteId = 53;
                    $element->sectionId = 36;

                    $blockIds = \benf\neo\elements\Block::find()
                        ->ownerId($element->id)
                        ->siteId(11)
                        ->ids();

                    $thisClone->update(
                        '{{%elements_sites}}',
                        [
                            'siteId' => 53,
                        ],
                        [
                            'siteId' => 11,
                            'elementId' => $blockIds,
                        ]
                    );

                    $thisClone->update(
                        '{{%neoblockstructures}}',
                        [
                            'ownerSiteId' => 53,
                        ],
                        [
                            'ownerSiteId' => 11,
                            'ownerId' => $element->id,
                        ]
                    );
                }
            }
        );

        $elementsService->resaveElements(
            $entryQuery,
            true,
            true
        );
    }

There must be something else which needs to be updated. How does the resave console command make it work? Our Neo blocks are nested, does this cause any additional problems?

Thanks!

ttempleton commented 2 months ago

Whoops, I forgot about needing to update the content table as well:

$thisClone->update(
  '{{%content}}',
  [
    'siteId' => 53,
  ],
  [
    'siteId' => 11,
    'elementId' => $blockIds,
  ]
);

Regarding the resave/entries console command - it does also internally use $elementsService->resaveElements(), but it did not move over any Neo blocks in my testing (or any Matrix blocks, for that matter), so I'm not sure why it worked in your case.

hiasl commented 2 months ago

Thanks again, this is working now. We also needed to move Matrix in NEO fields as well, but since we had your code, that was easy.

Btw: the resave/entries console command does move NEO blocks and Matrix as well, this would have been our fallback. But since it needs to be done manually, we prefer the migration.