Implement Haanga as a template system for OpenCart
4 min read

Implement Haanga as a template system for OpenCart

730 words

OpenCart is an e-commerce platform made in php, with impeccable development, 100% MVC. As a proof of concept, I have rewritten the template system of OpenCart to Haanga (Django templates for PHP, über efficient) by César Rodas.

Why Haanga?, simply because I like it.

Why OpenCart?, simply because I like how it is developed.

Implementation, I have created a TemplateEngine class with the following code:

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() {
			// include 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);

 		}

	}

Now we just need to tell opencart to use this class:

in /system/engine/controller.php

Modifying the render and fetch methods, renaming the old fetch to __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 . '!');
    	}
	}

[...]

To avoid using Haanga in the administration, we check that the url is not from the administration, if so we use the original template engine.

[...]
			if ( substr( $_SERVER['REQUEST_URI'], 0, 7) == '/admin/') {
				return $this->__fetch( $filename);
[...]

One of the peculiarities of the templates is that they make use of $$variable when displaying the output of the modules, and it is because of this (impossible, it seems, in Haanga) that I have added a method and check so that each module returns its output to the template in an “output” element of the matrix and it is the one we will show.

Thus, in the end, a template like the one for displaying categories remains something like the following examples:

/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  &#038;&#038;   !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 %}
                   &lt;option value="{{ sort['href'] }}" {% if sort_order == sort['value'] %} selected="selected"{% endif %}>{{ sort['text'] }}&lt;/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 %}

Comments

Latest Posts

6 min

1138 words

The first alpha version of PHP 8.5 has just been released, and I must confess it has me more excited than recent versions. It’s not just for the technical improvements (which are many), but because PHP 8.5 introduces features that will change the way we write code.

And when I say “change,” I mean the kind of changes that, once you use them, you can’t go back. Like when the null coalescing operator (??) appeared in PHP 7, or arrow functions in PHP 7.4.

4 min

678 words

A few days ago I read an article by Dominiek about the 5 principles for using AI professionally and found myself constantly nodding. After years of watching technologies arrive and evolve, AI gives me the same feelings I had with other “revolutions”: enthusiasm mixed with a necessary dose of skepticism.

Dominiek’s article especially resonated with me because it perfectly describes what we’re experiencing: a world where AI is getting into everything, but not always in the most useful or sensible way.

6 min

1149 words

Idempotency in Laravel: How to Avoid Duplicates in Your APIs with Elegance

In modern API development, one of the most critical challenges is ensuring that operations don’t execute multiple times accidentally. Imagine a user making a payment and, due to connectivity issues, clicking the “Pay” button multiple times. Without proper measures, you might process multiple payments for the same transaction. This is where idempotency comes into play.

What is Idempotency?

Idempotency is a mathematical concept applied to programming that guarantees that an operation produces the same result regardless of how many times it’s executed. In the context of APIs, it means you can make the same request multiple times without causing additional side effects.