Archive for the 'development' Category

Download the source for this entire series here!

Prepare the model

  1. Back to the file structure drawing board
    app/
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                - HelloWorld.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              Model/
                - Tip.php
                Mysql4/
                  - Tip.php
                  Tip/
                    - Collection.php
              sql/
                Twits_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0.php

    Notice the new folder Model and the associated files inside. Lets clarify:
    Model/Tip.php is the logical model that holds code regarding business logic and relations.
    Model/Mysql4/Tip.php is the data implementation model that associates the model with the table and its primary key.
    Model/Mysql4/Tip/Collection.php is the data implementation model of a collection of Tip. Similar to a recordset/resultset in .Net/Java. This will be used with a Grid later.

  2. Even more config.xml magic. Yay! More hairloss!
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twits>
                <version>0.2.0</version>
            </SavantDegrees_Twits>
        </modules>
        <global>
            <models>
                <twits>
                    <class>SavantDegrees_Twits_Model</class>
                    <resourceModel>twits_mysql4</resourceModel>
                </twits>
                <twits_mysql4>
                    <class>SavantDegrees_Twits_Model_Mysql4</class>
                    <entities>
                        <tip>
                            <table>tips</table>
                        </tip>
                    </entities>
                </twits_mysql4>
            </models>
    	<blocks>
                <twits>
                    <class>SavantDegrees_Twits_Block</class>
                </twits>
            </blocks>
             <helpers>
                <twits><class>SavantDegrees_Twits_Helper</class></twits>
            </helpers>
            <resources>
                <twits_setup>
                    <setup>
                        <module>SavantDegrees_Twits</module>
                    </setup>
                    <connection>
                        <use>core_setup</use>
                    </connection>
                </twits_setup>
                <twits_write>
                    <connection>
                        <use>core_write</use>
                    </connection>
                </twits_write>
                <twits_read>
                    <connection>
                        <use>core_read</use>
                    </connection>
                </twits_read>
            </resources>
        </global>
        <adminhtml>
            <menu>
                <twits translate="title" module="twits">
                    <title>Twits</title>
                    <sort_order>100</sort_order>
                    <action>twits/admin</action>
                </twits>
            </menu>
        </adminhtml>
        <frontend>
            <routers>
                <SavantDegrees_Twits>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twits</module>
                        <frontName>twits</frontName>
                    </args>
                </SavantDegrees_Twits>
            </routers>
        </frontend>
    </config>

    The global > models fragment define the tips table, the tip model and its resourceModel that we will be using shortly.

  3. Now for the model/Tip.php code:
    <?php
    class SavantDegrees_Twits_Model_Tip extends Mage_Core_Model_Abstract
    {
     
        protected function _construct()
        {
            $this->_init('twits/tip');
        }
    }

    Whats the point you ask!? The code does nothing but binds the model to the Twits namespace under the model twits. In Java that line would have been package Twits;. *i assume/think/foretold*
    (quickly moving on…)

  4. Then Model/Mysql4/Tip.php
    <?php
    class SavantDegrees_Twits_Model_Mysql4_Tip extends Mage_Core_Model_Mysql4_Abstract
    {
        protected function _construct()
        {
            $this->_init('twits/tip', 'tip_id');
        }
    }

    The code above tells Magento to hunt for the twits application’s twits table in the configuration. Yes i know the names are confusing now. Lets say instead of twits/tip it is twits/foo. In that case the system will find the configuration file associated with twits (which is our app/code/local/SavantDegrees/Twits/etc/config.xml), and look under config > global > models > twits_mysql4 > entities > foo to figure out its configuration instructions. You’ll see next that we’ll request for the table from that same configuration file. Capiche?
    As a resourceModel, the code binds the model to a underlying Mysql4 table.

  5. Lastly, Model/Mysql4/Tip/Collection.php
    <?php
    class SavantDegrees_Twits_Model_Mysql4_Tip_Collection extends Varien_Data_Collection_Db
    {
        protected $_tipTable;
     
        public function __construct()
        {
            $resources = Mage::getSingleton('core/resource');
            parent::__construct($resources->getConnection('twits_read'));
            $this->_tipTable= $resources->getTableName('twits/tip');
     
            $this->_select->from(
            		array('tip'=>$this->_tipTable),
     		       	array('*')
            		);
            $this->setItemObjectClass(Mage::getConfig()->getModelClassName('twits/tip'));
        }
    }

    Basically the model files created at this phase are simply trying to tie the model (singular and collection) to the underlying Mysql resource. The entire mechanism is half tied in the config.xml table declaration, the model individual instructions (from identifying the namespace to actually declaring the primary key of the table)

  6. Viola. Models are ready

» Arent we there yet? Part 5 – CRUD – Retrieve

Download the source for this entire series here!

Prepare the database

Next we’ll create the installation script that installs the twits table into the database.

  1. Once again, we begin with updating the file structure
    app/
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                - HelloWorld.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
              sql/
                twits_setup/
                  - mysql4-install-0.2.0.php
                  - mysql4-upgrade-0.1.0-0.2.0.php

    We simply included a sql folder that will house the eventual table creation sql code.

  2. Next up, updating the config.xml again. This time to add the setup declarations.
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twits>
                <version>0.2.0</version>
            </SavantDegrees_Twits>
        </modules>
        <global>
    	<blocks>
                <twits>
                    <class>SavantDegrees_Twits_Block</class>
                </twits>
            </blocks>
             <helpers>
                <twits><class>SavantDegrees_Twits_Helper</class></twits>
            </helpers>
            <resources>
                <twits_setup>
                    <setup>
                        <module>SavantDegrees_Twits</module>
                    </setup>
                    <connection>
                        <use>core_setup</use>
                    </connection>
                </twits_setup>
                <twits_write>
                    <connection>
                        <use>core_write</use>
                    </connection>
                </twits_write>
                <twits_read>
                    <connection>
                        <use>core_read</use>
                    </connection>
                </twits_read>
            </resources>
        </global>
        <adminhtml>
            <menu>
                <twits translate="title" module="twits">
                    <title>Twits</title>
                    <sort_order>100</sort_order>
                    <action>twits/admin</action>
                </twits>
            </menu>
        </adminhtml>
        <frontend>
            <routers>
                <SavantDegrees_Twits>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twits</module>
                        <frontName>twits</frontName>
                    </args>
                </SavantDegrees_Twits>
            </routers>
        </frontend>
    </config>

    Here we’ve added a global > resources section that tells the setup script the folder for installation. We’ve also changed the version number to 0.2.0. When the page is initiated and Magento detects that the version has been upgraded, it will run the appropriate install script.

  3. Lastly the install script itself: mysql4-install-0.2.0.php
    <?php
    $installer = $this;
    $installer->startSetup();
     
    $installer->run("
    CREATE TABLE {$this->getTable('tips')} (
      `tip_id` int(10) unsigned NOT NULL auto_increment,
      `title` varchar(250) NOT NULL default '',
      `author` varchar(250) default NULL,
      `contents` text,
      PRIMARY KEY  (`tip_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      ");
     
    $installer->endSetup();
  4. Copy the same file contents to mysql4-upgrade-0.1.0-0.2.0.php. Why? because the code above is correct. But since this HOWTO is going through the phases in version 0.1, 0.2, and etc. Magento has already a record of twit_setup in its core_resource table bound to the previous version of 0.1 (its first run). So what we need now is a upgrading script instead of an installer. But if/when you do package your extension, you shouldn’t need the upgrade code. Just install straight to version 0.2 (or higher)
  5. Once again, wake up the Magento installer by logging into the admin backend, System > Configuration > Advanced. Click on Save Config. Clear your cache at /var/cache too just to be sure.
  6. Check to see if your table is created.
  7. Once your table is created, you can delete the mysql4-upgrade-0.1.0-0.2.0.php file. As explained above, you dont need it anymore!

» More Intangibles! … Part 4 – Model

Download the source for this entire series here!

Create the administrative complement

Next we’ll create the administration controller and configuration to achieve the https://127.0.0.1/magento/index.php/twits/admin reservation. This took me a long time to figure out how to do nicely.

  1. We’ll begin by updating the adding 2 new files (controller and helper) for the purpose of the Hello world in the admin site
    app/
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                - HelloWorld.php
              controllers/
                - AdminController.php
                - IndexController.php
              etc/
                - config.xml
              Helper/
                - Data.php
  2. Then we obviously create the adminController.php
    We’ll make the admin page reuse the same Hello world block that we’ve created in Phase 1.

    <?php
    class SavantDegrees_Twits_AdminController extends Mage_Adminhtml_Controller_Action
    {
        public function indexAction()
        {
    		$this->loadLayout()
    			->_addContent($this->getLayout()->createBlock('twits/helloWorld'))
    			->renderLayout();
        }
    }

    Note that this controller extends Mage_Adminhtml_Controller_Action instead. This enforces security and loads the admin layout for this controller instead of the frontend version. With this done, you can now visit https://127.0.0.1/magento/index.php/twits/admin to see the page in action. Again, this produces our ubiquitous Hello world. You’d probably figured out by now that /twits refers to our frontend controller reservation by our config.xml and then /admin refers to the adminController that we’ve just created. Now you can try https://127.0.0.1/magento/index.php/twits/admin/index to see the indexAction method being called. Try it with other action names!

  3. Next we’ll do the config.xml magic that allows us to use https://127.0.0.1/magento/index.php/admin/twits for the administration.
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twits>
                <version>0.1.0</version>
            </SavantDegrees_Twits>
        </modules>
        <global>
             <helpers>
                <twits><class>SavantDegrees_Twits_Helper</class></twits>
            </helpers>
    	<blocks>
                <twits>
                    <class>SavantDegrees_Twits_Block</class>
                </twits>
            </blocks>
        </global>
        <adminhtml>
            <menu>
                <twits translate="title" module="twits">
                    <title>Twits</title>
                    <sort_order>100</sort_order>
                    <action>twits/admin</action>
                </twits>
            </menu>
        </adminhtml>
        <frontend>
            <routers>
                <SavantDegrees_Twits>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twits</module>
                        <frontName>twits</frontName>
                    </args>
                </SavantDegrees_Twits>
            </routers>
        </frontend>
    </config>

    What’ changed?

    • The global > helper > [module] fragment defines the helper class that will help our future translation. We’ll be seeing the code for that shortly.
    • The adminhtml > menu > [module] fragment allows us to create the menu to access the twits/admin page. This is vital for version 1.3 onwards as the menu generates the necessary url key to access the page.
  4. And finally the helper Data.php file
    <?php
    class SavantDegrees_Twits_Helper_Data extends Mage_Core_Helper_Abstract
    {
    }

    Why bother? Its a quirk of Magento to try to translate the menu. So we NEED this helper. Otherwise Magento goes looking for a Mage_Twits_Helper_Data class. Which obviously doesnt exist! You could use the helper from existing Magento classes, but then you could use ALOT of stuff from Magento classes. And if you’re reading this tutorial, you’re obviously arent ready.

  5. When you’re done, go flush the cache by accessing System > Cache Management > Flush Magento Cache in the admin system. Then check out the new Twit menu item in the administration menu bar!

» Moving along … Part 3 – Database

Magento needs no explaning, but it does need a shitload of proper developers education. So much that our team spent a good amount of time trying to learn how to develop on the damn thing. So heres a synthesized description of our efforts. We’re breaking up this HOWTO into a few segments into increasing orders of complexity and integration.

Download the source for this entire series here!

Let us begin …

Setup Module Environment and Helloworld

We’re trying to achieve a proof of concept here to describe how the file structure, namespaces and config files interplay to achieve a Hello world message

  1. Create your directory structure! Our NEW company name is Savant Degrees! and our module shall be called….. Twits (for a lack of an imagination). The whole module (well, almost) should rest in the /app/code/local> folder. So lets first create the following folder structure. For a hello world module, you only need a config file, a controller and a block. Ergo…
    app/
      etc/
        modules/
          - SavantDegrees_All.xml (Or what ever your company name might be)
      code/
        local/
          SavantDegrees/ (Or what ever your company name might be)
            Twits/ (Or whatever your module name might be)
              Block/
                - HelloWorld.php
              controllers/
                - IndexController.php
              etc/
                - config.xml
  2. Create your config.xml file. Thats the crux of the whole module.
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twits>
                <version>0.1.0</version>
            </SavantDegrees_Twits>
        </modules>
        <global>
            <blocks>
                <twits>
                    <class>SavantDegrees_Twits_Block</class>
                </twits>
            </blocks>
        </global>
        <frontend>
            <routers>
                <SavantDegrees_Twits>
                    <use>standard</use>
                    <args>
                        <module>SavantDegrees_Twits</module>
                        <frontName>twits</frontName>
                    </args>
                </SavantDegrees_Twits>
            </routers>
        </frontend>
    </config>

    Let me explain this step a little…

    The modules > [module] > version number allows Magento to track when your module is upgraded. We’ll be seeing a version of that shortly.
    The global > blocks > [module] define the Block resources that are available under your package. We’ll be using this to generate a Hello world shortly
    The frontend > routers fragment define how the module can be access. The current code allows the block to be access via https://127.0.0.1/magento/index.php/twits

  3. Create your IndexController.php file. Its as simple as:
    class SavantDegrees_Twits_IndexController extends Mage_Core_Controller_Front_Action
    {
     
        public function indexAction()
        {
    	$this->loadLayout();
    	$this->getLayout()
    		->getBlock('content')->append(
    			$this->getLayout()->createBlock('twits/helloWorld')
        	);
            $this->renderLayout();
        }
    }

    More Explanation: The twits/index refers to the index block inside the Twits module, which is defined in the config file above. We’re trying to put its contents inside the layout.

  4. Lastly, we’ll create the Index.php Block:
    class SavantDegrees_Twits_Block_HelloWorld extends Mage_Core_Block_Template
    {
    	protected function _toHtml()
    	{
    		return 'Hello world';
    	}
    }

    You DO understand SOME PHP don’t you?

  5. Finally, we activate the module by creating the file SavantDegrees_All.xml file
    <?xml version="1.0"?>
    <config>
        <modules>
            <SavantDegrees_Twits>
                <active>true</active>
                <codePool>local</codePool>
            </SavantDegrees_Twits>
        </modules>
    </config>
  6. Wake up the Magento installer by logging into the admin backend, System > Configuration > Advanced > Advanced. Click on Save Config. Clear your cache at /var/cache too just to be sure.

Tada! Navigate to https://127.0.0.1/magento/index.php/twits to see your well deserved Hello World!

» Next … Part 2 – Admin Controller

Feb 24, 2009

HOWTO: Self-Signed SSL on IIS in Vista

Author: gaweee | Filed under: development, howto

Yes it can be done, no its not hard at all, no you dont have to download any weird sharewares and no it doesnt cost a cent. Lets go!

  1. Download the SelfSSL from microsoft downloads
  2. Install it, duh.
  3. Assuming you installed it where its supposed to be, go to C:\Program Files\IIS Resources\SelfSSL and run selfssl.exe
  4. Agree to it installing the certificate for you and press Y
  5. Open your IIS Manager > Sites
  6. Right click on the Default Web Site and choose Edit Bindings
  7. Add
  8. Select type as https and select the certificate you just installed.
  9. Done

Viola!

This adds the certificate for only a puny 7 days. If you’re like us, you’ll probably need a cert for a much longer period than that. No problem!
Fire up your command prompt and cd to C:\Program Files\IIS Resources\SelfSS\
type the following:

selfssl.exe /V:3650 /t

That’ll give you a nice cert for 10 years.
But wait, you’re original cert is still there! Wel’ll have to remove it.

  1. Start->Run->mmc.exe
  2. File -> Add/Remove Snap-in
  3. Certificates (on the left column)
  4. OK
  5. Navigate the tree to: Certificates -> Personal -> Certificates
  6. Delete the original SSL cert you created (see the expiration date differences)
  7. Follow the above instructions in customizing your IIS to the new cert now. =D

Note: This is used for development purposes and the certificates will be recognized as invalid for that domain. Go ahead and add the security exception in Firefox, IE, Chrome or whatever other browsers you’re using.

Feb 11, 2009

JQuery Progress Bar 1.2

Author: gaweee | Filed under: development, howto
jQuery progressBar screenshot

Thanks for all the comments and feedback! By popular request i’ve added the multi-colored progress bar and fixed some bugs. The new multi-colored bar changes from red to orange and then to green at configurable intervals. Check out the demo!

Since the last update this endeavour has seen comments claiming that it works and sometimes that it doesnt. I’ve tested the demo across Safari, Opera, IE6/7, Firefox and Chrome. Havent found any problem. For those of you who have problems, please do email me the problem.

Download the new jQuery progressbar here: jQuery progressbar
or view the demo here

Feb 8, 2009

HOWTO: Readymade Form CSS and Highlighting

Author: gaweee | Filed under: development, howto
jQuery WITSForm

We’ve been working on web development for awhile now, i’ve really only seen a handful of pretty forms. The interfaces seen at Uni-Form, mooflex and even to a certain extent linuxjournal are all intuitive and well thought out.

So in the good ol WITS culture, we’ve tried our hand at building our own version of a universal form interface. Alot of features were borrowed off work already done at Uni-Form. We just made it jQueried, lighter and more readable. Like all other jQuery plugins, insert jquery.js, insert the jquery.witsform.js script, insert the css, use the right html code, and you’re done! I’m pretty sure as time goes by i’ll keep improving it. So here’s version 1.0 meanwhile. We bothered so that you shouldnt have to.

download the jQuery WITSForm here: jQuery WITSForm
or view the demo here

Feb 8, 2009

HOWTO: IE6 testing environment on Vista

Author: gaweee | Filed under: development, howto

Lets cut through the chase, theres no way to install IE6 on vista but you can boot it off an image (which seems to be the most popular choice anyway), even if other sites teaches you registry hacks and etc, there are still differences between the real IE6 rendering versus the emulated/hacked version. But, what you can do is to install the Virtual PC with IE6 + XP SP3 Virtual Hard Disk.

Once you’ve downloaded both and installed the Virtual PC, run it. Then:
File > New Virtual Machine Wizard

  1. Create a virtual machine
  2. Choose some location of your preference
  3. Choose Windows XP for your operating system
  4. Use the recommended RAM (of if you can spare it, adjust it more or less to your preference)
  5. Use an existing Virtual Hard Disk
  6. Choose the downloaded IE6 + XPSP3 .vhd image
  7. Finish

When ready, click Start on the virtual image and viola!

Note:

  • The .vhd image always expires 3 months as of download, this is to prevent ppl from just living off an image (since i gather the image can be run off other operating systems too)
  • Go to Settings > Networking and choose the active network controller to give the IE6 image networking capabilities
  • If you’ve been developing your website on localhost, then you cannot access http://localhost/project on your IE6 anymore as thats is resolved internally. Instead use your main computer’s IP. In my case, http://192.168.1.1/project

Windows XP booting via Virtual PC  IE6 running on Virtual PC

Feb 7, 2009

HOWTO: PHP image resize, centered and cropped

Author: gaweee | Filed under: development, howto

Sorry for not posting in such a long time folks, i just came back from the Pegasus galaxy. While working on a Joomla! 1.5 project, we had to develop a function to crop and resize images for obvious content management and aesthetic reasons. So i gathered around some good sources on PHP.net and wrote the following class:

class ImageHelper {
 
	static function treatFilename($filename) {
		$newfilename = strtolower($filename);
		$newfilename = str_replace(" ","_",$newfilename);
		return $newfilename;
	}
 
	static function isPotrait($srcimage) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		return !ImageHelper::isLandscape($srcimage);
	}
 
	static function isLandscape($srcimage) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		$size	= getimagesize( $srcimage );
		return ($size[0] > $size[1]);
	}
 
	static function resizeImage($srcimage, $destimage, $width, $height) {
		if (!file_exists(realpath($srcimage)))
			return;
 
		$srcpathinfo 	= pathinfo($srcimage);
		$srcext 		= strtolower($srcpathinfo['extension']);
		$destpathinfo 	= pathinfo($destimage);
		$destext 		= strtolower($destpathinfo['extension']);
		$size 			= getimagesize( $srcimage );				// Get the size of the original image into an array [0]=> width, [1]=> height
		$image			= null;
		$canvas 		= imagecreatetruecolor( $width, $height );	// Prepare canvas
 
		// Create a new image in the memory from the file 
		switch ($srcext) { 
			case 'wbmp':
			case 'bmp':
				$image = imagecreatefromwbmp($srcimage);
				break;
			case 'jpg':
			case 'jpeg':
				$image = imagecreatefromjpeg($srcimage);
				break;
			case 'png':
				$image = imagecreatefrompng($srcimage);
				break;
			case 'gif':
				$image = imagecreatefromgif($srcimage);
				break;
			case 'xpm':
				$image = imagecreatefromxpm($srcimage);
				break;
			default:
				return;
		}
 
		// Calculate dimensions
		$widthratio		= $size[0]/$width;
		$heightratio	= $size[1]/$height;
		$dimensions 	= array(
						'ratio' 			=> $widthratio,
						'source_cropwidth' 	=> 0,
						'source_cropheight' => 0,
						'source_offsetx' 	=> 0,
						'source_offsety' 	=> 0
						);
 
		if ($heightratio < $widthratio)
			$dimensions['ratio'] 			= $heightratio;
 
		// let say image is 1200*800, then:
		// widthratio = 1200/400 = 3
		// heightratio = 800/300 = 2.66
		// since there is less height than width, the max scale we can do is 2.66, then
		// the targetwidth to crop = 400 * 2.66
		// the targetheight to crop = 300 * 2.66
		// the offset width = (1200 - (2.66 * 400))/2 = 68
		// the offset height = (800 - (2.66 * 300))/2 = 1
 
		$dimensions['source_width'] 		= $size[0];
		$dimensions['source_height'] 		= $size[1];
		$dimensions['source_cropwidth'] 	= $width * $dimensions['ratio'];
		$dimensions['source_cropheight'] 	= $height * $dimensions['ratio'];
		$dimensions['source_offsetx'] 		= ($size[0] - $dimensions['source_cropwidth']) / 2;
		$dimensions['source_offsety'] 		= ($size[1] - $dimensions['source_cropheight']) / 2;
 
		imagecopyresampled($canvas, $image, 0, 0, $dimensions['source_offsetx'], $dimensions['source_offsety'], $width, $height, $dimensions['source_cropwidth'], $dimensions['source_cropheight']);
 
		switch ($destext) {
			case 'jpg':
			case 'jpeg':
				imagejpeg( $canvas, $destimage );
				break;
			case 'png':
				imagepng( $canvas, $destimage );
				break;
			case 'gif':
				imagegif( $canvas, $destimage );
				break;
			case 'wbmp':
			case 'bmp':
				imagewbmp( $canvas, $destimage );
				break;
		}
 
		imagedestroy( $canvas );
		imagedestroy( $image );
 
		return true;
	}
}
use treatFilename to remove those pesky spaces, then use isPotrait or isLandscape to determine the orientation of the picture, then perform your resize accordingly. If your uploaded file input was called ulimage, then the code to resize your image would be:

	$width 		= 150;
	$height 		= 150;
	if (is_uploaded_file(@$_FILES['ulimage']['tmp_name'])){
		$targetfilename	= ImageHelper::treatFilename(uniqid() . "_" . $_FILES['ulimage']['name']);
		move_uploaded_file($_FILES['ulimage']['tmp_name'], dirname(__FILE__) . "/tmp/" . $_FILES['ulimage']['name']);
		ImageHelper::resizeImage(dirname(__FILE__) . "/tmp/" . @$_FILES['ulimage']['name'], dirname(__FILE__) . "/tmp/" . $targetfilename, $width, $height);
	}

The reason you need to save the file first is for the imageResize function to infer the image type off its extension. I hadnt bothered to create an image type override. :D

Get the source here: ImageHelper.php
or view the demo here
Jun 25, 2008

HOWTO: PHP and jQuery upload progress bar

Author: gaweee | Filed under: development, howto

With the controllable jQuery Progress Bar, writing a form upload progress bar seems like a piece of cake now. Hypothetically, all we need is to create the bar, poll for the progress of the file upload, derive the new progress bar value (in percentage) and set it.

To do that you need to prepare the php script to do it. By default PHP cant report the progress of upload progress. However people smarter than me have already solved that problem. In 5 mins i’ve found 2 solutions: the Alternative PHP Cache (APC) method as well as the UploadProgress method. Both of them are PECL packages. Because i couldnt get APC to work on my server properly, i’ll document the UploadProgress more in detail here…

Step 1: Install the uploadprogress package. Really simple just run the following command  

pecl install uploadprogress

Once that is done, register the extension to your PHP with the following line in your php.ini

extension=uploadprogress.so

then restart your apache/httpd

Step 2: Create the form and your progress bar  

<form id="uploadform" enctype="multipart/form-data" method="post">
<input id="progress_key" name="UPLOAD_IDENTIFIER" type="hidden" value="&lt;?= $uuid ?&gt;" />
<input id="ulfile" name="ulfile" type="file" />
<input type="submit" value="Upload" />
	<span id="uploadprogressbar" class="progressbar">0%</span>
</form>

this creates the form with a file field as well as a unique UPLOAD_IDENTIFIER hidden field that allows our script to check the progress of the form submission.

Step 3: Next the script itself to check the respond with the progress of the form submission. Lets call this file uploadprogress.php  

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 
if (@$_GET['id']) {
	echo json_encode(uploadprogress_get_info($_GET['id']));
	exit();
}

The header no-cache declarations circumvents IE’s cache of the response. Basically this form does nothing but respond with a json encoded string of the uploadprogress_get_info function. The id argument is the same one we used in the form. Think of it as a form-upload-process-id. A typical response looks like this:

{"time_start":"1214384364","time_last":"1214384366","speed_average":"25889","speed_last":"40952","bytes_uploaded" :"51778","bytes_total":"8125518","files_uploaded":"0","est_sec":"311"}

the response encodes a good deal of data about the form submission. most importantly for us: bytes_uploaded and bytes_total

Step 3: use jQuery and a timer to keep polling the page and update the progress bar value  

var progress_key = '';
 
// this sets up the progress bar
$(document).ready(function() {
	$("#uploadprogressbar").progressBar();
});
 
// fades in the progress bar and starts polling the upload progress after 1.5seconds
function beginUpload() {
	$("#uploadprogressbar").fadeIn();
	setTimeout("showUpload()", 1500);
}
 
// uses ajax to poll the uploadprogress.php page with the id
// deserializes the json string, and computes the percentage (integer)
// update the jQuery progress bar
// sets a timer for the next poll in 750ms
function showUpload() {
	$.get("uploadprogress.php?id=" + progress_key, function(data) {
		if (!data)
			return;
 
		var response;
		eval ("response = " + data);
 
		if (!response)
			return;
 
		var percentage = Math.floor(100 * parseInt(response['bytes_uploaded']) / parseInt(response['bytes_total']));
		$("#uploadprogressbar").progressBar(percentage);
 
	});
	setTimeout("showUpload()", 750);
}

viola! read the comments if you dont understand the code. it is _THAT_ straightforward. Of course there can be many improvements such as stopping the script when the upload reaches 100% but thats probably not really needed since the whole page is refreshed. But this approach allows the flexibility of ajax submissions and what nots.

Again, download the jQuery progressbar here: jQuery progressbar
or view the demo here

Note: for the APC solution seekers out there, assuming you can get APC to work, the solution is not so different. A few changes will get your there:  

  • Change the HTML hidden form field name from UPLOAD_IDENTIFIER to APC_UPLOAD_PROGRESS
  • Change the PHP uploadprogress_get_info($_GET['id']) to apc_fetch(‘upload_’.$_GET['id']));
  • Change the Javascript percentage calculation from:
    Math.floor(100 * parseInt(response['bytes_uploaded']) / parseInt(response['bytes_total']));
    to:
    Math.floor(100 * parseInt(response['current']) / parseInt(response['total']));