Friday, February 5, 2016

Why Drupal sucks

I use "sucks" here as a terminus technicus meaning "doesn't work in the way desired or expected".

Drupal "sucks" because it gets upgraded every couple of months and you have to upgrade. Now that's fine if you are using it in a business context where there are constantly paid employees to do that. But on a free or academic site, once set up it will be only occasionally updated. And when that new release comes out it effectively advertises the flaws to all the hackers of the universe: "Hey guys! You can hack Drupal sites with this neat exploit if the Drupal version is current-version minus 1". And of course they do. So you have to upgrade, and pronto, but you can't afford to. So you get hacked. Then you have to strip down your entire server because you don't know what the hackers did. And that really sucks.

Now all this would be gone if only the Drupal people would provide an automatic or at least easy upgrade path for new releases. Instead you have to take your site offline, then back up your database (why? Is the new release buggy?), then download the new server core, delete all your old stuff except the sites directory – and don't forget to cut and paste all those arcane rewrite rules from the old to the new .htaccess file – then install the new server, copy back the old sites, run the update script, take it back online and hope and pray that all your modules still work. They might not, especially that one you can't do without, whose author didn't bother to patch either. All this takes about half an hour, and you have to do it every 2 months on every damn Drupal site you maintain. So why not automate it? Is it really so hard? And don't tell me drush does all that because it only automates a couple of the easiest steps, and then you also have to worry about whether drush worked. I notice that Drupal 8 doesn't have an automatic update feature either. So that's why I think Drupal sucks. You're better off writing your own CMS. At least that way no one will know how to hack it.

mod_rewrite: redirect to another directory with additional parameters

Mod_rewrite is one of the hardest pieces of apache to get right: the documentation is awful, there is no way to test it and the syntax is arcane. All I wanted to do was redirect URLs from one directory to another, copying the existing GET parameters and adding some more. But there are a number of tricks to get it right. So my original URLs look like this:

http://me.org/mycms/olddir?param1=foo

And I wanted to redirect it to:

http://me.org/mycms/newdir?param1=foo&param2=poo&param3=roo

An additional requirement was that I wanted to add the rule to .htaccess in the CMS-directory, not root, since that's where the rules applied. The following rule worked in the "mycms" directory:


  RewriteEngine on
  RewriteRule ^olddir$ /mycms/newdir?param2=poo&param3=roo  [L,R,QSA]
  ... (other rules)

Since I was using Drupal, the <IfModule... was already present in the existing .htaccess file. So all I needed to do was add the correct RewriteRule. To explain what I understand by this: "^" means a URL starting with "mydir" since we are already in mycms (that's where the .htaccess file is), and ending ($) there. So only parameters follow. Now redirect to /mycms/newdir. If you don't start with "/" it will prepend the entire server path. Next, add your desired parameters, and finally append the existing ones via the QSA flag, which means "query string append". The "R" flag is needed because this is a redirect. The "L" flag is needed because otherwise other rules might be applied and this is the "last" rule we need here. I'm not sure all this is correct, but it is simple and it works. So I'm sticking with it.

Tuesday, January 19, 2016

Passing parameters to javascript via drupal_add_js

Nowadays most Web programs contain a significant Javascript (or JQuery) component but content management systems are still run in PHP, Java, python etc. In the case of the popular CMS Drupal, that language is PHP, and it would be rather nice if we had a reliable mechanism for passing arguments to a javascript file so that information would be available as parameters to customise its functionality. Unfortunately there is no such mechanism. Various hacks have been proposed and used within Drupal itself, so one can implant something like <script src="myscript.js?arg1=foo&arg2=bar"></script> and expect it to work, with the aid of a script that locates the script element, strips out the arguments and then passes them in javascript to a javascript function within myscript.js. Here's an example function that does it:

Using drupal_add_js

Another technique is to install a javascript file before the page is fully loaded so it will get executed when loading is complete. For this purpose the function drupal_add_js comes into play. It has to be called within a hook_init or hook_preprocess_page function. But since parameters to javascript files are not really allowed, this function escapes any content like '&', '=' or '?' into %26, %3D, %3F, so turning a request for a javascript file into something that is simply not there.

A workaround I used successfully is to precede the call to drupal_add_js('myscript.js','file') with another call to drupal_add_js(...,'inline');. In this way I managed to store the parameters in browser local storage and retrieve them similarly. e.g. for the inline call I used: drupal_add_js("localStorage.setItem('module_params', 'docid=english/harpur/h080&target=tabs-content')",'inline'); And to retrieve these values a function similar to the one above works just fine:

Sunday, January 10, 2016

Compressing an unsorted list of integers

You see a lot on offer for compressing sorted lists of integers, but not so much on the rather uncomfortable question of how to compress a list of unsorted numbers. So why would you want to do that? Surely all one needs to do is sort the list then compress it? The problem is that in many cases the order of the integers represents information that sorting would destroy. Here is my case in point.

Actual hit-positions in a search engine

In a search-engine you have to make a list of documents in which a term is found. Each document is assigned an identifier. So say we had a list of documents in which the word 'dog' occurs. It might be found in 7 documents: 0,1,1,2,2,3,4,6,11,21,21. We can use an algorithm like FastPFOR because the sorted list can be converted into a list of deltas, or the differences between successive entries. These will typically be much shorter numbers than the actual values. In my case an array of 100 document identifiers compressed down to just 13 integers. Cool. But what if you wanted to store the locations in those documents where the word 'dog' occurred as well? This would bloat the index considerably, since 'dog' might occur 100 times in a single document. I could sort and compress it the same way, but quite often it would be really short, maybe only one entry. Then the compression algorithm would actually increase the list size by three or four-fold, since the overhead for a compressed list adds a number of ints to the start, and – this is the real killer – one compressed list would be needed for each document the word was found in. So trying to compress it the conventional way would first, probably increase the overall index size, and second, you would need to maintain a lot of compressed lists.

The solution

Ideally, we would like just one list of word-positions for each word, just as we had one list of document identifiers for each word. But such a list would have to be unsorted, because if we sort it we would lose the information about which documents those positions refer to. Fortunately, most positions are quite small. They can't be greater then the length of the longest document the word is found in. Or if it was found in only a few documents, or always at the start, the values would be even smaller. Lets say that all the values are less than some amount like 127. (Convenient, huh?) Then the list could be stored in 8-bit integers with one 32-bit integer at the start to say how many bytes there were per integer. Or if 16 bits were needed, then we could use 2-byte ints, or 3 bytes for 24 bits etc. Worst case is when the documents are bigger than 4MB or so, when we will need 32-bit integers. But that's rare.

So the strategy is pretty simple. Scan all the numbers first to see what is the biggest (or smallest negative number) and work out how many bits we will need. I kind of cheat by rounding this up to the nearest 8 bits, but if you're interested you can refine it to have an arbitrary number of bits. But you won't gain much in compression and you will lose something in speed. Here's my Java code. It just uses ByteBuffer to build arbitrary-sized ints up to 32 bits, in 8-bit hops, and then stores the list as an array of 32-bit ints – compressed, of course. So typically what you'd expect is a 25%-50% reduction in the array size and in some cases 75%. Compressing it further by any significant amount seems to be impossible, given the near-random nature of the data.

I offer no guarantees that this works for all cases etc. But it is freeware. Do what you like with it.

Thursday, December 17, 2015

Tomcat 7 error

A few people seem to be bitten by an error in the Ubuntu distribution of Tomcat7. Basically when you shut down the service it gives the following messages:

Opinions differ on what causes it, but it is pretty clear: Tomcat is assuming that the shared, server and common directories, which are in /var/lib/tomcat7 are in /usr/share/tomcat7. In other words, the configuration has mixed up catalina.home with catalina.base. catalina.base is supposed to be /var/lib/tomcat7 and catalina.home is /usr/share/tomcat7. All you have to do is edit the file /var/lib/tomcat7/conf/catalina.properties so that all references to those directories have the correct prefix and you're good. I ignored the "common.loader" line as changing this created some weird effects. But lower down the file is mixed up. Reboot and you should be good.

Wednesday, November 4, 2015

Allow local File access via jQuery.ajax in Chrome/Chromium

It is often useful to encapsulate a website onto a local file system without using a web-server. I wanted to create a web-archive of a site and then substitute the jQuery get calls with local file reads. That way I would not need to access the Internet for the web-archive to work, and I could give it to other people on a usb stick, and they wouldn't have to install a webserver to run it. So I thought I'll use jQuery.get or jQuery.ajax to read the local file. They would all be JSON files, since that is what my server returns, but you can tweak it for other formats. After a bit of fiddling I got it right:

<!doctype HTML>
<html><head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link id="link" rel="external" href="data.json">
<script src="jquery-1.11.3.min.js"></script>
<script>
$(document).ready(function(){
  $.ajax({
    url: "data.json",
    beforeSend: function(xhr){xhr.overrideMimeType("application/json");},
    dataType: "json",
    success: function(data){$("#result").html(data.text)}
  });
});
</script>
</head>
<body>
<p id="result"></p>
</body>
</html>

This reads the local file data.json and sets the contents of the #result element to the value of the "text" property. Here's my data.json just to be clear:

{"text": "Oh my word!"}

You also need a local copy of jQuery. Now this works just dandy on Safari and Firefox and I'm told in IE, but not in Opera or Chromium. Chromium says that this is a cross-origin request. I don't see why. I opened my HTML file using the file:// protocol and that tried to open a file in the same directory using the file:// protocol. How is that cross-origin? Because it made me cross? But the workarounds in Chromium/Chrome are dire: they suggest installing a local webserver – ridiculous. The only reason I am doing this is to avoid that. Or they say use the --allow-file-access-from-files option when launching chromium. But it doesn't work, at least not on Linux. However, I discovered that if you add --allow-file-access as a second option it works, though neither works on its own. I saved the options in /usr/share/applications/chromium.desktop under the Exec= property. It's a bit of a pain to ask people to do that but it is far better than installing a webserver.

Here's my Exec line from chromium.desktop:

Exec=chromium-browser --allow-file-access-from-files --allow-file-access %U

To fix Opera is more sensible: You just set Allow File XMLHttpRequest in UserPrefs of opera:config.

Saturday, July 4, 2015

Swapping suffixes on file names in bash

A common problem when writing shell scripts is to swap suffixes for file names. For example, I wanted to translate a batch of markdown files to html but also to apply a sed script so I could get curly quotes and long dashes etc. To do that I needed to create a temporary file, so I had to go from file.md to file.tmp to file.html. Each time I needed to swap suffixes. Having looked around I couldn't find a neat way to do that, and most of them used expr, which starts a new process. I wanted to do it natively in bash or even dash (the default Ubuntu shell). So I wrote a trivial but neat function and a test, which can be stripped out. The function is all you need:

#!/bin/bash
string="banana.md"
function swap {
    echo "${1:0:(${#1}-(${#2}+1))}.$3"
}
swap "banana.md" "md" "tmp"

To use it in a real script just use backticks thus:

...
function swap {
    echo "${1:0:(${#1}-(${#2}+1))}.$3"
}
markdown myfile.md > `swap "myfile.md" "md" "html"`
...