Archive for August, 2012


Now that we can generate QR codes using the Google Chart API, and we have a controller action that serves it up for us with the correct .PNG header (see my previous post), we need to get it where it’s of any use – on paper!

So following on from my last post about Zend_Pdf, we can now do the following to get it in our PDF:

//draw qr code
 $target = 'http://example.co.uk/download/qr?target=http://www.example.co.uk/properties/view/id/'.$prop->getID();
 $img = file_get_contents($target); 
 file_put_contents(APPLICATION_PATH.'/uploads/tmp/'.$prop->getID().'-qr.png',$img);
 $image = Zend_Pdf_Image::imageWithPath(APPLICATION_PATH.'/uploads/tmp/'.$prop->getID().'-qr.png');
 $page->drawImage($image, 13, 13, 94,94);
 unlink(APPLICATION_PATH.'/uploads/tmp/'.$prop->getPid().'-qr.png');

The first line of code is the url of our download controllers QR action.  file_get_contents stores the image in memory, which we then save into a temp folder using file_put_contents. These commands are the equivalent of a lot of fopen etc, so use them if you don’t already!  This example uses my property object to make a dynamic filename, but this isn’t necessary, but I don’t need to tell you that!

We then use Zend_Pdf to load our png, and when drawing an image, the numbers in the argument are x1, y1, x2, y2 being bottom left and top right corners respectively. Finally, ditch the temp file with the unlink command. Have Fun!

Advertisements

If any of you have messed around with the google chart API you may or may not have come across an issue where the image returned from Google has no mime type.  Most browsers are smart enough to not care, but we would like our headers please where at all possible. So I made a QR action in my DownloadController:

public function qrAction()
{
   $target = $this->getRequest()->getParam('target');
   $saveas = $this->getRequest()->getParam('save');
   $dim = $this->getRequest()->getParam('dim');
   if(!isset($dim)){$dim = 200;}

   // disable view and layout 
   Zend_Layout::getMvcInstance()->disableLayout(); 
   $this->_helper->viewRenderer->setNoRender(); 
 
   $url = 'https://chart.googleapis.com/chart?';
   $params = array(
   'cht'=>'qr',
   'chs'=> $dim.'x'.$dim,
   'chl'=>$target
   );

   $url .= http_build_query($params);
   header ("Content-Type: image/png");
   if(isset($saveas))
   {
       header("Content-Disposition: attachment; filename=$saveas.png"); 
   }
   $content = file_get_contents($url); 
   echo $content;
}

As you can see, there are a couple of parameters that are accepted. Target is your target URL, and is the only required argument. A thing to note. Usually in Zend, we would usually access this like so:

http://example.com/download/qr/param/value/param/value

However as we are adding a url as a target the forward slashes mess this up, so we’ll use the old fashioned looking way of doing it:

http://example.com/download/qr?target=http://nasa.gov

This will give us our QR code with PNG headers as required 🙂 we can add dimensions to the url by adding:

http://example.com/download/qr?target=http://nasa.gov&dim=300

This will return a larger QR image. Finally, we can pass a filename to the saveas argument:

http://example.com/download/qr?target=http://nasa.gov&dim=300&saveas=nasa

which would bring up the save as dialog as nasa.png

Have Fun with your phone!

Once again IE stops a developer in his tracks and forces him to rewrite something that works perfectly in every other browser. This time – disabling Return using jQuery.

This works for everyone except Microsofts (quirky) browser. More enamel is lost in a year through developers teeth grinding that I’m sure dental profits are soaring through the roof. Anyway:

$(window).keyup(function(e) 
{    
    if (e.keyCode == 13)
    { 
       e.preventDefault();         
        return false;
    }
});

Whereas this will work

$('#my-form').bind("keypress", function (e) {
    if (e.keyCode == 13) 
    {
        e.preventDefault();
        return false;
    }
});

Irritating. Boycott IE!

I have a standard php site in work, which suddenly was to be joined onto a zend site. Wtf. Anyway.

Create a file zend_init.php and require that in your header.

<?php
// Define path to application directory
defined('APPLICATION_PATH')
 || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application/'));
 //die(APPLICATION_PATH);
// Define application environment
defined('APPLICATION_ENV')
 || define('APPLICATION_ENV', 'development');
 
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
 realpath(APPLICATION_PATH . '/../library'),
 get_include_path(),
)));
/** Zend_Application */
require_once 'Zend/Application.php';
// Create application, bootstrap, and run
$application = new Zend_Application(
 APPLICATION_ENV,
 APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap();

That’s it! Just edit the path to find where your application folder is.

Now without having to do any require_once() or include() commands, we can simply just call a new object like you would in your mvc app!

My shiny new website uses an awesome jquery tool called Coda Slider. It’s really good, but I kind of left the old fashioned people with a 1024×768 resolution behind, as my sites default width (and my slider image widths) are  1200px!

Anyway, I couldn’t have cared less, everyone uses a laptop or widescreen monitor anyway! Until that is the feedback started coming back. Looks like I need to fix this then :-s

First up, here’s the way the coda slider works:

<script src="/js/coda/jquery.coda-slider-3.0.min.js" type="text/javascript"></script>
 <link href="/js/coda/coda-slider.css" rel="stylesheet" />

..in the head, obviously. In your body, you set it out like this:

<div class="coda-slider-wrapper">
 <div class="coda-slider preload" id="coda-slider-1">
   <div class="panel">
     <div class="panel-wrapper">
       <img src="/img/coda/banner1.png" />
     </div>
   </div>
   <div class="panel imagepanel">
     <div class="panel-wrapper" style="text-align: center;">
       <img src="/img/coda/ddale.jpg" />
     </div>
     <div class="panel-wrapper" style="text-align: center;">
       <img src="/img/coda/let-your-property.png" />
     </div>
     <div class="panel-wrapper" style="text-align: center;">
       <img src="/img/coda/mobile.jpg" />
     </div>
   </div>
  </div>
 </div>

And in your <script> :

var slide = $('#coda-slider-1').codaSlider({
 autoHeight : false,
 autoSlide : true,
 dynamicArrows : false
 });

Now we have an awesome slider!

However, the guys with the 1024x768px monitors are crying in pain! So we can hack a quick fix for them:

if (screenwidth <= 1024) 
{
    $('div.panel-wrapper img').css('width','1000');
    $('.coda-slider-wrapper .coda-slider').css('width','1000');
    $('.coda-slider-wrapper .coda-slider').css('height','291');
    $('.coda-slider-wrapper .coda-slider .panel').css('width','1000');
}

That shrinks the image width and panel containers to fit 🙂 A bit hacky, but I still like it. Next time though, I’ll do my CSS in em’s and %ages!

 

Just when Zend couldn’t get any better, it already is!!

Today we’re farting about with RSS feeds and Twitter feeds! It’s remarkably simple! Lets get cracking:

In your controller:

    //look for Ron Paul news and twitter (Americans please vote this guy!!!)
    $q = 'Ron%20Paul';
    $this->view->q = $q;

    //get twitter feeds
    $twitter = new Zend_Service_Twitter_Search();
    $this->view->tweets = $twitter->search($q,array('lang' => 'en', 'rpp' => 8, 'show_user' => true));

    // get Google News Atom feed
    $this->view->feeds = array();
    $gnewsFeed = "http://news.google.com/news?hl=en&q=$q&output=atom";
    $this->view->feeds = Zend_Feed_Reader::import($gnewsFeed);

Thats it!!! In your view:

<?php 
  $count = 0;  
  foreach ($this->feeds as $entry)
  { 
?> 
  <p class="post">
    <span class="text"><a href="<?php echo $entry->getLink(); ?>"> <?php echo $entry->getTitle(); ?></a></span>
    <span class="time"><?php echo $entry->getDateModified(); ?></span>
  </p>
<?php 
    $count++; 
  }
?>

And for the tweets:

<?php foreach ($this->tweets['results'] as $tweet) {?>
<p class="tweet"> <span class="image">
<img src="<?php echo $tweet['profile_image_url']; ?>" /> </span>
<span class="user">
<?php echo $tweet['from_user'] . ': '; ?>
</span>
<span class="text">
<?php echo $tweet['text']; ?> </span>
<span class="time">
<?php echo $tweet['created_at']; ?>
</span>
</p>
<?php } ?>

Ok, I mean come on, how much easier does this stuff get?!?!?!? Rock on, amigos!

Zend_PDF rocks! It makes drawing a PDF almost child’s play. The main time eater though is the trial and error with the co-ordinates. So I stopped all this nonsense, and used Illustrator.

In adobe Illustrator, set the ruler origin to the bottom left of the page. Now in the transform pallette you can get the XY co-ordinates! Once you have created a mock up of the PDF, save a copy minus all the dynamic stuff, which we’ll drop in from our controller action.

First up, ditch the view and layout:

//disable the layout
 Zend_Layout::getMvcInstance()->disableLayout(); 
 $this->_helper->viewRenderer->setNoRender();

Next we create the Zend_PDF and templates, and set default fonts and colours:

//create a zend pdf
 $template = Zend_Pdf::load(APPLICATION_PATH.'/layouts/pdfs/proplist.pdf');
 $this->_pdf = new Zend_Pdf();
 $extractor = new Zend_Pdf_Resource_Extractor();
//set font
 $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
 $bold = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA_BOLD);
//set colors
 $black = new Zend_Pdf_Color_Html('#000000');
 $red = new Zend_Pdf_Color_Html('#ED1F24');
 $green = new Zend_Pdf_Color_Html('#009444');
 $blue = new Zend_Pdf_Color_Html('#0069B5');

We will have so many records per page, so the next bit goes in a for loop:

//add page
 $page = $extractor->clonePage($template->pages[0]);

 //get properties for this page
 $props = $this->_helper->paginator($rows,6);

We can set colours and fonts like this:

$page->setFillColor($black);
$page->setFont($bold, 11);

Add the page to the pdf like this

$pdf->pages[] = $page;

I created a function for getting the coordinates of each element, which adjusts depending which record on the page we are dealing with. This could be tweaked to suit your own project.

private function getCoordinates($row, $elem)
 {
 $xCoords = array(
 'header' => 21,
 'avail_date' => 419,
 'price' => 541,
 'summary' => 21,
 'hol_dep' => 70,
 'dep_date' => 128,
 'board_up' => 217,
 'keys' => 267,
 'tax_band' => 333,
 'on_web' => 388,
 'ref' => 437,
 'images' => 560,
 'condition' => 59,
 'move_date' => 130,
 'gas_safety' => 221 ,
 'hwsa' => 271 ,
 'let_fee' => 327,
 'man_fee' => 391,
 'landlord' => 452,
 'lha' => 551,
 'comment' => 64
 );

 $yCoords = array(
 'header' => 759,
 'avail_date' => 759,
 'price' => 759,
 'summary' => 739,
 'hol_dep' => 692,
 'dep_date' => 692,
 'board_up' => 692,
 'keys' => 692,
 'tax_band' => 692,
 'on_web' => 692,
 'ref' => 692,
 'images' => 692,
 'condition' => 680,
 'move_date' => 680,
 'gas_safety' => 680,
 'hwsa' => 680,
 'let_fee' => 680,
 'man_fee' => 680,
 'landlord' => 680,
 'lha' => 680,
 'comment' => 667
 );

 $z = array();
 $z['x'] = $xCoords[$elem];

 switch($row)
 {
 case 1:
 default:
 $z['y'] = $yCoords[$elem];
 break;
 case 2:
 $z['y'] = $yCoords[$elem] - 127.5;
 break;
 case 3:
 $z['y'] = $yCoords[$elem] - (127.5 * 2);
 break;
 case 4:
 $z['y'] = $yCoords[$elem] - (127.5 * 3);
 break;
 case 5:
 $z['y'] = $yCoords[$elem] - (127.5 * 4);
 break;
 case 6:
 $z['y'] = $yCoords[$elem] - (127.5 * 5);
 break;
 }
 return $z;
 }

Finally we can draw the text like this:

$text = '?';
 $page->setFillColor($blue);
 $z = $this->getCoordinates($row, 'condition');
 $page->drawText($text, $z['x'], $z['y'],'UTF-8');

Other things include fitting text to a specific width, or making a paragraph of multi lines:

private function widthForStringUsingFontSize($string, $font, $fontSize)
 {
 $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $string);
 //$drawingString = $string;
 $characters = array();
 for ($i = 0; $i < strlen($drawingString); $i++) {
 $characters[] = (ord($drawingString[$i++]) << 8 ) | ord($drawingString[$i]);
 }
 $glyphs = $font->glyphNumbersForCharacters($characters);
 $widths = $font->widthsForGlyphs($glyphs);
 $stringWidth = (array_sum($widths) / $font->getUnitsPerEm()) * $fontSize;
 return $stringWidth;
 }
private function getFontSizeForWidth($text,$font,$desiredSize,$maxWidth)
 {
 do 
 {
 $stringWidth = $this->widthForStringUsingFontSize($text, $font, $desiredSize);
 $desiredSize = $desiredSize - 0.5; 
 }
 while($stringWidth > $maxWidth);
 return $desiredSize + 0.5;
 }
private function getWrappedText($string, $font, $fontsize,$max_width)
 {
 $wrappedText = '' ;
 $lines = explode("\n",$string) ;
 foreach($lines as $line) {
 $words = explode(' ',$line) ;
 $word_count = count($words) ;
 $i = 0 ;
 $wrappedLine = '' ;
 while($i < $word_count)
 {
 /* if adding a new word isn't wider than $max_width,
 we add the word */
 if($this->widthForStringUsingFontSize($wrappedLine.' '.$words[$i]
 ,$font
 , $fontsize) < $max_width) {
 if(!empty($wrappedLine)) {
 $wrappedLine .= ' ' ;
 }
 $wrappedLine .= $words[$i] ;
 } else {
 $wrappedText .= $wrappedLine."\n" ;
 $wrappedLine = $words[$i] ;
 }
 $i++ ;
 }
 $wrappedText .= $wrappedLine."\n" ;
 }
 return $wrappedText ;
 }

To align text to the right, we subtract the width:

$width = $this->widthForStringUsingFontSize($text, $bold, 12);
 $z = $this->getCoordinates($row, 'price');
 $z['x'] = $z['x'] - $width;

And if we need wrapped text:

$text = $this->getWrappedText($text, $font,10, 550);

Awesome! Another what used to be pain in the arse task is now a piece of cake (or zf rather lol)!

I was replacing an old website hosted on a dedicated CPanel Apache server, with a new kick ass site written in Zend Framework. The problem with CPanel is that they don’t like you farting around with the DocumentRoot of Apache (by default /home/user/public_html/), which for a ZF app would be /home/user/public_html/public.

The way around this, without editing your vhosts or your httpd.conf (people with no dedicated server won’t be allowed to edit these anyway), is to upload a level down and symlink your public folder.

I use Dreamweaver (but ONLY for it’s FTP client!! DW totally sucks!) for my uploads, so in your FTP client I changed the upload folder to / instead of /public_html. Upload your files, and then SSH into the server.

we need to symlink the public_html folder, so make sure you’re not root for this part.  From your home folder:

# rm -fr ./public_html
# su nonrootuser
$ ln -s public public_html
$ exit
# chown -R nonrootuser:nonrootuser /home/nonrootuser/public_html
# chmod -R 0755 /home/nonrootuser/public_html

I’m not sure why, but symlinking whilst I was root seemed to cause problems, hence why i changed user before issuing the command. The chown and chmod fix a 500 error that says
“SoftException in Application.cpp:431: Mismatch between target GID (503) and GID (99) of file “/home/nonrootuser/public_html/index.php”

There we have it! Your ZF app is now loading propertly!

I’m loving discovering all these other parts of Zend Framework that I haven’t looked at yet. Today it was setting the locale of the application, and dishing out content specific to that region. Okay, lets get cracking.

In the root of the application, create /languages

And in there, create files like messages.es.php & messages.fr.php and messages.en_US.php. For English, as well as having en_GB and en_US, we should also have just en as a fallback. In these files, you put your translations in a big ass array:

<?php
return array(
  'nav-home'             => 'Inicio',
  'nav-services'         => 'Servicios',
  'nav-catalogue'         => 'Catálogo',
  'nav-contact'         => 'Contacto',
  'welcome'         => 'Bienvenido',
  'form-name'         => 'Nombre:',
  'form-email-address' => 'email:',
  'form-message'       => 'Mensaje:',
  'form-verification'  => 'verificación:',
  'form-send-message'  => 'Enviar',
  'form-title'         => 'Contacto',
);

Then you initialise the locale stuff in your bootstrap. If it can’t detect the locale from your browser then it will fall back to your default locale of choice :

protected function _initLocale()
{
    try 
    {
      $locale = new Zend_Locale('browser');
    } 
    catch (Zend_Locale_Exception $e) 
    {
      $locale = new Zend_Locale('en_GB');
    }
    $registry = Zend_Registry::getInstance();
    $registry->set('Zend_Locale', $locale);
}

To test the different locales, we can edit the bootstrap replacing the word browser for fr or es or en_US etc. But before we do that, a couple more things. First, initialise the translate stuff in the bootstrap too.

protected function _initTranslate()
{
    $translate = new Zend_Translate('array',
                  APPLICATION_PATH . '/../languages/',
                  null,
                  array('scan' => Zend_Translate::LOCALE_FILENAME,
                        'disableNotices' => 1));
    $registry = Zend_Registry::getInstance();
    $registry->set('Zend_Translate', $translate);
}

In the controller actions where you want anything to happen you get it from the Zend_Registry:

    $registry = Zend_Registry::getInstance();
    $this->view->locale = $registry->get('Zend_Locale');

Finally, you need to edit the nav links and the form field labels like so:

    <a href="blah"><?php echo $this->translate('nav-catalogue');?></a>

And the form:

    $email->setLabel('form-email-address');

Zend_Form is groovy enough to check for translations automatically, so that’s it really! A lot of fuss taken care of quite simply!

Another point is that now we have a locale, we can get locale specific versions of things like dates, currency, and number formatting. As an example:

echo $this->escape(
    Zend_Locale_Format::toNumber
    (
        $this->product->price,
        array
        ( 
            'locale' => $this->locale,
            'precision' => 2
        ) 
    )
);

And for a date:

    <?php echo $this->escape($this->productdate->get(Zend_Date::DATE_FULL)); ?>

As you can see this is easy and fun! So it would be awesome to have the ability to switch between them, yes? So lets make a locale controller:

class LocaleController extends Zend_Controller_Action

{

public function setAction()
 {
    // if supported locale, add to session
    if (Zend_Validate::is($this->getRequest()->getParam('locale'), 'InArray',
        array('haystack' => array('en_US', 'en_GB', 'de_DE', 'fr_FR')))) 
    {
      $session = new Zend_Session_Namespace('ttb.l10n');
      $session->locale = $this->getRequest()->getParam('locale');
    }
    // redirect to requesting URL
    $url = $this->getRequest()->getServer('HTTP_REFERER');
    $this->_redirect($url);
 } 
}

edit the _initLocale method in the bootstrap

  protected function _initLocale()
  {
     $session = new Zend_Session_Namespace('ttb.l10n');
     if ($session->locale) 
     {
        $locale = new Zend_Locale($session->locale);
     }
     if ($locale === null) 
     {
        try 
        {
            $locale = new Zend_Locale('browser');
        } 
        catch (Zend_Locale_Exception $e) 
        {
          $locale = new Zend_Locale('en_GB');
        }
     }
     $registry = Zend_Registry::getInstance();
     $registry->set('Zend_Locale', $locale);
  }

And then make some links in your layout to swap locales:

<a href="/locale/set/fr_FR"><img src="/img/locale/france-flag.png" />

Awesome! switching locales is a dawdle! Now to get on with my Spanish lessons some more! Adios!

Zend_Form is awesome. Zend_Validate and Zend_Filter are awesome. The Error decorators are also awesome, but sometimes a little too awesome.

I mean, when a user leaves an email field blank, and I had a NotEmpty and EmailAddress validator, I get two error messages!

  • Value is required and can't be empty
  • '' is no valid email address in the basic format local-part@hostname

Really I’d rather it just said:

  • Please enter a valid email address

Because some users, lets face it, are idiots! Nice idiots, but idiots all the same lol!

To stop this sort of thing happening, you can say in your form element:

$email = new Zend_Form_Element_Text('email');
 $email->setRequired(true)
 ->addFilter('StripTags')
 ->addFilter('StringTrim')
 ->addValidator('NotEmpty',true)
 ->addValidator('EmailAddress')
 ->setLabel('Email')
 ->setErrorMessages(array('Please enter a real email address'));

The key thing here is the true value in the NotEmpty validator. It breaks the chain of validators upon failure, stopping subsequent validators from checking and adding its error message too.
Without the true, but with the custom error message, you would get:

  • Please enter a valid email address
  • Please enter a valid email address

This should help save headaches!