UTF-8, internazionalizzare il proprio web

Ricordate questo articolo a proposito della gestione corretta di un sito codificato in ISO-8859-1? (Se non sapete di che parlo, leggetevelo, visto che almeno l’introduzione vi sarà utile per comprendere questo articolo)

Oggi parlerò di come estendere il proprio supporto alle lingue di tutto il mondo tramite UTF8 (Unicode Transformation Format, 8 bit), ossia uno dei charset che permette di codificare tutti i caratteri di qualsiasi alfabeto, dal cinese al suomi, e qualche posticino avanza pure per vari caratteri speciali (mai visto ❤ o ♫?)
Anche se UTF8 non è l’unica codifica a permettere ciò, è il charset raccomandato dall’IETF (Internet Engineering Task Force), quello standard per XML e JSON (i principali formati per il data exchange). Presenta anche altri vantaggi: essendo un charset multibyte a lunghezza variabile, codifica alcuni caratteri con 1, altri con 2, 3 o 4 byte. Ciò significa che se io uso solo i primi 256 caratteri, ossia alfabeto latino e poco più, le codifiche Latin 1 e UTF8 sono identiche, garantendo la stessa dimensione della stringa e la compatibilità con sistemi limitati all’ASCII (se usassi una stringa UTF8 in ANSI C non avrei problemi finchè rimango in quel range). Inoltre, tutti gli alfabeti conosciuti (compreso quello cuneiforme e quello fenicio) riescono a essere codificati con massimo 3 byte.
Per una comparazione con UTF16, UTF7 e UTF32, il sito UTF-8 Everywhere fornisce un bello scorcio della situazione.

Detto ciò, per un sito internazionalizzato, che abbia pochi problemi di codifica, che non sprechi memoria e che sia compatibile con i maggiori standard web, la giusta codifica è UTF8.

Grazie, ma come la implemento?
Niente paura! C’è da lavorare un po’, ma è possibile. Parlando di una configurazione PHP – MySQL, abbiamo le solite tre cose da equalizzare: codifica dell’output HTML, codifica del sorgente PHP, codifica del database MySQL.
Per quanto riguarda HTML, le solite cose da fare: tag <meta> per le pagine e impostazione dell’header Content-Type per gli script AJAX.

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

e

//il MIME-type dipende da che tipo di output volete servire: questo è per il JSON, text/html per semplice HTML, text/plain per testo semplice o application/xml per XML
header('Content-type: application/json; charset=utf8');

L’altra cosa di cui tenere conto è la codifica dei sorgenti PHP: anche quella dev’essere UTF8, per fare in modo che le stringhe hard-coded servite siano codificate con il giusto charset!
Se usate blocco note di Windows (oltre a cambiare editor), dovrete risalvarvi ogni file sorgente selezionando la codifica UTF8 (File > Salva con nome… > Codifica: UTF8). Se invece come me usate Notepad++, la cosa è estremamente più facile: andate in Configurazione > Preferenze…, selezionate la tab Nuovo documento/Directory predefinita e nel riquadro Codifica scegliete UTF-8 senza BOM (BOM: Byte Mark Order, serve a indicare al decoder l’endianness dei byte della stringa, il che crea problemi in PHP) e spuntate la casella Applica all’apertura di file ANSI. In questo modo aprendo un file sorgente in ANSI (che dovrebbe essere la codifica predefinita su Notepad++, quindi circa ogni file che avete creato) lo convertirete automaticamente in UTF8, e basterà aprire tutti i vostri sorgenti per convertirli tutti alla giusta codifica. Ottimo!

Passiamo alle gatte da pelare: MySQL.
Anche se gestire le codifiche è (per fortuna!) molto facile, il difficile è convertire un database che avete già creato con un altro charset in UTF8.
Il problema è che dovrete andare a cambiarvi la collation di ogni campo che la usa, in ogni tabella che avete creato.
Il modo più veloce per farlo è creare un dump in SQL del proprio database (su phpMyAdmin basta selezionare il database, cliccare sulla tab Esporta e subito su Esegui), aprirlo con un editor avanzato (tipo Notepad++) e rimpiazzare tutte le dichiarazioni della collation in latin1_swedish_ci (o altre, se ne avevate impostate di nuove) con una dichiarazione per utf8_unicode_ci. La cosa è molto rapida se usate le comode funzioni di replace dell’editor.
Dopodichè, svuotate il database da tutte le tabelle, settate charset e collation del database a UTF8 (eseguendo la query ALTER DATABASE nome_database DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci), e importate il dump modificato (su phpMyAdmin, tab Importa, seleziona il file di dump e Esegui).
Se tutto va bene dovreste avere la giusta codifica dei dati nel database.

Ultime due cose: impostare la codifica anche nei file di configurazione di MySQL e PHP.
Per il primo, aprite il file di configurazione (my.conf, my.ini o simili) e nella sezione [mysqld] aggiungete
collation-server=utf8_unicode_ci
character-set-server=utf8

mentre in quella [client] va aggiunto
character-set-server=utf8
In quanto a PHP, trovate sempre il file di configurazione (solitamente php.ini) e nella sezione principale [PHP] impostate default_charset = "utf-8".

Una volta cambiati entrambi i file di configurazione, riavviate il server web e godetevi la possibilità di scrivere in cirillico!

Utili

Upload e download asincrono

Ultimamente mi sono trovato a che fare con download e upload di file. La cosa che ho sempre odiato è il dover ricaricare la pagina per inviare un file o aprirne una nuova per scaricare un file.
Così ho cercato un po’ in giro…
La prima cosa che si potrebbe pensare è usare AJAX, che per noi programmatori fa rima con asincrono. A quanto sembra però, XmlHttpRequest non supporta i dati binari: niente da fare. Altre soluzioni sono basate su bridge Flash o totalmente su Flash, ma coi tempi che corrono è un buco nell’acqua (e comunque, non mi è mai piaciuto Flash).
La soluzione arriva dal passato: ricorrere ai cari vecchi iframe.
Anch’io ho storto il naso, ma cosa c’è di male a usare un bell’iframe nascosto? Contando il beneficio che si ha nell’usabilità, mettere un frame non è una tragedia. Contando che il 99% dei siti ne fa comunque uso più o meno volontariamente (anche solo i widget di Facebook sono basati sugli iframe), metterlo anche nel proprio non è uno sgarro dagli standard troppo imperdonabile.
Dissuasi i sensi di colpa, vediamo come fare.
Entrambe le soluzioni sono molto semplici: si tratta di caricare la pagina di download/upload nell’iframe nascosto invece che in bella vista all’utente.

Download
Questa è sicuramente la più semplice: potete trovarne un esempio sul sito Dowsplay
Agendo sull’attributo src dell’iframe carichiamo il file da scaricare nell’iframe, apparirà quindi la finestra di dialogo per salvare, e questa sarà l’unica cosa che l’utente vedrà.

Upload
Per l’upload dobbiamo gestire il submit del form che contiene il campo file.
Ecco un esempio minimale:


<!-- impostando l'attributo target con l'id dell'iframe dirigiamo il caricamento della nuova pagina nel frame invece che sotituirla a quella visualizzata
ricordiamoci enctype="multipart/form-data" per la codifica del file -->
<form method="POST" action="upload.php" target="upload_frame" enctype="multipart/form-data">
	<input type="file" class="fileinput"/>
	<input type="submit"/>
</form>
<!-- nascondiamo l'iframe con display: none; -->
<iframe id="upload_frame" style="display: none;"></iframe>

Quando l’utente invia il form non vedrà niente: starà a noi dare un qualche feedback giusto per fargli capire che è andato tutto a buon fine (o magari no!)

PHP e AJAX: come gestire le lettere accentate

Sto sviluppando un sito (un gran bel sito u.u) nel quale faccio una richiesta asincrona tramite AJAX a uno script PHP, il quale mi restituisce del codice HTML.
Ma se devo visualizzare delle lettere accentate… viene un gran guazzabuglio di strane à e simboli esoterici del genere che sembra che stia scrivendo in lappone.

A cosa è dovuto questo problema?
Alla codifica dei caratteri, o meglio al charset impostato per la pagina.
Il charset è l’insieme dei caratteri che scelgo di far visualizzare al browser. In base a questa informazione, il browser codifica la richiesta e trasforma quindi i byte nei caratteri corrispondenti.
Il charset più comune è l’ASCII, che prevede 128 caratteri, tra numeri, lettere, simboli e cartteri di utilità (tipo il carattere di new line). Tuttavia non rappresenta tutti i caratteri che esistono, considerando le centinaia di lettere diverse che ci sono in lingue orientali o anche solo in quelle nordiche o germaniche.
Pe rappresentare anche questi caratteri quindi si usa un charset chiamato ISO-8859-1, Latin 1 per gli amici, che prevede 256 simboli e comprende tutte le lettere “strane” degli alfabeti europei, oltre a qualche altro simbolo tipo lo ¥ o l’€.

Per impostare il charset della propria pagina web ci sono due modi:

  • Tramite un tag <meta> nell’head della pagina
  • Impostado l’header della richiesta HTTP

La prima opzione è indicata specialmente per pagine statiche o per script che producono un’intera pagina, mentre la seconda è l’ideale per script che producono un output per una richiesta AJAX, e quindi non possono inserire tag nell’head della pagina.

Quindi, per visualizzare correttamente i caratteri accentati, nella pagina principale che esegue la richiesta AJAX imposteremo il charset a ISO-8859-1 inserendo nell’head:
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
Mentre nella pagina PHP richiamata con AJAX imposteremo il charset nell’altro modo, ossia:
header("Content-type: text/html; charset=ISO-8859-1");
Ricordandoci che è possibile utilizzare la funzione header() solo prima di aver generato qualsiasi output con echo, print o simili, o verrà generato un errore.

Nello script AJAX sarà importante ricordarsi di codificare e decodificare i dati (siano essi GET o POST) inviati allo script con le funzioni encodeURIComponent e decodeURIComponent

data = encodeURIComponent("dati da inviare");
//...
//invio dei dati con AJAX
//...
document.getElementById("contenitore").appendChild(decodeURIComponent(response));

Ma siccome queste funzioni codificano l’output con il charset UTF-8, nello script PHP dovrò usare una funzione strategica: utf8_decode().
Passando i valori inviati in questa funzione, riconverto il tutto in ISO-8859-1, senza quindi vaere strane sorprese.

Dimenticavo:
Nel caso i dati vengano memorizzati in un database, anch’esso dovrà essere impostato con il charset Latin 1 (ISO-8859-1).
In poche parole, pagina, script e database devono avere lo stesso charset, qualunque esso sia.

Spero di essere stato utile, visto che il problema mi ha dato non poche grane…

Google ci spia, e ci guadagna sopra.

Stavo gironzolando qua e là per Google AdSense, sistema di banner pubblicitari da inserire nel proprio sito per guadagnare con visite e clic, quando mi salta all’occhio questa pagina.

Cosa cosa cosa??
Google memorizza tramite AdSense e Google Search i siti che visito, sapendo quindi praticamente tutto quello che a me piace e cosa io faccio? Ma chi gli ha dato l’autorizzazione?
Loro se ne escono con la frase “ma se cancelli i cookies del browser allora le informazioni saranno cancellate”. E intanto invadono la mia privacy, senza nemmeno dirmelo. Perchè è stata una botta di fortuna che abbia trovato quella pagina, altrimenti ne sapevo tanto quanto prima.

Non è questo il modo. Se scrivessero su quella bella paginona di google.com che sanno in quali pagine navighi, scommetto che non sarebbe altrettanto efficiente il sistema di AdSense. E di solito la prassi non è che “io ti spio, ma se vuoi smetto”, dovrebbe essere “io non ti spio, ma se vuoi comincio”.
Invito tutti a premere quel bel tastone “Opt out” in quella pagina. Secondo loro dovrebbe disattivare per sempre il tracciamento delle pagine sul tuo browser.
Problema risolto! Finchè non cambi browser…

Javascript e PHP: inviare variabili POST tramite scrittura dinamica del codice

Siccome ho implementato il contatore PHP che (se avete letto) utilizza un variabile POST per decidere se aumentarsi o no, ho avuto anche la necessità di trovare un modo per poter inserire dei link interni che mi inviasse la variabile POST che mi serviva.

Siccome ciò non avviene in modo esplicito tramite una query string (sarebbero variabili GET), non posso inserire i valori nell’attributo href del link.
Dopo un po’ di ricerche ho trovato una buona idea: inviare i dati utilizzando una form (nascosta) contenente le variabili da inviare, generando il submit tramite Javascript inserendo il codice nell’evento OnClick del link.


<form method="post" name="data_form" action="index.php">
	<!-- variabile per il contatore -->
	<input type="hidden" name="increase_counter" value="no" />
	<!-- variabile per la pagina da caricare -->
	<input type="hidden" name="p" value="pagina3" />
</form>
<!-- al click sul link, invio i dati della form (che ha metodo POST) -->
<!-- 'return false;' serve per "fermare" il link, che altrimenti, dopo l'esecuzione del codice in OnClick -->
<!-- cercherebbe di andare al documento pagina3, generando ovviamente un errore 404 -->
<a href="pagina3" onclick="document.data_form.submit(); return false;">Vai a pagina 3</a>

Ora però, nonostante il tutto funzionasse, mi trovavo a dover scrivere tutto il codice di una form ogni volta che inserivo un link. Ho quindi scritto una funzione che, prima di inviare il form, impostasse il giusto valore alle variabili a seconda dell’attributo href del link. In questo modo, utilizzo una sola form per tutti i link, con un bel risparmio di codice!


<!-- Nell'head della pagina, inserisco questo tag script: -->
<script language="javascript" type="text/javascript">
//la funzione ha un unico parametro, link_object, che viene passato usando 'this'
//questa rappresenta il link che è stato cliccato
function go_link (link_object) {
	//siccome ai link relativi viene aggiunto automaticamente l'url completo, lo tolgo
	document.data_form.p.value = link_object.href.replace("http://www.example.net/", "");
	document.data_form.submit();
}
</script>
....
....
<!-- All'inizio del body -->
<form method="post" name="data_form" action="index.php">
	<input type="hidden" name="increase_counter" value="no" />
	<input type="hidden" name="p" value="" />
</form>
....
....
<!-- Un link diventa quindi: -->
<a href="pagina3" onclick="go_link(this); return false;">Vai a pagina 3</a>

Una volta completata anche questa operazione, e mentre mi stavo approcciando a farlo in tutte le altre pagine, mi son detto: perchè non implementarlo nel motore PHP?
Così ho aggiunto il codice Javascript una volta sola per tutte le pagine (lo inserisce infatti la pagina PHP) e ho aggiunto una ulteriore funzione che al caricarsi della pagina aggiunge ai link interni (escludendo ancore e link esterni) l’attributo onload che richiama la funzione go_link()
In questo modo ho semplificato enormemente la cosa: riesco a lavorare in background (usando le variabili POST), senza dover riscrivere lo stesso codice per ogni link, grazie a Javascript.


<head>
<script language="javascript" type="text/javascript">
function insert_links_attributes () {
	//grazie all'array links, ho tutti i link contenuti nella pagina
	for (n in document.links) {
		//modifico il link solo se è interno (contiene quindi l'url del mio sito nell'attributo href
		//oppure se è un ancora (link a un punto diverso della stessa pagina, contraddistinto dal carattere #
		if ((document.links[n].href.indexOf("http://www.example.net/") != -1) && (document.links[n].href.indexOf("#") == -1)) {
	 		document.links[n].setAttribute("onclick", "go_link\(this\)\; return false\;", 0);
	 	}
	}
}
</script>
</head>
<!-- Chiamo la funzione insert_links_attributes una volta completato il caricamento della pagina -->
<body onload="insert_links_attributes();">

Contatore visite in PHP

Per il sito della mia classe (che sto ora rinnovando aggiungendo un motore PHP) ho sviluppato un contatore utenti in PHP, che mi conti solamente le visite da link esterni.

Il suo funzionamento è molto semplice: si basa su un file counter.txt che contiene il numero di utenti che hanno visitato la pagina. Se la pagina viene aperta da un link esterno, aumenta il contatore di 1, altrimenti, se si è giunti lì da un link all’interno del sito, mostra solamente il valore attuale del contatore. I numeri sono scritti con immagini prese da un contatore di Altervista (quello che ho dovuto sostituire, almeno così non si nota u.u), che potete scaricare da qui in un pacchetto ZIP.

Ecco qua il codice:

<?php
//nome del file con il valore del contatore
$file_name = 'Pages/counter.txt';
//controllo se devo aumentare il contatore
//ossia se provengo da un link esterno e non interno
//per fare ciò controllo se mi è stata passata la variabile increase_counter
//che invio in POST dai form del menu
if (empty($_POST['increase_counter'])) {
        $can_access = false;
        //ciclo finchè non riesco ad avere accesso al file
        //ciò perchè successivamente lo elimino, e in caso di due interrogazioni simultanee della pagina
        //potrebbe darsi che non trovando il file, ne crei uno nuovo e faccia ripartire il contatore da 0
        while (!$can_access) {
                if (file_exists($file_name)) {
                        //una volta avuto accesso al file, ne metto il contenuto in $count e l'aumento di 1
                        $counter_file = fopen($file_name, r);
                        $count = fread($counter_file, filesize($file_name));
                        fclose($counter_file);
                        $can_access = true;
                }
        }
        //elimino il file
        unlink($counter_file);
        //lo ricreo e lo apro in scrittura
        $counter_file = fopen($file_name, w);
        //e lo ci scrivo il nuovo valore aumentato di 1
        fwrite($counter_file, $count);
        fclose($counter_file);
} else {
        $can_access = false;
        //altrimenti, se provengo da un link interno, visualizzo l'attuale valore
        //in counter.txt
        while (!$can_access) {
                if (file_exists($file_name)) {
                        //una volta avuto accesso al file, ne metto il contenuto in $count
                        $counter_file = fopen($file_name, r);
                        $count = fread($counter_file, filesize($file_name));
                        fclose($counter_file);
                        $can_access = true;
                }
        }
}
//ottengo l'array dei caratteri che formano il numero
$count_chars = str_split($count);
//aggiungo degli 0 davanti al numero per portarlo a 7 cifre
for ($n = count($count_chars); $n < 7; $n++) {
        array_unshift($count_chars, "0");
}
//inserisco le immagini corrispondenti per ogni cifra
foreach ($count_chars as $number) {
        echo "<img src=\"/Immagini/Counter/" . $number . ".gif\" />";
}
?>

La variabile increase_counter la passo tramite il menu del sito, composto da form. Il codice di un pulsante, ad esempio, è questo:

<!-- Il form ha come metodo POST: le informazioni dei campi contenuti in esso saranno passate alla pagina
nell'attributo action, ma a differenza di GET, che le passa esplicitamente nella query della pagina, POST le immette in un array nascosto all'utente ma accessibile dal codice PHP presente nella pagina -->
<form action="index.php" method="POST">
        <!-- Pulsante vero e proprio che l'utente vede -->
        <input type="submit" value="Home" />
        <!-- Campo nascosto  increase_counter a cui assegno un valore (qualunque, l'importante che l'abbia -->
        <input type="hidden" name="increase_counter" value="no" />
</form>

Basterà preoccuparsi di passare increase_counter nei link interni al sito.
Si potrebbe anche utilizzare GET e quindi renderne il passaggio esplicito, visto che non sono private e necessariamente da nascondere, e ciò renderebbe più facile inserire i link

Maiuscole bastarde

Come credo sappiate (probabilmente siete arrivati qui dal link sul blog vecchio) ho trasferito il blog su uno spazio meno costoso, altrimenti non riesco a mantenerlo.

Ho quindi creato un nuovo sito su Altervista utilizzando l’opzione AlterBlog: meno flessibile dell’altersito, ma almeno è gratis 😀
Creato il nuovo AlterBlog, mi accingo a esportare il vecchio nel nuovo spazio web e sono felicemente sorpreso quando mi accorgo della fantastica opzione “Importa blog” che mi permette di importare un blog WordPress già esistente.
Dunque faccio “Importa blog” e mi appare la schermata per inserire URL del blog da importare e credenziali d’accesso dell’amministratore del blog. Entusiasta compilo i campi, clicco su Importa e….

Errore: nessun blog esistente all’indirizzo http://dowsplay.altervista.org/blog/

Armandomi di pazienza, ho riprovato, guardando bene di non fare errori a scrivere l’indirizzo, ma prova e riprova continuava a darmi errore. Così ho deciso di seguire il consiglio che il messaggio d’errore riportava:

….se l’errore persiste, contattaci sul forum.

Vado quindi sul forum di Altervista, faccio per aprire una nuova discussione, e mi viene chiesto di riautenticarmi (per almeno la dodicesima volta). Metto account e password, confermo e.. ta-dah!

Nome utente o password errati

E daie a riprovare finchè non mi sono rotto l’anima e mi sono collegato con l’altro account e aperta la discussione, ho scoperto che lo strumento per importare i blog accettava solo URL con tutte le lettere minuscole e quindi mi fallava sempre. Un moderatore particolarmente efficiente, fixando l’importatore, mi ha poi permesso di importare il blog.

Tutto per una maiuscola

PHP Hypertext Processor

Ieri ero in vena di imparare e mi sono messo a studiare la guida PHP di HTML.it
Davvero ben fatta devo dire, ho imparato tutte le basi in un paio d’ore, e scritto il mio primo (stupidissimo) script PHP

PHP è un linguaggio lato server (cioè eseguito dal server web, non dal computer dell’utente) che restituisce pagine HTML, e che è usato per poter fare pagine web che possano cambiare, pagine dinamiche. Questo stesso blog è scritto in PHP: struttura base sempre uguale, ma a seconda di ciò che richiede l’utente modifica la pagina HTML in output: la pagina per vedere un post ad esempio, è sempre la stessa, ma a seconda del parametro p passato nella query string visualizza un post piuttosto che un altro

Query string: la stringa nell’URL di una pagina dopo il carattere ? che contiene parametri che la pagina utilizzerà per il processamento del codice PHP piuttosto che ASP