] one field value
* [form:name] the form name
* [page:title] title of the page holding the form [page:url] its permalink
* [site:name] the site title
*
* Values are escaped for the context they land in: `html` (esc_html +
), `plain`, `url`
* (rawurlencode) or `header` (no CR/LF, used for subjects / names / addresses).
*/
final class Shortcodes
{
public const CONTEXTS = ['plain', 'html', 'url', 'header'];
public static function render(string $template, Submission $submission, string $context = 'plain'): string
{
if (!in_array($context, self::CONTEXTS, true)) {
$context = 'plain';
}
if ($context === 'html') {
// Line breaks typed in the settings become
; values are escaped below.
$template = nl2br($template, false);
}
$out = preg_replace_callback(
'/\[(all-fields|field:([a-z0-9_\-]+)|form:name|page:title|page:url|site:name)\]/i',
static function (array $m) use ($submission, $context): string {
$tag = strtolower($m[1]);
if ($tag === 'all-fields') {
return self::all_fields($submission, $context);
}
if (str_starts_with($tag, 'field:')) {
return self::escape($submission->text(strtolower($m[2])), $context);
}
switch ($tag) {
case 'form:name':
return self::escape($submission->form_name, $context);
case 'page:title':
return self::escape(function_exists('get_the_title') ? wp_strip_all_tags((string) get_the_title($submission->post_id)) : '', $context);
case 'page:url':
return self::escape(function_exists('get_permalink') ? (string) get_permalink($submission->post_id) : '', $context);
case 'site:name':
return self::escape(function_exists('get_bloginfo') ? wp_strip_all_tags((string) get_bloginfo('name')) : '', $context);
}
return '';
},
$template
);
// Headers: the typed template itself must not smuggle line breaks either (subject / name / address).
return $context === 'header' ? self::header_safe((string) $out) : (string) $out;
}
public static function escape(string $value, string $context): string
{
switch ($context) {
case 'html':
return nl2br(esc_html($value), false);
case 'url':
return rawurlencode($value);
case 'header':
return self::header_safe($value);
default:
return $value;
}
}
/** Strips CR/LF/NUL (mail header injection) and collapses whitespace. */
public static function header_safe(string $value): string
{
$value = preg_replace('/[\r\n\x00]+/', ' ', $value) ?? '';
return trim(preg_replace('/\s{2,}/', ' ', $value) ?? '');
}
private static function all_fields(Submission $submission, string $context): string
{
$lines = [];
foreach (Fields::inputs($submission->fields) as $id => $field) {
if ($field['type'] === 'password') {
continue;
}
$label = $submission->label($id);
$value = $submission->text($id);
if ($context === 'html') {
if ($field['type'] === 'upload' && is_array($submission->values[$id] ?? null)) {
$links = array_map(static fn($u): string => '' . esc_html(basename((string) $u)) . '', $submission->values[$id]);
$value_html = implode('
', $links);
} else {
$value_html = nl2br(esc_html($value), false);
}
$lines[] = '
' . esc_html($label) . '
' . ($value_html !== '' ? $value_html : '—') . '