Zend isUploaded with multidimensional POST

Tags: ,

While developing TypeCMS I came across a difficulty with Zend_File_Transfer_Adapter_Http::isUploaded, multidimensional POST data and multiple file fields. The difficulty is in the parameter you give to isUploaded.

Say for example we have this form:

<form method="post" enctype="multipart/form-data">
    <input type="file" name="typecms[file1]" />
    <input type="file" name="typecms[file2]" />
</form>

If you would choose files for both file fields then you can check whether or not it uploaded using the following:

$adapter = new Zend_File_Transfer_Adapter_Http();
if ($adapter->isUploaded('typecms')) {
    echo 'Uploaded!';
}

This would detect if both file fields were filled, but when you would only use file1 the above would not echo "Uploaded!" because file2 is empty.

To only check if file1 is uploaded you do the following:

$adapter = new Zend_File_Transfer_Adapter_Http();
if ($adapter->isUploaded('typecms_file1_')) {
    echo 'Uploaded!';
}

This will check if file1 is uploaded and echo "Uploaded!".

Read the full post | Comments

The challenge of creating a product configurator in Magento

Tags: ,

In my Magento career I have had to build a product configurator twice, it's a very challenging concept with a lot of things to think about before jumping into it. With this post I would like to tell you how I ended up building them and what kind of issues I came across and how I solved those issues.

Case 1

My first case was for a scooter webshop, the idea was to create your own scooter with your own colors and accessories, this being our first configurator we quickly took the obvious way of using custom options. I'll try to explain the way we handled this in a nutshell. There were several parts of the scooter you could give separate colors and there were certain dependencies which would need to be handled.

The deadline unfortunately forced us to create a php array (vs creating a nice UI) which had all these dependencies defined. On the frontend we changed the way Magento outputs the custom options and used jQuery to shape and structure the HTML. We slugged the labels of all the options and used them as a reference for the php array. I ended up writing some complex javascript with probably more nested "for" loops than anyone should ever have. The slugged labels were also used to get the right image from the filesystem, when selecting a different color we would stack the image on top of the other layers creating a live preview.

The problem with this method

Aside from the slightly bad implementation we did, here's what's wrong with using this method:

  1. The custom options are rendered in a block and are quite challenging to change, this leaves us with little flexibility unless we rewrite the entire block.
  2. There was no way we could keep track of stock, luckily this wasn't necessary but would have been good for the future.
  3. If we had made the nice UI then we would have had to work it into the custom options, them being dynamically done with javascript looks like a challenge.
  4. Without creating some complicated piece of UI, I couldn't imagine getting the dependencies manageable.
  5. You can't freely pick your price for every combination.

Case 2

Here's a case I'm quite proud of how it turned out. This one is for a lights shop, the idea was to be able to assemble lights by choosing between fixtures, lampshade shapes, colors and so forth. Knowing the difficulties we had with using custom options we initially decided to go down the configurable product road. First things first I created an import script which would import an excel which had all the dependencies in it. This import gave me a lot of flexibility throughout the project.

There were several different types of lights: floor lights, ceiling lights, table lights, etc. Each type of light was a configurable product, then I would calculate every single possible combination and created a simple product of it. The combinations were defined in its attributes which were needed to create the configurable product with the right options. I ended up having configurable products which had about 4000 simple products.

You might be surprised to hear this but Magento doesn't like it when you have that many simple products connected to a configurable product. The product view page took about 12 seconds to load (no caching) and likewise for the backend product edit page. You can do all the optimization you want but you should realize that at this point it's maybe better to take a different path. In real-time we actually discovered this after I had written most of the javascript (I tested with smaller products) to handle the frontend.

We quickly switched over to creating our own product type, making it as light as possible and only get what we need. I called it the "Configurator Product" (the module is called Configurator). I chose to stay away from creating any "tight" relations between the simple products and the configurator product so I thought I'd use a code which would identify the configurator product it belongs to. The so called collection code was the key to connect the simple products to the configurator product and vice versa. I made it possible to select the attributes that were supposed to show up as steps on the frontend. Now I was in full control of the amount of information I would have to retrieve and process on my product view page.

I handled the dependencies by getting every option of every attribute and storing all the product ids that have that option in a data attribute, I was able to execute these queries quickly thanks to MySQL's wonderful GROUP_CONCAT. The only thing stopping me from using it were limits that were set by configuration. Making sure these limits were increased before I would do the query resolved this issue (and I hope to never hit the limit again...). So GROUP_CONCAT enabled me to get all the product ids for that option instead of having to go through ~2000 rows multiple times.

Now that every option had a data attribute containing the product ids, all I had to do in my javascript was to filter the product ids to find the possible combinations. It's a bit complex to explain but it would come down to a script going through all the options and filtering the product ids to check if it would be left with one product id (there's only 1 product for every combination).

Every time the user would advance a step I would create an ajax request for every option in that step to retrieve the image and price that were used by that simple product. It's better to ajax the images and price because if Magento would have to load the products and resize a couple thousand images on one page it would take ages and probably crash. This way I have the load spread across the process and I keep my page load at around a second or two without caching (decent for a configurator with 4000 combinations if you ask me).

At the end of the steps I would setup the add to cart button to add the finally simple product to the cart and the default Magento workflow applies.

Case 1 vs Case 2

Here's what case 2's method does better than case 1's:

  1. Stock management is possible.
  2. Images are uploaded on the simple product, no more "slug this, concatanate that and hope it exists".
  3. Full control over my template making it easier to set everything up for my javascript.
  4. I can define prices for every combination possible.
  5. Dependencies are not defined on javascript level anymore, they are now defined by the simple products you have.
  6. I created a platform which I can extend easily because I built it from scratch.
  7. Using events in my javascript it was easy to extend that too. I ended up having a Configurator object which could give me all the information I needed to customize things.
  8. Special prices, catalog and shopping cart price rules apply for every individual combination.

Pitfalls

The most important thing is to make sure you know the requirements, it's the small details that can force you to change your entire plan. I can't stress this enough, I've done far too much refactoring because I didn't foresee some of the details. Luckily this refactoring was easily done because I had a flexible platform to work with.

Conclusion

Product configurators in Magento are challenging and fun if you are given the time to work on it. I don't think there's one method that works for all situations but I do believe that what I have now is very reusable for future projects! I am looking forward to case 3!

I hope I explained it well enough, it's hard putting such a huge process into a couple words and I have skipped many things but I hope I covered the important parts. Feel free to ask me anything!

Read the full post | Comments

Different settings for dev and live in your htaccess

Tags: , , , , , ,

A couple months ago I set myself the challenge of finding a method that would allow for a solid way of checking whether or not the current server was live or production.

I wanted the method to be as independent as possible and I wanted to be able to use it in my code and in my .htaccess

The method includes changing a file on your development server called /etc/apache2/envvars or /etc/sysconfig/httpd, that's it... I promise! This is particularly useful when you are running a development server (perhaps with dynamic vhosts) and want certain RewriteRules to not be applied in your development environment.

Ubuntu

Open /etc/apache2/envvars on your development server in your favorite editor and add the following line to the bottom of the file and restart apache:

export APACHE_ARGUMENTS="-D dev"

CentOS

Open /etc/sysconfig/httpd on your development server in your favorite editor and add or change the following line and restart httpd:

OPTIONS="-D dev"

Now you can use any of the following methods to determine whether you are on your development server or live server.

.htaccess:

<IfDefine !dev>
RewriteCond %{HTTPS} !=on
   RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfDefine>

The above checks if it's not the development server and then forces HTTPS.

CentOS

Add this to your .htaccess as well to get the PHP method underneath to work on CentOS

<IfDefine dev>
    SetEnv APACHE_ARGUMENTS "-D dev"
</IfDefine>

PHP (through apache):

define('DEV', stristr(getenv('APACHE_ARGUMENTS'), '-D dev') !== false);

I've made a ZF2 module to do the above AND allow for a dev.php config file to be loaded, overwriting all other settings when on development server.

https://github.com/thewebmen/TwmDev

And there you go, if you have any suggestions or perhaps different languages to be included in this list feel free to comment, tweet or send me an email.

Read the full post | Comments

ZF2 FlashMessenger view helper

Tags: , , , , ,

The FlashMessenger is a great controller plugin to use in your projects. First time I used it with ZF2 I was looking for a simple way of being able to access the messages from my layout and my view.

After some Googling I was surprised to see that there wasn't a solid view helper out there that did that. So I made this with the bonus of using Twitter Bootstrap classes and the option of retrieving the current messages as well.

File: module/Application/Module.php

class Module {

    /* ... */

    public function getViewHelperConfig()
    {
        return array(
            'factories' => array(
                'flashMessages' => function($sm) {
                    $flashmessenger = $sm->getServiceLocator()
                        ->get('ControllerPluginManager')
                        ->get('flashmessenger');

                    $messages = new \Application\View\Helper\FlashMessages();
                    $messages->setFlashMessenger($flashmessenger);

                    return $messages;
                }
            ),
        );
    }
}

File: module/Application/src/Application/View/Helper/FlashMessages.php

<?php

namespace Application\View\Helper;

use Zend\View\Helper\AbstractHelper;
use Zend\Mvc\Controller\Plugin\FlashMessenger as FlashMessenger;

/**
 * @author Rick <rick@thewebmen.com>
 * @company The Webmen
 */
class FlashMessages extends AbstractHelper
{
    /**
     * @var FlashMessenger
     */
    protected $flashMessenger;

    public function setFlashMessenger(FlashMessenger $flashMessenger)
    {
        $this->flashMessenger = $flashMessenger;
    }

    public function __invoke($includeCurrentMessages = false)
    {
        $messages = array(
            FlashMessenger::NAMESPACE_ERROR => array(),
            FlashMessenger::NAMESPACE_SUCCESS => array(),
            FlashMessenger::NAMESPACE_INFO => array(),
            FlashMessenger::NAMESPACE_DEFAULT => array()
        );

        foreach ($messages as $ns => &$m) {
            $m = $this->flashMessenger->getMessagesFromNamespace($ns);
            if ($includeCurrentMessages) {
                $m = array_merge($m, $this->flashMessenger->getCurrentMessagesFromNamespace($ns));
                $this->flashMessenger->clearCurrentMessagesFromNamespace($ns);
            }
        }

        return $messages;
    }
}

The usage is simple, from your layout or your view you can use this code to get the messages.

<?php foreach ($this->flashMessages() as $namespace => $messages) : ?>
    <?php if (count($messages)) : ?>
        <?php foreach ($messages as $message) : ?>
        <div class="alert alert-<?=$namespace?>">
            <?php echo $message; ?>
        </div>
        <?php endforeach; ?>
    <?php endif; ?>
<?php endforeach; ?>

You can also use $this->flashMessages(true) to include current messages (messages that are added on the same request).

<?php foreach ($this->flashMessages(true) as $namespace => $messages) : ?>

Read the full post | Comments

Magento string manipulation

Tags: , , , , , , ,

It sure came as a surprise to me that Magento had these pretty handy string manipulation function hidden into a helper named core/string.

Apart from having a lot of binary-safe and iconv functions that do the usual substr(), strlen(), etc. It also has powerful truncate(), str_split(), splitInjection() and splitWords() functions.

For your comfort I've made a little showcase of how these functions work an what their output is.

// truncate($string, $length = 80, $etc = '...', &$remainder = '', $breakWords = true)

$remainder = '';
$string = 'Vestibulum lobortis mattis massa. Fusce malesuada mauris -et purus interdum venenatis.';

echo Mage::helper('core/string')->truncate($string, 50, '...', $remainder, true);
// Vestibulum lobortis mattis massa. Fusce malesuada mauris et purus interdum venenatis. Aliquam er...

echo Mage::helper('core/string')->truncate($string, 50, '...', $remainder, false);
// Vestibulum lobortis mattis massa. Fusce malesuada mauris et purus interdum venenatis. Aliquam...

echo $remainder;
// malesuada mauris et purus interdum venenatis.

 

// splitInjection($str, $length = 50, $needle = '-', $insert = ' ')

$string = 'ABCDEFGHIKJLMNOPQRSTUVWXYZ';

echo Mage::helper('core/string')->splitInjection($string, 5, 'G');
// ABCDE FG HIKJLMNO PQRST UVWXY Z 

 

// splitWords($str, $uniqueOnly = false, $maxWordLength = 0, $wordSeparatorRegexp = '\s')

$string = 'I really really like turtles';

var_dump(Mage::helper('core/string')->splitWords($string));
/*
 * array(5) {
 *  [0]=>
 *  string(1) "I"
 *  [1]=>
 *  string(6) "really"
 *  [2]=>
 *  string(6) "really"
 *  [3]=>
 *  string(4) "like"
 *  [4]=>
 *  string(7) "turtles"
 *  }
 */

var_dump(Mage::helper('core/string')->splitWords($string, true));
/*
 * array(4) {
 *  ["I"]=>
 *  string(1) "I"
 *  ["really"]=>
 *  string(6) "really"
 *  ["like"]=>
 *  string(4) "like"
 *  ["turtles"]=>
 *  string(7) "turtles"
 *  }
 */

Read the full post | Comments