Skip to content


XMLRPC Services with Drupal

I’m working on a project that is using Drupal as a backend repository. A mobile app will need to access Drupal via XMLRPC so I installed Views and Services. I ran into a problem when trying to access the views.get service via XMLRPC – I kept getting access denied. After digging through the code, I traced the problem to a missing row in the services_key_permissions table. I then realized that when you define a key to use with key based authentication, you have to define which methods work with the key. So I went to Administrator/SiteBuilding/Services/Key/Edit and made checked views.get service option. Now it works like a charm. Hope this helps someone else out there.

Juice

Posted in Uncategorized.


Magento Quick Order Form

One of my clients requested a Quick Order Form for their site. Basically they wanted a table of their products with a text box for the customer to enter the quantity they want. I couldn’t find an existing custom module so I wrote my own. After creating the basic directory structure for a custom module, I started with the model. The model is very simple – it just retrieves the necessary data from the db

class OmniSubSole_QuickOrder_Model_Quickorder extends Mage_Core_Model_Abstract
{
 
    protected $_products = null;
 
    protected function setProducts($products){
	$this->_products = $products;
	return $this;
    }
 
    public function getProducts(){
	if ($this->_products === null) {
		$products = Mage::getModel('catalog/product')->getCollection();
		$products->addAttributeToFilter('status', 1);//enabled
		$products->addAttributeToFilter('visibility', array(1,2,3,4));//all categories
		$products->addAttributeToFilter('type_id', 'simple'); //<-------------Changed Here
		$products->addAttributeToSelect(array('id','sku','name', 'type_id','image','price','small_image','cart_increment'));
		$products->setOrder('name','ASC');     
 
		$this->setProducts($products);
	}
	return $this->_products;
    }
 
 
    protected function _construct()
    {
         $this->_init('omnisubsole_quickorder/quickorder');
    }
 
 
}

The addAttributeToFilter and addAttributeToSelect methods of the collection object are very useful, allowing you to only select certain products and only certain attributes of the product.

I then created a controller based on the CartController since I need to be able to add the items to the cart. The indexAction will display the form and the addAction will add the items to the cart. Note that you must require the CartController in order to override it.

require_once 'Mage/Checkout/controllers/CartController.php';
 
class OmniSubSole_QuickOrder_IndexController extends Mage_Checkout_CartController
{
    public function indexAction()
    {
	$this->products = Mage::getModel("omnisubsole_quickorder/quickorder")->getProducts();
        $this->loadLayout();
        $this->renderLayout();
    }
 
    public function addAction()
    {
	$hasError = false;
	$products =  Mage::getModel("omnisubsole_quickorder/quickorder")->getProducts();
        $cart   = $this->_getCart();
        $params = $this->getRequest()->getParams();
	foreach ($products as $product){
	    if (isset($params[$product->getId()]) && is_numeric($params[$product->getId()]) && $params[$product->getId()] > 0) {
		//echo "id = " . $product->getId() . " " . $params[$product->getId()];
		try {
			$productQty = $params[$product->getId()];
			$cart->addProduct($product, $productQty);
		    }
		catch (Mage_Core_Exception $e) {
		    $hasError = true;
		    if ($this->_getSession()->getUseNotice(true)) {
			$this->_getSession()->addNotice($e->getMessage());
		    } 
		    else {
			$messages = array_unique(explode("\n", $e->getMessage()));
			foreach ($messages as $message) {
			    //$this->_getSession()->addError($message . " productName = " . $product->getName());
			}
		    }
		}
		catch (Exception $e) {
		    $hasError = true;
		    $this->_getSession()->addException($e, "Cannot add " . $product->getName() . " to cart.");
		}
	    }
     	}
 
	$cart->save();
	//echo "haserror = " . $hasError;
    	$this->_getSession()->setCartWasUpdated(true);
 
 
    	Mage::dispatchEvent('quickorder_add_complete',
		array('product' => $product, 'request' => $this->getRequest(), 'response' => $this->getResponse())
    	);
 
	$this->getResponse()->setRedirect('/checkout/cart');
/*	if (!$hasError){
	    	$message = $this->__('%s was successfully added to your shopping cart.', Mage::helper('core')->htmlEscape($product->getName()));
	    	if (!$this->_getSession()->getNoCartRedirect(true)) {
			$this->_getSession()->addSuccess($message);
			$this->_goBack();
	    	}
	}
	else {
	    $url = $this->_getSession()->getRedirectUrl(true);
	    if ($url) {
	        $this->getResponse()->setRedirect($url);
	    } else {
	        $this->_redirectReferer(Mage::helper('checkout/cart')->getCartUrl());
	    }
	}*/
    }
}

Next, I created a block view which allows my phtml page to access the model; it also includes code to show the price associated with a product.

class OmniSubSole_QuickOrder_Block_View extends Mage_Core_Block_Template
{
    protected function _construct()
    {
        parent::_construct();
 
	//var_dump($this);
    }
 
    public function getProducts(){
	try {
		return Mage::getModel("omnisubsole_quickorder/quickorder")->getProducts();  
/*		foreach ($products as $product){
			echo $product->getId() . " " . $product->getName() . " " . $product->getSku() . " " . $product->getImage() . "<br>" . "\n";
		}*/
	}
	catch (Mage_Core_Exception $e) {
            Mage::logException($e);
            echo($e);
        }
 
    }
 
	public function getPriceHtml($product)
	{
		$this->setTemplate('catalog/product/price.phtml');
		$this->setProduct($product);
		return $this->toHtml();
	}
 
}

Next, I created the phtml page to generate the html for the form. I have configurable products so some of the code is used to get the product’s parent’s image and url for display.

<form method="post" action="/quickorder/index/add">
<?php 
<div class="page-title">
    <h1><?php echo $this->__('Quick Order Form') ?></h1>
</div>
<div class="category-products">
<input type='submit' value="Add to Cart"/>
    <?php $i=0; $_columnCount = 5; 
	  $_products = $this->getProducts(); 
	  $_collectionSize = count($_products);
	foreach ($_products as $_product):
	$imgSrc = $_product;
	$productUrlSrc = $_product;
	$parentIdArray = $_product->loadParentProductIds()->getData('parent_product_ids');
	if (count($parentIdArray)){
		$_parentProduct = Mage::getModel('catalog/product')->load(current($parentIdArray));
		$imgSrc = $_parentProduct;
		$productUrlSrc=$_parentProduct;
	}
?>
        <?php if ($i++%$_columnCount==0): ?>
        <ul class="products-grid">
        <?php endif ?>
            <li style="width:125px;" class="item<?php if(($i-1)%$_columnCount==0): ?> first<?php elseif($i%$_columnCount==0): ?> last<?php endif; ?>">
                <a href="<?php echo $productUrlSrc->getProductUrl() ?>" title="<?php echo $this->htmlEscape($this->getImageLabel($imgSrc, 'small_image')) ?>"><img src="<?php echo $this->helper('catalog/image')->init($imgSrc, 'small_image')->resize(80); ?>" width="80" height="80" alt="<?php echo $this->htmlEscape($this->getImageLabel($imgSrc, 'small_image')) ?>" /></a>
                <h2 class="product-name"><a href="<?php echo $productUrlSrc->getProductUrl() ?>" title="<?php echo $this->htmlEscape($_product->getName()) ?>"><?php echo $this->htmlEscape($_product->getName()) ?></a></h2>
                <?php echo $this->getPriceHtml($_product, true) ?>
                <div class="actions">Qty:
			<?php $minQty = $_product->getCartIncrement();
			if ($minQty==0 || !is_numeric($minQty)){
				$minQty =1;
			}
			if ($minQty > 1){
			?>			
                        <select name="<?php echo $_product->getId() ?>" id="<?php echo $_product->getId() ?>" class="input-text qty">
			<?php for ($x=0; $x<=11; $x++){
				echo "<option value=\"" . $x*$minQty . "\">" . $x*$minQty . "</option>\n";
			}?>
			</select>
			<?php  } else { ?>
                       	<input type="text" name="<?php echo $_product->getId() ?>" id="<?php echo $_product->getId() ?>" class="input-text qty"/>
			<?php } ?>
                </div>
            </li>
        <?php if ($i%$_columnCount==0 || $i==$_collectionSize): ?>
        </ul>
        <?php endif ?>
        <?php endforeach ?>
        <script type="text/javascript">decorateGeneric($$('ul.products-grid'), ['odd','even','first','last'])</script>
</div>
<input type='submit' value="Add to Cart"/></form>

Last, create the config.xml:

<config>
    <modules>
        <OmniSubSole_QuickOrder>
            <version>0.1</version>
        </OmniSubSole_QuickOrder>
    </modules>
    <global>
        <models>
		<omnisubsole_quickorder> <!-- This is used when istanting your Model EG: Mage::getModel("spinonesolutions_helloworld/hellworld") -->  
			<class>OmniSubSole_QuickOrder_Model</class> <!-- That class to use when isntanting objects of type above. -->  
		</omnisubsole_quickorder>  	
        </models>
        <blocks>
            <omnisubsole_quickorder><class>OmniSubSole_QuickOrder_Block</class></omnisubsole_quickorder>
        </blocks>
        <helpers>
            <omnisubsole_quickorder><class>OmniSubSole_QuickOrder_Helper</class></omnisubsole_quickorder>
        </helpers>
    </global>
    <frontend>
        <routers>
            <omnisubsole_quickorder>
                <use>standard</use>
                <args>
                    <module>OmniSubSole_QuickOrder</module>
                    <frontName>quickorder</frontName>
                </args>
            </omnisubsole_quickorder>
        </routers>
          <layout>
              <updates>
                  <omnisubsole_quickorder module="OmniSubSole_QuickOrder">
                      <file>quickorder.xml</file>
                  </omnisubsole_quickorder>
              </updates>
          </layout>
    </frontend>
    <default>
    </default>
</config>

and the referenced quickorder.xml for the layout:

<layout>
 
    <omnisubsole_quickorder_index_index>
        <reference name="root">
            <action method="setTemplate"><template>page/2columns-left.phtml</template></action>
        </reference>
	<reference name="head">
	       <action method="setTitle"><title>QuickOrder</title></action>
	</reference>
        <reference name="content">
            <block type="omnisubsole_quickorder/view" template="omnisubsole/quickorder/quickorder.phtml" name="omnisubsole.quickorder" />
        </reference>
        <reference name="left">
            <block type="customer/account_navigation" name="customer_account_navigation" before="-" template="customer/account/navigation.phtml">
                <action method="addLink" translate="label" module="customer"><name>account</name><path>customer/account/</path><label>Account Dashboard</label></action>
                <action method="addLink" translate="label" module="customer"><name>account_edit</name><path>customer/account/edit/</path><label>Account Information</label></action>
                <action method="addLink" translate="label" module="customer"><name>address_book</name><path>customer/address/</path><label>Address Book</label></action>
                <action method="addLink" translate="label" module="omnisubsole_quickorder"><name>quickorder</name><path>quickorder</path><label>QuickOrder</label></action>
	        <action method="addLink" translate="label" module="sales"><name>orders</name><path>sales/order/history/</path><label>My Orders</label></action>
	    </block>
            <remove name="tags_popular"/>
 
        </reference>
    </omnisubsole_quickorder_index_index>
</layout>

Here’s a zip of the install quickorder.zip

Posted in magento, php.


Advanced Search in Magento 1.4

I recently ran into a problem with upgrading to Magento 1.4. The Blue Theme and Modern Theme both were missing the “pager” on the advanced search results page. After tracing through the code, I finally found the problem – a missing action tag in catalogsearch.xml that defines the name of the toolbar block. The correct code is:

 <catalogsearch_advanced_result translate="label">
        <label>Advanced Search Result</label>
        <update handle="page_two_columns_left" />
        <!-- Mage_Catalogsearch -->
        <reference name="root">
            <action method="setTemplate"><template>page/2columns-left.phtml</template></action>
        </reference>
        <reference name="left">
                <block type="catalog/navigation" name="catalog.leftnav" after="currency" template="catalog/navigation/left.phtml"/>
        </reference>
 
        <reference name="content">
            <block type="catalogsearch/advanced_result" name="catalogsearch_advanced_result" template="catalogsearch/advanced/result.phtml">
                <block type="catalog/product_list" name="search_result_list" template="catalog/product/list.phtml">
                    <block type="catalog/product_list_toolbar" name="product_list_toolbar" template="catalog/product/list/toolbar.phtml">
                        <block type="page/html_pager" name="product_list_toolbar_pager"/>
                    </block>
                    <action method="addColumnCountLayoutDepend"><layout>empty</layout><count>6</count></action>
                    <action method="addColumnCountLayoutDepend"><layout>one_column</layout><count>5</count></action>
                    <action method="addColumnCountLayoutDepend"><layout>two_columns_left</layout><count>4</count></action>
                    <action method="addColumnCountLayoutDepend"><layout>two_columns_right</layout><count>4</count></action>
                    <action method="addColumnCountLayoutDepend"><layout>three_columns</layout><count>3</count></action>
                    <action method="setToolbarBlockName"><name>product_list_toolbar</name></action>
                </block>
                <action method="setListOrders"/>
                <action method="setListModes"/>
                <action method="setListCollection"/>
            </block>
        </reference>
    </catalogsearch_advanced_result>

Posted in magento.


Adding “Mutliselect” Attributes in Magento

In my current work, I’ve come across the need to create “multiselect” attributes in Magento. These are handy for selecting media formats (i.e. vhs, dvd, blue-ray) or product colors (red, green, blue, etc). The following code creates a mutliselect attribute named Media Format with labels of “Media Format” on the front and back ends with values of dvd, vhs, blue-ray.
The array indices of 0 and 1 refer to the Admin or Default Store View (i.e. front end). This is based on code from the AdminHtml/controllers/Catalog/Product/AttributeController.php script in Magento v. 1.3.2.2.

#!/usr/bin/php
<?php
define('MAGENTO', realpath('/var/www/magento'));
ini_set('memory_limit', '128M');
 
require_once MAGENTO . '/app/Mage.php';
 
Mage::app();
 
$model = Mage::getModel('catalog/entity_attribute');
 
$model->setAttributeCode('mediaFormat');//attribute code
$model->setIsComparable(0);
$model->setIsConfigurable(0);	
$model->setIsFilterable(0);
$model->setIsFilterableInSearch(0);	
$model->setIsGlobal(0);
$model->setIsRequired(0);
$model->setIsSearchable(0);	
$model->setIsUnique(0);	
$model->setIsUsedForPriceRules(0);	
$model->setIsVisibleInAdvancedSearch(0);	
$model->setIsVisibleOnFront(0);	
$model->setDefaultValueYesno(0);
$model->setUsedInProductListing(0);	
 
$model->setFrontendInput('multiselect'); //make it a multiselect/dropdown
//0 is the admin value, 1 is the "default store view" (frontend) value
$frontEndLabel=array('0'=>'Media Format',
                      '1'=>'Media Format');
 
$model->setFrontendLabel($frontEndLabel);	
//0 is the admin value, 1 is the "default store view" (frontend) value
$optionData = array('value' =>
						array('option_0'=>array('0'=>'dvd','1'=>'dvd'),
						      'option_1'=>array('0'=>'vhs','1'=>'vhs'),     
						      'option_2'=>array('0'=>'blue-ray','1'=>'blue-ray')     
						)
					,
			        'order' =>
						array('option_0'=>1,
							  'option_1'=>2,
							  'option_2'=>3)
			   );
$model->setOption($optionData);
 
 
$data['backend_model'] = 'eav/entity_attribute_backend_array';
$data['apply_to'] = array();
$model->addData($data);
$model->setEntityTypeId(Mage::getModel('eav/entity')->setType('catalog_product')->getTypeId());
$model->setIsUserDefined(1);
var_dump($model);
 
try {
	$model->save();
}
catch(Exception $e) {
    echo $e->getMessage();
}
?>

Posted in magento, php.

Tagged with , .


Parsing Large XML files with PHP

As part of my on going work of helping to migrate an e-commerce site to Magento, I’ve discovered that I need to parse a very large (>350MB) XML file. My first instinct was to use the SimpleXML parser. After a short test, where loading the file took 20 minutes and used up all of my computers memory, I looked for another solution. XMLReader (it comes with php 5.1) is the answer. It’s not as “pretty” as SimpleXML but its a stream processor so it does not have to load the entire file into memory to process it. This means you can more or less process record by record. Here are a few links that show how to use it:

http://www.ibm.com/developerworks/xml/library/x-xmlphp2.html

http://www.ibm.com/developerworks/library/x-pullparsingphp.html

http://blog.liip.ch/archive/2004/05/10/processing_large_xml_documents_with_php.html

Posted in php.

Tagged with , , .


Configurable Products in Magento

The ecommerce site I’m working on sells some instructional videos, some in different formats (DVD, VHS) and some in mutliple languages (English, Spanish, Portugese, etc). This scenario lends itself to the use of Magento’s Configurable Products. Basically, a configurable product allows the user to choose certain attributes of the product at the time they add the product to their cart. In our scenario, a user would choose the format and the language of the video at the time they add the item to the cart. You can find a good tutorial on setting up Configurable Products through the admin tool at Magento’s Wiki. As described in this wiki entry, you first have to create your custom attributes and then an attribute set that contains the new attributes. I created a Media Format attribute and a Language attribute and then added them to a Videos attribute set. You then create Simple Products based on the attribute set, one for each possbile combination of atttributes, i.e. Video 1 DVD English, Video 1 DVD Spanish, Video 1 VHS English, Video 1 VHS Spanish, etc. Finally you create a configurable product based on the attribute set.

In my case, however, I’m adding products programatically via a php script; not through the admin tool. I traced through the code and the data flow using Firebug on Firefox to determine how configurable products are created. As in the manual process, you first have to create simple products based on the attribute set with all possible combinations of the attributes. Notice that I’m setting the visibility of the simple products to “Nowhere”. This ensures that a user will only see the configurable product.

<?php
define('MAGENTO', realpath('/var/www/magento'));
ini_set('memory_limit', '128M');
 
require_once MAGENTO . '/app/Mage.php';
 
Mage::app();
            //create dvd english product
	    $product = Mage::getModel('catalog/product');
	    $product->setTypeId('simple');
	    $product->setTaxClassId(0); //none
	    $product->setWebsiteIds(array(1));  // store id
	    $product->setAttributeSetId(26); //Videos Attribute Set
	/*	<option value="6">dvd</option>7
		<option value="5">vhs</option> */
	    $product->setMediaFormat(6);  //DVD video
            $product->setLanguage(9); //English
	    $product->setSku(ereg_replace("\n","","videoTest2.1-dvd-english"));
	    $product->setName(ereg_replace("\n","","videoTest2.1"));
	    $product->setDescription("videoTest2");
	    $product->setInDepth("video test");    
	    $product->setPrice("129.95");
	    $product->setShortDescription(ereg_replace("\n","","videoTest2.1"));
	    $product->setWeight(0);
	    $product->setStatus(1); //enabled
	    $product->setVisibility(1); //nowhere
	    $product->setMetaDescription(ereg_replace("\n","","videoTest2.1"));
	    $product->setMetaTitle(ereg_replace("\n","","videotest2"));
	    $product->setMetaKeywords("video test");
	    try{
	    	$product->save();
                $productId = $product->getId();
	    	echo $product->getId() . ", $price, $itemNum added\n";
	    }
	    catch (Exception $e){ 		
	    	echo "$price, $itemNum not added\n";
		echo "exception:$e";
	    } 
 
            //create dvd spanish product
	    $product = Mage::getModel('catalog/product');
	    $product->setTypeId('simple');
	    $product->setTaxClassId(0); //none
	    $product->setWebsiteIds(array(1));  // store id
	    $product->setAttributeSetId(26); //Videos Attribute Set
	/*	<option value="6">dvd</option>7
		<option value="5">vhs</option> */
	    $product->setMediaFormat(6);  //DVD video
            $product->setLanguage(8); //Spanish
	    $product->setSku(ereg_replace("\n","","videoTest2.1-dvd-spanish"));
	    $product->setName(ereg_replace("\n","","videoTest2.1"));
	    $product->setDescription("videoTest2");
	    $product->setInDepth("video test");    
	    $product->setPrice("129.95");
	    $product->setShortDescription(ereg_replace("\n","","videoTest2.1"));
	    $product->setWeight(0);
	    $product->setStatus(1); //enabled
	    $product->setVisibility(1); //nowhere
	    $product->setMetaDescription(ereg_replace("\n","","videoTest2.1"));
	    $product->setMetaTitle(ereg_replace("\n","","videotest2"));
	    $product->setMetaKeywords("video test");
	    try{
	    	$product->save();
                $productId = $product->getId();
	    	echo $product->getId() . ", $price, $itemNum added\n";
	    }
	    catch (Exception $e){ 		
	    	echo "$price, $itemNum not added\n";
		echo "exception:$e";
	    } 
 
            //create vhs english product
	    $product = Mage::getModel('catalog/product');
	    $product->setTypeId('simple');
	    $product->setTaxClassId(0); //none
	    $product->setWebsiteIds(array(1));  // store id
	    $product->setAttributeSetId(26); //Videos Attribute Set
	/*	<option value="6">dvd</option>7
		<option value="5">vhs</option> */
	    $product->setMediaFormat(5);  //VHS video
            $product->setLanguage(9); //English
	    $product->setSku(ereg_replace("\n","","videoTest2.1-vhs-english"));
	    $product->setName(ereg_replace("\n","","videoTest2.1"));
	    $product->setDescription("videoTest2");
	    $product->setInDepth("video test");    
	    $product->setPrice("129.95");
	    $product->setShortDescription(ereg_replace("\n","","videoTest2.1"));
	    $product->setWeight(0);
	    $product->setStatus(1); //enabled
	    $product->setVisibility(1); //nowhere
	    $product->setMetaDescription(ereg_replace("\n","","videoTest2.1"));
	    $product->setMetaTitle(ereg_replace("\n","","videotest2"));
	    $product->setMetaKeywords("video test");
	    try{
	    	$product->save();
                $productId = $product->getId();
	    	echo $product->getId() . ", $price, $itemNum added\n";
	    }
	    catch (Exception $e){ 		
	    	echo "$price, $itemNum not added\n";
		echo "exception:$e";
	    } 
 
            //create vhs spanish product
	    $product = Mage::getModel('catalog/product');
	    $product->setTypeId('simple');
	    $product->setTaxClassId(0); //none
	    $product->setWebsiteIds(array(1));  // store id
	    $product->setAttributeSetId(26); //Videos Attribute Set
	/*	<option value="6">dvd</option>7
		<option value="5">vhs</option> */
	    $product->setMediaFormat(5);  //DVD video
            $product->setLanguage(8); //Spanish
	    $product->setSku(ereg_replace("\n","","videoTest2.1-vhs-spanish"));
	    $product->setName(ereg_replace("\n","","videoTest2.1"));
	    $product->setDescription("videoTest2");
	    $product->setInDepth("video test");    
	    $product->setPrice("129.95");
	    $product->setShortDescription(ereg_replace("\n","","videoTest2.1"));
	    $product->setWeight(0);
	    $product->setStatus(1); //enabled
	    $product->setVisibility(1); //nowhere
	    $product->setMetaDescription(ereg_replace("\n","","videoTest2.1"));
	    $product->setMetaTitle(ereg_replace("\n","","videotest2"));
	    $product->setMetaKeywords("video test");
	    try{
	    	$product->save();
                $productId = $product->getId();
	    	echo $product->getId() . ", $price, $itemNum added\n";
	    }
	    catch (Exception $e){ 		
	    	echo "$price, $itemNum not added\n";
		echo "exception:$e";
	    } 
 
?>

Once you’ve created each of the simple products, you can create the Configurable Product. Configruable Products have two special variables that you must set: Configurable Products Data and Configurable Attributes Data. Configurable Products Data is an array containing the simple products, along with their configurable attribute data and pricing information, that are associated with the configurable product. The JSON “object” that is passed via the admin interface looks like:


{"5792": [{"label": "dvd", "attribute_id": "491", "value_index": "6", "is_percent": 0, "pricing_value"
: ""}, {"label": "Endlish", "attribute_id": "500", "value_index": "9", "is_percent": 0, "pricing_value"
: ""}], "5807": [{"label": "dvd", "attribute_id": "491", "value_index": "6"}, {"label": "Spanish", "attribute_id"
: "500", "value_index": "8", "is_percent": 0, "pricing_value": ""}], "5791": [{"label": "vhs", "attribute_id"
: "491", "value_index": "5", "is_percent": 0, "pricing_value": ""}, {"label": "Endlish", "attribute_id"
: "500", "value_index": "9"}], "5808": [{"label": "vhs", "attribute_id": "491", "value_index": "5"},
{"label": "Spanish", "attribute_id": "500", "value_index": "8"}]}

which translates to this in PHP:

$data = array(
              '5791'=>array('0'=>
                        array('attribute_id'=>'491','label'=>'vhs','value_index'=>'5','is_percent'=>0,'pricing_value'=>''),
                      '1'=>
                        array('attribute_id'=>'500','label'=>'English','value_index'=>'9','is_percent'=>0,'pricing_value'=>'')
                ),
               '5792'=>array('0'=>
                        array('attribute_id'=>'491','label'=>'dvd','value_index'=>'6','is_percent'=>0,'pricing_value'=>''),
                       '1'=>
                         array('attribute_id'=>'500','label'=>'English','value_index'=>'9','is_percent'=>0,'pricing_value'=>'')
                ),
               '5807'=>array('0'=>
                        array('attribute_id'=>'491','label'=>'dvd','value_index'=>'6','is_percent'=>0,'pricing_value'=>''),
                       '1'=>
                         array('attribute_id'=>'500','label'=>'Spanish','value_index'=>'8','is_percent'=>0,'pricing_value'=>'')
                ),
               '5808'=>array('0'=>
                        array('attribute_id'=>'491','label'=>'vhs','value_index'=>'6','is_percent'=>0,'pricing_value'=>''),
                       '1'=>
                         array('attribute_id'=>'500','label'=>'Spanish','value_index'=>'8','is_percent'=>0,'pricing_value'=>'')
                )
);

The is_percent and pricing_value options allow you to set different prices for each customization. So if you wanted the DVD’s to cost $5 more than the default, just set the pricing_value to 5. Or if you prefer to do a percent of the default cost, set the is_percent parameter to 1 and enter a percent (0 to 100) for the pricing_value.

The Configurable Attributes Data is another array that defines which of the product attributes are configurable. It takes this form:

$data = array('0'=>array('id'=>NULL,'label'=>'Media Format','position'=> NULL,
                   'values'=>array('0'=>
                                            array('value_index'=>5,'label'=>'vhs','is_percent'=>0,
                                                    'pricing_value'=>'0','attribute_id'=>'491'),
                                        '1'=>
                                            array('value_index'=>6,'label'=>'dvd',
			                            'is_percent'=>0,'pricing_value'=>'0','attribute_id'=>'491')
		    ),
                    'attribute_id'=>491,'attribute_code'=>'media_format','frontend_label'=>'Media Format',
		    'html_id'=>'config_super_product__attribute_0'),
             '1'=>array('id'=>NULL,'label'=>'Language','position'=> NULL,
                   'values'=>array('0'=>
                                            array('value_index'=>8,'label'=>'Spanish','is_percent'=>0,
                                                    'pricing_value'=>'0','attribute_id'=>'500'),
                                        '1'=>
                                            array('value_index'=>9,'label'=>'English',
			                            'is_percent'=>0,'pricing_value'=>'0','attribute_id'=>'500')
		    ),
                    'attribute_id'=>500,'attribute_code'=>'media_format','frontend_label'=>'Language',
		    'html_id'=>'config_super_product__attribute_1')
        );

Putting it all together:

<?php
define('MAGENTO', realpath('/var/www/magento'));
ini_set('memory_limit', '128M');
 
require_once MAGENTO . '/app/Mage.php';
 
Mage::app();
            //create dvd english product
	    $product = Mage::getModel('catalog/product');
	    $product->setTypeId('configurable');
	    $product->setTaxClassId(0); //none
	    $product->setWebsiteIds(array(1));  // store id
	    $product->setAttributeSetId(26); //Videos Attribute Set
	    $product->setSku(ereg_replace("\n","","videoTest2.2"));
	    $product->setName(ereg_replace("\n","","videoTest2.2"));
	    $product->setDescription("videoTest2.2");
	    $product->setInDepth("video test");    
	    $product->setPrice("129.95");
	    $product->setShortDescription(ereg_replace("\n","","videoTest2.2"));
	    $product->setWeight(0);
	    $product->setStatus(1); //enabled
	    $product->setVisibility(4); //catalog and search
	    $product->setMetaDescription(ereg_replace("\n","","videoTest2.2"));
	    $product->setMetaTitle(ereg_replace("\n","","videotest2.2"));
	    $product->setMetaKeywords("video test");
           $data = array('5791'=>
               array('0'=>
                        array('attribute_id'=>'491','label'=>'vhs','value_index'=>'5','is_percent'=>0,'pricing_value'=>''),
                      '1'=>
                        array('attribute_id'=>'500','label'=>'English','value_index'=>'9','is_percent'=>0,'pricing_value'=>'')
                ),
               '5792'=>array('0'=>
                        array('attribute_id'=>'491','label'=>'dvd','value_index'=>'6','is_percent'=>0,'pricing_value'=>''),
                       '1'=>
                         array('attribute_id'=>'500','label'=>'English','value_index'=>'9','is_percent'=>0,'pricing_value'=>'')
                ),
               '5807'=>array('0'=>
                        array('attribute_id'=>'491','label'=>'dvd','value_index'=>'6','is_percent'=>0,'pricing_value'=>''),
                       '1'=>
                         array('attribute_id'=>'500','label'=>'Spanish','value_index'=>'8','is_percent'=>0,'pricing_value'=>'')
                ),
               '5808'=>array('0'=>
                        array('attribute_id'=>'491','label'=>'vhs','value_index'=>'6','is_percent'=>0,'pricing_value'=>''),
                       '1'=>
                         array('attribute_id'=>'500','label'=>'Spanish','value_index'=>'8','is_percent'=>0,'pricing_value'=>'')
                )
            );
	    $product->setConfigurableProductsData($data);
            $data = array('0'=>array('id'=>NULL,'label'=>'Media Format','position'=> NULL,
                   'values'=>array('0'=>
                                            array('value_index'=>5,'label'=>'vhs','is_percent'=>0,
                                                    'pricing_value'=>'0','attribute_id'=>'491'),
                                        '1'=>
                                            array('value_index'=>6,'label'=>'dvd',
			                            'is_percent'=>0,'pricing_value'=>'0','attribute_id'=>'491')
		    ),
                    'attribute_id'=>491,'attribute_code'=>'media_format','frontend_label'=>'Media Format',
		    'html_id'=>'config_super_product__attribute_0'),
                   '1'=>array('id'=>NULL,'label'=>'Language','position'=> NULL,
                   'values'=>array('0'=>
                                            array('value_index'=>8,'label'=>'Spanish','is_percent'=>0,
                                                    'pricing_value'=>'0','attribute_id'=>'500'),
                                        '1'=>
                                            array('value_index'=>9,'label'=>'English',
			                            'is_percent'=>0,'pricing_value'=>'0','attribute_id'=>'500')
		    ),
                    'attribute_id'=>500,'attribute_code'=>'media_format','frontend_label'=>'Language',
		    'html_id'=>'config_super_product__attribute_1')
           );
	    $product->setConfigurableAttributesData($data);
	    $product->setCanSaveConfigurableAttributes(1);
 
	    try{
	    	$product->save();
                $productId = $product->getId();
	    	echo $product->getId() . ", $price, $itemNum added\n";
	    }
	    catch (Exception $e){ 		
	    	echo "$price, $itemNum not added\n";
		echo "exception:$e";
	    } 
?>

I came up with my attribute_ids, values, etc by viewing the source of the admin interface. I’m sure there’s a way to use the attribute model to get the values but since I manually create the attributes and attribute sets, I haven’t bothered.

Posted in magento.

Tagged with .


Fixing Search in Magento 1.3.2.1

After upgrading to Magento 1.3.2.1, the search feature stopped working. I rebuilt the indexes but still no search results. I traced through the code and found that the new version filters out uncategorized products (products not assigned to a category). To revert to the old functionality modify the addVisibleInSearchFilterToCollection function in app/code/core/Mage/Catalog/Model/Product/Visibility.php (around line 80):

public function addVisibleInSearchFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection)
    {
        //$collection->setVisibility($this->getVisibleInSearchIds());
        //$collection->addAttributeToFilter('visibility', array('in'=>$this->getVisibleInSearchIds()));
        return $this;
    }

Posted in magento.

Tagged with , .


Adding Links to Magento

My client processes a lot of orders over the phone and wanted to save a little time in the order creation process in Magento. We decided to add a sub-menu under the Sales/Orders menu – it only saves one click but when you’re doing a lot of orders, that adds up.

So how do you modify the Magento Admin menus? The content of the menus are controlled by various config.xml files. If you open app/code/core/Mage/Sales/etc/config.xml and look for the adminhtml tag, you’ll see (note: I’ve only included the xml relevant for the menu):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    <adminhtml>
        <menu>
             <sales translate="title" module="sales">
                <title>Sales</title>
                <sort_order>20</sort_order>
                <depends><module>Mage_Sales</module></depends>
                <children>
                    <order translate="title" module="sales">
                        <title>Orders</title>
                        <action>adminhtml/sales_order</action>
                        <sort_order>10</sort_order>
                    </order>
                    <invoice translate="title" module="sales">
                        <title>Invoices</title>
                        <action>adminhtml/sales_invoice</action>
                        <sort_order>20</sort_order>
                    </invoice>
                    <shipment translate="title" module="sales">
                        <title>Shipments</title>
                        <action>adminhtml/sales_shipment</action>
                        <sort_order>30</sort_order>
                    </shipment>
                    <creditmemo translate="title" module="sales">
                        <title>Credit Memos</title>
                        <action>adminhtml/sales_creditmemo</action>
                        <sort_order>40</sort_order>
                    </creditmemo>
                </children>
             </sales>
        </menu>
</adminhtml>

As you can see, the xml defines the menu items for Sales. Now my goal is to take the Orders item and add two children: View Orders and New Order. Now I could add the children directly to the app/code/core/Mage/Sales/etc/config.xml file but then when I upgrade Magento, my changes would be overwritten

Instead we’ll create a custom module to define the new children.

The first step in creating a custom module is to create the directory structure. Under app/code/local create a directory for you namespace; I used ULR. Under that, create a Sales directory since we’re going to be ovrriding the default Mage/Sales functionality. Finally create an etc directory under that. So you should have something like: app/code/local/[your_name_space]/Sales/etc.

In the etc directory, create a config.xml file with these contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0"?>
<config>
    <modules>
        <ULR_Sales>
            <version>0.1.0</version>
        </ULR_Sales>
    </modules>
	<adminhtml>
  	  <menu>
             <sales>
                <children>
                    <order>
                        <children>
                            <orderView translate="title" module="sales">
                                <title>View Orders</title>
                                <action>adminhtml/sales_order</action>
                                <sort_order>1</sort_order>
                            </orderView>
                            <orderNew translate="title" module="sales">
	                     <title>New Order</title>
	                     <action>adminhtml/sales_order_create/index</action>
	                     <sort_order>2</sort_order>
                            </orderNew>
                         </children>
         	   </order>
               </children>
           </sales>
        </menu>
     </adminhtml> 
</config>

The modules section defines the name of this module – we’ll use its value in a minute. As you might guess, the adminhtml section defines the new menus; we are telling Magento to “merge” our custom config.xml with the default Sales config.xml. Magento actually overrides its default config with our config.

The last step in getting our modifications to work is to create a file defining our module in app/etc/modules; call it ULR.xml (use you namespace for the filename). Place this code into it:

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<config>
    <modules>
        <ULR_Sales>
            <active>true</active>
            <codePool>local</codePool>
        </ULR_Sales>
    </modules>
</config>

Notice that the “name” of the module (ULR_Sales) matches the name of the module in the config.xml config section.

That should do it. Now when you mouse over the Sales menu, you will see that Orders has a sub-menu with “View Orders” and “New Order”.

Posted in magento.

Tagged with , .


Migrating to Magento

Recently, I’ve been helping a friend migrate is web “store” from a collection of static html pages (2000+) to magento.  The old store worked but wasn’t really functional.  Everything was hardcoded and there was no shopping cart.  After doing some research, I decided to give magento a try.  This meant, we had to migrate the existing static html pages, both “product” pages and “expository” pages.

I started by writing a perl script to parse the pages and pull out the relevant product data.  The original pages were done in one of the WYSIWYG editors, so there was a somewhat standard format.  I used the perl IdentityParse package (HTML::Parser) to parse and, where necessary, preserve the original html files. I was able to pull out ~750 products, with their attributes (e.g. price, description, sku, etc) and in some places, the images associated with the product.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#!/usr/bin/perl -w
use strict;
no warnings "all";
package IdentityParse;
use base "HTML::Parser";
 
my $plainContent;
my $description;
my $format="";
my $price="";
my $itemNum="";
my $length="";
my $author="";
my $allLines = "";
my $inMainTable = 0;
my $inSecondaryTable=0;
my $inHeader=0;
my $inUL=0;
my $navTrailText = "";
my %categoryHierarcy;
 
sub trim($)
{
	my $string = shift;
	$string =~ s/^\s+//;
	$string =~ s/\s+$//;
	return $string;
}
 
 
sub text {
	my ($self, $text) = @_;
	# just print out the original text
	$text =~ s/\s{2,}/ /g;
	$text =~ tr/"/""/;
	$plainContent.= $text;
}
 
sub start {
	my ($self, $tag, $attr, $attrseq, $origtext) = @_;
 
	if ($tag =~ m/^table$/i && $attr->{'width'} =~ m/^583$/i) {
		$inMainTable=1;
	} 
	elsif ($tag =~ m/^table$/i){
		$inSecondaryTable=1;
		$plainContent.=$origtext;
	}
	elsif ($tag =~ m/^tr$/i && $inMainTable) {
		if ($inSecondaryTable){
			$plainContent.=$origtext;
		}
 
	}
	elsif ($tag =~ m/^td$/i && $inMainTable) {
		if ($inSecondaryTable){
			$plainContent.=$origtext;
		}
	}
	elsif ($tag =~m/^h1$/i && $attr->{'class'} =~ m/^hdrPage$/i){
		$inHeader = 1;
		$plainContent.= "<div class=\"page-head\"><h3>";
	}
	elsif ($tag =~m/^p$/i && $attr->{'class'} =~ m/^hdrPage$/i){
		$inHeader = 1;
		$plainContent.= "<div class=\"page-head\"><h3>";
	}
	elsif ($tag=~m/^img$/i && $attr->{'src'} =~ m/spacer\.gif$/){
 
	}
	elsif ($tag=~m/^img$/i){
		my $imagePath = $attr->{'src'};
		my @imagePathParts = split(/\//,$imagePath);
		my $partsLen = @imagePathParts;
		my $filename = $imagePathParts[$partsLen-1];
		$plainContent.="<img src=\"/skin/frontend/4ulr/blue/images/$filename\" align=\"right\">";
	}
	elsif ($tag=~/^ul$/i) {
		$inUL=1;
		$plainContent.="<ul class=\"disc\">";
	}
	else{
		$plainContent.=$origtext;
	}
 
}
sub end {
	my ($self, $tag, $attr, $attrseq, $origtext) = @_;
 
	if ($tag =~ m/^table$/i && $inMainTable) {
		if ($inSecondaryTable){
			$inSecondaryTable=0;
			$plainContent.=$attr;
		}
		else {
			$inMainTable=0;
		}
	}
	elsif ($tag =~ m/^tr$/i && $inMainTable) {
		if($inSecondaryTable){
			$plainContent.=$attr;
		}
	}
	elsif ($tag =~ m/^td$/i && $inMainTable) {
		if($inSecondaryTable){
			$plainContent.=$attr;
		}
	}
	elsif($tag =~m/^h1$/i && $inHeader){
		$inHeader=0;
		$plainContent.="</h3></div><div class=\"content\">";
	}
	elsif($tag =~m/^p$/i && $inHeader){
		$inHeader=0;
		$plainContent.="</h3></div><div class=\"content\">";
	}
	elsif ($tag=~/^ul$/i && $inUL==1) {
		$plainContent.=$attr;
	}
	else{
		$plainContent.=$attr;
	}
 
}
 
 
package main;
my $p = new IdentityParse;
my $categories={};
my @files = get_htmls("/home/juice/4ulr/parsing/products/counseling/");
open(OUTPUT, ">counselinglistHTML.csv");
print OUTPUT "filename\tbodyContent\n";
 
 
foreach my $file (@files){
	print "$file\n";
	$plainContent="";
	$description="";
	$allLines="";
	$format="";
	$price="";
	$itemNum="";
	$length="";
	$author="";
 
	open(MYINPUTFILE, "<$file"); # open for input
	my(@lines) = <MYINPUTFILE>; # read file into list
	my $title="";
	my $content="";
	my $contentStart=0;
	my @contentLines;
	chomp(@lines);
	foreach my $line (@lines){
	    chomp($line);
	    $line=~ s/\r//g;
		$allLines .= $line;
		if ($line =~ m/<title>(.*)<\/title>/i){
			$title = $1;
			$title =~ tr/"/""/;
			$title = trim($title);
		}
 
		if ($line =~ m/<!---------- tbl main content ---------->/i){
			$contentStart = 1;
		}
		if ($contentStart){
			push(@contentLines, $line);
		}
		if ($line =~ m/<!-------- \/ tbl main content ---------->/i){
			$contentStart = 0;
		}
	}
	print "\n";
 
	foreach my $contentLine (@contentLines){
		$content.=$contentLine;
	}
	#parse to get content
	$plainContent="";
	$p->parse($content);
	my $vanillaContent = $plainContent;
   	$vanillaContent=~s/\s{2,}/ /g;
	$vanillaContent.="</div>";
   	#print $plainContent;
	print "title = $title\n";
#	print "description = $plainDescription\n";
#	print "bodyContent = $vanillaContent\n";
#	print "format = $format\n";
#	print "price = $price\n";
#	print "itemNum = $itemNum\n";
#	print "length = $length\n";
#	print "author = $author\n";
	$p->eof;
	$navTrailText="";
	print OUTPUT "$file\t$vanillaContent\n";
	close MYINPUTFILE;
}
 
sub trim($)
{
	my $string = shift;
	$string =~ s/^\s+//;
	$string =~ s/\s+$//;
	return $string;
}
 
sub get_htmls {
    my $path    = shift;
 
    opendir (DIR, $path)
        or die "Unable to open $path: $!";
 
    my @files =
        map { $path . '/' . $_ }
        grep { !/^\.{1,2}$/ }
        readdir (DIR);
 
    # Rather than using a for() loop, we can just
    # return a directly filtered list.
    return
        grep { (/\.html*$/) && (! -l $_) }
        map { -d $_ ? get_htmls ($_) : $_ }
        @files;
}

I then started using Magento’s web service to load the data.  Unfortunatley it was slow and I couldn’t get some of the custom attributes to load.  I found some relevant posts in the magento forums, and switched over to php script using the magento classes to load the data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/usr/bin/php
<?php
define('MAGENTO', realpath('/var/www/magento'));
ini_set('memory_limit', '128M');
 
require_once MAGENTO . '/app/Mage.php';
 
Mage::app();
 
//$ourFileName = "listshort.csv"
$ourFileName = "Catalog_short.csv";
$fh = fopen($ourFileName, 'r') or die("Can't open file");
while (!feof($fh)) {
/*	$theData = fgets($fh);
	$theData = ereg_replace('"',"",$theData);
	echo($theData);
	list($filename,$title,$imageName, $keywords, $description,$bodyContent,$format,$price,$itemNum,$length,$author, $catHier) = split("\t", $theData);
	preg_match('@/home/juice/4ulr/parsing/products(\/.*)\.htm[l]?@', $filename, $matches);
	$urlKey = $matches[1];*/
	list($title,$description, $bodyContent, $format, $length,$author,$itemNum, $price,$rental,$keywords,$drop) = fgetcsv($fh);
 
	$price = ereg_replace("[^0-9.]","",$price);
	$imageSet=false;
/*	if (strlen($imageName)){
		$imageName = "/var/www/magento/media/catalog/product" . $imageName;
		$imageSet = true;
	}*/
	if (strlen($itemNum) > 0 && strlen($price) > 0){
//		echo "$imageName\n";
 
	    $product = Mage::getModel('catalog/product');
	    $product->setTypeId('simple');
	    $product->setTaxClassId(0); //none
	    $product->setWebsiteIds(array(1));  // store id
	    $product->setAttributeSetId(4);
//	    $product->setMediaFormat(6);
//	    $product->setMetaCategory(array(3,4));
	    $product->setSku(ereg_replace("\n","",$itemNum));
	    $product->setName(ereg_replace("\n","",$title));
	    $product->setDescription($bodyContent);
	    $product->setInDepth($bodyContent);    
	    $product->setPrice($price);
	    $product->setShortDescription(ereg_replace("\n","",$description));
	    $product->setWeight(0);
	    $product->setStatus(1);
	    $product->setVisibilty(4);
	    $product->setMetaDescription(ereg_replace("\n","",$description));
	    $product->setMetaTitle(ereg_replace("\n","",$title));
	    $product->setMetaKeywords($keywords);
//	    $product->setUrlKey($urlKey);
	    $visibility = array (
	            'thumbnail',
	            'small_image',
	            'image'
	    );
	    if ($imageSet){
	    	try {
	    		$product->addImageToMediaGallery($imageName,$visibility,false, false);
	    	}
	    	catch (Exception $e) {echo "no image\n";}
	    }     
	    try{
	    	$product->save();
	    	echo "$price, $itemNum added\n";
	    }
	    catch (Exception $e){ 		
	    	echo "$price, $itemNum not added\n";
	    } 
 
 
	    $stockItem = Mage::getModel('cataloginventory/stock_item');
	    $stockItem->loadByProduct($product->getId());
	    //var_dump($stockItem);
 
	    if (!$stockItem->getId()) {
	        $stockItem->setProductId($product->getId())->setStockId(1);
	    }
	    $stockItem->setData('qty', 0);
	    $stockItem->setData('is_in_stock', 1);
 
	    $stockItem->save();
	}
	else {
		echo "$price, $itemNum not added\n";
	}
} 
 
?>

I then went back and began parsing the “expository” pages in the site.  Again using the magento classes I was able to create “CMS pages”.  Now I just need to change all the links so that they work 8).

Posted in magento.

Tagged with , , .


Bare with Me

I’m new to blogging so bare with me. Many call me juice, thus the name of the blog. I’m an internet specialist, having been involved with web development since 1994. Everything from cgi via c, to perl, to J2EE, flex, etc. Currenlty, I’m working with GDSs, reservation systems, magento, php (cake) and data migration.

Posted in Rambling and Musings.

Tagged with , .