Post by wal_j » Mon Aug 17, 2009 8:37 am

Bug
  • Thousands of your customers are loosing the product options and product option values that they have in their shopping cart, so when they return to the store and their baskets are reloaded, the products are still there, but all options have been lost.
Who does it affect
  • Customers with accounts that have products in their cart. (the customer doesn't need to be logged on as carts are saved when the customer logs out)( but they can be logged on and the issue still occurrs)
  • Guests who have products in their cart who are online and shopping at the time.
  • YOU the store owner as I believe that continuing through checkout will produce no errors about the lost Options and therefore you will either send out the incorrect product because of lack of options or you wont be able to send out the product if you realise the options are missing and you wont know what exactly your customer ordered.
Steps to replicate the Bug
  • 1: Log on as a customer to your store.
  • 2: Add a product, with product options to your cart.
  • 3: Log Out (Your cart is then saved in your account, if you log back in, your cart should reload and the options are still there. but in this scenario you need to be logged out to continue the next steps, as I beleive knowing that customers saved carts are being destroyed is an important point)
  • 4: Log into your Admin section.
  • 5: Go to Catalog > Products and click edit on the product that you have just added to your cart.
  • 6: Click Save (nothing needs to be changed, but things can be changed it makes no difference)
  • All customers and guests online at the time of clicking "save" have lost all product options for this product if it was in their basket at the time of clicking. Logging in as the customer again you will see that the product you previously added to the cart is reloaded but the options have been lost.
Reason
  • When Save is clicked on the edit product all saved options and option values for this product are deleted and then recreated according to the submitted form. Therefore the product option id and product option value id numbers change on every click of Save, so the product option id and product option value id numbers that are saved in a customers cart dont correspond to the new id numbers.
Solution
  • I think i have it sorted with very few changes. I have tested it in 1.3.2 only and with only one language (English) so would be interested to know if it works with multi-language. It should do though.
  • I added two lines in the tpl file and rewrote maybe 20 lines in the editProduct function, and thats it.
Changes

/admin/view/template/catalog/product_form.tpl
About Line 248

Find

Code: Select all

              
<td valign="top"><?php echo $entry_sort_order; ?><br />
    <input type="text" name="product_option[<?php echo $option_row; ?>][sort_order]" value="<?php echo $product_option['sort_order']; ?>" size="5" /></td>
    <td align="right"><a onclick="removeOption('<?php echo $option_row; ?>');" class="remove"><?php echo $button_remove; ?></a></td>
Replace with

Code: Select all

              
<td valign="top"><?php echo $entry_sort_order; ?><br />
    <input type="text" name="product_option[<?php echo $option_row; ?>][sort_order]" value="<?php echo $product_option['sort_order']; ?>" size="5" />
    <input type="hidden" name="product_option[<?php echo $option_row; ?>][product_option_id]" value="<?php echo $product_option['product_option_id']; ?>" /></td>
    <td align="right"><a onclick="removeOption('<?php echo $option_row; ?>');" class="remove"><?php echo $button_remove; ?></a></td>
About Line 276

Find

Code: Select all

              
<td><?php echo $entry_sort_order; ?><br />
    <input type="text" name="product_option[<?php echo $option_row; ?>][product_option_value][<?php echo $option_value_row; ?>][sort_order]" value="<?php echo $product_option_value['sort_order']; ?>" size="5" /></td>
    <td align="right"><a onclick="removeOptionValue('<?php echo $option_value_row; ?>');" class="remove"><?php echo $button_remove; ?></a></td>      
Replace with

Code: Select all

              
<td><?php echo $entry_sort_order; ?><br />
    <input type="text" name="product_option[<?php echo $option_row; ?>][product_option_value][<?php echo $option_value_row; ?>][sort_order]" value="<?php echo $product_option_value['sort_order']; ?>" size="5" />
    <input type="hidden" name="product_option[<?php echo $option_row; ?>][product_option_value][<?php echo $option_value_row; ?>][product_option_value_id]" value="<?php echo $product_option_value['product_option_value_id']; ?>" /></td>
    <td align="right"><a onclick="removeOptionValue('<?php echo $option_value_row; ?>');" class="remove"><?php echo $button_remove; ?></a></td>

/admin/model/catalog/product.php
Find the Function editProduct and replace the whole function with the one below.

Code: Select all

	public function editProduct($product_id, $data) {
		$dont_delete_product_option = array();
		$dont_delete_product_option_value = array();

		$this->db->query("UPDATE " . DB_PREFIX . "product SET model = '" . $this->db->escape($data['model']) . "', quantity = '" . (int)$data['quantity'] . "', stock_status_id = '" . (int)$data['stock_status_id'] . "', date_available = '" . $this->db->escape($data['date_available']) . "', manufacturer_id = '" . (int)$data['manufacturer_id'] . "', shipping = '" . (int)$data['shipping'] . "', price = '" . (float)$data['price'] . "', sort_order = '" . (int)$data['sort_order'] . "', weight = '" . (float)$data['weight'] . "', weight_class_id = '" . (int)$data['weight_class_id'] . "', length = '" . (float)$data['length'] . "', width = '" . (float)$data['width'] . "', height = '" . (float)$data['height'] . "', measurement_class_id = '" . (int)$data['measurement_class_id'] . "', status = '" . (int)$data['status'] . "', tax_class_id = '" . (int)$data['tax_class_id'] . "', date_modified = NOW() WHERE product_id = '" . (int)$product_id . "'");

		if (isset($data['image'])) {
			$this->db->query("UPDATE " . DB_PREFIX . "product SET image = '" . $this->db->escape($data['image']) . "' WHERE product_id = '" . (int)$product_id . "'");
		}
		
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_description WHERE product_id = '" . (int)$product_id . "'");
		
		foreach ($data['product_description'] as $language_id => $value) {
			$this->db->query("INSERT INTO " . DB_PREFIX . "product_description SET product_id = '" . (int)$product_id . "', language_id = '" . (int)$language_id . "', name = '" . $this->db->escape($value['name']) . "', meta_description = '" . $this->db->escape($value['meta_description']) . "', description = '" . $this->db->escape($value['description']) . "'");
		}
		
		if (isset($data['product_option'])) {
			foreach ($data['product_option'] as $product_option) {
				$product_option_id = '';
				
				if(array_key_exists('product_option_id', $product_option)){
					$this->db->query("UPDATE " . DB_PREFIX . "product_option SET sort_order = '" . (int)$product_option['sort_order'] . "' WHERE product_option_id = '" . (int)$product_option['product_option_id'] . "'");
					$product_option_id = (int)$product_option['product_option_id'];
					
					foreach ($product_option['language'] as $language_id => $language) {
						$this->db->query("UPDATE " . DB_PREFIX . "product_option_description SET name = '" . $this->db->escape($language['name']) . "' WHERE product_option_id = '" . $product_option_id . "' AND language_id = '". (int)$language_id ."'");
					}
				}else{
					$this->db->query("INSERT INTO " . DB_PREFIX . "product_option SET product_id = '" . (int)$product_id . "', sort_order = '" . (int)$product_option['sort_order'] . "'");
					$product_option_id = $this->db->getLastId();
					
					foreach ($product_option['language'] as $language_id => $language) {
						$this->db->query("INSERT INTO " . DB_PREFIX . "product_option_description SET product_option_id = '" . (int)$product_option_id . "', language_id = '" . (int)$language_id . "', product_id = '" . (int)$product_id . "', name = '" . $this->db->escape($language['name']) . "'");
					}
				}
				
				$dont_delete_product_option[] = $product_option_id;		
				
				if (isset($product_option['product_option_value'])) {
					foreach ($product_option['product_option_value'] as $product_option_value) {
						$product_option_value_id = '';
						
						if(array_key_exists('product_option_value_id', $product_option_value)){
							$this->db->query("UPDATE " . DB_PREFIX . "product_option_value SET price = '" . (float)$product_option_value['price'] . "', prefix = '" . $this->db->escape($product_option_value['prefix']) . "', sort_order = '" . (int)$product_option_value['sort_order'] . "' WHERE product_option_value_id = '" . (int)$product_option_value['product_option_value_id'] . "'");
							$product_option_value_id = (int)$product_option_value['product_option_value_id'];
							
							foreach ($product_option_value['language'] as $language_id => $language) {
								$this->db->query("UPDATE " . DB_PREFIX . "product_option_value_description SET name = '" . $this->db->escape($language['name']) . "' WHERE product_option_value_id = '" . (int)$product_option_value_id . "' AND language_id = '" . (int)$language_id . "'");
							}
						}else{
							$this->db->query("INSERT INTO " . DB_PREFIX . "product_option_value SET product_option_id = '" . (int)$product_option_id . "', product_id = '" . (int)$product_id . "', price = '" . (float)$product_option_value['price'] . "', prefix = '" . $this->db->escape($product_option_value['prefix']) . "', sort_order = '" . (int)$product_option_value['sort_order'] . "'");
							$product_option_value_id = $this->db->getLastId();
							
							foreach ($product_option_value['language'] as $language_id => $language) {
								$this->db->query("INSERT INTO " . DB_PREFIX . "product_option_value_description SET product_option_value_id = '" . (int)$product_option_value_id . "', language_id = '" . (int)$language_id . "', product_id = '" . (int)$product_id . "', name = '" . $this->db->escape($language['name']) . "'");
							}
						}	
						$dont_delete_product_option_value[] = $product_option_value_id;				
					}
				}
			}
		}
		
		// Delete Product Option that were not submitted for update
		$where_clause = array("WHERE product_id = '" . (int)$product_id . "'"); 
		foreach($dont_delete_product_option as $value){
			$where_clause[] .= "product_option_id != '". $value . "'";
		}
		$where_sql = implode(" AND ", $where_clause);
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_option ". $where_sql);
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_option_description ". $where_sql);
		
		
		// Delete Product Option Values that were not submitted for update
		$where_clause2 = array("WHERE product_id = '" . (int)$product_id . "'"); 
		foreach($dont_delete_product_option_value as $value){
			$where_clause2[] .= "product_option_value_id != '". $value . "'";
		}
		$where_sql2 = implode(" AND ", $where_clause2);
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_option_value ". $where_sql2);
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_option_value_description ". $where_sql2);
				
		
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_discount WHERE product_id = '" . (int)$product_id . "'");

		if (isset($data['product_discount'])) {
			foreach ($data['product_discount'] as $value) {
				$this->db->query("INSERT INTO " . DB_PREFIX . "product_discount SET product_id = '" . (int)$product_id . "', customer_group_id = '" . (int)$value['customer_group_id'] . "', quantity = '" . (int)$value['quantity'] . "', priority = '" . (int)$value['priority'] . "', price = '" . (float)$value['price'] . "', date_start = '" . $this->db->escape($value['date_start']) . "', date_end = '" . $this->db->escape($value['date_end']) . "'");
			}
		}
		
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_special WHERE product_id = '" . (int)$product_id . "'");
		
		if (isset($data['product_special'])) {
			foreach ($data['product_special'] as $value) {
				$this->db->query("INSERT INTO " . DB_PREFIX . "product_special SET product_id = '" . (int)$product_id . "', customer_group_id = '" . (int)$value['customer_group_id'] . "', priority = '" . (int)$value['priority'] . "', price = '" . (float)$value['price'] . "', date_start = '" . $this->db->escape($value['date_start']) . "', date_end = '" . $this->db->escape($value['date_end']) . "'");
			}
		}
		
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_image WHERE product_id = '" . (int)$product_id . "'");
		
		if (isset($data['product_image'])) {
			foreach ($data['product_image'] as $image) {
        		$this->db->query("INSERT INTO " . DB_PREFIX . "product_image SET product_id = '" . (int)$product_id . "', image = '" . $this->db->escape($image) . "'");
			}
		}
		
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_to_download WHERE product_id = '" . (int)$product_id . "'");
		
		if (isset($data['product_download'])) {
			foreach ($data['product_download'] as $download_id) {
				$this->db->query("INSERT INTO " . DB_PREFIX . "product_to_download SET product_id = '" . (int)$product_id . "', download_id = '" . (int)$download_id . "'");
			}
		}
		
		$this->db->query("DELETE FROM " . DB_PREFIX . "product_to_category WHERE product_id = '" . (int)$product_id . "'");
		
		if (isset($data['product_category'])) {
			foreach ($data['product_category'] as $category_id) {
				$this->db->query("INSERT INTO " . DB_PREFIX . "product_to_category SET product_id = '" . (int)$product_id . "', category_id = '" . (int)$category_id . "'");
			}		
		}

		$this->db->query("DELETE FROM " . DB_PREFIX . "product_related WHERE product_id = '" . (int)$product_id . "'");

		if (isset($data['product_related'])) {
			foreach ($data['product_related'] as $related_id) {
				$this->db->query("INSERT INTO " . DB_PREFIX . "product_related SET product_id = '" . (int)$product_id . "', related_id = '" . (int)$related_id . "'");
			}
		}
		
		$this->db->query("DELETE FROM " . DB_PREFIX . "url_alias WHERE query = 'product_id=" . (int)$product_id. "'");
		
		if ($data['keyword']) {
			$this->db->query("INSERT INTO " . DB_PREFIX . "url_alias SET query = 'product_id=" . (int)$product_id . "', keyword = '" . $this->db->escape($data['keyword']) . "'");
		}
		
		$this->cache->delete('product');
	}
Only a small part of the function has changed but it was easier to post the whole thing rather than specify each line.

Hope this helps people out.
Best Wishes
Alex
Last edited by i2Paq on Mon Feb 15, 2010 4:42 pm, edited 1 time in total.
Reason: Title fix

New member

Posts

Joined
Tue May 12, 2009 11:51 pm

Post by stinn » Mon Feb 15, 2010 10:09 am

Does this fix work for 1.4? Or will it need to be modified?

Newbie

Posts

Joined
Mon Feb 15, 2010 10:08 am
Who is online

Users browsing this forum: No registered users and 11 guests