1

I am creating an OpenCart extension where the admin can change his email templates using the user interface in the admin panel.

I would like the user to have the option to add variables to his custom email templates. For example he could put in:

Hello $order['customer_firstname'], your order has been processed.

At this point $order would be undefined, the user is simply telling defining the message that is to be sent. This would be stored to the database and called when the email is to be sent.

The problem is, how do I get "$order['customer_firstname']" to become a litteral string, and then be converted to a variable when necessary?

Thanks Peter

4
  • 1
    eval(), but you really really don't want to go there. Remember: PHP is a templating system. There's other ways of giving "variable" access without using actual variables. Commented Nov 8, 2012 at 15:12
  • So you want your php-syntax variable reference $var[key] to be stored as-is in the database, and then have it evaluated on outputting? Commented Nov 8, 2012 at 15:12
  • Don't ever use eval() in this situation. Use placeholders (i.e. %CustomerName% in Prash's answer), and then replace them at run time. You also don't want your users to know what your PHP code/variables look like. I'm upvoting prash's answer... but look up str_replace because he's using it weirdly. Commented Nov 8, 2012 at 15:16
  • @mario yes that is correct. I think the %var% example is the answer, I will try it when I get home later! Commented Nov 8, 2012 at 15:17

7 Answers 7

7

If I understand your question correctly, you could do something like this:

The customer has a textarea or similar to input the template

Dear %NAME%, blah blah %SOMETHING%

Then you could have

$values = array('%SOMETHING%' => $order['something'], '%NAME%' => $order['name']);
$str = str_replace(array_keys($values), array_values($values), $str);
Sign up to request clarification or add additional context in comments.

5 Comments

Not the appropriate use of str_replace. But good answer nonetheless.
Yeah, that makes sense, but the user will be using around 40 variables. Is there a way I can set it to do that for each "%VARIABLE%"? Thanks for your answer
@JasonMcCreary agreed. should either just use strings as first and second parameter, or use arrays for both. third param is always the original string
@PeterStuart look up str_replace docs. It's more like this: $search = array("%name%", "%phone%"); $replace = array($name, $phone); str_replace($search, $replace, $str);
One other question, multiple variables will be used, along with multiple keys. Is there a way round that?
2

the user will be using around 40 variables. Is there a way I can set it to do that for each "%VARIABLE%"?

Yes, you can do so for each variable easily with the help of a callback function.

This allows you, to process each match with a function of your choice, returning the desired replacement.

$processed = preg_replace_callback("/%(\S+)%/", function($matches) {
        $name = $matches[1]; // between the % signs
        $replacement = get_replacement_if_valid($name);
        return $replacement;
    },
    $text_to_replace_in
);

From here, you can do anything you like, dot notation, for example:

function get_replacement_if_valid($name) {
    list($var, $key) = explode(".", $name);
    if ($var === "order") {
        $order = init_oder(); // symbolic
        if(array_key_exists($key, $order)) {
            return $order[$key];
        }
     }
     return "<invalid key: $name>";
}

This simplistic implementation allows you, to process replacements such as %order.name% substituting them with $order['name'].

Comments

1

You could define your own simple template engine:

function template($text, $context) {
    $tags = preg_match_all('~%([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)%~', $text, $matches);

    for($i = 0; $i < count($matches[0]); $i++) {
        $subject = $matches[0][$i];
        $ctx = $matches[1][$i];
        $key = $matches[3][$i];
        $value = $context[$ctx][$key];

        $text = str_replace($subject, $value, $text);
    }

    return $text;
}

This allows you to transform a string like this:

$text = 'Hello %order.name%. You have %order.percent%% discount. Pay a total ammount of %payment.ammount% using %payment.type%.';

$templated = template($text, array(
    'order' => array(
        'name' => 'Alex',
        'percent' => 20
    ),

    'payment' => array(
        'type' => 'VISA',
        'ammount' => '$299.9'
    )
));
echo $templated;

Into this:

Hello Alex. You have 20% discount. Pay a total ammount of $299.9 using VISA.

This allows you to have any number of variables defined.

7 Comments

What if the variable is $order['name']. Would it have to be like this: %order[name]%
No, you would just call the function like this: template('Hello %name%.', $order);
So would it be like $text = 'Hello %%order%name% ?
@PeterStuart No, you don't need order. $text = 'Hello %name%'; is sufficent.
Here is where it gets tricky, there will be another variable present that has the 'name' as a variable key. So I will have $payment['name'] and $shipping['name']
|
1

If you want to keep the PHP-syntax, then a regex would be appropriate to filter them:

$text = preg_replace(
            "/   [$] (\w+)  \[ '? (\w+) \'? \]   /exi",
            "$$1['$2']",    # basically a constrained eval
            $text
        );

Note that it needs to be executed in the same scope as $order is defined. Else (and preferrably) use preg_replace_callback instead for maximum flexibility.

You could also allow another syntax this way. For example {order[customer]} or %order.customer% is more common and possibly easier to use than the PHP syntax.

Comments

1

You can store it as Hello $order['customer_firstname'] and while accessing make sure you have double-quotes "" to convert the variable to its corresponding value.

  echo "Hello $order['customer_firstname']"; 

Edit: As per the comments, a variation to Prash's answer,

str_replace('%CUSTOMERNAME%', $order['customer_name'], $str);     

2 Comments

But $order['customer_firstname'] won't be defined when adding the code to the database.
ooh...then Prash's answer is the way to go.
0

What you're looking for is:

eval("echo \"" . $input . "\";");

but please, PLEASE don't do that, because that lets the user run any code he wants.

A much better way would be a custom template-ish system, where you provide a list of available values for the user to drop in the code using something like %user_firstname%. Then, you can use str_replace and friends to swap those tags out with the actual values, but you can still scan for any sort of malicious code.

This is why Markdown and similar are popular; they give the user control over presentation of his content while still making it easy to scan for HTML/JS/PHP/SQL injection/anything else they might try to sneak in, because whitelisting is easier than blacklisting.

1 Comment

Aaaaand someone posted the exact same answer as me while I was reading the comments on a different page.
-1

Perhaps you can have a template like this:

$tpl = "Hello {$order['customer_firstname']}, your order has been processed.".

If $order and that specific key is not null, you can use echo $tpl directly and show the content of 'customer_firstname' key in the text. The key are the curly braces here.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.