PHP: una classe per generare un calendario stampabile in PDF

screenshot del calendarioQuesta classe PHP genera un calendario gregoriano in PDF, disponendo ogni mese in una pagina con l’elenco dei giorni feriali e delle festività. E’ possibile scegliere l’anno e cambiare il logo che compare in testa alla pagina.

Si avvicina l’anno nuovo e quasi tutti siamo alla ricerca di calendari. Sebbene molto spesso ci vengono regalati a scopo pubblicitario, una buona parte di questi calendari sono poco utili. Infatti, troppo spesso si tende a privilegiare l’aspetto estetico, a corredo grandi immagini, belle per carità, ma poi i giorni sono scritti piccoli che ci vuole la lente per leggerli. Oppure sono pieni zeppi di informazioni tanto inutili quanto ingombranti, di nomi di santi assolutamente improponibili per gli onomastici dei viventi di questa era, ed ancora, così minimalisti che non sono segnate nemmeno le festività nazionali.

Per questi motivi, ed anche per imparare qualcosa di più sulle funzioni di data e ora in PHP, ho pensato di scrivere una classe in grado di generare il mio calendario da appendere in cucina!

Il codice è abbastanza versatile: genera un’array multidimensionale con i mesi in lettere, i giorni in numero, i giorni della settimana, e un flag che indica se il giorno corrisponde ad una festività oppure no. La classe estende le lbrerie PDF R&OS, in questo modo è sufficiente istanziare una sola classe per generare anche il PDF.

Ecco il codice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php 
require_once("class.ezpdf.php");
 
class calendarPDF extends Cezpdf {
 
    var $calArray = array();
    var $yearNum = NULL;
    var $logo = NULL;
    var $easterDay = array();
 
    function buildCalendar($yearYouWant,$logoPath){
      $this->yearNum = empty($yearYouWant) ? intval(date("Y")) : $yearYouWant;
      $this->logo = $logoPath;
      $e = strtotime("$yearYouWant-03-21 +".
                            easter_days($yearYouWant)." days");
      $this->easterDay = array("month"=>intval(date("n",$e)), 
                               "day"=>intval(date("j",$e)));
 
      for ($i=1;$i<=12;$i++) {
        $this->buildMonths($i);        
      }
    }
 
   function buildMonths ($monthNum)
   {
    $giorniSettimana = array("Domenica","Lunedì","Martedì","Mercoledì",
                             "Giovedì","Venerdì","Sabato");
    $mesiAnno = array("GENNAIO","FEBBRAIO","MARZO","APRILE","MAGGIO",
                      "GIUGNO","LUGLIO","AGOSTO","SETTEMBRE","OTTOBRE",
                      "NOVEMBRE","DICEMBRE");                              
    $firstDay = mktime(0,0,0,$monthNum,1,$this->yearNum);
    $daysPerMonth = intval(date("t",$firstDay));
    $monthName = $mesiAnno[$monthNum-1];
    for ($i = 1; $i<=$daysPerMonth; $i++)
    {
      $actualDay = mktime(0,0,0,$monthNum,$i,$this->yearNum);
      $numDay = intval(date("w",$actualDay));
 
      if ($numDay == 0 ) {
        $f = true;
      }
      elseif ($this->isHoliday($monthNum,date("j",$actualDay))) {
          $f = true;
        }
      elseif (($monthNum == $this->easterDay['month']) && 
                (intval(date("j",$actualDay)) == $this->easterDay['day']+1)) {
          $f = true;
      }
      else {
          $f = false;
      }
 
      $this->calArray[$monthName][] = array("day"=>date("d",$actualDay), 
                                      "weekday"=>iconv('UTF-8','CP1252',
                                      $giorniSettimana[$numDay]),
                                      "F"=>$f);                          
    }   
   }
 
   function isHoliday ($monthNumber,$dayNumber)
   {
    $monthNumber = intval($monthNumber);
    $dayNumber = intval($dayNumber);
    $holiday = array(1=>array(1,6), 
                     4=>array(25),
                     5=>array(1),
                     6=>array(2),
                     8=>array(15),
                     11=>array(1),
                     12=>array(8,25,26)                                      
                     );
     $holidayKeys = array_keys($holiday);
     if (in_array($monthNumber,$holidayKeys)){
       if (in_array($dayNumber,$holiday[$monthNumber])) {
        return true;
       }
     }
     return false;
   }
}
?>

Per ottenere il testo in italiano, ho scelto di non utilizzare le funzioni setlocale() e strftime() perché, come viene spiegato in questo post, a seconda del sistema operativo impiegato, cambia la stringa per l’impostazione della lingua, passata come secondo parametro a setlocale(). Per ovviare a questo fastidioso comportamento, ho preferito costruire due array, uno per i nomi dei mesi ed uno per i nomi dei giorni della settimana.
Per la determinazione delle festività ho impostato in un array le date nazionali italiane, ovvero

  • 1 e 6 gennaio
  • 25 aprile
  • 1 maggio
  • 2 giugno
  • 15 agosto
  • 1 novembre
  • 8, 25 e 26 dicembre

Per ottenere, invece, la festività del lunedì dopo Pasqua, è necessario calcolare la data della Pasqua che varia in dipendenza del ciclo lunare e del tipo di calendario (noi utilizziamo il gregoriano, gli ortodossi il giuliano). Chi ha voglia di saperne di più sull’algoritmo di calcolo può fare un’approfondimento qui, chi invece è pigro (come me) può ricorrere alle funzioni PHP easter_date() o easter_days() che restituiscono rispettivamente la data della Paqua in UTS (valida solo fra il 1970 e il 2037) e il numero di giorni dopo il 21 marzo dell’anno in corso.
Poiché easter_date() risulta essere affetta da un bug riportato qui, ho preferito usare easter_days() che riporta sempre date corrette.

Ecco infine il codice per generare il calendario in PDF:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
    require_once("calendario_ita.php");
 
    $cal = new calendarPDF('a4','portrait');
    $cal->buildCalendar(2009,"./logo.png");
    $cal->selectFont('./fonts/Helvetica.afm');
    $tmp = array(
      'b'=>'Helvetica-Bold.afm'
      ,'i'=>'Helvetica-Oblique.afm'
      ,'bi'=>'Helvetica-BoldOblique.afm'
      ,'ib'=>'Helvetica-BoldOblique.afm'
      ,'bb'=>'Helvetica-Bold.afm'
    );
    $cal->setFontFamily("Helvetica.afm",$tmp);
    $cal->ezSetMargins(30,20,60,30);
 
    foreach ($cal->calArray as $calMonth => $dati) {
      $cal->ezImage($cal->logo,0,490,"none","left");
      $cal->setColor(0,0,1,0);
      $monthString = "<b>".$calMonth." ".$cal->yearNum."</b>";
      $cal->ezSetY(700);
      $cal->ezText($monthString,18);
      $cal->ezSetDY(-10);
      $cal->setColor(0,0,0);
      foreach ($dati as $singleDay) {
        $toPrint = $singleDay['day']."  ".$singleDay['weekday'];
        if ($singleDay['F']) {
            $toPrint = "<b>".$toPrint."</b>";
            $cal->setColor(1,0,0,0);
        }   
        $y = $cal->ezText($toPrint,14);
        $cal->setColor(0,0,0,0);
        $cal->setLineStyle(1);
        $cal->line(155,$y,540,$y);
        $y = $cal->ezSetDY(-4);
      }
      $cal->ezNewPage();
    }
    $cal->stream();
?>

Se volete solo stampare il calendario cliccate qui, se invece siete interessati anche al codice, in questo pacchetto è disponibile la classe (compresa la libreria R&OS) e il codice per gli esempi.

Conclusioni

Semplice, se nessuno mi regala un calendario… me lo stampo da me!!

Riferimenti ed approfondimenti:

PHP: stampa di codici a barre EAN13 e UPC-A con le librerie EzPDF

Codice EAN13 con ezPdfLa libreria di classi R&OS non dispone di metodi per la creazione di codici a barre, è necessartio ricorrere a soluzioni esterne. Esistono principalmente due differenti modi di stampare i codici a barre in un documento PDF. Uno si basa sulla generazione di un immagine bitmap e la successiva importazione nel documento attraverso i metodi di visualizzazione delle immagini. Il secondo invece, si basa sulla generazione della sequenza di barre utilizzando l’apposito algoritmo e i metodi di disegno dei rettangoli della classe PDF.
Se si preferisce il primo metodo, si può utilizzare PHP-Barcode 0.3pl1, oppure il pacchetto PEAR-PHP: Image_Barcode. Entrambi fanno uso delle librerie grafiche PHP GD per generare immagini del codice a barre.

Pur considerando che il primo metodo ha i suoi vantaggi, trovo che la possibiltà di generare direttamente le barre sia il sistema più efficiente. Il problema principale era dover costruire l’algoritmo per il disegno delle barre, ma una breve ricerca sul forum di EzPDF (PDF-PHP) mi ha facilitato il compito: esiste infatti uno script già pronto per l’utilizzo con le librerie FPDF, si tratta quindi solo di modificarlo per farlo girare con i metodi di EzPDF. Questo script implementa la generazione di codici di tipo EAN13 e UPC-A, che sono peraltro i più diffusi, rispettivamente in Europa e negli Stati Uniti.

La conversione dello script è stata piuttosto semplice perché i metodi di FPDF hanno i corrispettivi nelle librerie EzPDF. Praticamente, l’unica correzione è stata fatta sulla scala dei punti. Il risultato finale è una classe che estende le librerie EzPDF ed integra i metodi per la generazione dei codici a barre. Il codice rimane così molto pulito senza la necessità di ricorrere a terze parti o programmi esterni:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!--?php require('class.ezpdf.php'); class PDF extends Cezpdf { function EAN13($x,$y,$barcode,$h=45.3543307087,$w=0.992125984252) //$h=16 * 72/25.4; $w=.35 * 72/25.4; { $this-&gt;Barcode($x,$y,$barcode,$h,$w,13);
   }
 function UPC_A($x,$y,$barcode,$h=45.3543307087,$w=0.992125984252)
   //$h=16 * 72/25.4; $w=.35 * 72/25.4;
   {
     $this-&gt;Barcode($x,$y,$barcode,$h,$w,12);
   }
 function GetCheckDigit($barcode) {
   //Compute the check digit
   $sum=0;
   for($i=1;$i&lt;=11;$i+=2)
     $sum+=3*$barcode{$i};
   for($i=0;$i&lt;=10;$i+=2) $sum+=$barcode{$i}; $r=$sum%10; if($r&gt;0)
     $r=10-$r;
   return $r;
 }  
 function TestCheckDigit($barcode) {
  //Test validity of check digit
  $sum=0;
  for($i=1;$i&lt;=11;$i+=2)
   $sum+=3*$barcode{$i};
  for($i=0;$i&lt;=10;$i+=2) $sum+=$barcode{$i}; return ($sum+$barcode{12})%10==0; } function Barcode($x,$y,$barcode,$h,$w,$len) { //Padding $barcode=str_pad($barcode,$len-1,'0',STR_PAD_LEFT); if($len==12) $barcode='0'.$barcode; //Add or control the check digit if(strlen($barcode)==12) $barcode.=$this-&gt;GetCheckDigit($barcode);
  elseif(!$this-&gt;TestCheckDigit($barcode))
   die('Incorrect check digit');
  //Convert digits to bars
  $codes=array(
   'A'=&gt;array(
   '0'=&gt;'0001101','1'=&gt;'0011001','2'=&gt;'0010011','3'=&gt;'0111101','4'=&gt;'0100011',
   '5'=&gt;'0110001','6'=&gt;'0101111','7'=&gt;'0111011','8'=&gt;'0110111','9'=&gt;'0001011'),
   'B'=&gt;array(
   '0'=&gt;'0100111','1'=&gt;'0110011','2'=&gt;'0011011','3'=&gt;'0100001','4'=&gt;'0011101',
   '5'=&gt;'0111001','6'=&gt;'0000101','7'=&gt;'0010001','8'=&gt;'0001001','9'=&gt;'0010111'),
   'C'=&gt;array(
   '0'=&gt;'1110010','1'=&gt;'1100110','2'=&gt;'1101100','3'=&gt;'1000010','4'=&gt;'1011100',
   '5'=&gt;'1001110','6'=&gt;'1010000','7'=&gt;'1000100','8'=&gt;'1001000','9'=&gt;'1110100')
  );
  $parities=array(
   '0'=&gt;array('A','A','A','A','A','A'),
   '1'=&gt;array('A','A','B','A','B','B'),
   '2'=&gt;array('A','A','B','B','A','B'),
   '3'=&gt;array('A','A','B','B','B','A'),
   '4'=&gt;array('A','B','A','A','B','B'),
   '5'=&gt;array('A','B','B','A','A','B'),
   '6'=&gt;array('A','B','B','B','A','A'),
   '7'=&gt;array('A','B','A','B','A','B'),
   '8'=&gt;array('A','B','A','B','B','A'),
   '9'=&gt;array('A','B','B','A','B','A')
  );
  $code='101';
  $p=$parities[$barcode{0}];
  for($i=1;$i&lt;=6;$i++)
   $code.=$codes[$p[$i-1]][$barcode{$i}];
  $code.='01010';
  for($i=7;$i&lt;=12;$i++)
   $code.=$codes['C'][$barcode{$i}];
  $code.='101';
  //Draw bars
  for($i=0;$i&lt;strlen($code);$i++) { if($code{$i}=='1') $this-&gt;filledRectangle($x+$i*$w,$y,$w,$h);
  }
  $last_x = $x+$i*$w;
  //Print text uder barcode
  $this-&gt;selectFont('./fonts/Courier');
  $font_size = 12;
  $start_text = $x + (($last_x - $x) - $font_size*.6*$len) /2;
  $this-&gt;addText($start_text,$y-11,$font_size,substr($barcode,-$len)); 
 }
}
?-->

Un esempio di codice per l’applicazione della classe:

1
2
3
4
<!--?php require('ezbarcode.php'); $pdf =&amp;amp; new PDF('a4','portrait'); $pdf-&gt;selectFont('./fonts/Courier');
$pdf-&gt;EAN13(40,700,'123456789012');
$pdf-&gt;stream();
?-->

Questo è il risultato, il file è scaricabile qui.

Ho ritoccato la stampa del testo sotto il codice in modo che la stringa venga centrata. Per fare questo ho utilizzato il font Courier che ha la proprietà di avere i caratteri di lunghezza fissa.

Conclusioni:

Mi sembra una soluzione semplice ed efficace per quanti abitualmente utilizzano le librerie PDF di R&OS. Potrà essere utile anche a chi utilizza queste librerie per la stampa di PDF con il framework PHP: P4A2. Gli utilizzatori di P4A3 potrebbero essere anche interessati a questo progetto dello Zend Framework.

Riferimenti ed approfondimenti:


Alberto ha scritto:

Ciao, complimenti per l’articolo molto ben fatto!!!
Non sono molto pratico della libreria PDF come posso fare se volessi aggiungere più codici a barre sulla stessa pagina? grazie!!!!

Alberto
21.04.09 10:23
Mario Spada ha scritto:

Ciao Alberto, per mettere più codici sulla stessa pagina basta inserire il metodo $pdf->EAN13() in un ciclo. Per esempio:

foreach ($data as $riga){
$pdf->EAN13($x,$y,$riga[‘barcode’]);
$x+= $a;
$y-=$b;
}

dove $a e $b sono rispettivamente gli shift delle coordinate della pagina e $data è un array multidimensionale che contiene i dati, e ‘barcode’ è il nome del campo che contiene i valori dei codici a barre. Ovviamente devi controllare $a e $b per avere il numero di colonne che desideri e fare il salto pagina dove vuoi tu…!

P4A3 framework: stampa di pdf con le librerie Zend_Pdf

logo pdfNelle precedenti versioni del framework P4A non c’era nessuna libreria interna per la generazione dei documenti pdf.
Era quindi necessario adottare librerie esterne quali ezPdf o FPDF.
La versione 3, invece, incorpora la libreria Zend_Pdf che è integrata nello Zend framework.
A dire il vero, mi ero ben abituato ad ezPdf, e nulla vieta di utilizzarlo ancora, ma mi è sembrato doveroso provare anche le funzionalità delle classi Zend, che oltretutto hanno l’innegabile vantaggio di essere comprese nel framework. Da una prima analisi sommaria, mi sembra che hanno un discreto numero di funzioni utili per il disegno, ma non altrettanto per la manipolazione e gestione del testo. Anche per la formattazione delle tabelle, non mi sembra che ci sia nulla di simile a quanto offre ezPdf.

Passiamo all’esempio pratico: per la prova ho utilizzato la solita applicazione di esempio “Products catalogue”, implementando un pulsante che stampa la scheda del prodotto selezionato. Qundi nel costruttore di products.php ho inserito il seguente codice per creare il bottone e associargli l’evento per la stampa della scheda.

...
// Pdf print button ---------------------------
$this-&gt;build("p4a_button", "printProduct")
	-&gt;setLabel("Print pdf")
	-&gt;implement("onclick", $this, "printDoc");
$this-&gt;build("p4a_fieldset", "fs_pdf")
	-&gt;setLabel("Print product card")
	-&gt;anchor($this-&gt;printProduct);
// --------------------------------------------
...

Vediamo ora il codice del metodo per la stampa vera e propria del pdf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public function printDoc()
{
  require_once(P4A_SERVER_DIR."/p4a/".P4A_LIBRARIES_PATH."/Zend/Pdf.php");
 
  $imageName = $this-&gt;fields-&gt;picture-&gt;getNewValue(0);
  if (!empty($imageName)){
    $imagePath = P4A_SERVER_DIR . P4A_UPLOADS_PATH .
                  "/" . $imageName;
    $image = Zend_Pdf_Image::imageWithPath($imagePath);
  }
  $brand = $this-&gt;fields-&gt;brand-&gt;getNewValue();
  $model = $this-&gt;fields-&gt;model-&gt;getNewValue();
  $price = $this-&gt;fields-&gt;price-&gt;getNewValue();
 
  $description = $this-&gt;fields-&gt;description-&gt;getNewValue();
  $description = strip_tags($description);
  $wrapDescription = wordwrap($description, 42, "§");    
  $arrDescription = split("§",$wrapDescription);
 
  $doc = new Zend_Pdf; 
  $page1 = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4); 
  $page1-&gt;setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_COURIER),14); 
  $doc-&gt;pages[] = $page1; 
  if (!empty($imageName)){
    $page1-&gt;drawImage($image, 50, 720, 150, 750);
  }
  $page1-&gt;drawText("Marca: ".$brand,50,700,'UTF-8'); 
  $page1-&gt;drawText("Modello: ".$model,50,680,'UTF-8');
  $page1-&gt;drawText("Prezzo: ".$price,50,660,'UTF-8');  
  $page1-&gt;drawText("Descrizione: ",50,640,'UTF-8');
  $y = 640;
  foreach ($arrDescription as $segment){
    $page1-&gt;drawText($segment,160,$y,'UTF-8');
    $y -= 20;
  }
  $pdfData = $doc-&gt;render();
  P4A_Output_File($pdfData,"product.pdf"); 
}

Il campo “description” potrebbe essere anche piuttosto lungo, e poiché non ho trovato nessuna funzione o parametri per fissare un box di stampa entro il quale il testo possa essere formattato, ho dovuto preparare la stringa di testo dividendola in elementi di un array di stringhe di lunghezza prefissata. Per farlo senza spezzare le parole, ho usato la funzione php wordwrap() impostando come interruzione di testo un carattere molto poco usato: “§“. Quindi ho usato questo carattere come separatore dei segmenti di testo. Non è proprio una soluzione robustissima, sarebbe opportuno scrivere un metodo apposito più preciso, nell’attesa che le classi Zend_Pdf vengano arrichite come lascierebbe intendere questo post.

Ecco l’elenco dei formati pagina disponibili:

  • Zend_Pdf_Page::SIZE_A4
  • Zend_Pdf_Page::SIZE_A4_LANDSCAPE
  • Zend_Pdf_Page::SIZE_LETTER
  • Zend_Pdf_Page::SIZE_LETTER_LANDSCAPE

E’ anche possibile specificare un formato personalizzato passando direttamente i valori ($x, $y) in punti di 1/72 di pollice.

Questi invece sono alcuni dei font disponibili:

  • Zend_Pdf_Font::FONT_COURIER
  • Zend_Pdf_Font::FONT_COURIER_BOLD
  • Zend_Pdf_Font::FONT_COURIER_ITALIC
  • Zend_Pdf_Font::FONT_COURIER_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_TIMES
  • Zend_Pdf_Font::FONT_TIMES_BOLD
  • Zend_Pdf_Font::FONT_TIMES_ITALIC
  • Zend_Pdf_Font::FONT_TIMES_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_HELVETICA
  • Zend_Pdf_Font::FONT_HELVETICA_BOLD
  • Zend_Pdf_Font::FONT_HELVETICA_ITALIC
  • Zend_Pdf_Font::FONT_HELVETICA_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_SYMBOL
  • Zend_Pdf_Font::FONT_ZAPFDINGBATS

Infine, un esempio del documento pdf generato.

Per maggiori dettagli vi rimando ai link nel paragrafo: “Riferimenti ed approfondimenti”

Note:

E’ necessario scaricare l’ultima versione (2.99.9) preview di P4A3 che però non è ancora troppo stabile e soffre di qualche imperfezione. Quelle già note sono state corrette, quindi è necessario attingere al SVN per il corretto funzionamento del framework. In particolare, al momento è necessario prelevare il seguente sorgente dal SVN: p4a_db.php rev. 1762 che risolve un problema relativo agli apici usati nelle query. In alternativa si deve aspettare il nuovo imminente rilascio.
Infine, la funzione code>P4A_Output_File() genera un file temporaneo nella directory /uploads/tmp che quindi deve essere presente e avere i permessi di scrittura. Purtroppo non ho ancora capito perché i file temporanei non vengano eliminati alla fine della sessione, come mi sarei aspettato. Indagherò su questa anomalia, nel frattempo bisogna eliminarli manualmente per evitare che la directory si riempia di pdf!

Riferimenti ed approfondimenti:

P4A framework: esempio di applicazione completa per la gestione delle fatture

Screenshot dell'applicazioneL’idea di realizzare questo esempio di applicazione in P4A è nata per comparare i tempi di sviluppo in un ambiente RAD di tipo WYSIWYG (MS Visual Basic) che genera desktop applications e un framework PHP che genera web applications. Dei vantaggi delle applicazioni web ho già scritto in un precedente articolo.
Il progetto è molto semplice: un programma che gestisce la fatturazione di una ditta che fa servizi, quindi senza magazzino.
Ho scelto questo progetto perché la realizzazione non presenta grosse difficoltà e può essere utile a qualcuno, ed anche perché ci sono dentro diversi aspetti che ricorrono anche in applicazioni più grandi, come ad esempio la gestione di tabelle master-detail e lo sviluppo di un report personalizzato in pdf.
Inoltre lo schema del database è pressoché identico a quello di un tutorial per l’approccio ai dati con Visual Basic .Net 2005 (Visual Basic 2005 passo-passo: Accesso ai dati),
in questo modo sarà più facile effettuare il confronto.
Va detto che non ho ancora realizzato l’equivalente in VB.Net, ma avendo una certa esperienza di VB6 posso dire quanto tempo avrei impiegato a realizzarlo: diciamo 2 giorni al massimo. Ebbene, considerando che la documentazione di P4A è piuttosto scarsa, che il framework non ha un’interfaccia grafica WYSIWYG con strumenti drag’n’drop e wizard per la creazione dei report, e infine, che non ho una conoscenza dello stesso paragonabile all’esperienza che ho con Visual Basic, mi sarei aspettato di impiegare come minimo il doppio del tempo, invece l’applicazione completa è stata realizzata in soli 2 giorni di lavoro nemmeno troppo assiduo (non c’erano clienti col fiato sul collo!).
Avessi voluto usare solo PHP e javascript, ci avrei messo davvero molto di più…!
Un buon risultato, visto che sappiamo quanto sia importante produrre applicazioni in breve tempo per tenere bassi i costi ed accontentare clienti sempre meno disposti ad investire grosse cifre sul software.

Quali sono le caratteristiche dell’esempio?

  • Una maschera per la gestione dell’intestazione della ditta
  • Una maschera per la gestione dell’anagrafica clienti (con ricerca per nome)
  • Una maschera per la gestione delle fatture divisa in dati fattura e dettaglio prestazioni
  • Una procedura di stampa della fattura in formato pdf
  • Una maschera per il riepilogo delle fatture emesse e la possibilità di filtraggio per periodo

Qualche screenshot…

maschera iniziale
maschera iniziale
intestazione della ditta
intestazione della ditta
anagrafica clienti
anagrafica clienti
gestione delle fatture: master
gestione delle fatture: master
gestione delle fatture: detail
gestione delle fatture: detail
stampa pdf
stampa pdf
riepilogo delle fatture
riepilogo delle fatture

 

Note tecniche:
Ho provato l’applicazione sia con P4A 2.1.7 che con l’ultima release 2.2.2.
Il funzionamento è corretto sia con PHP4.x e MySQL4.x che con PHP5.2.4 e MySQL5.0.x, in quest’ultimo caso però è necessario impostare la direttiva del php.ini: memory_limit = 64M, il default è 8M.
All’interno del pacchetto, nella directory ‘_private’ ci sono i dump .sql del database MySQL effettuati con phpMyAdmin e con Heidi, il nome del database deve essere: ‘base_app_invoices’.
Infine per la generazione del report in pdf, ho utilizzato la classe R&OS (pdf-php su Sourceforge), conosciuta anche come ezPdf e licenza di tipo Public Domain

Download:

Pacchetto zip completo di librerie R&OS

Conclusioni:

Se il rapporto 1:1 dei tempi di sviluppo con queste differenti tecnologie rimane lo stesso anche per applicazioni complesse, e constatato il vantaggio delle applicazioni web, è da considerare seriamente l’adozione di questo framework PHP fra i propri strumenti di lavoro.

Riferimenti ed approfondimenti:

P4A framework: come stampare un pdf

P4A: toolbar con pulsante pdfNella documentazione del framework PHP P4A della Crealabs, non ci sono informazioni troppo dettagliate su come
produrre report in formato pdf. Cercando nei thread del forum, ci sono diversi frammenti ed indizi, che
aiutano parecchio, ma si perde un po’ di tempo a metterli insieme per produrre il codice necessario a
generare il report come si desidera. In questo articolo mostrerò la soluzione che ho scelto mettendo insieme i vari indizi, e applicandola alla base application “Products Catalogue” distribuita sul sito della Crealabs come applicazione di esempio.

Step 1: scelta ed installazione delle librerie pdf

In questo thread del forum, viene suggerito di usare le librerie FPDF o R&OS.
Ho scelto R&OS semplicemente perché in passato avevo avuto qualche difficoltà con FPDF, probabilmente per mia colpa. Credo che siano in sostanza equvalenti.
Una volta scaricato il pacchetto, è necessario creare nella root della applicazione (nel nostro caso: localhost/p4a/applications/products_catalogue/) una cartella che si deve chiamare
libraries. All’interno di questa cartella vengono decompresse le librerie (nel nostro caso i due files: class.pdf.php e class.ezpdf.php) e la cartella fonts contenente i file per la generazione dei font.

Step 2: Aggiunta di un pulsante nella toolbar

A questo punto bisogna creare un pulsante per la generazione del report in pdf. Personalmente ho scelto di crearlo nella toolbar, ma evidentemente si può crearlo dove si vuole.
All’interno del file products.php sarà sufficiente aggiungere questo codice PHP:

1
2
3
4
5
6
7
8
9
10
// Toolbar
$toolbar = &amp; $this->build("p4a_standard_toolbar",
                              "toolbar");
$toolbar->setMask($this);
$toolbar->addSeparator($position = "left");
// Aggiungo un pulsante alla toolbar per la stampa su pdf
$toolbar->addButton('stampapdf','pdf');
// Intercetto onClick per eseguire stampa_documento()
$this->intercept($toolbar->buttons->stampapdf,
                 'onClick','stampa_documento');

Notate che, per comodità, ho creato una variabile $toolbar contenente l’oggetto toolbar.
Il secondo parametro 'pdf' passato al metodo addButton crea automaticamente un img link all’icona pdf.png.
Questa icona deve essere presente nella cartella di default per il set di icone di P4A: localhost/p4a/icons/default/32/

In alternativa, si può impostare a NULL questo parametro per lasciare solo la scritta impostata ('stampapdf').

L’ultima riga di codice intercetta l’evento onClick sul pulsante ed esegue il metodo stampa_documento

Step 3: Aggiunta del metodo stampa_documento

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function stampa_documento()  
// funzione per la stampa su pdf
  {
  require('class.ezpdf.php');
  $pdf =&amp; new Cezpdf('a4','portrait');
  $pdf->selectFont($_SERVER['DOCUMENT_ROOT'].
                   P4A_APPLICATION_PATH.
                   '/libraries/fonts/Helvetica');
  $db =&amp; p4a_db::singleton();
  $res = $db->getAll("SELECT model,
   brands.description AS brand,
   categories.description AS category, selling_price
   FROM products
   LEFT JOIN brands
   ON brands.brand_id = products.brand_id
   LEFT JOIN categories
   ON categories.category_id = products.category_id");
  $pdf->ezTable($res,'','Products',array('fontSize'=>12));
  $type = 'application/pdf';
  $filename = 'report.pdf';
  header("Pragma: public");
  header("Cache-Control: must-revalidate, post-check=0,
         pre-check=0");
  header("Cache-Control: private", false);
  header("Content-type: $type");
  header("Content-Disposition: attachment;
          filename=\"$filename\"");
  $pdfcode = $pdf->output(1); // flusso dati senza header
  header("Content-Length: " . strlen($pdfcode));
  echo $pdfcode;
  die();
  }

Note: nella variabile $res viene memorizzato un’array multidimensionale associativo che è il risultato della query. Come esempio ho preparato una query di tipo JOIN perché nella maggior parte dei casi, è proprio questo il tipo utilizzato.

Il path per la scelta dei font deve essere assoluto, per averlo in termini relativi è possibile utilizzare la variabile $_SERVER['DOCUMENT_ROOT'] propria del PHP e la costatnte P4A_APPLICATION_PATH propria del framework P4A.

Per forzare il browser ad aprire il report pdf in una nuova finestra, ho cambiato l’header del documento generato, specificando che il contenuto è un attachment. Altrimenti, il contenuto viene riconosciuto come mime-type 'application/pdf' e (a seconda dei browser utilizzati) viene aperto nella stessa finestra.

Infine il nuovo processo viene terminato attraverso l’istruzione die();

Download:

Il sorgente comprende anche le librerie R&OS e l’SQL per creare il database con alcuni record d’esempio per il report in pdf. Il report generato è visualizzabile qui.

Conclusioni:

Il framework P4A è uno strumento davvero ben fatto, gli autori hanno fatto un gran lavoro, adesso sta a noi utilizzatori, attraverso l’interscambio di informazioni, generare la documentazione necessaria al suo sviluppo.
A tal proposito è mia intenzione produrre altra documentazione su altri aspetti di comune interesse agli utilizzatori di questo ottimo framework.

Riferimenti ed approfondimenti:


Valerio Maglietta ha scritto:

Definire ottima questa imbeccata di Mario Spada e’ il minimo…
L’unica aggiunta che mi sento di consigliare è quello di preprocessare il contenuto della stringa da stampare tramite iconv.
Ovvero per noi Italiani:

$res = iconv(‘UTF-8’, ‘CP1252’, $res);

prima dare in pasto $res a ezTable come nell’esempio precedente

In questa maniera caratteri accentati, simbolo euro e simili, non ci daranno problemi.
Nel mio caso (so linux Ubuntu 7.10) la codifica iniziale è UTF-8 ma potrebbe essere differente (ISO-8859-1, ISO-8859-16).
CP1252 è la codifica Windows Western European (sembra che R&OS sia stato sviluppato proprio sotto win…).

Grazie 1k Mario!