1. Proyecto: Creación de un módulo para prestashop
Modelo de clases reutilizable para futuros módulos
Creado por: Laurent Raulier 30 de julio de 2008
Corregido por: Ludovic Bodin 14 de agosto, 2008
Traducción: Noel López 5 de septiembre 2008
Objetivo del proyecto:
Proporcionar una forma sencilla de crear nuevos módulos para la solución de
comercio electrónico Prestashop mediante la creación de un "patrón de diseño" para
la estructura de un módulo.
Medios:
El uso de las actuales clases y métodos de prestashop.
**Para seguir este manual es necesario tener nociones de Programación orientada a objetos.
2. Índice
A. Creación de módulo Prestashop
..................................................................................................................................................
4
A.1 Árbol Prestashop
........................................................................................................................................
4
A.2 Tipos de módulos
.......................................................................................................................................
5
A.3 Patrón de diseño
.........................................................................................................................................
5
A.4 Las traducciones de módulos
.....................................................................................................................
6
A.5 El código de nuestro módulo
.....................................................................................................................
8
B. El Backoffice
................................................................................................................................................
11
B.1 Terminología
............................................................................................................................................
11
B.2 Los módulos
.............................................................................................................................................
12
C. El Frontoffice
................................................................................................................................................
15
C.1 Presentación
.............................................................................................................................................
15
C.2 La adición de un método de Frontoffice
.................................................................................................
16
C.3 Modificar la posición
...............................................................................................................................
19
D. Los métodos genéricos
.................................................................................................................................................
21
D.1 Generalidades
............................................................................................................................................
21
D.2 El uso de estos métodos
...........................................................................................................................
22
D.3 Caso de estudio
.........................................................................................................................................
22
4. La parte que nos interesa aquí es, por supuesto, el directorio "módulos". En la figura pueden
verse un árbol que contiene los módulos incluidos por defecto en la instalación de
Prestashop.
Estos módulos se dividen en varios tipos de módulos, hay módulos ' block ' que representan
en sí mismos un tipo de objeto utilizable y también hay módulos más "Genéricos".
A.2 Tipos de módulos
Todos los módulos están organizados de la misma manera, haciendo así más sencillo crear
un nuevo módulo.
Se podría hablar de un "patrón" de los módulos Prestashop. Y este es relativamente simple.
A.3 Patrón de diseño
Vamos a tomar como ejemplo el módulo prestashop / modules / blockcategories.
5. Todo módulo incluye como mínimo un archivo <nombreMódulo>.php y otro
<nombreMódulo>.tpl.
En nuestor ejemplo de Blockcategories el árbol de directorios sería:
+ Blockcategories
Blockcategories.php
Blockcategories.tpl
Con ya dije, con estos dos archivos es suficiente para crear un nuevo módulo.
En el directorio de ejemplo, se puede observar también la presencia de un archivo logo.gif
que va a ser el icono que se muestre en la tabla de módulos de administración (Back office).
Encontramos también otros archivos (script php) representados con abreviaturas de códigos
de países:
es: Español
en: Inglés
fr.: Francés
d: Alemán
nl: Holandés
..
A.4 Las traducciones de módulos
Los script php cuyo nombre está formado por las abreviaturas de códigos de países
contienen las traducciones del módulo a esa lengua. En la siguiente imagen podemos ver una
captura del archivo fr.php del módulo blockcategories.
7. Nos permite traducir todas las expresiones que se quieran, de un idioma a otro, y de esta
forma podemos reutilizar para cualquier idioma tanto los script php como las plantillas
Smarty.
Estos archivos de idiomas, para ser utilizados en las traducciones, deben encontrarse en el
directorio raíz del módulo. Prestashop no los encontrará si se encuentran por ejemplo en los
subdirectorios.
La traducción se hará de la misma manera para expresiones, siempre encapsulando en el
"Método de traducción," para los archivos presentes en el frontoffice (raíz prestashop) y en
las plantillas (templates) de temas prestashop (prestashopDemo /themes/prestashop).
A nivel práctico, lo mejor es construir su módulo con un idioma de "referencia" y a través
del Back office utilizar la herramienta de "traducción" para editar otros idiomas y guardar
sus expresiones traducidas.
Dependiendo de la elección de idioma en el menú accederás a las expresiones a traducir.
En las plantillas, la sintaxis de la llamada es un poco diferente:
{l s = 'la expresión' mod = 'el módulo'}
Si no se pone el atributo mod, la expresión no se traducirá correctamente.
Otros comentarios: utilizar sólo las comillas simples, escapar de apóstrofes con barra
invertida: y después de copiar y pegar cuidado con duplicar espacios "White" (blancos).
A nivel del árbol de directorios, si es necesario agregar clases y funciones "enlazadas" al
módulo, por convenio crearemos un directorio (llamado "Class" por ejemplo) para
almacenarlas.
De hecho, en el script básico del módulo se deben incluir solo las funciones y constructores
necesarios (su interfaz) y las funcionalidades que se desee añadir mejor en archivos
separados.
Para crear nuestro nuevo módulo, hace falta un mínimo de código (todo lo que aún no se ha
automatizado) para indicar la información relativa a este módulo (nombre, el que parte del
administrador…), como una clase, un constructor y los métodos.
A.5 El código de nuestro módulo
Nuestra clase módulo va a heredar de la clase Module (prestashop / clases / Module.php) y,
por tanto, ya tendrá unas pocas propiedades y métodos desde el principio.
8. class BlockCategories extends Module
{
function __construct()
{
$this->name = 'blockcategories';
$this->tab = 'Blocks';
$this->version = ‘1.0’
}
}
El nombre de la clase es la del directorio con una mayúscula. Los desarrolladores Prestashop
utilizan una notación UpperCamelCase como una extensión de la clase madre.
function __construct :
El constructor de nuestra clase tendrá los siguientes atributos:
$this->name = 'blockcategories' :
El atributo name contiene el nombre de nuestra clase.
$this->tab = 'Blocks'
"Asignación de un valor a la pestaña (tab)" Este atributo contiene "el valor utilizado en la
tabla de módulos de gestión en la administración (Back office); nuestro módulo se mostrará
en el grupo de módulos Block (si ponemos el valor del ejemplo)….
$this->version = ‘1.0’
la version !
La “raíz” del código….
$this->page = basename(__FILE__, '.php');
$this->displayName = $this->l('Categories block');
$this->description = $this->l('Adds a block featuring product
categories');
$this->page = basename(__FILE__, '.php');
La asignación de la ruta del script.
Comentario: la función basename separa el nombre del fichero y el nombre de la carpeta,
__FILE__ contiene la ruta completa y el nombre de archivo (desde PHP 4.0.2, __FILE__
contiene la ruta absoluta)
$this->displayName = $this->l('Categories block');
El nombre que se utilizará en Prestashop. Utiliza el método "l" del que hemos hablado
anteriormente, así que será traducido por Prestashop.
Si comienzas a escribir la información en Inglés (que es una buena cosa para la
internacionalización del módulo), se continuará en esta idioma… no es bueno que se mezcle
Inglés con francés por ejemplo, o todo quedará muy mezclado.
9. $this->description = $this->l('Adds a block featuring product
categories');
Una breve descripción del modulo, también traducido.
Y finalmente…
parent::__construct ();
Llama al constructor de la clase padre.
Un pequeño ejemplo:
Estructura:
+ Módulo
+ Unmodule
unmodule.php
unmodule.tpl
Código unmodule.php:
<?php
class UnModule extends Module
{
function __construct()
{
$this->name = 'unmodule';
$this->tab = 'Divers';
$this->version = 1.0;
parent::__construct();
$this->page = basename(__FILE__, '.php');
$this->displayName = $this->l('Module block');
$this->description = $this->l('Adds a block featuring nothing');
}
function install()
{
if(parent::install() == false)
return false;
return true;
}
}
?>
10. Resultados en la parte de administración:
El resultado de nuestro código estará presente en los módulos y accesible desde el Back
office a través de la pestaña Módulos
Si lo instalas no hará nada más que llamar al método de instalar de la clase padre
Module.php , que registrará los datos en la tabla module de la base de datos.
Añadir un icono en el back office:
Para añadir un icono al módulo desde el "Back office" simplemente hay que insertar una
imagen de 16x16 en el directorio del módulo nombrándolo como logo.gif.
Puedes encontrar más de 700 mini iconos en www.famfamfam.com/lab/icons/silk/
B. El Backoffice
B.1 Terminología
Con el fin de comprendernos y hablar de las mismas cosas, ya que a veces la terminología
Back office y Front office se confunde en un sitio de comercio electrónico, vamos a realizar
un pequeño repaso de términos que nunca viene mal:
11. Back office: literalmente "trastienda" lo que el cliente no ve, lo que el usuario del sitio no
sabe ni siquiera que existe.
Front office: es la parte visible, la interfaz de usuario, por la que se puede interactuar con
la empresa, la web.
Diseño MVC: (modelovistacontrolador), basado en que cada una de las partes está
separada.
Ahora que tenemos claros los términos podemos entrar en la "Trastienda".
B.2 Los módulos
La parte que nos preocupa en este momento es la de los módulos
Para llegar hasta aquí (Back office), tienes que identificarte…. Esto demuestra que todo el
mundo no puede tocarlo!
Lo interesante es que podemos actuar en un módulo y, por tanto, modificar algunos
parámetros de información desde el Back office.
Por lo tanto, desde el Back office más que crear el módulo lo personalizamos, así los
módulos son de uso más flexible y cubrimos mayor número de situaciones.
No estoy hablando del color de un botón de comandos, hablo más bien de oportunidades y
opciones disponibles para administrar el sistema.
En el momento de la concepción del módulo, suelta el teclado y organiza tu módulo antes de
escribir el código…
12. Piensa en si el módulo será accesible para el usuario (módulo de prestación de nuevas
funciones para el usuario) o sólo para el administrador (módulo de control y administración).
¿Cuáles son las funciones que va a hacer (el método)? ¿Qué datos debo tener (acceso a la
base de datos, recuperación de datos desde formularios)? ¿Cuál debería ser el resultado a la
salida? ¿Qué salida? Y si tengo que mostrar, ¿cómo?
Aquí está una lista (no es exhaustiva) de preguntas.
Para acceder a la configuración del módulo, se debe añadir el siguiente método a nuestro
módulo:
public function getContent ()
{
// Instruciones ….
}
Este poco código, añade en el Back office un enlace hacia la página de configuración
asociada.
Si se sigue el enlace, la página destino estará vacía de momento.
Tenga en cuenta que es interesante pasar los parámetros por el método GET:
/index.php?
tab=AdminModules&configure=unmodule&token=604ae5....etc
Por otro lado, para que un módulo se "añada" a una parte de "Front office" deberá referirse a
un "gancho" (Hook) que debe existir en la tabla Prestashop del mismo nombre.
Las tablas de Hook presentes en Prestashop son:
1. payment 13. updateproduct 24.
2. newOrder 14. top orderConfirmation
3. paymentConfirm 15. extra 25. createAccount
4. paymentReturn 16. deleteproduct 26.
5. updateQuantity 17. productfooter customerAccount
6. rightColumn 18. invoice 27. orderSlip
7. leftColumn 19. 28. productTab
8. home updateOrderStatus 29.
9. header 20. adminOrder productTabContent
10. cart 21. footer 30. shoppingCart
11. authentication 22. PDFInvoice
12. addproduct 23. adminCustomers
13. El nuevo código quedara así:
class BlockUnModule extends Module
{
function __construct()
{
$this->name = 'blockunmodule';
$this->tab = 'Divers';
$this->version = 1.0;
parent::__construct();
$this->page = basename(__FILE__, '.php');
$this->displayName = $this->l('Module block');
$this->description = $this->l('Adds a block featuring
nothing');
}
function install()
{
if(parent::install() == false OR !$this-
>registerHook('leftColumn'))
return false;
return true;
}
public function getContent()
{
}
function hookLeftColumn($params)
{
global $smarty;
return $this->display(__FILE__, 'blockunmodule.tpl');
}
function hookRightColumn($params)
{
return $this->hookLeftColumn($params);
}
}
Uno de los puntos más importante es la “llamada”, de nuestro módulo a ciertas partes del
Front office (en el caso de ser necesario obviamente).
La sintaxis utilizada es aquí la que los usuarios de Zend Framework seguramente conocen: la
concatenación del hook y el nom_del_hook: hookLeftColumn y hookRightColumn.
En este caso, si se quiere "enganchar" un módulo (su interfaz o parte visible) a la derecha, es
redirigido hacia la columna de izquierda... ¡No hacemos siempre lo que queremos en la vida!
14. Volveremos más tarde a esta noción del Back office y de la interacción entre módulos, pero
en esta primera aproximación a la concepción de un nuevo módulo de Prestashop, nos
quedaremos en lo esencial y a continuación pasamos a interesarnos por como mostrar el
Front office y en el código del fichero ' blockunmodule.tpl ' (la vista).
C. El Frontoffice
C.1 Presentación
Como hemos señalado anteriormente el Front office es la parte visible por el usuario de
nuestro sitio de comercio electrónico.
La página está organizada en distintas partes, tanto para la organización de módulos como
para el diseño CSS.
Para comprender mejor esta estructura basta con abrir su "demoPrestashop " con su
navegador favorito (" Fire Fox") y habilitar firebug...
Ps: firebug es una extensión de Fire Fox indispensable para cualquier desarrollo web.
Cuando enganches el módulo gracias al método HookLeftColumn desde su clase vas a
añadir éste al "div" identificado por su "id" left_column (una etiqueta div css es un tipo de
bloque, es decir, que puede contener otros elementos).
15. No creo que sea solamente una coincidencia que los desarrolladores de Prestashop asociaron
bien estas 2 partes en el momento de la concepción del proyecto.
Si miras un poco más cerca la captura de pantalla este "block" contiene el block de la clase
myaccount.
Es importante entender la forma en que estos módulos están organizados y su
relación con la “vista” de las hojas de estilo css.
Debería estar familiarizado con las hojas de estilo si no es así… una dirección: alsacréations
(en francés)
Es hora de interesarse un poco más por nuestro archivo que va a añadir un
precioso superbloque en la columna izquierda.
Blockmonmodule.tpl
Sea cual sea el nombre que le des, indícalo en el método hookLeftColumn return $this>
display (__FILE__, 'blockmonmodule.tpl ») , esto significa que nuestro archivo .tmp está
en el directorio actual…. Sin embargo, puedes ponerlo donde quieras he indicar la ruta.
Ps: fijate bien en el nombre del .tpl ya que si copias el ejemplo debes cambiar
blockunmodule.tpl por blockmonmodule.tpl o bien crear el .tpl con el nombre
blockunmodule.tpl.
Deberíamos abordar aquí parte de Smarty y de css aunque este no es el propósito de este
tutorial. Así que vamos a “pasar de puntillas” esta parte y tendremos en cuenta solamente lo
relacionado directamente con la creación del módulo.
C.2 La adición de un método de Frontoffice
Si se prevé que nuestro módulo añadirá funcionalidades para los usuarios, hay que agregar,
naturalmente, los métodos que tengan en cuenta las acciones realizadas por este usuario.
Por lo tanto, desde el bloque previamente añadió, señalamos un lugar (una nueva página)
que se encargará de tratar estas acciones de los usuarios.
En nuestro archivo 'blockmonmodule.tpl', añadiremos un enlace a esta nueva página. En
cuanto al estilo reutilizamos el de Prestashop, mediante la aplicación de clases css ya
definidas en el archivo global.css ( Demo prestashop temas prestashop css global.css).
<!-- Block unmodule -->
<div id="categories_block_left" class="block">
<h4>{l s='Block' mod='blockunmodule'}</h4>
<div class="block_content">
<ul>
<li><a href="modules/blockunmodule/classes/unmodule.function.php"
title="nouvelles fonctions utilisateurs">
16. {l s='Nouvelles fonctions' mod='blockunmodule'}
</a></li>
</ul>
</div>
</div>
<!-- /Block unmodule -->
Si has seguido correctamente el tutorial desde el comienzo, deberás tener en tu Front office
un bonito bloque con un enlace a nuestro script unmodule.function.php que se encontrará en
nuestro repertorio de clases del módulo.
Podríamos poner este fichero de script directamente con los otros ficheros de script del
"Front", pero es mejor tenerlo en nuestro módulo, para ser coherentes con nuestra
manera de hacer proyectos y poder instalarlo fácilmente.
Ps: un pequeño detalle "purista" respecto del código de nuestro módulo de la columna
izquierda:
No es necesario e incluso puede considerarse como "divites" utilizar una etiqueta "div" que
contenga una etiqueta "ul", de hecho, las etiquetas' ul ya están etiquetadas como Type
bloque y es inútil, por lo tanto, el encapsular en un div superfluo. La mejor solución sería
aplicar el estilo directamente al ul:
<ul class=’block_content’>
// vos listes ...
</ul>
Como ya dijimos el link “Nouvelles fonctions” apunta al script que contendrá nuestras
funciones.
Para hacer una prueba, puedes añadir un " hello mis nuevas funciones " en el fichero
blockunmoduleclassesunmodule.function .php.
Un poco decepcionante el resultado, pero es el esperado, y podemos observar que nuestro
fichero php funciona.
Con carácter excepcional, hablaremos un poco de Smarty para poder hacer esta página
"Prestashop".
Smarty es la biblioteca que maneja la “vista” de nuestra aplicación. En pocas palabras
Smarty analiza de forma dinámica las partes entre corchetes “{…}” para transformarlas en
código html legible por el navegador.
20. D. Los métodos genéricos
D.1 Generalidades
No se trata de una lista exhaustiva de métodos Prestashop para que puedas generar la
documentación con PHPDocumentor o yendo directamente a doc prestashop, sino más bien
un recordatorio de las oportunidades que ofrece esta clase de herramientas.
Conviene revisarlos antes de escribir una función para comprobar que no esté ya presenta en
PrestaShop.
Algunos ejemplos:
Si está trabajando del lado del Backoffice sin duda necesitará en algún momento un
identificador; este se pasa como un argumento GET en la URL, algo como esto:
token = 604ae5e405da9eb32fc414a….
La herramienta siempre da 2 métodos: getToken y getAdminToken
Puede asociar la herramienta: ::getValue a menudo.
Si se utiliza un formulario y envías datos con los métodos GET o POST, para comprobar
si un valor está presente, puede utilizar el método isSubmit.
Si tienes que enviar una SQL, para recuperar la conexión "Persistente" a la base de datos
desde la clase Db puedes hacerlo fácilmente con el método getInstance.
Y luego utilizar Ejecutar para enviarlo a mysql.
En muchos casos, será útil poder recuperar un valor de configuración de la tabla
"configuración".
En lugar de reescribir una función, puedes usar Configuration ::get (nom_configaration).
Tienes que basarte en tu experiencia y buscar las funciones que se adapten a ti….
Para hacer esto, basta con hacer una sencilla prueba
echo Configuration ::get('BLOCK_CATEG_MAX_DEPTH' );
Configuration::updateValue('BLOCK_CATEG_MAX_DEPTH',
intval($valeur));
echo Configuration ::get('BLOCK_CATEG_MAX_DEPTH' );
Vea los cambios en la tabla ‘configuration’, y el efecto producido sobre el sitio.
Prueba a editar un script y ver el código y las observaciones formuladas por los
desarrolladores de Prestashop.
21. Escribe sentencias SQL y prueba los métodos dB (véase la ficha de clase resumen Db.
Clase) ... y modifica el código que no dejará de tener errores!
D.2 El uso de estos métodos
He sentido que te he dejado un poco frustrado al final del capítulo anterior, cuando te dejé
con una página en blanco y un título <h2>.
Pero después de esta breve presentación de métodos "genéricos" (que yo mismo llamo
"utilidades"), podemos crear el código de una aplicación simple que permita a los usuarios
ver la información sobre sus últimas órdenes y si el cliente ha echo más de 2 pedidos
ofrecerle un foto de un coche…
D.3 Caso de estudio
Lo primero, necesitamos el nombre del cliente; recuperar su número de identificación de
cliente (en id_customer prestashop), el número de pedidos asociados con este cliente, si el
número de pedidos es estrictamente superior a 1 ganó un coche…
Estas son las "funciones" a programar:
o Si el cliente está conectado recuperar su identificador si no, no se hace nada.
o Prepara la consulta SQL.
o Hacer la conexión a la base de datos con Db: getInstance y ejecutar la consulta.
o Tratar la respuesta (la clasificación y del tratamiento).
o Prepara los datos para pasar a Smarty
o Mostrar la respuesta
Ya tenemos nuestros 2 archivos: unmodulefunction.php y unmodulefunction.tpl.
El usuario llegará a la página de tratamiento a través del enlace en nuestro módulo; en
nuestro script actuaremos en función de su estado: conectado o no
global $smarty;
include('../../../config/config.inc.php');
include(_PS_ROOT_DIR_.'/header.php');
// Le client est il connecté ou non
// Si pas connecté
if (!$cookie->isLogged())
{
// Données à transmettre
$message = "pour profiter de tout nos avantages et connaitre les
derniers infos, merci de vous identifiez";
$smarty->assign('message',$message);
}
else
22. {
// Données à transmettre
$message = "bonjour";
$smarty->assign('message',$message);
}
$smarty->display(dirname(__FILE__).'/unmodulefunction.tpl');
include(_PS_ROOT_DIR_.'/footer.php');
Pon a prueba tu código ...
Si el cliente está registrado, es mejor darle la bienvenida mostrando su nombre, o nombre y
apellido, el caso es hacerle saber que está claramente identificado y que ha sido reconocido.
Los datos están en la base de datos, y podemos usar el ID para recuperar datos que se
necesitan:
$customer = new Customer(intval($cookie->id_customer));
No hay nada particular...
Vamos a pasar la información que necesitamos mostrar a nuestro gestor de Smarty; pero no
será necesario pasar toda la información.
En un primer paso, ponemos los valores que queremos excluir de la tabla:
$exclusion = array('secure_key', 'old_passwd', 'active',
'date_add','date_upd');
Esto es sólo un ejemplo, por supuesto, adapta la tabla como necesites.
Vamos a construir nuestro "usuario" como parámetro a Smarty teniendo en cuenta nuestras
excepciones (campos excluidos).
$fields = $customer->getFields();
foreach ($fields AS $key => $value)
if (!in_array($key, $exclusion))
$customer -> {$key};
De nuevo nada especial, fijate en la sintaxis: $customer> ($ Key);
Vamos a hacer una pequeño inciso "técnico". Un nombre de variable se pone de la siguiente
manera:
$nom = ‘123’ ; / / no es un nombre válido
Utilizando:
$key -> {‘123’} ; / /'123' será "permitido" como nombre de variable…
23. Una simple prueba:
$nom = ‘123’ ;
$$nom = ‘456’ ;
echo ${‘123’} ; / / muestra '456'
Para terminar:
$smarty->assign('customer',$customer);
Y en el código .tpl:
{$message}, {$customer->firstname}
Preparemos ahora nuestro SQL; recordar que necesitamos seleccionar el número de compra
ya pasado por nuestro cliente identificado en $cookie> id_customer
$sql = "SELECT count( `id_order` )
FROM `orders`
JOIN `customer` ON `customer`.`id_customer` =`orders`.`id_customer`
WHERE `customer`.`id_customer` = $cookie->id_customer";
Utilizaremos los métodos de la clase abstracta Db para ejecutar nuestra Quero. Y tambien
como argumento para Smarty
$reponse = Db::getInstance() -> ExecuteS($sql);
$smarty -> assign('commande',$nombre_commande[0]);
En nuestro archivo .tpl:
{$commande}
Como es lógico probamos el resultado antes de continuar. Todo funciona como estaba
previsto?
Proseguimos: Consulta en la “tabla” de nuestro proyecto “adicción de funciones de usuario”.
Lo que se muestra a continuación es el resultado del tratamiento:
Dependiendo de si el resultado es estrictamente menos de 1: mensaje usuario
Y si es por encima de 1: vista del coche ganado + mensaje
El tratamiento se puede hacer en el script php o en Smarty.
Es preferible tratar los datos en el script y enviar sólo lo estrictamente necesario a la vista
para no mezclar todo.
24. switch($nombre_commande[0])
{
case 0 : {
$msg = "Aucune commande enregistrée à votre nom";
$img = _PS_IMG_."logo.jpg";
}
break;
case 1 : {
$msg = "Encore une commande pour gagner !";
$img = _PS_IMG_."logo.jpg";
}
break;
case 2 : {
$msg = "Bravo ! vous avez gagnez une voiture";
$img = _PS_IMG_."voiture.jpg";
}
break;
default : $msg = "non renseigné"}
Bueno, a fuerza de transmitir valores a smarty, repite la misma sintaxis.
A continuación se utiliza una matriz de valores:
$smarty -> assign(array('customer'=>$customer,
'message'=>$message,
'commande'=>$msg,
'image' =>$img));
Y no olvides añadir las imágenes que quieres mostrar, en el ejemplo se encuentran en demo /
prestashop / img pero nada es obligatorio y tu función de usuario es funcional.
A crear tus propios módulos…
Ahhh sí… el coche que se gana: