Adding custom option to order line - php

I'm currently trying to add a custom option to a specific orderline on add to cart via the following:
public function addToPackageQuote()
{
$cart = Mage::getSingleton("checkout/cart");
$quote = Mage::getSingleton("checkout/session")->getQuote();
$packageId = Mage::getModel('MyTuxedo_OPP/Package')->checkPackageId();
$products = $this->sortArray();
foreach ($products as $productInfo) {
try {
$split = explode(",", $productInfo);
$_product = Mage::getModel('catalog/product')->load($split[0]);
if($_product->isConfigurable()) {
$simpleId = $this->getConfigurableSimple($split[1],$split[3],$split[0]);
} else {
$simpleId = $split[0];
}
$product = Mage::getModel('catalog/product')->load($simpleId);
$options = new Varien_Object(array(
"qty" => 1,
"custom_options" => array(
"package" => $packageId,
"packageName" => Mage::helper('MyTuxedo_OPP')->getPackageName()
)
));
$quote->addProduct($product, $options);
$this->_getSession()->setCartWasUpdated(true);
$quote->save();
} catch (Exception $e) {
echo $e->getMessage();
}
$this->addFreeItems();
}
$cart->save();
unset($_SESSION['products']);
unset($_SESSION['productId']);
$cart->save();
// Let's unset all the package sessions (apart from a few that are needed!).
$this->kill();
}
This method is completely seperate from the generic add to cart handler, and is used purely in a packages system so that it adds simple products exclusively (also breaks down configurables super attribute to find the simple product too).
These simple products have no custom options attached to them in the Magento backend, nor is it a goal to add custom options to the product itself. What I would like to do is attach custom options to the order-line that is then transferred over to the order if a purchase is made. So effectively data that is added at the add to cart method and no where else!
The add to cart method works as expected it's just not including the custom options I am trying to attach. I have also tried defining the options object as simply:
$options = new Varien_Object(array(
"qty" => 1,
"package" => $packageId,
"packageName" => Mage::helper('MyTuxedo_OPP')->getPackageName()
)
The above info, not including qty is not in the orderline object at all, and I can't seem to work out where to move on from here.
Endlessly googling at the moment so some help would be most appreciated!!
I do appreciate I’m instantiating the product model object twice in this, however the plan is to just get it working then optimise! :)

You have to set the custom options for the product before adding it to cart.
$product->setCustomOptions($options);
The in Mage_Sales_Model_Quote::_addCatalogProduct() the custom options will be added to the cart item.
See also here: http://www.magentocommerce.com/boards/viewthread/49659/
By the way: Your code may be pretty slow because you are loading products twice in a foreach loop. You should consider some refactoring by using the product collection instead. Also it looks kind of hackish to directly access the $_SESSION variable here. You could rather use the Checkout Session for that (Mage::getSingleton('checkout/session')).

I have now resolved this, after much headache. You can add a custom option to the cart and not have to instantiate the product object and save a custom option to do this, it can be done via tacking onto an observer, and pulling the quote items.
After tacking onto: sales_quote_add_item
I then used:
public function addCustomData($observer) {
$event = $observer->getEvent();
$quote_item = $event->getQuoteItem();
$quote = $session->getQuote();
$quote_item->addOption(array("product_id" => $quote_item->getProduct()->getId(),
"product" => $quote_item->getProduct(),
"code" => 'PackageId',
"value" => Mage::getModel('MyTuxedo_OPP/Package')->checkPackageId()
));
$quote->save();
}
It is most important to include the product object and id, as the function doesn't use the loaded object for some reason.
You can then get at the object via:
$_item->getOptionByCode('PackageId')->getValue();
Quick piece of handy info, if it dumps a stack trace in front of you it can't find the defined option, lose the getValue() (if using var_dump) function to see if you are getting a null value, otherwise xdebug will give you a ton of hints to get around it.

Related

Is there a better way to get shipping method meta/settings from order in WooCommerce?

I have seen this question come up here and there, but it has not been fully answered. I know how to get the WC_Order_Item_Shipping information from an order, but how can I get a WC_Shipping_Method object from the orders shipping method item?
I have stored some custom options through a custom field in the admin setting page for the WooCommerce shipping methods. I want to get this field from an order. Right now I am doing this, and I can get to my information:
foreach ($order->get_shipping_methods() as $shipping_method) {
$options = get_option('woocommerce_' . $shipping_method->get_method_id() . '_' . $shipping_method->get_instance_id() . '_settings');
}
But this does not feel future proof. If the structure of the saved data changes, then everything will break. I'd rather use the getter from WC_Shipping_Method get_instance_option(). The problem is that I am not allowed to instantiate such an object, and there does not seem to be a wc_get_shipping_method() function I can use to get the method based on it's instance ID, such as with orders and products.
I could go the long route and save this info to order meta after payment is made, but that also does not seem like an optimized way of doing it. I want to get the information as it is saved in the present moment, not as it was saved to an order when the order was made.
Is the way that I am doing it now the only way, or is there a better way?
EDIT:
This is the code that adds the custom field:
function shipping_instance_form_add_extra_fields($settings)
{
$settings['custom_shipping_id'] = [
'title' => 'Custom Shipping ID',
'type' => 'number',
'description' => '',
];
return $settings;
}
function shipping_instance_form_fields_filters()
{
$shipping_methods = WC()->shipping->get_shipping_methods();
foreach ($shipping_methods as $shipping_method) {
add_filter('woocommerce_shipping_instance_form_fields_' . $shipping_method->id, 'shipping_instance_form_add_extra_fields');
}
}
add_action('woocommerce_init', 'shipping_instance_form_fields_filters');
If you already have shipping method instance id you can simply create instance of class WC_Shipping_Method giving it instance id as constructor parameter like $method = new WC_Shipping_Method($instance_id). After that you can use method get_instance_option to get value of that option like $custom_shipping_id = $method->get_instance_option('custom_shipping_id');

specific php session variables lost in Chrome only,

I lose some session variables in Chrome only
After reading every similar question and answer on this site, I have to note:
It is not a 404 / missing favicon issue
Also not a redirect issue. The session is started and closed properly.
I have a $_SESSION['customer'] and $_SESSION['order'] variable, for instance.
As expected, $_SESSION['customer'] holds an object of customer data, $_SESSION['order'] holds an array of products.
When I add a product to the order, I need to look up any customer discounts for this specific product. Then it turns out in Chrome, the $_SESSION['customer'] variable is missing, and the sql query returns no discounts, since there is no customer number.
Let's give you some code to work with:
I am on the update order page, where I still have my customer and order variable.
I call this function with an ajax request to return a list of products:
public function live_search_product(){
$this->load->model('orders', 'orders');
$qry = $_GET['qry'];
if (strlen($qry)>0) {
$products = $this->orders_model->search_products($qry, false);
if ($products){
foreach ($products as $product){
echo ''.$product['short_title'] . '<br>';
}
}
}
}
I select the product from the dropdown list, and end up in this function, where I suddenly have lost my $_SESSION['customer'] object and $_SESSION['order'] array. Please note that all my other session variables still exist.
public function add_product() {
$this->load->model('orders', 'orders');
$product = $this->orders_model->get_product_by_id($this->router->id);
$discount = $this->discount->get_discount($product->id, $product->product_group_id, $_SESSION['customer']->id, $_SESSION['customer']->group_id );
$_SESSION['order'][$this->router->id] = array('product' => $product ,
'quantity' => 1,
'rental' => 0,
'bruto' => $product->price_ex,
'discount' => $discount->discount_percentage,
'netto' => ((100 - $discount->discount_percentage)/100) * $product->price_ex,
'total' => ((100 - $discount->discount_percentage)/100) * $product->price_ex);
$this->calculate_order_totals();
}
In firefox and iexplorer and edge there is no problem, it works.
I really hope you have any other suggestions.
**** EDIT ****
Every controller has an index function that is called automatically. route orders/orders will call orders/orders/index, and for instance route orders/orders/add_product, will call the function add_product in the orders controller (residing in the orders folder).
It turns out that in Chrome, somehow I get routed to the index function of this orders controller. Which is an overview orders function where I reset all order variables, to make sure nothing is left when I open a new order.
I have changed that, so this specific situation is solved.
However, can not understand why I get redirected to this function, when calling the add_product function directly.
Any ideas?

Drupal/Ubercart custom price php code for roles

Im building an e-commerce site for wholesale foods and the pricing for products change depending on the user logged in. Ive looked at member pricing and basically every module i could find to do with altering the price but they are either for drupal 6 or not really what im after. Im using Drupal 7 with ubercart 3.
Ive found this module http://drupal.org/project/uc_custom_price. It adds a field within product creation that allows custom php code to be added to each individual product which is exactly what im after. however im not that good with php which is why ive been hunting modules instead of changing code.
What ive got at the moment is:
if ([roles] == 'test company') {
$item->price = $item->price*0.8;
}
Except the [roles] part is the wrong thing to use there and it just throws errors. Ive tried using things like $users->uid =='1' to try to hook onto a user like that but that didnt work either.
what would be the correct variable to put there?
thanks
try this Drupal 7 global $user object
global $user; // access the global user object
if(in_array("administrator",$user->roles)){ // if its administrator
$item->price = $item->price*0.8;
}elseif(in_array("vip",$user->roles)){ // if its a vip
//..
}elseif(in_array("UserCompanyX",$user->roles)){ // if its a user from company X
//..
}
or
if($user->roles[OFFSET] == "ROLE"){
// price calculation
}
$user->roles is an array of the roles assigned to the user.
hope it helped
Make your own module with UC Price API:
http://www.ubercart.org/docs/developer/11375/price_api
function example_uc_price_handler() {
return array(
'alter' => array(
'title' => t('Reseller price handler'),
'description' => t('Handles price markups by customer roles.'),
'callback' => 'example_price_alterer',
),
);
}
function example_price_alterer(&$price_info, $context, $options = array()){
global $user;
if (in_array("reseller", $user->roles)) { //Apply 30% reseller discount
$price_info["price"] = $context["subject"]["node"]->sell_price - (
$context["subject"]["node"]->sell_price * 0.30) ;
}
return;
}
See also: http://www.ubercart.org/forum/development/14381/price_alteration_hook

Where does Magento Set a Quote Item's Price?

Whenever you load the cart page in Magento, the following code is run
$cart->init();
$cart->save();
One side effect of this is that the prices for any items in the cart are updated if the price of the product has been updated. This actually updates the entry in sales_flat_quote_item. I'm trying to track down where in code the price is updated on each quote item, and where each quote item is saved.
I know the myrid locations it could be set. I'm hoping someone knows where it actually is set. Magento 1.7x branch specifically, although info from all versions is welcome.
Dug this one up myself. So there's this
#File: app/code/core/Mage/Sales/Model/Quote.php
foreach ($this->getAllAddresses() as $address) {
...
$address->collectTotals();
...
}
which leads to this
#File: app/code/core/Mage/Sales/Model/Quote/Address.php
public function collectTotals()
{
Mage::dispatchEvent($this->_eventPrefix . '_collect_totals_before', array($this->_eventObject => $this));
foreach ($this->getTotalCollector()->getCollectors() as $model) {
$model->collect($this);
}
Mage::dispatchEvent($this->_eventPrefix . '_collect_totals_after', array($this->_eventObject => $this));
return $this;
}
The getTotalCollector object returns a sales/quote_address_total_collector object, which loads a series of collector models from global/sales/quote/totals and calls collect on them. The sub-total collector's collect method ultimately calls this
#File: app/code/core/Mage/Sales/Model/Quote/Address/Total/Subtotal.php
protected function _initItem($address, $item)
{
//...
if ($quoteItem->getParentItem() && $quoteItem->isChildrenCalculated()) {
$finalPrice = $quoteItem->getParentItem()->getProduct()->getPriceModel()->getChildFinalPrice(
$quoteItem->getParentItem()->getProduct(),
$quoteItem->getParentItem()->getQty(),
$quoteItem->getProduct(),
$quoteItem->getQty()
);
$item->setPrice($finalPrice)
->setBaseOriginalPrice($finalPrice);
$item->calcRowTotal();
} else if (!$quoteItem->getParentItem()) {
$finalPrice = $product->getFinalPrice($quoteItem->getQty());
$item->setPrice($finalPrice)
->setBaseOriginalPrice($finalPrice);
$item->calcRowTotal();
$this->_addAmount($item->getRowTotal());
$this->_addBaseAmount($item->getBaseRowTotal());
$address->setTotalQty($address->getTotalQty() + $item->getQty());
}
//...
}
and this is where the quote item gets it's price set/rest.
From a high level, the code that starts the whole process are lines 464 and 465 of Mage_Checkout_Model_Cart :
$this->getQuote()->collectTotals();
$this->getQuote()->save();
The new product price is being set against the quote in Mage_Sales_Model_Quote_Address_Total_Subtotal in the _initItem method. You will see $item->setPrice in the the if / else statement starting at line 104
If you are trying to do custom price changes on products in the cart, rather than extend and modify core classes, I use an observer sales_quote_save_before. It works great if you are trying to customize pricing (especially when I have products that can be custom length).

How to find out master product of simple product?

How can I find out, if a simple product is part of a configurable product and then get the master product? I need this for the product listing.
Just found out:
$_product->loadParentProductIds();
$parentIds = $_product->getParentProductIds();
Let's say that you have your simple product's Product ID.
To get all the parent configurable product IDs of this simple product, use the following code:-
<?php
$_product = Mage::getModel('catalog/product')->load(YOUR_SIMPLE_PRODUCT_ID);
$parentIdArray = $_product->loadParentProductIds()
->getData('parent_product_ids');
if(!empty($parentIdArray)) {
// currently in the master configurable product
print_r($parentIdArray); // this prints all the parent product IDs using your simple product.
}
?>
I suppose this should get you going.
For Magento 1.4.2 and above use the following method instead:
$configurable_product_model = Mage::getModel(‘catalog/product_type_configurable’);
$parentIdArray = $configurable_product_model->getParentIdsByChild($simple_product_id);
After version 1.4.2.0 the loadParentProductIds() and getParentProductIds() methods are deprecated. Don't ask me why. Personally I kind of liked those methods. So I've reintroduced them to my local Mage classes. This is how:
Copy
app/code/core/Mage/Catalog/Model/Product.php
to
app/code/local/Mage/Catalog/Model/Product.php
and change the loadParentProductIds() method, found around line 1349 to:
public function loadParentProductIds()
{
return $this->_getResource()->getParentProductIds($this);
}
This piece of code will query its resource for its parent product ids. For this to work we'll need to rewrite the getParentProductIds() method in the resource class.
So copy:
app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product.php
to
app/code/local/Mage/Catalog/Model/Resource/Eav/Mysql4/Product.php
Find the deprecated getParentProductIds() method. Should be somewhere around line 535. Overwrite it with the pre 1.4.2.0 code:
public function getParentProductIds($object){
$childId = $object->getId();
$groupedProductsTable = $this->getTable('catalog/product_link');
$groupedLinkTypeId = Mage_Catalog_Model_Product_Link::LINK_TYPE_GROUPED;
$configurableProductsTable = $this->getTable('catalog/product_super_link');
$groupedSelect = $this->_getReadAdapter()->select()
->from(array('g'=>$groupedProductsTable), 'g.product_id')
->where("g.linked_product_id = ?", $childId)
->where("link_type_id = ?", $groupedLinkTypeId);
$groupedIds = $this->_getReadAdapter()->fetchCol($groupedSelect);
$configurableSelect = $this->_getReadAdapter()->select()
->from(array('c'=>$configurableProductsTable), 'c.parent_id')
->where("c.product_id = ?", $childId);
$configurableIds = $this->_getReadAdapter()->fetchCol($configurableSelect);
return array_merge($groupedIds, $configurableIds);
}
Now you once again can do this:
$_product->loadParentProductIds()->getData('parent_product_ids');
Hope this helps you out!

Categories