I have a simple template engine that works fine the simple template, but I don't know how to adapt it to make it work with loops :
class Template {
public $template;
function getFile($file) {
$this->template = file_get_contents($file);
}
function set($tag, $content) {
$this->template = str_replace("{".$tag."}", $content, $this->template);
}
function ouput() {
eval("?>".$this->template."<?");
}
}
That's the loop I want to parse and display:
{{#each Stuff}}
{{Thing}} are {{Desc}}
{{/each}}
I dont want use any SMARTY or Twig engine.
Any idea please?
Ok, keep in mind this is just for learning purposes. You can't ask on SO for the whole code, you need to try and post question about your tries.
This code parse a string for a foreach and then executes it:
<?php
$var = array(2, 4);
$str = 'for i in var';
$a = explode(' ', $str);
foreach (${$a[3]} as $i => $value)
{
echo $value;
}
Read this part from PHP docs to understand what i did.
Related
I was coding a TemplateView Class which is replacing #somevariable# placeholders with it's controller set correspondent. ViewHelpers are called via placeholders that simulate a function like #headerFiles()# for example.
The thing missing is a foreach loop definition like for example the smarty
{foreach from=$var key=index item=value} definition.
Until now I was iterating over a database object inside the controller and having a heredok or string concatenation assigned to the view. So there is a lot of html inside the controllers which I think should not be the controllers job.
I already implemented smarty into the app for testing and it's working fine - But when I decide to really use it I would have to change so many things like all partial templates would have to be .tpl files and all template directories would have to be merged ? or in one place and so on.
So my question is:
Are there any good examples or codesnippets I could use to implement a custom foreach loop method ? Before I have to change everything to use smarty. I also want to avoid using plain php like <?php foreach(): endforeach; ?>
UPDATE:
what concerns would one have going this direction ?
In a template:
#foreach array#
if($key !== 'fart'){
echo $val;
}
#endforeach#
class Foreacher {
private $template;
protected $array = array('fart' => 'poop', 'foo', 'bar');
public function __construct($template){
$this->template = file_get_contents($template);
}
public function parse(){
if(preg_match_all('/#foreach ([\w]+)#(.*?)#endforeach#/is', $this->template, $matches, PREG_SET_ORDER)){
$var = $matches[0][1];
$freach = "<?php foreach(\$this->$var as \$key => \$val){";
$freach .= $matches[0][2];
$freach .= "} ?>";
$parsed = str_replace($matches[0][0], $freach, $this->template);
$this->render($parsed);
}
}
public function __get($var){
if(isset($this->$var)){
return $this->$var;
}
return null;
}
protected function render($string){
$tmp = tmpfile();
fwrite($tmp, $string);
fseek($tmp, 0);
ob_start();
$file = stream_get_meta_data($tmp);
include $file['uri'];
$data = ob_get_clean();
fclose($tmp);
echo $data;
}
}
I am currently trying to create a simple template engine in PHP. The main thing I care about is security, however template tutorials do not. Lets say I have a database table with a username and his description. The user can type whatever he wants there.
My guess would be to use htmlspecialchars() function, to prevent javascript and html injection. But what about 'template code injection'? If my template rule is to replace [#key] to "value", the user can update his description that interferes with my template handler. Should I treat "[", "#", "]" as special characters and replace them with their ascii code when using my set method?
template.php:
class Template {
protected $file;
protected $values = array();
public function __construct($file) {
$this->file = $file;
}
public function set($key, $value) {
$this->values[$key] = $value;
}
public function output() {
if (!file_exists($this->file)) {
return "Error loading template file ($this->file).";
}
$output = file_get_contents($this->file);
foreach ($this->values as $key => $value) {
$tagToReplace = "[#$key]";
$output = str_replace($tagToReplace, $value, $output);
}
return $output;
}
}
example.tpl:
Username: [#name]
About me: [#info]
index.php:
include 'template.php';
$page = new Template('example.tpl');
$page->set('info', '[#name][#name][#name]I just injected some code.');
$page->set('name', 'Tom');
echo $page->output();
This would display:
Username: Tom
About me: TomTomTomI just injected some code.
The code I used is based on:
http://www.broculos.net/2008/03/how-to-make-simple-html-template-engine.html
Change your function to search in the unchanged template only once for the known keys:
public function output() {
if (!file_exists($this->file)) {
return "Error loading template file ($this->file).";
}
$output = file_get_contents($this->file);
$keys = array_keys($this->values);
$pattern = '$\[#(' . implode('|', array_map('preg_quote', $keys)) . ')\]$';
$output = preg_replace_callback($pattern, function($match) {
return $this->values[$match[1]];
}, $output);
return $output;
}
I was thinking about it and I think this solution is fastest and simplest:
foreach ($this->values as $key => $value) {
$tagToReplace = "[#$key]";
if (strpos($output, "[#$value]") !== FALSE)
$value = '['.substr($value,1,-1).']';
$output = str_replace($tagToReplace, $value, $output);
}
It replaces brackets in value with html entity string if [$value] is in output.
Used this html entity list
For future adopters:
This kind of solution is OK if Template Parser is implemented by loading non-interpeted/non-evaled file (as is OP's case by loading local file using file_get_contents). However, if it's implemented by INCLUDING PHP view, beware that this check won't handle the case when you put some user-modifiable data from database into view directly (without using parser, e.g. <?=$var;?>) and then use template parser for this view. In that case, parser has no way to know if these data are part of template structure and this check won't work for them. (I don't know how should this case be handled properly.) Anyhow, I think best practice is to never pass sensitive data to template parser, even if you don't use them in your template. When attacker then tricks parser to eval his custom data, he won't get information he didn't already have. Even better, don't use template parser.
I have one array for data
$data = array(title=>'some title', date=>1350498600, story=>'Some story');
I have a template
$template = "#title#, <br>#date(d)#<br> #date(m)#<br>#date(Y)#<br> #story#";
All i want is to fit data into template and i know that can be done by str_replace but my problem is the date format. date format is coming from the template not from the data, in data date is stored as php date.
yesterday i tried to ask the same question but i think my question wasn't clear.
Anybody please help me.
i think it won't work with str_replace easily so i'm going to use preg_replace
$data = array('title'=>'some title', 'date'=>1350498600, 'story'=>'Some story');
$template = "#title#, <br>#date(d)#<br> #date(m)#<br>#date(Y)#<br> #story#";
$result = preg_replace_callback('/#(\w+)(?:\\((.*?)\\))?#/', function ($match) use($data) {
$value = isset($data[$match[1]]) ? $data[$match[1]] : null;
if (!$value) {
// undefined variable in template throw exception or something ...
}
if (! empty($match[2]) && $match[1] == "date") {
$value = date($match[2], $value);
}
return $value;
}, $template);
Instead of using date(m) or date(Y) you could also do things like
date(d-m-Y) using this snippet
This has the disadvantage that you can format only the date variable using this mechanism. But with a few tweaks you can extend this functionality.
Note: If you use a PHP version below 5.3 you can't use closures but you can do the following:
function replace_callback_variables($match) {
global $data; // this is ugly
// same code as above:
$value = isset($data[$match[1]]) ? $data[$match[1]] : null;
if (!$value) {
// undefined variable in template throw exception or something ...
}
if (! empty($match[2]) && $match[1] == "date") {
$value = date($match[2], $value);
}
return $value;
}
$data = array('title'=>'some title', 'date'=>1350498600, 'story'=>'Some story');
$template = "#title#, <br>#date(d)#<br> #date(m)#<br>#date(Y)#<br> #story#";
// pass the function name as string to preg_replace_callback
$result = preg_replace_callback('/#(\w+)(?:\\((.*?)\\))?#/', 'replace_callback_variables', $template);
You can find more information about callbacks in PHP here
I'd suggest using a templating engine like so:
https://github.com/cybershade/CSCMS/blob/master/core/classes/class.template.php
And then your templates turn out like this:
https://github.com/cybershade/CSCMS/blob/master/themes/cybershade/site_header.tpl
and
https://github.com/cybershade/CSCMS/blob/master/modules/forum/views/viewIndex/default.tpl
Download this file: http://www.imleeds.com/template.class.txt
Rename the extension to .PHP from .TXT
This is something I created years ago, I keep my HTML away from my PHP, always. So see an example below.
<?php
include("template.class.php");
//Initialise the template class.
$tmpl = new template;
$name = "Richard";
$person = array("Name" => "Richard", "Domain" => "imleeds.com");
/*
On index.html, you can now use: %var.name|Default if not found% and also, extend further, %var.person.Name|Default%
*/
//Output the HTML.
echo $tmpl->run(file_get_contents("html/index.html"));
?>
I'm trying to use Mustache together with i18n (php, within Wordpress). I've got the basic __ functionality working nicely, something like this
class my_i18n {
public function __trans($string) {
return __($string, 'theme-name');
}
}
class mytache {
public function __()
{
return array('my_i18n', '__trans');
}
}
Then to output a template with an i18n string, I can simply do this
$context = new mytache;
$template = "<div>{{#__}}String to translate{{/__}}</div>";
$m = new Mustache;
echo $m->render($template, $context);
So far everything is fine. However, I want to be able to translate strings with parameters. i.e. the equivalent of sprint_f(__('Account Balance: %s'), $balance);.
It seems that if I do something like {{#__}}Account Balance: {{balance}}{{/__}} it doesn't work. I'm guessing because the inner tag gets converted first and therefore the translation cannot be found for the phrase.
Any ideas how to achieve this cleanly with Mustache?
UPDATE: here's the end-result snippet (with massive help from bobthecow):
class I18nMapper {
public static function translate($str) {
$matches = array();
// searching for all {{tags}} in the string
if (preg_match_all('/{{\s*.*?\s*}}/',$str, &$matches)) {
// first we remove ALL tags and replace with %s and retrieve the translated version
$result = __(preg_replace('/{{\s*.*?\s*}}/','%s', $str), 'theme-name');
// then replace %s back to {{tag}} with the matches
return vsprintf($result, $matches[0]);
}
else
return __($str, 'theme-name');
}
}
class mytache {
public function __()
{
return array('I18nMapper', 'trans');
}
}
I added an i18n example here... it's pretty cheesy, but the test passes. It looks like that's almost the same as what you're doing. Is it possible that you're using an outdated version of Mustache? The spec used to specify different variable interpolation rules, which would make this use case not work as expected.
On my behalf I would suggest using normal, fully functional template engine. I understand, that small is great and everything, but for example Twig is much more advanced. So I would recommend it.
About mustache. Can't you just extend you translation method! For example you pass {{#__}}Account Balance: #balance#{{/__}}
function __( $string, $replacement )
{
$replaceWith = '';
if ( 'balance' == $replacement )
{
$replaceWith = 234.56;
}
return str_replace( '#' . $replacement . '#', $replaceWith, $string );
}
class my_i18n
{
public function __trans( $string )
{
$matches = array();
$replacement = '';
preg_match( '~(\#[a-zA-Z0-9]+\#)~', $string, $matches );
if ( ! empty( $matches ) )
{
$replacement = trim( $matches[0], '#' );
}
return __( $string, $replacement );
}
}
$Mustache = new Mustache();
$template = '{{#__}}Some lime #tag#{{/__}}';
$MyTache = new mytache();
echo $Mustache->render( $template, $MyTache );
This is a very ugly example, but you can make it fancy yourself. As I see Mustache on it's own will not be able to do what you want.
Hope that helped.
I need to make a small and simple php template engine I searched a lot and many of them were too complex to understand and I don't want to use smarty and other similar engines, I have got some idea from Stack Overflow like this:
$template = file_get_contents('file.html');
$array = array('var1' => 'value',
'txt' => 'text');
foreach($array as $key => $value)
{
$template = str_replace('{'.$key.'}', $value, $template);
}
echo $template;
Now instead of echo the template I just want to add include "file.html" and it will display the file with correct variable values and I want to put the engine in a separate place and just include it in the template what I want to use it declare the array and at the end include the html file like phpbb. Sorry I am asking to much but can anyone just explain the basic concept behind this?
EDIT: Well let me be frank i am making a forum script and i have got tons of ideas for it but i want make its template system like phpbb so i need a separate template engine custom one if you can help then please you are invited to work with me. sorry for the ad.. :p
file.html:
<html>
<body>
<h3>Hi there, <?php echo $name ?></h3>
</body>
</html>
file.php:
<?php
$name = "Keshav";
include('file.html');
?>
Doesn't get simpler than this. Yes, it uses global variables, but if simple is the name of the game, this is it. Simply visit 'http://example.com/file.php' and off you go.
Now, if you want the user to see 'file.html' in the browser's address bar, you'd have to configure your webserver to treat .html files as PHP scripts, which is a little more complicated, but definitely doable. Once that's done, you can combine both files into a single one:
file.html:
<?php
$name = "Keshav";
?>
<html>
<body>
<h3>Hi there, <?php echo $name ?></h3>
</body>
</html>
What if, for a script easier to maintain, you move those to functions?
something like this:
<?php
function get_content($file, $data)
{
$template = file_get_contents($file);
foreach($data as $key => $value)
{
$template = str_replace('{'.$key.'}', $value, $template);
}
return $template;
}
And you can use it this way:
<?php
$file = '/path/to/your/file.php';
$data = = array('var1' => 'value',
'txt' => 'text');
echo get_content($file, $data);
Once you iron out all bugs, fix huge performance problem you're getting yourself into, you'll end up with template engine just like Smarty and otheres.
Such find'n'replace approach is much slower than compilation to PHP. It does not handle escaping very well (you'll run into XSS problems). It will be quite difficult to add conditions and loops, and you will need them sooner or later.
<?php
class view {
private $file;
private $vars = array();
public function __construct($file) {
$this->file = $file;
}
public function __set($key, $val) {
$this->vars[$key] = $val;
}
public function __get($key, $val) {
return (isset($this->vars[$key])) ? $this->vars[$key] : null;
}
public function render() {
//start output buffering (so we can return the content)
ob_start();
//bring all variables into "local" variables using "variable variable names"
foreach($this->vars as $k => $v) {
$$k = $v;
}
//include view
include($this->file);
$str = ob_get_contents();//get teh entire view.
ob_end_clean();//stop output buffering
return $str;
}
}
Here's how to use it:
<?php
$view = new view('userprofile.php');
$view->name = 'Afflicto';
$view->bio = "I'm a geek.";
echo $view->render();