The Advisory Boar

By Abhijit Menon-Sen <ams@toroid.org>

Debian vs. WordPress+Minamaze

2017-01-06

On the twelfth day after christmas, my true love said to me, “This wordpress theme won't let me save any customisations. Can you take a look at it?”

The theme customisation menu in WordPress displays various options in the left sidebar, and a live preview of the changes on the right. You can edit things in the menu and see what they look like, and there's a "Save and Publish" button at the top. But the button remained stuck at "Saved" (greyed-out), and never detected any changes. Nor was the menu properly styled, and many other controls were inoperative.

We found other reports of the problem, but no definitive solution. Disabling certain plugins fixed the problem for some people, but that didn't help us—hardly any plugins were active anyway, and none of the problematic ones were installed.

We looked at network requests for the page in the Chrome developer console, and saw a series of 404 responses for local CSS and JS resources within the Minamaze theme. Here's one of the failing URLs:

http://localhost/wp/var/lib/wordpress/wp-content/themes/minamaze/admin/main/inc/extensions/customizer/extension_customizer.min.js?ver=2.0.0

That /var/lib/wordpress certainly didn't belong in the URL, so we went grepping in the code to see how the URL was being generated. It took us quite some time to figure it out, but we eventually found this code that was used to convert a filesystem path to the static resources into a URL (slightly edited here for clarity):

site_url(str_replace(ABSPATH, '/', $_extension_dir))

(Summary: Take /a/b/c ($_extension_dir), replace /a/b/ (ABSPATH) with a single /, and use the resulting /c as a relative URL.)

ABSPATH was set to /usr/share/wordpress/, but the extension dir was under /var/lib/wordpress/, so it's no surprise that stripping ABSPATH from it didn't result in a valid URL. Not that doing search-and-replace on filesystem paths is the most robust way to do URL generation in the first place, but at least we could see why it was failing.

The Debian package of WordPress is… clever. It places the code under /usr/share/wordpress (owned by root, not writable by www-data), but overlays /var/lib/wordpress/wp-content for variable data.

Alias /wp /usr/share/wordpress
Alias /wp/wp-content /var/lib/wordpress/wp-content

This is a fine scheme in principle, but it is unfortunately at odds with WordPress standard practice, and the Debian README mentions that liberal applications of chown www-data may be required to soothe the itch.

Unfortunately, it also means that themes may not be installed under ABSPATH, which usually doesn't matter… until some theme code makes lazy and conflicting assumptions.

The eventual solution was to ditch /usr/share/wordpress and use only /var/lib/wordpress for everything. Then ABSPATH was set correctly, and the URL generation worked. (We tried to override the definition of ABSPATH in wp-config.php, but it's a constant apparently set by the PHP interpreter.)

In the end, however, I couldn't quite make up my mind whether to blame the Debian maintainers of Wordpress for introducing this overlay scheme, or the theme developers for generating URLs by doing string manipulation on filesystem paths, or the Wordpress developers for leaving static file inclusion up to theme developers in the first place.

Well, why not all three?

Spreadsheet::XLSX and humongous spreadsheets

2011-05-29

I've been using Spreadsheet::Read backed by Spreadsheet::ParseExcel and Spreadsheet::XLSX to process Excel spreadsheets, and the combination works reasonably well (despite various frustrating problems in retrieving dates).

Today, I wrote code with lots of diagnostics to parse a large spreadsheet I'd never seen before. The code ran properly for a while, and then complained of various inconsistencies in the data. After much digging, I realised that Spreadsheet::XLSX wasn't parsing columns beyond ZZ. LibreOffice showed me data that my Perl code wasn't seeing; but it, too, has an arbitrary 1024 column limit. I had to unzip the XLSX file and read the XML source to find the real number of columns: 1059.

Until today, I'd never imagined a spreadsheet that big either.

The fault lies with the following code in Spreadsheet/XLSX.pm, which assumes that the column name is two letters followed by a number:

foreach ($member_sheet -> contents =~ /(\<.*?\/?\>|.*?(?=\<))/g) {
    if (/^\<c r=\"([A-Z])([A-Z]?)(\d+)\"/) {
        $col = ord ($1) - 65;
                
        if ($2) {
            $col++;
            $col *= 26;
            $col += (ord ($2) - 65);
        }

Fortunately, it's not hard to extend the code to handle longer names:

foreach ($member_sheet -> contents =~ /(\<.*?\/?\>|.*?(?=\<))/g) {
    if (/^\<c r=\"([A-Z])([A-Z]*)(\d+)\"/) {
    $col = ord ($1) - 65;

    my $rest = $2;
    while (my $l = substr ($rest, 0, 1, '')) {
        $col++;
        $col *= 26;
        $col += (ord ($l) - 65);
    }

I sent the patch to Dmitry Ovsyenko, author of Spreadsheet::XLSX, but received no response.