* @copyright 2007-2011 PrestaShop SA
* @version Release: $Revision: 7733 $
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/
class ProductControllerCore extends FrontController
{
protected $product;
public $php_self = 'product.php';
protected $canonicalURL;
public function setMedia()
{
parent::setMedia();
Tools::addCSS(_THEME_CSS_DIR_.'product.css');
Tools::addCSS(_PS_CSS_DIR_.'jquery.fancybox-1.3.4.css', 'screen');
Tools::addJS(array(
_PS_JS_DIR_.'jquery/jquery.fancybox-1.3.4.js',
_PS_JS_DIR_.'jquery/jquery.idTabs.modified.js',
_PS_JS_DIR_.'jquery/jquery.scrollTo-1.4.2-min.js',
_PS_JS_DIR_.'jquery/jquery.serialScroll-1.2.2-min.js',
_THEME_JS_DIR_.'tools.js',
_THEME_JS_DIR_.'product.js'));
if (Configuration::get('PS_DISPLAY_JQZOOM') == 1)
{
Tools::addCSS(_PS_CSS_DIR_.'jqzoom.css', 'screen');
Tools::addJS(_PS_JS_DIR_.'jquery/jquery.jqzoom.js');
}
}
public function canonicalRedirection()
{
// Automatically redirect to the canonical URL if the current in is the right one
// $_SERVER['HTTP_HOST'] must be replaced by the real canonical domain
if (Validate::isLoadedObject($this->product))
{
$canonicalURL = self::$link->getProductLink($this->product);
if (!preg_match('/^'.Tools::pRegexp($canonicalURL, '/').'([&?].*)?$/', Tools::getProtocol().$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']))
{
header('HTTP/1.0 301 Moved');
if (defined('_PS_MODE_DEV_') AND _PS_MODE_DEV_)
die('[Debug] This page has moved
Please use the following URL instead: '.$canonicalURL.'');
Tools::redirectLink($canonicalURL);
}
}
}
public function preProcess()
{
if ($id_product = (int)Tools::getValue('id_product'))
$this->product = new Product($id_product, true, self::$cookie->id_lang);
if (!Validate::isLoadedObject($this->product))
{
header('HTTP/1.1 404 Not Found');
header('Status: 404 Not Found');
}
else
$this->canonicalRedirection();
parent::preProcess();
}
public function process()
{
global $cart, $currency;
parent::process();
if (!Validate::isLoadedObject($this->product))
$this->errors[] = Tools::displayError('Product not found');
else
{
if ((!$this->product->active AND (Tools::getValue('adtoken') != Tools::encrypt('PreviewProduct'.$this->product->id))
|| !file_exists(dirname(__FILE__).'/../'.Tools::getValue('ad').'/ajax.php')))
{
header('HTTP/1.1 404 page not found');
$this->errors[] = Tools::displayError('Product is no longer available.');
}
elseif (!$this->product->checkAccess((int)self::$cookie->id_customer))
$this->errors[] = Tools::displayError('You do not have access to this product.');
else
{
self::$smarty->assign('virtual', ProductDownload::getIdFromIdProduct((int)$this->product->id));
if (!$this->product->active)
self::$smarty->assign('adminActionDisplay', true);
/* Product pictures management */
require_once('images.inc.php');
if ($this->product->customizable)
{
self::$smarty->assign('customizationFormTarget', Tools::safeOutput(urldecode($_SERVER['REQUEST_URI'])));
if (Tools::isSubmit('submitCustomizedDatas'))
{
$this->pictureUpload($this->product, $cart);
$this->textRecord($this->product, $cart);
$this->formTargetFormat();
}
elseif (isset($_GET['deletePicture']) AND !$cart->deletePictureToProduct((int)($this->product->id), (int)(Tools::getValue('deletePicture'))))
$this->errors[] = Tools::displayError('An error occurred while deleting the selected picture');
$files = self::$cookie->getFamily('pictures_'.(int)($this->product->id));
$textFields = self::$cookie->getFamily('textFields_'.(int)($this->product->id));
foreach ($textFields as $key => $textField)
$textFields[$key] = str_replace('
', "\n", $textField);
self::$smarty->assign(array(
'pictures' => $files,
'textFields' => $textFields));
}
/* Features / Values */
$features = $this->product->getFrontFeatures((int)self::$cookie->id_lang);
$attachments = ($this->product->cache_has_attachments ? $this->product->getAttachments((int)self::$cookie->id_lang) : array());
/* Category */
$category = false;
if (isset($_SERVER['HTTP_REFERER']) AND preg_match('!^(.*)\/([0-9]+)\-(.*[^\.])|(.*)id_category=([0-9]+)(.*)$!', $_SERVER['HTTP_REFERER'], $regs) AND !strstr($_SERVER['HTTP_REFERER'], '.html'))
{
if (isset($regs[2]) AND is_numeric($regs[2]))
{
if (Product::idIsOnCategoryId((int)($this->product->id), array('0' => array('id_category' => (int)($regs[2])))))
$category = new Category((int)($regs[2]), (int)(self::$cookie->id_lang));
}
elseif (isset($regs[5]) AND is_numeric($regs[5]))
{
if (Product::idIsOnCategoryId((int)($this->product->id), array('0' => array('id_category' => (int)($regs[5])))))
$category = new Category((int)($regs[5]), (int)(self::$cookie->id_lang));
}
}
if (!$category)
$category = new Category($this->product->id_category_default, (int)(self::$cookie->id_lang));
if (isset($category) AND Validate::isLoadedObject($category))
{
self::$smarty->assign(array(
'path' => Tools::getPath((int)$category->id, $this->product->name, true),
'category' => $category,
'subCategories' => $category->getSubCategories((int)self::$cookie->id_lang, true),
'id_category_current' => (int)$category->id,
'id_category_parent' => (int)$category->id_parent,
'return_category_name' => Tools::safeOutput($category->name)
));
}
else
self::$smarty->assign('path', Tools::getPath((int)$this->product->id_category_default, $this->product->name));
self::$smarty->assign('return_link', (isset($category->id) AND $category->id) ? Tools::safeOutput(self::$link->getCategoryLink($category)) : 'javascript: history.back();');
if (Pack::isPack((int)$this->product->id) AND !Pack::isInStock((int)$this->product->id))
$this->product->quantity = 0;
$group_reduction = (100 - Group::getReduction((int)self::$cookie->id_customer)) / 100;
$id_customer = (isset(self::$cookie->id_customer) AND self::$cookie->id_customer) ? (int)(self::$cookie->id_customer) : 0;
$id_group = $id_customer ? (int)(Customer::getDefaultGroupId($id_customer)) : _PS_DEFAULT_CUSTOMER_GROUP_;
$id_country = (int)($id_customer ? Customer::getCurrentCountry($id_customer) : Configuration::get('PS_COUNTRY_DEFAULT'));
// Tax
$tax = (float)(Tax::getProductTaxRate((int)($this->product->id), $cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}));
self::$smarty->assign('tax_rate', $tax);
$productPriceWithTax = Product::getPriceStatic($this->product->id, true, NULL, 6);
if (Product::$_taxCalculationMethod == PS_TAX_INC)
$productPriceWithTax = Tools::ps_round($productPriceWithTax, 2);
$productPriceWithoutEcoTax = (float)($productPriceWithTax - $this->product->ecotax);
$ecotax_rate = (float) Tax::getProductEcotaxRate($cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
$ecotaxTaxAmount = Tools::ps_round($this->product->ecotax, 2);
if (Product::$_taxCalculationMethod == PS_TAX_INC && (int)Configuration::get('PS_TAX'))
$ecotaxTaxAmount = Tools::ps_round($ecotaxTaxAmount * (1 + $ecotax_rate / 100), 2);
self::$smarty->assign(array(
'quantity_discounts' => $this->formatQuantityDiscounts(SpecificPrice::getQuantityDiscounts((int)$this->product->id, (int)Shop::getCurrentShop(), (int)self::$cookie->id_currency, $id_country, $id_group), $this->product->getPrice(Product::$_taxCalculationMethod == PS_TAX_INC, false), (float)$tax),
'product' => $this->product,
'ecotax_tax_inc' => $ecotaxTaxAmount,
'ecotax_tax_exc' => Tools::ps_round($this->product->ecotax, 2),
'ecotaxTax_rate' => $ecotax_rate,
'homeSize' => Image::getSize('home'),
'product_manufacturer' => new Manufacturer((int)$this->product->id_manufacturer, self::$cookie->id_lang),
'token' => Tools::getToken(false),
'productPriceWithoutEcoTax' => (float)($productPriceWithoutEcoTax),
'features' => $features,
'attachments' => $attachments,
'allow_oosp' => $this->product->isAvailableWhenOutOfStock((int)($this->product->out_of_stock)),
'last_qties' => (int)Configuration::get('PS_LAST_QTIES'),
'group_reduction' => $group_reduction,
'col_img_dir' => _PS_COL_IMG_DIR_,
));
self::$smarty->assign(array(
'HOOK_EXTRA_LEFT' => Module::hookExec('extraLeft'),
'HOOK_EXTRA_RIGHT' => Module::hookExec('extraRight'),
'HOOK_PRODUCT_OOS' => Hook::productOutOfStock($this->product),
'HOOK_PRODUCT_FOOTER' => Hook::productFooter($this->product, $category),
'HOOK_PRODUCT_ACTIONS' => Module::hookExec('productActions'),
'HOOK_PRODUCT_TAB' => Module::hookExec('productTab'),
'HOOK_PRODUCT_TAB_CONTENT' => Module::hookExec('productTabContent')
));
$images = $this->product->getImages((int)self::$cookie->id_lang);
$productImages = array();
foreach ($images AS $k => $image)
{
if ($image['cover'])
{
self::$smarty->assign('mainImage', $images[0]);
$cover = $image;
$cover['id_image'] = (Configuration::get('PS_LEGACY_IMAGES') ? ($this->product->id.'-'.$image['id_image']) : $image['id_image']);
$cover['id_image_only'] = (int)($image['id_image']);
}
$productImages[(int)$image['id_image']] = $image;
}
if (!isset($cover))
$cover = array('id_image' => Language::getIsoById(self::$cookie->id_lang).'-default', 'legend' => 'No picture', 'title' => 'No picture');
$size = Image::getSize('large');
self::$smarty->assign(array(
'cover' => $cover,
'imgWidth' => (int)($size['width']),
'mediumSize' => Image::getSize('medium'),
'largeSize' => Image::getSize('large'),
'accessories' => $this->product->getAccessories((int)self::$cookie->id_lang)
));
if (count($productImages))
self::$smarty->assign('images', $productImages);
/* Attributes / Groups & colors */
$colors = array();
$attributesGroups = $this->product->getAttributesGroups((int)(self::$cookie->id_lang)); // @todo (RM) should only get groups and not all declination ?
if (is_array($attributesGroups) AND $attributesGroups)
{
$groups = array();
$combinationImages = $this->product->getCombinationImages((int)(self::$cookie->id_lang));
foreach ($attributesGroups AS $k => $row)
{
/* Color management */
if (((isset($row['attribute_color']) AND $row['attribute_color']) OR (file_exists(_PS_COL_IMG_DIR_.$row['id_attribute'].'.jpg'))) AND $row['id_attribute_group'] == $this->product->id_color_default)
{
$colors[$row['id_attribute']]['value'] = $row['attribute_color'];
$colors[$row['id_attribute']]['name'] = $row['attribute_name'];
if (!isset($colors[$row['id_attribute']]['attributes_quantity']))
$colors[$row['id_attribute']]['attributes_quantity'] = 0;
$colors[$row['id_attribute']]['attributes_quantity'] += (int)($row['quantity']);
}
if (!isset($groups[$row['id_attribute_group']]))
{
$groups[$row['id_attribute_group']] = array(
'name' => $row['public_group_name'],
'is_color_group' => $row['is_color_group'],
'default' => -1,
);
}
$groups[$row['id_attribute_group']]['attributes'][$row['id_attribute']] = $row['attribute_name'];
if ($row['default_on'] && $groups[$row['id_attribute_group']]['default'] == -1)
$groups[$row['id_attribute_group']]['default'] = (int)($row['id_attribute']);
if (!isset($groups[$row['id_attribute_group']]['attributes_quantity'][$row['id_attribute']]))
$groups[$row['id_attribute_group']]['attributes_quantity'][$row['id_attribute']] = 0;
$groups[$row['id_attribute_group']]['attributes_quantity'][$row['id_attribute']] += (int)($row['quantity']);
$combinations[$row['id_product_attribute']]['attributes_values'][$row['id_attribute_group']] = $row['attribute_name'];
$combinations[$row['id_product_attribute']]['attributes'][] = (int)($row['id_attribute']);
$combinations[$row['id_product_attribute']]['price'] = (float)($row['price']);
$combinations[$row['id_product_attribute']]['ecotax'] = (float)($row['ecotax']);
$combinations[$row['id_product_attribute']]['weight'] = (float)($row['weight']);
$combinations[$row['id_product_attribute']]['quantity'] = (int)($row['quantity']);
$combinations[$row['id_product_attribute']]['reference'] = $row['reference'];
$combinations[$row['id_product_attribute']]['unit_impact'] = $row['unit_price_impact'];
$combinations[$row['id_product_attribute']]['minimal_quantity'] = $row['minimal_quantity'];
$combinations[$row['id_product_attribute']]['id_image'] = isset($combinationImages[$row['id_product_attribute']][0]['id_image']) ? $combinationImages[$row['id_product_attribute']][0]['id_image'] : -1;
}
//wash attributes list (if some attributes are unavailables and if allowed to wash it)
if (!Product::isAvailableWhenOutOfStock($this->product->out_of_stock) && Configuration::get('PS_DISP_UNAVAILABLE_ATTR') == 0)
{
foreach ($groups AS &$group)
foreach ($group['attributes_quantity'] AS $key => &$quantity)
if (!$quantity)
unset($group['attributes'][$key]);
foreach ($colors AS $key => $color)
if (!$color['attributes_quantity'])
unset($colors[$key]);
}
foreach ($groups AS &$group)
natcasesort($group['attributes']);
foreach ($combinations AS $id_product_attribute => $comb)
{
$attributeList = '';
foreach ($comb['attributes'] AS $id_attribute)
$attributeList .= '\''.(int)($id_attribute).'\',';
$attributeList = rtrim($attributeList, ',');
$combinations[$id_product_attribute]['list'] = $attributeList;
}
self::$smarty->assign(array(
'groups' => $groups,
'combinaisons' => $combinations, /* Kept for compatibility purpose only */
'combinations' => $combinations,
'colors' => (sizeof($colors) AND $this->product->id_color_default) ? $colors : false,
'combinationImages' => $combinationImages));
}
self::$smarty->assign(array(
'no_tax' => Tax::excludeTaxeOption() OR !Tax::getProductTaxRate((int)$this->product->id, $cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')}),
'customizationFields' => ($this->product->customizable ? $this->product->getCustomizationFields((int)self::$cookie->id_lang) : false)
));
// Pack management
self::$smarty->assign('packItems', $this->product->cache_is_pack ? Pack::getItemTable($this->product->id, (int)(self::$cookie->id_lang), true) : array());
self::$smarty->assign('packs', Pack::getPacksTable($this->product->id, (int)(self::$cookie->id_lang), true, 1));
}
}
self::$smarty->assign(array(
'ENT_NOQUOTES' => ENT_NOQUOTES,
'outOfStockAllowed' => (int)(Configuration::get('PS_ORDER_OUT_OF_STOCK')),
'errors' => $this->errors,
'categories' => Category::getHomeCategories((int)self::$cookie->id_lang),
'have_image' => (isset($cover) ? (int)$cover['id_image'] : false),
'tax_enabled' => Configuration::get('PS_TAX'),
'display_qties' => (int)Configuration::get('PS_DISPLAY_QTIES'),
'display_ht' => !Tax::excludeTaxeOption(),
'ecotax' => (!sizeof($this->errors) AND $this->product->ecotax > 0 ? Tools::convertPrice((float)($this->product->ecotax)) : 0),
'currencySign' => $currency->sign,
'currencyRate' => $currency->conversion_rate,
'currencyFormat' => $currency->format,
'currencyBlank' => $currency->blank,
'jqZoomEnabled' => Configuration::get('PS_DISPLAY_JQZOOM')
));
}
public function displayContent()
{
parent::displayContent();
self::$smarty->display(_PS_THEME_DIR_.'product.tpl');
}
public function pictureUpload(Product $product, Cart $cart)
{
if (!$fieldIds = $this->product->getCustomizationFieldIds())
return false;
$authorizedFileFields = array();
foreach ($fieldIds AS $fieldId)
if ($fieldId['type'] == _CUSTOMIZE_FILE_)
$authorizedFileFields[(int)($fieldId['id_customization_field'])] = 'file'.(int)($fieldId['id_customization_field']);
$indexes = array_flip($authorizedFileFields);
foreach ($_FILES AS $fieldName => $file)
if (in_array($fieldName, $authorizedFileFields) AND isset($file['tmp_name']) AND !empty($file['tmp_name']))
{
$fileName = md5(uniqid(rand(), true));
if ($error = checkImage($file, (int)(Configuration::get('PS_PRODUCT_PICTURE_MAX_SIZE'))))
$this->errors[] = $error;
if ($error OR (!$tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS') OR !move_uploaded_file($file['tmp_name'], $tmpName)))
return false;
/* Original file */
elseif (!imageResize($tmpName, _PS_UPLOAD_DIR_.$fileName))
$this->errors[] = Tools::displayError('An error occurred during the image upload.');
/* A smaller one */
elseif (!imageResize($tmpName, _PS_UPLOAD_DIR_.$fileName.'_small', (int)(Configuration::get('PS_PRODUCT_PICTURE_WIDTH')), (int)(Configuration::get('PS_PRODUCT_PICTURE_HEIGHT'))))
$this->errors[] = Tools::displayError('An error occurred during the image upload.');
elseif (!chmod(_PS_UPLOAD_DIR_.$fileName, 0777) OR !chmod(_PS_UPLOAD_DIR_.$fileName.'_small', 0777))
$this->errors[] = Tools::displayError('An error occurred during the image upload.');
else
$cart->addPictureToProduct((int)($this->product->id), $indexes[$fieldName], $fileName);
unlink($tmpName);
}
return true;
}
public function textRecord(Product $product, Cart $cart)
{
if (!$fieldIds = $this->product->getCustomizationFieldIds())
return false;
$authorizedTextFields = array();
foreach ($fieldIds AS $fieldId)
if ($fieldId['type'] == _CUSTOMIZE_TEXTFIELD_)
$authorizedTextFields[(int)($fieldId['id_customization_field'])] = 'textField'.(int)($fieldId['id_customization_field']);
$indexes = array_flip($authorizedTextFields);
foreach ($_POST AS $fieldName => $value)
if (in_array($fieldName, $authorizedTextFields) AND !empty($value))
{
if (!Validate::isMessage($value))
$this->errors[] = Tools::displayError('Invalid message');
else
$cart->addTextFieldToProduct((int)($this->product->id), $indexes[$fieldName], $value);
}
elseif (in_array($fieldName, $authorizedTextFields) AND empty($value))
$cart->deleteTextFieldFromProduct((int)($this->product->id), $indexes[$fieldName]);
}
public function formTargetFormat()
{
$customizationFormTarget = Tools::safeOutput(urldecode($_SERVER['REQUEST_URI']));
foreach ($_GET AS $field => $value)
if (strncmp($field, 'group_', 6) == 0)
$customizationFormTarget = preg_replace('/&group_([[:digit:]]+)=([[:digit:]]+)/', '', $customizationFormTarget);
if (isset($_POST['quantityBackup']))
self::$smarty->assign('quantityBackup', (int)($_POST['quantityBackup']));
self::$smarty->assign('customizationFormTarget', $customizationFormTarget);
}
public function formatQuantityDiscounts($specificPrices, $price, $taxRate)
{
foreach ($specificPrices AS $key => &$row)
{
$row['quantity'] = &$row['from_quantity'];
// The price may be directly set
if ($row['price'] != 0)
{
$cur_price = (Product::$_taxCalculationMethod == PS_TAX_EXC ? $row['price'] : $row['price'] * (1 + $taxRate / 100));
if ($row['reduction_type'] == 'amount')
$cur_price = Product::$_taxCalculationMethod == PS_TAX_INC ? $cur_price - $row['reduction'] : $cur_price - ($row['reduction'] / (1 + $taxRate / 100));
else
$cur_price = $cur_price * ( 1 - ($row['reduction']));
$row['real_value'] = $price - $cur_price;
}
else
{
global $cookie;
$id_currency = (int)$cookie->id_currency;
if ($row['reduction_type'] == 'amount')
{
$reduction_amount = $row['reduction'];
if (!$row['id_currency'])
$reduction_amount = Tools::convertPrice($reduction_amount, $id_currency);
$row['real_value'] = Product::$_taxCalculationMethod == PS_TAX_INC ? $reduction_amount : $reduction_amount / (1 + $taxRate / 100);
}
else
{
$row['real_value'] = $row['reduction'] * 100;
}
}
$row['nextQuantity'] = (isset($specificPrices[$key + 1]) ? (int)($specificPrices[$key + 1]['from_quantity']) : -1);
}
return $specificPrices;
}
}