I need to extend the Magento shopping cart to include an extra step for a store locator. I understand that I need to overwrite the core OnePage controller (Mage_Checkout_OnepageController) and blocks (Mage_Checkout_Block_Onepage) but what needs to be done with regards to keeping track of the extra information (e.g. user's selected options from my custom step).
There are a number of steps required here to get the whole solution.
Firstly, create a new module. Use the ModuleCreator if you wish.
Then, write a setup script in your module to add the new fields to Magento's attribute structure, e.g. :
$setup = new Mage_Sales_Model_Mysql4_Setup('core_setup');
$setup->startSetup();
$setup->addAttribute('quote', 'my_attribute', array('type' => 'varchar', 'visible' => false, 'required' => false));
$setup->addAttribute('order', 'my_attribute', array('type' => 'varchar', 'visible' => false, 'required' => false));
$setup->addAttribute('invoice', 'my_attribute', array('type' => 'varchar', 'visible' => false, 'required' => false));
$setup->addAttribute('creditmemo', 'my_attribute', array('type' => 'varchar', 'visible' => false, 'required' => false));
Note the use of the Mage_Sales_Model_Mysql4_Setup to add the fields to the relevant sales_flat_quote and sales_flat_order tables.
Now, insert the following values in your module's config.xml file:
<global>
<fieldsets>
<sales_convert_quote>
<my_attribute>
<to_order>*</to_order>
</my_attribute>
</sales_convert_quote>
<sales_convert_order>
<my_attribute>
<to_cm>*</to_cm>
<to_invoice>*</to_invoice>
</my_attribute>
</sales_convert_order>
</fieldsets>
That will instruct Magento to copy the values of your custom field from quote to order to invoice and credit_memo, etc.
Then in your custom block/controller code, you will be able to use Magento's magic getters and setters to persist the values.
$oQuote = Mage::getSingleton('checkout/session')->getQuote();
$oQuote->setMyAttribute('some_value');
$oQuote->save();
You should see the new column and value saved in sales_flat_quote. Then once the customer completes checkout, the same value should be saved in sales_flat_order.
Note that the above code can be extended to work for quote_item and order_item by just changing quote to quote_item etc, however, if you wish to save attribute values that have been set on your products, then some extra work is required.
Insert a new block of XML into your config.xml (again inside the global node):
<sales>
<quote>
<item>
<product_attributes>
<my_attribute />
</product_attributes>
</item>
</quote>
</sales>
Where my_attribute is the attribute code on the product model. That will make the my_attribute available on the linked product, so you can access it via
$oQuoteItem->getProduct()->getMyAttribute()
without needing to perform a full Mage::getModel('catalog/product')->load($oQuoteItem->getProductId()). This is much more efficient.
Then, you will need an observer to copy the values from the product object to the quote_item object. So, declare your observer in the config.xml:
<events>
<sales_quote_item_set_product>
<observers>
<quoteitem_set_custom_data>
<type>singleton</type>
<class>mymodule/observer</class>
<method>setCustomDataOnQuoteItem</method>
</quoteitem_set_custom_data>
</observers>
</sales_quote_item_set_product>
<events>
and write code in your observer class like this:
public function setCustomDataOnQuoteItem($oObserver){
$oProduct = $oObserver->getProduct();
$oQuoteItem = $oObserver->getQuoteItem();
foreach(array('my_attribute') as $vAttributeCode){
$oQuoteItem->setData($vAttributeCode,$oProduct->getData($vAttributeCode));
}
}
Here is a complete working module.. its (almost) the same as the above code of Johnatan.
You will find it here:
https://bitbucket.org/vovsky/adding-custom-product-attribute-to-quote-and-order-items-in/
And full explanation of every step here: http://www.atwix.com/magento/custom-product-attribute-quote-order-item/
Related
I'm planning to build an extension like Shopping Cart, Price Rule, or Catalog Price Rule.
I've already tried to learn something from existing Magento code, that you can see on:
app/code/core/Mage/Adminhtml/Block/Promo/Quote/Edit/Tab/Conditions.php
To show a Conditions Rule field, I've tried to add this script, but it didn't work properly
$fieldset->addField('conditions', 'text', array(
'name' => 'conditions',
'label' => Mage::helper('salesrule')->__('Conditions'),
'title' => Mage::helper('salesrule')->__('Conditions'),
))->setRule($model)->setRenderer(Mage::getBlockSingleton('rule/conditions'));
The question is:
How to display the conditional field properly on my custom field?
How to apply rule conditions on the front-end?
Thanks in advance.
update,
take a look at my screenshot
https://docs.google.com/file/d/0BwLN4KpQhoGbU181R0ZKanJSdVE/edit?usp=drivesdk
this is my form.php:
<?php
class KS_Kscoba_Block_Adminhtml_Tcoba_Edit_Tab_Form
extends Mage_Adminhtml_Block_Widget_Form
/*
extends Mage_Adminhtml_Block_Widget_Form
implements Mage_Adminhtml_Block_Widget_Tab_Interface
*/
{
protected function _prepareForm()
{
$model = Mage::registry('current_promo_quote_rule');
$form = new Varien_Data_Form();
$this->setForm($form);
$fieldset = $form->addFieldset("kscoba_form", array("legend"=>Mage::helper("kscoba")->__("Item information")));
$fieldset->addField("kolom1", "text", array(
"label" => Mage::helper("kscoba")->__("Kolom 1"),
"name" => "kolom1",
));
$fieldset->addField('kolom2', 'select', array(
'label' => Mage::helper('kscoba')->__('Kolom 2'),
'values' => KS_Kscoba_Block_Adminhtml_Tcoba_Grid::getValueArray1(),
'name' => 'kolom2',
));
/*
problem start here
*/
$renderer = Mage::getBlockSingleton('adminhtml/widget_form_renderer_fieldset')
->setTemplate('promo/fieldset.phtml')
->setNewChildUrl($this->getUrl('*/promo_quote/newConditionHtml/form/rule_conditions_fieldset'));
$fieldset = $form->addFieldset('conditions_fieldset', array(
'legend'=>Mage::helper('salesrule')->__('Apply the rule only if the following conditions are met (leave blank for all products)')
))->setRenderer($renderer);
$fieldset->addField('conditions', 'text', array(
'name' => 'conditions',
'label' => Mage::helper('salesrule')->__('Conditions'),
'title' => Mage::helper('salesrule')->__('Conditions'),
))->setRule($model)->setRenderer(Mage::getBlockSingleton('rule/conditions'));
if (Mage::getSingleton("adminhtml/session")->getTcobaData())
{
$form->setValues(Mage::getSingleton("adminhtml/session")->getTcobaData());
Mage::getSingleton("adminhtml/session")->setTcobaData(null);
}
elseif(Mage::registry("tcoba_data")) {
$form->setValues(Mage::registry("tcoba_data")->getData());
}
return parent::_prepareForm();
}
}
am I missing something?
1. Conditions Field
I may be overlooking another issue, but when I tested your form.php, the conditions field was missing because Mage::registry('current_promo_quote_rule') was undefined. The conditions field appeared on the page after I populated $model with a Mage_SalesRule_Model_Rule object.
Magento 1.8 registers the current_promo_quote_rule in _initRule() and editAction() of the Mage_Adminhtml_Promo_QuoteController (app/code/core/Mage/Adminhtml/controllers/Promo/QuoteController.php).
2. Frontend
Using shopping cart price rules as an example, the discounted price is applied in the frontend through the checkout module.
Mage/Checkout/controllers/CartController.php has a couponPostAction() function which is called when the user submits a coupon code from the cart or checkout page. This function gets the cart's Mage_Sales_Model_Quote object, sets the coupon code on that object, and refreshes the totals of each item using the collectTotals() function of Mage_Sales_Model_Quote.
The quote object's collectTotals() gets the related Mage_Sales_Model_Quote_Address objects and calls their collectTotals() functions. Those functions get each of the collector objects associated with the address and call its collect() method.
One of those collector objects is a Mage_SalesRule_Model_Quote_Discount, whose collect() method gets each Mage_Sales_Model_Quote_Item associated with this address, then calculates and stores its discount using a Mage_SalesRule_Model_Validator.
The specific logic in the conditions is read and applied deeper in the SalesRule module.
Does anyone know how can I add a custom product attribute with a widget renderer?
You can see this in Promo rules if you select SKU you'll got an Ajax popup with product selection.
so how would I go about it?
in :
$installer->addAttribute(Mage_Catalog_Model_Product::ENTITY...
In other words, how can I use a widget to select custom attribute values?
EDIT:
The scenario is as follows:
I would like to create a product attribute that will, upon a button click, open a product selection widget.
After the selection, the selected SKU's will go in in a comma delimited format.
This behavior can be seen in the catalog and shopping cart price rules.
If you filter the rule by SKU (SKU attribute must be enabled to "apply to rules"), you'll get a field and a button that will open the product selection widget.
Here is some thoughts that should get you going on the right track:
First, in a setup script, create your entity:
$installer->addAttribute('catalog_product', 'frontend_display', array(
'label' => 'Display Test',
'type' => 'varchar',
'frontend_model' => 'Test_Module/Entity_Attribute_Frontend_CsvExport',
'input' => 'select',
'required' => 0,
'user_defined' => false,
'group' => 'General'
));
Make sure to set the frontend_model to the model that you are going to use. The frontend model affects the display of the attribute (both in the frontend and the adminhtml sections).
Next, create yourself the class, and override one or both of the following functions:
public function getInputType()
{
return parent::getInputType();
}
public function getInputRendererClass()
{
return "Test_Module_Block_Adminhtml_Entity_Renderer_CsvExport";
}
The first (getInputType()) is used to change the input type to a baked in input type (see Varien_Data_Form_Element_* for the options). However, to set your own renderer class, use the latter function - getInputRendererClass(). That is what I am going to demonstrate below:
public function getElementHtml()
{
return Mage::app()->getLayout()->createBlock('Test_Module/Adminhtml_ExportCsv', 'export')->toHtml();
}
Here, to clean things up, I am instantiating another block, as the element itself doesn't have the extra functions to display buttons and the like.
Then finally, create this file:
class Test_Module_Block_Adminhtml_ExportCsv extends Mage_Adminhtml_Block_Widget
{
protected function _prepareLayout()
{
$button = $this->getLayout()->createBlock('adminhtml/widget_button')
->setData(array(
'label' => $this->__('Generate CSV'),
'onclick' => '',
'class' => 'ajax',
));
$this->setChild('generate', $button);
}
protected function _toHtml()
{
return $this->getChildHtml();
}
}
This doesn't cover the AJAX part, but will get you very close to getting the rest to work.
I was wondering whether there is any way to create custom Orders statuses in Magento. I am developing a Magento Extension in which I have to add some custom order status to magneto orders.
I googled a lot but didn't find any good resources for this.
Could anybody explain how to do this, any resources to refer.
Lets say you want to add "Authorized Payment" status with "authorized" code.
Add the following to config.xml of your module under config/global:
<sales>
<order>
<statuses>
<authorized translate="label">
<label>Authorized Payment</label>
</authorized>
</statuses>
<states>
<authorized translate="label">
<label>Authorized Payment</label>
<statuses>
<authorized default="1"/>
</statuses>
<visible_on_front>1</visible_on_front>
</authorized>
</states>
</order>
</sales>
Earlier it was quite enough but in recent versions (1.5.x.x if I recall correctly) the following bit is also required. Add the following to mysql setup/update file of your extension:
<?php
$installer = $this;
$statusTable = $installer->getTable('sales/order_status');
$statusStateTable = $installer->getTable('sales/order_status_state');
$statusLabelTable = $installer->getTable('sales/order_status_label');
$data = array(
array('status' => 'authorized', 'label' => 'Authorized Payment')
);
$installer->getConnection()->insertArray($statusTable, array('status', 'label'), $data);
$data = array(
array('status' => 'authorized', 'state' => 'authorized', 'is_default' => 1)
);
$installer->getConnection()->insertArray($statusStateTable, array('status', 'state', 'is_default'), $data);
?>
This technically adds new status to your system. Now you can set it to your order like this:
$order->setState('authorized', true, 'Status history message')
->save();
Please let me know if you have any questions.
So that the status is visible at the front is needed:
<config>
<modules>
<MyCompany_MyModule>
<active>true</active>
<codePool>local</codePool>
<depends>
<Mage_Sales/>
</depends>
</MyCompany_MyModule>
</modules>
</config>
I want every user, that registers on my Magento instance, to upload a certificate that shows me that he registered a business.
I already added the fields in the template. But how can I fetch the file and save the filename / contents in the customer record?
Is there a way to extend the functionality in the Controllers?
This is actually even easier:
Just make sure you set these parameters on your config.xml:
'attributes' => array(
'prooffile' => array(
'type' => 'text',
'input' => 'file',
'label' => 'Proof file',
'visible' => true,
'required' => false,
'position' => 100,
"user_defined" => false,
),
This adds a nice editor in your admin backend.
The way I did this:
I added the file field to the registration form:
<li class="fields">
<div class="field">
<div class="input-box">
<label for="prooffile"><?php echo $this->__('Proof of business registration') ?><span class="required">*</span></label><br />
<input type="file" name="prooffile" id="prooffile" title="<?php echo $this->__('Proof of business registration') ?>" class="required-entry input-text" />
</div>
</div>
</li>
Also, make sure that you set the form enctype to "multipart/form-data".
After that, I created a class that subscribes to the "user-register-success" Event. Magento has a very solid Event/Observer mechanism built in.
To do this, you have to have a custom module. In the modules etc/config.xml, add these lines for the event listener:
<events>
<customer_register_success> <!-- The name of the Event -->
<observers>
<customfields> <!-- Your module name -->
<type>singleton</type>
<class>customfields/observer</class> <!-- The class name, that holds your callback function -->
<method>handle_file_upload</method>
</customfields>
</observers>
</customer_register_success>
</events>
This registers an event handler for the event customer_register_success. Make sure that you create a file Observer.php in your modules Model folder:
Model/Observer.php:
<?php
class Komola_Customfields_Model_Observer
{
public function __construct()
{
}
public function handle_file_upload($observer)
{
$customer = $observer->getCustomer();
if (isset($_FILES['prooffile']['name']) && $_FILES['prooffile']['name'] != "") {
$uploader = new Varien_File_Uploader("prooffile");
$uploader->setAllowedExtensions(array('jpg', 'jpeg', 'gif', 'png', 'pdf'));
$uploader->setAllowRenameFiles(false);
$uploader->setFilesDispersion(false);
$path = Mage::getBaseDir("media") . DS . "customer" . DS;
$logoName = $customer->getId() . "." . pathinfo($_FILES['prooffile']['name'], PATHINFO_EXTENSION);
$uploader->save($path, $logoName);
$customer->setProoffile($logoName);
$customer->save();
}
}
}
This takes the uploaded file, and saves the file in the folder media/customer (make sure to create this folder and to make it writable!). It also saves the file name in a custom customer attribute.
In the module installer file, create the attribute like this, and it will appear in the customer backend.
An extra part is needed for newer version of Magento (not sure from when exactly, but it is true as of Magento Community Edition 1.6 and up).
The "used_in_forms" key cannot be in the array passed to the addAttribute call directly (won't work). It probably contain the names of the forms from which the customer model will accept the values and not ignore them when being saved.
Known values are at this question's answers: Can no longer add registration fields in Magento 1.4.2.0 (The answer by Folker Schellenberg)
I think it is the name of the controller and action that rendered the form. This name is also the main layout handle name of the page (eg: customer_account_edit).
It should be noted that the customer form in the front-end is HTML-based. It doesn't dynamically render inputs from the attributes like the backend forms. This means that if these attributes should be input by the user, the template needs to be amended to contain the proper input tags as well (and the proper value added in the used_in_forms array).
$attributeCode = "uploaded_file";
$attributeLabel = "Uploaded file";
$installer->addAttribute('customer', $attributeCode, array(
'type' => 'text',
'input' => 'file',
'label' => $attributeLabel,
'global' => true,
'visible' => true,
'required' => false,
'user_defined' => false
));
// For newer versions of Magento, otherwise won't show up.
$eavConfig = Mage::getSingleton('eav/config');
$attribute = $eavConfig->getAttribute('customer', $attributeCode);
$attribute->setData('used_in_forms', array('customer_account_create', 'adminhtml_customer'));
$attribute->setData('sort_order', 200);
$attribute->save();
Another possible type is 'image' which renders exactly as 'file' except it shows the image in a preview box (a small one). Maybe good for customer photo ?
Also, noteworthy is that is this specific for the customer form (the class that handles this type of attribute is: Mage_Adminhtml_Block_Customer_Form_Element_File and Mage_Adminhtml_Block_Customer_Form_Element_Image), so this won't work in a product attribute without custom work.
Hope this helps !
I would like to add a new attribute to Magento's EAV attribute model. Is this possible?
I know that Magento lets you extend models using static fields (which are on the entity table), but I would like to add a new field to the EAV attribute table itself (for catalog product attributes). The new attribute will be a new setting, similar to "Visible in Category List".
To add a new setting for product attributes, you can create an extension that (1) adds the new column to the catalog/eav_attribute table, and (2) puts in a field for the new setting in the attribute edit page using an observer.
(1) Create your new DB field (is_visible_in_category_list)
Make an SQL script for your extension, and add the new column. I would recommend using the catalog/eav_attribute table, but you could probably use eav_attribute as well:
$installer = $this;
$installer->startSetup();
$table = $installer->getTable('catalog/eav_attribute');
$installer->getConnection()->addColumn(
$table,
'is_visible_in_category_list',
"TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0'"
);
$installer->getConnection()->addKey(
$table,
'IDX_VISIBLE_IN_CATEGORY_LIST',
'is_visible_in_category_list'
);
$installer->endSetup();
Here, I also added an index for faster querying.
(2) Add the field to the edit product attribute page
An event is fired when preparing the attribute edit form, so let's observe it:
<events>
<adminhtml_catalog_product_attribute_edit_prepare_form>
<observers>
<is_visible_in_category_list_observer>
<class>mymodule/observer</class>
<method>addVisibleInCategoryListAttributeField</method>
</is_visible_in_category_list_observer>
</observers>
</adminhtml_catalog_product_attribute_edit_prepare_form>
</events>
Then, add the new field in the observer:
public function addVisibleInCategoryListAttributeField($observer)
{
$fieldset = $observer->getForm()->getElement('base_fieldset');
$attribute = $observer->getAttribute();
$fieldset->addField('is_visible_in_category_list', 'select', array(
'name' => 'is_visible_in_category_list',
'label' => Mage::helper('mymodule')->__('Visible in Category List'),
'title' => Mage::helper('mymodule')->__('Visible in Category List'),
'values' => Mage::getModel('adminhtml/system_config_source_yesno')->toOptionArray(),
));
}
That's all. Saving the setting from the edit page is automatically handled because the field name in the form matches the DB field name.