I'm having an issue trying to find all the variables in an html file.
The HTML file contains an email template, when the email itself is sent out, it converts variables like "$EMAIL_FIRST_NAME" into things like "John" because information is sent to the email function to replace all occurrences of "$EMAIL_FIRST_NAME" with "John". The issue I'm having is trying to help people create these email templates. I want to provide them the ability to insert test data.
They can take their email template (editing in a textarea) and by use of jquery it loads their template into a new window to preview. And I have it replacing some "stock" fields. However I'm running into the issue where it would be nice for them to be able to add test data.
And since each template is for different purposes it would be nice to have the correct fields appear. I'm looking for a way to use PHP to look through the HTML template and find the variable to compile an array (to be used to create input boxes).
Some snippets for examples:
<meta http-equiv="Content-Type" content="text/html; charset=$CHARSET">
$INTRO_ORDER_NUM_TITLE $INTRO_ORDER_NUMBER
$INTRO_DATE_TITLE $INTRO_DATE_ORDERED
$WEBSITE_ADDRESSindex.php?main_page=contact_us
I'm looking for a way to give an array like this:
array('$CHARSET','$INTRO_ORDER_NUM_TITLE','$INTRO_ORDER_NUMBER','$INTRO_DATE_TITLE','$INTRO_DATE_ORDERED','$WEBSITE_ADDRESS')
Some of them I could just do an explode with a space, and then find those that start with $. However the others namely $WEBSITE_ADDRESS is a bit more challenging as the rest is not part of the variable.
All the variables are suppose to start with $ and be all capital letters.
I'm looking for a way to find the sub strings that start with $ and then through the last capital letter.
Ideas?
You could use an expression such as this: \$[A-Z_]+ (example here) to look for strings which start with a dollar sign ($) and is followed by one or many upper case letters and underscores.
As pointed out by #Jan, you can use preg_replace_callback() to have your code do some logic when replacing.
In addition to npintis regex, here's an example with a callback function. I would change the $ sign to something not so complicated for PHP (e.g. _ before and after):
<?php
$tmpl = '<meta http-equiv="Content-Type" content="text/html; charset=_CHARSET_">
_INTRO_ORDER_NUM_TITLE_ _INTRO_ORDER_NUMBER_
_INTRO_DATE_TITLE_ _INTRO_DATE_ORDERED_
_WEBSITE_ADDRESS_index.php?main_page=contact_us';
$allowed = array('_CHARSET_','_INTRO_ORDER_NUM_TITLE_','_INTRO_ORDER_NUMBER_','_INTRO_DATE_TITLE_','_INTRO_DATE_ORDERED_','_WEBSITE_ADDRESS_');
$replacements = array("_CHARSET_" => "some stupid charset");
$regex = '~(?<variable>_[A-Z_]+)~';
$tmpl = preg_replace_callback(
$regex,
function ($match) {
global $allowed, $replacements;
$m = $match["variable"];
if (in_array($m, array_keys($allowed))) {
return $replacements[$m];
// or anything else
}
},
$tmpl
);
echo $tmpl;
// now you have a stupid charset ...
?>
Try this one:-
$str = '<meta http-equiv="Content-Type" content="text/html; charset=$CHARSET">
$INTRO_ORDER_NUM_TITLE $INTRO_ORDER_NUMBER
$INTRO_DATE_TITLE $INTRO_DATE_ORDERED
$WEBSITE_ADDRESSindex.php?main_page=contact_us';
$input = preg_match_all('/\$[A-Z_]+/', $str, $match);
$result = array_unique($match[0]);
echo '<pre>'; print_r($result);
Output:-
Array
(
[0] => $CHARSET
[1] => $INTRO_ORDER_NUM_TITLE
[2] => $INTRO_ORDER_NUMBER
[3] => $INTRO_DATE_TITLE
[4] => $INTRO_DATE_ORDERED
[5] => $WEBSITE_ADDRESS
)
Related
I have an XML file that contains FAQs, but some of the contents of the answers need to use PHP functions to output appropriate content.
How can I find the placeholders within the answers, and replace them with PHP functions? Ideally, I would like to be able to have the functions set as variables to be changeable across multiple websites this code would be used on.
XML File (placeholders in last block, %LOCAL_NO% and %PREM_NO%)
<?xml version="1.0" encoding="UTF-8"?>
<faqs>
<faq>
<category>General</category>
<q>How old do I have to be to use your service?</q>
<a>You must be at least 18 years of age.</a>
</faq>
<faq>
<category>General</category>
<q>How long is a psychic reading?</q>
<a>The length of a psychic reading is completely up to you. It depends on the number and complexity of the questions you ask. The average length of a reading is 15 to 20 minutes.</a>
</faq>
<faq>
<category>General</category>
<q>Can I choose the psychic I speak with?</q>
<a>Of course! You can choose who you would like to speak to by selecting your desired psychic's profile and following the online prompts via the online booking page, or call us on %PREM_NO% and enter their PIN, or call %LOCAL_NO% and our live receptionists will connect you to a psychic that matches your requirements!</a>
</faq>
</faqs>
PHP output
<?php // General FAQs
$faqGeneral = $xml->xpath("/faqs/faq[category='General']");
echo "<h2>General</h2>";
foreach ($faqGeneral as $faq) { ?>
<h3><?php echo $faq->q; ?></h3>
<p><?php echo $faq->a; ?></p>
<?php } ?>
That looks like a case for preg_replace_callback, of course called before evaluating the XML. Which also ensures that the "PHP-echoed" values do not break XML syntax.
$data = array(
'tags' => array(
'PREM_NO' => '1-800-CSICOP',
)
);
$filledXML = preg_replace_callback(
'#%(\\w+)%#', // A-Z and underscore between %%'s
function ($matches) use ($data) {
switch($matches[1]) {
case 'PREM_NO':
case 'WHATEVER':
return $data['tags'][$matches[1]];
case 'YYYYMMDD':
return date('Y-m-d');
default:
return '';
}
},
$xmlString);
// $xml = loadXML($xmlString);
$xml = loadXML($filledXML);
This allows for special tags such as YYYYMMDD to return runtime-calculated values, as well as externals. You can even include a PDO handle in $data and be able to run SQL queries inside the function.
A simpler version
$tags = array(
'%PREM_NO%' => '12345',
'%YYYYMMDD%' => date('Y-m-d'),
// ... et cetera
);
$filledXML = str_replace(array_keys($tags), array_values($tags), $xmlString);
If you know the strings to match and the values before (i.e., not dynamic) then you can just do a str_replace inline.
If they are dynamic then you can grab the values from your database (or wherever you are storing them) and then loop through and str_replace them.
Or, you could use regex, something like /(\%[a-z_]+\%)/i. For that you can look into preg_match_all.
str_replace in the PHP manual
preg_match_all in the PHP manual
Update: You can use arrays as parametera for str_replace. E.g.,
$find = array('%PREM_NO%', '%LOCAL_NO%');
$replace = array('012345', '67890');
$answer = str_replace($find, $replace, $faq->a);
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
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);
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'].
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.
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.
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);
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.
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.
I am designing a Mail Template editor in my application. I need an idea to find the occurance of specific variables to be able to convert them using preg_replace or preg_match in my code.
For example, my template looks like this: (Sample only) this is what is returned from the textarea variable.
<p>Thank you for your order at {site_name}.</p>
In this example, I would like to replace the {site_url} with a specific variable or code from PHP since I can't parse PHP directly into the textarea.
Hope my question was clear, any help appreciated.
Edit: Is my question clear? I need to replace the {} strings using my own php code. The php code cann't be used in textarea directly. That is why i need to find a templating system that replace predefined variables {... } but convert them using php when interpreting the template.
Do you mean something like this?
<p>Thank you for your order at <?=$site_name?>.</p>
or
<p>Thank you for your order at <?php echo $site_name?>.</p>
edited:
Ahhh, do you mean something like this then:
$html = '<p>Thank you for your order at {site_name}.</p>';
$php_variables_array = array(
"site_url" => "http://www.google.co.uk",
"site_name" => "Google",
);
foreach ($php_variables_array as $key => $value)
{
$html = str_replace("{" . $key . "}", $value, $html);
}
echo $html;
<?php echo $site_name;?>
I'm working on a simple templating system. Basically I'm setting it up such that a user would enter text populated with special tags of the form: <== variableName ==>
When the system would display the text it would search for all tags of the form mentioned and replace the variableName with its corresponding value from a database result.
I think this would require a regular expression but I'm really messed up in REGEX here. I'm using php btw.
Thanks for the help guys.
A rather quick and dirty hack here:
<?php
$teststring = "Hello <== tag ==>";
$values = array();
$values['tag'] = "world";
function replaceTag($name)
{
global $values;
return $values[$name];
}
echo preg_replace('/<== ([a-z]*) ==>/e','replaceTag(\'$1\')',$teststring);
Output:
Hello world
Simply place your 'variables' in the variable array and they will be replaced.
The e modifier to the regular expression tells it to eval the replacement, the [a-z] lets you name the "variables" using the characters a-z (you could use [a-z0-9] if you wanted to include numbers). Other than that its pretty much standard PHP.
Very useful - Pointed me to what I was looking for...
Replacing tags in a template e.g.
<<page_title>>, <<meta_description>>
with corresponding request variables e,g,
$_REQUEST['page_title'], $_REQUEST['meta_description'],
using a modified version of the code posted:
$html_output=preg_replace('/<<(\w+)>>/e', '$_REQUEST[\'$1\']', $template);
Easy to change this to replace template tags with values from a DB etc...
If you are doing a simple replace, then you don't need to use a regexp. You can just use str_replace() which is quicker.
(I'm assuming your '<== ' and ' ==>' are delimiting your template var and are replaced with your value?)
$subject = str_replace('<== '.$varName.' ==>', $varValue, $subject);
And to cycle through all your template vars...
$tplVars = array();
$tplVars['ONE'] = 'This is One';
$tplVars['TWO'] = 'This is Two';
// etc.
// $subject is your original document
foreach ($tplVars as $varName => $varValue) {
$subject = str_replace('<== '.$varName.' ==>', $varValue, $subject);
}
I have an HTML document as a string
I want to search for a keyword in this document and figure out where did it appear in the document
I mean in which tag did it appear
did it appear in H1,H2 or TITLE tag
lets say my document is
$string = "<html>
<head>
<title>bar , this is an example</title>
</head>
<body>
<h1>latest news</h1>
foo <strong>bar</strong>
</body>
</html>";
$arr = find_term("bar",$string);
print_r($arr);
I expect the result to be like this
[0]=> title
[1]=> strong
because "bar" appeared one time in TITLE tag and one time in the STRONG tag
I knew it is a complicated question, that is why I am asking if someone knows the answer :)
thanks
what I have so far is
function find_term($term,$string){
$arr = explode($term, $string);
return $arr;
}
$arr = find_term("bar",$string);
print_r($arr);
now we have an array which has the value
Array
(
[0] => <html>
<head>
<title>
[1] => , this is an example</title>
</head>
<body>
<h1>latest news</h1>
foo <strong>
[2] => </strong>
</body>
</html>
)
you can see that the last tag of every element of the array is the tag which contains "bar"
but the question now is how to know the last tag appeard in every element?
Thanks
You can use DOMDocument and xpath for that.
<?php
$doc = new DOMDocument;
$doc->loadhtml('<html>
<head>
<title>bar , this is an example</title>
</head>
<body>
<h1>latest news</h1>
foo <strong>bar</strong>
<i>foobar</i>
</body>
</html>');
$xpath = new DOMXPath($doc);
foreach($xpath->query('//*[contains(child::text(),"bar")]') as $e) {
echo $e->tagName, "\n";
}
prints
title
strong
i
Note the i-element. It contains foobar, not bar as a single word and matches the xpath query. So this solution may or may not suffice.
I am not a php programmer, but generally if you can get hold of an html dom parser, it would make it easy. Find all text nodes and search them for the text string. Whenver you have a match, just retrieve the name of the parent node.
Without a dom parser, there are two problems to deal with.
Unless you are using xhtml, html isn't xml. <br> is a good example that you will have to hardcode around.
Secondly, the following combination of tags will have to be considered "<a><b>bar<c></c></a>". It should result in the answer "a", and not "b" or "c".
Even after having located the "bar" string, you can't therefore not just find the next or previous tag. Instead you should set a counter to 1 and start back tracking. When you encounter a start tag, you decrease by one and when you encounter an end tag you increase by one. When the counter drops to 0, save the tag you are currently on.
Finally, there is also malformed html such as "<i><b>bar</i></b>". I don't really know if there is a good way to deal with that.
i think u need first ,
parse html into array ,
find function that do it like : http://www.php.happycodings.com/Arrays/code35.html
or class like : http://www.phpclasses.org/browse/package/5139.html
after that search in this array by loop.
The following code will work, most of the time. It won't respect HTML comments and may get confused by quoted strings (eg <img alt="<grin>" ...) but wont't choke on pathological cases like <i><b>foo</i>bar</b>, and even gives a reasonable result.
It does not notice tags like <?php>, and doesn't know about empty tags like <br> or <input> but will ignore tags like </br />. You could add logic to ignore empty tags (img, hr, br, input, etc).
The search word is surrounded by \b (word boundary) so foobar is not matched.
$html = "<html>
<head>
<title>bar , this is an example</title>
</head>
<body class=3>
<h1>latest news</h1>
foo <strong>bar</strong> <br />bar
<i>foobar</i>
</body>
</html>";
$search = 'bar';
preg_match_all('/(\<(\/?)(\w+).*?(\/?)\>)|(\b'.$search.'\b)/i', $html, $matches, PREG_SET_ORDER);
$tags = array();
$found = array();
foreach ($matches as $m) {
if ($m[2] == '/') {
$n = array_search($m[3], $tags);
if ($n !== false)
array_splice($tags, $n, 1);
}
else if ($m[3] and !$m[4]) {
array_unshift($tags, $m[3]);
}
else if ($m[5]){
$found[] = $tags[0];
}
}
print_r($found);
It outputs (with the extra bar after the <br /> tag)
Array
(
[0] => title
[1] => strong
[2] => body
)
Hm, that's a tricky question.
Why don't you search in the string for your keyword, remember the position where you found it, and then go through the string backwards until you see the first "<", write that into your array until you see ">".