Category Archives: internet

Hide disable exclude specific mail account in thunderbird global inbox

I use Thunderbird’s unified inbox as it is a very convenient way to manage multiple accounts. Yet there was a management account where the inbox was a huge pile of messages that needed to stay there but that I didn’t want to show in there unless I specifically clicked on that account.

No other guide on the first page of google results gave info on it, but here’s how you do it.

Right-click on the Inbox master node under which all the accounts’ inboxes are listed, then click on Properties, then Choose the selected folders to search, and un-check the Inbox of the account you want to hide. Then click on Update.

There you go.

Cannot downgrade Xiaomi Router 3 firmware to development version

I bought a Mi Router 3 for the express purpose of installing Padavan firmware on it, but lo and behold, I could not for the life of me install the BIN file for the 2.11.20 development version of the firmware (see here and here) as, when choosing the BIN file from the browse file dialog of the router web interface, the popup just went numb, no action, no buttons, no upload activity. Tried many times it with Firefox Chrome and even Internet Explorer AND Edge, NOTHING!

Then, I found in a forum (can’t remember for the life of me where) that there is an alternative method do to it:

  1. Put the BIN file in an empty USB pendrive
  2. Turn off the router (unplug the power)
  3. Place the USB stick in the router
  4. Press the reset button on the back of the router, and while keeping it pressed plug in the power, and STILL keep the reset switch pressed
  5. Stay there like this for a while contemplating on the meaning of existence
  6. In my case I noticed that after a while (10-15 secconds maybe?) the front led was flashing in a different way than when it just booted up, so I figured I could finally leave the reset switch be
  7. I connected to after a while when the front led became static blue and, go figure, the firmware had downgraded

Firefox not firing onReadyStateChange with two AJAX requests and a confirm dialogue

There are many discussions like this on the internet, but I am confident I am now examining a very specific problem of Firefox, with a very specific AJAX routine.

Symptoms: there are other AJAX requests in the background, and there is a request initiated by the user, which requires confirmation (your average “are you sure you want to delete?” dialogue). The AJAX request to delete the entry never fired. Every AJAX request was in the default, asynchronous mode.

No matter what, I couldn’t for the life of me make that request work, everything else did. Only the “delete” functions didn’t (in Firefox, as Chome and Internet Explorer worked no problem).

I used console.log messages as crumbs along the way, to follow the flow, and it stopped right at onReadyStateChange, as anything after it wouldn’t show zip in the console. Which meant the onReadyStateChange wasn’t being triggered.

I even thought it was something off with the function name (maybe the delete inside it borked something, silly me thought).

I also tried switching to synchronous mode the incriminated requests… and welp, they worked! For a short while I thought about leaving them be synchronous, but I didn’t want those pesky warnings in the console about synchronous requests ruining the user esperience.

Only by chance an epifany happened, and the obvious came forth (I KNEW there had to be a reasonable explanation which I had yet to grasp).

Well, my configuration went like this (it’s an internal web application we are talking about): an AJAX request triggered each second to a PHP to check the user credentials and inactivity time, and an AJAX request by user intervention was launched to delete an entry… after a confirm dialogue requested, well, user confirmation for the delete (duh).

Everything worked flawlessly before, the problem started appearing after I added the background AJAX request each second (which I didn’t want to get rid of anyway).

As you may or may not know, the window.confirm dialogue is a synchronous structure, which means the page execution is frozen until the user presses either OK or CANCEL in the dialogue (same thing happens with the window.alert function).

So, imagine the background AJAX request being queued behind the confirm dialogue, and obviously taking precedence over anything else that had to come after, and the new AJAX request being formed right after the press on the OK button of the confirm dialogue, to delete the entry that the confirmation was for.

Well, these two requests were launched at the very same time, in a manner that Firefox (not the other browsers though) probably overlapped the onReadyStateChange and never triggered the one for the secondary AJAX request.

In fact getting rid of the confirmation dialogue solved the problem.

Either that, or I will have to come up with an asynchronous method of requesting user confirmation.

Conversione automatica valuta estera PayPal su eBay… guardacaso

Ok, è da ieri che anche impostando manualmente la conversione della valuta estera su paypal alla mia carta di pagamento, questo dal sito paypal nel percorso astruso delle impostazioni, comunque il pannello di pagamento di ebay non mi permette di far valere questa opzione e di default usa il cambio valuta di paypal, che naturalmente applica commissioni più care di quelle della mia banca.

Cerco su internet diverse guide, e sembra che qualcun altro stia avendo lo stesso problema, anche fuori dall’italia (vedi qui), nonostante abbia impostato tale parametro correttamente su sito paypal.

Chiamo il numero verde, diversi minuti di attesa, e l’operatrice mi dice:

Lo sappiamo, è un problema tecnico che ci è noto e al momento non esistono soluzioni né ci hanno comunicato i tempi previsti per il rientro della questione




Ops sto scrivendo tutto maiuscolo.

Anche pagando col metodo “carta di credito” dal pannello paypal, e inserendo manualmente i dati della carta, COMUNQUE la valuta viene cambiata in automatico da paypal, e questo senza che appaia una preview del prezzo convertito in euro prima di cliccare su “Conferma e paga”, come invece accade se ci si appoggia al conto paypal.


A giuochi conclusi (se mai si concluderanno questi giochi, secondo me sono solo prove tecniche di appropriazione indebita forzata) dovrebbero quantomeno riconoscere un rimborso sul conto paypal di ciascuna delle persone coinvolte, pari alla cifra spesa in più per colpa di questo “disguido”.

Ma che sia un disguido non ci credo proprio.

Add custom social media share icon buttons in phpBB

Self reference article: how to add responsive social media share icons on your forum for each page.

HTML to add to /styles/stylename/template/over_header.html, right under <div id="page-header" class="page-width">

 <div id="socialshare" data-expendable="yes">
 <a class="twittersharebutton" href="{SOCIAL_URL}" target="_blank">t</a>
 <a class="facebooksharebutton" href="{SOCIAL_URL}" target="_blank">f</a>
 <a class="googlesharebutton" href="{SOCIAL_URL}" target="_blank">g</a>

CSS to add at the bottom of <head> section of same file:

 #socialshare {
a.twittersharebutton, a.facebooksharebutton, a.googlesharebutton {
 text-decoration: none;
a.twittersharebutton {
a.facebooksharebutton {
a.googlesharebutton {
 line-height:60% !important;
#socialshare a:hover {
@media (min-width: 1100px) {
 #socialshare {
#socialshare>div {
 width: 50px;
a.twittersharebutton, a.facebooksharebutton, a.googlesharebutton {
 #page-header {position:relative;}
@media (max-width:1099px) {
 #socialshare {
a.twittersharebutton, a.facebooksharebutton, a.googlesharebutton {

Code to add in /includes/functions.php, in the template variable assignment (look for LAST_VISIT_DATE)

'SOCIAL_URL' => urlencode(generate_board_url() . '/' . $user->page['page']),

Reload, all done!

AFWall+ and Linux Deploy, no internet access unless firewall is disabled

This is a personal reminder and also an easier-to-find heads up to those looking for a solution: if you installed linux on Android via Linux Deploy, and find that, no matter how you set rules on AFWall+ you can never get internet to the mounted linux image, unless you disable the firewall altogether (not recommendable since you installed a firewall in the first place), then here is the solution provided in this thread (it’s all due to DNS calls being blocked without a possibility to make them pass through in the vanilla AFWall+).

Under AFWall+ contextual menu, open the custom script editor, and inser these lines:


$IPTABLES -A afwall-wifi -m owner --uid-owner root -p udp --sport=67 --dport=68 -j RETURN
$IPTABLES -A afwall-wifi -m owner --uid-owner nobody -p udp --sport=67 --dport=68 -j RETURN
$IPTABLES -A afwall-wifi -m owner --uid-owner root -p udp --sport=53 -j RETURN
$IPTABLES -A afwall-wifi -m owner --uid-owner nobody -p udp --sport=53 -j RETURN
$IPTABLES -A afwall-wifi -m owner --uid-owner root -p tcp --sport=53 -j RETURN
$IPTABLES -A afwall-wifi -m owner --uid-owner nobody -p tcp --sport=53 -j RETURN
$IPTABLES -A afwall-3g -m owner --uid-owner root -p udp --dport=53 -j RETURN
$IPTABLES -A afwall-3g -m owner --uid-owner nobody -p udp --dport=53 -j RETURN
$IPTABLES -A afwall-3g -m owner --uid-owner root -p tcp --dport=53 -j RETURN
$IPTABLES -A afwall-3g -m owner --uid-owner nobody -p tcp --dport=53 -j RETURN

making sure you preserve the line return after each RETURN since pasting directly into the tiny textbox of AFWall+ may lose the carriage returns.

BAM you will have internet from your android linux without having to disable the firewall. Naturally, you will also have to enable internet access to “Applications running as root”.

Update: as per Peter’s suggestion in the comments (thank you Peter!) if you still get errors with this approach you may need to add a couple more lines, like so:

$IPTABLES -A afwall-wifi-wan -m owner –uid-owner 5000 -j RETURN
$IPTABLES -A afwall-wifi-lan -m owner –uid-owner 5000 -j RETURN

where “5000” is an id you have to customize to your needs, and you can get it either from AFWall’s errors logs, or by checking the /etc/passwd file for the current user’s entry.

Installare deluge bittorrent su Ubuntu con controllo remoto via interfaccia web

Caso tipico: avete, o volete impostare, un server domestico con Ubuntu Linux, che agisca anche da server sempre attivo per il download e upload via bittorrent.
La richiesta di risorse è molto limitata e potete resuscitare un sistema molto vecchio, ad esempio persino un vecchio laptop su cui non volete nemmeno installare l’interfaccia grafica (a patto che abbia un disco sufficientemente capiente per i file che volete scaricare).

L’idea di partenza è avere quindi un server che faccia tutto dietro le quinte, e che sia sicuro.

Questa guida è ispirata ad altre due guide: questa e quest’altra.

Se volete la spiegazione dei vari passaggi, la trovate in fondo all’articolo.

Installato Ubuntu (l’ultima versione, al momento di scrivere, è la 12.04 Precise Pangolin) eseguite i seguenti comandi:

sudo adduser --disabled-password --system --home /home/deluge --gecos "BitTorrent Service" --group deluge
sudo mkdir /home/deluge/Incoming
sudo chown deluge:deluge /home/deluge/Incoming
sudo mkdir /home/deluge/Completed
sudo chown deluge:deluge /home/deluge/Completed
sudo add-apt-repository ppa:deluge-team/ppa
sudo apt-get update
sudo apt-get install deluged deluge-webui

Quindi create il file /etc/init/deluge.conf:

sudo nano /etc/init/deluge.conf

e incollate all’interno il seguente testo:

start on (filesystem and networking) or runlevel [2345]
stop on runlevel [016]
env uid=deluge
env gid=deluge
env umask=000
exec start-stop-daemon -S -c $uid:$gid -k $umask -x /usr/bin/deluged -- -d

per salvare premete Ctrl-X e date l’ok premendo Y o S a seconda che Ubuntu sia in inglese o italiano.

Poi create il file /etc/init/deluge-web.conf:

sudo nano /etc/init/deluge-web.conf

e incollate all’interno il seguente testo:

start on started deluge
stop on stopping deluge
env uid=deluge
env gid=deluge
env umask=027
exec start-stop-daemon -S -c $uid:$gid -k $umask -x /usr/bin/deluge-web

Per avviare deluge è necessario il comando:

sudo start deluge

e per terminarlo:

sudo stop deluge

mentre per riavviarlo (ad esempio dopo modifiche alla configurazione, perché queste ultime abbiano effetto):

sudo restart deluge

L’interfaccia web di deluge si avvia e chiude contemporaneamente al demone, quindi non è necessario intervenire su quest’ultima; raggiungerla è piuttosto banale: se avete un’interfaccia grafica installata sullo stesso server, aprite un browser e dirigetevi all’indirizzo http://localhost:8112 (la password di default è deluge), altrimenti se vi collegate da un altro PC della rete, allora usate http://:8112.

Una volta ottenuto l’accesso all’interfaccia web è consigliabile effettuare alcuni cabiamenti: innanzitutto se vi compare la finestra di connessione al demone, premete il pulsante Connect, quindi dal pannello in alto premete Preferences e passate in rassegna le varie sezioni.

Vorrete probabilmente ridimensionare il numero di connessioni totali (400-500), attivare la crittografia (mettete “Enabled” e “Full stream” nelle varie opzioni e attivate la casellina), impostare correttamente le cartelle di salvataggio dei file (mettete /home/deluge/Incoming per i file in arrivo, e /home/deluge/Completed per i file che sono stati scaricati completamente), nella sezione Interface cambiate la porta di default ad una diversa, e attivate la casella SSL, cambiate la password premendo il pulsante Change sotto alle caselle di testo, e quindi riavviate deluge per attivare i cambiamenti. Se avete attivato SSL e cambiato porta, ad esempio 1234, vi dovrete collegare all’indirizzo https://localhost:1234 (oppure https://:1234), fate attenzione al protocollo, che diventa https con la “s” finale che sta per secure. Il vostro browser vi farà presente che il sito richiede dei certificati, se siete su Firefox cliccate su “Aggiungi eccezione” e salvate il certificato, oppure seguite gli opportuni passaggi sugli altri browser.

Cosa fa questa guida esattamente?

I vari comandi all’inizio servono a creare un utente riservato a deluge (invisibile dalla schermata di login, si potrebbe dire che è un account “di servizio”), in modo che una eventuale compromissione dell’account da remoto tramite una falla di sicurezza del demone deluge non metta a rischio l’intero server; viene creato un account chiamato deluge appartenente al gruppo deluge con cartella home uguale indovinate un po’ a /home/deluge nella quale verranno poi create le cartelle Incoming e Completed destinate rispettivamente ai file in fase di download ed allo spostamento di questi ultimi una volta completati. Il comando chown serve a attribuire all’utente deluge la proprietà delle suddette cartelle, siccome le creiamo come utente root.
Nella creazione dello script di avvio delige.conf il valore 000 assegnato al parametro umask serve a permettere l’accesso in lettura e scrittura alle cartelle di scaricamento di deluge da parte degli altri account del server, in modo che ad esempio non sia necessario impostare mirabolanti configurazioni multiutente per accedere via Samba alle cartelle del server da un pc Windows.
Se avete installato Samba, piuttosto, potete molto più facilmente accedere al server deluge usando, tramite rete locale, il nome del server. Se ad esempio l’hostname della macchina Ubuntu è pincopallo, una volta che avete impostato il WORKGROUP in /etc/samba/smb.conf ad un nome uguale al workgroup del vostro PC Windows, dal vostro browser preferito potete digitare semplicemente https://pincopallo:1234 (se vogliamo seguire l’esempio precedente).

Le “subitate” (i contatti più pazzi ricevuti su e eBay annunci)

Dopo l’ennesima offerta folle ricevuta per un oggetto in vendita, ho deciso di aprire questo post per riportare tutti i contatti assurdi ricevuti, come venditore, tramite ed eBay annunci, i due siti di compravendita gratuiti più usati in Italia.

La più gettonata in assoluto, per qualunque oggetto:

ancora disponibile?

dopodiché nel 95% dei casi non si fanno più vivi (a questo punto penso sia un modo come un altro per fare phishing di indirizzi email)

Segue breve sezione con tutti i messaggi chiaramente di phishing di indirizzi email:

  • è la vostra voce ancora in vendita? quanto vuoi per la vendita? Vorrei conoscere la condizione di questo oggetto ora ..
  • sella salut est ce point encore disponibles à la vente
  • ciao buon giorno sono interessato a questo tuo bene, se è disponibile
  • hallo Verkäufer sind d Versandbereitschaft 4 sale wenn ja freundlicherweise mailen Sie mir jetzt ……
  • Hi is this item still available??? holla back asap Sent From My virgin Mobile®
  • Voglio comprare questo articolo da voi e sono pronto a pagare
  • Ciao,
    Il mio nome è Samuel Ezechiele.
    Il tuo tuo oggetto ancora disponibili per vendita??
    Sono molto interessato ad acquistare immediatamente.
    Mi ha gentilmente risposto nel più breve tempo possibile.
    Il capitano Richard.
  • ditemi se questa voce è ancora disponibile per la vendita
  • è la vostra voce ancora in vendita e in buone condizioni.
  • ww45149 , Ciao! Sembra un buon affare, posso contattarti?
  • vorrei comparare

Di tutto un po’ (le voci più recenti saranno aggiunte mano a mano in cima a questa lista):

  • si ma nuovi le vendono a 200 accetti 100 euro? (condizionatore proposto a 400€, mai utilizzato ed imballato da qualche anno, e che vendevo al 70% del costo più basso trovato su internet per lo stesso modello)
  • 200 in contrassegno (vedi sopra)
  • facciamo 200 euro e lo passo a prendere anche domaniii (desktop completo a 250€, scritto chiaro e tondo “non mi chiedete sconti”)
  • ciao, il telefono con il problema auricolare è difficilmente vendibile, 40€ mi sembra un po troppo se per te va bene per liberartene posso offrirti 20€ fammi sapere (cellulare Android  a 40€, con l’auricolare interno difettoso, ma perfettamente funzionante con auricolare a filo o bluetooth)
  • 20 euro ritiro ad albano (vedi sopra)
  • Salve, a me potrebbe interessarmi il tubo, sa dirmi forse il prezzo per il trasporto a siracusa??? Grazie (quantità considerevole di tubature da irrigazione, ad un ottimo prezzo, ma assolutamente non spedibili per il volume occupato)
  • salve il prezzo e per tuto ? e puoi spedire ? sempre a mio carico perche abbito a milano si puoi fammi sapere quanto costa spedizione (vedi sopra)
  • 20 euro (telegrafico; era una stufa a gas svenduta a 30€)
  • interessatro acquisto prezzo 50 euro compreso trasporto pagamento postapay saluti (PC a 60€)
  • fa schifo buttala è meglio (sedia a rotelle)
  • Ma si può mettere l’annuncio di una sedia a rotelle ( e che sedia…) nella categoria delle bici? Ignorante!!! (il represso di turno… e non c’erano categorie più appropriate tra cui scegliere, incredibile ma vero)
  • Salve..8 comprese spese? GRAZIE (scheda audio a 10€)
  • ti pago il passaggio di proprietà (vecchia auto funzionante a 200€)
  • ciao mi chiamo Paolo non ho lavoro da offrirti, sono sincero, però ho visto la foto del tuo annuncio e sono rimasto colpito sei davvero molto bella, mi farebbe piacere conoscerti se me lo permetti, ti lascio la mia mail (risposta ad un annuncio per ripetizioni di matematica inserito per conto della mia ragazza)
  • salve con 30 euro + spedizione me lo vende (portatile funzionante da 50€)
  • BUTTALOOOOOOO!!!!! (portatile di cui sopra)
  • 270 euro e vengo a ritirarlo io…di piu nn interessa (pc superaccessoriato a 350€)
  • me ne serve uno….che ci faccio con tre computer ? (3 pc venduti “in blocco”)
  • buona sera se facciamo 15 euro me la vengo a prendere puoi farmi sapere (lavatrice funzionante svenduta a 30€)
  • ciao sono stefano!!15 euro e me lo vengo a prendere (disco esterno 2.5″ da 60GB a 25€)
  • Salve, posso offrire 10€ massimo, capisco che l’offerta sia al di sotto della richiesta ma l’hd è un pò piccolino come capienza (vedi sopra)
  • senti se tela seti chiamami queste è il mio numero
  • Ciao e BUON ANNO. La macchina è molto vecchia e considerando l’età da un giorno all’altro potrebbe non funzionare più. Ciò premesso al max potrei arrivare a 50 Euro con spedizone in contrassegno. So già che dirai che è una cifra ridicola e piuttosto lo regali o lo butti. Al posto Tuo farei anche io così. Concludo suggerendo che se non lo vendi, NON REGALARLO, piuttosto buttalo, perché sia amici, sia parenti, se qualcosa va storto il meno che Ti può capitare è … bella macchina che mi hai regalato, ho perso tutti i dati che avevo caricato … della serie “CURNUTO e MAZZIATO” … ciao e SCUSA per mio suggerimento Roby (portatile datato ma perfettamente funzionante, a 70€)
  • offro 30 euro. (cellulare con TV da 60€)
  • salve, le mie finanze non mi permettono di spendere più di 40 euro (come sopra)
  • *Conclusioni* Forse è meglio se lo tieni per te. Gli utenti come te purtroppo stanno rovinando il retrocomputing ;-( Buona serata (pc storico IBM… lo voleva regalato per preservare la gloria del retrocomputing?)
  • ti offro 20 euro ok? (scheda madre integrata Atom, venduta a 50€)
  • buona sera,se soni ancora disponibili ,posso offrire non piu’ di € 200,00.grazie. (due poltrone capitonné d’epoca a 500€)
  • senti sono interessato alcquisto di entrambi a 50 euro, non intendo chiederti sconti solo di regalarmi la spedizione (due dischi esterni da 500GB, 70€)
  • Ciao, volevo poporti di vendercelo a 50euro più le sp dato che ho gia’ la maggior parte del corso (corso completo originale Fabbri, 100€)
  • compro tutto prezzo foufee

Cache PHP to gzipped static html pages using htaccess redirect

No matter the server performance, the fastest kind of website for a visitor is the one with static HTML pages; this way the server just has to upload existing data to the browser instead of starting the PHP compiler, open a connection to MySQL, fetch data, format the page and only after that send it. Less CPU, less memory, less processing time, more users served in a smaller amount of time.

Avoiding PHP and MySQL execution altogether is the key, and my project was to create something similar to WPSuperCache to be implemented in a generic PHP website, so my thanks go to said plugin’s developers for the source code that gave me precious tips.

DISCLAIMER: this guide presumes you are “fluent” with PHP and .htaccess coding, and is meant to just give directions as of how to obtain a certain result. This guide will not give a pre-cooked solution to just copy/paste into your website, you need to change/add code to adapt it to your need; I am not the person who can help you if you need a tailored solution, simply because I have no time to give away free personalized help!

Here’s our logical scheme:

  1. Visitor comes to website and asks for page X;
  2. Webserver checks via .htaccess (avoiding PHP execution) if there is a static cached version of page X already, and in this case either serves the gzipped file (if the client supports it), or falls back to the uncompressed HTML version… at which point our scheme ends; otherwise, if no cached version exists, continue to next step;
  3. PHP builds the page and serves it to the visitor as “fresh”, but at the same time saves the HTML output as both gzipped and uncompressed version so the next visitor will directly download that one.

This is a very bare procedure, which needs to be perfected by the following conditions:

  • Not all pages need to be cached (those that by their very nature change very often, like real time statistics, a poll, whatever)… in fact, some pages must NOT be cached (those that only moderators can view, both for secutiry reasons and consistency); PHP must avoid caching those pages, so that htaccess will always serve the “fresh” PHP page.
  • Pages that do get cached still need to be refreshed, because sometimes they can change (articles or posts that are modified/updated with time, or where comments get posted).
  • Some pages, even if the corresponding cached version exists, must NOT be served from cache, for instance when POST data is submitted (sometimes, also with GET, you as the webmaster should know if this is the case), that requires PHP to be handled.

Define “caching behaviour” in PHP

This section is where you set the variables that the PHP caching routine will check against to know when and how to do its job.


And explained:

  • $cachepath is the folder where you want the static (HTML and gzip) files to be stored (the website I use this caching routine on, has all the pages accessible from root folder with rewrite URLs, in other words the only slash in the URL is the one after the domain name, and I have no need to reproduce any folder structure; if you do, you’re on your own);
  • $cachesuffix is a string I need to add to the URL string (for example if address is then in this case the cachefile will be named pagename-static), this is useful if you want to cache the homepage which is not named index.php but is just because in that case, the cachefile name would be empty and .htaccess won’t find it;
  • $nocachelist is an associative array where you have to add as many keys (pointing to a value of 1) as the pages you don’t want (for whatever reason) to cache; in the key name you have to put the string the user would write in the URL bar after the domain name slash to get to the page, for example if you don’t want to cache you would be using “statistics”=>1 in there, as already is.

Have PHP actually save to disk the cached pages

     if (
        !isset($nocachelist[$_GET["page"]]) &&
        !$_SESSION["admin"] &&
        !count($_POST) &&
    ) {
        //build the uncompressed cache
        if (!file_exists($cachepath.$url.$cachesuffix.".html")) {
        //build the compressed cache
        if (!file_exists($cachepath.$url.$cachesuffix.".html.gz")) {

Pretty much self explanatory, isn’t it.
No seriously, do you really want me to elaborate?

Ok, you got it.

  • The $_GET[“page”] is a GET value I set early in the code to know where we are in the website, you can use any variable here as long as you can check it against the $nocachelist array;
  • The other conditions in the first if should be clear, they avoid building the page’s cache if the CMS’s admin is logged (security) or if POST data is submitted or if there is a query string appended to the URL (consistency/stability);
  • $url is a variable that I define early in the code, and contains the string after the domain name slash and before the query string question mark, basically the kind of string you fill the $nocachelist array with (if you were paying attention, you may now think I have a redundant variable since $url and $_GET[“page”] should be the same, but this is not the case for other reasons);
  • $html is the string variable that, across the whole CMS, defines the raw HTML code to echo at the end of the PHP execution; you can either do like I do and define such string, or use an output buffer to obtain HTML if you instead print the HTML directly to screen during PHP execution;
  • ~creationtime is a “hotkey” I use in my template to plug in the time in seconds that was needed to create the page in PHP; since I am creating a cached version now, the creation time of the page is zero, because it’s already there to be downloaded from the browser instead of having to be compiled by the server, so in there I print either “cached&gzipped” for clients that support gzip, or only “cached” when the browser doesn’t not; you can safely strip out this part, as this is more of a eyecandy/nerdy/debug thing.

Let .htaccess send the cached files before starting the PHP compiler

AddEncoding x-gzip .gz
<FilesMatch "\.html\.gz$">
    ForceType text/html

RewriteCond %{REQUEST_URI} !/forum/
RewriteCond %{QUERY_STRING} !.*=.*
RewriteCond %{HTTP:Cookie} !^.*(isadmin|nocache).*$
RewriteCond %{HTTP:X-Wap-Profile} !^[a-z0-9\"]+ [NC]
RewriteCond %{HTTP:Profile} !^[a-z0-9\"]+ [NC]
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond /full/path/to/your/htdocs/cache/$1-static.html.gz -f
RewriteRule ^(.*) /cache/$1-static.html.gz [L,T=text/html]

RewriteCond %{REQUEST_URI} !/forum/
RewriteCond %{QUERY_STRING} !.*=.*
RewriteCond %{HTTP:Cookie} !^.*(isadmin|nocache).*$
RewriteCond %{HTTP:X-Wap-Profile} !^[a-z0-9\"]+ [NC]
RewriteCond %{HTTP:Profile} !^[a-z0-9\"]+ [NC]
RewriteCond /full/path/to/your/htdocs/cache/$1-static.html -f
RewriteRule ^(.*) /cache/$1-static.html [L]

This is the code you need to plug in .htaccess, preferably after everything else, but before defining the custom error pages; anyway, since you should be .htaccess-fluent, you shouldn’t need to be told where this fits best.

Some detailing for the curious:

  • First bit is needed (at least I needed it) to serve the gzipped files in a way that the browser knows to handle, otherwise I just got gibberish (the gzip was being sent to output without being uncompressed by the browser first);
  • if the cookies “isadmin” or  “nocache” exist, the cache version of the page will not be served even if it exists; easy explanation, if an admin is logged, and there is special content on a page that only admins can see, you don’t want them to see the “vanilla” cached version of the pages instead; so it’s your duty in this case to set a “isadmin” cookie when an admin logs in, and remove it when the admin logs out;
  • choosing the correct full path on the webserver was a bit tricky in my case, I can’t quite remember where the issue was, but depending on the method I used to get it I had different paths; I kind of remember I had to choose between $_SERVER[“DOCUMENT_ROOT”] and dirname(__FILE__) because only one was working with .htaccess;
  • you don’t really have to change much in this code snippet unless you have particular needs; the /forum/ path exclusion could be irrelevant in your case;
  • thanks go to WPSuperCache developers for their .htaccess code that I stole to build this snippet!

Last but not least

The cache is there to help you, not to give an handicap to your website. You choose what the cache performance should be, and how many times it should get served instead of the dynamic page, before considering it paied off, but you have to clear the cache from time to time to make sure you’re not serving outdated stuff to your visitors.

In my case I use an external cronjob provider ( to trigger a PHP routine every night which includes the following:

while (($file = readdir($handle))!==false) @unlink("cache/".$file);

so that everyday the website starts off with a fresh cache. Not less important, you should clear the cache of a single page as soon as you know that page changed, unless you’re ok with the changes being visibile only the next day after all the cache is cleared anyway. Example: you edit a page while logged as admin, or a user posts a comment, or anything happens that you have control over that alters in any way the page’s HTML: simply use unlink() to delete both the gzipped and uncompressed caches and the website will recreate them with the updated content.


Have fun with pimping up this draft!

Redirect mobile and desktop users to the correct CSS via .htaccess file

The winning solution for a mobile compliant website is to have a single HTML structure for every visitor, and just choose the appropriate CSS to render the page correctly for both worlds; so, I’m not speaking here about a /mobile/ folder under your website root, nor any ?mobile query string, but about the exact same URL, that has all the candy when viewed by a desktop browser, while is super-compact and sleak if the browser is running on a smartphone.

How do you do that?

First, get yourself two stylesheets suited for desktop and mobile, or rather, like I did, prepare a set of CSS files, where there is a “common.css” always used, and a “desktop.css” and “mobile.css” that get loaded when needed. Also, get yourself minify in order to define a “desk” and a “mobi” group of files to load with a simple URL (I use this procedure on another website).

Then add this to your .htaccess file, fairly early after the start of the RewriteEngine:

RewriteCond %{HTTP_USER_AGENT} !^.*(2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800).* [NC]
RewriteCond %{HTTP_user_agent} !^(w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-).* [NC]
RewriteRule ^stylesheets\.css$ /min/index.php?g=cssdesk [L]
RewriteRule ^stylesheets\.css$ /min/index.php?g=cssmobi [L]

First of all, these directives do not consider the iPad as a mobile device, since it has plenty of screen space in my opinion to just work as a desktop browser; if you do not agree, just insert an “ipad” string in there together with the rest.

Some explaining is in order: credit for the RewriteCond’s goes to the creators of WP Supercache, which has been a model to write my own caching system on another website of mine (next article is coming about the caching routine itself).

Those RewriteCond lines tie the next RewriteRule directive only to desktop browsers (since they exclude all user agents that can be associated to mobile devices), so the first RewriteRule redirects to the /min/index.php?g=cssdesk minified CSS set tailored for desktops (change it as needed). The [L] directive right after it tells .htaccess to ignore everything else after that. When the RewriteCond’s are not met, it means a mobile device is coming to your site, so the first RewriteRule is ignored, and the one after that is used that redirects to the mobile CSS set instead.

You see that I redirect requests for a file named “stylesheets.css”. Guess what, this file doesn’t exist on my website, and it just references from the template’s HTML: it’s .htaccess that deals with giving the browser the correct data. In this case all you have to do is reference the stylesheets in your HTML code like this:

<link rel="stylesheet" href="/stylesheets.css" type="text/css" media="screen" />

The advantages? This is faster if compared to the same operation done with PHP, since a webserver processes .htaccess directives faster than it compiles PHP code, plus you may be saving an additional include operation on an external PHP file. Also, an approach of this type is absolutely needed if you want to implement a no-PHP-execution caching system, because then there would be no PHP code to use to address browsers to the desktop or mobile CSS, would it.