Customizing WordPress Jetpack Mobile Theme

JetPack by WordPressI have successfully using Jetpack Mobile Theme to serve the mobile version of my blogs, as of now it uses a theme called minieleven (that is starting to look a bit old…), with some tweaks here and there it can shine brighter and be customized to your taste, but unfortunately you can’t use a child theme, so other strategies must be used, so all the hard work isn’t wiped out by an update (and you do your updates, right?).

The plug-in native customization options are rather weak. You can customize, excerpts or full posts in home/archive/search pages, hide or show featured images, and display a promo for WordPress mobile apps. And that’s it, no more customization options for the mobile theme. Actually (never tried myself) if your main theme has colour and background options they should be picked up. And that’s it. From that point onward it’s up to you. So here we go…

First, if you are using Adsense for monetization, you will probably will notice that the Adsense mobile ads come with a nasty yellow background and cropped at the far right. The fix is very easy, go to Jetpack-> Options -> Custom CSS (be sure to check Mobile-compatible to Yes and simply add this:

.mobile-theme ins.adsbygoogle {
	background-color: transparent !important;
}

.mobile-theme ins.adsbygoogle iframe {
	width: inherit !important;
}

This is an important concept, as from here you can CSS override anything in the mobile theme, just be sure to prefix your selector with the .mobile-theme class, so you don’t mess up the desktop version.

This is all good for minor adjustments, and let’s say honestly this Jetpack feature is a little bit dumb, because it adds the custom CSS both to the mobile and the desktop version, actually to act on mobile you must switch an option and append the .mobile selector. This is dumb because to customize a desktop theme you simply create a child theme…. let’s say you want to use your own font with font-face, .mobile @font-face simply wont work.

The best bet for anything more than simple teaks is to create your own plug-in that pulls a specific style-sheet for the jetpack mobile version. A plug-in to customize other plug-in, wordpress is getting a bit tricky. happily this is quite easy, create a directory inside your /wp-content/plugins/ directory, let’s say jetpack-mobile-customizer and create a new file called jetpack-mobile-customizer.php with this content:

<?php
/**
 * @package Jetpack Mobile Customizer
 * @version 0.1
 */
/*
Plugin Name: Jetpack Mobile Customizer
*/

// Check if we are on mobile
function jetpackme_is_mobile() {

    // Are Jetpack Mobile functions available?
    if ( ! function_exists( 'jetpack_is_mobile' ) )
        return false;

    // Is Mobile theme showing/not showing?
    if ( isset( $_COOKIE['akm_mobile'] ) && $_COOKIE['akm_mobile'] == 'false' )
        return false;

    if ( isset( $_COOKIE['akm_mobile'] ) && $_COOKIE['akm_mobile'] == 'true' )
        return true;


    return true;
}

function jetpackme_add_css() {
    if ( jetpackme_is_mobile()) {
        wp_register_style('custom_jpmobile_style', 
                          '/wp-content/themes/my-child-theme/jpmobile_style.css');
        wp_enqueue_style('custom_jpmobile_style');
    }
}

add_action('wp_enqueue_scripts', 'jetpackme_add_css');

?>

and customize on line 26 your (child) theme and css. Now, just place all the CSS stuff on this file and it will be applied only to the jetpack mobile site.

To customize the number of posts showed on the mobile version (probably 5 posts it’s a bit overwhelming on hand-held devices), just add this code to your plug-in:

function custom_posts_per_page() {
    if ( jetpackme_is_mobile()) {
        return 1; // mobile version posts number
    } else {
        return false;
    }
}

add_filter('pre_option_posts_per_page', 'custom_posts_per_page', 10000);

And to customize the footer? Yes, again we go to the plug-in to add our stuff and hide the other stuff with CSS. On the plug-in add:

function jetpackme_custom_footer() {
    if ( jetpackme_is_mobile()) {
        global $wp;
        $current_url =  trailingslashit( home_url( add_query_arg( array(), $wp->request ) ) );
        echo '<span><a href="'.$current_url.
             '?ak_action=reject_mobile">Go to Desktop Version</a></span>'.
             '<p align="center">&copy; All rights reserved, yadayadayada</p>';
        return false;
    }
}

add_action('wp_mobile_theme_footer', 'jetpackme_custom_footer');

and in the CSS:

footer #site-generator {
        padding: 0 !important;
}

footer #site-generator > a {
        display:none;
}

So, there you go. Some clear ideas how to do the job of customizing the Jetpack Mobile Theme and surviving updates.

And now for a completely free bonus, a full customization of the menu and search. A more modern look and feel with everything hidden (menu items and search form) and of course replacing the ugly “Menu” and down arrow text with the ubiquitous 3 horizontal bars. Just open this blog with a mobile device or force the mobile view on this link.

We are using all the concepts previous explained and the very useful gettext filter. On the plug-in:

// Check if we are on mobile
function jetpackme_is_mobile() {

    // Are Jetpack Mobile functions available?
    if ( ! function_exists( 'jetpack_is_mobile' ) )
        return false;

    // Is Mobile theme showing?
    if ( isset( $_COOKIE['akm_mobile'] ) && $_COOKIE['akm_mobile'] == 'false' )
        return false;

    return jetpack_is_mobile();
}

function jetpackme_add_css_js() {
    if ( jetpackme_is_mobile()) {
        wp_register_style('custom_jpmobile_style', 
                          '/wp-content/themes/my-child-theme/jpmobile_style.css');
        wp_enqueue_style('custom_jpmobile_style');

        wp_register_script('custom_jpmobile_script', 
                           '/wp-content/themes/my-child-theme/jpmobile_script.js', 
                           array('jquery'));
    }
}

function fix_menu($translated_text, $text, $domain) {
    if ( jetpackme_is_mobile()) {
		switch ( $translated_text ) {
			case 'Menu' :
				$translated_text = '<span>Menu</span>';
				break;
			case 'Termo' :
				$translated_text = 'Pesquisar';
				break;
		 }
	}

	return $translated_text;
}

add_filter('gettext', 'fix_menu');
add_action('wp_enqueue_scripts', 'jetpackme_add_css_js');

The CSS:

.search-form #s, .menu-search {
	background-color: transparent !important;
}

.menu-search {
	-moz-box-shadow: none !important;
	-webkit-box-shadow: none !important;
	box-shadow: none !important;
	margin: 0 0.6em 0 !important;
	padding: 1% 2.5% !important;
	padding-right: 0 !important;
	padding-bottom: 0 !important;
	margin-bottom: 0 !important;
	width: auto !important;
	height: auto !important;
}

.menu-search::after {
	display:none;
}

#access {
	width:50% !important;
}

#access h3::before {
    content: "\f419";
    color: #666;
	font:1.3em "Genericons";
	line-height:46px;
}

#access h3.menu-toggle {
	color:#000 !important;
	padding: 0 !important;
	width: auto !important;
}

#access h3.menu-toggle span {
	display:inline-block;
	text-indent: -9999px;
}

#access .menu-toggle::after {
	display:none !important;
}

#access ul.nav-menu {
	left:0 !important;
	background-color:#f1f1f1 !important;
	-moz-box-shadow: none !important;
	-webkit-box-shadow: none !important;
	box-shadow: 0 2px 2px -2px #999 !important;
	padding: 0 !important;
	border-bottom:1px solid rgba(0,0,0,0.1) !important;
	margin-top:3px !important;
}

#access ul.nav-menu li {
	margin: 0 0.6em 0 !important;
	padding: 0 2.5% !important;
	border-bottom: 0 !important;
}

#access ul.nav-menu li a {
	display:block !important;
	padding:1em 0 !important;
}

#access ul.nav-menu::before {
	display:none !important;
}

.search-form {
	width:49% !important;
	display:none;
}

.search-form #s {
	border:1px solid #ddd !important;
	color:#999 !important;
	padding: 0.3em !important;
}

.search-form #s:focus {
	font-size: 1em !important;
	color:#000 !important;
	padding: 0.3em !important;
}

And the extra Javascript file loaded in the plug-in:

(function($) {
	$('h3.menu-toggle').click(function() {
		if ($(this).hasClass('toggled-on') == true) {
			$('div.search-form').show(500);
		} else {
			$('div.search-form').hide();
		}
	});
})(jQuery);

Comments, thoughts or other stuff feel free to use the comment box bellow.

CTT – Correios de Portugal check-digit

CTT - Correios de PortugalI was unable to find anywhere a Portuguese national postal service, package registry number check-digit validation. So, I politely asked them and soon after a PDF describing the algorithm was on my desk.

It’s pretty simple and standard stuff (only an awkward multiplier order of each digit), so the best way to describe it’s with a validation function in PHP (even for non PHP users/programmers should be simple to enough to understand at a glance):

function checkDigitCTT($ref) {
    if (! preg_match('/^[a-z]{2}[0-9]{9}[a-z]{2}$/i', $ref))
        return false;
            
    $digits      = substr($ref, 2, 9);
    $multipliers = array(8,6,4,2,3,5,9,7);
        
    $tmp = 0;
    foreach ($multipliers as $k => $v)
        $tmp = $tmp + (((int) $digits[$k]) * $v);
			
    $tmp = round($tmp % 11);

    if ($tmp == 0)
        $check_digit = 5;
    else if ($tmp == 1)
        $check_digit = 0;
    else
        $check_digit = 11 - $tmp;
			
    if ($check_digit == ((int) $digits[8]))
        return true;

    return false;
}

Raspberry PI open hotspot for your company site(s) only

Raspberry HotspotThe problem is really simple, you want/need to give open Wifi to your customers (let’s say inside a shop), but to you own company website (or websites) only. And nothing else, no other resources in the (internal or external) network.

The solution is simple and it comes in a tiny format… you will just need a Raspberry PI with a Wifi USB dongle that supports AP mode. Your company website should have an exclusive IP address

Side note: as normal, I’m not liable for any kind of mess, data loss, massive meteorite smash or other apocalyptic event in your world due to this guide.

Have the PI installed with the latest Raspbian, booted and logged in as root (sudo -s or equivalent).

Update the software sources:

apt-get update

Install the required software

apt-get install hostapd dnsmasq

Configure the wireless interface with a static IP address,
edit /etc/network/interfaces

iface wlan0 inet static
address 10.0.0.1
netmask 255.255.255.0
broadcast 255.0.0.0
# pre-up iptables-restore &lt; /etc/iptables.rules

and restart the interface

ifdown wlan0
ifup wlan0

Here I choosed the 10.0.0.1 address to isolate the Wifi guests from the 192.168.1.x internal network. You should adapt it according to your existing set-up.

edit /etc/default/hostapd

and replace
#DAEMON_CONF=””

with
DAEMON_CONF=”/etc/hostapd/hostapd.conf”

now edit (it’s a new file) /etc/hostapd/hostapd.conf

For a full list of switches and whistles please do refer to http://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf, we go with a very minimalistic (but functional) configuration

interface=wlan0
ssid=WIFI-FREE-AS-BEER
channel=0
macaddr_acl=0
auth_algs=1
wmm_enabled=0
driver=nl80211
hw_mode=g
ieee80211n=1

Here we can start the service.

service hostapd start

and I got the dreadful failed in red font… a lsusb command quickly showed the infamous RTL8188CUS chip:
Bus 001 Device 004: ID 0bda:8176 Realtek Semiconductor Corp. RTL8188CUS 802.11n WLAN Adapter

Thanks to the good people of the Internets you get a quick fix (you are downloading an external binary… so cross your fingers before installation, and nothing bad will happen to your PI… well, it worked for me).

wget http://dl.dropbox.com/u/1663660/hostapd/hostapd
chmod 755 hostapd
mv /usr/sbin/hostapd /usr/sbin/hostapd.ori
mv hostapd /usr/sbin/

and change in /etc/hostapd/hostapd.conf
driver=nl80211
to
driver=rtl871xdrv

service hostapd start

service [….] Starting advanced IEEE 802.11 management: ok
hostapdioctl[RTL_IOCTL_HOSTAPD]: Invalid argument

Even with the warning output the service managed to start and work correctly.

By now there should be an open network called WIFI-FREE-AS-BEER available to log in, but the process will stall in the Obtaining IP Address stage. So it’s time to move to the DHCP and DNS server.

Edit /etc/dnsmasq.conf, and place at the end of the file the lines

address=/#/aaa.bbb.ccc.ddd
interface=wlan0
dhcp-range=10.0.0.10,10.0.0.250,12h015/05/raspberry-pi-open-hotspot-for-your-company-sites-only/
no-resolv
log-queries
log-dhcp

adjust the aaa.bbb.ccc.ddd to the exclusive public IP address of your company website. Basically we are configuring Dnsmasq to answer all name resolution queries to your public IP address, and setting DCHP leases to the Hostspot clients from IP 10.0.0.10 to 10.0.0.250 valid for 12h periods.

From now on it should be possible to log in to the Hotspot, but no data flow, so let’s take care of this now. First activate the kernel IP forwarding

echo 1 > /proc/sys/net/ipv4/ip_forward

and then adjust iptables rules

iptables -F
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i wlan0 -o eth0 -p tcp -d aaa.bbb.ccc.ddd --dport 80 -j ACCEPT
iptables -A FORWARD -i wlan0 -o eth0 -p tcp -d aaa.bbb.ccc.ddd --dport 443 -j ACCEPT
iptables -A FORWARD -i wlan0 -o eth0 -p udp -d 10.0.0.1 --dport 53 -j ACCEPT
iptables -A FORWARD -i wlan0 -o eth0 -p udp -d 10.0.0.1 --dport 67:68 -j ACCEPT
iptables -A FORWARD -i wlan0 -j DROP 

remember to replace aaa.bbb.ccc.ddd with your the exclusive public IP address like in dnsmasq. From this point there should be a fully functional system. You can login to the Hotspot, and any http/https request will be landing in your company website. All other network traffic (except for the DHCP and name resolution will be blocked).

Now, to wrap up just make all this stuff survive reboots:

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

update-rc.d hostapd defaults
update-rc.d dnsmasq defaults

iptables-save > /etc/iptables.rules

and uncomment in /etc/network/interfaces the line
# pre-up iptables-restore < /etc/iptables.rules

There is just one thing left, avoid the captive portal detection and the respective sign in to network message. If you are using some kind of URL mapping/decoupling system (really hope you do) it’s pretty easy.

For Android, test for http://clients3.google.com/generate_204 request and send a 204 header and 0 bytes:

if (isset($script_parts) && $script_parts[0] == 'generate_204') {
    header('HTTP/1.1 204 No Content');
    header('Content-Length: 0');
    die();
}

For iOS lalaland test for the user agent ‘CaptiveNetworkSupport’ and send a 200 response:

if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/CaptiveNetworkSupport/i', $_SERVER['HTTP_USER_AGENT'])) {
    header("HTTP/1.1 200 OK");
    die();
}

That’s it folks, and I wonder what will be the next use for this tiny big computer?

UPDATE
After all been working well and good for a long time, maybe after a reboot a problem surfaced. Maybe a whim of the bits gods, the system was using the dnsmasq on internal lookups for all interfaces ignoring the interface directive.

So for example if one ssshed into the raspberry and tried to wget google.com one would get our company site…. not good.

Simple fix, manually edit /etc/resolv.conf, you can use Google public DNS (not censored) or your LAN Router IP (that normally uses the upstream DNS of your provider).

# Google IPv4 nameservers
nameserver 8.8.8.8
nameserver 8.8.4.4

and to not be automatic overwritten by dhcpclient updates set the immutable bit:

chattr +i /etc/resolv.conf

UPDATE 2
Noticed that the raspberry was missing /etc/network/interfaces (no file at all and I don’t recall to delete it). Maybe the problem was due to this and Maybe it’s time for a new SD card and fresh install.

Btrfs killed ZFS (on my desktops)

not-btrfsFollowing the latest post about Btrfs, until yesterday both ZFS and Btrfs were running for testing and evaluation purposes, in my primary desktop machine.

The outcome was pretty straight forward, i wiped all the ZFS stuff from the disks.  Now, my desktop is just running Btrfs (also root/boot).

Why?

Very simple reason, Btrfs is very well integrated with the OS, right from the system install, to the automatic apt-snapshots (that already saved my ass a couple of times), to the graphical tools graceful integration. On the other side, ZFS…. let’s just say that to use ZFS you actually have to add a repository and then install ZFS in the system, and just then you are ready to start and use it.

Actually both of the file systems are from Oracle, but Btrfs is GPL licensed and ZFS is CDDL license so don’t expect a kernel/OS integration anytime soon….

For my personal needs, Btrfs has all the whistles and bells, mainly sub-volumes, snapshots and compression. And it’s pretty damn fast and stable (zero problems to this day). The only thing i miss is native encryption, but probably it will roll out in an upcoming version. Sure ZFS can be more mature and feature rich, but it’s lack of integration it’s just a pita (at least for linux desktop)…

For more in depth information about Btrfs i totally recommend this presentation:

 

Linux snapshots – apt-btrfs-snapshot

Since a couple (half dozen) of years I have been using the beautiful KDE Desktop in the very well made and supported Kubuntu distribution. As technology progresses and the switch on desktop computers from fast spinning disks to solid state disks is being made, I sleep better at night with a SSD… and if even SSDs can eventually fail (as Linus Torvalds knows…), you should always backup or cloud your important data, so I was much more worried by a system messed up with some update/upgrade or my own incompetence than trough hard drive failure.

So it was time to try snapshots in Linux. As always in Unix land there’s more than one way to cook an egg. You can go with LVM + classical File System, ZFS or Btrfs (and surely many other options). I did a new Kubuntu installation with the installer defaults using Btrfs as the file system (wanted to test drive Btrfs anyway) and from here is very simple to implement snapshots.

First thing, make sure that you have the default Btrfs setup

# btrfs subvolume list /
ID 257 gen 97520 top level 5 path @
ID 258 gen 97520 top level 5 path @home

Install apt-btrfs-snapshot

# apt-get install apt-btrfs-snapshot

And check that snapshots are supported

# apt-btrfs-snapshot supported
Supported

Here actually I get an error the first time i ran it

# apt-btrfs-snapshot supported
Traceback (most recent call last):
File "/usr/bin/apt-btrfs-snapshot", line 92, in
apt_btrfs = AptBtrfsSnapshot()
File "/usr/lib/python3/dist-packages/apt_btrfs_snapshot.py", line 113, in __init__
self.fstab = Fstab(fstab)
File "/usr/lib/python3/dist-packages/apt_btrfs_snapshot.py", line 76, in __init__
entry = FstabEntry.from_line(line)
File "/usr/lib/python3/dist-packages/apt_btrfs_snapshot.py", line 49, in from_line
return FstabEntry(*args[0:6])
TypeError: __init__() missing 3 required positional arguments: 'mountpoint', 'fstype', and 'options'

this was caused by unsupported fuse syntax entries in /etc/fstab, just had to change
sshfs#user@host:/path/ /mountpoint fuse options 0 0
to
user@host:/path/ /mountpoint fuse.sshfs options 0 0

and it will work. From now on the system is pretty much autonomous, every time you apt-get upgrade a snapshot will be made for you. Take notice that each snapshot is relative to root only, this means that /home is excluded, we are taking snapshot of the system not user files and configs…

# apt-btrfs-snapshot list
Available snapshots:
@apt-snapshot-2014-12-17_10:27:24
@apt-snapshot-2014-12-18_10:33:50
@apt-snapshot-2014-12-18_19:57:02
@apt-snapshot-2014-12-19_17:17:13

or you can force a new snapshot

# apt-btrfs-snapshot snapshot

to rollback, just issue

# apt-btrfs-snapshot set-default @apt-snapshot-2014-12-18_19:57:02

and reboot, yeah… fuckin awesome!

Note, i noticed some problems in apt-btrfs-snapshot to delete and list some of own snashots. Probably because of updates in btrfs or apt-btrfs-snapshot itself (pretty common after a distribution upgrade). The delete command doesn’t works as expected and btrfs gives also error. The situation is like this:

You see the snapshot in the list:

#btrfs subvolume list /
ID 257 gen 361392 top level 5 path @
ID 258 gen 361392 top level 5 path @home
ID 505 gen 361392 top level 5 path @apt-snapshot-2015-11-12_13:01:23

But when you go to delete it, btrfs spits an awful ERROR: error accessing…

btrfs subvolume delete @apt-snapshot-2015-11-12_13:01:23
Transaction commit: none (default)
ERROR: error accessing '@apt-snapshot-2015-11-12_13:01:23'

But the solution it quite simple, just mount all the btrfs device and delete it by path:

#mount /dev/sda1 /mnt/
# ls /mnt/
drwxr-xr-x 1 root root 78 Nov 12 13:01 ./
drwxr-xr-x 1 root root 244 Ago 4 12:30 ../
drwxr-xr-x 1 root root 244 Ago 4 12:30 @/
drwxr-xr-x 1 root root 244 Ago 4 12:30 @apt-snapshot-2015-11-12_13:01:23/
drwxr-xr-x 1 root root 40 Fev 7 2015 @home/
# btrfs subvol delete /mnt/@apt-snapshot-2015-11-12_13\:01\:23/
Transaction commit: none (default)
Delete subvolume '/mnt/@apt-snapshot-2015-11-12_13:01:23'
# cd /
# umount /mnt

even so, sometimes when you try to manual delete like above you get an

ERROR: cannot delete ‘/mnt/@apt-snapshot-2015-11-12_13:01:23’: Directory not empty

in this situation just dig a bit deeper

#cd /mnt/@apt-snapshot-2015-11-12_13:01:23
# rm -rf *
rm: cannot remove 'var/lib/machines': Operation not permitted
# subvol delete /mnt/@apt-snapshot-2015-11-12_13:01:23/var/lib/machines/
# subvol delete /mnt/@apt-snapshot-2015-11-12_13:01:23/
# cd /
# umount /mnt

You can thank me later