Puede que tu PrestaShop sea lo más maravilloso del mundo, puede que no. Puede que haya algo que no te termine de convencer y quieras cambiar, puede que tu mente haya forjado una oscura idea y no encuentres forma comercial para ponerla en práctica. Por ello tienes la libertad (y el deber) de crear y modificar tu tienda. Tu tienda es tu morada y no hay que pedir licencia de obra al ayuntamiento para poner un banner con musica horrenda (lo de horrendo ya cada uno…) que suene hasta con los altavoces apagados y a pantalla completa ofertando el fin del mundo a 1€.

Bromas a parte, crear un módulo en PrestaShop es, sabiendo algo de programación, muy sencillo.  Pero también puede ser muy muy complicado, tan complicado como la idea que tu mente haya forjado.

Esta guía va dirigida a PrestaShop 1.6, habrá cosas que sirvan en la rama 1.5 y cosas que no servirán en la 1.7. Como mi blog está vivo (le doy poco de comer, de ahí que esté flacucho) iré añadiendo observaciones sobre la versión 1.7. También añadir que aquí voy a poner fragmentos de código que en conjunto no van a hacer nada, eres tu, lector, el que tendrá que aplicar lo que exponga aquí a tu módulo. Yo sólo te doy la llave y te muestro el camino a la puerta (y no, no es la de la calle).

En los ejemplos de este tutorial no voy a profundizar en particularidades de multi-idioma y multi-tienda, pero considero oportuno poner el detalle en tu conocimiento, así pues habrá momentos donde se mencionen estas 2 posibilidades aunque en los ejemplos no se utilicen.

En la documentación de PS y en los foros podemos encontrar información sobre cómo crear módulos. Como desarrollador he visto numerosas veces que la información es escasa y/o incompleta, y que el usuario del foro más activo en dar respuestas es el arbusto rodante… Olvidé mirar si estaba Clint Eastwood apoyado en un

de la página, mirándome.

La documentación oficial acerca de crear módulos la podeis encontrar aquí: http://doc.prestashop.com/display/PS16/Creating+a+PrestaShop+module
Bastante más completo pero que quizá no resolverá todas tus necesidades, como no resolvió las mías, es el libro: PrestaShop Module Development, de Fabien Serny (ISBN 978-1-78328-025-4)

Antes de nada indicar que PrestaShop tiene establecidas unas normas de codificación, las cuales podeis encontrar aquí: http://doc.prestashop.com/display/PS16/Coding+Standards.

También puntualizar que PrestaShop utiliza una arquitectura de software (o patrón de diseño) MVC (Modelo-Vista-Controlador), es por ello que cuando desarrolles un módulo no des una patada a lo establecido y metas espagueti por todas partes, por muy pastafari que seas 😉

En PrestaShop 1.5 y 1.6 se utiliza el motor de plantillas Smarty. Para la rama 1.7 va a ir desapareciendo este para dar paso a Twig, que es el motor de plantillas del nuevo core de PrestaShop, Symfony.

¿Por qué a estas alturas publico algo sobre desarrollo en PS 1.6? Oficialmente la rama 1.6 va a seguir en desarrollo hasta 2018, y las tiendas que esten en 1.6 no creo que vayan a migrar a 1.7, del mismo modo que hay infinidad de tiendas con 1.5.

¿Qué hay de nuevo en PrestaShop 1.7? http://build.prestashop.com/news/prestashop-1-7-and-symfony/

Cuando desarrollamos un módulo disponemos de algunas herramientas para el desarrollador por parte de PS, como es el modo de depuración de errores y el profiling. Ambos se activan en el fichero /config/defines.inc.php:

  • _PS_MODE_DEV_ en true hará que los errores sean más descriptivos, es decir, no dirá “error al guardar el cliente”, sino que dirá “el campo X del cliente no es válido”.
  • _PS_DEBUG_PROFILING_ en true hará que PS nos proporcione una gran cantidad de información técnica acerca de tiempos de carga por clase / controlador / módulo, usos de memoria y volcará también todas las consultas realizadas a la base de datos.

Comencemos por la estructura de un módulo. Los módulos se alojan en la carpeta /modules. Si en tu PS no existe esa carpeta, revisa el valor asignado a  _PS_MODULE_DIR_ en /config/defines.inc.php, puede que el que montó la tienda pecara de tuneador extremo y lo cambiara de sitio.

Pues lo primero es definir un nombre corto al módulo, que sea descriptivo y que no esté ocupado por otro módulo de los que ya tenemos. Como soy muy original lo voy a llamar “mi módulo”. Acto seguido creamos la carpeta del módulo, sin espacios ni caracteres “extraños”, vease “mimodulo”.

Perfecto, ahora creamos el archivo del mismo nombre mimodulo.php en la carpeta. Dentro de este archivo definimos una clase PHP que se llamará? Exacto! mimodulo, que extenderá a la clase nativa de PS Module. Si el módulo en cuestión es una pasarela de pago entonces extenderá a la clase PaymentModule. Antes de declarar la clase es conveniente comprobar si está definida la omnipresente _PS_VERSION_.

Estructura de carpetas

Aunque el módulo es tuyo y realmente puedes hacer con él lo que quieras, si por ejemplo lo quieres publicar en addons.prestashop.com deberas pasar por cumplir con los estandares establecidos. “Pero es que hay módulos que se pasan el estandar por…” Cierto, pero seguramente lleven ahí desde antes que hubiera un estandar establecido.

Aquí voy a hablar de todas las carpetas “estandar”. Puede que tu módulo realmente no necesite ni la mitad de las que se reflejan o que por el contrario lleve alguna que has pensado conveniente usar para alojar “algo”.

  • classes: aqui alojaremos las clases de nuestro módulo. Recuerda que PS usa patrón MVC y las clases se corresponden al modelo.
  • controllers: aqui se alojaran los controladores, que se iran separados en otras 2 carpetas admin para controladores del back-office y front para cotroladores del front-office
  • override: “no me gusta tu cara”, aqui es donde podrás alterar una clase o controlador nativo de PS y retorcerlo a tu gusto.
    2 cosas a saber: no está permitido hacer override en módulos que se comercialicen en PS Addons y PS 1.7 en principio no va a tener overrides debido a la propia naturaleza de los namespaces en PHP y a que el nuevo Core de PS 1.7  trabaja 100% con namespaces.
  • translations: aqui se alojaran las traducciones de nuestro módulo a los distintos idiomas. Si bien se recomienda no meter la zarpa ahí, en un momento dado no es complicado abrir un archivo de idioma ya generado y cambiar la traducción. Eso si, mucho cuidadín en cambiar la clave del array o no cargará la traducción.
  • upgrade: donde se alojaran los cambios de las distintas actualizaciones de nuestro módulo. Hablaremos de esto más adelante.
  • views: aquí se aloja todo lo relacionado con la vista, es decir: plantillas, scripts JS, hojas de estilo, imagenes… No hay un estandar de organización en cuanto a las carpetas para los archivos que no sean plantillas, yo desde aquí sugiero pero una vez más el desarrollador tiene total libertad para ponerlo al gusto.

El constructor

El constructor es nativo de PHP, más información sobre constructores/destructores en php.net

En PS la clase que define a nuestro módulo debe tener una serie de variables definidas por defecto, de las cuales las obligatorias son:

  • author: (string) el nombre de la persona o empresa que desarrolla y mantiene el módulo.
  • name: (string) el nombre técnico del módulo, sin esta variable PS no instalará el módulo.
  • description: (string) breve descripción del módulo.
  • displayName: (string) el nombre mostrado en la vista de módulos del back-office.
  • tab: (string) a que categoría pertenece el módulo, hace que el módulo sea más sencillo de localizar. Las categorias disponibles son: administration, advertising_marketing, analytics_stats, billing_invoicing, checkout, content_management, dashboard, emailing, export, front_office_features, i18n_localization, market_place, merchandizing, migration_tools, mobile, others, payments_gateways, payment_security, pricing_promotion, quick_bulk_update, search_filter, seo, shipping_logistics, slideshows, smart_shopping, social_networks.
  • version: (string) la versión del módulo, en formato x.x ó x.x.x. La importancia del valor de la versión reside en que si desarrollaramos una actualización, se compraran la versión instalada con la disponible a actualizar. Veremos eso más adelante en cómo se actualiza un módulo.

Luego tenemos otros parámetros que no son obligatorios pero que pueden hacernos falta:

  • bootstrap: (boolean) con esta variable indicamos si nuestro módulo va a cargar Twitter Bootstrap para nuestras plantillas o no. Valor por defecto: true. Esto en realidad es un arcaismo de la rama 1.5 y se mantiene por retrocompatibilidad.
  • dependencies: (array) un array que contendrá los módulos que necesitamos para que este funcione correctamente.
  • need_instance: (boolean) Si necesitamos que nuestro módulo se cargue al mostrar el tab de módulos en el back-office.  Sólo el módulo cronjobs tiene esta feature activa. Valor por defecto: 1.
  • ps_versions_compliancy: (array) este array contendrá 2 posiciones ‘min’ y ‘max’ que indicará el rango de versiones PS para el que es válido nuestro módulo. Si estamos seguros de que nuestro módulo va a funcionar para la rama 1.6 podemos usar la constante _PS_VERSION_, indicará que es válido para la versión actual.

Resumiendo, un ejemplo en código:

 

Por último podemos ponerle una imagen a nuestro módulo en lugar de esa (?) ¿Cómo? Escogemos la imagen deseada, en proporción 1:1, en formato PNG  y con un tamaño mínimo de 32×32 px, le ponemos al archivo el nombre logo.png y lo ubicamos en la carpeta de nuestro módulo. Yo personalmente le pongo 64×64 px para que se vea más nítida y siga siendo ligera.

mimodulo2

El icono utilizado en el ejemplo está bajo licencia Creative Commons CC0.

config.xml

Este archivo lo crea automáticamente PS, es un resumen de la configuración de nuestro módulo.



	mimodulo
	< ![CDATA[Mi primer modulo]]>
	< ![CDATA[1.0.0]]>
	< ![CDATA[Cómo crear modulos en PrestaShop]]>
	< ![CDATA[Gustavo Durán]]>
	< ![CDATA[front_office_features]]>
	0
	0
	

¿Ya está? Pero ¿¡dónde está el fin del mundo que prometías!? No te preocupes, continuamos!

Introducción a los objetos Context, Configuration, Db y Tools

PrestaShop tiene definidos multitud de objetos para su funcionamiento. Ahora mismo vamos a centrarnos en 4 de ellos con los que necesitaremos trabajar continuamente.

Context

En este objeto tenemos el entorno en el que estamos, cambiará dependiendo de si estamos en front-office o en back-office. Tiene un método estático para invocarla allá donde nos encontremos, así que grábate esto a fuego: Context::getContext()

Dentro de Context tenemos el cliente activo, el carro de la compra con todos los productos, el controlador activo… Por defecto PS ya tiene definido el contexto dentro de nuestro módulo, así que podeis invocarlo dentro de este mediante $this->context.

Iremos viendo las distintas posibilidades según vayamos necesitandolas.

Más información en: http://doc.prestashop.com/display/PS16/Using+the+Context+Object

Configuration

A través de este objeto podemos acceder a la configuración de PS almacenada en la base de datos, así pues podemos consultar y/o modificar parámetros almacenados e incluso almacenar la configuración de nuestro módulo.

Estos valores pueden ser multi-idioma y multi-tienda, así que puede ser un valor global para todas las tiendas e idiomas o puede ser concreto. Ten en cuenta esto a la hora de definir el dato que quieras almacenar y sobre todo a la hora de inicializarlo.

get($key, $id_lang = null, $id_shop_group = null, $id_shop = null)

Mediante Configuration::get($key) obtenemos el valor almacenado para esa $key.

updateValue($key, $values, $html = false, $id_shop_group = null, $id_shop = null)

Mediante Configuration::updateValue($key, $values) almacenamos en $key el valor establecido en $value.

Db

El objeto Db se comunica con los datos y nos abstrae de la tecnología que haya debajo de nuestro PrestaShop, que puede ser MariaDb, MySQL, PostgreSQL, NoDB… A nosotros esto nos va a dar igual, hasta cierto punto.

Salvo que nuestro módulo requiera usar tablas propias o vaya a ignorar el uso de los objetos nativos de PrestaShop (lo cual no recomiendo) vamos a evitar el uso de este y definir un objeto con el modelo pertinente. Recuerda: MVC.

El objeto Db puede ser llamado globalmente en PrestaShop mediante Db::getInstance(). Luego disponemos de ciertos métodos para operar en la base de datos. Voy a comentar aquí los 2 que dan más juego: execute() y executeS().

Ambas hacen lo mismo: ejecutan una sentencia SQL, sin embargo executeS() devolverá los resultados en un array asociativo. En cambio execute() devolverá true si la consulta fue realizada con éxito, por ello está más indicada para consultas tipo INSERT, UPDATE y REPLACE.

$result = Db::getInstance()->executeS('
    SELECT m.`id_mimodulo`, m.`name`
    FROM `'._DB_PREFIX_.'mimodulo` m
    WHERE m.`active` = 1
    LIMIT 1');
foreach ($result as $row) {
    if ($row['name'] == 'ola ke ase') {
        // do something
    }
}

Por otra parte getRow y getValue son similares a executeS, ambas añaden automáticamente un LIMIT 1 a nuestra consulta y getValue además sólo permite que ésta solo devuelva un campo,

// Ejemplo de getRow
$result = Db::getInstance()->getRow('
    SELECT m.`id_mimodulo`, m.`name`
    FROM `'._DB_PREFIX_.'mimodulo` m
    WHERE m.`active` = 1');
if ($result['name'] == 'ola e ase') {
    // do something
}

// Ejemplo de getValue
$name = Db::getInstance()->getValue('
    SELECT m.`name`
    FROM `'._DB_PREFIX_.'mimodulo` m
    WHERE m.`active` = 1');
if ($name == 'ola e ase') {
    // do something
}

Tools

El objeto Tools es muy extenso, desde redirecciones, trabajar con fechas y arrays, obtener configuraciones… Sólo voy a explicar lo que más nos hace falta saber ahora mismo:

getValue($key, $default_value = false)

Con Tools::getValue($key, ‘default’) tenemos acceso a los parametros GET y POST recibidos, así que olvídate de usar $_GET y $_POST, utilizar este método te hará el desarrollo muuucho más cómodo evitando tener que estar comprobando si existe y todas las líneas de código que ello conlleva.

Ejemplo:

$var = Tools::getValue('param1', 0);

$var va a tomar el valor recibido mediante POST o GET. Si no estuviera definido ninguno de ambos, tomará el valor por defecto 0.

IMPORTANTE: por motivos de seguridad nunca nunca nunca nunca nunca, pero que NUNCA, tomes por válido un valor recibido por GET o POST. Valida el dato SIEMPRE. Ayúdate con la manipulación de tipos de PHP o con el objeto Validate de PrestaShop.

d($object, $kill = true) y p($object)

Estos métodos son como insertar en el código die(print_r($object, 1)), aunque algo más sofisticados. La diferencia entre Tools::p() y Tools::d() es que el último detiene la ejecución por defecto.

Yo personalmente soy más de usar error_log(print_r($object, 1)) para depuración.

Instalación / desinstalación del módulo

Existen 2 métodos dentro del objeto del módulo para realizar la instalación/desinstalación. Mediante estos métodos declararemos hooks, añadiremos a la base de datos cambios que necesitemos, inicializaremos parámetros, etc.

Ambas funciones devolverán true o false y no reciben parámetros. Esto implica que si quieres hacer algo diferente, o que no se haga, al resetear o eliminar el módulo debes controlarlo mediante hooks, concretamente con los hooks actionModuleInstallBefore o actionModuleInstallAfter. Hablaré con más detalle de los hooks en la siguiente entrega.

Ejemplo de install():

public function install() {
    if (!parent::install())
      return false;
    $this->registerHook('hookName');
    Db::getInstance(_PS_USE_SQL_SLAVE_)->execute('
         CREATE TABLE IF NOT EXISTS `'._DB_PREFIX_.'mimodulo` (
                  `id_mimodulo` int(11) unsigned NOT NULL AUTO_INCREMENT,
                  `data` varchar(45) NOT NULL,
                  `active` int(1) unsigned DEFAULT "1",
                  `date_add` datetime DEFAULT NULL,
                  `date_upd` datetime DEFAULT NULL,
                  PRIMARY KEY (`id_mimodulo`),
                  UNIQUE KEY `id_mimodulo_UNIQUE` (`id_mimodulo`)
                )');
    Configuration::set('MIMODULO_PARAM1', '1');
    Configuration::set('MIMODULO_PARAM1', '2');
    return true;
}

Ejemplo de uninstall():

public function uninstall() {
    $this->unregisterHook('hookName');
    Configuration::deleteByName('MIMODULO_PARAM');
    Db::getInstance(_PS_USE_SQL_SLAVE_)->execute('DROP TABLE `'._DB_PREFIX.'mimodulo`');
    return parent::uninstall();
}

Configuración del módulo

Para que nuestro módulo tenga un formulario de configuración disponemos de un método específico llamado getContent(). Este método deberá devolver la plantilla, así que los amantes del espagueti pondrán ahi directamente el código HTML… Pero no, mejor usar una plantilla! Esta plantilla la llamaos con el método display(). La plantilla la va a buscar en su sitio correspondiente, que para este caso sería mimodulo/views/templates/hook/.

Para ello necesitamos tirar de Context para:

  • cargar hojas de estilos y scripts con el objecto controller
  • acceder al objeto Smarty y asignar variables y valores
public function getContent() {
   $this->saveConfiguration();

    $this->context->controller->addJS($this->_path.'views/js/getContent.js');
    $this->context->controller->addCSS($this->_path.'views/css/getContent.css');

    $this->context->smarty->assign('miParamentro1', Configuration::get('MIMODULO_PARAM1'));
    $this->context->smarty->assign('miParamentro2', Configuration::get('MIMODULO_PARAM2'));
    return $this->display(__FILE__, 'getContent.tpl');
}

public function saveConfiguration() {
    if (Tools::isSubmit('submitForm')) {
        Configuration::updateValue('MIMODULO_PARAM1', Tools::getValue('miParametro1'));
        Configuration::updateValue('MIMODULO_PARAM2', Tools::getValue('miParametro2'));
        $this->context->smarty->assign('saved', 1);
    }
}

Y la plantilla en mimodulo/views/templates/hook/getContent.tpl

{l s=’Configuracion de mi modulo’ mod=’mimodulo’}

 

{l s=’Production’ mod=’curadenerp’}

 

 

                       

 

Pues con esto ya tenemos un módulo que no hace nada. Pronto habrá nuevas entradas!

En próximos episodios….

  • Hooks y plantillas Smarty
  • Clases, controladores back-office y front-office, tabs
  • Overrides

3 comments. Leave new

Muy buen artículo, muy bien explicado y he ha servido de gran ayuda, pero ¿para cuando la parte II? y la III…. me ha dejado con las ganas 🙁

Me ha sido muy útil esta entrada, me aclaraste bastantes cosas.
Quedo en espera de la siguiente.
Un saludo.

el tutorial es muy completo, por el tiempo que tiene imagino que no hubo o no se llego poder hacer la parte 2 pero es lo mas completo en creacion (de al menos el archivo principal y como acceder a data) que he encontrado

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.