PHP API
4 min read
wpmcf_resolve_variables()
wpmcf_resolve_variables( string $template, ?EvaluationContext $context = null ): stringReplaces every {{ }} block in $template and returns the text. Everything outside the blocks is kept.
echo esc_html( wpmcf_resolve_variables( 'Hello {{ glb.company }}, it is {{ sys.year }}.' ) );
// Hello Acme, it is 2026.Situation | Result |
|---|---|
The plugin has not booted yet (before | The template, unchanged |
An unexpected error inside the engine | The template, unchanged; the error is logged under module |
A block that cannot be evaluated | That block is empty; the rest is resolved |
No context, inside the loop | The loop post ( |
No context, outside any post | No object: |
A context of type |
|
The return value is not escaped. Escape it for where it goes (esc_html(), esc_attr(), wp_kses_post()). It is also not subject to the "who may run expressions" check of stored field values, because it is trusted code: do not pass visitor input to it. See Security.
wpmcf_make_context()
wpmcf_make_context( ?int $object_id = null, string $context_type = 'post', array $source_values = [] ): EvaluationContextBuilds the context that tells the engine which object fg and wp.post_* refer to, and what src contains.
Argument | Meaning |
|---|---|
| The object ID. Omitted or |
|
|
| An array readable as |
// A field of a specific post
$ctx = wpmcf_make_context( 42 );
echo esc_html( wpmcf_resolve_variables( '{{ fg.product.price | number(2) }}', $ctx ) );
// A field of a term
$ctx = wpmcf_make_context( $term->term_id, 'term' );
echo esc_html( wpmcf_resolve_variables( '{{ fg.category_extra.headline }}', $ctx ) );
// Values of your own
$ctx = wpmcf_make_context( null, 'post', [ 'city' => 'London', 'n' => 10 ] );
echo esc_html( wpmcf_resolve_variables( "{{ src.city == 'London' ? 'Capital' : 'City' }}", $ctx ) );The evaluation context
WPMass\CustomFields\Variables\Context\EvaluationContext is immutable: each with* method returns a modified copy.
Method | Purpose |
|---|---|
| An empty context (object none, type |
| Set the object |
|
|
| Values for |
| Free-form data for namespace resolvers (for example |
| Read it back |
$ctx = wpmcf_make_context( $term->term_id, 'term' )->withMeta( 'term_id', $term->term_id );
echo esc_html( wpmcf_resolve_variables( '{{ wp.term_name }} ({{ wp.term_slug }})', $ctx ) );The engine
$engine = wpmcf_variable_engine(); // ?VariableEngine, null until the plugin has bootedMethod | Returns |
|---|---|
| The resolved text |
| Like |
| Whether the text contains |
| The registry |
$engine = wpmcf_variable_engine();
if ( $engine ) {
$html = $engine->resolveEscaped(
'<p class="price">{{ fg.product.price | number(2) }}</p>',
wpmcf_make_context( get_the_ID() ),
'esc_html'
);
}resolve() itself is not guarded against exceptions the way wpmcf_resolve_variables() is. Prefer the function unless you need resolveEscaped().
The registry
$registry = wpmcf_variable_registry(); // ?VariableRegistryMethod | Purpose |
|---|---|
| Add a namespace (a closure builds the resolver on first use) |
| Add a filter. |
| Add a function for |
| Existence checks (names are lowercased) |
| Lists of lowercase names |
Registering a name again replaces the earlier entry.
Extend the engine
Add namespaces, filters and functions from the wpmcf/variables/register action. It receives the registry and fires once, when the registry is built after the plugin has booted.
use WPMass\CustomFields\Variables\Context\EvaluationContext;
use WPMass\CustomFields\Variables\Namespace\NamespaceResolverInterface;
use WPMass\CustomFields\Variables\Registry\VariableRegistry;
final class My_Resolver implements NamespaceResolverInterface {
public function resolve( array $path, EvaluationContext $ctx ): mixed {
// {{ my.rating }} → $path = [ 'rating' ]
return match ( $path[0] ?? '' ) {
'rating' => (string) get_post_meta( (int) $ctx->getObjectId(), 'rating', true ),
default => null, // null renders nothing
};
}
public function getNamespace(): string {
return 'my';
}
}
add_action( 'wpmcf/variables/register', function ( VariableRegistry $registry ): void {
$registry->registerNamespace( 'my', new My_Resolver() );
$registry->registerFilter( 'shout', fn ( $value, $suffix = '!' ) => strtoupper( (string) $value ) . $suffix );
$registry->registerFunction( 'sum_all', fn ( ...$numbers ) => array_sum( $numbers ) );
} );Now {{ my.rating }}, {{ glb.name | shout('?') }} and {{ fn.sum_all(1, 2, 3) }} work.
Rules for extensions:
- Return plain values. Strings, numbers, booleans,
nullor lists. Lists print joined by,;nullandfalseprint nothing;trueprints1. - Never trust the path.
$pathcomes from editors. Whitelist the names you handle (matchabove) and read only data that is safe to print publicly. - Do not print secrets. Passwords and keys must not be reachable. The built-in
fgandglbresolvers exclude password values; follow that rule. - Names are made of letters, digits and underscores and are matched case-insensitively.
fn,func,fonkandfunctionare reserved for function calls. - Throwing is safe (the block renders empty), but returning
nullis cleaner.
Timing
The listener must be attached before the registry is built after boot. Attach it when your plugin file loads, or on plugins_loaded. If you may run later than that, skip the action and register directly on the registry:
$registry = wpmcf_variable_registry();
if ( $registry ) {
$registry->registerFilter( 'shout', fn ( $value ) => strtoupper( (string) $value ) );
}Adding request data to the context
The wpmcf/variables/context filter lets a module add data to the context at the start of every top-level evaluation. Programmatic SEO uses it to give field values on a generated page access to the page's data row.
add_filter( 'wpmcf/variables/context', function ( EvaluationContext $ctx ): EvaluationContext {
return $ctx->withMeta( 'campaign', 'spring-sale' );
} );Reading values without expressions
When you only need a value and not a template, use wpmcf_get_field() and wpmcf_get_global(). They evaluate {{ }} inside the stored value, once, under the rules in Where variables work.
Still stuck? Contact support