OpenCart es una plataforma de comercio electrónica hecha en php, con un desarrollo impecable, 100% MVC. Como prueba de concepto he reescrito el sistema de plantillas de OpenCart a Haanga (plantillas “Django” para PHP, über eficiente) de César Rodas.
¿ Por que Haanga ?, simplemente porque me gusta.
¿ Por qué OpenCart ?, simplemente porque me gusta como está desarrollado.
Implementación, he creado una clase TemplateEngine con el siguiente código:
class TemplateEngine {
public $template = null ;
public $default = array();
public $config = array();
function __construct( $templateDir = null ) {
$this->loadDefaults( $templateDir);
}
protected function loadDefaults( $templateDir) {
$this->default = array();
$this->config = array(
'template_dir' => (( is_null( $templateDir )) ? Settings::getInstance()->getValue('Web.root') .'templates' : $templateDir),
'cache_dir' => Settings::getInstance()->getValue('Web.root'). 'cache.templates',
'compiler' => array(
'if_empty' => FALSE,
'autoescape' => FALSE,
'strip_whitespace' => TRUE,
'allow_exec' => TRUE,
'global' => array( ),
'use_hash_filename' => FALSE
)
);
}
function loadEngine() {
// incluimos Haanga
require_once Settings::getInstance()->getValue('Web.root') . 'lib/vendors/Haanga.php';
Haanga::configure($this->config);
}
function loadTemplate( $name ) {
$this->template = $name ;
}
function display( $vars = array()) {
$this->loadEngine();
$vars = array_merge( $this->default, $vars);
Haanga::Load( $this->template , $vars);
}
}
Ahora sólo queda indicarle al opencart que haga uso de esta clase:
en /system/engine/controller.php
Modificando los métodos render y fetch, renombrando el antiguo fetch a __fetch
[...]
protected function render($return = FALSE) {
foreach ($this->children as $child) {
$action = new Action($child);
$file = $action->getFile();
$class = $action->getClass();
$method = $action->getMethod();
$args = $action->getArgs();
if (file_exists($file)) {
require_once($file);
$controller = new $class($this->registry);
$controller->index();
$this->data[$controller->id] = $controller->output;
$this->addToModule( $controller->id, $controller->output );
} else {
exit('Error: Could not load controller ' . $child . '!');
}
}
if ($return) {
return $this->fetch($this->template);
} else {
$this->output = $this->fetch($this->template);
}
}
protected function addToModule( $module , $output ) {
$i = 0 ;
if ( !isset( $this->data['modules'] )) { return ;}
foreach( $this->data['modules'] as $item ) {
if ( $item['code'] == $module ) {
$this->data['modules'][$i]['output'] = $output;
return ;
}
$i++;
}
}
protected function fetch($filename) {
if ( substr( $_SERVER['REQUEST_URI'], 0, 7) == '/admin/') {
return $this->__fetch( $filename);
} else {
ob_start();
$this->templateEngine->loadTemplate( $filename );
$this->templateEngine->display( $this->data);
$content = ob_get_contents();
ob_end_clean();
return $content;
}
}
protected function __fetch($filename) {
$file = DIR_TEMPLATE . $filename;
if (file_exists($file)) {
extract($this->data);
ob_start();
require($file);
$content = ob_get_contents();
ob_end_clean();
return $content;
} else {
exit('Error: Could not load template ' . $file . '!');
}
}
[...]
Para evitar utilizar Haanga en la administración se comprueba que la url no sea de la administración, de ser así se utiliza el motor de plantillas original.
[...]
if ( substr( $_SERVER['REQUEST_URI'], 0, 7) == '/admin/') {
return $this->__fetch( $filename);
[...]
Una de las particularidades de las plantillas es que hacen uso de $$variable a la hora de mostrar la salida de los módulos, y es por esto (imposible, por lo que parece, en Haanga) que he añadido un método y comprobación para que cada módulo devuelva a la plantilla su salida en un elemento “output” de la matriz y es el que mostraremos.
Así, al final, una plantilla como la de la visualización de las categorías queda en algo como los siguientes ejemplos:
/catalog/view/theme/default/common/column_right.tpl
<div id="column_right">
{% for module in modules %}
{{ module.output }}
{% endfor %}
</div>
/catalog/view/theme/default/product/category.tpl
{% extends "layout/default.html" %}
{% block content %}
<div id="content">
<div class="top">
<div class="left">
</div>
<div class="right">
</div>
<div class="center">
<h1>
{{ heading_title }}
</h1>
</div>
</div>
<div class="middle">
<table style="padding-bottom:10px;">
<tr>
{% if thumb %}
<td>
<img src="{{ thumb }}" alt="{{ heading_title }}" />
</td>
{% endif %}
{% if description %}
<td>
{{ description }}
</td>
{% endif %}
</tr>
</table>
{% if !categories && !products %}
<div class="content">
{{ text_error|default:"" }}
</div>{% endif %}
{% if categories %}
<table>
{% for category in categories %}
<a href="{{ category['href'] }}"><img src="{{ category['thumb'] }}" title="{{ category['name'] }}" alt="{{ category['name'] }}" style="margin-bottom: 3px;" /></a><br />
<a href="{{ category['href'] }}">{{ category['name'] }}</a>
</table>
{% endfor %}
{% endif %}
{% if products %}
<div class="sort">
<div class="div1">
<select name="sort" onchange="location = this.value">
{% buffer sort_order %}{{sort}}-{{order}}{% endbuffer %}
{% for sort in sorts %}
<option value="{{ sort['href'] }}" {% if sort_order == sort['value'] %} selected="selected"{% endif %}>{{ sort['text'] }}</option>
{% endfor %}
</select>
</div>
<div class="div2">
{{ text_sort }}
</div>
</div>
{% inline "elements/lista_productos.html" %}
<div class="pagination">
{{ pagination }}
</div>
{% endif %}
</div>
<div class="bottom">
<div class="left">
</div>
<div class="right">
</div>
<div class="center">
</div>
</div>
</div>
{% endblock %}