En este tutorial vamos a ver cómo añadir nuevos campos a la dirección del cliente en Prestashop 1.6. También podemos hacerlo en Prestashop 1.5. Me refiero a esto:
Como podéis ver en la imagen de arriba, hay un nuevo campo en el formulario de dirección del cliente.
Compatibilidad: Prestashop 1.6 y 1.5
Descargar los archivos del proyectoLo que vamos a hacer...
Con el fin de añadir un nuevo campo al formulario de inscripción del cliente, vamos a crear un par de overrides, también crearemos un nuevo campo en la base de datos, y por último, modificaremos el archivo address.tpl de nuestro tema.
La clase address y la base de datos
Queremos que el cliente pueda rellenar un campo que no existe, por lo tanto debemos crear un campo en nuestra base de datos. Así que, vayamos a nuestra herramienta para manejar nuestras bases de datos (en mi caso phpMyAdmin), y accedamos a la tabla ps_address para crear un campo nuevo:
Como vemos en la imagen, tenemos que agregar un campo de tipo VARCHAR y 64 de longitud.
Ahora tenemos que informar al objeto address (la clase) de que tiene un nuevo campo a tratar. Asi que, debemos crear un archivo dentro de prestashop/override/classes/ y lo llamaremos Address.php. Agreguemos el siguiente código (sin olvidar la etiqueta de apertura <?php) dentro del archivo:
class Address extends AddressCore { public $mi_campo; public static $definition = array( 'table' => 'address', 'primary' => 'id_address', 'fields' => array( 'id_customer' => array('type' => self::TYPE_INT, 'validate' => 'isNullOrUnsignedId', 'copy_post' => false), 'id_manufacturer' => array('type' => self::TYPE_INT, 'validate' => 'isNullOrUnsignedId', 'copy_post' => false), 'id_supplier' => array('type' => self::TYPE_INT, 'validate' => 'isNullOrUnsignedId', 'copy_post' => false), 'id_warehouse' => array('type' => self::TYPE_INT, 'validate' => 'isNullOrUnsignedId', 'copy_post' => false), 'id_country' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), 'id_state' => array('type' => self::TYPE_INT, 'validate' => 'isNullOrUnsignedId'), 'alias' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'required' => true, 'size' => 32), 'company' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 64), 'lastname' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => true, 'size' => 32), 'firstname' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => true, 'size' => 32), 'vat_number' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName'), 'address1' => array('type' => self::TYPE_STRING, 'validate' => 'isAddress', 'required' => true, 'size' => 128), 'address2' => array('type' => self::TYPE_STRING, 'validate' => 'isAddress', 'size' => 128), 'postcode' => array('type' => self::TYPE_STRING, 'validate' => 'isPostCode', 'size' => 12), 'city' => array('type' => self::TYPE_STRING, 'validate' => 'isCityName', 'required' => true, 'size' => 64), 'other' => array('type' => self::TYPE_STRING, 'validate' => 'isMessage', 'size' => 300), 'phone' => array('type' => self::TYPE_STRING, 'validate' => 'isPhoneNumber', 'size' => 32), 'phone_mobile' => array('type' => self::TYPE_STRING, 'validate' => 'isPhoneNumber', 'size' => 32), 'dni' => array('type' => self::TYPE_STRING, 'validate' => 'isDniLite', 'size' => 16), 'deleted' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool', 'copy_post' => false), 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDateFormat', 'copy_post' => false), 'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDateFormat', 'copy_post' => false), 'mi_campo' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName', 'size' => 64), ), ); }
El controlador Address del Back-office
Ahora tenemos que hacer override sobre el controlador del Back-office, que es el responsable de mostrar el formulario para modificar la dirección de cada cliente.
Para ello debemos crear el archivo AdminAddressesController.php en prestashop/override/controllers/admin. Necesitamos extender el método renderForm(); por lo tanto debemos tomar el archivo original prestashop/controllers/admin/AdminAddressesController.php; localizaremos el método renderForm() y lo copiaremos.
A continuación, debemos agregar el siguiente código dentro de nuestro controlador en la carpeta del override...
class AdminAddressesController extends AdminAddressesControllerCore { }
... y ahora pegamos el método renderForm() dentro de la clase. Así es como queda el mío (en Prestashop 1.6.0.5):
class AdminAddressesController extends AdminAddressesControllerCore { public function renderForm() { $this->fields_form = array( 'legend' => array( 'title' => $this->l('Addresses'), 'icon' => 'icon-envelope-alt' ), 'input' => array( array( 'type' => 'text_customer', 'label' => $this->l('Customer'), 'name' => 'id_customer', 'required' => false, ), array( 'type' => 'text', 'label' => $this->l('Identification Number'), 'name' => 'dni', 'required' => false, 'col' => '4', 'hint' => $this->l('DNI / NIF / NIE') ), array( 'type' => 'text', 'label' => $this->l('Address alias'), 'name' => 'alias', 'required' => true, 'col' => '4', 'hint' => $this->l('Invalid characters:').' <>;=#{}' ), array( 'type' => 'text', 'label' => $this->l('Home phone'), 'name' => 'phone', 'required' => false, 'col' => '4', 'hint' => Configuration::get('PS_ONE_PHONE_AT_LEAST') ? sprintf($this->l('You must register at least one phone number.')) : '' ), array( 'type' => 'text', 'label' => $this->l('Mobile phone'), 'name' => 'phone_mobile', 'required' => false, 'col' => '4', 'hint' => Configuration::get('PS_ONE_PHONE_AT_LEAST') ? sprintf($this->l('You must register at least one phone number.')) : '' ), array( 'type' => 'textarea', 'label' => $this->l('Other'), 'name' => 'other', 'required' => false, 'cols' => 15, 'rows' => 3, 'hint' => $this->l('Forbidden characters:').' <>;=#{}' ), ), 'submit' => array( 'title' => $this->l('Save'), ) ); $id_customer = (int)Tools::getValue('id_customer'); if (!$id_customer && Validate::isLoadedObject($this->object)) $id_customer = $this->object->id_customer; if ($id_customer) { $customer = new Customer((int)$id_customer); $token_customer = Tools::getAdminToken('AdminCustomers'.(int)(Tab::getIdFromClassName('AdminCustomers')).(int)$this->context->employee->id); } $this->tpl_form_vars = array( 'customer' => isset($customer) ? $customer : null, 'tokenCustomer' => isset ($token_customer) ? $token_customer : null ); // Order address fields depending on country format $addresses_fields = $this->processAddressFormat(); // we use delivery address $addresses_fields = $addresses_fields['dlv_all_fields']; $temp_fields = array(); foreach ($addresses_fields as $addr_field_item) { if ($addr_field_item == 'company') { $temp_fields[] = array( 'type' => 'text', 'label' => $this->l('Company'), 'name' => 'company', 'required' => false, 'col' => '4', 'hint' => $this->l('Invalid characters:').' <>;=#{}' ); $temp_fields[] = array( 'type' => 'text', 'label' => $this->l('VAT number'), 'col' => '2', 'name' => 'vat_number' ); } else if ($addr_field_item == 'lastname') { if (isset($customer) && !Tools::isSubmit('submit'.strtoupper($this->table)) && Validate::isLoadedObject($customer) && !Validate::isLoadedObject($this->object)) $default_value = $customer->lastname; else $default_value = ''; $temp_fields[] = array( 'type' => 'text', 'label' => $this->l('Last Name'), 'name' => 'lastname', 'required' => true, 'col' => '4', 'hint' => $this->l('Invalid characters:').' 0-9!<>,;?=+()@#"�{}_$%:', 'default_value' => $default_value, ); } else if ($addr_field_item == 'firstname') { if (isset($customer) && !Tools::isSubmit('submit'.strtoupper($this->table)) && Validate::isLoadedObject($customer) && !Validate::isLoadedObject($this->object)) $default_value = $customer->firstname; else $default_value = ''; $temp_fields[] = array( 'type' => 'text', 'label' => $this->l('First Name'), 'name' => 'firstname', 'required' => true, 'col' => '4', 'hint' => $this->l('Invalid characters:').' 0-9!<>,;?=+()@#"�{}_$%:', 'default_value' => $default_value, ); } else if ($addr_field_item == 'address1') { $temp_fields[] = array( 'type' => 'text', 'label' => $this->l('Address'), 'name' => 'address1', 'col' => '6', 'required' => true, ); } else if ($addr_field_item == 'address2') { $temp_fields[] = array( 'type' => 'text', 'label' => $this->l('Address').' (2)', 'name' => 'address2', 'col' => '6', 'required' => false, ); } elseif ($addr_field_item == 'postcode') { $temp_fields[] = array( 'type' => 'text', 'label' => $this->l('Zip/Postal Code'), 'name' => 'postcode', 'col' => '2', 'required' => true, ); } else if ($addr_field_item == 'city') { $temp_fields[] = array( 'type' => 'text', 'label' => $this->l('City'), 'name' => 'city', 'col' => '4', 'required' => true, ); } else if ($addr_field_item == 'country' || $addr_field_item == 'Country:name') { $temp_fields[] = array( 'type' => 'select', 'label' => $this->l('Country'), 'name' => 'id_country', 'required' => false, 'col' => '4', 'default_value' => (int)$this->context->country->id, 'options' => array( 'query' => Country::getCountries($this->context->language->id), 'id' => 'id_country', 'name' => 'name' ) ); $temp_fields[] = array( 'type' => 'select', 'label' => $this->l('State'), 'name' => 'id_state', 'required' => false, 'col' => '4', 'options' => array( 'query' => array(), 'id' => 'id_state', 'name' => 'name' ) ); } } // merge address format with the rest of the form array_splice($this->fields_form['input'], 3, 0, $temp_fields); return parent::renderForm(); } }
Debemos añadir a la variable $this->fields_form el nuevo campo; justo después de este código...
array( 'type' => 'text', 'label' => $this->l('Identification Number'), 'name' => 'dni', 'required' => false, 'col' => '4', 'hint' => $this->l('DNI / NIF / NIE') ),
...añadir el siguiente...
array( 'type' => 'text', 'label' => $this->l('Mi campo personalizado'), 'name' => 'mi_campo', 'required' => false, 'col' => '4', 'hint' => $this->l('Mi campo personalizado!') ),
Por último, al final del método cambiar la línea...
return parent::renderForm();
Por...
return AdminController::renderForm();
Haciendo esto la nueva lista de campos no es sustituida por la original.
Debemos asegurarnos de que el nombre de nuestro campo coincida con el insertado en la base de datos y en los archivos que hemos visto hasta ahora.
Para que el override sea llevado a cabo debemos eliminar el archivo prestahop/cache/class_index.php. No hay peligro en borrar este achivo, vuelve a regenerarse.
La plantilla Address del Front-office
Como último paso, vamos a agregar nuestro campo personalizado a la plantilla del Front-office. Este campo es el que los clientes verán a la hora de registrarse. Abramos el archivo address.tpl, ubicado en la carpeta de nuestro tema. Buscar este código:
{if $field_name eq 'vat_number'} <div id="vat_area"> <div id="vat_number"> <div class="form-group"> <label for="vat-number">{l s='VAT number'}</label> <input type="text" class="form-control validate" data-validate="{$address_validation.$field_name.validate}" id="vat-number" name="vat_number" value="{if isset($smarty.post.vat_number)}{$smarty.post.vat_number}{else}{if isset($address->vat_number)}{$address->vat_number|escape:'html':'UTF-8'}{/if}{/if}" /> </div> </div> </div> {/if}
Y justo después añadir:
{if $field_name eq 'mi_campo'} <div id="vat_area"> <div id="mi_campo"> <div class="form-group"> <label for="mi_campo">{l s='Mi_campo'}</label> <input type="text" class="form-control validate" data-validate="{$address_validation.$field_name.validate}" id="mi_campo" name="mi_campo" value="{if isset($smarty.post.mi_campo)}{$smarty.post.mi_campo}{else}{if isset($address->mi_campo)}{$address->mi_campo|escape:'html':'UTF-8'}{/if}{/if}" /> </div> </div> </div> {/if}
Guardamos cambios y ya está.
Añadiendo el nuevo campo para que por fín sea visible
Para que nuestro campo sea visible, debemos editar el formato de dirección del país pertinente. Para ello debemos ir al Back-office y acceder a Localización > Países > Nuestro país
Como podemos ver en esta imagen, debemos clicar en el campo que acabamos de crear y será insertado en la lista de campos.
Opcional: Hacer que el nuevo campo sea obligatorio
Si necesitamos que nuestro campo sea obligatorio será necesario crear otro override. Debemos crear un archivo en prestashop/override/controllers/front, lo llamaremos AddressController.php.
Incluir (como siempre) la etiqueta de apertura PHP <?php y peguemos el siguiente código dentro del archivo:
class AddressController extends AddressControllerCore { }
Tenemos que sobreescribir el método processSubmitAddress(). Por lo tanto, si nuestra versión de Prestashop es anterior a la 1.6.0.5, tomaremos el método original de prestashop/controllers/front/AddressController.php y lo copiaremos en este override. De lo contrario podemos utilizar el siguiente código:
protected function processSubmitAddress() { $address = new Address(); $this->errors = $address->validateController(); $address->id_customer = (int)$this->context->customer->id; // Check page token if ($this->context->customer->isLogged() && !$this->isTokenValid()) $this->errors[] = Tools::displayError('Invalid token.'); // Check phone if (Configuration::get('PS_ONE_PHONE_AT_LEAST') && !Tools::getValue('phone') && !Tools::getValue('phone_mobile')) $this->errors[] = Tools::displayError('You must register at least one phone number.'); if ($address->id_country) { // Check country if (!($country = new Country($address->id_country)) || !Validate::isLoadedObject($country)) throw new PrestaShopException('Country cannot be loaded with address->id_country'); if ((int)$country->contains_states && !(int)$address->id_state) $this->errors[] = Tools::displayError('This country requires you to chose a State.'); $postcode = Tools::getValue('postcode'); /* Check zip code format */ if ($country->zip_code_format && !$country->checkZipCode($postcode)) $this->errors[] = sprintf(Tools::displayError('The Zip/Postal code you\'ve entered is invalid. It must follow this format: %s'), str_replace('C', $country->iso_code, str_replace('N', '0', str_replace('L', 'A', $country->zip_code_format)))); elseif(empty($postcode) && $country->need_zip_code) $this->errors[] = Tools::displayError('A Zip/Postal code is required.'); elseif ($postcode && !Validate::isPostCode($postcode)) $this->errors[] = Tools::displayError('The Zip/Postal code is invalid.'); // Check country DNI if ($country->isNeedDni() && (!Tools::getValue('dni') || !Validate::isDniLite(Tools::getValue('dni')))) $this->errors[] = Tools::displayError('The identification number is incorrect or has already been used.'); else if (!$country->isNeedDni()) $address->dni = null; } // Check if the alias exists if (!$this->context->customer->is_guest && !empty($_POST['alias']) && (int)$this->context->customer->id > 0) { $id_address = Tools::getValue('id_address'); if(Configuration::get('PS_ORDER_PROCESS_TYPE') && (int)Tools::getValue('opc_id_address_'.Tools::getValue('type')) > 0) $id_address = Tools::getValue('opc_id_address_'.Tools::getValue('type')); if (Db::getInstance()->getValue(' SELECT count(*) FROM '._DB_PREFIX_.'address WHERE `alias` = \''.pSql($_POST['alias']).'\' AND id_address != '.(int)$id_address.' AND id_customer = '.(int)$this->context->customer->id.' AND deleted = 0') > 0) $this->errors[] = sprintf(Tools::displayError('The alias "%s" has already been used. Please select another one.'), Tools::safeOutput($_POST['alias'])); } // Check the requires fields which are settings in the BO $this->errors = array_merge($this->errors, $address->validateFieldsRequiredDatabase()); // Don't continue this process if we have errors ! if ($this->errors && !$this->ajax) return; // If we edit this address, delete old address and create a new one if (Validate::isLoadedObject($this->_address)) { if (Validate::isLoadedObject($country) && !$country->contains_states) $address->id_state = 0; $address_old = $this->_address; if (Customer::customerHasAddress($this->context->customer->id, (int)$address_old->id)) { if ($address_old->isUsed()) $address_old->delete(); else { $address->id = (int)($address_old->id); $address->date_add = $address_old->date_add; } } } if ($this->ajax && Tools::getValue('type') == 'invoice' && Configuration::get('PS_ORDER_PROCESS_TYPE')) { $this->errors = array_unique(array_merge($this->errors, $address->validateController())); if (count($this->errors)) { $return = array( 'hasError' => (bool)$this->errors, 'errors' => $this->errors ); die(Tools::jsonEncode($return)); } } // Save address if ($result = $address->save()) { // Update id address of the current cart if necessary if (isset($address_old) && $address_old->isUsed()) $this->context->cart->updateAddressId($address_old->id, $address->id); else // Update cart address $this->context->cart->autosetProductAddress(); if ((bool)(Tools::getValue('select_address', false)) == true OR (Tools::getValue('type') == 'invoice' && Configuration::get('PS_ORDER_PROCESS_TYPE'))) $this->context->cart->id_address_invoice = (int)$address->id; elseif (Configuration::get('PS_ORDER_PROCESS_TYPE')) $this->context->cart->id_address_invoice = (int)$this->context->cart->id_address_delivery; $this->context->cart->update(); if ($this->ajax) { $return = array( 'hasError' => (bool)$this->errors, 'errors' => $this->errors, 'id_address_delivery' => (int)$this->context->cart->id_address_delivery, 'id_address_invoice' => (int)$this->context->cart->id_address_invoice ); die(Tools::jsonEncode($return)); } // Redirect to old page or current page if ($back = Tools::getValue('back')) { if ($back == Tools::secureReferrer(Tools::getValue('back'))) Tools::redirect(html_entity_decode($back)); $mod = Tools::getValue('mod'); Tools::redirect('index.php?controller='.$back.($mod ? '&back='.$mod : '')); } else Tools::redirect('index.php?controller=addresses'); } $this->errors[] = Tools::displayError('An error occurred while updating your address.'); }
Una vez copiado el código anterior, buscar este código y...
// Check page token if ($this->context->customer->isLogged() && !$this->isTokenValid()) $this->errors[] = Tools::displayError('Invalid token.');
... justo después, añadir el siguiente:
// Check mi campo if ( !Tools::getValue('mi_campo')) $this->errors[] = Tools::displayError('El campo "Color favorito" es obligatorio');
De esta forma si se deja el campo vacío se emitirá un mensaje de error.
Como siempre, y para que el override pueda tener lugar, borrar el archivo prestashop/cache/class_index.php.
Y con esto hemos terminado!!!
Si tenéis problemas con la parte del código, descargar los archivos del proyecto. Estos archivos contienen todo el código necesario, y una vez hayáis probado todo lo indicado en el tutorial, comprenderéis mejor toda la explicación.
consulta, y como puedo hacer lo opuesto? quitar campos?
ResponderEliminarSaludos
Desde Localización > Países > Nuestro país en el Back-office puedes eliminar los campos. Fíjate en la última imagen de esta entrada, verás un recuadro con los campos: company, dni, address1 etc...
EliminarPues borras, por ejemplo, "dni" del recuadro y ya no se verá más en el Front-office de Prestashop.
Hola, Francisco Javier.
ResponderEliminarLlevo buscando durante varios días por google un tutorial para hacer exactamente lo que explicas aquí pero relativo a añadir un nuevo campo a lo que sería la clase "Customer", ya que quiero que el usuario lo rellene cuando se registre como nuevo usuario.
Además, necesitaría que fuese obligatorio (cosa que también contemplas en este tutorial) y que el nuevo campo fuese un select con varias opciones, las cuales tomaría de una tabla que tengo creada en la base de datos. En la tabla tengo una columna con un id y otra columna con el nombre, el cual quiero que les aparezca como opciones al usuario. Sé que en un momento dado podría indicar las opciones manualmente en el código, pero pienso que así estaría más optimizado (aunque sé que complica la cosa).
El problema es que no encuentro ningún tutorial específico para lo que quiero, aunque el tuyo se acerca bastante. He visto una web en inglés (http://nemops.com/new-customer-address-fields-prestashop/#.VX_FhlKz5UM) donde se explica tu mismo tutorial y que al final hacen referencia a que esto mismo se puede aplicar para lo que yo quiero, modificando la clase Customer y sus controladores, así como otros archivos como authentication.tpl, identity.tpl y order-opc-new-account.tpl.
¿Podrías echarme una mano?
Muchas gracias.
Hola amigo,
EliminarPrecisamente me basé en este tutorial en inglés, lo he intentado pensando que lo conseguiría, pensaba que lo tenía controlado, pero no hay manera, no me ha funcionado, lo siento.
Es una pena.
ResponderEliminarLa verdad es que he avanzado bastante, pero no termino de conseguirlo.
He hecho override de las clases "Customer", "AdminCustomersController" y "Tools" (ésta para añadir una función que obtiene los elementos de la base de datos a mostrar en el select).
Luego he modificado los archivos "identity.tpl", "authentication.tpl" y "order-opc-new-account.tpl", pero no carga las opciones en el select del formulario de registro, las cuales debería leer de la base de datos (con el método de la clase Tools). Viendo los errores que muestra, la variable que se supone que trae los elementos de la base de datos no está inicializada, así que ahí estoy estancado.
Para este procedimiento que he seguido, me fijé un poco en lo que hace prestashop con el select para construir la fecha de nacimiento, pero desconozco si debo hacer algo más aparte de lo que ya he hecho.
Seguiré buscando a ver si puedo conseguirlo.
Saludos.
Hola,
ResponderEliminar¿Serías tan amable de enviarme por email los archivos que estás utilizando? Si es posible señala con comentarios los cambios que has hecho en los .tpl
A ver si entre los dos conseguimos algo.
¿Qué versión de Prestashop estás usando?
Un saludo.
Mi correo:
Eliminarreleitor101@hotmail.com
Mi correo:
Eliminarreleitor101@hotmail.com