TIL: split

I had a SQL file with 19M rows, one insert on each row, and I wanted to split it into smaller chunks. I found out that there is a split command which does exactly that:

split -l 100000 -d --additional-suffix=.sql mv-ids.sql mv-ids-

This takes mv-ids.sql file as an input and produces mv-ids-nn.sql files where each file has 100k lines. In my case nn goes from 00 to 89 and then from 9000 onwards. Good enough.

Learned today: Go templates

I built a library for loading configuration files that are actually Go templates, with some custom template functions for including values from other configuration files and loading secrets from files mounted by docker secrets.

If it turns out to be usable maybe I can open-source it some day, but for now it is proprietary.

Learned today: Apache strips Authorization header

TL;DR: CGIPassAuth On

The problem was that WordPress Rest API was not getting the HTTP basic auth information in the request. In this RunCloud setup there is first Nginx, which proxies to Apache, which proxies to FastCGI, which runs PHP ( I suppose the only reason Apache is there is to support .htaccess files).

First I suspected Nginx, because I’ve recently had issues with Nginx not always forwarding request headers to upstream host. I added  proxy_pass_request_headers on there, but it made no difference.

I created a test script that prints out the Authorization header, and indeed it was not getting all the way to PHP. Some other headers, like User-Agent were getting through.

I checked the Apache access logs, and the username was not getting printed there, so I thought that Apache is not getting the header.  I tried making the request directly to Apache, bypassing Nginx, but my test script still wasn’t getting the header. That lead me to suspect either Apache or PHP.

Finally I managed to find a Stack Overflow answer that lead me to the solution. I added CGIPassAuth On to .htaccess and bingo! Problem solved.

Experiences with Go

For a few months now, I’ve been programming mostly in Go. Go is a programming language developed by Google, some kind of modern version of C.

Instead of listing here all the findings, I’ll just point to the excellent summary by Sylvain Vallez. With my couple of months of experience in Go, I can fully agree with Sylvain’s points.

When I think of Go, the first thing that comes to my mind is the error handling. Probably half of my code is for error handling:

if err != nil {
   return err
}

But overall I’m quite satisfied with Go. It gets the job done, and I like statically typed languages. If it had generics, I might even enjoy using it.

Laravel Notification Channel for Zoner SMS-Gateway

We decided to use Zoner SMS-Gateway for sending SMS messages in a Laravel application I developed for a client. It is a Finnish service for sending SMS messages with a simple HTTP API.

In Laravel, Notification is the abstraction for any kind of message that can be sent via a Notification Channel. Laravel includes implementations for some notification channels, and there are community-provided ones in Laravel Notification Channels site.

There was no notification channel implementation for Zoner, so I created one. As instructed in Laravel Notification Channels site, I made a pull request to get my implementation listed there, but so far there hasn’t been any reaction to it. I wonder if there is anyone updating the site any more. Meanwhile, you can get the implementation from our own GitHub repository.

Timeout at getCurrentPosition in Cordova app on Android

I made a simple Cordova application that wraps the web site of a client. The app asked for the current location using the “official” Geolocation plugin (cordova-plugin-geolocation), but it turned out quite unstable on Android. Sometimes it just stopped providing location. Someone suggested rebooting the phone as a fix, which did help, but is not a very good solution in the long term.

Somewhere I saw that Google suggests using its Play Services API for getting the location, instead of the vanilla Android API. Then I found Google Location Services for Cordova plugin that uses the Play Services location API. Somebody in Stack Overflow suggested that too. The original geolocation plugin gets its location from the webview, which I suppose gets it from the vanilla Android API.

Because the Google Location Services plugin is naturally for Android only, I still use the geolocation plugin on iOS. Here’s the code that uses the Google Location Services plugin if it exists and the default one otherwise:

var locationService = navigator.geolocation;
if (cordova && 'plugins' in cordova && 'locationServices' in cordova.plugins) {
  locationService = cordova.plugins.locationServices.geolocation;
  console.log('Using cordova-plugin-locationservices');
}

locationService.getCurrentPosition(
  function (position) {
    var positionStr = position.coords.latitude + ',' + position.coords.longitude;
    console.log('Got position: ' + positionStr);
    // ...
  },
  function (err) {
    console.log('Did not got position: code=' + err.code + ' message=' + err.message);
    // ...
  },
  {
    enableHighAccuracy: false,
    maximumAge: 30*60*1000, // 30 min
    timeout: 20000 // ms
   }
);

How to restore price suffix for variable product in WooCommerce

WooCommerce does not show price suffix for variable products if the suffix contains placeholder variables. The reason is explained in the comment of WC_Product_Variable::get_price_html method:

Note: Variable prices do not show suffixes like other product types. This is due to some things like tax classes being set at variation level which could differ from the parent price. The only way to show accurate prices would be to load the variation and get IT’s price, which adds extra overhead and still has edge cases where the values would be inaccurate.

If you still want to show the price suffix just like for normal products, do as Mike Jolley laconically says in this issue report:

Use the filter in that method if you want to force it programmatically.

So, here is my take on what that filter would look like:

add_filter('woocommerce_get_price_suffix', function ( $html, $product, $price, $qty ) {
     if ( ! $html && $product instanceof WC_Product_Variable) {
         // Copied from plugins/woocommerce/includes/abstracts/abstract-wc-product.php#get_price_suffix
         if ( ( $suffix = get_option( 'woocommerce_price_display_suffix' ) ) 
             && wc_tax_enabled() 
             && 'taxable' === $product->get_tax_status() 
         ) {
             $replacements = array(
                 '{price_including_tax}' => wc_price( wc_get_price_including_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) ),
                 '{price_excluding_tax}' => wc_price( wc_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) ),
             );
             $html = str_replace( array_keys( $replacements ), array_values( $replacements ), ' <small class="woocommerce-price-suffix">' . wp_kses_post( $suffix ) . '</small>' );
         }
     }
 
     return $html;
}, 10, 4);