Prestashop ajax error when decommissioning or quantity increase - prestashop

my big problem is when I choose a version of product or that I update the quantity I have an error ajax "Looking at the console in the console the url of the query ajax the parameters token, id_product and id_customization are duplicated like this:
https://xeof.com/index.php?controller=product?token=64c9f388aad0883a78f350addff907a8&id_product=81&id_customization=0&group%5B19%5D=291&qty=2&token=64c9f388aad0883a78f350addff907a8&id_product=119&id_customization=0&token=64c9f388aad0883a78f350addff907a8&id_product=74&id_customization=0
but that's not all in the log php error I have this error: Undefined variable: find_best in G: \ Dropbox \ Sites \ xeof.com \ classes \ Product.php on line 6103
In the class the call method is this:
/**
* Get an id_product_attribute by an id_product and one or more
* id_attribute.
*
* e.g: id_product 8 with id_attribute 4 (size medium) and
* id_attribute 5 (color blue) returns id_product_attribute 9 which
* is the dress size medium and color blue.
*
* #param int $idProduct
* #param int|int[] $idAttributes
* #param bool $findBest
* #return int
* #throws PrestaShopException
*/
public static function getIdProductAttributeByIdAttributes($idProduct, $idAttributes, $findBest = false)
{
$idProduct = (int) $idProduct;
if (!is_array($idAttributes) && is_numeric($idAttributes)) {
$idAttributes = array((int) $idAttributes);
}
if (!is_array($idAttributes) || empty($idAttributes)) {
throw new PrestaShopException(sprintf('Invalid parameter $idAttributes with value: "%s"', print_r($idAttributes, true)));
}
$idAttributesImploded = implode(',', array_map('intval', $idAttributes));
$idProductAttribute = Db::getInstance()->getValue('
SELECT
pac.`id_product_attribute`
FROM
`' . _DB_PREFIX_ . 'product_attribute_combination` pac
INNER JOIN `' . _DB_PREFIX_ . 'product_attribute` pa ON pa.id_product_attribute = pac.id_product_attribute
WHERE
pa.id_product = ' . $idProduct . '
AND pac.id_attribute IN (' . $idAttributesImploded . ')
GROUP BY
pac.`id_product_attribute`
HAVING
COUNT(pa.id_product) = ' . count($idAttributes)
);
if ($idProductAttribute === false && $findBest) {
//find the best possible combination
//first we order $idAttributes by the group position
$orderred = array();
$result = Db::getInstance()->executeS('
SELECT
a.`id_attribute`
FROM
`'._DB_PREFIX_.'attribute` a
INNER JOIN `'._DB_PREFIX_.'attribute_group` g ON a.`id_attribute_group` = g.`id_attribute_group`
WHERE
a.`id_attribute` IN (' . $idAttributesImploded . ')
ORDER BY
g.`position` ASC'
);
foreach ($result as $row) {
$orderred[] = $row['id_attribute'];
}
while ($idProductAttribute === false && count($orderred) > 0) {
array_pop($orderred);
$idProductAttribute = Db::getInstance()->getValue('
SELECT
pac.`id_product_attribute`
FROM
`'._DB_PREFIX_.'product_attribute_combination` pac
INNER JOIN `'._DB_PREFIX_.'product_attribute` pa ON pa.id_product_attribute = pac.id_product_attribute
WHERE
pa.id_product = '.(int)$idProduct.'
AND pac.id_attribute IN ('.implode(',', array_map('intval', $orderred)).')
GROUP BY
pac.id_product_attribute
HAVING
COUNT(pa.id_product) = '.count($orderred)
);
}
}
if (empty($idProductAttribute)) {
throw new PrestaShopObjectNotFoundException('Can not retrieve the id_product_attribute');
}
return $idProductAttribute;
}
I replace the variable like this on line 6103:
Before:
if ($ idProductAttribute === false && $ find_best)
After:
if ($ idProductAttribute === false && $ findBest)
But no luck I have an exception:
Can not retrieve the id_product_attribute at line 6143 in file classes / Product.php
which I do not find the solution!
I noticed that when I change theme for the classic without having to change php code these settings are not duplicate and I am no other php error.

change this:
if ($ idProductAttribute === false && $ findBest)
to:
$findBest = true;
if ($ idProductAttribute === false && $ findBest)

On Prestashop 1.7.4 I have added this override/classes/Products.php file in order to solve the error during AJAX calls
<?php
class Product extends ProductCore {
public static function getIdProductAttributeByIdAttributes($idProduct, $idAttributes, $findBest = true)
{
return parent::getIdProductAttributeByIdAttributes($idProduct, $idAttributes, $findBest);
}
/**
* #deprecated 1.7.3.1
* #see Product::getIdProductAttributeByIdAttributes()
*/
public static function getIdProductAttributesByIdAttributes($id_product, $id_attributes, $find_best = true)
{
return self::getIdProductAttributeByIdAttributes($id_product, $id_attributes, $find_best);
}
}
Now combinations works properly

Related

Spreadsheets error " String could not be parsed as XML"

When I try to get all titles of spreadsheets from Google drive, I got this message The string could not be parsed as XML:
ERROR -> SimpleXMLElement::__construct(): Entity: line 1: parser error : Start tag expected, '<' not found in C:\....
ERROR-> SimpleXMLElement::__construct(): { in C:\....
ERROR-> SimpleXMLElement::__construct(): ^ in C:\...
I try to connect on Gooogle drive, which I success, but I need to get the names of spreadsheets on Google drive, I previously use API v3 now I need to use V4.
this is code for call method getSpreadsheets();
$serviceRequest = new DefaultServiceRequest($token->access_token, $token->token_type);
ServiceRequestFactory::setInstance($serviceRequest);
$spreadsheetService = new Google\Spreadsheet\SpreadsheetService();
$spreadsheetFeed = $spreadsheetService->getSpreadsheets();
This is the code of method getSpreadsheets:
public function getSpreadsheets()
{
return new SpreadsheetFeed(
ServiceRequestFactory::getInstance()->get('v4/spreadsheets/1u7WYzJOYMX7uH3AIM70yVLOaHBy8p_uifuJe_Saa2T4?fields=sheets.properties.title')
);
}
And this is SpreadsheetFeed class, where is error causes:
namespace Google\Spreadsheet;
use ArrayIterator;
use SimpleXMLElement;
/**
* Spreadsheet feed.
*
* #package Google
* #subpackage Spreadsheet
* #author Asim Liaquat <asimlqt22#gmail.com>
*/
class SpreadsheetFeed extends ArrayIterator
{
/**
* The spreadsheet feed xml object
*
* #var \SimpleXMLElement
*/
protected $xml;
/**
* Initializes the the spreadsheet feed object
*
* #param string $xml the raw xml string of a spreadsheet feed
*/
public function __construct($xml)
{
$this->xml = new SimpleXMLElement($xml);
$spreadsheets = array();
foreach ($this->xml->entry as $entry) {
$spreadsheets[] = new Spreadsheet($entry);
}
parent::__construct($spreadsheets);
}
/**
* Gets a spreadhseet from the feed by its title. i.e. the name of
* the spreadsheet in google drive. This method will return only the
* first spreadsheet found with the specified title.
*
* #param string $title
*
* #return \Google\Spreadsheet\Spreadsheet|null
*/
public function getByTitle($title)
{
foreach($this->xml->entry as $entry) {
if($entry->title->__toString() == $title) {
return new Spreadsheet($entry);
}
}
return null;
}
}
I try to find a solution how to pass this error!
Any Help, how to solve this error?
Thanks!
This is solution for my app, migrate from v3 to v4 spreadsheets API:
$this->service_drive = new Google_Service_Drive($this->client);
$optParams = array('q'=> 'mimeType="application/vnd.google-apps.spreadsheet"');
$this->files = $this->service_drive->files->listFiles($optParams);
}
// $this->service_drive = new Google_Service_Drive($this->client);
//$optParams = array('q'=> 'mimeType="application/vnd.google-apps.spreadsheet"');
//$this->files = $this->service_drive->files->listFiles($optParams);
//$gdata_spreadsheets=$this->files;//my test
if($this->files !==null){
if (count($this->files->getFiles()) == 0) {
$gdata_spreadsheets=array("No Spreadsheets");
} else {
foreach ($this->files->getFiles() as $file) {
$gdata_spreadsheets[$file->getId()]=$file->getName();
}
}
}
//trenuto ispod code rjesavam
if($gdata->spreadsheet_id != '' && isset( $gdata_spreadsheets[$gdata->spreadsheet_id] ) && $this->files !== null){
$this->service = new Google_Service_Sheets($this->client);
$this->response = $this->service->spreadsheets->get($gdata->spreadsheet_id);
$this->sheets=$this->response->getSheets();
// $spreadsheet = $this->service_drive($gdata_spreadsheets[$gdata->spreadsheet_id]);
// $worksheetFeed = $spreadsheet->getWorksheets();
foreach ( $this->sheets as $sheet ){
$gdata_worksheets[$sheet->properties->sheetId]= $sheet->properties->title;
//$gdata_worksheets[]=$this->sheets;
}
//$gdata_worksheets[]=$gdata->worksheet_id;
if($gdata->worksheet_id != '' && isset( $gdata_worksheets[$gdata->worksheet_id] )){
$range = 'List 2!A1:Z';
$worksheet = $this->service->spreadsheets_values->get($gdata->spreadsheet_id, $range);
$cellFeed = $worksheet->getValues();
foreach( $cellFeed As $row) {
//$row = $cellEntry->getRow();
//$col = $cellEntry;
// $gdata_worksheets=$this->service;
if( $row > 1 ){
$gdata_columns=$row;
break;
}
}
}
}
} catch(Exception $e){
$error = $e->getMessage();
}
This is one part of my code, where is described how to migrate from v3 to v4 spreadsheets API.

Behat ensure that select field has these options

With Behat how can I make sure that a select field contains a given set of options?
I can't see any core methods for checking this.
Add the following to your FeatureContext.php:
/**
* #Then /^the select field "([^"]*)" should have a list containing:$/
*
* #param $locator
* string $locator input id, name or label
* #param \Behat\Gherkin\Node\PyStringNode $list
* A list of options that should be present.
*/
public function shouldHaveAListContaining($locator, PyStringNode $list): void {
$session = $this->getSession();
$page = $session->getPage();
$element = $page->findField($locator);
if ($element === NULL) {
throw new \InvalidArgumentException(sprintf('Could find element "%s".', $locator));
}
$options = [];
foreach ($element->findAll('css', 'option') as $option) {
$options[] = $option->getText();
}
$missing = array_diff($list->getStrings(), $options);
if (count($missing) > 0) {
$context = [$locator, implode(', ', $missing)];
throw new \RuntimeException(vsprintf('Element "%s" is missing these options "%s"', $context));
}
}
And the call it like this:
And the select field "YOUR_LABEL" should have a list containing:
"""
Option 1
Option 2
"""

Prestashop 1.7 - City field as dropdown or autocomplete

I am using Prestashop 1.7 and I need to change the city input field.
I have a billing software that only allows me to use predefined cities. So I have created a table ps_cities with the entries (id an city name).
I know how to write a dropdown or a autocomplete script, but I do not know where to change the input type in the Prestashop files.
On the 1.6 version you have the input field in a theme file, but somehow I fail to find in the new version.
In PrestaShop 1.7.7.X I've created a module that includes some new (and cool!) hooks like showing below. I consider this one a good option because it will be easier to maintain in the next PrestaShop releases.
Some assumptions here: I created a relationship model CityAddress with two fields id_city and id_address and a City model with fields like name, id_state, id_country, also I continued using Address::city string name for compatibility.
/**
* #see /classes/form/CustomerAddressFormatter.php#L156
* #param array $param [
* #var array $fields
* ]
*/
public function hookAdditionalCustomerAddressFields($params)
{
($params['fields']['city'])->setType('hidden');
// New field
$formField = $params['fields'];
$formField = (new FormField())
->setName('id_city')
->setLabel($this->l('City'))
->setRequired(true)
->setType('select')
;
// If an address already exits, select the default city
if (Tools::getIsset('id_address')) {
$address = new Address(Tools::getValue('id_address'));
if (!empty($address->id_state)) {
$cities = City::getCitiesByIdState((int) $address->id_state);
if (!empty($cities)) {
foreach ($cities as $city) {
$formField->addAvailableValue(
$city['id_city'],
$city['name']
);
}
$id_city = CityAddress::getIdCityByIdAddress((int) $address->id);
$formField->setValue($id_city);
}
}
}
// Add the id_city field in the position of the city field
$keys = array_keys($params['fields']);
$search = 'city';
foreach ($keys as $key => $value) {
if ($value == $search) {
break;
}
}
$part1 = array_slice($params['fields'], 0, $key + 1);
$part2 = array_slice($params['fields'], $key + 1);
$part1['id_city'] = $formField;
$params['fields'] = array_merge($part1, $part2);
}
This one to validate the field:
/**
* #see /classes/form/CustomerAddressForm.php#L123
* #param array $param [
* #var CustomerAddressForm $form
* ]
*/
public function hookActionValidateCustomerAddressForm($params)
{
if (empty(Tools::getValue('id_city'))
|| empty(Tools::getValue('city'))) {
return false;
}
$form = $params['form'];
$idCityField = $form->getField('id_city');
$idCity = (int) Tools::getValue('id_city');
$cityObj = new City($idCity);
$city = pSQL(Tools::getValue('city'));
if ($cityObj->name !== $city) {
$idCityField->addError(sprintf(
$this->l('Invalid name in field id_city %s and city %s'),
$cityObj->name,
$city
));
return false;
}
return true;
}
And the submitted field:
/**
* #see /classes/form/CustomerAddressForm.php#L153
* #param array $param [
* #var Address $address
* ]
*/
public function hookActionSubmitCustomerAddressForm($params)
{
/** #var Address */
$address = $params['address'];
$address->save();
if (!Validate::isLoadedObject($address)) {
throw new PrestaShopException($this->l('Address object error while trying to save city'));
}
// If address has a previous value then update it
$cityAddress = CityAddress::getCityAddressByIdAddress((int) $address->id);
$city = City::getCityByNameAndIdState($address->city, $address->id_state);
$cityAddress->id_city = $city->id;
$cityAddress->id_address = $address->id;
$cityAddress->save();
}
It is possible if you have this line in the additionalCustomerAddressFields hook:
https://github.com/PrestaShop/PrestaShop/blob/develop/classes/form/CustomerAddressFormatter.php#L150
For previous version I included ['fields' => &$format] as a parameter.
You can find all form fields of the Front Office in your theme's /templates/_partials/form-fields.tpl file

Prestashop 1.5. Currency sign in blockspecials module

I am using blockspecials module and there is currency shown as you can see in a picture. As long as I know, if you remove currency sign from here, you remove it from everywhere in a shop. Maybe there is a way to remove it just from the blockspecials? It doesn't look good this way.
Thanks for help in advance.
This module is calling a custom prestashop smarty function named displayWtPrice (located in /classes/Tools.php). This function correctly formats a number into currency. If you don't want this formatting, remove the smarty function from within the blockspecials.tpl.
By default it looks like ;
{if !$PS_CATALOG_MODE}
<span class="price-discount">{if !$priceDisplay}{displayWtPrice p=$special.price_without_reduction}{else}{displayWtPrice p=$priceWithoutReduction_tax_excl}{/if}</span>
<span class="price">{if !$priceDisplay}{displayWtPrice p=$special.price}{else}{displayWtPrice p=$special.price_tax_exc}{/if}</span>
{/if}
Remove the smarty tags.
{if !$PS_CATALOG_MODE}
<span class="price-discount">{if !$priceDisplay}{$special.price_without_reduction}{else}{$priceWithoutReduction_tax_excl}{/if}</span>
<span class="price">{if !$priceDisplay}{$special.price}{else}{$special.price_tax_exc}{/if}</span>
{/if}
This will leave you with an unformatted number.
If you require formatting but without the symbol and for it to be specific to just this one function, you are going to have to modify the core of prestashop.
You would have to duplicate the displayPrice function in /classes/Tools.php by overriding/extend Tools.php - create new class /overrides/classes/Tools.php
<?php
/**
* Tools
*/
class Tools extends ToolsCore
{
/**
* Return price with currency sign for a given product
*
* #param float $price Product price
* #param object $currency Current currency (object, id_currency, NULL => context currency)
* #return string Price correctly formated (sign, decimal separator...)
*/
public static function displayPrice($price, $currency = null, $no_utf8 = false, Context $context = null, $showSymbol = true)
{
if (!is_numeric($price))
return $price;
if (!$context)
$context = Context::getContext();
if ($currency === null)
$currency = $context->currency;
// if you modified this function, don't forget to modify the Javascript function formatCurrency (in tools.js)
elseif (is_int($currency))
$currency = Currency::getCurrencyInstance((int)$currency);
if (is_array($currency))
{
$c_char = $currency['sign'];
$c_format = $currency['format'];
$c_decimals = (int)$currency['decimals'] * _PS_PRICE_DISPLAY_PRECISION_;
$c_blank = $currency['blank'];
}
elseif (is_object($currency))
{
$c_char = $currency->sign;
$c_format = $currency->format;
$c_decimals = (int)$currency->decimals * _PS_PRICE_DISPLAY_PRECISION_;
$c_blank = $currency->blank;
}
else
return false;
$blank = ($c_blank ? ' ' : '');
$ret = 0;
if (($is_negative = ($price < 0)))
$price *= -1;
$price = Tools::ps_round($price, $c_decimals);
switch ($c_format)
{
/* X 0,000.00 */
case 1:
$ret = (($showSymbol) ? $c_char : '').$blank.number_format($price, $c_decimals, '.', ',');
break;
/* 0 000,00 X*/
case 2:
$ret = number_format($price, $c_decimals, ',', ' ').$blank.(($showSymbol) ? $c_char : '');
break;
/* X 0.000,00 */
case 3:
$ret = (($showSymbol) ? $c_char : '').$blank.number_format($price, $c_decimals, ',', '.');
break;
/* 0,000.00 X */
case 4:
$ret = number_format($price, $c_decimals, '.', ',').$blank.(($showSymbol) ? $c_char : '');
break;
/* 0 000.00 X Added for the switzerland currency */
case 5:
$ret = number_format($price, $c_decimals, '.', ' ').$blank.(($showSymbol) ? $c_char : '');
break;
}
if ($is_negative)
$ret = '-'.$ret;
if ($no_utf8)
return str_replace('€', chr(128), $ret);
return $ret;
}
}
Now you need to override in the same way we have done with Tools but this time the function displayWtPrice in the Product class.
We would override this function to like below;
public static function displayWtPrice($params, &$smarty)
{
return Tools::displayPrice($params['p'], Context::getContext()->currency, false, null, (($params['showsymbol'] == false) ? false : true));
}
We have specified the additional function parameter which will be taken from smarty if provided.
Now you need to modify your blockspecials.tpl with the additional param in displayWtPrice with showsymbol=false
{if !$PS_CATALOG_MODE}
<span class="price-discount">{if !$priceDisplay}{displayWtPrice p=$special.price_without_reduction showsymbol=false}{else}{displayWtPrice p=$priceWithoutReduction_tax_excl showsymbol=false}{/if}</span>
<span class="price">{if !$priceDisplay}{displayWtPrice p=$special.price showsymbol=false}{else}{displayWtPrice p=$special.price_tax_exc showsymbol=false}{/if}</span>
{/if}

Can the the default conditions operator be changed from OR to AND on the Taggable extension?

$tagged = Os :: model()-> withTags("windows, windows7, windowsXp")-> find();
I want to retrieve the records that are tagged with any of the following
windows, windows7, windowsXp.
By default the tags are generating a condition which are AND-ed. I want to use the OR operator for the tags. So if the record contains windows, windows7 but not windowsXp it won't be retrieved.
I've managed to find a workaround, by editing the getFindByTagsCriteria() in the ETaggableBehavior.php that comes in the extension folder.
/**
* Get criteria to limit query by tags.
* #access private
* #param array $tags
* #return CDbCriteria
*/
protected function getFindByTagsCriteria($tags) {
$criteria = new CDbCriteria();
$pk = $this->getOwner()->tableSchema->primaryKey;
if(!empty($tags)){
$conn = $this->getConnection();
$criteria->select = 't.*';
if(count($tags) >0){
$criteria -> join .= "
JOIN {$this->getTagBindingTableName()} bt
ON t.{$pk} = bt.{$this->getModelTableFkName()}
JOIN {$this->tagTable} tag0
ON tag0.{$this->tagTablePk} = bt.{$this->tagBindingTableTagId} AND (";
for($i = 0, $count = count($tags); $i < $count; $i++){
$tag = $conn->quoteValue($tags[$i]);
$criteria->join .= " tag0.`{$this->tagTableName}` = $tag OR";
}
$criteria -> join = rtrim($criteria -> join, "OR");
$criteria -> join .= ")";
}
}
if($this->getScopeCriteria()){
$criteria->mergeWith($this->getScopeCriteria());
}
return $criteria;
}
I would really appreciate any other way without having to modify the plugin itself.
What I'd do here is set the withTags() method in your model to take an array value, for example something like this:
/**
* #param array $tags List of tags to search for
* #return named scope
*/
public function withTags($tags)
{
$condition = '1';
$params = array();
foreach($tags as $key=>$value)
{
$condition.=' OR tag = :tag'.$key;
$params[':tag'.$key] = $value;
}
$this->getDbCriteria()->mergeWith(array(
'condition'=>$condition,
'params'=>$params,
));
return $this;
}
This way you should be able to call your named scope like so:
$tags = array('windows', 'windows7', 'windowsXp'),
$tagged = Os::model()->withTags($tags)->findAll();
Sam from Yii development team helped me solve this by adding two more functions to the ETaggableBehavior.php
/**
* Get criteria to limit query to match any of tags specified
* #access private
* #param array $tags
* #return CDbCriteria
*/
protected function getFindByAnyTagsCriteria($tags) {
$criteria = new CDbCriteria();
$pk = $this->getOwner()->tableSchema->primaryKey;
if(!empty($tags)){
$conn = $this->getConnection();
foreach($tags as &$tag) {
$tag = $conn->quoteValue($tag);
}
unset($tag);
$tags = implode(', ', $tags);
$criteria->select = 't.*';
$criteria->join .=
"JOIN {$this->getTagBindingTableName()} bt ON t.{$pk} = bt.{$this->getModelTableFkName()}
JOIN {$this->tagTable} tag ON tag.{$this->tagTablePk} = bt.{$this->tagBindingTableTagId} AND tag.`{$this->tagTableName}` IN ($tags)";
}
}
if($this->getScopeCriteria()){
$criteria->mergeWith($this->getScopeCriteria());
}
return $criteria;
}
/**
* Limit current AR query to have any of tags specified.
* #param string|array $tags
* #return CActiveRecord
*/
public function taggedWithAnyOf($tags) {
$tags = $this->toTagsArray($tags);
if(!empty($tags)){
$criteria = $this->getFindByAnyTagsCriteria($tags);
$this->getOwner()->getDbCriteria()->mergeWith($criteria);
}
return $this->getOwner();
}