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.

WordCamp Jyväskylä

Today I attended WordCamp Jyväskylä, a one-day event focused on WordPress, mostly from developer’s point of view this time. The event had presentations on two tracks, and it was difficult to choose which track to attend as both tracks had interesting presentations.  I chose these presentations / workshops:

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
   }
);

Issue with WordPress text widget, Black Studio TinyMCE Widget and icl_sw_filters_widget_text

We had a problem where the text of plain old WordPress text widgets was not showing on pages. Error log showed entries like

[13-Dec-2017 09:25:29 UTC] PHP Warning: call_user_func_array() expects parameter 1 to be a valid callback, function 'icl_sw_filters_widget_text' not found or invalid function name in /home/itukylat/public_html/wp-includes/class-wp-hook.php on line 288

Function icl_sw_filters_widget_text is defined in WPML String Translation plugin, but we didn’t have that installed. The only place where I could find hook to icl_sw_filters_widget_text being added is in Black Studio Tiny MCE Widget plugin, in black-studio-tinymce-widget/includes/class-compatibility-plugins.php, function wpml_widget_after:

if ( false === has_filter( 'widget_text', 'icl_sw_filters_widget_text' ) && function_exists( 'icl_sw_filters_widget_text' ) || version_compare( $this->wpml_get_version(), '3.8.0' ) >= 0 ) {
    add_filter( 'widget_text', 'icl_sw_filters_widget_text', 0 );
}

and indeed that gets called, even though there is no icl_sw_filters_widget_text function anywhere.

I suspect there is something wrong with the if statement, but I didn’t dig any deeper. But I did report the issue to Black Studio Tiny MCE Widget author.

Meanwhile, as a workaround I defined my own icl_sw_filters_widget_text function:

function icl_sw_filters_widget_text($text) {
    return $text;
}

Now the texts in text widgets show up again.

Workaround for gitlab-runner issue #2408 “Cannot connect to the Docker daemon”

TL;DR; Change your pull policy to “if-not-present” or “never”.

Our Continuous Deployment pipeline at Wysiwyg worked fine, until it stopped working. It started giving errors like this:

Pulling docker image registry.gitlab.com/wysiwygoy/dev/cd-deploy ...
ERROR: Preparation failed: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

A bit of googling resulted in gitlab-runner issue #2408. I tried some of the suggestions in the thread but couldn’t get it working.

Finally I found a workaround: Because it fails when pulling my custom image from gitlab.com registry, I changed the pull policy of the runner (in config.toml of the runner) to “if-not-present”. The executor then skips pulling the image and executes its actual job just fine.

I suspect that gitlab.com registry responds too slowly and the docker client library gives up with the error mentioned above.

The downside of course is that if I update my executor image I need to pull the new image manually. In practice it doesn’t happen that often, so I can live with that until the fix to gitlab-runner is in.

I posted my finding as a comment to the issue.

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);

 

Making SOAP call with named parameters in PHP

TL;DR; Use SoapParam.

Yes, I know, SOAP is dead. But I have to use it in a client project where a Laravel application needs to call another service (Smilehouse Workspace, ugh!), which only provides a SOAP API.

I didn’t have WSDL available 1), so I passed null as the “wsdl” parameter to SoapClient constructor:

$client = new \SoapClient(null, [
   'location' => 'https://myworkspace.url/workspace.admin/openinterfaceaddress',
   'uri' => 'http://www.smilehouse.com/wsdl',
   'trace' => true,
]);

‘trace’ => true is useful when you want to see the XML sent in the request.

The other end expects the call to have a parameter called “String_1”. SoapClient::__soapCall documentation for the arguments parameter says:

An array of the arguments to pass to the function. This can be either an ordered or an associative array. Note that most SOAP servers require parameter names to be provided, in which case this must be an associative array.

So I tried passing an associative array:

return $client->__soapCall('getOpenInterfaceAddress', [
   'String_1' => 'my parameter value'
]);

Maybe I’m missing something, but no matter what I tried, the request XML had “param0” as the parameter name (you can get the request XML with SoapClient::__getLastRequest() when having ‘trace’ => true as SoapClient constructor option):

<SOAP-ENV:Body>
  <ns1:getOpenInterfaceAddress>
    <param0 xsi:type="xsd:string">my parameter value</param0>
  </ns1:getOpenInterfaceAddress>
</SOAP-ENV:Body>

Some commenters in the documentation tell to pass parameters as an array of arrays, but that didn’t work either.

Finally I found the solution: I need to use SoapParam object as a parameter:

$result = $client->__soapCall('getOpenInterfaceAddress', [
   new \SoapParam('my parameter value', 'String_1')
]);

Now the other end accepted my request and I could move on to bang my head against the next wall!

  1. Later I found out there is WSDL for this openinterfaceaddress API too. So I could just use the WSDL mode instead. Bah.