This doesn't warrant a full tutorial, but I wrote a little function that will recursively search through a repeater field to find the first non-empty field matching a list of fields to look for. I needed something like this to generate a fallback for SEO fields (og:image, og:description et c.). Here it is:
<?php
namespace ProcessWire;
/**
* Find the first non-empty field out of a list of fields in a repeater or repeater matrix field.
* Will recursively search through nested repeaters until it finds a non-empty
* field.
*
* @param RepeaterPageArray $repeater The field to search through.
* @param array $fields A list of fields the function should look for.
* @param array|null $allowed_repeater_types All the Repeater Matrix types the function will check. Leave empty to allow all.
* @param array|null $allowed_repeater_fields All the repeater fields the function will check recursively. Leave empty to allow all.
* @return void
*/
function firstRecursiveRepeaterMatch(
RepeaterPageArray $repeater,
array $fields,
?array $allowed_repeater_types = null,
?array $allowed_repeater_fields = null
) {
// iterate over the items of the repeater
foreach ($repeater as $current) {
// if the function is currently inside a repeater matrix field,
// skip this item if it isn't one of the allowed types, unless
// allowed_repeater_types is empty (all types allowed)
if (
$current instanceof RepeaterMatrixPage
&& is_array($allowed_repeater_types)
&& !in_array($current->type, $allowed_repeater_types)
) {
continue;
}
// get all fields of the current item
foreach ($current->getFields() as $field) {
$name = $field->name;
// if the current field is another repeater, check it recursively
$fieldtype_class = $field->getFieldType()->className();
if ($fieldtype_class === 'FieldtypeRepeater' || $fieldtype_class === 'FieldtypeRepeaterMatrix') {
// continue with the next item if the field name isn't one of the
// allowed repeater fields, unless allowed_repeater_fields empty (null)
if (
is_array($allowed_repeater_fields)
&& !in_array($name, $allowed_repeater_fields)
) {
continue;
}
$deep_search = firstRecursiveRepeaterMatch(
$current->get($name),
$fields,
$allowed_repeater_types,
$allowed_repeater_fields
);
// if the deep search inside the repeater
// finds something, the function ends here
if ($deep_search !== null) {
return $deep_search;
}
}
// if the current field name is one of the requested
// fields, return it's value if it isn't empty
if (in_array($name, $fields)) {
$value = $current->get($name);
if (
// check for empty values
!empty($value)
// if the value is any wirearray, check
// if it has at least one item
&& (!$value instanceof WireArray || $value->count() > 0)
) {
return $value;
}
}
}
}
// if the function reaches this point, there is no match in the entire tree
return null;
}
Can be used like this:
$seo_description = firstRecursiveRepeaterMatch(
// content sections fields, a repeater matrix with multiple types
$page->sections,
// look for the first non-empty instance of any of those fields
['body', 'html_basic', 'html_full'],
// only check sections of the following types
['section_text', 'section_columns', 'section_accordion'],
// only check the following nested repeaters recursively
['columns', 'accordion']
);
Wanted to share because I thought it could be useful to others. It should be easy to adjust the matching condition from non-emtpy fields to some other condition, depending on the use case ...