Sep 30, 2008

how to add pdf support to tasks Jr

  1. Introduction
  2. Presentation
  3. Beginning
  4. The front controller
  5. PDF generation class
  6. Conclusion

I recently hlooked for a free tool to manage a todo list. As I have a LAMPP server at home, I obviously looked for a php-mysql solution that can manage hierarchy or tasks. After som times hanging around, i found what was the closest to my need: tarsks-jr that can be downloaded here

Table of content

Installation was quite easy, and it is really user-friendly, but 2 features are missing for this free version:

  • multi-user access
  • pdf export (I'm not even sure it is available in the charging verion)

As I had to exchange my task list with customers in a read-only way, I decided to write a pdf export plugin for tasks.
well, in fact, application is not designed for plugins, so as I can't just give you a zip file, I'll explain what I did. Let's start.

Table of content

Well, software achitecture is easy to understand, you've got a frontend page that creates the menu and dispatches requests.

Considering this, first task is obviously to add a menu entry for PDF export, so I inserted this at line 253:

<?php $uri = 'export_pdf.php'.($screen=='focus' ? '?screen=focus&root='.(int)$_GET['root'] : '') ?>
<div class="menuHR"><img src="images/clear.gif" height="1" width="1"></div>
<table width="100%" cellpadding="0" cellspacing="4" class="hand" onclick="window.open('<?php echo $uri; ?>');" title="export pdf">
 <tr>
  <td width="16"><img src="images/icon_pdf.png" width="16" height="16"></td>
  <td><span class="menuText">export pdf</span></td>
 </tr>
</table>

I know this is dirty and non standard complyant, but the whole application is coded this way, so I addapted my coding style.

So, I just added a link that open in a new window and open the export_pdf.php page.
the $uri variable is used to be able to select only a task and its subtasks to export as pdf.

Now, to have everything work fine, let's create our export_pdf.php file, and leave it empty. As we are creating files, let's add a library file that we'll call pdf_functions.php.

We're now ready to start real work !

Table of content

As we want to be able to create several type of PDF files (all tasks and subtasks view and a single task and its sub tasks), we need to implement a front controller. I decided to do it the same way as the initial one was made: quick and dirty.

first, let's include libraries:

<?
ini_set('short_open_tag', '1');

// suppress Notice messages
error_reporting(E_ALL);

$timer = microtime(); // start page timer

// make config changes in "config.php"

// includes
require_once('objects.php');
require_once('database.php');
require_once('functions.php');
require_once('pdf_functions.php');
require_once('compatibility.php');
require_once('config.php');
ob_start(); // hack to keep some weird char? from starting output in spanish.php
require_once('languages/'.$custom->language.'.php');
ob_end_clean();

// set page variables
$page = new page;
$task = new task;

require_once('set_up.php');

// session
session_start();
if (!empty($_REQUEST["home_sort_order"])) {
 $_SESSION["home_sort_order"] = $_REQUEST["home_sort_order"];
}
if (empty($_SESSION["mode"])) {
 $_SESSION["mode"] = "standard";
}
if (!isset($_SESSION["messages"])) {
 $_SESSION["messages"] = array();
}

$debug = 0;         // set to 1 to display debug messages
$current_page = "index.php?current_page=1"; // initialize string
$reload_task = 1;        // when this gets set to 0, it stops the save from happening


// not mobile? we'll use the standard interface
$_SESSION["mode"] = "standard";

if ($debug == 1) {
 print_r($_REQUEST);
}
require_once('engine.php');

I just copied the code from the original index.php and added our library. By the way, I set the error level to E_ALL for debugging.

Now, we're ready for the PDF generation.

Table of content

I'm using FPDF library to create the document.
To install it, just download the library from the website and copy font folder and fpdf.php to a folder you name fpdf.

As told in the library manual, I extended original class to add my own generation functions. As documentation is included I give the code as is, without more explanations, you just have to know that the function used from front controller is called printTask.

<?php

require_once("fpdf/fpdf.php");
class tasksPDF extends FPDF{
 /**
  * the task level
  */
 private $level=0;
 /**
  * the subtask offset width for rendering
  */
 private $offset = 5;
 /**
  * headers numbers
  */
 private $numbers = array(0);
 
 /**
  * automatically calculates the cell height from the fontsize
  */
 function AutoCell($w, $text, $border=0, $align='J', $fill=false)
 {
  $this->MultiCell($w, $this->FontSize+2, $text, $border, $align, $fill);
 }
 
 /**
  *
  */
 function MarginCell($text, $border=0, $align='J', $fill=false)
 {
  if($this->level>=2) 
  {
   $this->Cell(($this->level-1)*$this->offset, $this->FontSize+2, '', 0, 0);
  }
  if($this->level>=1)
  {
   $this->Cell($this->offset, $this->FontSize+2, '', '', 0);
  }
  $this->AutoCell(0,$text, $border, $align, $fill);
 }
 
 function printTask($task, $show_completed_tasks = 0, $levels = 0)
 {
  
  //increase the task number
  $this->numbers[$this->level]++;
  
  //prepare for title
  $this->SetFont("Arial","B",12);
  $this->SetFillColor(204,204,204);
  $y = $this->GetY();
  $this->MarginCell('    '.implode($this->numbers, '.').' '.$task->title, 'TLR', 'L',true);
  $this->Image('images/icon_priority_'.$task->priority.'.gif', $this->level*$this->offset + $this->lMargin + 1, $y+1, 4,4);
  
  //insert body text
  $this->SetFont("Arial","",10);
  $this->SetFillColor(255,255,255);
  $this->MarginCell($task->notes, 'LR');
  
  //get childrens according to configuration
  if ($show_completed_tasks == 1) {
   $displayed_children = $task->children;
  }
  else {
   $displayed_children = $task->open_children;
  }
  
  // if some, then insert linebreak
  if(count($displayed_children))
  {
   $y = $this->GetY();
   $this->Cell(0,2,' ','R',2);
   if($y>$this->GetY()) $y = $this->tMargin;
   $this->Line($this->level * $this->offset + $this->lMargin, $y, $this->level*$this->offset + $this->lMargin, $this->GetY());
  }
  
  // let's go for childrens
  $this->level++;     // level up (identation up ;) )
  array_push($this->numbers, 0); // prepare numerotation
  
  foreach($displayed_children as $child)
  {
   //get actual position
   $y = $this->GetY();
   //get the task
   $child_task = new task;
   $child_task->retrieve($child);
   $this->printTask($child_task, $show_completed_tasks);
   // did we reach the end of page ?
   if($y>$this->GetY()) $y = $this->tMargin;
   //draw vertical line to act as a box
   $this->Line(($this->level-1) * $this->offset + $this->lMargin, $y, ($this->level-1)*$this->offset + $this->lMargin, $this->GetY());
  }
  
  // back to normal
  $this->level--;
  //back to previous numerotation
  array_pop($this->numbers);
  
  //if childrens displayed, then a line break
  if(count($displayed_children))
  {
   $y = $this->GetY();
   //close the last box
   $this->Line( ($this->level + 1) * $this->offset + $this->lMargin, $y, $this->w - $this->rMargin, $y);
   //add a blank line
   $this->Cell(0,2,' ','R',2);
   // did we reach the end of the page ?
   if($y>$this->GetY()) $y = $this->tMargin;
   $this->Line($this->level * $this->offset + $this->lMargin, $y, $this->level*$this->offset + $this->lMargin, $this->GetY());
  }
  // close the box
  $this->Line($this->level * $this->offset + $this->lMargin, $this->GetY(), $this->w - $this->rMargin, $this->GetY());
  
 }
 
 
}


This is not very complete, but in the actual state, it feets my needs... I let you adapt this code for your need.

Now we've got everything, let's finish our front controller by adding following code at the end:

<?php // for syntax higlight, when copying to the end of the front controller you don't need it
$PDF = new tasksPDF();
$PDF->AddPage();


switch ($screen) {
 case "home":
  $PDF->setTitle('PDF export of all tasks');
  foreach ($tasks as $temp) {
   $task = new task;
   $task->retrieve($temp);
   $PDF->printTask($task, $page->check_flag("show_completed_tasks"));
  }
  break;
 case "focus":
  $PDF->setTitle('PDF export of task: '+$task->title);
  $PDF->SetFont("Arial","B",14);
  $PDF->AutoCell(0,"Project: ".$task->title, 0, 'C');
  $PDF->SetFont("Arial","",12);
  $PDF->AutoCell(0,"\n".$task->notes."\n");
  //only show subtasks
  $show_completed_tasks = $page->check_flag("show_completed_tasks");
  if ($show_completed_tasks == 1) {
   $displayed_children = $task->children;
  }
  else {
   $displayed_children = $task->open_children;
  }
  foreach($displayed_children as $child)
  {
   $child_task = new task;
   $child_task->retrieve($child);
   $PDF->printTask($child_task, $show_completed_tasks);
  }
  break;
 default:
  die('unknown action !');
}

$database->disconnect();
header("Content-Type: application/pdf; charset=".$language->charset);
$PDF->Output();

you can customize it as you want, it is very easy and

Table of content

Unlike usual, this article isn't very detailed, but it's late, and this blog is about javascript, not php ;)
I hope it gonna be usefull for somebody, and at least it allowed me to learn the FPDF great library !

That's all folk !

Table of content

No comments: