AdminProductsController.php 191 KB
Newer Older
1 2
<?php
/*
3
* 2007-2015 PrestaShop
4
*
5
* NOTICE OF LICENSE
6
*
7
* This source file is subject to the Open Software License (OSL 3.0)
8 9
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
10
* http://opensource.org/licenses/osl-3.0.php
11 12 13 14 15 16 17 18 19 20 21
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
*  @author PrestaShop SA <contact@prestashop.com>
22
*  @copyright  2007-2015 PrestaShop SA
23
*  @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
24 25
*  International Registered Trademark & Property of PrestaShop SA
*/
26

27
class AdminProductsControllerCore extends AdminController
28 29 30
{
	/** @var integer Max image size for upload
	 * As of 1.5 it is recommended to not set a limit to max image size
31
	 */
rMalie's avatar
rMalie committed
32 33
	protected $max_file_size = null;
	protected $max_image_size = null;
34

35
	protected $_category;
36 37 38 39
	/**
	 * @var string name of the tab to display
	 */
	protected $tab_display;
40
	protected $tab_display_module;
41

42 43 44 45 46
	/**
	 * The order in the array decides the order in the list of tab. If an element's value is a number, it will be preloaded.
	 * The tabs are preloaded from the smallest to the highest number.
	 * @var array Product tabs.
	 */
47
	protected $available_tabs = array();
48

49 50
	protected $default_tab = 'Informations';

51
	protected $available_tabs_lang = array();
52

53 54
	protected $position_identifier = 'id_product';

55
	protected $submitted_tabs;
56

57
	protected $id_current_category;
58

59 60
	public function __construct()
	{
61
		$this->bootstrap = true;
62 63 64
		$this->table = 'product';
		$this->className = 'Product';
		$this->lang = true;
65
		$this->explicitSelect = true;
66 67 68
		$this->bulk_actions = array(
			'delete' => array(
				'text' => $this->l('Delete selected'),
69
				'icon' => 'icon-trash',
70 71 72
				'confirm' => $this->l('Delete selected items?')
			)
		);
73 74 75
		if (!Tools::getValue('id_product'))
			$this->multishop_context_group = false;

76 77
		parent::__construct();

78
		$this->imageType = 'jpg';
79
		$this->_defaultOrderBy = 'position';
80
		$this->max_file_size = (int)(Configuration::get('PS_LIMIT_UPLOAD_FILE_VALUE') * 1000000);
lLefevre's avatar
lLefevre committed
81
		$this->max_image_size = (int)Configuration::get('PS_PRODUCT_PICTURE_MAX_SIZE');
82
		$this->allow_export = true;
aFolletete's avatar
aFolletete committed
83

84
		// @since 1.5 : translations for tabs
85
		$this->available_tabs_lang = array(
86
			'Informations' => $this->l('Information'),
87 88
			'Pack' => $this->l('Pack'),
			'VirtualProduct' => $this->l('Virtual Product'),
89
			'Prices' => $this->l('Prices'),
90
			'Seo' => $this->l('SEO'),
91
			'Images' => $this->l('Images'),
92
			'Associations' => $this->l('Associations'),
93
			'Shipping' => $this->l('Shipping'),
94
			'Combinations' => $this->l('Combinations'),
95
			'Features' => $this->l('Features'),
96 97 98
			'Customization' => $this->l('Customization'),
			'Attachments' => $this->l('Attachments'),
			'Quantities' => $this->l('Quantities'),
99
			'Suppliers' => $this->l('Suppliers'),
100 101 102
			'Warehouses' => $this->l('Warehouses'),
		);

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
		$this->available_tabs = array('Quantities' => 6, 'Warehouses' => 14);
		if ($this->context->shop->getContext() != Shop::CONTEXT_GROUP)
			$this->available_tabs = array_merge($this->available_tabs, array(
				'Informations' => 0,
				'Pack' => 7,
				'VirtualProduct' => 8,
				'Prices' => 1,
				'Seo' => 2,
				'Associations' => 3,
				'Images' => 9,
				'Shipping' => 4,
				'Combinations' => 5,
				'Features' => 10,
				'Customization' => 11,
				'Attachments' => 12,
				'Suppliers' => 13,
			));

		// Sort the tabs that need to be preloaded by their priority number
		asort($this->available_tabs, SORT_NUMERIC);
123 124 125 126

		/* Adding tab if modules are hooked */
		$modules_list = Hook::getHookModuleExecList('displayAdminProductsExtra');
		if (is_array($modules_list) && count($modules_list) > 0)
127 128 129 130 131
			foreach ($modules_list as $m)
			{
				$this->available_tabs['Module'.ucfirst($m['module'])] = 23;
				$this->available_tabs_lang['Module'.ucfirst($m['module'])] = Module::getModuleName($m['module']);
			}
132

133 134
		if (Tools::getValue('reset_filter_category'))
			$this->context->cookie->id_category_products_filter = false;
135 136 137 138 139 140 141 142 143
		if (Shop::isFeatureActive() && $this->context->cookie->id_category_products_filter)
		{
			$category = new Category((int)$this->context->cookie->id_category_products_filter);
			if (!$category->inShop())
			{
				$this->context->cookie->id_category_products_filter = false;
				Tools::redirectAdmin($this->context->link->getAdminLink('AdminProducts'));
			}
		}
144
		/* Join categories table */
145 146
		if ($id_category = (int)Tools::getValue('productFilter_cl!name'))
		{
147
			$this->_category = new Category((int)$id_category);
148 149
			$_POST['productFilter_cl!name'] = $this->_category->name[$this->context->language->id];
		}
150
		else
151 152 153 154
		{
			if ($id_category = (int)Tools::getValue('id_category'))
			{
				$this->id_current_category = $id_category;
155
				$this->context->cookie->id_category_products_filter = $id_category;
156 157 158 159 160 161 162 163
			}
			elseif ($id_category = $this->context->cookie->id_category_products_filter)
				$this->id_current_category = $id_category;
			if ($this->id_current_category)
				$this->_category = new Category((int)$this->id_current_category);
			else
				$this->_category = new Category();
		}
164

165
		$join_category = false;
166
		if (Validate::isLoadedObject($this->_category) && empty($this->_filter))
167 168
			$join_category = true;

Damien Metzger's avatar
Damien Metzger committed
169
		$this->_join .= '
170
		LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = a.`id_product`)
171 172
		LEFT JOIN `'._DB_PREFIX_.'stock_available` sav ON (sav.`id_product` = a.`id_product` AND sav.`id_product_attribute` = 0
		'.StockAvailable::addSqlShopRestriction(null, null, 'sav').') ';
173

174 175
		$alias = 'sa';
		$alias_image = 'image_shop';
176

177 178 179
		$id_shop = Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP? (int)$this->context->shop->id : 'a.id_shop_default';
		$this->_join .= ' JOIN `'._DB_PREFIX_.'product_shop` sa ON (a.`id_product` = sa.`id_product` AND sa.id_shop = '.$id_shop.')
				LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON ('.$alias.'.`id_category_default` = cl.`id_category` AND b.`id_lang` = cl.`id_lang` AND cl.id_shop = '.$id_shop.')
180
				LEFT JOIN `'._DB_PREFIX_.'shop` shop ON (shop.id_shop = '.$id_shop.')
181 182
				LEFT JOIN `'._DB_PREFIX_.'image_shop` image_shop ON (image_shop.`id_image` = i.`id_image` AND image_shop.`cover` = 1 AND image_shop.id_shop = '.$id_shop.')
				LEFT JOIN `'._DB_PREFIX_.'product_download` pd ON (pd.`id_product` = a.`id_product`)';
183

184
		$this->_select .= 'shop.name as shopname, a.id_shop_default, ';
Jérôme Nadaud's avatar
Jérôme Nadaud committed
185
		$this->_select .= 'MAX('.$alias_image.'.id_image) id_image, cl.name `name_category`, '.$alias.'.`price`, 0 AS price_final, a.`is_virtual`, pd.`nb_downloadable`, sav.`quantity` as sav_quantity, '.$alias.'.`active`, IF(sav.`quantity`<=0, 1, 0) badge_danger';
186

Damien Metzger's avatar
Damien Metzger committed
187 188 189
		if ($join_category)
		{
			$this->_join .= ' INNER JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_product` = a.`id_product` AND cp.`id_category` = '.(int)$this->_category->id.') ';
190
			$this->_select .= ' , cp.`position`, ';
Damien Metzger's avatar
Damien Metzger committed
191
		}
192 193 194

		$this->_group = 'GROUP BY '.$alias.'.id_product';

195 196 197 198
		$this->fields_list = array();
		$this->fields_list['id_product'] = array(
			'title' => $this->l('ID'),
			'align' => 'center',
199
			'class' => 'fixed-width-xs',
Kevin Granger's avatar
Kevin Granger committed
200
			'type' => 'int'
201 202
		);
		$this->fields_list['image'] = array(
203
			'title' => $this->l('Image'),
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
			'align' => 'center',
			'image' => 'p',
			'orderby' => false,
			'filter' => false,
			'search' => false
		);
		$this->fields_list['name'] = array(
			'title' => $this->l('Name'),
			'filter_key' => 'b!name'
		);
		$this->fields_list['reference'] = array(
			'title' => $this->l('Reference'),
			'align' => 'left',
		);

		if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP)
			$this->fields_list['shopname'] = array(
221
				'title' => $this->l('Default shop'),
222 223 224 225 226 227 228 229 230 231
				'filter_key' => 'shop!name',
			);
		else
			$this->fields_list['name_category'] = array(
				'title' => $this->l('Category'),
				'filter_key' => 'cl!name',
			);
		$this->fields_list['price'] = array(
			'title' => $this->l('Base price'),
			'type' => 'price',
Kevin Granger's avatar
Kevin Granger committed
232
			'align' => 'text-right',
233 234 235 236 237
			'filter_key' => 'a!price'
		);
		$this->fields_list['price_final'] = array(
			'title' => $this->l('Final price'),
			'type' => 'price',
Kevin Granger's avatar
Kevin Granger committed
238
			'align' => 'text-right',
239
			'havingFilter' => true,
240 241
			'orderby' => false,
			'search' => false
242
		);
243

244 245 246
		if (Configuration::get('PS_STOCK_MANAGEMENT'))
			$this->fields_list['sav_quantity'] = array(
				'title' => $this->l('Quantity'),
247
				'type' => 'int',
Kevin Granger's avatar
Kevin Granger committed
248
				'align' => 'text-right',
249 250
				'filter_key' => 'sav!quantity',
				'orderby' => true,
251
				'badge_danger' => true,
252
				//'hint' => $this->l('This is the quantity available in the current shop/group.'),
253
			);
254

255
		$this->fields_list['active'] = array(
256
			'title' => $this->l('Status'),
257 258
			'active' => 'status',
			'filter_key' => $alias.'!active',
Kevin Granger's avatar
Kevin Granger committed
259
			'align' => 'text-center',
260
			'type' => 'bool',
261
			'class' => 'fixed-width-sm',
262 263
			'orderby' => false
		);
264

Damien Metzger's avatar
Damien Metzger committed
265
		if ($join_category && (int)$this->id_current_category)
266 267 268 269 270 271
			$this->fields_list['position'] = array(
				'title' => $this->l('Position'),
				'filter_key' => 'cp!position',
				'align' => 'center',
				'position' => 'position'
			);
272
	}
273

274 275 276 277 278 279 280 281
	public static function getQuantities($echo, $tr)
	{
		if ((int)$tr['is_virtual'] == 1 && $tr['nb_downloadable'] == 0)
			return '&infin;';
		else
			return $echo;
	}

282 283
	public function setMedia()
	{
284 285
		parent::setMedia();

286 287 288 289 290 291 292
		$bo_theme = ((Validate::isLoadedObject($this->context->employee)
			&& $this->context->employee->bo_theme) ? $this->context->employee->bo_theme : 'default');

		if (!file_exists(_PS_BO_ALL_THEMES_DIR_.$bo_theme.DIRECTORY_SEPARATOR
			.'template'))
			$bo_theme = 'default';

293 294 295
		$this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$bo_theme.'/js/jquery.iframe-transport.js');
		$this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$bo_theme.'/js/jquery.fileupload.js');
		$this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$bo_theme.'/js/jquery.fileupload-process.js');
296
		$this->addJs(__PS_BASE_URI__.$this->admin_webpath.'/themes/'.$bo_theme.'/js/jquery.fileupload-validate.js');
297 298 299
		$this->addJs(__PS_BASE_URI__.'js/vendor/spin.js');
		$this->addJs(__PS_BASE_URI__.'js/vendor/ladda.js');
	}
300

301
	protected function _cleanMetaKeywords($keywords)
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
	{
		if (!empty($keywords) && $keywords != '')
		{
			$out = array();
			$words = explode(',', $keywords);
			foreach ($words as $word_item)
			{
				$word_item = trim($word_item);
				if (!empty($word_item) && $word_item != '')
					$out[] = $word_item;
			}
			return ((count($out) > 0) ? implode(',', $out) : '');
		}
		else
			return '';
	}

	protected function copyFromPost(&$object, $table)
	{
		parent::copyFromPost($object, $table);
		if (get_class($object) != 'Product')
			return;

		/* Additional fields */
		$languages = Language::getLanguages(false);
		foreach ($languages as $language)
			if (isset($_POST['meta_keywords_'.$language['id_lang']]))
			{
330
				$_POST['meta_keywords_'.$language['id_lang']] = $this->_cleanMetaKeywords(Tools::strtolower($_POST['meta_keywords_'.$language['id_lang']]));
331
				// preg_replace('/ *,? +,* /', ',', strtolower($_POST['meta_keywords_'.$language['id_lang']]));
332 333 334 335 336 337
				$object->meta_keywords[$language['id_lang']] = $_POST['meta_keywords_'.$language['id_lang']];
			}
		$_POST['width'] = empty($_POST['width']) ? '0' : str_replace(',', '.', $_POST['width']);
		$_POST['height'] = empty($_POST['height']) ? '0' : str_replace(',', '.', $_POST['height']);
		$_POST['depth'] = empty($_POST['depth']) ? '0' : str_replace(',', '.', $_POST['depth']);
		$_POST['weight'] = empty($_POST['weight']) ? '0' : str_replace(',', '.', $_POST['weight']);
338

339
		if (Tools::getIsset('unit_price') != null)
340
			$object->unit_price = str_replace(',', '.', Tools::getValue('unit_price'));
341
		if (Tools::getIsset('ecotax') != null)
342
			$object->ecotax = str_replace(',', '.', Tools::getValue('ecotax'));
343 344

		if ($this->isTabSubmitted('Informations'))
sfroment42's avatar
sfroment42 committed
345
		{
346
			$object->available_for_order = (int)Tools::getValue('available_for_order');
sfroment42's avatar
sfroment42 committed
347 348 349 350 351
			$object->show_price = $object->available_for_order ? 1 : (int)Tools::getValue('show_price');
			$object->online_only = (int)Tools::getValue('online_only');
		}
		if ($this->isTabSubmitted('Prices'))
			$object->on_sale = (int)Tools::getValue('on_sale');
352
	}
353

354 355 356 357 358 359 360 361 362
	public function getList($id_lang, $orderBy = null, $orderWay = null, $start = 0, $limit = null, $id_lang_shop = null)
	{
		$orderByPriceFinal = (empty($orderBy) ? ($this->context->cookie->__get($this->table.'Orderby') ? $this->context->cookie->__get($this->table.'Orderby') : 'id_'.$this->table) : $orderBy);
		$orderWayPriceFinal = (empty($orderWay) ? ($this->context->cookie->__get($this->table.'Orderway') ? $this->context->cookie->__get($this->table.'Orderby') : 'ASC') : $orderWay);
		if ($orderByPriceFinal == 'price_final')
		{
			$orderBy = 'id_'.$this->table;
			$orderWay = 'ASC';
		}
363
		parent::getList($id_lang, $orderBy, $orderWay, $start, $limit, $this->context->shop->id);
364 365 366 367 368

		/* update product quantity with attributes ...*/
		$nb = count($this->_list);
		if ($this->_list)
		{
369 370
			$context = $this->context->cloneContext();
			$context->shop = clone($context->shop);
371 372
			/* update product final price */
			for ($i = 0; $i < $nb; $i++)
373
			{
374 375 376
				if (Context::getContext()->shop->getContext() != Shop::CONTEXT_SHOP)
					$context->shop = new Shop((int)$this->_list[$i]['id_shop_default']);

377 378
				// convert price with the currency from context
				$this->_list[$i]['price'] = Tools::convertPrice($this->_list[$i]['price'], $this->context->currency, true, $this->context);
379
				$this->_list[$i]['price_tmp'] = Product::getPriceStatic($this->_list[$i]['id_product'], true, null, 2, null, false, true, 1, true, null, null, null, $nothing, true, true, $context);
380
			}
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
		}

		if ($orderByPriceFinal == 'price_final')
		{
			if (strtolower($orderWayPriceFinal) == 'desc')
				uasort($this->_list, 'cmpPriceDesc');
			else
				uasort($this->_list, 'cmpPriceAsc');
		}
		for ($i = 0; $this->_list && $i < $nb; $i++)
		{
			$this->_list[$i]['price_final'] = $this->_list[$i]['price_tmp'];
			unset($this->_list[$i]['price_tmp']);
		}
	}

Rémi Gaillard's avatar
Rémi Gaillard committed
397 398 399
	protected function loadObject($opt = false)
	{
		$result = parent::loadObject($opt);
400
		if ($result && Validate::isLoadedObject($this->object))
401
		{
402
			if (Shop::getContext() == Shop::CONTEXT_SHOP && Shop::isFeatureActive() && !$this->object->isAssociatedToShop())
403 404 405 406
			{
				$default_product = new Product((int)$this->object->id, false, null, (int)$this->object->id_shop_default);
				$def = ObjectModel::getDefinition($this->object);
				foreach ($def['fields'] as $field_name => $row)
407
				{
408
					if (is_array($default_product->$field_name))
Rémi Gaillard's avatar
Rémi Gaillard committed
409
						foreach ($default_product->$field_name as $key => $value)
410
							$this->object->{$field_name}[$key] = $value;
411
					else
412
						$this->object->$field_name = $default_product->$field_name;
413
				}
414
			}
Rémi Gaillard's avatar
Rémi Gaillard committed
415
			$this->object->loadStockData();
416
		}
Rémi Gaillard's avatar
Rémi Gaillard committed
417 418 419
		return $result;
	}

420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
	public function ajaxProcessGetCountriesOptions()
	{
		if (!$res = Country::getCountriesByIdShop((int)Tools::getValue('id_shop'), (int)$this->context->language->id))
			return ;

		$tpl = $this->createTemplate('specific_prices_shop_update.tpl');
		$tpl->assign(array(
			'option_list' => $res,
			'key_id' => 'id_country',
			'key_value' => 'name'
			)
		);

		$this->content = $tpl->fetch();
	}
435

436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
	public function ajaxProcessGetCurrenciesOptions()
	{
		if (!$res = Currency::getCurrenciesByIdShop((int)Tools::getValue('id_shop')))
			return ;

		$tpl = $this->createTemplate('specific_prices_shop_update.tpl');
		$tpl->assign(array(
			'option_list' => $res,
			'key_id' => 'id_currency',
			'key_value' => 'name'
			)
		);

		$this->content = $tpl->fetch();
	}
451

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
	public function ajaxProcessGetGroupsOptions()
	{
		if (!$res = Group::getGroups((int)$this->context->language->id, (int)Tools::getValue('id_shop')))
			return ;

		$tpl = $this->createTemplate('specific_prices_shop_update.tpl');
		$tpl->assign(array(
			'option_list' => $res,
			'key_id' => 'id_group',
			'key_value' => 'name'
			)
		);

		$this->content = $tpl->fetch();
	}

468
	public function processDeleteVirtualProduct()
469
	{
470
		if (!($id_product_download = ProductDownload::getIdFromIdProduct((int)Tools::getValue('id_product'))))
471
			$this->errors[] = Tools::displayError('Cannot retrieve file');
472 473 474
		else
		{
			$product_download = new ProductDownload((int)$id_product_download);
475

476
			if (!$product_download->deleteFile((int)$id_product_download))
477
				$this->errors[] = Tools::displayError('Cannot delete file');
478
			else
479
				$this->redirect_after = self::$currentIndex.'&id_product='.(int)Tools::getValue('id_product').'&updateproduct&key_tab=VirtualProduct&conf=1&token='.$this->token;
480
		}
481 482 483

		$this->display = 'edit';
		$this->tab_display = 'VirtualProduct';
484 485
	}

486 487 488 489 490 491 492 493 494 495 496 497
	public function ajaxProcessAddAttachment()
	{
		if (isset($_FILES['attachment_file']))
		{
			if ((int)$_FILES['attachment_file']['error'] === 1)
			{
				$_FILES['attachment_file']['error'] = array();

				$max_upload = (int)ini_get('upload_max_filesize');
				$max_post = (int)ini_get('post_max_size');
				$upload_mb = min($max_upload, $max_post);
				$_FILES['attachment_file']['error'][] = sprintf(
498
					$this->l('File %1$s exceeds the size allowed by the server. The limit is set to %2$d MB.'),
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
					'<b>'.$_FILES['attachment_file']['name'].'</b> ',
					'<b>'.$upload_mb.'</b>'
				);
			}

			$_FILES['attachment_file']['error'] = array();

			$is_attachment_name_valid = false;
			$attachment_names = Tools::getValue('attachment_name');
			$attachment_descriptions = Tools::getValue('attachment_description');

			if (!isset($attachment_names) || !$attachment_names)
				$attachment_names = array();

			if (!isset($attachment_descriptions) || !$attachment_descriptions)
				$attachment_descriptions = array();

			foreach ($attachment_names as $lang => $name)
			{
Jerome Nadaud's avatar
Jerome Nadaud committed
518 519
				$language = Language::getLanguage((int)$lang);

520 521 522 523
				if (Tools::strlen($name) > 0)
					$is_attachment_name_valid = true;

				if (!Validate::isGenericName($name))
Jerome Nadaud's avatar
Jerome Nadaud committed
524
					$_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('Invalid name for %s language'), $language['name']);
525
				elseif (Tools::strlen($name) > 32)
Jerome Nadaud's avatar
Jerome Nadaud committed
526
					$_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('The name for %1s language is too long (%2d chars max).'), $language['name'], 32);
527 528 529
			}

			foreach ($attachment_descriptions as $lang => $description)
Jerome Nadaud's avatar
Jerome Nadaud committed
530 531 532
			{
				$language = Language::getLanguage((int)$lang);

533
				if (!Validate::isCleanHtml($description))
Jerome Nadaud's avatar
Jerome Nadaud committed
534 535
					$_FILES['attachment_file']['error'][] = sprintf(Tools::displayError('Invalid description for %s language'), $language['name']);
			}
536 537 538 539 540 541 542 543 544 545

			if (!$is_attachment_name_valid)
				$_FILES['attachment_file']['error'][] = Tools::displayError('An attachment name is required.');

			if (empty($_FILES['attachment_file']['error']))
			{
				if (is_uploaded_file($_FILES['attachment_file']['tmp_name']))
				{
					if ($_FILES['attachment_file']['size'] > (Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024 * 1024))
						$_FILES['attachment_file']['error'][] = sprintf(
546
							$this->l('The file is too large. Maximum size allowed is: %1$d kB. The file you are trying to upload is %2$d kB.'),
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
							(Configuration::get('PS_ATTACHMENT_MAXIMUM_SIZE') * 1024),
							number_format(($_FILES['attachment_file']['size'] / 1024), 2, '.', '')
						);
					else
					{
						do $uniqid = sha1(microtime());
						while (file_exists(_PS_DOWNLOAD_DIR_.$uniqid));
						if (!copy($_FILES['attachment_file']['tmp_name'], _PS_DOWNLOAD_DIR_.$uniqid))
							$_FILES['attachment_file']['error'][] = $this->l('File copy failed');
						@unlink($_FILES['attachment_file']['tmp_name']);
					}
				}
				else
					$_FILES['attachment_file']['error'][] = Tools::displayError('The file is missing.');

				if (empty($_FILES['attachment_file']['error']) && isset($uniqid))
				{
					$attachment = new Attachment();

					foreach ($attachment_names as $lang => $name)
						$attachment->name[(int)$lang] = $name;

					foreach ($attachment_descriptions as $lang => $description)
						$attachment->description[(int)$lang] = $description;

					$attachment->file = $uniqid;
					$attachment->mime = $_FILES['attachment_file']['type'];
					$attachment->file_name = $_FILES['attachment_file']['name'];

					if (empty($attachment->mime) || Tools::strlen($attachment->mime) > 128)
						$_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file extension');
					if (!Validate::isGenericName($attachment->file_name))
						$_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file name');
					if (Tools::strlen($attachment->file_name) > 128)
						$_FILES['attachment_file']['error'][] = Tools::displayError('The file name is too long.');
					if (empty($this->errors))
					{
						$res = $attachment->add();
						if (!$res)
							$_FILES['attachment_file']['error'][] = Tools::displayError('This attachment was unable to be loaded into the database.');
						else
						{
							$_FILES['attachment_file']['id_attachment'] = $attachment->id;
							$_FILES['attachment_file']['filename'] = $attachment->name[$this->context->employee->id_lang];
							$id_product = (int)Tools::getValue($this->identifier);
							$res = $attachment->attachProduct($id_product);
							if (!$res)
								$_FILES['attachment_file']['error'][] = Tools::displayError('We were unable to associate this attachment to a product.');
						}
					}
					else
						$_FILES['attachment_file']['error'][] = Tools::displayError('Invalid file');
				}
			}

			die(Tools::jsonEncode($_FILES));
		}
	}

606

607 608 609 610 611
	/**
	 * Attach an existing attachment to the product
	 *
	 * @return void
	 */
612
	public function processAttachments()
613
	{
614 615 616 617 618
		if ($id = (int)Tools::getValue($this->identifier))
		{
			$attachments = trim(Tools::getValue('arrayAttachments'), ',');
			$attachments = explode(',', $attachments);
			if (!Attachment::attachToProduct($id, $attachments))
619
				$this->errors[] = Tools::displayError('An error occurred while saving product attachments.');
620
		}
621 622
	}

623
	public function processDuplicate()
624
	{
625
		if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
626
		{
627
			$id_product_old = $product->id;
628 629 630 631 632 633 634 635 636 637
			if (empty($product->price) && Shop::getContext() == Shop::CONTEXT_GROUP)
			{
				$shops = ShopGroup::getShopsFromGroup(Shop::getContextShopGroupID());
				foreach ($shops as $shop)
					if ($product->isAssociatedToShop($shop['id_shop']))
					{
						$product_price = new Product($id_product_old, false, null, $shop['id_shop']);
						$product->price = $product_price->price;
					}
			}
638 639 640 641 642 643 644 645 646 647 648 649 650 651
			unset($product->id);
			unset($product->id_product);
			$product->indexed = 0;
			$product->active = 0;
			if ($product->add()
			&& Category::duplicateProductCategories($id_product_old, $product->id)
			&& ($combination_images = Product::duplicateAttributes($id_product_old, $product->id)) !== false
			&& GroupReduction::duplicateReduction($id_product_old, $product->id)
			&& Product::duplicateAccessories($id_product_old, $product->id)
			&& Product::duplicateFeatures($id_product_old, $product->id)
			&& Product::duplicateSpecificPrices($id_product_old, $product->id)
			&& Pack::duplicate($id_product_old, $product->id)
			&& Product::duplicateCustomizationFields($id_product_old, $product->id)
			&& Product::duplicateTags($id_product_old, $product->id)
652
			&& Product::duplicateDownload($id_product_old, $product->id))
653
			{
654 655
				if ($product->hasAttributes())
					Product::updateDefaultAttribute($product->id);
656

657
				if (!Tools::getValue('noimage') && !Image::duplicateProductImages($id_product_old, $product->id, $combination_images))
658
					$this->errors[] = Tools::displayError('An error occurred while copying images.');
659 660
				else
				{
661
					Hook::exec('actionProductAdd', array('id_product' => (int)$product->id, 'product' => $product));
662
					if (in_array($product->visibility, array('both', 'search')) && Configuration::get('PS_SEARCH_INDEXATION'))
663
						Search::indexation(false, $product->id);
664
					$this->redirect_after = self::$currentIndex.(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&conf=19&token='.$this->token;
665 666 667
				}
			}
			else
668
				$this->errors[] = Tools::displayError('An error occurred while creating an object.');
669
		}
670 671
	}

672
	public function processDelete()
673 674
	{
		if (Validate::isLoadedObject($object = $this->loadObject()) && isset($this->fieldImageSettings))
675
		{
676 677
			// check if request at least one object with noZeroObject
			if (isset($object->noZeroObject) && count($taxes = call_user_func(array($this->className, $object->noZeroObject))) <= 1)
678
				$this->errors[] = Tools::displayError('You need at least one object.').' <b>'.$this->table.'</b><br />'.Tools::displayError('You cannot delete all of the items.');
679
			else
680
			{
dMetzger's avatar
dMetzger committed
681 682 683 684 685 686 687
				/*
				 * @since 1.5.0
				 * It is NOT possible to delete a product if there are currently:
				 * - physical stock for this product
				 * - supply order(s) for this product
				 */
				if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $object->advanced_stock_management)
688
				{
dMetzger's avatar
dMetzger committed
689 690 691 692
					$stock_manager = StockManagerFactory::getManager();
					$physical_quantity = $stock_manager->getProductPhysicalQuantities($object->id, 0);
					$real_quantity = $stock_manager->getProductRealQuantities($object->id, 0);
					if ($physical_quantity > 0 || $real_quantity > $physical_quantity)
693
						$this->errors[] = Tools::displayError('You cannot delete this product because there is physical stock left.');
694
				}
dMetzger's avatar
dMetzger committed
695

696
				if (!count($this->errors))
dMetzger's avatar
dMetzger committed
697
				{
698 699 700 701
					if ($object->delete())
					{
						$id_category = (int)Tools::getValue('id_category');
						$category_url = empty($id_category) ? '' : '&id_category='.(int)$id_category;
Vincent Augagneur's avatar
Vincent Augagneur committed
702
						PrestaShopLogger::addLog(sprintf($this->l('%s deletion', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int)$object->id, true, (int)$this->context->employee->id);
703 704 705 706
						$this->redirect_after = self::$currentIndex.'&conf=1&token='.$this->token.$category_url;
					}
					else
						$this->errors[] = Tools::displayError('An error occurred during deletion.');
dMetzger's avatar
dMetzger committed
707
				}
708 709
			}
		}
710
		else
711
			$this->errors[] = Tools::displayError('An error occurred while deleting the object.').' <b>'.$this->table.'</b> '.Tools::displayError('(cannot load object)');
712 713
	}

714
	public function processImage()
715
	{
716 717
		$id_image = (int)Tools::getValue('id_image');
		$image = new Image((int)$id_image);
718
		if (Validate::isLoadedObject($image))
719
		{
720 721
			/* Update product image/legend */
			// @todo : move in processEditProductImage
722
			if (Tools::getIsset('editImage'))
723
			{
724 725
				if ($image->cover)
					$_POST['cover'] = 1;
726

727
				$_POST['id_image'] = $image->id;
728 729
			}

730
			/* Choose product cover image */
731
			elseif (Tools::getIsset('coverImage'))
732
			{
733 734 735
				Image::deleteCover($image->id_product);
				$image->cover = 1;
				if (!$image->update())
736
					$this->errors[] = Tools::displayError('You cannot change the product\'s cover image.');
737
				else
738
				{
739
					$productId = (int)Tools::getValue('id_product');
740
					@unlink(_PS_TMP_IMG_DIR_.'product_'.$productId.'.jpg');
741
					@unlink(_PS_TMP_IMG_DIR_.'product_mini_'.$productId.'_'.$this->context->shop->id.'.jpg');
742
					$this->redirect_after = self::$currentIndex.'&id_product='.$image->id_product.'&id_category='.(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&action=Images&addproduct'.'&token='.$this->token;
743 744
				}
			}
745 746

			/* Choose product image position */
747
			elseif (Tools::getIsset('imgPosition') && Tools::getIsset('imgDirection'))
748 749
			{
				$image->updatePosition(Tools::getValue('imgDirection'), Tools::getValue('imgPosition'));
750
				$this->redirect_after = self::$currentIndex.'&id_product='.$image->id_product.'&id_category='.(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&add'.$this->table.'&action=Images&token='.$this->token;
751
			}
752
		}
753
		else
754
			$this->errors[] = Tools::displayError('The image could not be found. ');
755
	}
756

757
	protected function processBulkDelete()
758
	{
vAugagneur's avatar
vAugagneur committed
759
		if ($this->tabAccess['delete'] === '1')
760
		{
vAugagneur's avatar
vAugagneur committed
761
			if (is_array($this->boxes) && !empty($this->boxes))
762
			{
vAugagneur's avatar
vAugagneur committed
763
				$object = new $this->className();
764

vAugagneur's avatar
vAugagneur committed
765 766 767 768 769
				if (isset($object->noZeroObject) &&
					// Check if all object will be deleted
					(count(call_user_func(array($this->className, $object->noZeroObject))) <= 1 || count($_POST[$this->table.'Box']) == count(call_user_func(array($this->className, $object->noZeroObject)))))
					$this->errors[] = Tools::displayError('You need at least one object.').' <b>'.$this->table.'</b><br />'.Tools::displayError('You cannot delete all of the items.');
				else
770
				{
vAugagneur's avatar
vAugagneur committed
771 772 773
					$success = 1;
					$products = Tools::getValue($this->table.'Box');
					if (is_array($products) && ($count = count($products)))
774
					{
vAugagneur's avatar
vAugagneur committed
775 776 777 778 779 780
						// Deleting products can be quite long on a cheap server. Let's say 1.5 seconds by product (I've seen it!).
						if (intval(ini_get('max_execution_time')) < round($count * 1.5))
							ini_set('max_execution_time', round($count * 1.5));

						if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'))
							$stock_manager = StockManagerFactory::getManager();
781

vAugagneur's avatar
vAugagneur committed
782
						foreach ($products as $id_product)
dMetzger's avatar
dMetzger committed
783
						{
vAugagneur's avatar
vAugagneur committed
784 785 786 787 788 789 790 791 792 793 794 795
							$product = new Product((int)$id_product);
							/*
							 * @since 1.5.0
							 * It is NOT possible to delete a product if there are currently:
							 * - physical stock for this product
							 * - supply order(s) for this product
							 */
							if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $product->advanced_stock_management)
							{
								$physical_quantity = $stock_manager->getProductPhysicalQuantities($product->id, 0);
								$real_quantity = $stock_manager->getProductRealQuantities($product->id, 0);
								if ($physical_quantity > 0 || $real_quantity > $physical_quantity)
796
									$this->errors[] = sprintf(Tools::displayError('You cannot delete the product #%d because there is physical stock left.'), $product->id);
vAugagneur's avatar
vAugagneur committed
797
							}
798
							if (!count($this->errors))
799 800
							{
								if ($product->delete())
Vincent Augagneur's avatar
Vincent Augagneur committed
801
									PrestaShopLogger::addLog(sprintf($this->l('%s deletion', 'AdminTab', false, false), $this->className), 1, null, $this->className, (int)$product->id, true, (int)$this->context->employee->id);
802 803 804
								else
									$success = false;
							}
805 806
							else
								$success = 0;
dMetzger's avatar
dMetzger committed
807
						}
808
					}
809

vAugagneur's avatar
vAugagneur committed
810 811 812 813 814 815 816
					if ($success)
					{
						$id_category = (int)Tools::getValue('id_category');
						$category_url = empty($id_category) ? '' : '&id_category='.(int)$id_category;
						$this->redirect_after = self::$currentIndex.'&conf=2&token='.$this->token.$category_url;
					}
					else
817
						$this->errors[] = Tools::displayError('An error occurred while deleting this selection.');
818 819
				}
			}
vAugagneur's avatar
vAugagneur committed
820 821
			else
				$this->errors[] = Tools::displayError('You must select at least one element to delete.');
822
		}
823
		else
vAugagneur's avatar
vAugagneur committed
824
			$this->errors[] = Tools::displayError('You do not have permission to delete this.');
825
	}
826

827
	public function processProductAttribute()
828
	{
829
		// Don't process if the combination fields have not been submitted
Rémi Gaillard's avatar
Rémi Gaillard committed
830
		if (!Combination::isFeatureActive() || !Tools::getValue('attribute_combination_list'))
831
			return;
832

833
		if (Validate::isLoadedObject($product = $this->object))
834
		{
rMalie's avatar
rMalie committed
835
			if ($this->isProductFieldUpdated('attribute_price') && (!Tools::getIsset('attribute_price') || Tools::getIsset('attribute_price') == null))
836
				$this->errors[] = Tools::displayError('The price attribute is required.');
837
			if (!Tools::getIsset('attribute_combination_list') || Tools::isEmpty(Tools::getValue('attribute_combination_list')))
838
				$this->errors[] = Tools::displayError('You must add at least one attribute.');
839

840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858
			$array_checks = array(
				'reference' => 'isReference',
				'supplier_reference' => 'isReference',
				'location' => 'isReference',
				'ean13' => 'isEan13',
				'upc' => 'isUpc',
				'wholesale_price' => 'isPrice',
				'price' => 'isPrice',
				'ecotax' => 'isPrice',
				'quantity' => 'isInt',
				'weight' => 'isUnsignedFloat',
				'unit_price_impact' => 'isPrice',
				'default_on' => 'isBool',
				'minimal_quantity' => 'isUnsignedInt',
				'available_date' => 'isDateFormat'
			);
			foreach ($array_checks as $property => $check)
				if (Tools::getValue('attribute_'.$property) !== false && !call_user_func(array('Validate', $check), Tools::getValue('attribute_'.$property)))
					$this->errors[] = sprintf(Tools::displayError('Field %s is not valid'), $property);
859

860
			if (!count($this->errors))
861 862 863 864 865 866 867
			{
				if (!isset($_POST['attribute_wholesale_price'])) $_POST['attribute_wholesale_price'] = 0;
				if (!isset($_POST['attribute_price_impact'])) $_POST['attribute_price_impact'] = 0;
				if (!isset($_POST['attribute_weight_impact'])) $_POST['attribute_weight_impact'] = 0;
				if (!isset($_POST['attribute_ecotax'])) $_POST['attribute_ecotax'] = 0;
				if (Tools::getValue('attribute_default'))
					$product->deleteDefaultAttributes();
868

869
				// Change existing one
870
				if (($id_product_attribute = (int)Tools::getValue('id_product_attribute')) || ($id_product_attribute = $product->productAttributeExists(Tools::getValue('attribute_combination_list'), false, null, true, true)))
871
				{
872
					if ($this->tabAccess['edit'] === '1')
873
					{
874
						if ($this->isProductFieldUpdated('available_date_attribute') && (Tools::getValue('available_date_attribute') != '' &&!Validate::isDateFormat(Tools::getValue('available_date_attribute'))))
875
							$this->errors[] = Tools::displayError('Invalid date format.');
876 877
						else
						{
878 879 880 881 882 883 884 885 886 887 888 889 890
							$product->updateAttribute((int)$id_product_attribute,
								$this->isProductFieldUpdated('attribute_wholesale_price') ? Tools::getValue('attribute_wholesale_price') : null,
								$this->isProductFieldUpdated('attribute_price_impact') ? Tools::getValue('attribute_price') * Tools::getValue('attribute_price_impact') : null,
								$this->isProductFieldUpdated('attribute_weight_impact') ? Tools::getValue('attribute_weight') * Tools::getValue('attribute_weight_impact') : null,
								$this->isProductFieldUpdated('attribute_unit_impact') ? Tools::getValue('attribute_unity') * Tools::getValue('attribute_unit_impact') : null,
								$this->isProductFieldUpdated('attribute_ecotax') ? Tools::getValue('attribute_ecotax') : null,
								Tools::getValue('id_image_attr'),
								Tools::getValue('attribute_reference'),
								Tools::getValue('attribute_ean13'),
								$this->isProductFieldUpdated('attribute_default') ? Tools::getValue('attribute_default') : null,
								Tools::getValue('attribute_location'),
								Tools::getValue('attribute_upc'),
								$this->isProductFieldUpdated('attribute_minimal_quantity') ? Tools::getValue('attribute_minimal_quantity') : null,
891
								$this->isProductFieldUpdated('available_date_attribute') ? Tools::getValue('available_date_attribute') : null, false);
892 893
								StockAvailable::setProductDependsOnStock((int)$product->id, $product->depends_on_stock, null, (int)$id_product_attribute);
								StockAvailable::setProductOutOfStock((int)$product->id, $product->out_of_stock, null, (int)$id_product_attribute);
894
						}
895
					}
tDidierjean's avatar
tDidierjean committed
896
					else
897
						$this->errors[] = Tools::displayError('You do not have permission to add this.');
tDidierjean's avatar
tDidierjean committed
898 899 900 901 902 903
				}
				// Add new
				else
				{
					if ($this->tabAccess['add'] === '1')
					{
904
						if ($product->productAttributeExists(Tools::getValue('attribute_combination_list')))
905
							$this->errors[] = Tools::displayError('This combination already exists.');
tDidierjean's avatar
tDidierjean committed
906
						else
907
						{
tDidierjean's avatar
tDidierjean committed
908 909 910 911 912 913
							$id_product_attribute = $product->addCombinationEntity(
								Tools::getValue('attribute_wholesale_price'),
								Tools::getValue('attribute_price') * Tools::getValue('attribute_price_impact'),
								Tools::getValue('attribute_weight') * Tools::getValue('attribute_weight_impact'),
								Tools::getValue('attribute_unity') * Tools::getValue('attribute_unit_impact'),
								Tools::getValue('attribute_ecotax'),
914
								0,
tDidierjean's avatar
tDidierjean committed
915 916 917 918 919 920
								Tools::getValue('id_image_attr'),
								Tools::getValue('attribute_reference'),
								null,
								Tools::getValue('attribute_ean13'),
								Tools::getValue('attribute_default'),
								Tools::getValue('attribute_location'),
921
								Tools::getValue('attribute_upc'),
922 923 924
								Tools::getValue('attribute_minimal_quantity'),
								Array(),
								Tools::getValue('available_date_attribute')
tDidierjean's avatar
tDidierjean committed
925
							);
926 927 928
							StockAvailable::setProductDependsOnStock((int)$product->id, $product->depends_on_stock, null, (int)$id_product_attribute);
							StockAvailable::setProductOutOfStock((int)$product->id, $product->out_of_stock, null, (int)$id_product_attribute);
						}
tDidierjean's avatar
tDidierjean committed
929 930
					}
					else
931
						$this->errors[] = Tools::displayError('You do not have permission to').'<hr>'.Tools::displayError('edit here.');
tDidierjean's avatar
tDidierjean committed
932
				}
933
				if (!count($this->errors))
tDidierjean's avatar
tDidierjean committed
934
				{
935
					$combination = new Combination((int)$id_product_attribute);
936
					$combination->setAttributes(Tools::getValue('attribute_combination_list'));
937 938 939 940 941 942

					// images could be deleted before
					$id_images = Tools::getValue('id_image_attr');
					if (!empty($id_images))
						$combination->setImages($id_images);

tDidierjean's avatar
tDidierjean committed
943
					$product->checkDefaultAttributes();
944 945 946 947 948
					if (Tools::getValue('attribute_default'))
					{
						Product::updateDefaultAttribute((int)$product->id);
						if(isset($id_product_attribute))
							$product->cache_default_attribute = (int)$id_product_attribute;
949

950 951
						if ($available_date = Tools::getValue('available_date_attribute'))
							$product->setAvailableDate($available_date);
952 953
						else
							$product->setAvailableDate();
954
					}
955 956 957
				}
			}
		}
958
	}
959

960
	public function processFeatures()
961 962 963 964
	{
		if (!Feature::isFeatureActive())
			return;

965
		if (Validate::isLoadedObject($product = new Product((int)Tools::getValue('id_product'))))
966
		{
967 968 969 970 971 972
			// delete all objects
			$product->deleteFeatures();

			// add new objects
			$languages = Language::getLanguages(false);
			foreach ($_POST as $key => $val)
973
			{
974
				if (preg_match('/^feature_([0-9]+)_value/i', $key, $match))
975
				{
976 977 978
					if ($val)
						$product->addFeaturesToDB($match[1], $val);
					else
979
					{
980
						if ($default_value = $this->checkFeatures($languages, $match[1]))
981
						{
982 983
							$id_value = $product->addFeaturesToDB($match[1], 0, 1);
							foreach ($languages as $language)
984
							{
985 986
								if ($cust = Tools::getValue('custom_'.$match[1].'_'.(int)$language['id_lang']))
									$product->addFeaturesCustomToDB($id_value, (int)$language['id_lang'], $cust);
987
								else
988
									$product->addFeaturesCustomToDB($id_value, (int)$language['id_lang'], $default_value);
989 990 991 992 993
							}
						}
					}
				}
			}
994 995
		}
		else
996
			$this->errors[] = Tools::displayError('A product must be created before adding features.');
997 998
	}

999 1000 1001
	/**
	 * This function is never called at the moment (specific prices cannot be edited)
	 */
1002
	public function processPricesModification()
1003 1004
	{
		$id_specific_prices = Tools::getValue('spm_id_specific_price');
1005
		$id_combinations = Tools::getValue('spm_id_product_attribute');
1006 1007 1008 1009
		$id_shops = Tools::getValue('spm_id_shop');
		$id_currencies = Tools::getValue('spm_id_currency');
		$id_countries = Tools::getValue('spm_id_country');
		$id_groups = Tools::getValue('spm_id_group');
1010
		$id_customers = Tools::getValue('spm_id_customer');
1011 1012 1013 1014 1015 1016 1017 1018
		$prices = Tools::getValue('spm_price');
		$from_quantities = Tools::getValue('spm_from_quantity');
		$reductions = Tools::getValue('spm_reduction');
		$reduction_types = Tools::getValue('spm_reduction_type');
		$froms = Tools::getValue('spm_from');
		$tos = Tools::getValue('spm_to');

		foreach ($id_specific_prices as $key => $id_specific_price)
1019 1020 1021
			if ($reduction_types[$key] == 'percentage' && ((float)$reductions[$key] <= 0 || (float)$reductions[$key] > 100))
				$this->errors[] = Tools::displayError('Submitted reduction value (0-100) is out-of-range');
			elseif ($this->_validateSpecificPrice($id_shops[$key], $id_currencies[$key], $id_countries[$key], $id_groups[$key], $id_customers[$key], $prices[$key], $from_quantities[$key], $reductions[$key], $reduction_types[$key], $froms[$key], $tos[$key], $id_combinations[$key]))
1022 1023 1024
			{
				$specific_price = new SpecificPrice((int)($id_specific_price));
				$specific_price->id_shop = (int)$id_shops[$key];
1025
				$specific_price->id_product_attribute = (int)$id_combinations[$key];
1026 1027 1028
				$specific_price->id_currency = (int)($id_currencies[$key]);
				$specific_price->id_country = (int)($id_countries[$key]);
				$specific_price->id_group = (int)($id_groups[$key]);
1029
				$specific_price->id_customer = (int)$id_customers[$key];
1030 1031 1032 1033 1034 1035 1036
				$specific_price->price = (float)($prices[$key]);
				$specific_price->from_quantity = (int)($from_quantities[$key]);
				$specific_price->reduction = (float)($reduction_types[$key] == 'percentage' ? ($reductions[$key] / 100) : $reductions[$key]);
				$specific_price->reduction_type = !$reductions[$key] ? 'amount' : $reduction_types[$key];
				$specific_price->from = !$froms[$key] ? '0000-00-00 00:00:00' : $froms[$key];
				$specific_price->to = !$tos[$key] ? '0000-00-00 00:00:00' : $tos[$key];
				if (!$specific_price->update())
1037
					$this->errors[] = Tools::displayError('An error occurred while updating the specific price.');
1038
			}
1039
		if (!count($this->errors))
1040
			$this->redirect_after = self::$currentIndex.'&id_product='.(int)(Tools::getValue('id_product')).(Tools::getIsset('id_category') ? '&id_category='.(int)Tools::getValue('id_category') : '').'&update'.$this->table.'&action=Prices&token='.$this->token;
1041 1042 1043

	}

1044
	public function processPriceAddition()
1045
	{
1046 1047 1048 1049
		// Check if a specific price has been submitted
		if (!Tools::getIsset('submitPriceAddition'))
			return;

1050
		$id_product = Tools::getValue('id_product');
1051
		$id_product_attribute = Tools::getValue('sp_id_product_attribute');
1052 1053 1054 1055
		$id_shop = Tools::getValue('sp_id_shop');
		$id_currency = Tools::getValue('sp_id_currency');
		$id_country = Tools::getValue('sp_id_country');
		$id_group = Tools::getValue('sp_id_group');
1056
		$id_customer = Tools::getValue('sp_id_customer');
1057
		$price = Tools::getValue('leave_bprice') ? '-1' : Tools::getValue('sp_price');
1058 1059
		$from_quantity = Tools::getValue('sp_from_quantity');
		$reduction = (float)(Tools::getValue('sp_reduction'));
1060
		$reduction_tax = Tools::getValue('sp_reduction_tax');
1061
		$reduction_type = !$reduction ? 'amount' : Tools::getValue('sp_reduction_type');
1062
		$reduction_type = $reduction_type == '-' ? 'amount' : $reduction_type;
1063
		$from = Tools::getValue('sp_from');
1064 1065
		if (!$from)
			$from = '0000-00-00 00:00:00';
1066
		$to = Tools::getValue('sp_to');
1067 1068
		if (!$to)
			$to = '0000-00-00 00:00:00';
1069

1070 1071 1072 1073 1074
		if (($price == '-1') && ((float)$reduction == '0'))
			$this->errors[] = Tools::displayError('No submitted reduction value');
		elseif (strtotime($to) < strtotime($from))
			$this->errors[] = Tools::displayError('Invalid date range');
		elseif ($reduction_type == 'percentage' && ((float)$reduction <= 0 || (float)$reduction > 100))
1075 1076
			$this->errors[] = Tools::displayError('Submitted reduction value (0-100) is out-of-range');
		elseif ($this->_validateSpecificPrice($id_shop, $id_currency, $id_country, $id_group, $id_customer, $price, $from_quantity, $reduction, $reduction_type, $from, $to, $id_product_attribute))
1077 1078
		{
			$specificPrice = new SpecificPrice();
1079
			$specificPrice->id_product = (int)$id_product;
1080
			$specificPrice->id_product_attribute = (int)$id_product_attribute;
1081 1082 1083 1084
			$specificPrice->id_shop = (int)$id_shop;
			$specificPrice->id_currency = (int)($id_currency);
			$specificPrice->id_country = (int)($id_country);
			$specificPrice->id_group = (int)($id_group);
1085
			$specificPrice->id_customer = (int)$id_customer;
1086 1087 1088
			$specificPrice->price = (float)($price);
			$specificPrice->from_quantity = (int)($from_quantity);
			$specificPrice->reduction = (float)($reduction_type == 'percentage' ? $reduction / 100 : $reduction);
1089
			$specificPrice->reduction_tax = $reduction_tax;
1090
			$specificPrice->reduction_type = $reduction_type;
1091 1092
			$specificPrice->from = $from;
			$specificPrice->to = $to;
1093
			if (!$specificPrice->add())
1094
				$this->errors[] = Tools::displayError('An error occurred while updating the specific price.');
1095 1096 1097
		}
	}

1098
	public function ajaxProcessDeleteSpecificPrice()
1099
	{
1100
		if ($this->tabAccess['delete'] === '1')
1101
		{
1102 1103
			$id_specific_price = (int)Tools::getValue('id_specific_price');
			if (!$id_specific_price || !Validate::isUnsignedId($id_specific_price))
1104
				$error = Tools::displayError('The specific price ID is invalid.');
1105 1106 1107 1108
			else
			{
				$specificPrice = new SpecificPrice((int)$id_specific_price);
				if (!$specificPrice->delete())
1109
					$error = Tools::displayError('An error occurred while attempting to delete the specific price.');
1110
			}
1111
		}
1112
		else
1113
			$error = Tools::displayError('You do not have permission to delete this.');
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126

		if (isset($error))
			$json = array(
				'status' => 'error',
				'message'=> $error
			);
		else
			$json = array(
				'status' => 'ok',
				'message'=> $this->_conf[1]
			);

		die(Tools::jsonEncode