Description
NB: this is closely related to #1988
Short description of the issue
When a page has a PageTable field and it's joined with e.g. $pages->get($selector, ['loadOptions' => ['joinFields' => ['c_ref']]])
or $pages->find($selector, ['loadOptions' => ['joinFields' => ['c_ref']]])
(as opposed to id=123, field=c_ref
), the resulting $p->c_ref
is empty when the field contains more than one page.
Expected behavior
Adding joinFields
should not affect the result, only performance.
Actual behavior
If there are more than one subpage, they are not fetched.
Screenshots/Links that demonstrate the issue
Possible cause: PageValues.php tries to explode
the value, but only does it if the GROUP_CONCAT
separator is seen in the results.
Issue #1988 was fixed by making sure that $value
is an array.
I believe the correct way to fix this would be to avoid searching for separators in the string, and instead make sure that we always explode
after GROUP_CONCAT
. But it seems to me that instead we could just change the earlier FieldtypePageTable.php
fix like this:
-if($value && !is_array($value)) $value = array($value);
+if($value && !is_array($value)) $value = explode(FieldtypeMulti::multiValueSeparator, $value);
Steps to reproduce the issue
The script below creates a parent and child template, p
and c
, and a field c_ref
in the p
template. Then it adds these pages:
Parent: parent-a
Child: child-a1
Child: child-a2
Parent: parent-b
Child: child-b1
If run without GET params, it fetches all the pages and prints the above output. If run with ?join
, only the lone child child-b1
is printed.
Setup/Environment
- ProcessWire version: latest dev
- (Optional) PHP version: 8.4
The script:
<?php
$processwirePath = '/var/www/html/';
include($processwirePath . 'index.php');
header('Content-Type: text/plain');
////// Install fixture templates, field and pages.
////// First, clean up previous run (if any).
// Remove pages with template 'p' or 'c'.
$pagesToRemove = $pages->find("template=p|c");
foreach ($pagesToRemove as $page) $pages->delete($page, true);
$pagesToRemove = $pages->find("template=p|c");
foreach ($pagesToRemove as $page) $pages->delete($page, true);
// Remove templates 'p' and 'c'.
$pTemplate = $templates->get('p');
if ($pTemplate) $templates->delete($pTemplate);
$cTemplate = $templates->get('c');
if ($cTemplate) $templates->delete($cTemplate);
// Remove field 'c_ref' if it exists.
$field = $fields->get('c_ref');
if ($field) $fields->delete($field);
// Create templates and field.
$pTemplate = $templates->add('p');
$pTemplate->save();
$cTemplate = $templates->add('c');
$cTemplate->save();
$field = $fields->makeItem();
$field->name = 'c_ref';
$field->type = 'PageTable';
$field->label = "c_ref";
$field->template_id = [$cTemplate->id];
$field->inputfield = "InputfieldPageTable";
$field->parent_id = 1;
$field->save();
$pTemplate->fields->add($field);
$pTemplate->save();
// Create pages.
$parent_a = $pages->add('p', '/', 'parent-a');
$child_a1 = $pages->add('c', '/', 'child-a1');
$parent_a->c_ref->add($child_a1);
$parent_a->save();
$child_a2 = $pages->add('c', '/', 'child-a2');
$parent_a->c_ref->add($child_a2);
$parent_a->save();
$parent_b = $pages->add('p', '/', 'parent-b');
$child_b1 = $pages->add('c', '/', 'child-b1');
$parent_b->c_ref->add($child_b1);
$parent_b->save();
//////// Now everything is set up.
$options = isset($_GET['join']) ? ['loadOptions' => ['joinFields' => ['c_ref']]] : [];
echo "--- get() ---\n";
foreach (['parent-a', 'parent-b'] as $parentName) {
$parent = $wire->pages->get("name=$parentName", $options);
echo "Parent: $parentName\n";
foreach ($parent->c_ref as $child) {
echo " Child: " . $child->name . "\n";
}
}
$wire->pages->unCacheAll();
echo "--- find() ---\n";
foreach ($wire->pages->find("template=p", $options) as $parent) {
echo "Parent: $parent->name\n";
foreach ($parent->c_ref as $child) {
echo " Child: " . $child->name . "\n";
}
}