En la anterior entrada hemos conseguido en el back office gestionar la personalización de un producto con selectores, pero de momento no tiene ningún efecto en nuestra parte visible de la tienda.
En esta imagen se ven seleccionados los ficheros en la carpeta override que crearemos ahora.
Presentación de los selectores en la página de producto
En este caso el controlador que hay que modificar es ProductControllerCore
y la plantilla product.tpl
la modificamos directamente en nuestro tema.
La función que se ocupa de generar el contenido para la plantilla dentro de la clase ProductControllerCore
es initContent()
. Sobrescribimos la función en la clase hija y en este caso llamamos primero a la función padre para luego añadir nuestra nueva variable de forma homóloga a como se hace para los otros dos campos
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class ProductController extends ProductControllerCore { public function initContent() { parent::initContent(); if (!$this->errors) { $select_fields = array(); if ($this->product->customizable) { $texts = $this->context->cart->getProductCustomization($this->product->id, Product::CUSTOMIZE_SELECTFIELD, true); foreach ($texts as $text_field) $select_fields['selectFields_'.$this->product->id.'_'.$text_field['index']] = str_replace('<br />', "\n", $text_field['value']); } $this->context->smarty->assign(array('selectFields' => $select_fields)); } } |
Hay que cambiar ligeramente getProductCustomization
de la clase Cart
para que tenga en cuenta nuestro nuevo tipo de campo, así que creamos una clase Cart
que hereda de CartCore
para sobrescribir ese método. La ponemos en la carpeta override classes junto a la clase Product
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Cart extends CartCore{ public function getProductCustomization($id_product, $type = null, $not_in_cart = false) { if (!Customization::isFeatureActive()) return array(); $result = Db::getInstance()->executeS(' SELECT cu.id_customization, cd.index, cd.value, cd.type, cu.in_cart, cu.quantity FROM `'._DB_PREFIX_.'customization` cu LEFT JOIN `'._DB_PREFIX_.'customized_data` cd ON (cu.`id_customization` = cd.`id_customization`) WHERE cu.id_cart = '.(int)$this->id.' AND cu.id_product = '.(int)$id_product. ($type === Product::CUSTOMIZE_FILE ? ' AND type = '.(int)Product::CUSTOMIZE_FILE : ''). ($type === Product::CUSTOMIZE_TEXTFIELD ? ' AND type = '.(int)Product::CUSTOMIZE_TEXTFIELD : ''). ($type === Product::CUSTOMIZE_SELECTFIELD ? ' AND type = '.(int)Product::CUSTOMIZE_SELECTFIELD : ''). ($not_in_cart ? ' AND in_cart = 0' : '') ); return $result; } } |
Modificamos la plantilla product.tpl
en nuestro tema para que tenga una nueva sección para pintar los selectores (justo después de la sección de Texto).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{if $product->select_fields|intval} <div class="customizableProductsSelect"> <h3>{l s='Select'}</h3> <ul id="select_fields"> {counter start=0 assign='customizationField'} {foreach from=$customizationFields item='field' name='customizationFields'} {if $field.type == 2} <li class="customizationUploadLine{if $field.required} required{/if}"> <label for ="selectField{$customizationField}">{assign var='key' value='selectFields_'|cat:$product->id|cat:'_'|cat:$field.id_customization_field} {if !empty($field.name)}{$field.name}{/if}{if $field.required}<sup>*</sup>{/if}</label> <select name="selectField{$field.id_customization_field}" id="selectField{$customizationField}" class="attribute_select" > <option value=""></option> {assign var='options' value="\n"|explode:$field.combo} {foreach from=$options item='option'} <option value="{$option|trim}" {if isset($selectFields.$key) && $selectFields.$key==$option|trim} selected="selected"{/if}>{$option|trim}</option> {/foreach} </select> </li> {counter} {/if} {/foreach} </ul> </div> {/if} |
Las opciones se sacan del nuevo atributo combo
que se corresponde al campo combo
de la tabla ps_customization_select_field_lang
. Los retornos de carro nos marcan cada item del selector.
Guardado de los selectores en la página de producto
La función que se ocupa de guardar los campos de texto es textRecord()
. Sobrescribimos esta función copiándola de la clase core y la modificamos para que además guarde en el carrito nuestros campos selectores.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
protected function textRecord(){ if (!$field_ids = $this->product->getCustomizationFieldIds()) return false; $authorized_text_fields = array(); foreach ($field_ids as $field_id){ if ($field_id['type'] == Product::CUSTOMIZE_TEXTFIELD) $authorized_text_fields[(int)$field_id['id_customization_field']] = 'textField'.(int)$field_id['id_customization_field']; if ($field_id['type'] == Product::CUSTOMIZE_SELECTFIELD) $authorized_text_fields[(int)$field_id['id_customization_field']] = 'selectField'.(int)$field_id['id_customization_field']; } $indexes = array_flip($authorized_text_fields); foreach ($_POST as $field_name => $value) if (in_array($field_name, $authorized_text_fields) && !empty($value)) { if (!Validate::isMessage($value)){ $this->errors[] = Tools::displayError('Invalid message'); }else{ $type=Product::CUSTOMIZE_TEXTFIELD; if(strpos($field_name, 'selectField', 0) === 0)//startswith $type=Product::CUSTOMIZE_SELECTFIELD; $this->context->cart->addTextFieldToProduct($this->product->id, $indexes[$field_name], $type, $value); } } else if (in_array($field_name, $authorized_text_fields) && empty($value)) $this->context->cart->deleteCustomizationToProduct((int)$this->product->id, $indexes[$field_name]); } |
Como vimos en una entrada anterior hay un par de funciones javascript que se ocupan de validar y vaciar los campos al añadir el producto a la cesta que no están pensadas para selectores. Las sobrescribimos al principio de product.tpl
en la sección de <script>
.
1 2 3 4 5 6 |
<script type="text/javascript"> // <![CDATA[ //Sobrescribimos la función que vacía los campos de texto personalizados y comprueba los campos obligatorios emptyCustomizations=custom_emptyCustomizations; checkCustomizations=custom_checkCustomizations; |
Utilizamos las mismas funciones que entonces en el mismo fichero product.js
. En este caso las he añadido el prefijo custom_.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
/ función idéntica a la original emptyCustomizations en tools.js linea 218 // pero teniendo en cuenta que en nuestro caso tenemos un input de tipo select que no necesita resetar el html function custom_emptyCustomizations(){ if(typeof(customizationFields) == 'undefined') return; $('.customization_block .success').fadeOut(function(){ $(this).remove(); }); $('.customization_block .error').fadeOut(function(){ $(this).remove(); }); for (var i = 0; i < customizationFields.length; i++) { var el=$('#' + customizationFields[i][0]); if(!el.is('select')){//Si es un select no hay que vaciar el html el.html(''); } el.val(''); } } //función idéntica a la original checkCustomizations en tools.js linea 204 //pero teniendo en cuenta que en nuestro caso tenemos un input de tipo select function custom_checkCustomizations() { var pattern = new RegExp(' ?filled ?'); if (typeof customizationFields != 'undefined') for (var i = 0; i < customizationFields.length; i++) { var el=$('#' + customizationFields[i][0]); /* If the field is required and empty then we abort */ if(el.is('select')){//Si es un select no hace falta mirar el html, con mirar el valor tenemos suficiente if (parseInt(customizationFields[i][1]) == 1 && (el.val().length == 0) && !pattern.test(el.attr('class'))) return false; }else{ //original if (parseInt(customizationFields[i][1]) == 1 && (el.html() == '' || el.text() != el.val()) && !pattern.test(el.attr('class'))) return false; } } return true; } |
Buscando plantillas donde se visualice los productos del pedido y un par de bugs de prestashop
Lo mejor es hacer una búsqueda del texto CUSTOMIZE_TEXTFIELD en todas las plantillas del proyecto. Una vez descartadas las del tema default salen 21. P.ej en el front end tenemos
Simplemente añadimos la condición OR y el valor aparecerá. Estas modificaciones las hacemos directamente en nuestro tema.
1 |
{elseif $type == $CUSTOMIZE_TEXTFIELD OR $type == Product::CUSTOMIZE_SELECTFIELD } |
En el back office la plantilla que interesa es _customized_data.tpl
(sección productos en la pantalla de detalle de pedido). La sobrescritura de esta plantilla en la carpeta override no funciona directamente. Me imagino que es algo que tendrán en cuenta para futuras versiones.
De momento o machacas directamente la plantilla original o sobrescribes la plantilla orders/helpers/view/view.tpl
donde se hace un include de _customized_data.tpl
. El único objetivo de sobrescribir view.tpl
es que incluya nuestra versión modificada de forma que en vez de
1 |
{include file='controllers/orders/_customized_data.tpl'} |
ponemos
1 |
{include file='../_customized_data.tpl'} |
Así funciona el sistema de override, aunque me he encontrado que el tema de las traducciones falla en prestashop para este caso (me salia la pantalla de pedidos en inglés en lugar de español). Lo que he hecho es en la clase smartyadmin.config.inc.php
al final del método smartyTranslate modificar un fallo que producía que no buscase las traducciones bien. Supongo que en futuras versiones lo solucionarán.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
else { //MODIFICACION: Si el filename contiene override H:\wamp\www\prestashop/override/controllers\admin\templates\\orders/helpers/view/view.tpl //encuentra admin pero tendria que ser orders // Split by \ and / to get the folder tree for the file $folder_tree = preg_split('#[/\\\]#', $filename); $overridekey = array_search('override', $folder_tree); if ($overridekey !== false){ $class = 'Admin'.Tools::toCamelCase($folder_tree[$overridekey + 5], true); }else{ $key = array_search('controllers', $folder_tree); // If there was a match, construct the class name using the child folder name // Eg. xxx/controllers/customers/xxx => AdminCustomers if ($key !== false) $class = 'Admin'.Tools::toCamelCase($folder_tree[$key + 1], true); elseif (isset($folder_tree[0])) $class = 'Admin'.Tools::toCamelCase($folder_tree[0], true); else $class = null; } |
product.tpl
, product.js
y también smartyadmin.config.inc.php
desde éste otro link. Las modificaciones de la búsqueda de CUSTOMIZE_TEXTFIELD te las dejo para tí 😉.