Today I learned…

Last weekend, I decided to update this website: the WordPress version, the plugins and the PHP version on the server itself. That last update had an interesting consequence: the OpenID plugin broke (which I didn’t notice until I got a nightly warning email).

The (kindly) supplied error message and stacktrace pointed quite straightforward to the cause of the problem: ” Uncaught ValueError: Unknown format specifier ” ” … #0 /home/xxx/domains/erikroos.org/public_html/wordpress/wp-content/plugins/wp-openid-selector/wp-openid-selector.php(63): printf()”.

I decided to take the hard route and try to fix the problem by hand. On the indicated line I found a printf statement with a %1 placeholder in it. As always, Stackoverflow gave the solution almost immediately: in PHP 8, a placeholder must always be terminated with a $, and optionally a format specifier like “s” (for string). So I replaced all the %1’s with %1$1 and, simple as that, OpenID worked again, allowing me to log in again (which can be quite handy).

When looking at the plugin code, I noticed the use of a function “named” __. Some googling showed that this is a built-in WordPress function for translating the given string using the current locale. It is somewhat similar to PHP’s own gettext() function, which can be aliased using a single _. And I thought Python was strange for using dunders (double underscores) in function names!

Today I learned…

PHP has a nice library to work with zip archives: ZipArchive. However, you cannot open zip files that are hosted on some website. First you have to download the zip file, and then you can open it using ZipArchive. An example:

$file = 'http://www.somesite.com/file.zip';
file_put_contents('file.zip', fopen($file, 'r'));
$zip = new ZipArchive();
if ($zip->open('file.zip') === true) {
    for ($i = 0; $i < $zip->numFiles; $i++) { 
         $stat = $zip->statIndex($i);
         if (strpos($stat['name'], 'partoffilename') !== false) {
             $fileContents = $zip->getFromIndex($i);
             // Do something here...
         }
    }
}

Also note something interesting here: if you don’t know the exact name of the file inside the zip archive that you want to open, you cannot use getFromName(), so you have to take another approach. In the above example, I iterate through the files in the archive using an index, checking every file for the part of the filename that I do know. When there’s a match, I can use the current index and getFromIndex() to open the file.

Today I learned…

In PHP, if you sort -or better: do not sort- an array, using a custom function that happens to return 0 every time, the resulting order is undefined as opposed to the original order as you might expect.

So, if you do something like this and X is never set:

usort($arr_content['content']['results'], function($a, $b) {
    if (isset($a['X']) && isset($b['X'])) {
       if ($a['X'] == $b['X']) {
            return 0;
        }
        if ($a['X'] < $b['X']) {
            return -1;
        }
        return 1;
    }
    if (isset($a['X'])) {
        return -1;
    }
    if (isset($b['X'])) {
        return 1;
    }
    return 0; // this is returned for every element since $a['X'] and $b['X'] are never set
 });

you end up with an array that’s sorted in an unexpected way, in my case even perfectly reversed!

I had to work around this “feature” of PHP by checking the number of undefined X’es in the array and skipping the usort() of their number is equal to the number of elements in the array.