Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
template
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
4 / 4
14
100.00% covered (success)
100.00%
1 / 1
 render
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 resolve
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 lookup
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 to_text
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2// This file is part of Moodle - https://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <https://www.gnu.org/licenses/>.
16
17declare(strict_types=1);
18
19namespace tool_autoflow\local\engine;
20
21/**
22 * Fills {{dotted.path}} placeholders from the run context.
23 *
24 * There is no expression language and no eval: a placeholder is a path into the
25 * context and nothing else. Missing paths render as empty text.
26 *
27 * @package    tool_autoflow
28 * @copyright  2026 DevLMS <contact@devlms.com>
29 * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30 */
31final class template {
32    /** One placeholder: word characters separated by dots, optional spaces inside the braces. */
33    private const PLACEHOLDER = '\{\{\s*([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)\s*\}\}';
34
35    /**
36     * Replace every placeholder in a string with its value as text.
37     *
38     * @param string $text
39     * @param array $context
40     * @return string
41     */
42    public static function render(string $text, array $context): string {
43        return preg_replace_callback(
44            '/' . self::PLACEHOLDER . '/',
45            fn(array $m) => self::to_text(self::lookup($m[1], $context)),
46            $text
47        );
48    }
49
50    /**
51     * Resolve a node parameter. A string that is exactly one placeholder keeps the
52     * value's type; other strings are rendered; arrays are resolved item by item.
53     *
54     * @param mixed $value
55     * @param array $context
56     * @return mixed
57     */
58    public static function resolve($value, array $context) {
59        if (is_array($value)) {
60            return array_map(fn($item) => self::resolve($item, $context), $value);
61        }
62        if (!is_string($value)) {
63            return $value;
64        }
65        if (preg_match('/^\s*' . self::PLACEHOLDER . '\s*$/', $value, $m)) {
66            return self::lookup($m[1], $context);
67        }
68        return self::render($value, $context);
69    }
70
71    /**
72     * Read a dotted path from the context. Returns null when any step is missing.
73     *
74     * @param string $path
75     * @param array $context
76     * @return mixed
77     */
78    public static function lookup(string $path, array $context) {
79        $value = $context;
80        foreach (explode('.', $path) as $key) {
81            if (!is_array($value) || !array_key_exists($key, $value)) {
82                return null;
83            }
84            $value = $value[$key];
85        }
86        return $value;
87    }
88
89    /**
90     * Convert a value to the text used inside a rendered string.
91     *
92     * @param mixed $value
93     * @return string
94     */
95    private static function to_text($value): string {
96        if ($value === null) {
97            return '';
98        }
99        if (is_bool($value)) {
100            return $value ? 'true' : 'false';
101        }
102        if (is_array($value)) {
103            return json_encode($value);
104        }
105        return (string) $value;
106    }
107}