Remove qTranslate from WordPress and keep translations

This website goes BACK in time, hell if it does. It started maybe in 2001, with static HTML. Then I played for a while with SHTML, before jumping head on to PHP and writing my own, ugly CMS.

I already had italian and english articles translated in here, and when I switched to wordpress (oh, joy and pain) I found this nifty plugin called qTranslate that kind of automated the translation management.

Looked like a good idea back then, so I installed it and moved all bilingual articles in it.

Yeah, looked like a good idea back then.

After a while though, as WordPress updates progressed, I noticed how I couldn’t write new posts anymore because the custom editor changes broke… either that, or I had to manually add the language tags, or I had to keep back the version of WordPress not to lose editor function. NO BUENO!

Until qTranslate stopped working altogether, and sorry guys, it’s not mantained anymore, f*ck you!

Luckily qTranslate-X was released, giving some more oxygen to my rare yet constant contribution to this blog.

Then, guess what, even qTranslate-X was discontinued.

Luckily qTranslate-XT came out, and it’s on GitHub, so as far as I see it’s actively followed, developed, improved… stil it doesn’t cut it for me.

I mean, developers are doing a GREAT job… can you imagine what a huge hassle is following development of a tool so complex, and coordinating the efforts of several people, while trying to keep the code working after major WordPress updates are released?

There must be a ot of people who thank anything that is sacred for qTranslate-XT’s existence.

I’m not one of those, since especially lately, I’m either releasing articles in italian, or in english, so there is not a lot of translations going on.

Everytime I searched for methods to remove qTranslate, every strategy involved choosing a language to keep, and just thrasing the others! As if I didn’t invest a LOT of time translating! Why should I waste al this work?

I used to think the work to do that by myself was going to be immense so I never tried, until today, when I achieved the objective, and am now happily composing, on Gutenberg editor, with a qTranslate-less version of my blog, where every article has been kept, and the URL redirection of “ephestione.it/it/title” has been fixed and redirected to “ephestione.it/it-title”.

What’s the strategy? Well, I built the code for my own website (and I am NOT willing to customize it for yours, unless you offer to pay me), so these are the premises:

  1. Not every article is bilingual, on the contrary most are either in english or italian
  2. I obviously want to mantain both languages
  3. I don’t care if the blogroll will show both italian and english articles (some the translation of the other) in the same sequence
  4. I want to keep the existing database entry, and add another database entry for the additional language (if present), in this second case english is kept to its original database row, and italian is inserted as a new row
  5. It is made to work with bilingual sites, but in reality it will most definitely work fine with multilingual sites and you may even not need to edit anything; still, you are expected to have familiarity with PHP to run it with confidence (BACKUP DATABASE FIRST!!!!11!!!1oneone)

Following is the code.

<?php

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

$dbhost="localhost";
$dbname="dbname";
$dbuser="dbuser";
$dbpass="dbpass";

function doquery($query) {
	$result=mysqli_query($GLOBALS["dbcon"],$query);
	if ($report=mysqli_error($GLOBALS["dbcon"])) {
		die($report);
	}
	return $result;
}

function excerpt($string) {
	return substr($string, 0, 30)."...";
}

$erroremysql=false;

$GLOBALS["dbcon"]=@mysqli_connect($dbhost, $dbuser, $dbpass);
if (mysqli_error($GLOBALS["dbcon"])) $erroremysql=true;
@mysqli_select_db($GLOBALS["dbcon"],$dbname);
if (mysqli_error($GLOBALS["dbcon"])) $erroremysql=true;
@mysqli_set_charset($GLOBALS["dbcon"],'utf8');
if (mysqli_error($GLOBALS["dbcon"])) $erroremysql=true;

$posts=doquery("SELECT * FROM wp_posts WHERE post_type='post'");

$a=array("<!--:","-->");
$b=array("\[:","\]");
$lang=array("it","en");
$main="en";

echo '<font face="Courier New">';

while ($post=mysqli_fetch_assoc($posts)) {
	echo "<strong>post {$post["ID"]}</strong>:<br/>";
	if (strpos($post["post_title"],"[:en]")!==false || strpos($post["post_title"],"[:it]")!==false) {
		$s=$b;
	}
	else if (strpos($post["post_title"],"<!--:en-->")!==false || strpos($post["post_title"],"<!--:it-->")!==false) {
		$s=$a;
	}
	$data=array();
	foreach ($lang as $l) {
		if (preg_match('/'.$s[0].$l.$s[1].'([\s\S]+?)'.$s[0].$s[1].'/',$post["post_title"],$matches)) {
			$data[$l][0]=$matches[1];
			preg_match('/'.$s[0].$l.$s[1].'([\s\S]+?)'.$s[0].$s[1].'/',$post["post_content"],$matches);
			$data[$l][1]=$matches[1];
		}
	}
	if (count($data)>1) {
		foreach ($data as $k=>$v) {
			echo "$k: ".excerpt($v[0])." - ".excerpt(strip_tags($v[1])).(($k==$main)?" main":"")."<br/>";
			//it is the main language, just updates post stripping the other language
			if ($k==$main) {
				doquery("UPDATE wp_posts SET post_title='".mysqli_real_escape_string($dbcon,$v[0])."', post_content='".mysqli_real_escape_string($dbcon,$v[1])."' WHERE ID=".$post["ID"]);
				echo "\n";
			}
			//it is not, so creates a new post copying over the rest of the data
			else {
				doquery("
					INSERT INTO wp_posts (
						post_author,
						post_date,
						post_date_gmt,
						post_title,
						post_content,
						post_modified,
						post_modified_gmt,
						post_name)
					VALUES (
						{$post["post_author"]},
						'{$post["post_date"]}',
						'{$post["post_date_gmt"]}',
						'".mysqli_real_escape_string($dbcon,$v[0])."',
						'".mysqli_real_escape_string($dbcon,$v[1])."',
						'{$post["post_modified"]}',
						'{$post["post_modified_gmt"]}',
						'".$k."-{$post["post_name"]}')
				");
				echo "\n";
			}
		}
	}
	else  {
		echo "1: ".excerpt($data[key($data)][0])." - ".excerpt(strip_tags($data[key($data)][1]))." main"."<br/>";
		doquery("UPDATE wp_posts SET post_title='".mysqli_real_escape_string($dbcon,$data[key($data)][0])."', post_content='".mysqli_real_escape_string($dbcon,$data[key($data)][1])."' WHERE ID=".$post["ID"]);
		echo "\n";
	}
}

This is what you need to add to .htaccess in the root of your public_html folder, obviously adapting it to your needs, and adding more, similar rows if you have additional languages:

RewriteRule ^it/([a-z0-9\-\_]+)/$ /it-$1/ [R=301,L]

In my case, it worked like a charm, even if not without some cold sweats.

PHP script to batch download from Wallpaperscraft website

I found myself interesting in renewing my wallpapers gallery, and Wallpaperscraft website is really full of themed collections, like, it’s huge!

But then, who’s going to just download every picture by hand, right? Well I know PHP, so one morning when I had the time I jotted down some lines of code.

This script is working at the time of writing, but any change occurring in the source code or in the url structure may break it (not that it would be so hard to fix it anyway).

This is clear to anyone who can run a PHP script, just change the configurable values at the top, the folder name “Wallpapers” in the code, and it’s good to go.

<?php

$webbase="https://wallpaperscraft.com/catalog/city/page";
$imgbase="https://images.wallpaperscraft.com/image/";
$res="_1920x1080";

$i=1;
$c=1;
$goon=true;
while ($goon) {
	echo "\nscarico da $webbase$i\n\n";
	$html=file_get_contents($webbase.$i);
	if (strpos($html,"<html>")===false) {
		$html=gzdecode($html);
		if (strpos($html,"<html>")===false) {
			echo "pagina $i sballata, riscarico...\n";
			sleep(2);
			continue;
		}
	}
	preg_match_all("/<a class=\"wallpapers__link\" href=\"([\/a-z0-9\_]+)\">/",$html,$matches);
	//var_dump($matches);
	if ($matches[1][0]) {
		foreach ($matches[1] as $image) {
			$image=explode("/",$image);
			$image=end($image);
			if (!file_exists("Wallpapers/$image.jpg")) {
				$handle=@fopen($imgbase.$image.$res.".jpg", 'rb');
				if ($handle) {
					echo "$i:$c $image ...\n";
					file_put_contents("Wallpapers/$image.jpg",$handle);
					fclose($handle);
					$c++;
					//https://images.wallpaperscraft.com/image/pier_dock_sea_dusk_shore_118549_1920x1080.jpg
				}
			}
		}
	}
	else {
		$goon=false;
	}
	//sleep(1);
	$i++;
}

Firmware con root e senza cloud dell’aspirapolvere robot Xiaomi

Per saltare alle istruzioni, senza leggere l’interessantissimo e utile preambolo, clicca qui.

Il Mi Robot, anche conosciuto come Xiaomi Vacuum Robot, o Mijia Robot, è un aspirapolvere che vale ben più del suo prezzo (attualmente lo si trova a 210€ spedito con offerte che vengono periodicamente ripetute), con la mappatura della casa, il percorso intelligente di pulizia, e il ritorno automatico alla base per ricaricarsi e poi continuare, se il primo passaggio non può essere completato con la capacità di carica residua.

Sono usciti numerosi modelli successivi, quello direttamente a lui superiore è il Mijia 1S, che aggiunge 200MPa di potenza massima di aspirazione, dei percorsi “ancora più intelligenti”, una telecamerina sul dorso per spiare riconoscere ancora meglio casa nostra, e la possibilità di definire stanze, e impostare nmuri virtuali, direttamente dal programma. Quest’ultima funzione in particolare è quella secondo me più interessante, perché evita di dover acquistare i costosissimi, e francamente brutti. cordoli di plastica nera per delimitare le aree di casa.

Come tutti i prodotti Xiaomi però ha un difettaccio brutto: si collega al cloud della casa madre per caricare una pletora di dati ignoti (in quanto crittografati), che in particolare per questo modello saranno i dati del WiFi e la piantina di casa… non so voi, ma a me scoccia assai.

Il metodo più semplice per bloccare evitare questo comportamento, è evitare del tutto di collegarlo all’applicazione, lasciandolo quindi orfano di rete; questo però significa che il robot manterrà aperto il suo AP hotspot WiFi senza password, al quale chiunque potrà collegarsi, anche il vicino di casa dall’altra parte del muro. Inoltre questo non permette di visualizzare la piantina di casa, comandarlo da remoto, ecc ecc.

Potete inoltre associarlo all’applicazione Mi Home, avendo però l’accortezza di bloccare tramite router le connessioni esterne originate dal MAC address del robot, in modo che non possa inviare dati rimanendo comunque accessibile dall’applicazione; però a quel punto potrebbe essere Mi Home a inviare dati al posto del robot… AHHHH che dilemma.

A proposito, se avete problemi di connessione tra Mi Robot e Mi Home, accertatevi di aver attivato il GPS e aver concesso all’applicazione il permesso di accedere ai dati di geolocalizzazione, altrimenti non si collegherà mai… ahi questi cinesi.

Insomma, ci vorrebbe un modo per farlo funzionare senza collegarlo al cloud, ma mantenendo piantina, controllo remoto, e magari aggiungendo altre funzioni interessanti.

Quello che segue è un tutorial pane e salame:

  1. Scaricare l’applicazione XVacuum, disponibile per Android e iPhone/iOS
  2. Scaricare un firmware con root e Valedudo precompilato al suo interno, potete usare questo servizio, che è configurabile, oppure scaricare le versioni pronte da questo archivio (Gen1 corrisponde al primo Mi Robot, Gen2 al roborock S50 e atri, Gen3 al Mijia 1S ed altri, che attualmente non sono rootabili)
  3. Resettare il wifi del robot (nel Gen1 basta tenere premuti i due pulsanti principali fino a ricevere la notifica vocale)
  4. Collegare il proprio smartphone alla rete wifi del robot (disattivando la linea dati che altrimenti verrebbe usata di default causa assenza di connessione internet)
  5. Aprire l’applicazione XVacuum, e verificare che la connessione al robot sia stata effettuata
  6. Premere il pulsante per eseguire il flash del firmware, e selezionare il file .pkg che avrete ottenuto al punto 2
  7. Attendere che il firmware sia prima scaricato dal robot, e quindi installato
  8. Ri-collegarsi alla rete wifi del robot, con la linea dati disattivata, e aprire sul browser del telefono l’indirizzo 192.168.8.1
  9. Andare nella sezione impostazioni, aprire la sezione WiFi, e impostare i dati di connessione alla propria WiFi di casa/ufficio (dovrete poi ricollegarvi al WiFi di casa, e cercare l’IP che avrà assunto il robot, questo è compito vostro)
  10. Potete scaricare da questo archivio il file .pkg delle voci del robot, e dalla sezione Impostazioni/Voci dell’interfaccia web di Valetudo, caricare il pkg che verrà immediatamente installato

Buon divertimento!

Decode Raspberry vcgencmd get_throttled response with a PHP script

If you search for an interpretation to get a meaning out of the command

vcgencmd get_throttled

which might return something like:

throttled=0x50005

you will find many forum posts that basically tell you it is a bitcode and that you have to decode it following this table:

Bit Meaning
0 Under-voltage detected
1 Arm frequency capped
2 Currently throttled
3 Soft temperature limit active
16 Under-voltage has occurred
17 Arm frequency capped has occurred
18 Throttling has occurred
19 Soft temperature limit has occurred

(from this GitHub page)

yyyyeaaahhhh right.

Finally I found a comprehensible explanation here and I decided to write a script around it, and since I know PHP this is what I used.

So, from your home folder,

nano throttled.php

paste this inside:

<?php
$codes=array(
0=>"Under-voltage detected",
1=>"Arm frequency capped",
2=>"Currently throttled",
3=>"Soft temperature limit active",
16=>"Under-voltage has occurred",
17=>"Arm frequency capped has occurred",
18=>"Throttling has occurred",
19=>"Soft temperature limit has occurred");

$output=exec("vcgencmd get_throttled");
$output=explode("0x",$output);

if ($output[1]=="0") {
    echo "all fine, lucky you\n";
    exit();
}

$output=str_split($output[1]);
$bincode="";
foreach ($output as $hex) {
    $bincode.=str_pad(base_convert($hex,16,2),4,"0",STR_PAD_LEFT);
}
$bincode=array_reverse(str_split($bincode));
foreach ($bincode as $k=>$v) {
    if ($v) {
        echo $codes[$k]."\n";
    }
}

And then run:
php throttled.php

Display correct Raspberry CPU frequency in byobu status bar

I love byobu, since when I first discovered it existed me years ago. I didn’t even know screen was a thing back then, but byobu… oh byobu.

So anyway, I’ve come to rely a lot on it, and on its status bar, which with time allowed to display “accurately” both CPU temperature and clock.

On Raspberry Pi, I just discovered, byobu doesn’t always show the correct clock.

I noticed that the command:

vcgencmd measure_clock arm

returned a lower CPU clock, 0.6GHz (and that’s when I finally noticed that my Raspberry Pi 3B+ was permanently throttled because of low voltage).

Delving deeper, I realized that byobu takes the CPU temperature information from something like, if not exactly, this:

sudo cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq

which is the information given back by the Linux Kernel. I’ve read on RaspberryPi’s official forums (couldn’t find the link again now), and the consensus is that kernel is not necessarily correct, but the firmware always is.

The correct CPU clock frequency then must be asked to the firmware with the vcgencmd command above.

Now, how to display the correct clock in byobu status bar?

I used this guide:

http://blog.dustinkirkland.com/2010/01/byobu-custom-status-notifications-in-3.html

and with a litte help from RaspberryPi official forums:

https://www.raspberrypi.org/forums/viewtopic.php?f=91&t=251695

(those guys were REALLY fast in replying)

I created the $HOME/.byobu/bin/5_clk file, containing the following code:

#!/bin/bash
vcgencmd measure_clock arm | awk ' BEGIN { FS="=" } ; { printf("#[fg=#ffffff,bg=cyan]%.1fGHz#[default]\n", $2 / 1000000000) } '

made it executable with

chmod +x $HOME/.byobu/bin/5_clk

and enabled the “custom” row in the byobu toggles, while disabling the native info.

This replaces the cpu frequency info with the same color formatting, and refreshes every 5 seconds.

Control SIM800 with PHP through serial port

…or rather, control anything through serial port with PHP.

This guide will be soon updated to a full-fledged serial console made in PHP (show output from device, but also send commands).

I must say, credit goes to this github repository which in turn is based on another known repository, and which I heavily simplified and stripped down because I don’t like having to deal with PHP classes and everything that has to do with it.

Let’s cut down the overhead, and to the chase.

This is an example code:

function serialread() {
   global $sim800;
   $chars = [];
   do {
     $char = fread($sim800, 1);
     $chars[] = $char;
   } while ($char != "\n" && $char != "");
   return join('', $chars);
}

exec("stty -F $tty cs8 9600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts");
$sim800=fopen($tty,"w+b");
stream_set_blocking($sim800, false);
fwrite($sim800,"AT\r");
fwrite($sim800,"AT+DDET=1\r");
fwrite($sim800,"AT+CLIP=1\r");
fwrite($sim800,"ATS0=2\r");
fwrite($sim800,"AT+CMGF=1\r");
sleep(1);

while (true) {
   $data = trim(serialread());
   if (strlen($data)>1) {
     echo $data."\n";
   }
   sleep(1);
}

And now let’s do some explaining.

$tty is the string containing the value of the serial device file, in my case I defined it as $tty="/dev/ttyUSB0"; because I prefer using an external adapter and keeping more juicy GPIO pins to myself.

The $sim800=fopen($tty,"w+b"); instruction opens the serial stream as a writeable and readable file, and resets its content to empty, the b in "w+b" forces the connection to be established in binary format.

The stream_set_blocking($sim800, false); instruction allows the fread() command to immediately return a result, even if null, after calling it, instead of waiting for something to be output to the serial console.

The fwrite() instructions are to initialize the SIM800 module as I like it, you may want to search on the official AT commands of the SIM800 to find their meaning. What’s to be learnt here, is that, after you issue a command string, you want to end it with "\r" which is a carriage return, and sends the Enter key to the console; using "\n" which instead is a newline would not work to issue commands.

I use trim() on the serialread() function (which by the way is stolen from the repository linked above) because the SIM800 likes to add a lot of new lines around, and to parse and/or display the serial ouput of the module it’s better if you have a bare string to process.

That’s all folks.

Lettura di una fattura elettronica firmata xml p7m con PHP

Se il file di fattura elettronica che bisogna aprire è firmato digitalmente, e cioè ha una seconda estensione p7m, come avete notato il contenuto è tutto sommato leggibile ad occhio perché non viene oscurato, ma semplicemente “inquinato” dai blocchi della firma digitale.

Diventa però inutilizzabile da qualunque parser PHP, e richiede la estrazione del file XML dall’involucro firmato p7m.

Lista della spesa:

  • il programma openssl.exe scaricabile da questa pagina (vi serve lo zip dei file binari)
  • il file .xml.p7m

La sintassi del comando openssl è come segue:

openssl.exe smime -verify -noverify -in fattura.xml.p7m -inform DER -out fattura.xml

Quindi nel vostro bel codice PHP lancerete qualcosa tipo:

exec("percorso\openssl.exe smime -verify -noverify -in $fatturap7m -inform DER -out percorso\\".basename($fatturap7m,".p7m").".xml",$output);

Avrete quindi disponibile il file XML in chiaro, col quale potrete seguire quanto illustrato in questo articolo.

Fix Firefox and Chrome not starting in Windows 10 1809 and 1903

Back when I installed Windows 10 1809 update I was all happy of having updated my operating system.

Until I actually started using the system: all browsers except Microsoft’s stopped working, plus thunderbid was also down, and several other applications I couldn’t remember.

I resetted the settings, reinstalled from scratch, but nothing helped.

So I rolled back to the previous state, and everything was back to normal.

A few days ago Windows 10 plainly told me “update now to latest release or your version will have no more support”

YIKES MUST UPDATE NNNNOOOOOWWWWW

Fast forward to 1903 version, Firefox and Chrome started AGAIN not even being able to boot, and Thunderbird wasn’t that snappy either.

I wasn’t up to rolling back AGAIN to 1803.

I deleted for both Firefox and Chrome the original profiles, and the browsers appeared to be starting again, but after a while they kept randomly crashing and not being able to be opened at all.

Browsing around I found a lot of people having similar problems, and a lot supposed solutions… that didn’t work: reset DNS cache, delete profiles, disable CFG (control flow guard), blah blah blah.

But I tried compatibility mode, setting it for Windows 8. Chrome started working again but I didn’t like the window title bar that was imposed on me with that, so I also tried disabling compatibility mode, and instead I disabled “full screen optimizations”, and BAM Chrome worked even out of Windows 8 compatibility mode, and even after restoring the old profile! (which I had simply renamed)

Then it was the turn of Firefox: disabling full screen optimizations wasn’t enough, but enabling Windows 8 compatibility mode let me restore the old profile alright.

Thank you Windows!

Telegram Bot API getUpdates on long polling shorts on 50 seconds

I didn’t want to implement an open HTTPs server for webhooks, even if they look sexy, but I also wanted to load the Telegram servers as little as possible, so given that the long polling doesn’t really get any delay if you arbitrarily increase the timeout*, I setted up a 300 seconds timeout for a 5 minutes refresh of the request.

My getUpdates URL is built like so:

"https://api.telegram.org/bot".$telegrambot."/getUpdates?timeout=".$timeout."&offset=".$offset

where $timeout is set at 300.

What I found out from my PHP app logs is this:

Time | UpdateID | HTTP Response | Total time
20:56:09 455417146 200 50.210294
20:56:59 455417146 200 50.205697
20:57:49 455417146 200 50.18303
20:58:39 455417146 200 50.191815
20:59:30 455417146 200 50.180151
21:00:20 455417146 200 50.178455
21:01:10 455417146 200 50.204421
21:02:00 455417146 200 50.197673
21:02:50 455417146 200 50.193216
21:03:41 455417146 200 50.205001
21:04:31 455417146 200 50.190687
21:05:21 455417146 200 50.178421
21:06:11 455417146 200 50.198388
21:07:01 455417146 200 50.191959
21:07:51 455417146 200 50.190216
21:08:42 455417146 200 50.193751
21:09:32 455417146 200 50.192767
21:10:22 455417146 200 50.189805
21:11:12 455417146 200 50.205147
21:12:02 455417146 200 50.191382
21:12:53 455417146 200 50.191173
21:13:43 455417146 200 50.201623
21:14:33 455417146 200 50.190784
21:15:23 455417146 200 50.189714
21:16:13 455417146 200 50.191169
21:17:04 455417146 200 50.193477
21:17:54 455417146 200 50.199469
21:18:44 455417146 200 50.210404
21:19:34 455417146 200 50.183195
21:20:24 455417146 200 50.178331
21:21:15 455417146 200 50.203791
21:22:05 455417146 200 50.203149
21:22:55 455417146 200 50.190459
21:23:45 455417146 200 50.19248
21:24:35 455417146 200 50.193081
21:25:26 455417146 200 50.180961
21:26:16 455417146 200 50.197347

No matter what timeout value you set in the URL, the server will always return after 50 seconds.

Why oh why?

* you can check on Wikipedia for a long polling description, anyway here’s the hang of it: you load a URL with a set timeout in the query, the webserver accepts the request but doesn’t send back any data until either some updates are found, or the timeout expires. So, for example, if the timeout is 60 seconds, either there’s an update in the next minute, after which the server immediately responds with the update information, or at the 60 seconds mark the server will respond with “no updates”, at which point you can send out an HTTP request again to that URL, and the cycle starts anew. Since there is no real delay between the updates and the server response, it would be sensible to set a high timeout, even 10 minutes, so, while still getting updates as soon as possible, you will load the server very little.