⚡ AJAX en modulos PrestaShop

Actualizado: 2024-12-01

Los modulos de PrestaShop exponen endpoints AJAX creando un ModuleFrontController con la propiedad $ajax = true. Las llamadas AJAX deben incluir un token de seguridad para evitar CSRF y llamadas no autorizadas.

#Controlador AJAX con ModuleFrontController

controllers/front/ajax.php — endpoint AJAX con acciones
php
<?php

if (!defined('_PS_VERSION_')) { exit; }

class MyModuleAjaxModuleFrontController extends ModuleFrontController
{
    /** @var bool Solo responde JSON, sin layout HTML */
    public $ajax = true;

    public function initContent(): void
    {
        parent::initContent();

        $action = Tools::getValue('action', '');

        header('Content-Type: application/json; charset=utf-8');

        switch ($action) {
            case 'getItems':
                $this->processGetItems();
                break;
            case 'saveItem':
                $this->processSaveItem();
                break;
            case 'deleteItem':
                $this->processDeleteItem();
                break;
            default:
                $this->ajaxRender(json_encode([
                    'success' => false,
                    'error'   => 'Accion no valida: ' . htmlspecialchars($action),
                ]));
        }
    }

    private function processGetItems(): void
    {
        $items = Db::getInstance()->executeS(
            'SELECT * FROM `' . _DB_PREFIX_ . 'mymodule_items`
             WHERE active = 1 ORDER BY sort_order ASC'
        );

        $this->ajaxRender(json_encode([
            'success' => true,
            'items'   => $items ?: [],
        ]));
    }

    private function processSaveItem(): void
    {
        // Validar token CSRF
        $token = Tools::getValue('token');
        if (!$token || $token !== Tools::getToken(false)) {
            $this->ajaxRender(json_encode(['success'=>false,'error'=>'Token invalido']));
            return;
        }

        $name = trim(Tools::getValue('name', ''));
        if (empty($name)) {
            $this->ajaxRender(json_encode(['success'=>false,'error'=>'Nombre requerido']));
            return;
        }

        Db::getInstance()->insert('mymodule_items', [
            'name'   => pSQL($name),
            'active' => 1,
        ]);

        $this->ajaxRender(json_encode([
            'success' => true,
            'id'      => (int) Db::getInstance()->Insert_ID(),
        ]));
    }

    private function processDeleteItem(): void
    {
        $id = (int) Tools::getValue('id');
        if ($id <= 0) {
            $this->ajaxRender(json_encode(['success'=>false,'error'=>'ID invalido']));
            return;
        }

        Db::getInstance()->delete('mymodule_items', 'id_mymodule_item = ' . $id);
        $this->ajaxRender(json_encode(['success' => true]));
    }
}

#Seguridad: validacion de token

Generar y validar tokens anti-CSRF
php
<?php

// ── GENERAR TOKEN (en el template que inicia la llamada AJAX) ──
// En el FrontController que muestra la pagina:
$this->context->smarty->assign([
    'ajax_token' => Tools::getToken(false),  // token estatico de sesion
    'ajax_url'   => $this->context->link->getModuleLink('mymodule', 'ajax'),
]);

// En el template Smarty:
// {assign var='token' value={Tools::getToken(false)}}

// ── VALIDAR TOKEN (en el controlador AJAX) ──
$token = Tools::getValue('token');
if (!$token || $token !== Tools::getToken(false)) {
    http_response_code(403);
    $this->ajaxRender(json_encode(['error' => 'Forbidden']));
    return;
}

// ── ALTERNATIVA: static_token (no cambia entre peticiones de la misma sesion) ──
// Generar:
$staticToken = Tools::encrypt('mymodule-ajax-secret');
// Validar:
if (Tools::getValue('static_token') !== Tools::encrypt('mymodule-ajax-secret')) {
    // token invalido
}

#JavaScript: fetch API (moderno)

Llamadas AJAX con fetch (ES6+)
javascript
// Asumiendo que PHP exporto ajaxUrl y ajaxToken al JS
// (ver seccion 'Exportar URL al JS')

// GET request
async function getItems() {
  try {
    const resp = await fetch(window.MyModule.ajaxUrl + '?action=getItems');
    const data = await resp.json();
    if (data.success) {
      console.log('Items:', data.items);
    }
  } catch (err) {
    console.error('Error AJAX:', err);
  }
}

// POST request con token
async function saveItem(name) {
  const formData = new FormData();
  formData.append('action', 'saveItem');
  formData.append('name', name);
  formData.append('token', window.MyModule.token);

  try {
    const resp = await fetch(window.MyModule.ajaxUrl, {
      method: 'POST',
      body: formData,
    });
    const data = await resp.json();
    if (data.success) {
      console.log('Guardado con ID:', data.id);
    } else {
      alert('Error: ' + data.error);
    }
  } catch (err) {
    console.error('Error:', err);
  }
}

#JavaScript: jQuery AJAX (legacy)

Llamadas AJAX con jQuery (compatible con PS 1.7)
javascript
// GET
$.ajax({
  url: window.MyModule.ajaxUrl,
  data: { action: 'getItems' },
  dataType: 'json',
  success: function(data) {
    if (data.success) {
      console.log(data.items);
    }
  },
  error: function(xhr, status, error) {
    console.error('Error AJAX:', error);
  }
});

// POST con token
$.ajax({
  url: window.MyModule.ajaxUrl,
  method: 'POST',
  data: {
    action: 'saveItem',
    name: $('#item-name').val(),
    token: window.MyModule.token
  },
  dataType: 'json',
  success: function(data) {
    if (data.success) {
      console.log('ID:', data.id);
    }
  }
});

#Exportar URL al JS desde PHP

Exportar variables PHP al JavaScript via hookActionFrontControllerSetMedia
php
<?php

// En el modulo — usar el hook de media para añadir JS inline con las URLs
public function hookActionFrontControllerSetMedia(): void
{
    // Solo en la pagina del modulo
    $controller = $this->context->controller;
    if (!($controller instanceof ModuleFrontController) || $controller->module->name !== $this->name) {
        return;
    }

    $jsVars = [
        'ajaxUrl' => $this->context->link->getModuleLink($this->name, 'ajax', [], true),
        'token'   => Tools::getToken(false),
        'baseUrl' => $this->context->link->getBaseLink(),
    ];

    $jsInline = 'window.MyModule = ' . json_encode($jsVars) . ';';

    $this->context->controller->registerJavascript(
        $this->name . '-vars',
        'data:text/javascript,' . rawurlencode($jsInline),
        ['server' => 'remote', 'position' => 'head', 'priority' => 1]
    );
}
Descargar en Markdown Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.