AKA Marketing.com Logo            VISIT THE BLOG            

Blogged thoughts, is our web blog. Expect views, opinion, rants and tirades about everything and anything 

« Home / Forums »        

 


Subscribe to our SEO / IT related blog by entering your email address below

Blogged thoughts

| by the www.akamarketing.com team

Welcome to our blog, the blog will (mostly) contain postings related or loosely related to the IT sector in general, although you may find a high occurance of SEO (search engine optimisation / search engine marketing) related posts as this is our particular area of expertise. Expect some personal and general topical stuff on occasion also. The postings could be general comments, opinion, code samples, product reviews, service reviews, website reviews, rants or whatever, nothing too serious though.

PHP to PDF conversion with TCPDF

July 25th, 2008

Recently I had a development client which as part of a larger system had a requirement of creating a PDF based report from his clients metrics, KPI’s etc. which he could then forward onto them. It was simple numerical data but for presentation purposes it was needed in PDF… you know to look good.

In the past when budget was less of an issue I used PDFLib, a commercial library which these days is available as part of the core PHP package. This project however required me to look for a free alternative. I found TCPDF on Sourceforge. It had almost 80,000 downloads, good documentation, lots of examples and was being used by applications such as Joomla, Drupal, Moodle and phpMyAdmin so I said I’d give it a go.

Installation was easy, basically I just needed to copy the TCPDF folder to my www space and require() the main class file from PHP scripts that needed to create PDFs on the fly.

I have to say I found it quite a slow & tedious process to create the more complex dynamic PDFs with this library, however this is because of what I was trying to do in the overall sense and was not the libraries ‘fault’, after all creating PDFs dynamically is quite different than creating webpages dynamically. I found having to work out all the ‘maths’ for positioning elements and the fact you can’t just press refresh to see if your latest line or two outputted as intended the most frustrating.  

OK to give you a feel for how the TCPDF class library can be used I’ll go through how I actually created the PDF report which my client wanted by providing a striped down version of the code. The two interesting things about the report was that it had to have a table with all the data and the page the table was on had to be presented in landscape style (because the table was wide). The table I output is related to Golf and is very simple, but hopefully it will be a good TCPDF starting block for you. 

Creating a table with TCPDF
Within the TCPDF class there are a couple of useful methods which enable me to output a nice table with DB data embedded in the cells. These are writeHTML(), writeHTMLCell()Cell() & MultiCell(). I had to rule out Cell() for the most part as it does not support putting HTML into the cell data. Although I could have outputted a standard HTML coded table using writeHTML() I went with MultiCell() in the end. The code below is similar to what I used, it produces this PDF (please right click and save as… otherwise your browser might crash). Be sure to change the line that says ‘FIX THIS LINE’… I had to remove the HTML because Wordpress was acting the goat again. The full not-messed-up-by-wordpress version is available too.

//reference the class so you can use it
 require_once(’../tcpdf/tcpdf.php’);

 // create new PDF document
 $pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true);

 //do not show header or footer
 $pdf->SetPrintHeader(false); $pdf->SetPrintFooter(false);

 // add a page - landscape style
 $pdf->AddPage(”L”);

 // set font
 $pdf->SetFont(”freeserif”, “”, 11);
 
 /////////////////////////////////////////////
 //  START TABLE HEADER
 /////////////////////////////////////////////
  
 //Colors, line width and bold font for the header
 $pdf->SetFillColor(11, 47, 132); //background color of next Cell
 $pdf->SetTextColor(255); //font color of next cell
 $pdf->SetFont(”,’B'); //b for bold
 $pdf->SetDrawColor(0); //cell borders - similiar to border color
 $pdf->SetLineWidth(.3); //similiar to cellspacing
 
 $cols=array(’Rank’,'Player’,'Pts. Avg.’,'Total Pts.’);//Column titles
 $width=array(20,50,40,30); //amount of elements must correspond with $header array above
 
 for($i = 0; $i < count($cols); $i++)
{
     //void Cell( float $w, [float $h = 0], [string $txt = ''], [mixed $border = 0],
     //[int $ln = 0], [string $align = ''], [int $fill = 0], [mixed $link = ''], [int $stretch = 0]) 
     $pdf->Cell($width[$i],7,$cols[$i],1,0,’C',1);
 }
 
 $pdf->Ln(); //new row
 
 ////////////////////////////////////////////////
 //  START TABLE BODY
 ////////////////////////////////////////////////

 //styling for normal non header cells
 $pdf->SetTextColor(0); //black
 $pdf->SetFont(”,”);

 //the data - normally would come from DB, web service etc.
 $rank = array(’1′,’2′,’3′);
 $player = array(’Tiger Woods,  USA’,'Phil Mickelson,  USA ‘,’Padraig Harrington,  Irl ‘);
 $playerWWW = array(’http://tigerwoods.com/’,'http://philmickelson.com/’,'http://padraigharrington.com/’);
 $avgPts = array(’10′,’9′,’8′);
 $totPts = array(’100′,’90′,’80′);

 //create & populate table cells
 for($i = 0; $i < count($rank); $i++)
 {
       if($i == "2")//highlight Harrington because he Irish... 
      {                //in reality you might highlight profits/losses etc.
          $pdf->SetFillColor(89, 239, 152); //green
      }
      else
      {
         $pdf->SetFillColor(255); //white
      }
      
       //link the players name to his website

      $playerANDlink = “a href=\”$playerWWW[$i]\”>$player[$i]/a”; //FIX THIS LINE
  
     //int MultiCell( float $w, float $h, string $txt, [mixed $border = 0], [string $align = 'J'],
     //[int $fill = 0], [int $ln = 1], [int $x = ''], [int $y = ''], [boolean $reseth = true],
     //[int $stretch = 0], [boolean $ishtml = false])
     $pdf->MultiCell($width[0],7,$rank[$i],1,’C',1,0,”,”,1,0,1); 
     $pdf->MultiCell($width[1],7,$playerANDlink,1,’C',1,0,”,”,1,0,1); 
     $pdf->MultiCell($width[2],7,$avgPts[$i],1,’C',1,0,”,”,1,0,1);
     $pdf->MultiCell($width[3],7,$totPts[$i],1,’C',1,0,”,”,1,0,1);
  
     $pdf->Ln(); //new row
 }

 //output the PDF to the browser
 $pdf->Output(”./pdfs/example.pdf”, “F”); //F for saving output to file
 

PDF creation and setup
OK I’ll briefly go through this code then. The first couple of lines really just sets up the PDF document or pages within the document, please refer to the TCPDF class documentation for more information. The only real item of note here is the method for creating a landspaced PDF page. The default AddPage() method takes no parameters and with this a page is created with the default page style (as per the overall TCPDF config file) which is usually portrait style, so pass in an ‘L’ for landscape pages. It is possible to have some pages landscape and some portrait style in a single PDF document.

Table Header
The TCPDF class has a lot of methods for setting the style of elements. The styles set will correspond to the next cell/element drawn. Most of them are obvious. SetFillColor() sets the background color of a cell when that cell is set to be painted or filled. The fun begins though when you actually start outputting cells (retangles). The header is just plain text so I used cell(). Cell() is well documented on the TCPDF site and it is easy to use. Parameters in order from left to right are, width, height, cell text, border true or false, where next cell should go, cell alignment, fill in cell true or false, optional link and stretch options.

The $ln - where next cell should go parameter, is useful if you want to build your tables vertically rather than horizontally. Leave it at 0 to go to the right and then call Ln() (kind of like what tr does in HTML table) to start a new row is what I suggest. If the fill parameter is set to true the cell background will be the color set by SetFillColor() as mentioned above, if no fill color has yet be set, the background will be grey. My header is built by using a loop to create the four required cells. The first iteration in the loop will be:

$pdf->Cell(20,7,”Rank”,1,0,’C',1);

which means create a cell of width 20 and height 7 with its value set to “Rank”. It should have a border, have its value centered and should have its background filled in.

Table Body
The main body of the table is very similar, but uses the method MultiCell() as we want the ability to output HTML as the cells’ value. A couple of arrays of data are created and populated. These will slot into the cells we are about to create. In reality the values of the cell will likely come straight from a DB or webservice but hardcoded arrays is fine for this sample.

MultiCell() has a lot of the same parameters which we have come across when using cell() above so I won’t mention them again. It also introduces a couple of new parameters including, X and Y for setting the positional coordinates of a cell, Reseth which resets the height of the last cell (without setting this to true your likely to get crazy looking tables… leave it to true and forget about it) and ishtml which determines if the cell value can hold HTML or not. MultiCell()’s full definition is below.  

int MultiCell( float $w, float $h, string $txt, [mixed $border = 0], [string $align = 'J'], [int $fill = 0], [int $ln = 1], [int $x = ''], [int $y = ''], [boolean $reseth = true], [int $stretch = 0], [boolean $ishtml = false])

It’s pretty simple to use. It provides power by allowing you to set the exact X and Y coordinates of a cell, but also ease of use in the sense that if you don’t specify values for X and Y it will just output at the current position (just like cell() does) so you don’t have to do any logic to get suitable X & Y values… in most cases anyhow.

After four calls to MultiCell() which printed one row of cells, we call Ln() to move to a new line. In fact we didn’t even need to do this to be honest, we could have just changed the $ln parameter value from 0 (to the right) to 1 (to the beginning of the next line) on our fourth cell in each row. The code then would change from this:

$pdf->MultiCell($width[2],7,$avgPts[$i],1,’C',1,0,”,”,1,0,1);
$pdf->MultiCell($width[3],7,$totPts[$i],1,’C',1,0,”,”,1,0,1);
  
$pdf->Ln(); //new row

to this:

$pdf->MultiCell($width[2],7,$avgPts[$i],1,’C',1,0,”,”,1,0,1);
$pdf->MultiCell($width[3],7,$totPts[$i],1,’C',1,1,”,”,1,0,1);

Personally I prefer the first way of doing things as it’s more obvious that a new line/row is being outputted. 

Before the call to MultiCell() I changed the fill colour of the cells related to Padraig Harrington (for those that don’t know who he is… he’s a two time Golf Major champion from Dublin), I set them back to white for all other rows. Of course that’s more hardcoding, in a ‘real world’ scenario you might highlight your good figures in green and your bad figures in red.

Outputting the final PDF
When you’ve finished creating all required cells, images, links, text etc. you have to call the Output() method to actually get your hands on your dynamically created PDF. This can take no parameters in which case the PDF is sent to the browser, more commonly though, developers specify the filename and the destination of the generated PDF. The destination can be one of four values, these are:

I: send the file inline to the browser.
D: send to the browser and force a file download with the name given by name.
F: save to a local file with the name given by name.
S: return the document as a string.

You can see my code sets the destination value as F:

$pdf->Output(”./pdfs/example.pdf”, “F”);

this is telling TCPDF to save the dynamically generated PDF document in the pdfs folder with the name example.pdf. On Windows it’s not needed but on Unix based machines you will need to set appropriate permissions on the pdfs (or whatever) folder to allow TCPDF to write the pdfs to it.

A little tip when your developing locally (as opposed to directly on your webhost) and using ‘F’ for the destination parameter is to create your PDFs with a random filename so you can simply press refresh on your script that does the PDF creation logic. If you have a static filename as I do in this example (called example.pdf) and you have the last generated PDF file (also example.pdf) open TCPDF will not be able to write the PDF (as it is aleady open, so a sharing violation error will occur internally). What I often use for random filenames during development is sha1(microtime()), this means to check changes I just need to press refresh on my PHP script and then visit my PDFs folder without having to close previous versions of my PDF.

S is useful if you want to sent the PDF as an attachment in an email without first saving it to disk somewhere.

Both I and D allow you to access the PDF quickly via the browser. A note about these two lads is this… Internet Explorer often looks at the extension of the file, (which will be .php) and assumes that the output will be HTML and thus will not present you a PDF, it will likely present a load of binary data in the webpage itself which obviously is not what you want. Firefox handles both I and D perfectly so I recommend using this during development, you obviously need to keep this in mind when you go into production too as your users might have the same problem too. It might be an idea to save to disk first, provide a link to the pdf and then periodically purge your temp PDFs folder.

Conclusion
I guess you could say that was kind of an introduction to TCPDF, my own introduction to it came from the TCPDF examples page. Thanks to Nicola Asuni for all her hard work on the examples and on TCPDF itself of course.

At this stage I’m really only learning TCPDF myself too so at the moment so I can’t really comment on its real power yet. I’ve come across a couple of issues using it so far but none were without workarounds, I imagine the commercially available libraries will out do it but for a library that’s free and relatively easy to use I offer my closing statement as… so far so good.

Off to sunny Algarve on an extended working holiday

March 14th, 2008

Well folks, it’s long been touted that my company will eventually not have a requirement for inhouse application development skills and when the final release of the new IT Strategy contained the following sentences & excerpts I pretty much knew that sooner or later the curtains would come down on my three year (circa) software development role with ESB International.

In accordance with the target Application Architecture IT & BP Function will not invest in the development of Application Development skills.

Buy Not Build - Purchase application software unless a substantial business case can be made for the development of a system in-house.

Minimise the Level of Application Development - An overall application architecture objective is to minimise the level of application development.

Rather than wait around until the development work completely dries up (and not wanting to move into the ‘Business’ side of things) I made a decision to resign and move over to the Algarve for nine or so months on an extended working holiday, in fact I’m ‘working’ (cough…) my notice as we ’speak’ and will be finished in ESBI on the 4th of April.

As for the Algarve, well the family have an apartment over there and I’ve managed to get a small bit of part time work with a Faro based web development company which should give me two-three days of work a week, of course I’m going to be looking after my usual quota of SEO work too. Additionally I’ve also got some Dublin based contacts keeping an eye out for any SEO/dev work which I may be able to take care of remotely so I should have enough bits and bobs work wise to keep me going.

On the three or four days I’m not working well what can I say… the place has 3000 hours of sunshine a year, full course meals (with wine) can be gotten for 15 euro-ish, bottles of decent beer cost about 30 cent, the beaches are magnificant, the females are magnificant and best of all the place is full of us Irish.

All the fun begins when I arrive on the 9th of April courtesy of a €12.99 Ryanair flight (thanks Michael…) so if anyone happens to be over in the Algarve anytime from mid April to the end of the year look me up.  If not don’t worry I’ll send you guys a postcard. :-)

Potential Trapattoni related headlines over the next while?

February 14th, 2008

It’s finally settled, Trapattoni is in the FAI hotseat after about 111 days of searching. Along with him comes a fresh challenge to the pun kings of Ireland, namely the tabloid sports writers. What will the headlines be like over the next two or so years? Here’s a little taster of what you may see in newspapers like TheStar, TheSun and TheMirror:

 ’Shut your Trap’ - Guaranteed to be used, perhaps when the smack talk begins between himself and rival managers in Group 8, or perhaps in response to Stephen Ireland if he dishes out more made up stories.

 ’Trap Rap’ - If Trapattoni gets disciplined by UEFA, Fifa etc?

 ’Through the Trap door’ - Perhaps in reference to getting into South Africa via the playoffs?

 ’Trap-ped in Group 8′ - Perhaps in reference to failing to qualify?
 
 ’Offside Trap’ - If Ireland or our opponents score a controversial late goal?

 ’Trapa-phoney’ - If Trapattoni turns out to be completely useless?

 ’Crapa-ttoni’ - As above.

 ’Snapa-ttoni - Perhaps if Trapattoni loses the head and does a Jack Charlton/John Aldridge USA 94 job?

 ’Trapa-stoney’ - If Trapattoni ever admits to smoking pot… perhaps in a heart to heart interview with ‘Dobbo‘?

 ’Gio-canny’ - If he makes an inspired substitution?

 ’The Italian Job’ - Going from his name to his nationality, of course I’m sure we will see it thrown in a couple of times too. Hopefully it will be in the context of Trapattoni engineering a big result for us and not in the context of a defeat by the Italian team.

‘Godfather’ - If he becomes loved by the Irish public?

‘Don Trapattoni’ - As above.

OK well I’m kind of clutching at straws now so I’ll leave it at that but there’s a few other good ones floating about the net, send in any I’ve missed. By the way if there are any budding tabloid headline writers among us check out http://www.rhymezone.com/ to see what you can come up with yourself.

$_ENV, the Environment variable array is empty in PHP

February 13th, 2008

The environment variables are usually made available to your scripts via $_ENV, just like $_POST, $_GET etc. are. It seems however that the registration by PHP of these kind of variables for use in your scripts is governed by a configuration option called variables_order in the php.ini file. By default many of the latest installs (and associated php.ini’s) of PHP will have this option set to ‘GPCS’, which stands for Get, Post, Cookie and Built-in variables respectively.

Therefore if you find your php_info() function call returning a bucket load of environment related information but yet a print_r($_ENV) returns an empty array () check the aforementioned setting and be sure to add E in there somewhere so as to instruct PHP to make environment variables available to you and your scripts.

While I’m not 100% certain I believe the ordering of the letters is only important when you have the register_globals option set to on. If register_globals is set to on (off by default since 4.2 and ditched in 6.0) then PHP will make all the variable kinds (Post, Get etc.) specified in variables_order available as global variables in the order you specified in that directive. Imagine for instance that you have ‘EGP’ specified in variables_order and register_globals set to on, if you have a variable called ‘Path’ in both the $_ENV and $_POST arrays, the value from the post array will overwrite the value from the environment array and thus the global variable $Path will hold information from $_POST and not $_ENV. If register_globals was turned to off, you would simply access all array indexes by their full name such as $_ENV["Path"] and $_POST["Path"] and thus they will be considered as 100% different variables.

PHP SQL Server connection problems - mssql_connect() [function.mssql-connect]: Unable to connect to server

February 12th, 2008

As pear (pun intended) usual if I have what I believe is a useful solution to something I’ve spent an eternity trying to fix in my code I will try put it up on the old blog for the benefit of others. The problem I was having for a large part of yesterday and earlier on this morning (Tuesday the 12th) related to the use of a PEAR based Database Abstraction Layer (DAL) module called DB. For those that don’t know a DAL allows a developer to call generic database access code, which then in turn calls DB provider specific code. The advantage of this is that the developer can change the underlying database system by changing a single line of code in a connection string as opposed to all DB access logic in his or her code.

The Database Abstraction Layer I was using: DB is actually depreciated but I was using it for legacy purposes as most of our existing PHP apps use it. The thing to remember with DALs is that ‘under the hood’ they will eventually call DB provider specific code so problems with DB, any other PEAR DAL or indeed any DAL in general may not be caused by the DAL itself but in fact caused by the underlying DB provider specific code.

My problem in this case was that I just seemed to keep getting connection errors when I called the ‘connect’ function in DB. The code I used was similar to:

require_once(’DB.php’); //makes the DB extension available to my code

//connection string for SQL Server database
$db = DB::connect(”mssql://username:PaSsWoRd@dbhost/dbname”);

if(DB::iserror($db))
{
       die($db->getMessage());
}

and the error I got was DB Error: connect failed. As you can see my underlying database is SQL Server as indicated by ‘mssql’ in the above code.
I’m not too sure about the more recent releases of DB (I was using one from 2003) but it seemed DB was not very useful from an error debugging point of view as it didn’t ‘bubble up’ all error messages and only spit out something very generic and very useless.

The thought struck me that perhaps the underlying PHP MS SQL function library was not enabled in the PHP configuration file. This turned out to be the case. Enabling it was as simple as adding a line or two into the PHP.ini file:

[PHP_MSSQL]
extension=php_mssql.dll

If you had of called

$db = mssql_connect(”dbhost”,”username”,”PaSsWoRd”);

directly in your code, which the above PEAR DB code eventually did you would have got a much more helpful error like the following:

PHP Fatal error: Call to undefined function mssql_connect() in…

which you would (hopefully) immediately diagnose as being related to the availability of the PHP mssql library itself and not DAL related.

After enabling the extension, I was still getting the DB Error: connect failed generic error from PEAR DB so I decided to work directly with the mssql_connect function to see if again it was a SQL Server issue. When I called mssql_connect I still couldn’t get a connection and got the error below so it was obviously not PEAR DB playing up.

PHP Warning: mssql_connect() [function.mssql-connect]: Unable to connect to server: servername

It turns out that my connection string was 100% correct however the version of the ntwdblib.dll file on my PHP box was not compatibile with certain recent versions of SQL Server. According to the PHP website ntwdblib.dll is required for the PHP MSSQL extension to work:

The extension requires the MS SQL Client Tools to be installed on the system where PHP is installed. The Client Tools can be installed from the MS SQL Server CD or by copying ntwdblib.dll from \winnt\system32 on the server to \winnt\system32 on the PHP box. Copying ntwdblib.dll will only provide access through named pipes. Configuration of the client will require installation of all the tools.

It seems that for whatever reason the very latest installs of PHP include a version of ntwdblib.dll that will not work with SQL Server 2003, SQL Server 2005 and as far as I’m aware SQL Server Express. The version of ntwdblib installed is likely to be 2000.2.8.0 when what you need to have to talk to recent versions of SQL Server is 2000.80.194.0. This file can actually be present in locations other than winnt\system32 depending on your platform and installation setup so I suggest you do a search for it, check the version and if it doesn’t end it 80.194.0 download the latest version from the UserScape.com site and use it to overwrite the existing version. In my case I installed PHP as a CGI on Windows Server 2003 so the file was present directly in the PHP folder. When I updated it and tried my code again everything worked fine, including the original PHP DAL DB:connect call.

In the end it turned out my problems were nothing to do with the PEAR DB module but were related to PHPs SQL Server functions. If your still having problems connecting to SQL Server from PHP I suggest you visit the relevant PHP page located at http://ie2.php.net/function.mssql-connect which contains a lot of user contributed information about ntwdblib.dll and other issues which may be causing your problems and associated pain.

What’s up with some recruitment sites not being able to handle IT search strings?

February 8th, 2008

The IT industry like most industries has its fair share of abbreviations, jargon, keywords and general ’shop talk’. With that in mind it surprises me how many recruitment websites can’t handle searches for simple IT strings like ‘c#’, ‘c++’, ‘asp.net’, ‘.net’ etc. I think the IT industry in particular makes extensive use of the web for job searching so all recruitment sites should facilitate say a c# developer who wants to search for ‘c#’. Have a look at recruitment sites like elanIT.ie, CPL.ie, EmployIreland.com which were some of the sites I spotted which couldn’t handle most if not all of the above search strings.

HOME | ABOUT US | CONTACT US | SITEMAP | GOOGLE SITE SEARCH | TOP
12 Lorcan Crescent, Santry, Dublin 9, Ireland +353 87 9807629