En este tutorial veremos paso a paso como añadir el campo ciudad como menú desplegable al crear o modificar una dirección.
EL primer paso es crear una tabla con las ciudades. La tabla la llamaremos city, y tendrá esta estructura
Los id_state deben ser los da cada estado que tenga su tienda prestashop. La tabla se vería así:
El siguiente paso es modificar la tabla ps_address añadiendo un campo id_city
Por último, modificaremos la tabla address_format definiendo para que países usaremos el formato de droptown en la ciudad.
Solo basta con editar el campo format, que generalmente cuando contiene estados luce asi:
Y le cambiaremos el campo city asi city:name. Name añadiría el dropdown:
A esta altura la tienda nos dará errores ya que faltaran clases, asi que ahora debemos modificar los archivos para que todo esto funcione.
Primero crearemos un archivo City.php en la carpeta clases que permitirá trabajar con las ciudades.El archivo contendrá:
class CityCore extends ObjectModel
{
public $id_city;
public $id_state;
public $name;
protected $fieldsRequired = array('id_city', 'name');
protected $fieldsSize = array('name' => 128, 'id_state' => 10);
protected $fieldsValidate = array('name' => 'isGenericName', 'id_state' => 'isUnsignedId', );
protected $table = 'city';
protected $identifier = 'id_city';
private static $_cache_get_cities = array();
public function getFields()
{
parent::validateFields();
$fields['id_city'] = (int)($this->id_city);
$fields['name'] = pSQL($this->name);
$fields['id_state'] = pSQL($this->id_state);
return $fields;
}
public function delete()
{
$id = $this->id;
parent::delete();
}
public static function getCities($id_state)
{
$id_city = Db::getInstance()->ExecuteS('
SELECT * FROM `'._DB_PREFIX_.'city`
WHERE `id_state` = '.(int)$id_state.'
ORDER BY `name`;'
);
return $id_city;
}
public static function getCityName($id_city)
{
return Db::getInstance()->getValue('
SELECT `name` FROM `'._DB_PREFIX_.'city`
WHERE `id_city` = '.(int)$id_city
);
}
}
El siguiente archivo es el clases/form/CustomerAddressFormatter.php
Buscaremos esta línea de estados
elseif ($entity === 'State') {
if ($this->country->contains_states) {
$states = State::getStatesByIdCountry($this->country->id, true);
foreach ($states as $state) {
$formField->addAvailableValue(
$state['id_state'],
$state[$entityField]
);
}
$formField->setRequired(true);
}
}
y añadiremos al final
elseif ($entity === 'city') {
$formField->setType('select');
$formField->setName('id_' . strtolower($entity));
$cities = State::getCities(315);
foreach ($cities as $city) {
$formField->addAvailableValue(
$city['id_city'],
$city[$entityField]
);
}
$formField->setRequired(false);
}
Luego añadiremos esta función a el archivo clases/State.php
public static function getCities($id_state)
{
$id_city = Db::getInstance()->ExecuteS('
SELECT * FROM `'._DB_PREFIX_.'city`
WHERE `id_state` = '.(int)$id_state.'
ORDER BY `name`;'
);
return $id_city;
}
Para estos archivos ya creados siempre podemos usar un override para evitar perder cambios al actualizar PrestaShop
EL siguiente archivo es el clases/Address.php
Añadiremos al inicio las variables publicas:
public $id_city;
public $cityName;
En la fucion public static $definition = array(
Añadiremos al final el campo creado id_city para la tabla address
'id_city' => 'isUnsignedId',
Y añadiremos esta nueva función
public function getFields()
{
if (isset($this->id))
$sql = 'select * from '. _DB_PREFIX_ . 'city
WHERE id_city= ' . $this->id_city;
$sql2 = Db::getInstance()->getRow($sql);
$fields['id_address'] = (int)($this->id);
$fields['id_customer'] = is_null($this->id_customer) ? 0 : (int)($this->id_customer);
$fields['id_manufacturer'] = is_null($this->id_manufacturer) ? 0 : (int)($this->id_manufacturer);
$fields['id_supplier'] = is_null($this->id_supplier) ? 0 : (int)($this->id_supplier);
$fields['id_country'] = (int)($this->id_country);
$fields['id_state'] = (int)($this->id_state);
$fields['alias'] = pSQL($this->alias);
$fields['company'] = pSQL($this->company);
$fields['lastname'] = pSQL($this->lastname);
$fields['firstname'] = pSQL($this->firstname);
$fields['address1'] = pSQL($this->address1);
$fields['address2'] = pSQL($this->address2);
$fields['postcode'] = pSQL($this->postcode);
$fields['city'] = pSQL($sql2['name']);
$fields['other'] = pSQL($this->other);
$fields['phone'] = pSQL($this->phone);
$fields['phone_mobile'] = pSQL($this->phone_mobile);
$fields['vat_number'] = pSQL($this->vat_number);
$fields['dni'] = pSQL($this->dni);
$fields['deleted'] = (int)($this->deleted);
$fields['date_add'] = pSQL($this->date_add);
$fields['date_upd'] = pSQL($this->date_upd);
$fields['id_city'] = is_null($this->id_city) ? 0 : (int)($this->id_city);
return $fields;
}
Y al final modificaremos esta función
public static function initialize($id_address = null, $with_geoloc = false)
donde este elseif debe añadir el campo city
} elseif ($with_geoloc && isset($context->customer->geoloc_id_country)) {
$address = new Address();
$address->id_country = (int) $context->customer->geoloc_id_country;
$address->id_state = (int) $context->customer->id_state;
$address->id_city = (int) $context->customer->id_city;
$address->postcode = $context->customer->postcode;
}
Luego el classes/form/customeraddressform.php reemplazaremos esta función
public function submit()
{
if (!$this->validate()) {
return false;
}
$address = new Address(
$this->getValue('id_address'),
$this->language->id
);
foreach ($this->formFields as $formField) {
if ($formField->getName() == 'id_city'){
$id_city = Db::getInstance()->getRow('
SELECT * FROM `'._DB_PREFIX_.'city`
WHERE `id_city` = '.$formField->getValue().';');
$address->city = $id_city['name'];
$address->{$formField->getName()} = $formField->getValue();
} else {
$address->{$formField->getName()} = $formField->getValue();
}
}
if (!isset($this->formFields['id_state'])) {
$address->id_state = 0;
}
if (empty($address->alias)) {
$address->alias = $this->translator->trans('My Address', [], 'Shop.Theme.Checkout');
}
Hook::exec('actionSubmitCustomerAddressForm', array('address' => &$address));
$this->setAddress($address);
$this->getPersister()->save(
$address,
$this->getValue('token'));
Db::getInstance()->Execute('UPDATE `'._DB_PREFIX_.'address` SET `city` = \''.$address->city.'\' WHERE id_address = '.$address->id);
return true;
}
El siguiente paso es añadir el código javascript para que tome las ciudades al cambiar de estado:
Para esto modificaremos el archivo themes/nuestrotheme/templates/_partials/javascript.tpl de nuestro theme y le añadiremos este código al final:
<!-- direcciones --> {literal} <script> (function(){"use strict";var c=[],f={},a,e,d,b;if(!window.jQuery){a=function(g){c.push(g)};f.ready=function(g){a(g)};e=window.jQuery=window.$=function(g){if(typeof g=="function"){a(g)}return f};window.checkJQ=function(){if(!d()){b=setTimeout(checkJQ,100)}};b=setTimeout(checkJQ,100);d=function(){if(window.jQuery!==e){clearTimeout(b);var g=c.shift();while(g){jQuery(g);g=c.shift()}b=f=a=e=d=window.checkJQ=null;return true}return false}}})(); </script> {/literal} {if $page.page_name == "address" or $page.page_name == "order" or $page.page_name == "checkout"} <script type="text/javascript"> $(document).ready(function(){ $(".form-control-select .js-city").last().val(); var mi_ajaxurl = '{$urls.base_url}modules/'; var aux_id_state = {if isset($smarty.post.id_state) and $smarty.post.id_state <> null}{$smarty.post.id_state}{else}{if isset($customer.addresses[$smarty.get.id_address].id_state)}{$customer.addresses[$smarty.get.id_address].id_state}{else}0{/if}{/if}; var aux_id_city = {if isset($smarty.post.id_city) and $smarty.post.id_city <> null}{$smarty.post.id_city}{else}{if isset($customer.addresses[$smarty.get.id_address].id_city)}{$customer.addresses[$smarty.get.id_address].id_city}{else}0{/if}{/if}; var aux_city = '{if isset($smarty.post.city) and $smarty.post.city <> null}{$smarty.post.city}{else}{if isset($customer.addresses[$smarty.get.id_address].city)}{$customer.addresses[$smarty.get.id_address].city}{else}0{/if}{/if}'; {literal} $(document).ready(function(){ $.urlParam = function(name){ var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href); if (results==null) { return null; } return decodeURI(results[1]) || 0; } ajaxCity(); $("[name='id_state']").change(function() { ajaxCity(); }); $("[name='id_city']").change(function() { }); function ajaxCity(valueaaa){ $.ajax({ type: "GET", url: mi_ajaxurl+"ajax_addresses/ajax.php?ajaxCity=1&id_state="+$("[name='id_state']").val()+"&aux_id_state="+aux_id_state+"&aux_city="+aux_city, success: function(r){ if( r == 'false' ){ $("[name='id_city']").fadeOut(); $("[name='id_city'] option[value=0]").attr("selected", "selected"); }else{ $("[name='id_city']").html(r); $("[name='id_city']").fadeIn(); //$('#id_city option[value=0]').attr("selected", "selected"); $("[name='id_city'] option[value='+aux_id_city+']").attr("selected", "selected"); } $("[name='id_city']").trigger('click'); //$("#id_street").trigger('click'); /*/***ajaxStreet();*/ } }); }; }); {/literal} </script> {/if}
Por ultimo, crearemos un pseudo modulo para las funciones Ajax. Esto se puede hacer de otras formas, esta es solo una sencilla.En la carpeta modules, creamos una carpeta llamada ajax_addresses y dentro de la misma un archivo llamado ajax.php con este contenido:
<?php include(dirname(__FILE__). '/../../config/config.inc.php'); include(dirname(__FILE__). '/../../init.php'); // obtengo city if (isset($_GET['ajaxCity']) AND isset($_GET['id_state'])) { $idTemp = ( (int)(Tools::getValue('id_state')) == 0 ? (int)(Tools::getValue('aux_id_state')) : (int)(Tools::getValue('id_state')) ); $idcity = Tools::getValue('aux_city'); $states = Db::getInstance()->ExecuteS(' SELECT C.id_city, C.name FROM '._DB_PREFIX_.'city C WHERE C.id_state = '.$idTemp.' ORDER BY C.`name` ASC'); $states2 = Db::getInstance()->getRow(' SELECT C.id_city, C.name FROM '._DB_PREFIX_.'city C WHERE C.name = \''.$idcity.'\' ORDER BY C.`name` ASC'); if (is_array($states) AND !empty($states)) { $list = ''; if (Tools::getValue('no_empty') != true) if($idcity != null){ $list = '<option value="'.$states2['name'].'" class="showme" >'.($idcity == 0 ? "---" : $idcity) .'</option>'."\n"; } foreach ($states AS $state) if($idcity == 0){ $list .= '<option value="'.(int)($state['id_city']).'"'.((isset($_GET['id_city']) AND $_GET['id_city'] == $state['id_city']) ? ' selected="selected"' : '').'>'.$state['name'].'</option>'."\n"; } else{ $list .= '<option value="'.(int)($state['id_city']).'"'.((isset($idcity) AND $idcity == $state['name']) ? ' selected="selected"' : '').'>'.$state['name'].'</option>'."\n"; } } else $list = 'false'; die($list); }
Luego solo debemos limpiar cache y ya deberíamos ver las ciudades como dropdown.
Link a los archivos
https://github.com/shacker2/citiesasdropdownprestashop/tree/main