Wednesday, November 24, 2010

How to add tier prices in the product list

Inside the app/design/frontend/{interface}/{theme}/template/catalog/product/list.phtml add this inside the foreach loops for grid and/or list
<?php $this->setProduct(Mage::getModel('catalog/product')->setStoreId(Mage::app()->getStore()->getId())->load($_product->getId()))?>
<?php echo $this->getTierPriceHtml() ?> 

It does not look nice for grid but it can be fixed by css.

Tuesday, August 3, 2010

How to delete orders from Magento

This is a response for http://www.magentocommerce.com/boards/viewthread/197549/
Problem: How to delete (test) orders from Magento.
Possible solution:

Here is a free but 'quick and dirty' way to do it.
Create a file on the same level as index.php. Let's call it orders.php
<?php
$mageFilename = 'app/Mage.php';
require_once $mageFilename;
Varien_Profiler::enable();
Mage::setIsDeveloperMode(true);
ini_set('display_errors', 1);
umask(0);
Mage::app('default');
Mage::register('isSecureArea', 1);
//until here you gained access to the Magento models. The rest is custom code

$orderId = 156;//put here the id of the order you want to delete. THE ONE FROM THE DATABASE NOT THE INCREMENT_ID

$order = Mage::getModel('sales/order')->load($orderId);
$invoices = $order->getInvoiceCollection();
foreach ($invoices as $invoice){
$invoice->delete();
}
$creditnotes = $order->getCreditmemosCollection();
foreach ($creditnotes as $creditnote){
$creditnote->delete();
}
$shipments = $order->getShipmentsCollection();
foreach ($shipments as $shipment){
$shipment->delete();
}
$order->delete();
?>
Save and call it in your browser (http://my-magento.root/orders.php)
This will delete an order with the id you specify.
If you want to delete more of them just create a for (foreach) loop that will execute this code for each order you want to delete.

For example, if all your test orders are consecutive up to a number you can do this:
//let's say that the orders with ids less than 200 are test orders
$collection = Mage::getModel('sales/order')->getCollection()->addAttributeToFilter(array(array('attribute'=>'entity_id', 'lt'=>200)));
foreach ($collection as $o){
$orderId = $o->getId();
//here goes the code above starting below $orderId = 156.
}

I've used it in several occasions and it worked, but please back-up before using it. I will not be responsible if something goes wrong :)

Wednesday, June 9, 2010

Adding related products, up-sells and cross-sells programmaticly

Problem:
Adding related products, up-sells and cross-sells programmaticly.
http://www.magentocommerce.com/boards/viewthread/195440/

Possible solution:

In order to set related, up-sells and crosssells you need to do the following
Let's assume that the current product is $_product (that you got by doing Mage::getModel('catalog/product')->load(SOME ID))
//for related
$_product->setRelatedLinkData($param);
//for up-sells
$_product->setUpSellLinkData($param);
//for crosssells
$_product->setCrossSellLinkData($param);
$param is an array with the following structure
$param = array(
$associatedProductId=>array(
'position'=>$associatedProductPosition
)
)

Here is an example. Let's say you want to add products with ids 101 and 102 as related products to $_product on positions 3 and 5.
You should do something like this:

$param = array(
101=>array(
'position'=>3
),
102=>array(
'position'=>5
)
);
$_product->setRelatedLinkData($param);
//here ... some other product operations and in the end
$_product->save();

It works the same for up-sells and crosssels.

Thursday, May 20, 2010

how to find methods in Magento

Problem: http://www.magentocommerce.com/boards/viewthread/193422/
Here is how I've learned.
Let's say you have in a phtml file this line of code.
$object->method();
($object can be anything including $this).
And you don't know what type is $object.
put this line above the method call.
echo get_class($object);
Clear the cache (contents of var/cache) and reload the page. You should see the name of the class. For example Mage_Catalog_Model_Category. now all you have to do is to find the class.
Because Magento is based on Zend Framework it uses the the same convention for class naming.
So class Abc_Def_Ghi will be in the file Abc/Def/Ghi.php (for the example above the file is Mage/Catalog/Model/Category.php)
All the classes are placed in app/code/core or app/code/local/ or app/code/community or lib/ and after that follow the path like explained above.

If you don't find a method in that specific class look in one of the parents. Check the class that the current class extends.

You will not find all the methods in Magento. For example some of the methods that start with get, set, has, uns is possible that the methods are missing.
For this take a look in Varien_Object class (that is the base class for most of the objects) lib/Varien/Object.php file (remember the convention above?) and look in the 'magic' method __call().

Monday, May 17, 2010

Import products update

This is an update for http://marius-strajeru.blogspot.com/2009/11/import-simple-products-for-barrmy.html

Starting version 1.4 you can only perform product updates 'programmatic' only if Magento thinks you are in the admin panel.

If you try to do this from a non-admin page make sure you put this

Mage::app()->setCurrentStore(Mage::getModel('core/store')->load(Mage_Core_Model_App::ADMIN_STORE_ID));
before you call
$product->save();

Thursday, May 6, 2010

How to add product skus (or payment method) to the orders grid

[UPDATE] The solution provided here works only for Magento 1.4.0.0 or lower. Starting Magento 1.4.0.1 the order tables are not EAV anymore. I think (but I'm not sure) the logic is still OK but you have to try it by adding a new column to sales_flat_order and sales_flat_order_grid tables instead of adding a new attribute. [/UPDATE] I've tried in different ways to add articles skus (or payment method) to the orders grid in admin. I've tried 'left joining' different tables, performing different actions after applying _prepareCollection() method. All of them worked in some way, but the website started working slow when it reached 10000-12000 orders. And when I say slow I mean over 2 minutes to render the orders grid in admin. This was because of the slow queries which also resulted in slow loading of the frontend pages.
I found an easier solution, it's somehow redundant but it works and it's pretty fast.
I added a new varchar attribute for the order object.
EDIT: This only works in versions < 1.4
INSERT INTO `eav_attribute` (`attribute_id`, `entity_type_id`, `attribute_code`, `attribute_model`, `backend_model`, `backend_type`, `backend_table`, `frontend_model`, `frontend_input`, `frontend_label`, `frontend_class`, `source_model`, `is_required`, `is_user_defined`, `default_value`, `is_unique`, `note`) VALUES
(NULL, 11, 'product_skus', NULL, NULL, 'varchar', NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL, 0, '');

Clear the cache (contents of the folder var/cache)
Override the Mage_Sales_Model_Order class and in the _beforeSave() method add this:
if (!$this->getProductSkus()) {
$skus = array();
foreach ($this->getAllItems() as $item) {
$skus[] = $item->getSku();
}
$this->setProductSkus(implode(",", $skus));
}
This will add a string with all the skus separated by comma to the order model.
Now all you have to do is to show the string in the orders grid.
Override the Mage_Adminhtml_Block_Sales_Order_Grid block and in the _prepareColumns() method add a new column like this:
$this->addColumn('product_skus', array(
'header' => Mage::helper('sales')->__('Products'),
'index' => 'product_skus',
));
Added it where ever you want.
If you don't know how to overwrite a model or a class this is a good tutorial:
http://magedev.com/2009/06/03/magento-overriding-model-block-or-helper/

The only problem is that these changes will take effect only for the orders you make from now on.

You can add the skus for every order like this.
Create a new php file, let's call it orders.php and place it in the root of the application (same level as index.php) with this code:
<?php
$mageFilename = 'app/Mage.php';

require_once $mageFilename;

Varien_Profiler::enable();

Mage::setIsDeveloperMode(true);

ini_set('display_errors', 1);

umask(0);
Mage::app('default');
Mage::register('isSecureArea', 1);

$orders = Mage::getModel('sales/order')->getCollection()->addAttributeToSelect("*");
foreach ($orders as $order){
$order->save();
}
?>

and call it in the browser (http://yourwebsite.com/orders.php)

If you have many orders it will take some time to run but eventually ... you get my point.

If you want to do this with the payment method the system is the same. Add a new attribute:
INSERT INTO `eav_attribute` (`attribute_id`, `entity_type_id`, `attribute_code`, `attribute_model`, `backend_model`, `backend_type`, `backend_table`, `frontend_model`, `frontend_input`, `frontend_label`, `frontend_class`, `source_model`, `is_required`, `is_user_defined`, `default_value`, `is_unique`, `note`) VALUES
(NULL, 11, 'order_payment_method', NULL, NULL, 'varchar', NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL, 0, '');
Clear the cache.
In the _beforeSave() method add this:
if (!$this->getOrderPaymentMethod()) {
$this->setOrderPaymentMethod($this->getPayment()->getMethodInstance()->getTitle()); //if you want the payment method's name (Check / money order)
//$this->setOrderPaymentMethod($this->getPayment()->getMethod());//if you want the payment method's code (checkmo)
}

In the Mage_Adminhtml_Block_Sales_Order_Grid block _prepareColumns() method add this:
$this->addColumn('order_payment_method', array(
'header' => Mage::helper('sales')->__('Payment'),
'index' => 'order_payment_method',
));

Same as for the product skus, this will now work for previous orders, but you can run the same script as above to update all the old orders.

That's it. Let me know how it turns out.

Friday, April 30, 2010

Add new variable to order update e-mail

possible solution:

You need to override the Order model (app/code/core/Mage/Sales/Model/Order.php) and for the sendOrderUpdateEmail() method
you need to change this section
$mailTemplate->setDesignConfig(array('area'=>'frontend', 'store' => $this->getStoreId()))
->sendTransactional(
$template,
Mage::getStoreConfig(self::XML_PATH_UPDATE_EMAIL_IDENTITY, $this->getStoreId()),
$recipient['email'],
$recipient['name'],
array(
'order'     => $this,
'billing'   => $this->getBillingAddress(),
'comment'   => $comment
)
);
to this:
$mailTemplate->setDesignConfig(array('area'=>'frontend', 'store' => $this->getStoreId()))
->sendTransactional(
$template,
Mage::getStoreConfig(self::XML_PATH_UPDATE_EMAIL_IDENTITY, $this->getStoreId()),
$recipient['email'],
$recipient['name'],
array(
'order'     => $this,
'billing'   => $this->getBillingAddress(),
'comment'   => $comment,
'your_var_name'=>'SOMETHNG'
)
);

In the e-mail template you can show the new var like this

{{var your_var_name}}

Thursday, April 29, 2010

How to add a block above the 3 columns in 3 columns layout

Problem: Adding on home page a block above the 3 columns
http://www.magentocommerce.com/boards/viewthread/180617/

Possible solution:
Here is how i did it.
Edit app/design/frontend/{interface}/{theme}/layout/page.xml
In the <default> tag add this
<block type="core/text_list" name="top" as="top" translate="label">
<label>Top Row</label>
</block>
Now go to admin panel: Cms->Pages and edit the homepage.
I've created a new phtml file in which I placed the content I want on top
Let's say it's app/design/frontend/{interface}/{theme}/template/callouts/top_callout.phtml
In the design tab for 'Layout Update XML' I put this:

<reference name="top">
<block type="core/template" name="top.callout" template="callouts/top_callout.phtml" />
</reference>
You can put every block you want here. I just used this as an example. Just put it inside
<reference name="top">
....
</reference>
Now edit app/design/frontend/{interface}/{theme}/template/page/3columns.phtml
and add this line
<?php echo $this->getChildHtml('top') ?>
right under
<?php echo $this->getChildHtml('header') ?>
Save all files you edited and clear the contents of 'var/cache/'

I attached an image to see how it looks like.



View full size image

Thursday, April 22, 2010

How to change invoice status in Admin panel

Problem: When creating Invoice directly from Order in admin the invoice automatically gets marked as Paid. how can it be created with the status 'Pending' and marked as paid later. http://www.magentocommerce.com/boards/viewthread/54391/

Possible solution:
In order to create a 'pending' invoice, the payment method you use for the order must allow you to.
For that you need to add in the payment method's model this member:
protected $_canCapture = true;
You can add it right under
protected $_code  = 'PAYMENT CODE HERE';
For example if you want to do this for Check/money order you need to add the line above in app/code/core/Mage/Payment/Model/Method/Checkmo.php

After this when you create an invoice you will have an option near the save button to set the invoice status. (see picture attached). To create a pending invoice select 'Not capture'.
If you want to mark it later as paid edit the invoice and between the buttons on to one called 'Capture' (see picture). Well you get the idea....

Tuesday, April 13, 2010

Handle multi-language

Possible solution:
This is actually very simple. One store view = one language.
You download the desired language pack. For example French(France).
You should get a file called fr_FR_default_full_package.zip (or any other archive format)
Unzip this file over the root of your Magento application.
After that go to the admin panel. System->Configuration. Select from the top-left drop-down the store view you want to be in French and under the General tab 'Locale options' set Locale as French(France).
Save and clear the cache and you should have a store view in French. As for the products and categories again is simple. Edit the product you desire, Select from the top-left drop-down the French store view and enter the French values for the text attributes.

Monday, April 12, 2010

Move footer links to the top

Problem: How to move footer links to the top of the page
More details here:http://www.magentocommerce.com/boards/viewthread/178685/ or here http://www.magentocommerce.com/boards/viewthread/177423/

Possible solution:
Edit this file:
app/design/frontend/{interface}/{theme}/layout/page.xml
Look for this line:
<block type="page/template_links" name="footer_links" as="footer_links" template="page/template/links.phtml"/>
It should be inside of an other <block> tag named footer.
Remove it from there and put it inside the <block> named 'header' (same file).
It should look like this (or close to it):
BEFORE:
<block type="page/html_header" name="header" as="header">
<block type="page/template_links" name="top.links" as="topLinks"/>
<block type="page/switch" name="store_language" as="store_language" template="page/switch/languages.phtml"/>
<block type="core/text_list" name="top.menu" as="topMenu"/>
</block>
AFTER:
<block type="page/html_header" name="header" as="header">
<block type="page/template_links" name="top.links" as="topLinks"/>
<block type="page/switch" name="store_language" as="store_language" template="page/switch/languages.phtml"/>
<block type="core/text_list" name="top.menu" as="topMenu"/>
<block type="page/template_links" name="footer_links" as="footer_links" template="page/template/links.phtml"/>
</block>

Then edit app/design/frontend/{interface}/{theme}/template/page/html/header.phtml
Add this line at the end of the file (or where ever it fits your needs)
<?php echo $this->getChildHtml('footer_links') ?>
Clear the contents of dir var/cache and it should work. You might need to do some css work so the new added links would look nice.

Thursday, April 8, 2010

Adding a sidebar login box

Problem:
How to add a login box on sidebar.
More details here http://www.magentocommerce.com/boards/viewthread/178324/

Possible solution:
First create a phtml file. Let's call it mini.login.phtml.
Place it in app\design\frontend\{interface}\{theme}\template\customer\form\mini.login.phtml
Make sure the form has the same action as in login.phtml Also give the inputs the same name as they have in login.phtml.

Edit app\design\frontend\{interface}\{theme}\layout\customer.xml
under the tag <customer_logged_out> put the following
<reference name="left">
<block type="customer/form_login" name="customer_mini_login" before="-" template="customer/form/mini.login.phtml"/>
</reference>
Clear the cache and refresh.
This will put the mini.login at the top of your left column.If you want it lower play with the 'before' attribute. (You can even replace it with after)
See more details here. http://www.magentocommerce.com/design_guide/articles/intro-to-layouts

The side effect of this is that all the pages that contain this block will have the meta-tille 'Customer Login'. (at least for version 1.4.0.1)
To avoid this you have 2 options.
The 'quick and dirty' one.
Edit app\code\core\Mage\Customer\Block\Form\Login.php and in method _prepareLayout() remove this line:
$this->getLayout()->getBlock('head')->setTitle(Mage::helper('customer')->__('Customer Login'));
or remove the method completely (same effect).
This will work but the problem will reappear after an upgrade.
The 'by the book' option.
Overwrite the Mage_Customer_Block_Form_Login block. See tutorial here:
[http://magedev.com/2009/06/03/magento-overriding-model-block-or-helper/
After that in the the class that extends Mage_Customer_Block_Form_Login create the method _prepareLayout() that does nothing. Just returns the current object.
protected function _prepareLayout()
{
return $this;
}

Thursday, April 1, 2010

Create bulk discount rules

Problem:
Create bulk discount rules with unique coupon codes.
Details found here: http://www.magentocommerce.com/boards/viewthread/177465/
Possible solution:
This is not an extension, it's just a 'quick and dirty' script that I hope some day (when I have the time) will grow up and become an extension.

put the code below into a file (let's call it coupons.php) and follow the instructions below:

<?php
$mageFilename = 'app/Mage.php';

require_once $mageFilename;

Varien_Profiler::enable();

Mage::setIsDeveloperMode(true);

ini_set('display_errors', 1);

umask(0);
Mage::app('default');
Mage::register('isSecureArea', 1);
function generateUniqueId($length = null){
$rndId = crypt(uniqid(rand(),1)); 
$rndId = strip_tags(stripslashes($rndId)); 
$rndId = str_replace(array(".", "$"),"",$rndId); 
$rndId = strrev(str_replace("/","",$rndId));
if (!is_null($rndId)){
return strtoupper(substr($rndId, 0, $length));
} 
return strtoupper($rndId);
} 
function getAllCustomerGroups(){
//get all customer groups
$customerGroups = Mage::getModel('customer/group')->getCollection();
$groups = array();
foreach ($customerGroups as $group){
$groups[] = $group->getId();
}
return $groups;
}
function getAllWbsites(){
//get all wabsites
$websites = Mage::getModel('core/website')->getCollection();
$websiteIds = array();
foreach ($websites as $website){
$websiteIds[] = $website->getId();
}
return $websiteIds;
}
//read comments for each line
function generateRule(){
$uniqueId = generateUniqueId(10);
$rule = Mage::getModel('salesrule/rule');
$rule->setName($uniqueId);
$rule->setDescription('Generated by tzyganu');
$rule->setFromDate(date('Y-m-d'));//starting today
//$rule->setToDate('2011-01-01');//if you need an expiration date
$rule->setCouponCode($uniqueId);
$rule->setUsesPerCoupon(1);//number of allowed uses for this coupon
$rule->setUsesPerCustomer(1);//number of allowed uses for this coupon for each customer
$rule->setCustomerGroupIds(getAllCustomerGroups());//if you want only certain groups replace getAllCustomerGroups() with an array of desired ids 
$rule->setIsActive(1);
$rule->setStopRulesProcessing(0);//set to 1 if you want all other rules after this to not be processed
$rule->setIsRss(0);//set to 1 if you want this rule to be public in rss
$rule->setIsAdvanced(1);//have no idea what it means :)
$rule->setProductIds('');
$rule->setSortOrder(0);// order in which the rules will be applied

$rule->setSimpleAction('by_percent');
//all available discount types
//by_percent - Percent of product price discount
//by_fixed - Fixed amount discount
//cart_fixed - Fixed amount discount for whole cart
//buy_x_get_y - Buy X get Y free (discount amount is Y)

$rule->setDiscountAmount('20');//the discount amount/percent. if SimpleAction is by_percent this value must be <= 100
$rule->setDiscountQty(0);//Maximum Qty Discount is Applied to
$rule->setDiscountStep(0);//used for buy_x_get_y; This is X
$rule->setSimpleFreeShipping(0);//set to 1 for Free shipping
$rule->setApplyToShipping(1);//set to 0 if you don't want the rule to be applied to shipping
$rule->setWebsiteIds(getAllWbsites());//if you want only certain websites replace getAllWbsites() with an array of desired ids

$conditions = array();
$conditions[1] = array(
'type' => 'salesrule/rule_condition_combine',
'aggregator' => 'all',
'value' => "1", //[UPDATE] added quotes on the value. Thanks Aziz Rattani [/UPDATE]
'new_child' => ''
);
//the conditions above are for 'if all of these conditions are true'
//for if any one of the conditions is true set 'aggregator' to 'any'
//for if all of the conditions are false set 'value' to 0.
//for if any one of the conditions is false set 'aggregator' to 'any' and 'value' to 0
$conditions['1--1'] = Array
(
'type' => 'salesrule/rule_condition_address',
'attribute' => 'base_subtotal',
'operator' => '>=',
'value' => 200
);
//the constraints above are for 'Subtotal is equal or grater than 200'
//for 'equal or less than' set 'operator' to '<='... You get the idea other operators for numbers: '==', '!=', '>', '<'
//for 'is one of' set operator to '()';
//for 'is not one of' set operator to '!()';
//in this example the constraint is on the subtotal
//for other attributes you can change the value for 'attribute' to: 'total_qty', 'weight', 'payment_method', 'shipping_method', 'postcode', 'region', 'region_id', 'country_id'

//to add an other constraint on product attributes (not cart attributes like above) uncomment and change the following:
/*
$conditions['1--2'] = array
(
'type' => 'salesrule/rule_condition_product_found',//-> means 'if all of the following are true' - same rules as above for 'aggregator' and 'value'
//other values for type: 'salesrule/rule_condition_product_subselect' 'salesrule/rule_condition_combine'
'value' => 1,
'aggregator' => 'all',
'new_child' => '', 
);

$conditions['1--2--1'] = array
(
'type' => 'salesrule/rule_condition_product',
'attribute' => 'sku',
'operator' => '==',
'value' => '12',
);
*/
//$conditions['1--2--1'] means sku equals 12. For other constraints change 'attribute', 'operator'(see list above), 'value'

$rule->setData('conditions',$conditions);
$rule->loadPost($rule->getData());
$rule->save();
//[UPDATE]if you work with Mangento EE and you want to link banners to your rule uncomment the line of code below
//Mage::getResourceModel('enterprise_banner/banner')->bindBannersToSalesRule($rule->getId(), array(1,2));//the array(1,2, ...) is the array with all the banners you want to link to the rule.
//[/UPDATE]
}
?>


[UPDATE]. This was made for Magento 1.3 and lower. For versions >= 1.4 you have to add a new line
$rule->setCouponType(2);
Add it just before
$rule->save();
(Thanks Raj for pointing this out).
[/UPDATE]

[UPDATE 1.1]
For Magento 1.4 and later there are store labels for the discount rules.
To add this construct the following array
$labels = array();
$labels[0] = 'Default store label';//default store label
$labels[1] = 'Label for store with id 1';
//....
$labels[n] = 'Label for store with id n';
//add one line for each store view you have. The key is the store view ID

and before calling $rule->save(); add this line
$rule->setStoreLabels($labels);

(Thanks Sigmund for the update)
[/UPDATE 1.1]

Put the newly created file in the root of your magento installation (same level as index.php)
Walk through the file read all the comments and configure your discount settings (some programming skills are requred)
After everything is done add the following code at the end of the file (before php closing tag)

for ($i=1;$i<=200;$i++){//replace 200 with the number of coupons you want
generateRule();
}
[UPDATE 1.2]
If you want a nice script to import coupons using csv files with code, description and discount check this out: http://www.gielberkers.com/bulk-import-coupon-codes-in-magento/
Nice job Giel :). [/UPDATE 1.2]
call the page in you browser (http://your.magento.root/coupons.php) or run it from the command line.
Now check your admin for the new added rules.
PS (this does not work yet for 'Actions' tab).

Tuesday, March 16, 2010

Jut for fun!

While working on a theme for Magento 1.4.0.1 I found this in app\design\frontend\base\default\template\checkout\onepage\shipping.phtml between lines 49 and 56
<?php if(false): ?>
<div class="fields">
<label for="shipping:email" class="required"><em>*</em><?php echo $this->__('Email Address') ?></label>
<div class="input-box">
<input type="text" name="shipping[email]" id="shipping:email" value="<?php echo $this->htmlEscape($this->getAddress()->getEmail()) ?>" title="<?php echo $this->__('Email Address') ?>" class="input-text validate-email required-entry" />
</div>
</div>
<?php endif ?>

Good one Magento Team.

Monday, March 1, 2010

Retrieve attributes by groupId

Problem: how to retrieve product attributes by group id.
See here: http://www.magentocommerce.com/boards/viewthread/79694/

Possible solution:
<?php
require_once 'path/to/app_dir/app/Mage.php';
Mage::app('default');
$product = Mage::getModel('catalog/product');
$product->setAttributeSetId('THE ATTRIBUTE SET YOU WANT TO USE');
$groupId = 'THE ID OF THE GROUP YOU WANT TO RETRIEVE';

$collection = $product->getAttributes($groupId, false);
foreach($collection as $attribute){
//do something with the attribute
}
?> 

Saturday, February 27, 2010

IPv6 for online customers

Problem : how to make Magento show the ipv6 ip address on online customers.
See full text here http://www.magentocommerce.com/boards/viewthread/79160/

Possible (untested) solution. If someone tests it please tell me the results also:

In theory you cannot do this, because in the database the fields for ip address are 'bigint' (see tables that start with log_visitor_)
All the ip addresses stored in this table 'suffer' a modification with ip2long() method. Unfortunately php does not support IPv6 (at least that's what I know. If I'm wrong someone please correct me).
Enough with the problems.
Here is what you can try.
Change the type of the columns for ip addresses from bigint to varchar.
In app/code/core/Mage/Log/Model/Visitor.php look for these lines:
$this->addData(array(
'server_addr'           => $helper->getServerAddr(true),
'remote_addr'           => $helper->getRemoteAddr(true), 
....
and remove the 'true' parameters from the functions.
$this->addData(array(
'server_addr'           => $helper->getServerAddr(),
'remote_addr'           => $helper->getRemoteAddr(), 
This should save the ip addresses as strings.

We are half way there. Now all you have to do is make sure that the ip addresses are rendered in the back-end grid you need to change the \app\code\core\Mage\Adminhtml\Block\Customer\Online\Grid\Renderer\Ip.php file.
Change this
public function render(Varien_Object $row)
{
return long2ip($row->getData($this->getColumn()->getIndex()));
}

to
public function render(Varien_Object $row)
{
return $row->getData($this->getColumn()->getIndex());
}

or to this, for backwards compatibility (to see the previous saved ip addresses)
public function render(Varien_Object $row)
{
$val = $this->getColumn()->getIndex();
if (is_int($val)){
return long2ip($val);
}
return $val;
}

Clear the cache and test it.
I have no way of testing this but it seams to me that this is the way to go.

(Of course you don't need to modify the core files if you plan to upgrade, you can overwrite them the proper magento way ).

Monday, February 1, 2010

Save an object into the database with a specific id. (insert - not update)

Problem can be found here:

Possible solution:

The presence of an id means update and if the id is null it means is insert.
So when you try to insert and add an id Magento is actually trying to update the entity but the id you specify does not exist.
It's something similar to this:
UPDATE some_table SET some_field = some_value WHERE id = some_id; 
The sql goes through but nothing gets updated.
If you want to modify something (but I don't advice you to do this) you can change the Mage_Core_Model_Mysql4_Abstract and Mage_Core_Model_Abstract models (the save() method for both of them.)

If you look inside the save method of Mage_Core_Model_Mysql4_Abstract you will see that there's an 'if' statement
if (!is_null($object->getId())) {...}
This means exactly what I explained above.

Here is an idea on how you can make magento do an insert with a specified id.
Overwrite the Mage_Core_Model_Abstract::save() method to accept and other parameter.
Let's call it $alwaysInsert and set it's default value to false (so you will not interfere with the other objects that use save()).
Then pass the $alwaysInsert parameter to the save method from the resource.
Your new method could look like this:
public function save($alwaysInsert = false)
{
$this->_getResource()->beginTransaction();
try {
$this->_beforeSave();
$this->_getResource()->save($this, $alwaysInsert);
$this->_afterSave();

$this->_getResource()->commit();
}
catch (Exception $e){
$this->_getResource()->rollBack();
throw $e;
}
return $this;
}

In Mage_Core_Model_Mysql4_Abstract, change the method save() to accept 2 parameters.
It could look like this:
public function save(Mage_Core_Model_Abstract $object, bool $alwaysInsert)
{
if ($object->isDeleted()) {
return $this->delete($object);
}

$this->_beforeSave($object);
$this->_checkUnique($object);

if (!is_null($object->getId()) && !$alwaysInsert) {
$condition = $this->_getWriteAdapter()->quoteInto($this->getIdFieldName().'=?', $object->getId());
$this->_getWriteAdapter()->update($this->getMainTable(), $this->_prepareDataForSave($object), $condition);
} else {
$this->_getWriteAdapter()->insert($this->getMainTable(), $this->_prepareDataForSave($object));
$object->setId($this->_getWriteAdapter()->lastInsertId($this->getMainTable()));
}

$this->_afterSave($object);

return $this;
} 

WARNING:
There are 2 issues (at least that is all I can think about now)
1. I didn't test this. You can test it and tell everyone the results
2. I'm not sure how Magento behaves when extending an abstract class. Normally it shouldn't cause any problems, but make sure the class(es) you are working with extend the new class(es) that you just created (extensions of Mage_Core_Model_Abstract and Mage_Core_Model_Mysql4_Abstract not these classes themselves).

If instead of overwriting you decide to modify the core this should take care of these issues, but you are not suppose to modify the code.
I hope this works for you. If it doesn't I'm sorry.

Tuesday, January 19, 2010

Display 'template hints' in 'default config'

Problem: Display 'template hints' and 'bloc hints' in 'default config'
Posible solution
If you want this option displayed for 'default config' also, just edit the app\code\core\Mage\Core\etc\system.xml file.
Look for something like this
<template_hints translate="label">
<label<Template Path Hints</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>20</sort_order>
<show_in_default>0</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</template_hints>
replace
<show_in_default>0</show_in_default>
with
<show_in_default>1</show_in_default>
and clear the cache
Do the same for this section

<template_hints_blocks translate="label">
<label>Add Block Names to Hints</label>
<frontend_type>select</frontend_type>
<source_model>adminhtml/system_config_source_yesno</source_model>
<sort_order>21</sort_order>
<show_in_default>0</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
</template_hints_blocks>
Be careful. When you enable these 2 from 'default config' you will also see template hints and block hints in the admin section.

Monday, January 18, 2010

Avoid empty meta-title for products (for biostall)

Problem: in case the meta-title is not filled in for a product the title should replace it

Possible solution:

Create a new method in the product model (Mage_Catalog_Model_Product) class called getMetaTitle();
function getMetaTitle(){
if (!$this->getData('meta_title')){
$this->setMetaTitle($this->getTitle());
}
return $this->getData('meta_title');
}

A list of all the products in the website (for travelingwilly)

Problem: a list of all the products in the website.

Possible solution:

The easiest (less code) solution is to create a category called 'All' an put all the products in that category(beside the category that they belong to).
The second method implies some code.
Create the Mage_Catalog_Block_Product_All class and put it in a file located here (app/code/code/Mage/Catalog/Block/Product/All.php)
<?php
class Mage_Catalog_Block_Product_All extends Mage_Catalog_Block_Product_List
{
protected function _getProductCollection()
{
if (is_null($this->_productCollection)) {
$collection = Mage::getResourceModel('catalog/product_collection')
->addAttributeToSelect('*')
->addAttributeToFilter('status', 1)
->addAttributeToFilter(array(array('attribute'=>'visibility', 'eq'=>"4")));
$this->_productCollection = $collection;
}
return $this->_productCollection;
}
}
?>

After this create a CMS page. and in it's content put the following
{{block type="catalog/product_all" template="catalog/product/list.phtml"}}
In the "Layout" field (from the 'Custom design' tab) select '2columns-left' (or any other design just don't leave it to 'empty')
Save and that's it.
If the page you just created has the identifier ('all-products') and is enabled you will see a paginated list of all your products here:
http://www.yoursite/all-products (or http://www.yoursite/{YOUR STORE CODE}/all-products if you have store codes in the url)

I know that this is not 100% clean because you need to add some code in the core. This is no problem, you can add this also in the local folder.

EDIT: I forgot to close a curly bracket at the end of the class. Thanks To John (see who John is the comments below) I fixed it.

Display CMS Blocks on product description (for Mattse)

Problem: add static block to product description

possible solution

You can add a new method to the product model.
function getDescription(){
$processor = Mage::getModel('core/email_template_filter');
$html = $processor->filter($this->getData('description'));
return $html;
}

In this way you can use the same {{block}} and {{skin}} sections just like the static blocks and pages and e-mail templates.

Wednesday, January 13, 2010

how to display number of items in cart and cart total (for elmuchoprez)

Problem: how to display number of items in cart and cart total?
Possible solution

To display items in cart do this:
$this->helper('checkout/cart')->getSummaryCount();
or
Mage::helper('checkout/cart')->getSummaryCount();
And for the total:
Mage::getSingleton('checkout/session')->getQuote()->getSubtotal();
This will display only the total amount for the products.
If you want the grand total (including shipping & handling) do this:
Mage::getSingleton('checkout/session')->getQuote()->getGrandTotal();

Tuesday, January 5, 2010

Show customer group in order e-mail (for redbeetstudio)

Problem: Show customer group in order e-mail.
http://www.magentocommerce.com/boards/viewthread/71736/

Possible solution
In order for this to work in the e-mail template:
{{var order.getCustomerGroupName()}}
you have to create a method in the Order model (overwrite it or edit the core at your own risk).
function getCustomerGroupName(){
$group = Mage::getModel('customer/customer_group')->load($this->getCustomerGroupId());
if ($group->getId()){
return $group->getName();
}
return '';//or any default value if the customer is not in a group or is a guest
}