This page is still a beta!

Tutorials


4.2. The Humidor cam

The Humidor-cam is a part of the Typo3.com website. Here people can buy cigars which is said to empower the Typo3 development. The humidor-cam shows the contents of the humidor (a special container for cigars). This “cam”-image is not a live picture but rather automatically manipulated - it does of course not show an open Humidor anywhere. But the image is not evenmanipulated in Photoshop - it's in fact manipulated on-the-fly (although cached) by the powerful TypoScript graphics function, calculated by a custom PHP function. The data defining the content comes from a user defined database table. The contents here is connected to the shopping system at another server (courtesy of Inter-Photo.dk).

Thus this case included two challenges: To communicate the purchased amount of cigars from one server to the other  and creating the humidor image based on the database content of purchased cigars.

The whole thing took 6 hours to make one saturday night.

This is a screen shot from the cigar shopping basket. You can pay with VISA, Mastercard, Diners og Dankort:

Part I: The server-to-server communication

At the Typo3 server with the shopping solution (which is not the same as the www.typo3.com server), this was added to the TypoScript template:

includeLibs.ts_products_cigars = fileadmin/cigarLib.inc
plugin.tt_products.externalFinalizing= USER
plugin.tt_products.externalFinalizing{
  userFunc = user_cigars->addCigars
}

This will invoke a method addCigars() from the class user_cigars when the order is finalized and the basket is being emptied! This function must then communicate the contents of the basket to the typo3.com server. This is done with a PHP-function like this:

<?class user_cigars {    function addCigars($content,$conf)    {            // Initializes the basket from the session data        $this->cObj->regObj->initBasket($GLOBALS["TSFE"]->fe_user->getKey("ses","recs"));            // Internal accumulation in this array:        $basket =array();        if (is_array($this->cObj->regObj->calculatedBasket))    {            reset($this->cObj->regObj->calculatedBasket);            while(list(,$r)=each($this->cObj->regObj->calculatedBasket))    {                $basket[$r["rec"]["uid"]] = $r["count"];            }        }            // Setting the URL to fetch.             // The basket content is send very simply as a serialized array in a URL parameter!        $url = "http://www.typo3.com/[remote script].php?dat=".            rawurlencode(serialize(array($basket,$this->cObj->regObj->personInfo)));            // Opens the URL (which in turn passes the content of $basket to the remote script        if($fd = @fopen($url,"r"))    {            while (!feof($fd))    {                fread($fd, 5000);            }            fclose($fd);        }    }}?>

The remote script (below) at typo3.com unserializes the incoming $dat-array and subsequently updates the local database with the purchased cigars:

<?php    // Initializingerror_reporting (E_ALL ^ E_NOTICE);     // Including most needed class, including configurationinclude ("t3lib/class.t3lib_div.php");include ("typo3conf/localconf.php");    // Connecting to MySQLmysql_pconnect($typo_db_host, $typo_db_username, $typo_db_password);        // Unserializing incoming data$dat = unserialize(t3lib_div::GPvar("dat"));    // If this data was valid, insert the elements in the user_cigars tableif (is_array($dat[0]))    {    reset($dat[0]);    while(list($pUid,$count)=each($dat[0]))    {        $count = t3lib_div::intInRange($count,0,10);        for ($a=0;$a<$count;$a++)    {            $query = "                INSERT INTO user_cigars                 (tstamp,item,donation_by,smoked,removed)                 VALUES (".time().",'".addslashes($pUid)."','".addslashes($dat[1]["name"])."',0,0)";            $res = mysql($typo_db,$query);        }    }}    // Clearing the page cache of the page.$query = "DELETE FROM cache_pages WHERE page_id=1208";$res = mysql($typo_db,$query);?>

Part II: Rendering the Humidor image

As soon as the purchased cigars is transferred to the database at typo3.com, the cache is cleared for the page with the Humidor-cam (page id 1208, see PHP code listing above).

When the Humidor-page is viewed again, a classic “Script” type content element is configured to render the box contents. This is the TypoScript included to make this happen:

content.includeLibs.humidor_php = fileadmin/cigars/humidor.php
tt_content.script= CASE
tt_content.script.key.field = select_key
tt_content.script.humidor = USER
tt_content.script.humidor {
  userFunc = user_humidor->makeHumidor
  GB {
    XY = [5.w],[5.h]
    5 = IMAGE
    5.file = fileadmin/cigars/humidor_l.jpg
    999 = WORKAREA
    999.clear = 
    1000 = IMAGE
    1000.file = fileadmin/cigars/overlay.jpg
    1000.mask = fileadmin/cigars/overlay_mask.jpg
  }
  image.file = [set from php]
  image.file.ext = jpg
}

Notice how certain TypoScript values are set to aid the PHP-function. The GB-object above is being further manipulated in the humidor-class in fileadmin/cigars/humidor.php.

This is the PHP-class from fileadmin/cigars/humidor.php:

<?class user_humidor    {    function makeHumidor($content,$conf)    {        // begin:        $offsetBegin=explode(",","21,91");        // end:        $offsetEnd=explode(",","255,71");        // steps:        $steps = intval(sqrt(pow($offsetEnd[0]-$offsetBegin[0],2)+pow($offsetBegin[1]-$offsetEnd[1],2)));//        debug($steps);//        rotation: 1 -> -13        $mainCigars = array(            "233" => array("mob01",19,0,"My Own Blend, Robusto"),            "229" => array("mob02",16,0,"My Own Blend, Churchill"),            "234" => array("cohiba1",22,0,"COHIBA, Robusto"),            "232" => array("arturo1",16,0,"Arturo Fuente, Corona Grande"),            "231" => array("arturo2",20,0,"Arturo Fuente, Robusto"),            "230" => array("punch1",19,0,"Punch, Churchill")        );        $cigars=array();        $tableOutput=array();        $tableOutput[]='<tr bgColor="#ACBCC5">            <td><font face=verdana size=2><strong>Cigar:</strong></font></td>            <td nowrap><font face=verdana size=2><strong>Donated by:</strong></font></td>            <td><font face=verdana size=2><strong>Smoked?</strong></font></td>            <td><font face=verdana size=2></font></td>        </tr>';                $query = "SELECT * FROM user_cigars WHERE removed=0 ORDER BY tstamp LIMIT 30";        $res = mysql(TYPO3_db,$query);        while($row=mysql_fetch_assoc($res))    {            if (isset($mainCigars[$row["item"]]))    {                $cRow = $mainCigars[$row["item"]];                if ($row["smoked"])    {                    $cRow[2]=1;                }                $cigars[] = $cRow;                $tableOutput[]='<tr bgColor="#D2D8DB">                <td nowrap><font face=verdana size=1>'.$cRow[3].'</font></td>                <td nowrap><font face=verdana size=1>'.t3lib_div::fixed_lgd($row["donation_by"],25).'</font></td>                <td nowrap><font face=verdana size=1>'.($row["smoked"]?"Yes":"No, not yet").'</font></td>                <td><font face=verdana size=1><em>'.$row["comment"].'</em></font></td>                </tr>';            }        }        $a=99;        $xOff=0;        $yOff=0;        $totalWidth=0;        reset($cigars);        while(list(,$cigarArr)=each($cigars))    {            if ($totalWidth>$steps)    {                $xOff=0;                $yOff=0;                $totalWidth=0;                                $offsetBegin[0]+=10;                $offsetEnd[0]+=10;                $offsetBegin[1]-=4;                $offsetEnd[1]-=4;            }                // calculate the width of the rotated image:            $rotation = -(t3lib_div::intInRange(floor($totalWidth/$steps*14),0,14)-1);                $imgH = 194;            $imgW = 100;            $width = cos(2*pi()/360*abs($rotation))*$imgW + sin(2*pi()/360*abs($rotation))*$imgH;            $height = cos(2*pi()/360*abs($rotation))*$imgH + sin(2*pi()/360*abs($rotation))*$imgW;            $centerBoxW = 63;            $centerBoxH = 170;            if (!$cigarArr[2])    {                $a++;                            $conf["GB."][$a]="WORKAREA";                $conf["GB."][$a."."]["set"]=                        ($offsetBegin[0]+$xOff).",".($offsetBegin[1]+$yOff).                        ",".$centerBoxW.",".$centerBoxH;                        $a++;                $conf["GB."][$a]="IMAGE";                $conf["GB."][$a."."]["offset"]=                         intval(-($width-$centerBoxW)/2).",".                        intval(-($height-$centerBoxH)/2);                $conf["GB."][$a."."]["file"]="/fileadmin/cigars/".$cigarArr[0].".jpg";                $conf["GB."][$a."."]["file."]["params"]="-rotate ".$rotation;                $conf["GB."][$a."."]["mask"]="/fileadmin/cigars/".$cigarArr[0]."_mask.jpg";                $conf["GB."][$a."."]["mask."]["params"]="-rotate ".$rotation;            }                        $totalWidth +=$cigarArr[1]+1;            $xOff = intval(($offsetEnd[0]-$offsetBegin[0])*$totalWidth/$steps);            $yOff = intval(($offsetEnd[1]-$offsetBegin[1])*$totalWidth/$steps);        }        $picArr = $this->cObj->getImgResource("GIFBUILDER",$conf["GB."]);                $conf["image."]["file"] = $picArr[3];        $content = $this->cObj->IMAGE($conf["image."]);                $content.='<BR><BR><font face=verdana size=3><strong>Humidor contents:</strong></font><BR>';        $content.='<table border=0 cellpadding=2 cellspacing=2>'.implode("",$tableOutput).'</table>';        //    debug($conf["GB."]);//    debug($width."x".$height);//    debug($content);        return $content;    }}?>

This is a quite comprehensive script. This is a brief overview of what happens in the PHP script above:

  1. The cigars are available as a photo-file and a mask-file each. The cigars must be overlaid on the image through the mask. This is a few of the masks and cigar-images:

  1. The image of the humidor is seen from a perspective. We cannot manipulate perspective into the cigars. But to compensate we rotate the cigars more and more the closer they come to the right end of the box. The rotation is between -1 and 13 degrees at each end respectively.

  2. In addition the cigars must be laid down following a slanted line. Furthermore the space between each cigar must be calculated based on their individual width.

  3. A new row of cigars must be started if the bottom row is full.

  4. Cigars which are marked “smoked” are not rendered, however space are reserved for them so it looks like they are gone from the box.

  5. When all cigars are laid down in the box, we superimpose the bottom part of the humidor in order to make sure that the long cigars does not exceed the box-boundaries. And finally the shadow casted from the right must be reflected on the cigars. This is done by a black overlay with an opacity: