Closed jessedobbelaere closed 6 months ago
Did some debugging 🕵️
To repeat the problem: when I load the Formie form on my secondary site, it does not set a default value (which I populated using craft.formie.populateFormValues
) in my nested entries field in a Repeater. 🤔
In the query that fetches the default value (prepopulated), it filters incorrectly by siteId=1
, because we're browsing the secondary website (siteId
should be 2).
SELECT `elements`.`id`, `elements`.`canonicalId`, `elements`.`fieldLayoutId`, `elements`.`uid`, `elements`.`enabled`, `elements`.`archived`, `elements`.`dateLastMerged`, `elements`.`dateCreated`, `elements`.`dateUpdated`, `elements_sites`.`id` AS `siteSettingsId`, `elements_sites`.`slug`, `elements_sites`.`siteId`, `elements_sites`.`uri`, `elements_sites`.`enabled` AS `enabledForSite`, `entries`.`sectionId`, `entries`.`typeId`, `entries`.`authorId`, `entries`.`postDate`, `entries`.`expiryDate`, `content`.`id` AS `contentId`, `content`.`title`, `structureelements`.`root`, `structureelements`.`lft`, `structureelements`.`rgt`, `structureelements`.`level`, `structureelements`.`structureId`
FROM (SELECT `elements`.`id` AS `elementsId`, `elements_sites`.`id` AS `elementsSitesId`, `content`.`id` AS `contentId`, `structureelements`.`structureId`
FROM `elements` `elements`
INNER JOIN `entries` `entries` ON `entries`.`id` = `elements`.`id`
INNER JOIN `elements_sites` `elements_sites` ON `elements_sites`.`elementId` = `elements`.`id`
INNER JOIN `content` `content` ON (`content`.`elementId` = `elements`.`id`) AND (`content`.`siteId` = `elements_sites`.`siteId`)
LEFT JOIN `structureelements` `structureelements` ON (`structureelements`.`elementId` = `elements`.`id`) AND (EXISTS (SELECT *
FROM `structures` use index(primary)
WHERE (`id` = `structureelements`.`structureId`) AND (`dateDeleted` IS NULL)))
WHERE (`elements_sites`.`siteId`=1) AND (`elements`.`id`=6) AND (((`elements`.`enabled`=TRUE) AND (`elements_sites`.`enabled`=TRUE)) AND (`entries`.`postDate` <= '2023-12-23 01:01:59') AND ((`entries`.`expiryDate` IS NULL) OR (`entries`.`expiryDate` > '2023-12-23 01:01:59'))) AND (`elements`.`archived`=FALSE) AND (`elements`.`dateDeleted` IS NULL) AND (`elements`.`draftId` IS NULL) AND (`elements`.`revisionId` IS NULL)
ORDER BY FIELD(`elements`.`id`,2372)
LIMIT 1) `subquery`
INNER JOIN `elements` `elements` ON `elements`.`id` = `subquery`.`elementsId`
INNER JOIN `elements_sites` `elements_sites` ON `elements_sites`.`id` = `subquery`.`elementsSitesId`
INNER JOIN `entries` `entries` ON `entries`.`id` = `subquery`.`elementsId`
INNER JOIN `content` `content` ON `content`.`id` = `subquery`.`contentId`
LEFT JOIN `structureelements` `structureelements` ON (`structureelements`.`elementId` = `subquery`.`elementsId`) AND (`structureelements`.`structureId` = `subquery`.`structureId`)
ORDER BY FIELD(`elements`.`id`,2372)
However, the dropdown field shows all options correctly in the dropdown ✅ The query that fetches the options, correctly uses siteId=2
SELECT `elements`.`id`, `elements`.`canonicalId`, `elements`.`fieldLayoutId`, `elements`.`uid`, `elements`.`enabled`, `elements`.`archived`, `elements`.`dateLastMerged`, `elements`.`dateCreated`, `elements`.`dateUpdated`, `elements_sites`.`id` AS `siteSettingsId`, `elements_sites`.`slug`, `elements_sites`.`siteId`, `elements_sites`.`uri`, `elements_sites`.`enabled` AS `enabledForSite`, `entries`.`sectionId`, `entries`.`typeId`, `entries`.`authorId`, `entries`.`postDate`, `entries`.`expiryDate`, `content`.`id` AS `contentId`, `content`.`title`, `structureelements`.`root`, `structureelements`.`lft`, `structureelements`.`rgt`, `structureelements`.`level`, `structureelements`.`structureId`
FROM (SELECT `elements`.`id` AS `elementsId`, `elements_sites`.`id` AS `elementsSitesId`, `content`.`id` AS `contentId`, `structureelements`.`structureId`
FROM `elements` `elements`
INNER JOIN `entries` `entries` ON `entries`.`id` = `elements`.`id`
INNER JOIN `elements_sites` `elements_sites` ON `elements_sites`.`elementId` = `elements`.`id`
INNER JOIN `content` `content` ON (`content`.`elementId` = `elements`.`id`) AND (`content`.`siteId` = `elements_sites`.`siteId`)
LEFT JOIN `structureelements` `structureelements` ON (`structureelements`.`elementId` = `elements`.`id`) AND (EXISTS (SELECT *
FROM `structures` use index(primary)
WHERE (`id` = `structureelements`.`structureId`) AND (`dateDeleted` IS NULL)))
WHERE (`elements_sites`.`siteId`=2) AND (((`elements`.`enabled`=TRUE) AND (`elements_sites`.`enabled`=TRUE)) AND (`entries`.`postDate` <= '2023-12-23 01:01:59') AND ((`entries`.`expiryDate` IS NULL) OR (`entries`.`expiryDate` > '2023-12-23 01:01:59'))) AND (`elements`.`archived`=FALSE) AND (`elements`.`dateDeleted` IS NULL) AND (`elements`.`draftId` IS NULL) AND (`elements`.`revisionId` IS NULL)
ORDER BY `content`.`title`) `subquery`
INNER JOIN `elements` `elements` ON `elements`.`id` = `subquery`.`elementsId`
INNER JOIN `elements_sites` `elements_sites` ON `elements_sites`.`id` = `subquery`.`elementsSitesId`
INNER JOIN `entries` `entries` ON `entries`.`id` = `subquery`.`elementsId`
INNER JOIN `content` `content` ON `content`.`id` = `subquery`.`contentId`
LEFT JOIN `structureelements` `structureelements` ON (`structureelements`.`elementId` = `subquery`.`elementsId`) AND (`structureelements`.`structureId` = `subquery`.`structureId`)
ORDER BY `content`.`title`
Digging in the Formie code, I could see that:
Code that sets the Default value:
https://github.com/verbb/formie/blob/2.0.44.1/src/fields/formfields/Repeater.php#L182
Here, inside the populateValue
function, an array of $blocks
is created. Each block is a new NestedFieldRow
object, which always has siteId = 1
set 🔴. This is the main problem. It should be siteId=2
because that's the website we're currently browsing...
https://github.com/craftcms/cms/blob/4.5.13/src/base/Element.php#L2241-L2247
The value of siteId=1
gets set by Craft CMS. NestedFieldRow extends Element
, and in the Element.php
class, it always takes the primary site ID.
Code that retrieves the Dropdown options
https://github.com/verbb/formie/blob/2.0.44.1/src/fields/formfields/Entries.php#L216-L219
The options of a dropdown field are populated by getElementsQuery
which Formie explicitly restricts to the current site ID 👍
❓ Should Formie manually override the NestedFieldRow->siteId
just like it does in the dropdown getElementsQuery
? It feels wrong that the Repeater has NestedFieldRow
with siteId=1
on them, when I'm browsing site 2.
I have no prior experience with the Formie core, but I suppose something like this could do the job:
// src/fields/formfields/Repeater.php
public function populateValue($value): void
{
if (!is_array($value) || !isset($value[0])) {
return;
}
$blocks = [];
foreach ($value as $i => $fieldContent) {
try {
$row = new NestedFieldRow();
$row->fieldId = $this->id;
$row->setFieldValues($fieldContent);
+ if (Craft::$app->getIsMultiSite()) {
+ $row->siteId = Craft::$app->getSites()->getCurrentSite()->id;
+ }
$blocks[] = $row;
} catch (Throwable $e) {
continue;
}
}
if ($blocks) {
$this->defaultValue = new NestedFieldRowQuery(NestedFieldRow::class);
$this->defaultValue->setBlocks($blocks);
}
}
Adding this in my local setup seems to fix the issue. The value prepopulates nicely.
Great investigation here, and that's a very good point - something I hadn't considered. Appreciate your debugging this one!
Fixed for the next release. To get this early, run composer require verbb/formie:"dev-craft-4 as 2.0.44.1"
.
Fixed in 2.0.45
Describe the bug
I'm having trouble getting an entries field to prepopulate, inside a repeater field, on a secondary (multi-site) website 😅 I first thought that it was related to #1190, but it seems to be more complex than that.
I have a basic multi-site setup, with a primary and secondary website. I have a section
products
that is only enabled on the secondary site. I have a form with one repeater field with one nested entries field inside. And a standalone entries field below the repeater field (for debugging). I observed that:craft.formie.populateFormValues
to populate the default value of both the entries field. Only the standalone entries field displays the right default value. The nested field does not display a default value.Steps to reproduce
Using these steps, I can reproduce the bug in a new project:
Install a fresh craft cms
Add another hostname for the secondary site: modify
.ddev/config.yaml
and specify:And restart ddev:
ddev restart
Visit the admin panel: https://craftcmsprimary.ddev.site/admin and go to settings -> sites and create a secondary site. Fill in base url:
https://craftcmssecondary.ddev.site
.Create a section
Pages
(enabled for both sites) and create some dummy entriesCreate a section
Products
(enabled only for secondary site) and create two entriesddev composer require verbb/formie -w && ddev exec php craft plugin/install formie
entriesField
(keep all defaults, all sections are checked) and a number fieldquantityField
. Add a standalone entries field below the repeater (keep all defaults):entriesFieldTwo
.templates/test.twig
We populate using entries ID = 6 which the first product entry, in both entries fields.
We can see that only the standalone entries field gets set with a default, but not the nested entries field.
I assume that the repeater field population has some difficulty with entries that are exclusive to a secondary site? The entry with ID=6 is part of the dropdown values, but setting it as a default does not work?
Form settings
Craft CMS version
4.5.13
Plugin version
2.0.44.1
Multi-site?
Yes
Additional context
Section with entries is only available to secondary site