From redaxo-mform
Normalizes MForm link and repeater values in REDAXO module OUTPUT PHP using MFormOutputHelper. Resolves redaxo:// URLs to hrefs, decodes repeater JSON, processes customlink_url/text/target/class for frontend display.
npx claudepluginhub friendsofredaxo/claude-marketplace --plugin redaxo-mformThis skill uses the workspace's default tool permissions.
The `MFormOutputHelper` class (namespace `FriendsOfRedaxo\MForm\Utils`) normalises all link and repeater values for use in frontend templates.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Processes PDFs: extracts text/tables/images, merges/splits/rotates pages, adds watermarks, creates/fills forms, encrypts/decrypts, OCRs scans. Activates on PDF mentions or output requests.
Share bugs, ideas, or general feedback.
The MFormOutputHelper class (namespace FriendsOfRedaxo\MForm\Utils) normalises all link and repeater values for use in frontend templates.
Unified entry point that accepts any link format and returns a consistent array.
use FriendsOfRedaxo\MForm\Utils\MFormOutputHelper;
$data = MFormOutputHelper::createLinkData('REX_VALUE[1]');
| Input | Example |
|---|---|
| Internal article (redaxo:// prefix) | redaxo://12 |
| Internal article (plain numeric ID) | 14 |
| Media filename | image.jpg |
| External URL | https://example.com |
mailto:user@example.com | |
| Telephone | tel:+49123456789 |
Already-prepared array with customlink_url | passed through as-is |
Repeater array with id / name keys | ['id' => 'redaxo://12', 'name' => 'Artikel [12]'] |
[
'customlink_url' => 'https://example.com', // resolved URL ready for href=
'customlink_text' => 'Example', // display text (article name / media title / URL)
'customlink_target' => ' target="_blank" rel="noopener noreferrer"', // for external links
'customlink_class' => 'external', // 'internal' | 'external' | 'media' | 'mail' | 'tel'
'type' => 'external', // 'internal' | 'external' | 'media' | 'email' | 'telephone'
'article_id' => null, // int if internal link
'clang_id' => null, // int if internal link
'filename' => null, // string if media
'extension' => null, // file extension if media
'protocol' => 'https', // scheme for external
'domain' => 'example.com',
'metadata' => [], // article / media metadata
]
$data = MFormOutputHelper::createLinkData('REX_VALUE[1]', [
'extern_blank' => false, // default true – don't add target="_blank" for external
'mode' => 'raw', // 'frontend' (default) | 'raw' | 'strict'
]);
use FriendsOfRedaxo\MForm\Utils\MFormOutputHelper;
$data = MFormOutputHelper::createLinkData('REX_VALUE[1]');
if ('' !== $data['customlink_url']) {
echo '<a href="' . rex_escape($data['customlink_url']) . '"'
. $data['customlink_target'] . '>'
. rex_escape($data['customlink_text'])
. '</a>';
}
When you just need the resolved URL without metadata:
$url = MFormOutputHelper::getCustomLinkUrl('REX_VALUE[1]');
// or from an already-prepared array:
$url = MFormOutputHelper::getCustomLinkUrl($linkData);
Use MFormRepeaterHelper::decode() – it handles JSON decoding, HTML entity decoding, and filters disabled rows (online/offline toggle) in one call.
See the mform-flex-repeater skill for the full Repeater API including
filterByField(),sortByField(),groupByField(), andlimitItems().
use FriendsOfRedaxo\MForm\Repeater\MFormRepeaterHelper;
$rows = MFormRepeaterHelper::decode('REX_VALUE[1]');
// Alternative (v8-kompatibel / already-decoded array):
// $raw = json_decode(html_entity_decode('REX_VALUE[1]', ENT_QUOTES | ENT_HTML5, 'UTF-8'), true) ?? [];
// $rows = MFormRepeaterHelper::prepareItemsForOutput($raw);
use FriendsOfRedaxo\MForm\Utils\MFormOutputHelper;
// Adds <field>_normalized key alongside the original
$items = MFormOutputHelper::normalizeRepeaterItems($items, ['link', 'cta']);
foreach ($items as $item) {
$link = $item['link_normalized'];
echo '<a href="' . rex_escape($link['customlink_url']) . '">'
. rex_escape($link['customlink_text']) . '</a>';
}
// Replace original field (no _normalized suffix)
$items = MFormOutputHelper::normalizeRepeaterItems($items, ['link'], ['replace' => true]);
use FriendsOfRedaxo\MForm\Repeater\MFormRepeaterHelper;
use FriendsOfRedaxo\MForm\Utils\MFormOutputHelper;
$rows = MFormRepeaterHelper::decode('REX_VALUE[1]');
$rows = MFormOutputHelper::normalizeRepeaterItems($rows, ['cta_link']);
foreach ($rows as $row) {
$title = rex_escape($row['title'] ?? '');
$text = $row['text'] ?? ''; // may contain HTML from TinyMCE
$filename = $row['image'] ?? '';
$link = $row['cta_link_normalized'];
echo '<div class="card">';
if ($filename) {
echo '<img src="' . rex_url::media($filename) . '" alt="">';
}
echo '<h3>' . $title . '</h3>';
echo '<div>' . $text . '</div>';
if ($link['customlink_url']) {
echo '<a href="' . rex_escape($link['customlink_url']) . '"'
. $link['customlink_target'] . '>'
. rex_escape($link['customlink_text']) . '</a>';
}
echo '</div>';
}
When a media filename is passed, the returned array includes:
$data = MFormOutputHelper::createLinkData('hero.jpg');
// $data['type'] → 'media'
// $data['customlink_url'] → '/media/hero.jpg'
// $data['filename'] → 'hero.jpg'
// $data['extension'] → 'jpg'
// $data['metadata']['title'] → media title from mediapool
// $data['metadata']['width'] → image width
// $data['metadata']['height']→ image height
Check if the current slice is the first slice of an article:
if (MFormOutputHelper::isFirstSlice(REX_SLICE_ID)) {
// render differently for first module instance
}
createLinkData() or normalizeLinkData() instead of manually parsing redaxo:// – the helper resolves article names, media URLs and external link targets in one call.customlink_target is already a ready-to-embed attribute string ( target="_blank" rel="noopener noreferrer") or empty string – just echo it directly, no need to check it.customlink_class is a plain string ('internal', 'external', etc.), not an HTML class attribute string.normalizeRepeaterItems() does not mutate the original array – assign the return value.mode = 'raw' keeps backend helper labels (article name with [ID] suffix) in the name key. Use for debugging only.MFormRepeaterHelper::decode('REX_VALUE[n]') – it handles JSON decoding, HTML entity decoding, empty-string values on fresh modules, and filtering of disabled rows in one call. Only fall back to json_decode(html_entity_decode($raw, ENT_QUOTES | ENT_HTML5, 'UTF-8'), true) ?? [] when you have a v8-era already-decoded array (then post-process with MFormRepeaterHelper::prepareItemsForOutput()).