Preserve the current working directory in Bash (Linux) when you log off

posted by bob

We manage quite a few servers, and probably double the number of virtual machines as well (for testing, local provisioning etc). Sometimes it’s a pain that when you log off from one of these servers that you log back into your home directory (e.g. /home/bob) instead of the path that you wanted to be in.

If the path that you want to be in by default is a certain path, you can simply edit your login script at ~/.bashrc and add a line at the bottom that says “cd /path/that/you/want/to/go/to”.

If you want to go back to the last path you were at when you logged out, this is easy enough. What we do is this:

  1. On logout, write the current directory name to a file.
  2. On login, read from that file and change to the directory.

To do this, add the following lines to the following files:

~/.bash_logout (create if required)

pwd > ~/.lastdir

~/.bashrc (create if required, but you probably have one)

[ -s ~/.lastdir ] && cd `cat ~/.lastdir`

To test, log in and change to a directory, log out and then log back in again.

Meta keywords for OpenCart

posted by bob

OpenCart out of the box does not support meta keywords throughout the system. This is relatively easy to add, but you will need to edit each controller to enable this.

In this particular post we’ll take a look at adding support for meta keywords on the OpenCart home page. There is support already for a meta description and this can be configured in the back end of OpenCart under System » Settings » Store » Meta Tag Description.

Our aim will be to add another field beneath that called “Meta Tag Keywords” and have this reflect in the source of the homepage.

There are two parts to this: adding support for editing and storing the meta keywords in the admin, and adding the code to the front of the website so that the value will be used.

Magento 1.4.2.0 Onepage Checkout Save Shipping Address Not Working

posted by andy

One of our clients noticed that checking the save shipping address in the magento checkout does not save that address to the customeres addresses.
After some investigation I can confirm this is a 1.4.2.0 bug, but have not seen any solutions on the web, so here is my fix:

You should override the App/Code/Core/Mage/Checkout/Model/Type/Onepage.php class in the usual manner, and override the saveShipping method, to be like:

public function saveShipping($data, $customerAddressId)
{
    if (empty($data)) {
        return array('error' => -1, 'message' => $this->_helper->__('Invalid data.'));
    }
    $address = $this->getQuote()->getShippingAddress();
 
    if (!empty($customerAddressId)) {
       $customerAddress = Mage::getModel('customer/address')->load($customerAddressId);
        if ($customerAddress->getId()) {
             if ($customerAddress->getCustomerId() != $this->getQuote()->getCustomerId()) {
                 return array('error' => 1,
                     'message' => $this->_helper->__('Customer Address is not valid.')
                 );
             }
             $address->importCustomerAddress($customerAddress);
        }
    } else {
        /* @var $addressForm Mage_Customer_Model_Form */
        $addressForm    = Mage::getModel('customer/form'); 
        $addressForm->setFormCode('customer_address_edit')
            ->setEntity($address)
            ->setEntityType('customer_address')
            ->setIsAjaxRequest(Mage::app()->getRequest()->isAjax());
        // emulate request object
        $addressData    = $addressForm->extractData($addressForm->prepareRequest($data));
        $addressErrors  = $addressForm->validateData($addressData);
        if ($addressErrors !== true) {
            return array('error' => 1, 'message' => $addressErrors);
        }
        $addressForm->compactData($addressData);
         //the next 3 lines are the fix!!!
        if (!empty($data['save_in_address_book'])) {
             $address->setSaveInAddressBook(1);
        }
    }
 
    $address->implodeStreetAddress();
    $address->setCollectShippingRates(true);
 
    if (($validateRes = $address->validate())!==true) {
        return array('error' => 1, 'message' => $validateRes);
    }
 
     $this->getQuote()->collectTotals()->save();
 
    $this->getCheckout()
        ->setStepData('shipping', 'complete', true)
        ->setStepData('shipping_method', 'allow', true);
 
    return array();
}

This now follows a similar logic to the saveBilling address, setting a flag to save the address when the order is saved.

Magento: Default Product Listing Sort by not exists on Available Product Listing Sort By

posted by bob

We have a system that populates a Magento installation by reading an external database and creating, updating and deleting products and categories. Recently I had been receiving an obscure message when trying to update categories using the Magento API.

The message was simply: Default Product Listing Sort by not exists on Available Product Listing Sort By

After Googling around I hadn’t turned up much, which forced me into the bowels of Magento. This exception is thrown in the file app/code/core/Mage/Model/Category/Attribute/Backend/Sortby.php on line 81 and 85. This file is responsible for checking that the Sort By method specified for a category is valid.

Default Display Settings for a new cateogry

Default Display Settings for a new cateogry

When you create a category in Magento using the Admin UI, the values in the Display Settings tab of the category as set as per this screenshot (click to enlarge).  The default values are:

  • Available Product Listing Sort By = Use All Available Attributes
  • Default Product Listing Sort By = Use Config Settings

With this category created, I used the following test script to check to see if I could update the category.  This script simply asks for the information about the category (category 4066 in my case) and attempts to update the category with that information.

<?php
 	define('MAGENTO', getcwd() );
 	define('CATEGORY_ID', 4066 );
	require_once MAGENTO . '/app/Mage.php';
	umask(0);
	Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
 
	try
	{ 
		$info = Mage::getModel('catalog/category_api')->info( CATEGORY_ID );
		var_dump( $info );
		Mage::getModel('catalog/category_api')->update( CATEGORY_ID, $info );
 
		echo "\n\nSUCCESS\n\n";
	}
	catch( Exception $e )
	{
		var_dump( $e );
	}
?>

When I ran this with the default settings, I received a “data_invalid” exception, however tweaking it to use any of the values in the select box for the Default Product Listing Sort By resulted in the exception “Default Product Listing Sort by not exists on Available Product Listing Sort By” (tip: it was useful to pipe the output of this script to `less’). I then had a test script that I could use to verify when my problem was fixed.

I then fiddled around with the display settings in Magento (which by the way does not suffer from this problem when saving a category) and found that if I set the Available Product Listing Sort By to anything other than using the tick to use all available attributes, and set the Default Product Listing Sort By to anything other than using the tick to use the Config Settings, then things magically started working.

This eventually led to me reworking a bit of code to include specific values for the available_sort_by and default_sort_by attributes when updating the category. Here’s my bit of code – out of context, but you get the idea.

    $result = $api->update
    (
        $category_id,
        array
        (
            'image' => $image_path,
            'description' => $description,
            'meta_description' => $meta_description,
            'meta_keywords' => $meta_keywords,
            'available_sort_by' => array( 'name' ),
            'default_sort_by' => 'name'
        )
    );

Good luck!

Magento: loadParentProductIds() is deprecated

posted by bob

As of Magento 1.4.2.0, the loadParentProductIds() method in the product model has been deprecated and is no longer available.

Here’s an example of it being used:
http://www.magentocommerce.com/boards/viewthread/74229

Prior to Magento 1.4.2.0, this would have worked:

list( $parentId ) = $_product->loadParentProductIds()->getData('parent_product_ids');

After Magento 1.4.2.0, this works:

list( $parentId ) = Mage::getModel( 'catalog/product_type_grouped' )->getParentIdsByChild( $_product->getId() );

Note that the above fetched the parent product ID’s for a grouped product – there are also versions for configurable products in the catalog/product_type_configurable model.

Good luck!

Creating a Magento admin fully editable grid

posted by andy

Recently I created a custom module for a client, that required a change to the standard UI for the backend data. The standard UI creates a grid of objects (in my case freight weight and size classes) with a button to create a new one, and action column in the grid to let you edit or delete once that item is loaded.

Having a large number of simple and related values, we changed to a new custom UI, with an editable grid where the last row in the grid is to create a new entry, e.g.:

Custom editable grid

Custom editable grid

Our other requirement was that more than one of these tables could be shown in the same tab container (in our example one called weight and one called size).

Overview:

To achieve this result, we need to create the following file structure for our custom module:

Updating Magento categories, en masse

posted by bob

I had a problem where I had to update all of the categories in a Magento store to change the CMS block to “Sub Category Listing” because they had been created without a CMS block selected. Doing this through the UI would have been painful as there were over 300 categories that had to be updated.

The solution was relatively simple, thanks to a quick PHP script. I put this in the webroot as updatecategories.php:

<?php
 
ob_end_flush();
echo "Started " . date( "d/m/y h:i:s" ) . "\r\n";
require_once '/app/Mage.php';
umask(0);
Mage::app()->setCurrentStore( Mage_Core_Model_App::ADMIN_STORE_ID );
 
$categories = Mage::getModel( 'catalog/category' )->getCollection();
echo "Processing " . count( $categories ) . " categories\r\n";
foreach( $categories as $category )
{
    // 6 is the ID for "Sub Category Listing"
    $category->setData( 'landing_page', 6 )->save();
    echo ".";
}
echo "\r\n";
echo "Finished ".date( "d/m/y h:i:s" )."\r\n";
 
?>

I then ran the script (e.g. php updatecategories.php) and after about 30 seconds all categories were changed. You could probably point your web browser at the file as well to achieve the same effect. This will give you a starting point if you need to do anything else to the category, e.g. change the description, name, image etc.

The New Web: HTML5 and CSS3 Resources

posted by Aaron

Recently I’ve been coming across more interesting links to do with HTML5 and CSS3.

The first thing you want to know is can I use these two new technologies. Luckily that is where caniuse.com comes in handy. It is a great resource for looking at what web browsers are compatible with the newer standards.

So is HTML5 worth it? The website www.html5rocks.com will show you some of the great features it has and also let you play with them in the playground.

So you want to start writing in HTML5? You’ll need some good starting points. Boilerplates are a great way to start from a good place. The resources over at html5boilerplate.com can help you get that start.

Magento 1.4.2.0 changes to customer attributes

posted by andy

As of Magento 1.4.2.0 the customer model now implements eav from attributes, and a new customer form model. For anyone who has existing custom attributes, or are thinking of making some this will pose new issues in accessing and saving this data.

To implement a custom customer attribute, the sql/[module]_setup install file needs to add the new attribute and then add this attribute to several forms (those that deal with a customer). See below, but these are customer_account_edit, customer_account_create, adminhtml_customer and checkout_register.

Here is an example of an existing custom attributes to add a customer type field:
sql/[module]_setup/mysql4-install-0.1.0.php (for example)

function createCustomAttributes()
{
    $attribs = array(
        'customer' => array(
            'customer_type' => array(
                'type' => 'varchar',
                'label' => 'Customer Type',
                'sort_order' => 5,
                'required' => 0
            ),
        )
    );
 
    $setup = new Mage_Eav_Model_Entity_Setup('core_setup');
 
    foreach( $attribs as $entityName =&gt; $attributes )
    {
        foreach( $attributes as $attributeName => $attributeData )
        {
            $setup->addAttribute( $entityName, $attributeName, $attributeData );
        }
    }
}
 
createCustomAttributes();

This code will add the customer attribute ‘customer_type’ to the eav attribute table. Now comes the important difference introduced in Magento 1.4.2.0:

sql/[module]_setup/mysql4-install-0.1.0.php (continues)

//add attributes to new forms model attributes
$eavConfig = Mage::getSingleton('eav/config');
$attributeArray = array('customer_type');
 
//I handle this in an array incase several attributes need adding, depending on your needs.
foreach( $attributeArray as $attrib )
{
    $attribute = $eavConfig-&gt;getAttribute('customer', $attrib);
    $attribute->setData('used_in_forms',   array
        (
            'customer_account_edit',
            'customer_account_create',
            'adminhtml_customer',
            'checkout_register'
        )
    );
    $attribute->save();
}

This will add a field to all of the customer forms, and also the onepage checkout if using the user chooses to create a new account during checkout method! (although the latter will require adding to the template… in the format: name=”billing[customer_type]” as the other forms use the form model to generate the HTML fields)

If you are using the registration page, the attribute also needs to be added to the sales_flat_quote table: (in the same file..

$installer->getConnection()->addColumn(
    $installer->getTable('sales_flat_quote'),
    'customer_type',
    'text  NULL  DEFAULT  NULL  AFTER `customer_taxvat`'
);

And you will need to edit the module’s etc file:

<global>
        <fieldsets>
            <customer_account>
                <customer_type>
                    <create>1</create>
                    <update>1</update>
                    <name>1</name>
                    <to_quote>customer_type</to_quote>
                </customer_type>
            </customer_account>
            <checkout_onepage_quote>
                <customer_type>
                    <name>1</name>
                    <to_customer>customer_type</to_customer>
                </customer_type>
            </checkout_onepage_quote>
        </fieldsets>
</global>

Emulating “immediate mode” in a PHP CLI script

posted by bob

I’ve been working on a complex PHP CLI script lately and in order to debug something I’ve been putting var_dump() and die commands at the appropriate place.  This is annoying.

Packages like xdebug provide the ability to put a breakpoint in, but this requires you to configure your environment appropriately and use an IDE that can listen and step through the script.  Also, I’ve seen people having trouble using xdebug with PHP CLI – I haven’t tried it myself.

So, anyway my hacky solution was to roll the following code at the point I wanted to debug.  I had the variable scope at my disposal, so I could issue my own var_dump(), echo and variable assignment commands, before then entering “break;” to get out of the loop (I had to put an exception in for “break;” because the break is run in the eval command and it won’t break out of it without an error).

// Helper line so I can find and remove this code later:
echo "\n" . __FILE__ . ':' . __LINE__ . ' breakpoint' . PHP_EOL;
while( true )
{
	$cmd = readline( "debug&gt; " );
	if( $cmd == 'break;' ) { break; }
	eval( $cmd );
}

You may want to wrap this in some logic test so that it only fires when certain criteria are met.

Subscribe to RSS Feed Follow me on Twitter!