Fakeshttp://plind.dk/2016-04-02T22:31:00+02:00Manual permanent bans in Fail2ban2016-04-02T22:31:00+02:00Peter Lindtag:plind.dk,2016-04-02:manual-permanent-bans-in-fail2ban.html<p>I had reason to look into permanent bans in fail2ban recently.</p>D3 again2013-12-28T14:07:00+01:00Peter Lindtag:plind.dk,2013-12-28:d3-again.html<p>I've been working on extending my <a href="http://d3js.org">d3.js</a> knowledge
again - this time getting a more deep understanding of how it works and
what it does. I've had a good deal of help in the form of two books, one
a Christmas present from my family (<a href="https://shop.oreilly.com/product/0636920026938.do">Interactive Data Visualization for
the Web</a> - highly
recommended) and the other a book I got in one of the online sales
(<a href="https://shop.oreilly.com/product/9781782162162.do">Data Visualization with D3.js
Cookbook</a> - a Packt
book so it suffers the usual "could really need a proper
editing"-symptoms).</p>
<p>Anyway, it's seriously great fun to play with d3.js - it's a great
library and very easy to use. The Interactive Data Visualization book
makes it a breeze to pick up - I'm pretty much through the book by now
and all the examples work really well, especially if you have your
laptop on your for testing (though, remarkably, you'll get most of the
stuff even if you can't test it at the same time).</p>
<p>But, the point: while going through the learning motions, I've done some
examples on various d3.js techniques/features. They're here:</p>
<ul>
<li><a href="https://plind.dk/d3/increase/">http://plind.dk/d3/increase/</a></li>
<li><a href="https://plind.dk/d3/sorting/">http://plind.dk/d3/sorting/</a></li>
<li><a href="https://plind.dk/d3/group-animation/">http://plind.dk/d3/group-animation/</a></li>
<li><a href="https://plind.dk/d3/axed/">http://plind.dk/d3/axed/</a></li>
</ul>
<p>The source for each of them come in under 150 lines of js - that
includes spacing for proper reading and such, which just underlines how
easy it is to get things going. Anyway, I might write more about the
library as it is truly awesome and great fun to play with.</p>Apple keyboards, Ubuntu and Udev joy2013-09-06T18:00:00+02:00admintag:plind.dk,2013-09-06:udev-happiness.html<p>My work setup at my new job includes a tank of a laptop (HP - apparently
they're good at sturdy, power-filled laptops with that extra oomph when
you need to whack someone over the head with computer) and because I
don't want to spend my day typing on a laptop keybard (mind you, it's
pretty good quality, but still - too cramped) I got myself an Apple
keyboard. One of those shiny aluminium ones. They keys are just awesome
- my fingers are very happy about using it.</p>
<p>There's just one drawback: Mac users cannot use the same keyboard layout
the rest of us are comfortable with (the hipster factor, or some such)
so some keys are switched, others have been replaced. So what do you do
when you run Ubuntu 13.04? You hack it, of course.</p>
<h2>Step 1 - Fix the braindead reversal of function keys and special keys</h2>
<p>Seriously, this one is just stupid. Or, rather, it makes very good sense
if your user base is braindead. Only in that scenario would you need the
special functions more than function keys (fascinatingly, Apple, king of
the context aware os, make keyboards that disregard context in favour of
always having the same functionality). Enough whining, make hack now!</p>
<p>From <a href="https://help.ubuntu.com/community/AppleKeyboard">https://help.ubuntu.com/community/AppleKeyboard</a> we
learn that it's possible to add a config file to modprobe to set the
proper functionality for the keyboard. You'll need to:</p>
<div class="highlight"><pre><span></span>echo options hid_apple fnmode=2 | sudo tee -a /etc/modprobe.d/hid_apple.conf
sudo update-initramfs -u -k all
sudo reboot # consider waiting for step 2 with this
</pre></div>
<p>After this the function keys should be working normal.</p>
<h2>Step 2 - Fix reversed keys</h2>
<p>This follows the same recipe as above - hence the point in waiting. Do:</p>
<div class="highlight"><pre><span></span>echo options hid_apple iso_layout=0 | sudo tee -a /etc/modprobe.d/hid_apple.conf
sudo update-initramfs -u -k all
sudo reboot
</pre></div>
<p>This should make sure the key to the right of the left shift key is
working properly - also possibly the key over the tab key.</p>
<h2>Step 3 - The udev happiness</h2>
<p>Major annoyances fixed it's on to possibly the biggest: the swapping of
cmd and alt. Here, you can actually change the setup of the keyboard
using just the Keyboard Layout setting in Ubuntu - open the options
dialogue, select Options, then expand Alt/Win key behaviour and select
Left alt is switched with left win. This will flip the two.</p>
<p>Now, if don't use the computer without having the Apple keyboard plugged
in, congratulations - you can now breathe more easily (you might want to
take a look at the other possible changes to keyboard layout you can
make, though, as some of them make quite good sense).</p>
<p>If, however, you switch between using just the laptop and using the
plugged in keyboard, you'll get really annoyed as the alt and win keys
on your laptop suddenly switch - or those on the usb-keyboard do. So
what you really want is to be able to switch keyboard layouts
automatically when the usb-keyboard is plugged in or removed. This is
where udev (linux dynamic device management, according to man) comes in.
It allows you to write rules that are executed when devices are plugged
in or removed - if the rules match the device. Just what the doctor
ordered!</p>
<p>First you need to get a proper match rule-match for your device. This is
a bit of a jungle if you don't have experience with udev - but this is
how you could find what you need:</p>
<div class="highlight"><pre><span></span># this will export all devices udev knows about
# search this list for apple and mark down the top
# line of the paragraph you find it in - it'll start with
# /devices
udevadm info --export-db | less
# then use this command, to get rule matches
udevadm info --path <device path you copied above --attribute-walk --export | less
</pre></div>
<p>Usable entries from the above would be like</p>
<div class="highlight"><pre><span></span># product vendor - alright if you don't have other apple devices attached
ATTRS{idVendor}=="05ac"
</pre></div>
<p>or</p>
<div class="highlight"><pre><span></span># possibly better, wouldn't match other apple products
ATTRS{product}=="Apple Keyboard"
</pre></div>
<p>With these it's time to create the needed rule. My rule looks like this:</p>
<div class="highlight"><pre><span></span>SUBSYSTEM=="hid", ATTRS{product}=="Apple Keyboard", ACTION=="add", RUN+="/usr/local/bin/switch-keyboard.sh apple udev"
SUBSYSTEM=="hid", ATTRS{product}=="Apple Keyboard", ACTION=="remove", RUN+="/usr/local/bin/switch-keyboard.sh laptop udev"
</pre></div>
<p>I put the above in a file named apple-keyboard.rules which is put into
/etc/udev/rules.d/ - udev uses inotify to check the directory for
changes, so the rule should be ready to run soon as you have saved the
file.</p>
<p>To quickly go over what the file does:</p>
<ul>
<li>each line is rule that udev will try to match against device
changes. The line consists of match pairs: a category and a string
match on the other side</li>
<li>first, it will match on the hid subsystem - you could also try to
match on device or kernel level. For my needs, subsystem is fine</li>
<li>then it will try to match on attributes - here is where the info you
found with udevadm comes in. I've added the ATTRS{product} category
and match on "Apple Keyboard" - this will check parent attributes of
the device for that string</li>
<li>after this it will match on what is happening - the action. I'm
matching on either add or remove, to be able to switch keyboard
layouts when the keyboard is plugged in or removed</li>
<li>and finally there is a RUN key which specifies a script to run (in
fact the entire thing will be run, so you can include parameters) -
which is what allows you to do the layout switching</li>
</ul>
<p>The next step is figuring out how to change the keyboard layout from the
command line. This is possible with setxkbmap, as it happens - it'll let
you set a lot of different layout details, keyswitches being one. First,
you should use setxkbmap to note down the options you use with and
without the keyboard plugged in. This is done like this:</p>
<p># the options should display on the last line of output here<br />
setxkbmap -query</p>
<p>With both sets of options, you can then stick together a script file
that will change between the two. My script looks like this:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"udev"</span> <span class="o">]</span>
<span class="k">then</span>
nohup <span class="nv">$0</span> <span class="nv">$1</span> <span class="p">&</span>
<span class="k">else</span>
sleep 1
<span class="nv">DISPLAY</span><span class="o">=</span><span class="s2">":0.0"</span>
<span class="nv">HOME</span><span class="o">=</span>/home/peter/
<span class="nv">XAUTHORITY</span><span class="o">=</span><span class="nv">$HOME</span>/.Xauthority
<span class="nb">export</span> DISPLAY XAUTHORITY HOME
<span class="c1"># clear all options</span>
/usr/bin/sudo -u peter /usr/bin/setxkbmap -model <span class="s2">"pc105"</span> -layout <span class="s2">"gb,dk,pl,de"</span> -option <span class="s2">""</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"apple"</span> <span class="o">]</span>
<span class="k">then</span>
<span class="c1"># set the Apple keyboard</span>
/usr/bin/sudo -u peter /usr/bin/setxkbmap -rules <span class="s2">"evdev"</span> -model <span class="s2">"pc105"</span> -layout <span class="s2">"gb,dk,pl,de"</span> -option <span class="s2">"grp:lalt_lshift_toggle,altwin:swap_lalt_lwin,apple:alupckeys"</span>
<span class="k">else</span>
<span class="c1"># set the Apple keyboard</span>
/usr/bin/sudo -u peter /usr/bin/setxkbmap -rules <span class="s2">"evdev"</span> -model <span class="s2">"pc105"</span> -layout <span class="s2">"gb,dk,pl,de"</span> -option <span class="s2">"grp:lalt_lshift_toggle"</span>
<span class="k">fi</span>
<span class="k">fi</span>
</pre></div>
</td></tr></table>
<p>There are a couple of things worth noting here:</p>
<ul>
<li>udev runs without access to your normal environment. That means
specifying the display to set keyboard layout for, as well as a path
to your Xauthority file</li>
<li>it also means prefixing all commands with proper paths - /usr/bin/
will not be in the path</li>
<li>also, udev might need to finish processing the rules file in order
for the script to work properly - hence it is run from udev with a
parameter that makes it kick off itself again, using nohup. Some
experimenting seems to suggest this is not necessary - but doesn't
hurt though</li>
<li>lastly, the script first resets the keyboard layout options, before
setting the proper options depending upon add or remove from the
rules file</li>
<li>oh, and importantly: I got information for the script
from <a href="https://superuser.com/questions/249064/udev-rule-to-auto-load-keyboard-layout-when-usb-keyboard-plugged-in">http://superuser.com/questions/249064/udev-rule-to-auto-load-keyboard-layout-when-usb-keyboard-plugged-in</a> -
specifically the parts about display and Xauthority environment
vars, without which the script will fail</li>
<li>note: don't just copy the script as you'll end up with four keyboard
layouts to choose from and my chosen keys for switching - adapt it
to your needs then save it</li>
</ul>
<h2>Conclusion</h2>
<p>This is probably what I love most about linux: someone has come up with
ways to fix the problems you're facing, you can almost certainly find
information about how to do it, and it's all freely available. If you're
willing to spend a bit of time on it you'll learn a lot, empower
yourself, solve your problems, and won't spend a dime.</p>CI, Jenkins and tests2013-08-25T18:13:00+02:00Peter Lindtag:plind.dk,2013-08-25:ci-jenkins-and-tests.html<p>I'm starting a new job soon. Or, rather, I'm starting my old job at a
new employer. I'll be working on the same projects, the same software
platform, just a better place. Among other things, this will give me the
chance to introduce some new workflows and processes for the projects
I'm working on - and in that light, setting up <a href="https://en.wikipedia.org/wiki/Continuous_integration" title="Wikipedia article on the subject">continuous
integration</a>
seems a rather good idea.</p>
<p>One reason for that is that's just a good thing to do. In fact, it's
among The Right Things To Do, as far as I'm aware. Reading Martin Fowler
<a href="https://www.martinfowler.com/articles/continuousIntegration.html">on the
subject</a>,
Kent Beck (Test Driven Development) or Uncle Bob (The Clean Coder) and
it won't be long before you're ready to get cracking. Another reason is
that I won't be the only person working on the projects - part of the
work will be done outside my new company. Given that, having a CI system
setup, that will run all commits through the automated tests, letting me
know what happened, just seems like the only sane thing to do.</p>
<p>So what follows is a recipe for setting up CI in the form of
<a href="https://jenkins-ci.org/">Jenkins</a>, on a Debian 7 machine, running PHP
projects through Gitolite.</p>
<h2>Setting up Gitolite</h2>
<p>Apt-get install gitolite gets you rolling. After installing the package,
run dpkg-reconfigure gitolite and it'll let you set it up properly with
a path to store repositories in, an SSH key for the admin user, and
setting the user gitolite runs as.</p>
<p>This will install Gitolite 2.3 - if you require a newer version you
should add the relevant versions in apt and pin the package.</p>
<p>When done, add the projects you need to manage to the config file, and
add the proper users as well.</p>
<h2>Setting up Jenkins</h2>
<p>Jenkins is a Java app, so you'll need to install Java as the main
dependency. The test server I am running on is as mentioned a Debian 7.
It's only used as a server, no screen output. Hence, running Jenkins
should preferably be headless, as no desktop is needed. This means
installing openjdk-7-jre-headless with apt-get. After that, apt-get
install jenkins.</p>
<p>This will give you an install of Jenkins managed through apt. An
alternative to this is just downloading the Jenkins app directly from
their site and running it manually, like java -jar jenkins.war.</p>
<p>If you went with the apt-managed version, you can adapt settings in
/etc/default/jenkins - things like port and address to listen on, for
instance. Most of the settings for Jenkins are handled through the web
interface, though. You most likely want to specify which users can
administrate Jenkins - add jobs, plugins, change settings, etc.</p>
<p>If you installed through apt you should also have a jenkins user now.
Switch to this user, generate an SSH keypair using ssh-keygen, and stick
the public key in gitolite with read access to the projects you want to
use it for.</p>
<h2>Setting up Jenkins plugins</h2>
<p>To get Jenkins to integrate with Gitolite, you need a couple of plugins.
I went with the following:</p>
<ul>
<li><a href="https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin">https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin</a></li>
<li><a href="https://wiki.jenkins-ci.org/display/JENKINS/Git+Chooser+Alternative+Plugin">https://wiki.jenkins-ci.org/display/JENKINS/Git+Chooser+Alternative+Plugin</a></li>
<li><a href="https://github.com/lukanus/git-parameter">https://github.com/lukanus/git-parameter</a></li>
<li><a href="https://wiki.jenkins-ci.org/display/JENKINS/Clover+PHP+Plugin">https://wiki.jenkins-ci.org/display/JENKINS/Clover+PHP+Plugin</a></li>
<li><a href="https://wiki.jenkins-ci.org/display/JENKINS/Git+Client+Plugin">https://wiki.jenkins-ci.org/display/JENKINS/Git+Client+Plugin</a></li>
</ul>
<p>I added a bunch of others too, but haven't used them yet nor had the
need to use them - they aren't needed to get the Jenkins+PHP+Gitolite
combo set up.</p>
<h2>Setup environment</h2>
<p>Again, going with apt-get: apt-get install php5 phpunit. Also, add
any/all php extensions you need. To check that you have everything you
need, manually check out a copy of the project and try running your
tests. If it works, you're ready to setup the build job in Jenkins.</p>
<h2>Setting up first job</h2>
<p>Go to the web interface for Jenkins and click on New Job in the
dashboard. Give it a proper name and choose free-style software project.
On the configuration page for the project, specify Source Code
Management. Here you choose Git and give it the url for gitolite - if
it's on the same server, it's likely something like
gitolite@localhost:project-name.git. Choose one or more branches to
checkout and build. After that, make sure you check the Poll SCM
checkbox - the GIT plugin won't allow gitolite to kick off a build if
that settings isn't checked.</p>
<p>Finally, add post-build actions as needed. Here, I'm just going with a
shell script to kick off the tests - on fail, it'll exit with an exit
code other than 1, which will fail the project build. I prefer to keep
things simple - easier to manage. Hence a single, small shell script
seems nice to me. There is a very good alternative maintained
at <a href="https://jenkins-php.org/">http://jenkins-php.org/</a> - they have a
recipe for setting up Jenkins to run PHP project builds. This includes
details on plugins and software to install - it also includes a nice big
ant script, that will run all tests and all sorts of other things.
However, to me that means getting into ant to figure out how that works,
plus taking things I don't need out of the script. When a shell script
works just dandy, there's no reason to take on another technology, I
find - I'm already learning four (4) other languages, so I don't need to
pile on ant scripts.</p>
<p>When you've got the build job set up, try kicking off the build
manually. If you got everything set up properly, you should see your
project building fine (assuming you don't have any failing tests).</p>
<h2>Automatic builds</h2>
<p>The last bit to set up is the automatic building of projects. Here, you
add a post commit hook to gitolite. I found lots of pages/hints on how
to do this - none of them working, for some odd reason. What worked for
me was setting up a common post-receive hook
in /var/lib/gitolite/.gitolite/hooks/common/post-receive. The code looks
like:</p>
<div class="highlight"><pre><span></span><span class="x">JENKINS_URL=https://localhost:8010</span>
<span class="x">GIT_URL=gitolite@localhost</span>
<span class="x">/usr/bin/touch /tmp/test</span>
<span class="x">/usr/bin/wget -q </span><span class="p">$</span><span class="nv">JENKINS_URL</span><span class="x">/git/notifyCommit\?url=</span><span class="p">$</span><span class="nv">GIT_URL</span><span class="x">:</span><span class="p">$</span><span class="nv">GL_REPO</span><span class="x"> -O /dev/null</span>
</pre></div>
<p>After setting this up, I then symlink it from the individual
repositories (paths
like /var/lib/gitolite/repositories/\<repo>/hooks/). Somehow, I
couldn't find a proper description of how to do this - the documentation
at the Gitolite page is rather poor and doesn't help in any way. Most of
the descriptions for it seem to aim at Gitolite 3.</p>
<p>Anyway, what the hook does is make a request to Jenkins (remember to
change the port in the above script to what you're running Jenkins on),
specifically to an address provided by a Git plugin (this is why you
need to make sure about having checked the Poll SCM thing), which will
then kick off a build. You can of course also take other actions in this
hook - but for Jenkins, nothing more is needed in order to start the
build.</p>PHP test2013-08-03T20:28:00+02:00admintag:plind.dk,2013-08-03:php-test.html<p>I'm studying for the Zend PHP Certification and to help with that I
decided to create a couple of small tools to practice. My first target
was operators, specifically their precedence, because with 21 levels of
precedence it can be a bit hard to remember which ones are executed
first in a given expression.</p>
<p>Hence, I put together some HTML5, a bit of Javascript with jQuery, some
CSS using Twitter Bootstrap - and
voila, <a href="https://plind.dk/php-quiz/">plind.dk/php-quiz/</a> was born. The
quizzes are very simple, yet I think they work quite fine in terms of
practicing the knowledge.</p>
<h2>Order the scrambled info</h2>
<p>The first quiz is a simple list of all the operator levels - each level
displays the operators on it. Start the quiz and the list gets scrambled
and you then have to sort it. When done you check your results.</p>
<p>It's fascinating to me how easy it is to create a test like this. All
you need is: your list, a button for interaction (start, stop), a bit of
help from jQuery (detaching the list elements, sorting them using
Math.random(), reattaching them, allowing for list rearrangement with
jQuery.sortable(), and of course checking the result with a loop through
the elements) and some layouting/design, which is made easy with Twitter
Bootstrap.</p>
<p>Scrambling the list:</p>
<div class="highlight"><pre><span></span>items = this.list.children().css('background-color', 'transparent').detach();
items.sort(function(a, b) {
return Math.random() > 0.5 ? 1 : 0;
});
this.list.append(items);
</pre></div>
<p>Checking the sorted list:</p>
<div class="highlight"><pre><span></span>this.list.children().each(function(idx, item) {
var self = $(this);
if (idx == parseInt(self.data('index'), 10) - 1) {
self.css('background-color', '#afa');
} else {
self.css('background-color', '#faa');
failed = true;
}
});
</pre></div>
<p>Apart from that, there's just a bit of bookkeeping for the scoreboard
and some setup - nothing else. It took less than an hour to write the
test itself - and now I have something that I can easily use to train my
memory of operator precedence.</p>
<h2>Pick the higher precedence</h2>
<p>The second test I wrote was as easy or easier still to make. Create two
boxes with content, user has to choose the box representing the higher
precedence. At first I set an entire operator precedence level as
content but then thought better of it and just set it to a single
operator - so you're comparing two operators against each other. They
will always be of different precedence which makes it slightly easier -
but also means I don't have to do extra checks on associativity.</p>
<p>The javascript for setting up the test and for checking it is slightly
more complex than for the first test. Setup:</p>
<div class="highlight"><pre><span></span>var a = Math.floor(Math.random() * 20),
b,
temp;
do {
b = Math.floor(Math.random() * 20);
} while (a == b);
temp = this.operators[a].split(/, /);
temp = temp.length > 1 ? temp[Math.floor(Math.random() * temp.length)] : temp[0];
this.test_a.html(temp)
.attr('data-index', a);
temp = this.operators[b].split(/, /);
temp = temp.length > 1 ? temp[Math.floor(Math.random() * temp.length)] : temp[0];
this.test_b.html(temp)
.attr('data-index', b);
this.current_winner = a < b ? a : b;
</pre></div>
<p>The code for checking the result:</p>
<div class="highlight"><pre><span></span> window.setTimeout(function() {
that.reset();
}, 1500);
if (parseInt(element.attr('data-index'), 10) == this.current_winner) {
element.css('background-color', '#afa');
this.times_correct++;
} else {
element.css('background-color', '#faa');
if (element.prop('id') == 'test_a') {
this.test_b.css('background-color', '#afa');
} else {
this.test_a.css('background-color', '#afa');
}
}
</pre></div>
<p>As should be very obvious, there's really very little involved in making
this. Yet at the same time it's a dynamic quiz: I can keep going over it
and I won't know in advance exactly what I will be asked. In that sense
it works a whole lot better than a static multiple-choice quiz.</p>
<h2>Calculate the expression</h2>
<p>The latest test I've put in place so far is about reasoning through an
expression with different operators. The code for it is again more
complex - this time mainly because sticking an expression together that
is valid is more problematic than comparing different operator
precedence levels.</p>
<p>Putting together the expression looks like:</p>
<div class="highlight"><pre><span></span> setCalculation: function() {
var limit = this.operator_limit,
operator,
last_operator,
expression = Math.floor(Math.random() * 10),
last_seen_unary,
use_ternary = false,
can_use_non_ranged_comparison = true,
can_use_ranged_comparison = true,
operators = operator_test_module.expression_generator_operators;
while (limit) {
operator = operators[Math.floor(Math.random() * operators.length)];
if (!this.use_bool && operator_test_module.isBool(operator)) {
continue;
}
if (last_operator && operator_test_module.isNonAssociative(last_operator) && operator_test_module.isNonAssociative(operator)) {
continue;
}
if (operator_test_module.isUnary(operator)) {
last_seen_unary = operator;
continue;
}
if (operator === '? :') {
use_ternary = true;
continue;
}
if (operator_test_module.isNonRangedComparison(operator)) {
if (!can_use_non_ranged_comparison) {
continue;
} else {
can_use_non_ranged_comparison = false;
}
} else if (operator_test_module.isRangedComparison(operator)) {
if (!can_use_ranged_comparison) {
continue;
} else {
can_use_ranged_comparison = false;
}
}
expression += ' ' + operator + ' ';
if (last_seen_unary) {
expression += last_seen_unary + ' ';
last_seen_unary = null;
}
expression += Math.floor(Math.random() * 10);
limit--;
}
this.current_expression = expression;
this.expression.text(this.current_expression);
},
</pre></div>
<p>Problem one is that some of the operators don't play well together - you
can only have one ranged comparison operator in an expression, and only
one non-ranged comparison operator. Also, sticking binary operators in
an expression is easy - just put one between literals. But you also need
unary operators which should be prefixed to the literals. Then,
obviously, there's the ternary operator - which is not yet part of the
above code, mainly because of the added complexity.</p>
<p>Checking it is yet again another story. You could likely piece together
an equivalent expression for javascript, using parentheses to get the
correct precedence. That, however, is way too much work, when you can
just send the expression to a php script and have it evaluated against
the answer given. This, of course, results in a security problem: you
need to make sure that only proper expressions can be sent to the
testing script. And that was what led to this regex:</p>
<div class="highlight"><pre><span></span>#^\\d+ ( |< |<= |>= |> |== |=== |!= |!== |<> |\\* |/ |% |\\+ |- |<< |>> |& |\\^ |\\| |&& |\\|\\| |and |xor |or |~ |\\(bool\\) |\\(int\\) |\\(string\\) |\\(float\\) |\\(unset\\) |! |\\d+)+$#
</pre></div>
<p>It essentially tests for the various possible operators and number
literals - with no other input excepted, to make sure the eval()
function of php is not exploited.</p>
<p>After testing the expression generator a bit, I also realized you need
to be able to modify the expressions. There are two main factors: length
and operator type. Obviously, longer expressions become harder to
follow, however, the longer the expression the bigger the chance you'll
see and 'and', 'or' or 'xor' expression in there - and since they run
last, the result will be one of true or false. In fact, with a length of
5 literals, more than half the expressions evaluate to true or false -
and in the long run, it's just not as interesting to just guess at true
or false, as it is to see if you can follow the operators.</p>
<h2>Conclusion</h2>
<p>Writing these tests provide pretty awesome experience in and of itself.
While I knew the operators before as well, they're a lot closer to heart
(and memory) now. Also, it's really interesting to try out your skills
at something so easy yet rewarding - you see quickly how it's possible
to make something of value. It's one of the things I'm often missing
from my language learning experiences: proper projects to make.</p>PHP operator tests2013-07-30T21:16:00+02:00admintag:plind.dk,2013-07-30:php-operator-tests.htmlLanguage syntax2013-07-29T20:03:00+02:00Peter Lindtag:plind.dk,2013-07-29:language-syntax.html<p>As noted in my last post, I'm trying to pick a couple of new languages.
I'm currently looking at python (not really new to me, but reactivating
my knowledge of it), ruby, erlang and C#. Of those four, the one that's
strangest to me is ruby - and I've been trying to pinpoint why.</p>
<h2>Functions/methods I</h2>
<p>Most of the languages I've used have had a pretty similar syntax for
naming functions and for calling them. If you leave out assembly, then
the typical format (in regex) is:</p>
<p>[a-zA-Z0-9_]</p>
<p>So, alphanumeric with possibility for underscores. Different languages
have different conventions for function naming, though most conventions
span languages, and it's just convention anyway. However, in ruby,
function names can include some non-alphanumeric characters that you
normally wouldn't see in a function name - that is, if you're used to
php, javascript, c-like languages, python, etc.</p>
<p>So, sort! is a perfectly legal name in ruby. The exclamation point
signifies that the method will change it's object in place. Similarly, ?
can be used in function names - this normally signifies that the
function will return a boolean value.</p>
<p>Neither symbol can be used other than at the end of function names - but
still, it just throws me off. To me, ! and ? are operators, just like
*, /, + and % are - they aren't part of names. That means I still see
sort! as function + operator at first glance and only after thinking
about it do I realize what it is.</p>
<p>Now, the funny thing is, I actually think it's a pretty good naming
scheme - or, rather, I think it's a pretty great idea for an operator.
For any function, just slap on ! to have it alter the first given
parameter in-place instead of return a value. I doubt it would lead to
bigger WTF-moments than realizing some schmuck named his function with a
! but it doesn't alter anything in-place (the ! is a convention, nothing
more).</p>
<h2>Functions/methods II</h2>
<p>The other thing that's bugging me about ruby is the syntax for calling a
function. Specifically, that function(parameter) and function parameter
are both valid. Or, not that they're both valid, but that function
parameter is valid and preferred (where not completely illegible). It
does make sense that the interpreter will know the difference between a
function and other properties/variables and thus will know if it should
return the variable or call the function - however, the point is that to
me it just looks like you're asking for a property or a variable.
Leaving off the parentheses just makes it harder for me to figure out if
you're calling a function or not - is [].sort a property (it's Danish
for black) or a method?</p>
<p>Granted, sort is not likely to a big problem, but there are still a lot
of ruby I don't know - having to guess doesn't make it any easier. And
while I could obviously just stick to parentheses myself, I'm highly
unlikely to never look at another persons code. If for no other reasons
than because I am a fan of DRY.</p>
<p>Incidentally, ruby shares this coffeescript - and it bugs me there too.
In fact, coffeescript suffers more from it, in my eyes, because there
you can't distinguish between referencing a function that doesn't use
parameters and calling the same. Hence, you either need to use
parentheses in that case or a different construct to call functions
without parameters. Either way, your clean scheme just got compromised.</p>
<h2>Variable naming</h2>
<p>Continuing from the previous section, ruby has another quirk/feature,
that's to do with naming and syntax: to reference a global variable
inside a class, you prefix it with a dollar-sign. This actually means
the dollar-sign is syntax: without the dollar-sign, the variable won't
be global. However, it is also naming: my_variable (in the global
scope) and \$my_variable (in class scope) are not the same - even
though my_variable in the global scope is exactly that, global!</p>
<p>In some ways, I expect this makes it easier to deal with: you see a name
of a variable and you just take the entire string as the name - you
don't have to consider if any of it is an operator or something else.
But it does mean that if you're used to syntax as something different
from what it's used on, then you'll be awfully confused.</p>
<h2>Thoughts</h2>
<p>I find it interesting that the biggest differences and stumbling blocks
I see are in syntax. I might dislike what I see as anal retentiveness in
Java and C++, but I understand it quite well and intuitively get it.
With ruby I'm suddenly wondering: what exactly am I looking at? In that
sense it makes it harder to pick up - at least for me. Then again, I do
have some years experience staring at code, so it might also go the
other way for beginners, seeing as ruby does come closer to English than
most languages I have seen.</p>
<p>However, I also find that the things I stumble on are interesting and
could make some things easier. I think what I'm mainly stumbling on is
new syntax - a new way of doing things, which I'm not used to. And
that's actually cool: having your eyes opened to different ways of doing
things.</p>Gamified learning2013-07-22T20:01:00+02:00Peter Lindtag:plind.dk,2013-07-22:gamified-learning.html<p>One of the problems in developing as a programmer is motivation. Not
necessarily motivation to learn something new - I am constantly picking
up new and old tech books to learn something - but motivation to stick
with it. Well, to be honest, I do believe there is a schism between the
9-5 programmer and the geek/nerd/hacker who just can't let go of tech.
However, I don't particularly care about the former category: if a
person doesn't want to keep his or her skills up-to-date, then that's
their choice. The other category is more interesting to me though - in
part because it's the one I belong to. So, what can you do to keep
motivation high for a given topic, to avoid leaving it in favour of the
next thing?</p>
<h2>Problems</h2>
<p>Understanding the problem is the first step. To me, there are just too
many interesting things, too many technologies, languages, gadgets, etc.
and simply not enough time. I pick up something new, spend a bit of time
with it, get somewhat familiar with it ... then move on, typically
forgetting what I've learned.</p>
<p>There are two parts to the problem, solving either of them would mean
keeping the knowledge: 1) not sticking with the technology, 2) not using
the technology after I've picked it up. I have obviously failed on both
counts often, so the question is: which would be easier to address? The
former, as it happens. I love all things techy, but I don't have tons of
projects I want to do (sadly).</p>
<p>So how to address the former problem?</p>
<h2>Coderbits</h2>
<p>One of the catalysts for this post was creating a profile on
<a href="https://coderbits.com">coderbits.com</a> (<a href="http://coderbits.com/Fake51">my
profile</a>). It's supposedly a different type
of resume that better reflects your skills as a programmer or developer,
the idea being that Coderbits aggregates your activity on a variety on
sites and presents it as a whole.</p>
<p>What Coderbits does that addresses my problem is gamifying learning. You
earn badges for your activity on the aggregated sites - which includes a
number of sites that teach tech subjects. It's this combination that has
meant I've been studying the same things for more than a month now,
keeping up with the same topics for an extended period. It's very
simple, really: I'm playing a game to achieve a higher rank, getting
more badges, and to do it I have to learn and get tangible proof of
that.</p>
<p>The number of sites that are aggregated from is quite big, but not all
of the sources are used in the "game", unfortunately. Coderbits put me
on to <a href="https://wibit.net">wibit.net</a> from which I'm taking the C# course
- but that activity isn't tracked, most likely because wibit.net doesn't
actually test your progress (only your viewing of tutotials is tracked).
The set of aggregated sites is changing though, with more being added,
so hopefully there will also soon be sites that cover other interesting
topics like Erlang and such languages.</p>
<h2>Conclusion</h2>
<p>Well, there's not much to say other than: make a game out of it!
Gamifying things just adds an extra dimension, and at best you're
looking at more motivation, leading to more activity and more focus. If
the gamifying aspect has been done properly, then at worst you can
ignore it, if it doesn't appeal to you.</p>DKIM and postfix2013-05-22T22:14:00+02:00Peter Lindtag:plind.dk,2013-05-22:dkim-and-postfix.html<p>Had some fun installing <a href="http://en.wikipedia.org/wiki/Dkim" title="Wikipedia article on DKIM">DomainKeys Identified
Mail</a> (DKIM
for short) at work today and seeing as I had some problems I figured I'd
detail my experiences here, for future reference.</p>
<h2>Short intro</h2>
<p>The basic idea in DKIM is to be able to show that a given email message
came from a specific domain. In a way, it's like SPF but in reverse:
where SPF says that a given IP address is allowed to send e-mail (or not
allowed) for a specific domain, DKIM will state that a given e-mail
originated from a specific domain. Both technologies involve DNS - you
need to publish DNS records that a receiving server can use to check
that the message received is legit. The point being that if you can show
control over DNS records for the relevant domain, you gain a certain
amount of trust (as presumably not anyone can edit records for a given
domain).</p>
<p>Whereas with SPF all you need to do is create a TXT record, with DKIM
you'll need to setup some software. This is because DKIM creates a
signature of the e-mail sent and adds this signature as a header to the
e-mail. A receiver can then check that this signature is valid, upon
receipt, and thus make sure that the e-mail is legit.</p>
<h2>Installation</h2>
<p>I was working on a Debian Squeeze server, and the first of my worries
came from trying to install the two packages needed for the setup.
Essentially you want to install opendkim and opendkim-tools</p>
<p><code>apt-get install opendkim opendkim-tools</code></p>
<p>However, this gave errors about broken dependencies. The reason turned
out to be that opendkim was offered from Squeeze but opendkim-tools was
only found in Squeeze-backports. The two packages are incompatible with
one another (surprise!). The solution was to install the opendkim
package by specifying the newer version on the command line, so that
both packages got installed from Squeeze-backports.</p>
<p><code>apt-get install -t squeeze-backports opendkim opendkim-tools</code></p>
<p>This got the needed packages installed - so onto the next bit.</p>
<h2>Configuration</h2>
<p>Assuming a Debian installation, you'll now have an /etc/opendkim.conf
and an /etc/default/opendkim file. To this, we'll add a directory for
opendkim files (mainly private keys and some lookup tables). So</p>
<p><code>mkdir -p /etc/opendkim/keys chown opendkim:opendkim /etc/opendkim -R</code></p>
<p>Next, edit /etc/opendkim.conf, and make sure the following bits are set</p>
<p><code>UserId opendkim:opendkim KeyTable /etc/opendkim/keytable SigningTable /etc/opendkim/signingtable ExternalIgnoreList /etc/opendkim/trustedhosts InternalHosts /etc/opendkim/trustedhosts</code></p>
<p>In the setup/debug phase, you should use the following settings to help
narrow down problems:</p>
<p><code>LogResults Y LogWhy Y</code></p>
<p>Save opendkim.conf and edit /etc/opendkim/trustedhosts to contain</p>
<p><code>127.0.0.1 localhost</code></p>
<p>Save it, then edit /etc/opendkim/signingtable. Here, you put lines that
specify a domain and the matching DNS record ID to request a TXT record
from. So the form is</p>
<p><code>domain domain-key-dns</code></p>
<p>Stick in a line for every domain you need to sign e-mails for. Example
for plind.dk</p>
<p><code>plind.dk dkim._domainkey.plind.dk</code></p>
<p>After saving this file, open up /etc/opendkim/keytable and edit it.
Again, you want a line per domain to sign e-emails for - this time the
format looks like:</p>
<p><code>domain-key-dns domain:selector:path-to-private-key</code></p>
<p>The private keys haven't been created yet, but that doesn't matter - set
up a proper naming scheme for them now and stick to that, then you can
create keys before or after editing this file as you prefer. Example of
a keytable file for plind.dk</p>
<p><code>dkim._domainkey.plind.dk plind.dk:dkim:/etc/opendkim/keys/plind.dk/dkim.private</code></p>
<p>After editing this file, it's time to create the actual keys. CD into
/etc/opendkim/keys, create a directory per domain, then in each of the
domains run the following:</p>
<p><code>opendkim-genkey -r -h sha256 -d domain -s selector</code></p>
<p>Make sure to replace the domain with what you want to create keys for,
and the selector to match your setup. Also, make sure that you put in
'sha256' as the value for -h - I saw a couple of places online that used
rsa-sha56, which is invalid and might give you problems.</p>
<p>Example for plind.dk</p>
<p><code>opendkim-genkey -r -h sha256 -d plind.dk -s dkim</code></p>
<p>This generates a selector.txt and a selector.private file (substitute
selector with whatever you provided on the command line). From the
selector.txt file, you'll find the contents you need to create a DNS
record with. Remember, you need to create a TXT record for a domain
named like selector._domainkey.domain - so, using plind.dk as example
domain and dkim as selector, it would be dkim._domainkey.plind.dk.</p>
<p>When you create the TXT record, you only need to supply the content
within the quotation marks - leave the rest out.</p>
<p>After creating the private key with opendkim-genkey, step out on
directory and chown the folder and the files to opendkim - and make sure
that only dkim can read and write the files.</p>
<p>Then, lastly, it's time to edit /etc/default/opendkim to set details of
how opendkim can be connected with. I opted for a unix socket as they
are generally faster then TCP sockets, and we handle a fair amount of
email.</p>
<p>To set this, uncomment the line in your /etc/default/opendkim file that
looks like SOCKET:local:/var/run/opendkim/opendkim.sock - rename the
path as needed here.</p>
<p>Two things to keep in mind if you do this: if you try to connect postfix
and opendkim, you need to have opendkim create it's socket somewhere
that postfix can access it, which most likely means in
/var/spool/postfix as postfix will probably run chrooted. I created an
opendkim directory right under /var/spool/postfix, to put the socket in.
The second thing to keep in mind is handling permissions for the socket.
I found I needed to set the socket to 0777, before postfix could access
it properly. To support this, you'll also need to change the UMask
setting in /etc/opendkim.conf. After taking care of both, I could get
postfix to successfully connect to opendkim.</p>
<p>Lastly, restart opendkim and make sure that it's running - that way
you'll know if you made any mistakes along the way.</p>
<h2>Connecting to postfix</h2>
<p>To have postfix connect to opendkim, you need to set a couple of
settings in /etc/postfix/main.cf. Specifically, edit the file to include
these:</p>
<p><code>milter_default_action = accept milter_protocol = 6 smtpd_milters =unix:/path/to/socket non_smtpd_milters = unix:/path/to/socket</code></p>
<p>Remember that the path to the socket is relative to the chroot of
postfix, if it's running chrooted.</p>
<p>Finally, restart postfix to refresh the configuration, then try to send
e-mail with one of the domains you set up keys for. Try sending it to a
Gmail account (you should some extra info displayed for the e-mail) or
use www.mail-tester.com to check that a DKIM was added properly.</p>
<p>In case you experience problems, use the debugging settings of opendkim
and check the syslog for error messages.</p>Blog hiatus2013-05-05T11:44:00+02:00Peter Lindtag:plind.dk,2013-05-05:blog-hiatus-2.html<p>Ahhh, the blog hiatus - a regular visitor to this blog. 8 months since
the last post - about time that I get back to writing some more :) So,
getting back into writing I thought it a good idea to start with a new
theme for the site - one from ThemeAlley.</p>
<p>First blog-topic will be HTML5+RDFa - I'll be modifying the theme to
enable metadata, and also monitoring search engines to see the effect of
it.</p>PHP unit-testing2012-08-07T22:10:00+02:00admintag:plind.dk,2012-08-07:php-unit-testing.html<p>Lately I've begun work on Infosys again - a mixture of new features,
bug-fixes and an insane amount of refactoring. Because Infosys is a
pet-project of mine, partly done to experiment and learn, I have no
problem redoing all the core parts now and then - just for kicks.
However, the application also needs to work at the end of the day, and
unit-tests seem a fairly good idea (well, essentially they always do,
but sometimes more than others), so I've set about updating and creating
more unit-tests for Infosys.</p>
<p>Unit-testing is fairly easy to get going with if you've designed your
app properly - which essentially means you've avoided singletons and
globals, and used dependency injection. And you've either gone with OOP
or a functional paradigm (when your unit is a script 2,000 lines long
running in global scope, unit-testing just isn't an option).</p>
<h2>The problem</h2>
<p>However, even given the above, there may still be a snag or two. For
instance, there is still the good old question of the database. Also, if
you happen to have implementation details that are of protected
visibility, you might face some problems. And, of course, you might just
be in the middle of converting your app to a more reasonable
architechture.</p>
<p>Specifically, for Infosys, the problems I'm facing are:</p>
<ul>
<li>lack of data for testing database related classes</li>
<li>lack of access to internals of classes</li>
<li>tests must not change live data</li>
</ul>
<p>The part of the code I'm working on now is an Active Record pattern
hierarchy of data-classes. Most of the methods in the classes I want to
test deal with data, one way or the other - so there's just no testing
without data. Had I been using a Data Mapper pattern, I wouldn't have
had the problem, seeing as then I'd just be able to directly set the
data in the objects. Instead I went for a hierarchy along the lines of:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="n">DBObject</span>
{
// <span class="nb">all</span> <span class="n">db</span> <span class="n">data</span> <span class="n">goes</span> <span class="n">here</span>
<span class="n">protected</span> <span class="nv">$storage</span> = <span class="n">array</span>();
// <span class="n">various</span> <span class="n">code</span> <span class="n">here</span>, <span class="n">including</span> <span class="n">data</span> <span class="n">loading</span> <span class="n">stuff</span> <span class="n">such</span> <span class="n">as:</span>
<span class="n">public</span> <span class="n">function</span> <span class="n">findById</span>(<span class="nv">$id</span>)
{
// <span class="n">code</span> <span class="n">that</span> <span class="n">queries</span> <span class="n">the</span> <span class="n">database</span> <span class="n">goes</span> <span class="n">here</span>
}
}
<span class="k">class</span> <span class="n">User</span> <span class="n">extends</span> <span class="n">DBObject</span>
{
// <span class="n">various</span> <span class="n">code</span> <span class="n">here</span> <span class="n">that</span> <span class="n">calls</span> <span class="n">inherited</span> <span class="n">methods</span>
}
</pre></div>
<p>Now, the code reuse is nice and I'm happy with that - however, I'm less
happy with not being able to set data directly on the objects. Actually,
I'm unhappy about not being able to set important data like IDs, which
are shielded off from the outside. Even if I were able to do that,
though, I would still face another problem: the data classes use
inherited methods to load other objects, and they also keep track of
state through protected properties. On top of that there's then the
typical problem of avoiding tests messing up the database.</p>
<h2>Lack of data - solution</h2>
<p>The most obvious solution to the problem is to mock or stub the database
access. Luckily, the data classes are all injected with a DB object that
handles all database queries - this means that if I replace the DB
properly, the data classes will never know the difference but I can
write and run exactly the tests I want, providing the data objects with
the data I need.</p>
<p>There are different ways to go about mocking out a class: you can either
use PHPUnits built in methods, or you can subclass the class you need to
test. Both have pro's and con's - but normally I'd start with PHPUnits
mocks as one of the pros here is that it's easy to get started with.
This means using the inherited getMock() method to create a mock class
dynamically. Or, at least it used to mean that: now you can instead use
the getMockBuilder() method to chainlink some methods and end up with
the same result. The difference between the two?</p>
<div class="highlight"><pre><span></span><span class="x">// getMock() way</span>
<span class="p">$</span><span class="nv">mock</span><span class="x"> = </span><span class="p">$</span><span class="nv">this</span><span class="x">->getMock(</span>
<span class="x"> 'class',</span>
<span class="x"> </span><span class="p">$</span><span class="nv">methods_to_mock</span><span class="x">,</span>
<span class="x"> </span><span class="p">$</span><span class="nv">constructor_params</span><span class="x">,</span>
<span class="x"> </span><span class="p">$</span><span class="nv">new_class_name</span><span class="x">,</span>
<span class="x"> </span><span class="p">$</span><span class="nv">disable_original_constructor</span><span class="x">,</span>
<span class="x"> </span><span class="p">$</span><span class="nv">disable_original_clone_constructor</span><span class="x">,</span>
<span class="x"> </span><span class="p">$</span><span class="nv">disable_autoload</span><span class="x"></span>
<span class="x"> );</span>
<span class="x">// getMockBuilder() way</span>
<span class="p">$</span><span class="nv">mock</span><span class="x"> = </span><span class="p">$</span><span class="nv">this</span><span class="x">->getMockBuilder('class_name')</span>
<span class="x"> ->disableOriginalConstructor()</span>
<span class="x"> ->disableAutoload()</span>
<span class="x"> ->getMock();</span>
</pre></div>
<p>In case you only need to disable the constructor and autoload, the
getMockBuilder() way is so much more intuitive! It makes understanding
the test code a lot easier too, as you know what the parameters are for
the mock without looking them up.</p>
<p>So, armed with this, I could get to work mocking my DB class to inject
it into my data objects. Essentially, the basics I needed was:</p>
<div class="highlight"><pre><span></span><span class="x">// build DB mock</span>
<span class="p">$</span><span class="nv">mock</span><span class="x"> = </span><span class="p">$</span><span class="nv">this</span><span class="x">->getMockBuilder('DB')</span>
<span class="x"> ->disableOriginalConstructor()</span>
<span class="x"> ->getMock();</span>
</pre></div>
<p>This works and the data objects happily accept the mock. In terms of
dealing with data it doesn't do too much, though. To fix that:</p>
<div class="highlight"><pre><span></span><span class="x">// accept calls to query()</span>
<span class="p">$</span><span class="nv">mock</span><span class="x">->expects(</span><span class="p">$</span><span class="nv">this</span><span class="x">->any())</span>
<span class="x"> ->method('query')</span>
<span class="x"> ->will(</span><span class="p">$</span><span class="nv">this</span><span class="x">->returnCallback(array(</span><span class="p">$</span><span class="nv">this</span><span class="x">, 'mockDBQuery')));</span>
<span class="x">// accept calls to exec()</span>
<span class="p">$</span><span class="nv">mock</span><span class="x">->expects(</span><span class="p">$</span><span class="nv">this</span><span class="x">->any())</span>
<span class="x"> ->method('exec')</span>
<span class="x"> ->will(</span><span class="p">$</span><span class="nv">this</span><span class="x">->returnCallback(array(</span><span class="p">$</span><span class="nv">this</span><span class="x">, 'mockDBExec')));</span>
</pre></div>
<p>The point of the above calls is twofold: first, I can provide data to
the objects through their normal channels, but secondly I also get a
chance to react to the SQL run. As the SQL is mostly auto-generated,
it's fairly easy to setup rules for it. Currently I've just got a fairly
big method reacting to various queries run, but I could easily setup
testing so that each test declares which queries it expects should be
run, and anything else would trigger a fail.</p>
<p>One note about the methods above: making a PHPUnit mock return different
data for multiple calls to the same method can be hard to figure out,
but a bit of googling shows at least two ways:</p>
<ul>
<li>using the at() method</li>
<li>using a callback</li>
</ul>
<p>The at() method allows you to specify when a given method should be
called and what should happen when it is called. This also allows you to
specify that the same method will be called several times with different
results - normally PHPUnit just overwrites previous expects() for the
same method. With at() you specify the order of calls - however, that is
also the downside, you need to specify in exactly which order calls are
made. Should anything change, you then need to change your testing code
as well - even though the behaviour of your SUT remains the same (and
this is essentially the only thing you should care about).</p>
<p>So I'm using the second option, the callback. This works in the same
fashion as callbacks normally do in PHP - provide an array with an
object and the name of the method to call, and PHPUnit will call that
method when the expected test-method is called, providing your return
value from the callback to the caller of the test method. That allows me
to do something like:</p>
<div class="highlight"><pre><span></span> <span class="nt">public</span> <span class="nt">function</span> <span class="nt">mockDBQuery</span><span class="o">()</span>
<span class="p">{</span>
<span class="err">$</span><span class="n">args</span> <span class="o">=</span> <span class="n">func_get_args</span><span class="p">();</span>
<span class="err">$</span><span class="n">query</span> <span class="o">=</span> <span class="err">$</span><span class="n">args</span><span class="cp">[</span><span class="mi">0</span><span class="cp">]</span><span class="p">;</span>
<span class="err">$</span><span class="n">arguments</span> <span class="o">=</span> <span class="n">isset</span><span class="p">(</span><span class="err">$</span><span class="n">args</span><span class="cp">[</span><span class="mi">1</span><span class="cp">]</span><span class="p">)</span> <span class="o">&&</span> <span class="n">is_array</span><span class="p">(</span><span class="err">$</span><span class="n">args</span><span class="cp">[</span><span class="mi">1</span><span class="cp">]</span><span class="p">)</span> <span class="o">?</span> <span class="err">$</span><span class="n">args</span><span class="cp">[</span><span class="mi">1</span><span class="cp">]</span> <span class="o">:</span> <span class="n">array</span><span class="p">();</span>
<span class="n">if</span> <span class="p">(</span><span class="err">$</span><span class="n">query</span> <span class="n">instanceof</span> <span class="n">Select</span><span class="p">)</span> <span class="err">{</span>
<span class="err">$</span><span class="n">arguments</span> <span class="o">=</span> <span class="err">$</span><span class="n">query</span><span class="o">-></span><span class="n">getArguments</span><span class="p">();</span>
<span class="err">$</span><span class="n">query</span> <span class="o">=</span> <span class="err">$</span><span class="n">query</span><span class="o">-></span><span class="n">assemble</span><span class="p">();</span>
<span class="p">}</span>
<span class="nt">if</span> <span class="o">(</span><span class="nt">stripos</span><span class="o">($</span><span class="nt">query</span><span class="o">,</span> <span class="s1">'describe `users`'</span><span class="o">)</span> <span class="o">!==</span> <span class="nt">false</span><span class="o">)</span> <span class="p">{</span>
<span class="n">return</span> <span class="err">$</span><span class="n">this</span><span class="o">-></span><span class="n">returnUserTableInfo</span><span class="p">();</span>
<span class="p">}</span>
<span class="nt">if</span> <span class="o">(</span><span class="nt">stripos</span><span class="o">($</span><span class="nt">query</span><span class="o">,</span> <span class="s1">'describe `roles`'</span><span class="o">)</span> <span class="o">!==</span> <span class="nt">false</span><span class="o">)</span> <span class="p">{</span>
<span class="n">return</span> <span class="err">$</span><span class="n">this</span><span class="o">-></span><span class="n">returnRoleTableInfo</span><span class="p">();</span>
<span class="p">}</span>
<span class="nt">throw</span> <span class="nt">new</span> <span class="nt">Exception</span><span class="o">(</span><span class="s1">'Unexpected query: '</span> <span class="o">.</span> <span class="o">$</span><span class="nt">query</span><span class="o">);</span>
<span class="err">}</span>
<span class="nt">protected</span> <span class="nt">function</span> <span class="nt">returnUserTableInfo</span><span class="o">()</span>
<span class="p">{</span>
<span class="n">return</span> <span class="n">array</span><span class="p">(</span>
<span class="s1">'data goes here'</span><span class="o">,</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="nt">protected</span> <span class="nt">function</span> <span class="nt">returnRoleTableInfo</span><span class="o">()</span>
<span class="p">{</span>
<span class="n">return</span> <span class="n">array</span><span class="p">(</span>
<span class="s1">'data goes here'</span><span class="o">,</span>
<span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>Similarly, in the exec() method mock I can check that queries look right
and come with the correct amount of parameters.</p>
<h2>Lack of access to internals of classes</h2>
<p>The second problem I'm facing is that some of the behaviour of the data
objects is different based on internal state. More specifically, if they
have been loaded with data from the database, they're aware of that.
What it comes down to is essentially awares of object ID: it's only set
when loading the object or creating it in the database, to make sure it
cannot be set or changed from the outside (it would suck pretty bad to
load an object, accidentally change the ID, then save it as a new
object). While it's a good safety to have, it also creates problems, as
I'd need to create the object and then load it with data - even though I
have no need for that in my tests (it's best to minimize the amount of
code in tests, because you want to test as few things at a time as
possible, to be able to tell exactly what is breaking).</p>
<h2>Lack of access to internals of classes - solution</h2>
<p>To get around this problem, I went for the second solution outlined
above: subclassing. The beauty of this solution is that you can have
your cake and eat it: I am not changing the behaviour of my data objects
in any way, yet I still get to set them up exactly as I need them.
Example:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="n">DBObject</span>
{
<span class="n">protected</span> <span class="nv">$has_loaded</span> = <span class="n">false</span>;
<span class="n">protected</span> <span class="nv">$storage</span> = <span class="n">array</span>();
<span class="n">public</span> <span class="n">function</span> <span class="n">isLoaded</span>()
{
<span class="k">return</span> <span class="nv">$this-</span>><span class="n">has_loaded</span>;
}
}
<span class="k">class</span> <span class="n">User</span> <span class="n">extends</span> <span class="n">DBObject</span>
{
// <span class="n">various</span> <span class="n">code</span> <span class="o">and</span> <span class="n">stuff</span> <span class="nb">to</span> <span class="n">test</span> <span class="n">here</span>
}
<span class="k">class</span> <span class="n">UserMock</span> <span class="n">extends</span> <span class="n">User</span>
{
<span class="n">public</span> <span class="n">function</span> <span class="n">overrideHasLoaded</span>(<span class="nv">$bool</span>)
{
<span class="nv">$this-</span>><span class="n">has_loaded</span> = !!<span class="nv">$bool</span>;
<span class="k">return</span> <span class="nv">$this</span>;
}
<span class="n">public</span> <span class="n">function</span> <span class="n">overrideId</span>(<span class="nv">$id</span>)
{
<span class="nv">$this-</span>><span class="n">storage</span>[<span class="s">'id'</span>] = <span class="nv">$id</span>;
<span class="k">return</span> <span class="nv">$this</span>;
}
}
</pre></div>
<p>The User class doesn't have the overrideHasLoaded() or overrideId()
methods, neither does the DBObject class - so by subclassing User and
adding those methods, I'm not changing the behaviour of User, which
means the results of my tests will be valid. This way I can get at the
internals of the User class without exposing any of it to normal
operations. The mock subclasses I create this way live with my tests so
they won't get autoloaded by mistake - which could cause problems by
exposing an interface that shouldn't exist.</p>
<h2>Conclusion</h2>
<p>There just really isn't any good reason to avoid unit-testing - not even
technical ones. In fact, once you get started testing stuff, it turns
out it's good fun to figure out how you can make sure your code is
tested! So get to it :)</p>OpenVPN - my new friend2012-07-22T18:38:00+02:00Peter Lindtag:plind.dk,2012-07-22:openvpn-my-new-friend.html<p>I recently figured it would be a good idea to set up a VPN server, to be
able to route internet traffic securely to a point of departure I trust
as safe. I happen to control a server located in Germany, and so it was
fairly easy to find a spot to set things up on. Figuring out exactly how
to get things working was slightly more different though.</p>
<h2>The problem</h2>
<p>If you, like me, just google for info on how to install openvpn, you'll
soon get confused on an important point: do you use TUN or TAP? Most of
the guides I came across don't mention the distinction but just go on to
use one (primarily TAP) of them. The reason this might be a concern to
you is that one of them involves making low-level changes to your
networking - and if your server is not in the next room, easily
accessible, you might not want to mess to much with something that'll
leave it inaccessible.</p>
<p>If you want to actually figure out what the difference is, <a href="https://en.wikipedia.org/wiki/TUN/TAP">read further
on wikipedia</a>. Briefly, both TUN
and TAP are virtual network devices, but TAP is used with a network
bridge while TUN is used with routing. What this means is that when you
setup openvpn, with TAP you need to create a network bridge (which means
changing network configuration and thus bringing the network down and
up), while with TUN you need to setup proper NAT routing (not messing
with network configuration but with firewall rules - which can also end
up bad, but is easier to plan for).</p>
<p>Given these considerations, I opted for TUN, which is what the rest of
the post will be about. If you want to setup an openvpn server using
TAP, there are plenty of guides out there.</p>
<h2>The plan</h2>
<p>My use case for openvpn is installing the server on a debian machine
(running lenny) and connecting from a ubuntu machine (running 12.04).
The following steps are required:</p>
<ol>
<li>Install the openvpn server</li>
<li>Create certificate and keys</li>
<li>Edit server config</li>
<li>Setup routing</li>
<li>Setup client</li>
<li>Enjoy</li>
</ol>
<h3>Step 1 - install openvpn</h3>
<div class="highlight"><pre><span></span>apt-get install openvpn
</pre></div>
<p>Yeah. That was the easy part.</p>
<h3>Step 2 - create certificate and keys</h3>
<p>The next bit is creating the certificate and keys needed for
authentication and encryption. Because openvpn is based on SSL and TLS
you need certificates to get things working. This might sound like
something very tedious to get working, but fortunately there are some
easy to use scripts that come with the openvpn package. These are most
likely situated in /usr/share/doc/openvpn/examples/easy-rsa/2.0/ - copy
that folder to /etc/openvpn/easy-rsa and cd into it. Then edit the vars
file and fill in details for:</p>
<div class="highlight"><pre><span></span>KEY_SIZE=2048 # might as well set it high for good measure
CA_EXPIRE=3650 # 10 years so it doesn't suddenly run out
KEY_EXPIRE=3650
KEY_COUNTRY="Your country"
KEY_PROVINCE="Your province"
KEY_CITY="Your city"
KEY_ORG="Name of organization"
KEY_EMAIL="Relevant email address"
</pre></div>
<p>Entering the details in the above file means you do it once even if you
have to redo the certificates. After doing that, go through the
following steps:</p>
<div class="highlight"><pre><span></span># this exports the variables into the environment
./vars
# this makes sure you start from scratch (it removes all files from the ./key folder
./clean-all
# this builds the openvpns root certificate
./build-ca
# this builds the diffie-hellman parameter file needed
./build-dh
# this builds the specific certificate the server uses
./build-key-server server
# this builds a certificate set for a client
./build-key client
</pre></div>
<p>Additionally, you can also go through the extra step of creating an
extra key for tls authentication. According to the <a href="https://openvpn.net/index.php/open-source/documentation/howto.html">openvpn
documentation</a>,
this adds an extra signature to packets, giving extra protection. Not
all clients support it though, so check first if you can use it. You
generate the secret key this way:</p>
<div class="highlight"><pre><span></span>openvpn --genkey --secret ta.key
</pre></div>
<p>After this, all generated files are located in ./keys/. On the server
end, you need the following files:</p>
<ul>
<li>ca.crt</li>
<li>server.crt</li>
<li>server.key</li>
<li>dh2048.pem</li>
<li>ta.key (if you decided to use it)</li>
</ul>
<p>On the client end you need these files:</p>
<ul>
<li>ca.crt</li>
<li>client.crt</li>
<li>client.key</li>
<li>ta.key (if you decided to use it)</li>
</ul>
<h3>Step 3 - server configuration</h3>
<p>Next, you need to edit your openvpn server config. The config is located
at /etc/openvpn/server.conf. You should make sure the following values
are set as follows (leave any others as default):</p>
<div class="highlight"><pre><span></span>port 1194 # port openvpn will be listening on
proto udp # protocol that will be used
dev tun # network device type used
ca /etc/openvpn/easy-rsa/keys/ca.crt # root certificate used for signing certificates
cert /etc/openvpn/easy-rsa/keys/server.crt # servers certificate for authentication
key /etc/openvpn/easy-rsa/keys/server.key # servers private key for certificate
dh /etc/openvpn/easy-rsa/keys/dh2048.pem # diffie-hellman vars for establishing connection
server 10.9.0.0 255.255.255.0 # subnet that openvpn will assigning clients ip's from
ifconfig-pool-persist ipp.txt # file to record persistent ip's in, so clients get the same ip
push "redirect-gateway def1 bypass-dhcp" # directive to make clients redirect traffic through vpn
push "dhcp-option DNS 8.8.8.8" # directive to make clients use googles dns
tls-auth /etc/openvpn/easy-rsa/ta.key 0 # if you decided to use tls.-auth
user nobody # user the openvpn daemon runs as
group nogroup # group the openvpn daemon runs as
</pre></div>
<p>After editing the server.conf, time to restart the openvpn daemon. Run
/etc/init.d/openvpn restart, and the openvpn daemon should come up,
creating a tun0 network device in the proper subnet - check with
ifconfig.</p>
<h3>Step 4 - setup routing</h3>
<p>When you're through setting up the VPN server you need some firewall
rules to make sure packets go the proper place. Using iptables, here are
the commands that should get the routing part setup properly:</p>
<div class="highlight"><pre><span></span># use IP masquerading
iptables -t nat -A POSTROUTING -s 10.9.0.0/24 -o eth0 -j MASQUERADE
# accept all packets in the forward chain from device tun0 to device eth0
iptables -t filter -A FORWARD -i tun0 -o eth0 -j ACCEPT
# forward packages for related and established connections
iptables -t filter -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
</pre></div>
<p>After adding these rules to iptables, it's a good idea to save them to a
script with iptables-save - you can then later apply them again (if
needed) with iptables-apply. And obviously you can make sure they're
reloaded automatically on a server reboot.</p>
<p>Other than changing iptables rules, you also need to make sure that the
kernel allows IP forwarding. This is done with</p>
<div class="highlight"><pre><span></span>sysctl -w net.ipv4.ip_forward=1
</pre></div>
<p>To make sure the change sticks, edit /etc/sysctl.conf (or add a file
with the same content to /etc/sysctl.d/) and add:</p>
<div class="highlight"><pre><span></span>net.ipv4.ip_forward = 1
</pre></div>
<p>With these changes, the server should happily route incoming VPN
connections to the internet.</p>
<h3>Step 5 - setup client</h3>
<p>Finally, you need to setup your client to access the server. If using
Ubuntu (or any of the variants, presumably including a desktop version
of Debian), you can let NetworkManager abstract some of the concerns
away. Right click the network icon in the system tray, select VPN
connections and click on Configure VPN - then click on Add VPN. Choose
OpenVPN. Note that in later versions of Ubuntu, you might need to
install network-manager-openvpn first, to see the OpenVPN option:</p>
<div class="highlight"><pre><span></span>sudo apt-get install network-manager-openvpn network-manager-openvpn-gnome openvpn
</pre></div>
<p>With these installed, you should be able to choose the proper VPN server
to connect to.</p>
<p>Moving on, you should fill in a connection name (so you can remember
what it is you're connecting to), the Gateway (use either the domain
name you connect to or an IP address of the VPN server), type of
connection (in our case Certificates) and then choose the path for the
two certificates and the private key generated in step 2. User
certificate should be client.crt, CA certificate should be ca.crt and
private key should be client.key.</p>
<p>Next, click on the Advanced button, select the TLS Authentication tab,
and mark the Use additional TLS authentication checkbox. Select the
ta.key and select 1 under key direction. This should be all the
configuration needed for the client - save the connection info and try
connecting.</p>
<h3>Step 6 - enjoy</h3>
<p>If everything went according to plan, you should now be able to browse
the net through your VPN connection. Test the connection using a browser
and https://whatismyip.org/ - you should see the IP address of your
server.</p>Libraries2012-06-06T21:35:00+02:00Peter Lindtag:plind.dk,2012-06-06:libraries.html<p>For a new project I am using
<a href="https://www.doctrine-project.org/" title="Doctrine2">Doctrine2</a>, <a href="http://twitter.github.com/bootstrap/" title="Twitter Bootstrap">Twitter
Bootstrap</a>,
<a href="https://jquery.com">Jquery</a> & <a href="http://jqueryui.com/">JqueryUI</a>, and
<a href="https://backbonejs.org/" title="Backbone.js">Backbone.js</a> &
<a href="https://documentcloud.github.com/underscore/" title="Underscore">Underscore</a>.
At first I figured I'd try out Symfony2 and Yii, to see if maybe they
could kickstart the project and get me most of the crud functionality -
but while both of them probably could provide quite a bit, they also
both provide way more than I need. And worse still, both of them force
me in a given direction.</p>
<p>Maybe it's a question of already having a set of bad habits that I
really should do away with, but it very often feels like I spend way too
much time working against a framework. Yes, it does indeed enable doing
some things faster, and typically the way of thinking is not too far
from what I do anyway (Symfony2 is MVC oriented, as is CakePHP and
CodeIgniter, which I'd also consider using) - however for the project
I'm doing now I really just want to throw some minimal code together and
get some functionality going, and both Symfony and Yii got in the way of
that by insisting on too much configuration or giving me things I don't
need (Yii presented me with a boiled down phpmyadmin interface - what
the hell do I need that for??).</p>
<p>So instead I'm opting for including libraries in my project and just
using them as I see fit. Instead of dictating my code they become
building blocks, and then suddenly they do enable faster work. Of all
the libraries included here, I think jQuery and Twitter bootstrap do the
best jobs - the documentation is solid and they really get out of the
way. Doctrine2 is doing pretty good too, but it's harder getting it to
work properly for you. Also, it has more hidden snags than the others,
but seeing as it's also a more complex entity that's par for the course.</p>
<p>Backbone.js in particular could use some work, though.. The
documentation is rather poor, to be honest - it details the
functionality of the library but doesn't actually show you how you're
supposed to build with it. Instead, the creators just link to various
projects built with it. This has the sad consequence of you finding
better documentation about the library in a three-part blog post than on
the page of the library itself. Specifically, I've based my new project
off of the <a href="https://coenraets.org/blog/2011/12/backbone-js-wine-cellar-tutorial-part-1-getting-started/">blogging by Christophe Coenraets about a wine celler
project</a>.</p>
<p>One can of course argue that learning by example is the best way to
learn but that is, quite frankly, rubbish. Actual apprenticeship means
learning from a master (i.e. someone that knows the right way to do
things), it doesn't mean gleaning information from a blog and then
putting pieces together. An example of the problem: I have once before
tried to create a project with backbone.js, thinking that it would be a
good tool for my specific problem (managing a good number of
model-entities in javascript). However, the project ended up slow as
hell, soon as you had more than just a few models working. Why? I have
no idea. And that's the real problem: I don't know what I did wrong in
my use of backbone.js. I don't even know how to check if I made typical
errors or if I did in fact use best practices for backbone.js. Because
the <a href="https://github.com/documentcloud/backbone/wiki">documentation for the
project</a> is sparse, at
best.</p>
<p>This could be a good reason to get involved in the documentation of
backbone.js, but for me this project was really supposed to be about
getting my idea done - not about spending a good amount of time getting
to know how libraries work. Not that I mind spending some time getting
to know a tool, mind you, but that's just not what I wanted to do this
time round - I can spend plenty of time on that as soon as I have a
prototype up and running, but before that it just drags out the project
and takes away energy.</p>
<h2>Moral of the story</h2>
<p>There are two kinds of projects (and then all things in between, but
anyway - two extremes). There is the "I'm building something cool and
learning a lot while I'm doing it - I want everything to be done right".
And then there is "I'm building something cool and really want to get it
finished so I can use it - I want everything to work right". When you're
doing the second thing, having to learn a new tool extensively is just a
pain - that's the main reason taking up a new framework for a project
you just want to finish quickly is a bad idea.</p>
<p>To get the best of both worlds you need to think about which type of
project you're actually working on: do you mainly want to learn new
tools or do you want to get that idea done? If you don't give it some
thought before starting out, you'll end up with halfbaked projects - and
that's just no fun at all.</p>Debugging javascript2012-01-26T22:00:00+01:00Peter Lindtag:plind.dk,2012-01-26:debugging-javascript.html<p>Over the last week, I've had the joy of debugging some nasty javascript
bugs. Generally, I really like javascript, especially since discovering
<a href="https://www.crockford.com/">Douglas Crockfords take on it</a> (available in
boiled-down form at
<a href="https://www.amazon.com/exec/obidos/ASIN/0596517742/wrrrldwideweb">Amazon</a>).
It turned from the typical "Javascript is a ridiculous toy language that
doesn't work" to "My goodness, there is real beauty here!". There are,
however, some downsides to it - a number in the language itself and then
a fair amount connected to it. In fact, by far the worst parts of
javascript seem to me to be the implementations of it. Yes, it's time
for the good old "I hate you, Microsoft!!"</p>
<h2>Bug 1: Ajax post request</h2>
<p>The first bug I had to fix was not really a javascript bug. The only
buggy part about it, in terms of javascript, was that I was allowed to
write buggy code with no alarms going off. The problematic code was
sending a post request done through pure javascript - no jQuery magic or
anything like it, just pure javascript. Because the request needed to be
a fullblown multipart/form-data request, the headers were from
constructed from scratch - this included creating the boundary string
for the request. Unfortunately, the code I was using was creating a
flawed boundary (in particular, it included a semicolon at the end of
the boundary) but all browsers happily sent the request along. And
everything worked smoothly, till we upgraded the PHP binary on our
server: in version 5.3.9, PHP enforces the HTTP standard more strictly
and so it simply drops the incoming POST data if the boundary string
isn't formatted properly. Which meant that all of a sudden, the ajax
requests dropped like bricks.</p>
<p>How do you debug that? You can see that there's no POST data coming
through, but you also know the script sending the data worked until
recently - and you know the script hasn't changed. What you don't know
is that the server was upgraded - but you learn this after about 1 hour.
However, you do know that Chrome sends the data through just fine - but
IE and Firefox don't. Where do you start looking for the problem?
Nowhere do you get any errors (PHP doesn't notify you that the data from
the POST request was dropped - and the browsers don't tell you that the
request is faulty), so you have to guess at what the error might be.</p>
<p>What led me to the answer was that Google Chrome detected the problem
and fixed it - although silently. Chrome rewrites the boundary
identifier before sending the request through - if it detects a faulty
boundary identifier. So when I tried googles boundary string, suddenly
things worked. A bit of experimentation later and I had the solution -
which also led me to the <a href="https://php.net/ChangeLog-5.php#5.3.9">changelog for PHP
5.3.9</a>, where <a href="https://bugs.php.net/bug.php?id=55504">this thing is
mentioned</a>.</p>
<h2>Bug 2: cloning in IE9</h2>
<p>The second bug that caused me massive headaches was cloning DOM elements
in IE9. I do the cloning in order to send back html to the server, for
converting to PDF. Turns out that it's much, much, MUCH easier to deal
with the html through the DOM (and especially through jQuery or other
such libraries) than it is to deal with it through PHP. So what is done
is that the main node is cloned, various bits/pieces removed and then
the content is sent through a request as a text string. How could this
go wrong, you ask? Well, turns out that IE9 decided to throw hissy fits
and just randomly apply classes to elements. Well, not quite randomly:
in the process of cloning, it came across one class-name it liked so
much, that it decided to apply it to about 75% of all elements on the
page (but not all the elements - that would have been too consistent).</p>
<p>What fascinates me about this is that all other browsers I have tried
work just fine. Yes, that also means IE7 and IE8 (we don't cater to IE6
on the project) - no trouble there. Microsoft actually worked hard to
make sure that there were new bugs in IE9. Well, I suppose the world
wouldn't have been the same if one of their browsers actually worked as
it should.</p>
<p>Of course, this is exaggerating things. Most likely, the fault is mine
for cloning elements with IDs. Still, I think Microsoft got the golden
rule of interoperability wrong (the one that goes something like: "<em>be
lax</em> on your <em>input</em> but <em>strict</em> on your <em>output"</em>): instead of being
lax about input, they decided to be strict about it ... while they in
general are rather lax about their output (another example of this
sending a response to an ajax request - you had better not think to set
the charset to utf8, as that will just result in errors you have no idea
how decipher).</p>
<p>What makes me think that? Well, the fact that my hacky workaround works:
grab the string representation of the node to clone, add an extra bit to
all IDs, then create a new element from that and start working. And hey
presto, problem solved. Do I feel dirty now? Yes. Is my loathing of IE
and MS bigger? Yes. Was I fooled again by them, thinking that IE had in
fact improved? Yes. Shame on me? Yes.</p>
<h2>The twist</h2>
<p>I have recently developed a small error-handling script in JS (after
reading about a new service that lets you install that in an easy
fashion on your site and thinking "I can do that") and have put that up
on the site. I had hoped to glean information from this but it turned
out utterly useless for both bugs - because none of them were bugs in
javascript or with my programming. They were bugs of the implementation
of javascript in browsers - not of the language itself.</p>
<p>In fact, the error handler only served to confuse things, because it has
picked up on a number of bugs, including errors in browser plugins and
weird script handler issues in the Bing crawler. I don't regret putting
it into place but certainly needs a lot of analysing before it gives off
any goodies.</p>
<p>In the end, only lucky guessing and some deductions provided answers. Of
course, various tools could probably have helped, but I would have
needed to know that these would be useful in tracking down the error -
so I would still have had to guess at the problem. I have learned a bit
more about browsers, and - most importantly - about errors. The worst
possible error is the one disguised as success: either your typical
silent error (such as dropping the POST vars without mentioning it) or
your more atypical everything-is-fine error (where things fail but
appearances are kept up).</p>Switching to #!2011-11-23T14:26:00+01:00admintag:plind.dk,2011-11-23:switching-to.html<p>I recently got fed up with Ubuntu after switching from 10.10 to 11.10,
as Unity rubbed me in a very bad way. Also, my netbook got slower, some
personal modifications to my install got lost, and I couldn't figure out
how to change settings to my liking ... and <a href="https://humblysubmitted.blogspot.com/2010/11/its-true-youre-not-whore-if-its-for.html">I saw Cancer kick a
dog</a>.</p>
<p>My first consideration was to migrate to xubuntu or kubuntu, but I
couldn't quite figure out how to do that properly (it might be as simple
as installing xubuntu-desktop or xfce4) in a clean way, so in the end I
opted for installing <a href="https://crunchbanglinux.org/">#!</a> (crunchbang)
from scratch on my Lenovo S10-3s.</p>
<p>The setup itself is pretty easy using the graphical install tool from
the live cd, with only one non sequitur: I opted for an encrypted LVM
setup and wanted to keep /home on a separate partition. However, I still
use the rest of the system for things (including databases and such) so
I wanted to keep / and /home at about the same sizes. The #! install
tool doesn't see things the same way and allocated 10GB for / and 240GB
for /home. Not in itself a problem (though I don't why it's determined
to make the decision for me instead of asking me) however the GUI tool
then made it nigh impossible for me to make the setup I wanted. In fact,
I didn't get the setup I wanted for the only way I could see to make it
happen would be to go through the manual partition setup - which I
didn't fancy at the time (too big a chance to screw up). In effect it
forced me onto a one-partition setup - and that's silly.</p>
<p>Other than that the install went flawlessly. My version of #! is
Statler and most things in it work out of the box on my S10-3s. There
are a couple of things that I needed to do, to get things perfect.</p>
<h2>Wireless</h2>
<p>The Broadcom Corporation BCM4313 isn't properly recognized by the
installed drivers, so in order to get wifi working I needed to install
firmware-brcm80211. After that it seemed to work fine, although it still
had a problem of the wireless network not being enabled upon startup.
This was fixed with a startup script in /etc/rc.local - basically it
just does</p>
<div class="highlight"><pre><span></span># in /etc/rc.local
/usr/bin/nmcli nm wifi on
</pre></div>
<p>As NetworkManager is handling my wireless card anyway and it just needs
to be told to switch wireless on, this is all that's needed to have
wireless running after bootup.</p>
<p>I came across another problem though: on connecting to wireless
networks, if a connection was not made, the computer would freeze. Mouse
was still working but the desktop was non-responsive. In order to solve
this, I updated the wireless network card to the firmware drivers of
testing as well.</p>
<div class="highlight"><pre><span></span>sudo apt-get install -t testing firmware-brcm-80211
</pre></div>
<h2>Touchpad</h2>
<p>The synaptics touchpad of the S10-3s only allowed left clicking (not
through the actual button though) and scrolling - no right click. This
was fixed by adding Debian testing sources and pinning the kernel to
testing - which currently means version 3.0.0.1. After installing this
and rebooting, the touchpad worked just fine.</p>
<h2>Sound</h2>
<p>After install I had a small problem with the sound, in that when I
attached headphones the sound would still play through the normal
speakers. This issue was fixed by adding a line to
/etc/modprobe.d/alsa-base.conf</p>
<div class="highlight"><pre><span></span># in /etc/modprobe.d/alsa-base.conf
options snd-hda-intel model=thinkpad
</pre></div>
<p>After adding this and rebooting, sound would play as intended - either
through headphones or the normal speakers. However, #! comes with a
rather annoying use of the system speaker to make a bell sound. To turn
this off, you can use the <a href="https://akshaydandekar.wordpress.com/2011/06/30/shell-script-to-remove-system-beeps-in-crunchbang-linux/">script found at
Dandekar's</a>.
The most important parts are (I believe):</p>
<div class="highlight"><pre><span></span># in /etc/modprobe.d/alsa-base-blacklist.conf
blacklist pcspkr
# in ~/.config/openbox/autostart.sh
xset b off &
</pre></div>
<p>You'll essentially blacklist the driver for the pc-speaker and stop X
from making the bell sound.</p>
<h2>Conclusion</h2>
<p>Getting #! to run on my Lenovo S10-3s was not as easy as getting ubuntu
to run on it (although I had a couple of problems first time round there
too). However, it hasn't taken me long to make things work and the list
of things to fix is pretty small - the biggest issue being the wireless
card.</p>
<p>Overall my system seems faster and leaner - even though I used the #!
welcome script to install a ton of package right after install. I'm
definitely looking forward to getting into #! - so far there's nothing
I dislike but a lot of things I'm pretty happy about, such as the lack
of Unity.</p>PHP RESTful frameworks2011-11-14T21:50:00+01:00admintag:plind.dk,2011-11-14:php-restful-frameworks.html<p>I have to build a RESTful API for <a href="http://www.fastaval.dk">Fastaval</a>.
The purpose is to tie together three different tools, two of which are
built by ourselves (but different developers) and one of which is
Wordpress. Instead of trying to roll everything into one codebase the
solution I'm opting for is adding a mediator service in the middle. And
for that I'll use a PHP RESTful framework. So the question was, which
one to use.</p>
<p>I had a quick look at the scene and came up with the following
candidates:</p>
<ul>
<li><a href="https://peej.github.com/tonic/">Tonic</a></li>
<li><a href="https://www.slimframework.com/">Slim</a></li>
<li><a href="https://www.recessframework.org/">Recess</a></li>
<li><a href="https://github.com/jmathai/epiphany">Epiphany</a></li>
<li><a href="https://getfrapi.com/">Frapi</a></li>
</ul>
<p>There are others as well, but these looked like the best candidates for
quickly setting up a small API.</p>
<h2>Measuring</h2>
<p>My needs for the framework are:</p>
<ul>
<li>small size</li>
<li>get out of the way</li>
<li>don't force me to do more stuff than I need</li>
<li>few requirements</li>
<li>ability to get into it in 5-10 minutes</li>
</ul>
<h3>Tonic</h3>
<p>This framework seems nicely done. However, after looking at the source
and the setup for 5 minutes, I found more overhead than I want.
Specifically, the process of dispatching from request to controller
involved much more code than I think should be needed. On top of that,
it seems to include Smarty, which is 100% overkill for my needs (and
which I'm generally opposed to). And as a final hint that I wouldn't
like the framework, it included a module for doing basic http
authentication. Not exactly something I want in my RESTful API.</p>
<h3>Slim</h3>
<p>Here there's very little setup needed before you're running a RESTful
service. The framework really does all it can to get out of the way, so
after looking for 2 minutes at the included example index file you'll
know how to get the basics going. The size of the framework is about
230K, which is not bad. Overall a very good contestant.</p>
<h3>Recess</h3>
<p>After installing this and setting it up, I browse to the test page only
to find it needs a database to work. Instant dismissal: if my API
doesn't need a database I'm sure as hell not going to be bothered with
creating one for the framework.</p>
<h3>Epiphany</h3>
<p>This is the second final contestant for the framework to use. Again,
fairly simple setup and you'll know very fast after installing it how it
works, so you won't waste a lot of time reading docs before you can
begin to use it. I like it a bit less than Slim, though, as it seems to
need more manual setup (more static calls than I would expect, before
things are running). However, very small size (80K), nice set of
libraries, a nice surprise in terms of flexibility, so this one is
definitely not out of the race.</p>
<h3>Frapi</h3>
<p>This seems like a nice a fairly advanced framework, however, right out
of the box it involves too much complexity to be of any use to me. First
off, I need to setup two virtualhosts for it, one for the frontend and
one for the backend. I don't need that, as I don't need a backend.
Secondly, I couldn't even get to the point of figuring out if Frapi
needs a backend, as it wouldn't run without APC which I don't have
running on my small netbook. So it got disqualified from the race right
there. If it hadn't, it would have been on account of size, as it packs
in 31MB just in libraries.</p>
<h2>Conclusions</h2>
<p>I'll probably go with Slim though I'll try to do a minimal test-case of
Slim and Epiphany (just with an auth module) to compare developing with
both. I'm sure the other frameworks have positive sides as well, but
they strike me as overkill for a RESTful API. I get why you would want
to make things slightly more complex if the goal is developing web
applications (as Tonic is advertised for, for instance) but including
smarty? That's simply unnecessary bloat to me.</p>
<p>One thing I skipped over here is obviously the existing bigger
frameworks like CodeIgniter, CakePHP, Zend, etc. All these are way
overkill for an API, if you ask me. They don't get out of the way,
instead they dictate most of your choices (not necessarily a bad thing -
enforcing best practices is good in my book - just not needed here).
Zend, for instance, has a good set of modules that could come in quite
handy for a RESTful api (likely why Frapi includes a whole lot of Zend
code), but Zend seems to be built upon the idea that Java development
practices are good and can/should be used in PHP. Which is quite simply
wrong, if you ask me.</p>
<p>So yeah, the proper tool for the job, which in this case amounts to Slim
or Epiphany. Hereby recommended.</p>Mail madness2011-10-12T20:20:00+02:00Peter Lindtag:plind.dk,2011-10-12:mail-madness.html<p>Another post not on the topic I set out a little while ago, this time
about email - specifically webmail. A family member asked me to backup
their webmail account and it so happens to be a Hotmail account. Naively
I figure that by now, Microsoft must have created a fairly nice webmail
experience that will allow users to export their mail. A bit of research
later and I get the first disappointment: no, no such option exists.
Microsoft would love for you to use Hotmail, but reserves the right to
extend the middle finger to anyone that might be interested in getting
their own personal email out of Hotmail.</p>
<p>Fine, option #2: connect to Hotmail with an email client, say
Thunderbird. Setup is easy, indeed it goes very fast, but what's this?
POP3? But I want IMAP, as I don't want to bother with checking that
messages are not deleted online and I also want to download everything
in the various folders. Some more research and it's revealed that while
M\$ has no problem bleeding money out of their crap search engine, they
can't be bothered to provide a proper interface to their webmail. How
can these morons still be in business? How is it possible that they lure
so many people into using their crap?</p>
<p>Grudgingly, we move on to option #3: Microsofts own Windows Live Mail.
As the company is known for it's loser-mentality (keep everything
proprietary and make sure your own developers know "the secret APIs")
one might expect that this program (package, in fact: another
loser-mentality streak - you need to download an entire package to get
to their mail program, even though you have no use for the other crap)
would work. However, that's not the case: it only downloads whatever's
in the inbox. Microsoft does not believe in backup, apparently.</p>
<p>Conclusion: the only thing I actually managed to do to backup the emails
was to print them to PDF. Yes, that's how horribly bad Microsofts email
service is. It really, truly boggles my mind why anyone would ever trust
anything to do with email from this company (so please dump your POS
exchange servers - there are better alternatives anyway and they're
free).</p>
<p>Well, not entirely true: I also managed to get so angry that I wrote to
Microsoft that they could "fuck off and die". That's how bad my user
experience was.</p>Late night coding2011-09-14T23:25:00+02:00Peter Lindtag:plind.dk,2011-09-14:late-night-coding.html<p>While avoiding the <a href="http://plind.dk/2011/07/24/new-learning-series/" title="Me trying to learn scala">new learning
series</a>
I dreamt up I've been doing some reading and playing with postfix and
ldap. The first topic came about because of some stuff at work (had to
set up postfix with authentication, turned out to be much more complex
than one would expect) and I wanted to make sure I knew how to do it in
the future. The second part came about because my server setup is too
messy - I want to deal with the hosting in a proper fashion where
adding, updating, etc. is easy. Hence ldap came to mind: I want to run
an ldap server in one of the containers on the server, so all the other
containers can authenticate against it as needed and grab other info
from there as well.</p>
<p>So to make sure I don't forget these things after I've done them I've
got a library of files containing notes on how to do things. On top of
that, I've also got <a href="https://trac.edgewall.org/">Trac</a> running on my
server. I installed it mainly to keep track of hosting info (such as
server details, bugs and other things I needed to take care of, projects
from clients, etc) but I discovered it would also be useful to migrate
my library of notes onto Trac. Or rather, I haven't migrated it, I've
just copied it over to trac while keeping (and working on) the local
copy. Hence the need to sync everything. Locally, I keep the notes in
git, making for easy syncing across the network - but I don't have
anything like that setup for trac. Hence, a bit of late night coding and
hey presto! git hooks. I still need to work a bit on the pre-commit hook
but the post-commit hook is done. Just a very simple script to update
trac after I've committed changes to my notes locally. It looks like
this:</p>
<div class="highlight"><pre><span></span><span class="c1">// script residing in the main repo folder</span>
<span class="err">#</span><span class="o">!</span><span class="err">/usr/bin/php</span>
<span class="cp"><?php</span>
<span class="nb">define</span><span class="p">(</span><span class="s1">'TRAC_AUTH'</span><span class="p">,</span> <span class="s1">'username:password'</span><span class="p">);</span>
<span class="nb">define</span><span class="p">(</span><span class="s1">'COOKIEFILE'</span><span class="p">,</span> <span class="nb">tempnam</span><span class="p">(</span><span class="s1">'/tmp'</span><span class="p">,</span> <span class="s1">'cookies_'</span><span class="p">));</span>
<span class="nv">$self</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">'web_hook.php'</span><span class="p">,</span> <span class="s1">'web_update.php'</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">]))</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Call with name of file"</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>
<span class="k">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">in_array</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">],</span> <span class="nv">$self</span><span class="p">))</span> <span class="p">{</span>
<span class="k">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$url_base</span> <span class="o">=</span> <span class="s1">'https://path/to/server/wiki/'</span><span class="p">;</span>
<span class="nv">$url_suffix</span> <span class="o">=</span> <span class="s1">'?action=edit'</span><span class="p">;</span>
<span class="nv">$url_array</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
<span class="s1">'server.txt'</span> <span class="o">=></span> <span class="s1">'Server'</span><span class="p">,</span>
<span class="s1">'development.txt'</span> <span class="o">=></span> <span class="s1">'Development'</span><span class="p">,</span>
<span class="s1">'email.txt'</span> <span class="o">=></span> <span class="s1">'Email'</span><span class="p">,</span>
<span class="s1">'git.txt'</span> <span class="o">=></span> <span class="s1">'Git'</span><span class="p">,</span>
<span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$url_array</span><span class="p">[</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">]]))</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"No such file set: "</span> <span class="o">.</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>
<span class="k">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$url_part</span> <span class="o">=</span> <span class="nv">$url_array</span><span class="p">[</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">]];</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nx">fetch_data</span><span class="p">(</span><span class="nv">$url_base</span> <span class="o">.</span> <span class="nv">$url_part</span> <span class="o">.</span> <span class="nv">$url_suffix</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$data</span><span class="p">)</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Could not fetch data"</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>
<span class="k">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">put_data</span><span class="p">(</span><span class="nv">$data</span><span class="p">,</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">],</span> <span class="nv">$url_base</span> <span class="o">.</span> <span class="nv">$url_part</span><span class="p">))</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"Could not update trac"</span> <span class="o">.</span> <span class="nx">PHP_EOL</span><span class="p">;</span>
<span class="k">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * posts data to the proper</span>
<span class="sd"> * page, updating trac info</span>
<span class="sd"> *</span>
<span class="sd"> * @param string $data</span>
<span class="sd"> * @param string $filename</span>
<span class="sd"> * @param string $url</span>
<span class="sd"> *</span>
<span class="sd"> * @return string</span>
<span class="sd"> */</span>
<span class="k">function</span> <span class="nf">put_data</span><span class="p">(</span><span class="nv">$data</span><span class="p">,</span> <span class="nv">$filename</span><span class="p">,</span> <span class="nv">$url</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$dom</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DOMDocument</span><span class="p">();</span>
<span class="nv">$dom</span><span class="o">-></span><span class="na">loadHTML</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="nv">$xpath</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">DOMXPath</span><span class="p">(</span><span class="nv">$dom</span><span class="p">);</span>
<span class="nv">$form_token</span> <span class="o">=</span> <span class="nv">$xpath</span><span class="o">-></span><span class="na">query</span><span class="p">(</span><span class="s1">'//form[@id="edit"]//input[@name="__FORM_TOKEN"]'</span><span class="p">)</span><span class="o">-></span><span class="na">item</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-></span><span class="na">getAttribute</span><span class="p">(</span><span class="s1">'value'</span><span class="p">);</span>
<span class="nv">$version</span> <span class="o">=</span> <span class="nv">$xpath</span><span class="o">-></span><span class="na">query</span><span class="p">(</span><span class="s1">'//form[@id="edit"]//input[@name="version"]'</span><span class="p">)</span><span class="o">-></span><span class="na">item</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-></span><span class="na">getAttribute</span><span class="p">(</span><span class="s1">'value'</span><span class="p">);</span>
<span class="nv">$action</span> <span class="o">=</span> <span class="nv">$xpath</span><span class="o">-></span><span class="na">query</span><span class="p">(</span><span class="s1">'//form[@id="edit"]//input[@name="action"]'</span><span class="p">)</span><span class="o">-></span><span class="na">item</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-></span><span class="na">getAttribute</span><span class="p">(</span><span class="s1">'value'</span><span class="p">);</span>
<span class="nv">$from_editor</span> <span class="o">=</span> <span class="nv">$xpath</span><span class="o">-></span><span class="na">query</span><span class="p">(</span><span class="s1">'//form[@id="edit"]//input[@name="from_editor"]'</span><span class="p">)</span><span class="o">-></span><span class="na">item</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">-></span><span class="na">getAttribute</span><span class="p">(</span><span class="s1">'value'</span><span class="p">);</span>
<span class="nv">$comment</span> <span class="o">=</span> <span class="s2">"Saved from git hook"</span><span class="p">;</span>
<span class="nv">$text</span> <span class="o">=</span> <span class="nb">file_get_contents</span><span class="p">(</span><span class="nv">$filename</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$text</span><span class="p">))</span> <span class="p">{</span>
<span class="k">echo</span> <span class="s2">"No text to save"</span><span class="p">;</span>
<span class="k">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$post_data</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
<span class="s1">'__FORM_TOKEN'</span> <span class="o">=></span> <span class="nv">$form_token</span><span class="p">,</span>
<span class="s1">'version'</span> <span class="o">=></span> <span class="nv">$version</span><span class="p">,</span>
<span class="s1">'action'</span> <span class="o">=></span> <span class="nv">$action</span><span class="p">,</span>
<span class="s1">'from_editor'</span> <span class="o">=></span> <span class="nv">$from_editor</span><span class="p">,</span>
<span class="s1">'text'</span> <span class="o">=></span> <span class="nv">$text</span><span class="p">,</span>
<span class="s1">'comment'</span> <span class="o">=></span> <span class="nv">$comment</span><span class="p">,</span>
<span class="p">);</span>
<span class="nv">$curl_handle</span> <span class="o">=</span> <span class="nb">curl_init</span><span class="p">();</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_URL</span><span class="p">,</span> <span class="nv">$url</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_FOLLOWLOCATION</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_RETURNTRANSFER</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_USERPWD</span><span class="p">,</span> <span class="nx">TRAC_AUTH</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_POST</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_POSTFIELDS</span><span class="p">,</span> <span class="nv">$post_data</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_COOKIEFILE</span><span class="p">,</span> <span class="nx">COOKIEFILE</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">curl_exec</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">);</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * fetches the data from trac</span>
<span class="sd"> *</span>
<span class="sd"> * @param string $url</span>
<span class="sd"> *</span>
<span class="sd"> * @return string</span>
<span class="sd"> */</span>
<span class="k">function</span> <span class="nf">fetch_data</span><span class="p">(</span><span class="nv">$url</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$curl_handle</span> <span class="o">=</span> <span class="nb">curl_init</span><span class="p">();</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_URL</span><span class="p">,</span> <span class="nv">$url</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_FOLLOWLOCATION</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_RETURNTRANSFER</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_USERPWD</span><span class="p">,</span> <span class="nx">TRAC_AUTH</span><span class="p">);</span>
<span class="nb">curl_setopt</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">,</span> <span class="nx">CURLOPT_COOKIEJAR</span><span class="p">,</span> <span class="nx">COOKIEFILE</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">curl_exec</span><span class="p">(</span><span class="nv">$curl_handle</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// this is the post-commit file in .git/hooks/</span>
<span class="k">for</span> <span class="nx">LINE</span> <span class="nx">in</span> <span class="sb">`git log --oneline --name-only -1 HEAD | awk '{if ($2 == "") print $1}'`</span>
<span class="k">do</span>
<span class="k">if</span> <span class="o">!</span> <span class="o">./</span><span class="nx">web_update</span><span class="o">.</span><span class="nx">php</span> <span class="nv">$LINE</span>
<span class="nx">then</span>
<span class="k">echo</span> <span class="s2">"Failed to update trac with changes"</span>
<span class="nx">fi</span>
<span class="nx">done</span>
</pre></div>
<p>It should be fairly easy to figure out what's going on. The post-commit
hook checks all the lines from the log for the last commit and filters
the output, passing it to the update script as needed. The update script
fetches the edit page from trac for the given file, scrapes out the
needed variables to post back to trac, then sends a post request to
trac, thus updating the trac wiki.</p>
<p>Now, I'm sure there are better ways (most likely some sort of trac
plugin or api) to update trac remotely ... but the point is, this is
just a bit of late night coding and done deal: I'm now updating my trac
wiki whenever I commit my notes locally.</p>Interlude: nginx + php2011-08-03T18:34:00+02:00Peter Lindtag:plind.dk,2011-08-03:interlude-nginx-php.html<p>In a fit of "no I don't want to do more programming right now" I got
back to another interesting topic: alternatives to the
<a href="https://www.apache.org/">Apache</a> server. While I love it for it's
stability and ease of setup (I rarely have problems with that aspect of
web-development), it's still good to know of the alternatives and to see
what else is out there. I'll likely never play with
<a href="https://www.iis.net/">IIS</a> (unless MS decides it should be free and work
on a free OS ... madness, I know) but luckily there are other very
interesting alternatives, one of them being <a href="https://nginx.org/">nginx</a>.</p>
<p>The interesting thing about nginx is that it's so very lightweight. In
fact, on my server, I run nginx with php as a proxy for web and mail to
a number of VPS setups. The individual VPS setups that run webapps
typically run Apache, and not one of them is remotely close to the same
memory footprint as nginx + php. This is not much of an observation
though, seeing as the Apache installs are not trimmed to be low-resource
- however, I haven't trimmed nginx either, it just is small-footprint
resource-wise. Thus, for the average layperson sysadmin (a contradiction
in terms, though not in practice) an nginx + PHP install can make rather
good sense, especially if you're on a low-powered VPS setup.</p>
<p>So I decided to look a bit more into nginx and PHP. As mentioned I
already have it running on my server but that was a fairly fast setup
without too much thought - just consulting the various tutorials I could
find while trying to sidestep any obvious issues. Hence, more study was
needed.</p>
<h2>Installing nginx</h2>
<p>On Debian/Ubuntu you can just apt-get nginx</p>
<div class="highlight"><pre><span></span>sudo apt-get install nginx
</pre></div>
<p>That will, however, install a slightly older version of nginx. To get
the newest stable, go
to <a href="https://nginx.org/en/download.html">http://nginx.org/en/download.html</a> and
grab the latest.</p>
<div class="highlight"><pre><span></span>sudo -s
cd /opt
wget https://nginx.org/download/nginx-1.0.5.tar.gz && tar -zxvvf nginx-1.0.5.tar.gz
rm nginx-1.0.5.tar.gz
cd nginx-1.0.5
./configure --http-log-path=/var/log/nginx/ --error-log-path=/var/log/nginx/ --with-http_ssl_module --with-mail --with-mail_ssl_module --with-imap --with-imap_ssl_module --with-pcre
make
make install
</pre></div>
<p>You'll need a number of packages to be able to run this, but the
configure command should complain and tell you which if you don't know.
Also note that the mail and imap configuration settings above are in
place because I use nginx to proxy email access to the individual VPS
machines I have setup - if you don't need that, you don't need those
settings. After you're through running the commands (apt-get or manual
install), you should have nginx setup and ready to run (running
actually, if you used apt-get).</p>
<p>After this, you need to get PHP installed, and here things get more
tricky. There are a number of ways of going about things:</p>
<ul>
<li>compile PHP yourself from source
(<a href="https://php.net/downloads.php">http://php.net/downloads.php</a>) or
install php5-cgi using apt-get. Then use your a homegrown script to
manage PHP</li>
<li>as above, but use spawn-fcgi to manage PHP (depending upon your OS
version, you might need to install lighttpd as spawn-fcgi only
became</li>
<li>use PHP-FPM (manually compiled from
<a href="https://php-fpm.org/">http://php-fpm.org/</a> or installed via apt-get)</li>
</ul>
<p>The configuration from this point on differs a bit, because you'll
either be setting up your own script, configuring spawn-fcgi or
configuring PHP-FPM. However, the most relevant part for this post
refers to nginx and PHP communicating - for info on the bits about
setting things up, you can check out <a href="https://library.linode.com/web-servers/nginx/php-fastcgi">Linode
Library</a> for
scripts and info. The <a href="https://wiki.nginx.org/Configuration">nginx wiki</a>
also has good info. What it boils down to, in the end, is whether nginx
will be communicating with PHP over TCP or through sockets. If you
google you'll find differing advices but the majority seems to point in
the direction of sockets. The reasoning behind this is that sockets are
faster than TCP - there's much less overhead. You might also find some
warnings that some people have had problems with sockets though - so
it's not an easy choice, and essentially you need to test things out
yourself.</p>
<p>Which I then did - or rather, I settled for sockets, then decided to do
some benchmark testing, and then figured out there was a need to test
out both before you actually choose. The configuration I went with looks
like:</p>
<div class="highlight"><pre><span></span><span class="err">#</span> <span class="nt">relevant</span> <span class="nt">nginx</span> <span class="nt">part</span>
<span class="nt">location</span> <span class="o">/</span> <span class="p">{</span>
<span class="n">root</span> <span class="n">html</span><span class="p">;</span>
<span class="n">fastcgi_pass</span> <span class="n">unix</span><span class="o">:/</span><span class="n">tmp</span><span class="o">/</span><span class="n">php5</span><span class="o">-</span><span class="n">fpm</span><span class="o">.</span><span class="n">sock</span><span class="p">;</span>
<span class="n">fastcgi_index</span> <span class="n">index</span><span class="o">.</span><span class="n">php</span><span class="p">;</span>
<span class="n">fastcgi_param</span> <span class="n">SCRIPT_FILENAME</span> <span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">www</span><span class="o">/</span><span class="n">nginx</span><span class="o">-</span><span class="n">php</span><span class="o">/</span><span class="err">$</span><span class="n">fastcgi_script_name</span><span class="p">;</span>
<span class="n">include</span> <span class="n">fastcgi_params</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">#</span> <span class="nt">relevant</span> <span class="nt">php-fpm</span> <span class="nt">part</span><span class="o">,</span> <span class="nt">from</span> <span class="o">/</span><span class="nt">etc</span><span class="o">/</span><span class="nt">php5</span><span class="o">/</span><span class="nt">fpm</span><span class="o">/</span><span class="nt">pool</span><span class="nc">.d</span><span class="o">/</span>
<span class="nt">listen</span> <span class="o">=</span> <span class="o">/</span><span class="nt">tmp</span><span class="o">/</span><span class="nt">php5-fpm</span><span class="nc">.sock</span>
</pre></div>
<p>Together, the two will make the server serve requests to php-fpm through
sockets. Now, because I was playing with nginx and PHP-fastcgi I thought
it would be interesting to see how it would handle a bunch of requests,
so I fired up ab (Apache Benchmark) from apache and started flooding
nginx. First I went with something like:</p>
<div class="highlight"><pre><span></span>ab -kc 100 -n 1000 nginx-php:8000/
</pre></div>
<p>which worked fine (if you're not familiar with the options for ab, the
line above will fire 100 concurrent keepalive requests at the server at
nginx-php, port 8000, until a 1000 requests have been sent. I had put a
.php script with nothing but a single echo at the end of that url, just
to check the throughput. Then, just for kicks, I upped the concurrency
to 200 requests a second - and suddenly I start seeing errors. Checking
the error log of nginx, I see the following:</p>
<div class="highlight"><pre><span></span>connect() to unix:/tmp/php5-fpm.sock failed (11: Resource temporarily unavailable) while connecting to upstream
</pre></div>
<p>I then added a -v 3 to the ab command line, to get some more detailed
info about what nginx would output if that happened. Turns out you'll
get something like this:</p>
<div class="highlight"><pre><span></span><span class="n">WARNING</span><span class="o">:</span> <span class="n">Response</span> <span class="n">code</span> <span class="n">not</span> <span class="mi">2</span><span class="n">xx</span> <span class="o">(</span><span class="mi">502</span><span class="o">)</span>
<span class="n">LOG</span><span class="o">:</span> <span class="n">header</span> <span class="n">received</span><span class="o">:</span>
<span class="n">HTTP</span><span class="o">/</span><span class="mf">1.1</span> <span class="mi">502</span> <span class="n">Bad</span> <span class="n">Gateway</span>
</pre></div>
<p>Essentially, what happens is that nginx tries to open a connection to
PHP-fastcgi, fails for one reason or another, then responds to the user
with a 502 Bad Gateway.</p>
<p>Thinking that maybe it was a limitation based on the php setup I tried
changing the fastcgi settings (how many PHP processes to keep around,
max processes, etc) but no change - I kept coming up against the 502.
The limit on my local setup seems to be around 150-160 concurrent
requests - more than that and errors crop up. However, I then switched
to a TCP based setup and the errors are gone. Testing a bit, I up the
concurrency to 400 connections, still no errors. At triple the
concurrency I start to see problems, but not before.</p>
<p>However, instead of calling this a win for TCP there's the other aspect:
speed. Running a test with 100 concurrent connections for a total of
50,000 requests, sockets gave a 15% speed increase compared to TCP.
Looking at CPU consumption, on the other hand, showed a higher spike
with sockets than TCP (reasonably explained by bigger throughput).
Memory consumption didn't spike much for either setup, though sockets
used a tad more.</p>
<h2>Conclusion</h2>
<p>You need to figure out what your likely scenario is when setting up your
server. That should more or less self-explanatory - however, it's
something you find out time and again as you play around with hardware
and software.</p>
<p>While I have seen some possible solutions on how to ramp up the
concurrency for nginx + PHP using sockets, the main thing to do is ask
yourself: will I actually ever hit much more than 100 concurrent
requests that I need to forward to PHP? If not, then you're safe using
sockets. If you expect to hit much more, you should probably look into
how to optimize nginx with PHP - or consider other options that might
scale easier.</p>New learning series2011-07-24T19:39:00+02:00Peter Lindtag:plind.dk,2011-07-24:new-learning-series.html<p>I've decided to pick up
<a href="www.scala-lang.org" title="Scala language site">Scala</a> and in order to learn
the language I'm going to do the good old exercise: create a blog. As
always with these things: no, I have absolutely no interest in adding
blog app to this world, nor do I want to create my own blog app that
I'll need to update and do security fixes for. This is purely a learning
exercise and while I'll be posting code and such here, I want to
discourage anyone following this (or reading by accident) from using it
for anything but inspiration.</p>
<p>To gauge the efficiency of this, I'll be doing the same exercise with
<a href="https://drupal.org/" title="Drupal">Drupal</a>,
<a href="https://codeigniter.com/">CodeIgniter</a>, <a href="http://cakephp.org/">CakePHP</a>,
<a href="https://www.symfony-project.org/">Symfony</a> and
<a href="https://expressjs.com/">ExpressJS</a> for
<a href="https://node.js">nodeJS</a> (technically, this will be a learning exercise
on pretty much the same level as with Scala, as I don't know much of
node.js). And before I forget - I'll be using
<a href="https://liftweb.net/">Lift</a> for Scala for this exercise.</p>
<h2>Step 1: install Scala and Lift</h2>
<p>I am not entirely sure if installing Lift will also pull down Scala by
itself, or you're required to install it first, but as I want to learn
Scala on it's own, I decided to install it by itself first. That's
fairly easy and done as follows:</p>
<div class="highlight"><pre><span></span>cd /opt/
sudo wget https://www.scala-lang.org/downloads/distrib/files/scala-2.9.0.1-installer.jar
sudo java -jar scala-2.9.0.1-installer.jar
</pre></div>
<p>This will install the Scala interpreter and compiler (obviously you
should check for an updated jar before forward). It's possible you might
have some paths not set properly and will need to add them manually (in
one installation it worked fine, in another I needed to add
/usr/local/scala/bin/ to my paths.</p>
<p>Installing Lift is just as easy. Download the tarball, untar and then
install:</p>
<div class="highlight"><pre><span></span>cd /var/www/
wget https://github.com/lift/lift_24_sbt/tarball/master
tar -zxvvf master
</pre></div>
<p>After untar'ing the file, you have the choice of installing one of a
number of different packages. Lift comes with a basic html app, a blank
app, an MVC app and an xhtml app. You can choose to run whichever suits
you better - they have common dependencies and don't install to
anywhere, so there's problem in shopping around between them as you
like. You do, however, need to figure out which one to base your app on.
Whichever you choose, you install it first time round in the following
manner:</p>
<div class="highlight"><pre><span></span>cd /path/to/app
./sbt update ~jetty-run
</pre></div>
<p>This should pull down whatever dependencies you need and will end up
running a webserver on port 8080, ready for you to point your browser
to. And that's pretty much the end of the software setup :)</p>General browser blues2011-07-10T12:40:00+02:00Peter Lindtag:plind.dk,2011-07-10:general-browser-blues.html<p>Well, lately, IE is not the only browser to give me the blues. In fact,
all week I've been bitten by browsers handling things differently, often
just between versions of browsers.</p>
<h2>Firefox</h2>
<p>A page I had worked on used some absolute positioning within relative
positioned elements to achieve the proper layout (idea being to position
some buttons at the bottom of a box). It worked just fine in IE, Chrome,
FF 3+ ... but FF 4 choked. What was the problem? Well, the relative
positioned element was a table cell. Apparently, while most newer
browsers happily support this, there is no defined behaviour: see W3 for
<a href="https://www.w3.org/TR/CSS21/visuren.html#propdef-position">specs</a>.
Hence, FF 4 decided that while the table cell could be relative, the
absolute positioned elements should be positioned according to some
other element. Despite FF 3+ doing things exactly as expected.</p>
<h2>IE</h2>
<p>IE gave me a good chance to refresh my knowledge of HTTP status codes.
While I knew quite well that search engines will use the knowledge of
301s to avoid reindexing pages that have been moved (and also transfer
pagerank), I wasn't aware that browsers can (and will) cache HTTP
headers. Specifically, <a href="https://blogs.msdn.com/b/ie/archive/2010/07/14/caching-improvements-in-internet-explorer-9.aspx">IE9 will cache 301
responses</a>.
Now, this is actually a good thing, as it'll speed up browsing for
users. However, it means that web developers really need to remember
their status codes. If you use redirecting for login, logout or handling
POST requests to avoid problems with the back button, you need to use a
302, 303 or 307 - 301s might get cached by the browser (which actually
meant the logout function didn't work on the site I was working on). In
case you're using PHP (like I am) <a href="https://php.net/manual/en/function.header.php#78470">this note on the header()
page</a> is good.</p>IE9 blues2011-07-06T22:38:00+02:00Peter Lindtag:plind.dk,2011-07-06:ie9-blues.html<p>Today I had to deal with a weird bug in a site I was updating. As it
mimicked a bug I had fixed earlier for IE6 I assumed it would be the
same thing, but no - something quite different was wrong. In essence the
problem was that I had created a button with some styling to it, and to
make sure the button could expand I used a button element with a span
inside. Then, because the button had to trigger a
<a href="https://fancybox.net">Fancybox</a> popup, I had put an anchor element
inside the button, around the span. This worked without a hitch in the
proper browsers (Chrome, Firefox, etc) but not in IE6 - there I had to
add an event listener to the button, to trigger the click on the link
(so the bug was presumably the old IE thing of exactly what catches the
event).</p>
<p>However, according to the XHTML 1.0 transitional DTD an anchor inside a
button is not valid HTML. Fair enough, I thought, the button allows for
a host of other elements - such as a paragraph tag. So I wrapped the
anchor in a paragraph, checked the validator and sure, it was good and
valid. IE9 still wasn't happy though. Long story short, I realized that
IE9 would completely disregard any anchors inside a button - unless the
document was running in IE8 compatibility mode (which is another way of
saying that IE9 didn't get it but IE8 did).</p>
<p>So here's my problem: sure, I managed to fix the code by wrapping the
button in the anchor element, instead of the other way round, but I'd
just really love to know seemingly valid code creates such a big
problem. For reference, here's the code that will generate the problem:</p>
<div class="highlight"><pre><span></span><span class="cp"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"></span>
<span class="p"><</span><span class="nt">html</span> <span class="na">xmlns</span><span class="o">=</span><span class="s">"https://www.w3.org/1999/xhtml"</span> <span class="na">dir</span><span class="o">=</span><span class="s">"ltr"</span> <span class="na">lang</span><span class="o">=</span><span class="s">"da"</span> <span class="na">xml:lang</span><span class="o">=</span><span class="s">"da"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>blah<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">'https://plind.dk'</span><span class="p">></span>plind.dk<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">p</span><span class="p">></span>
<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</pre></div>
<p>I cannot see anything in the XHTML 1.0 transitional DTD to suggest that
the above is not valid code - and hence, IE9 should render it properly.
Especially seeing as IE8 seems to do the job ok. But, nope: the anchor
is disregarded.</p>
<p>Should anyone know why, I'd love to hear why this would be the case :)</p>Misc. internet2011-06-25T16:23:00+02:00Peter Lindtag:plind.dk,2011-06-25:misc-internet.html<p>Various ranting topics today - some smaller things that might not make
it to a post in themselves, but under the name "Misc. internet" here
they are.</p>
<h2>Denmark - big brother nation</h2>
<p>Back in 2002, after the 9/11 scare, the Danish government made a law
that requires ISPs to log internet traffic for 1 year
(<a href="https://www.themis.dk/searchinclude/lovsamling/Retsplejelovens_kapitel_71.html">http://www.themis.dk/searchinclude/lovsamling/Retsplejelovens_kapitel_71.html</a>
- text in Danish). The law was enacted essentially to help in
terrorism-related police-investigations, but of course there's no such
limit to the law. The effect of the law is to put a burden on ISPs
(having to log and store data for 1 year) while gaining no clear benefit
- how will you ever sift through such data? Worse still, the law was
useless from the start: exempt from logging were publicly available
internet-spots such as internet available through public libraries,
internet cafes, hotspots, etc. Thus, Danes were being watched, while
anyone wanting complete anonymity could have it if the wanted it (which,
presumably, terrorists do). In other words, big brother legislation at
it's worst: no benefit for the Dane, only less freedom.</p>
<p>Recently, a work-group under the ministry of justice have been reviewing
the laws for logging of internet traffic. If you were naive, you might
have hoped they would come up with the proposal of scrapping the laws.
If you were cynical, you might have thought they would come up with a
scheme to log all internet traffic. However, I have no idea what it
would take to foresee the level of idiocy they actually came up with:
requiring logging of all internet traffic and denying anyone access to
the net if they do not provide personal ID for logging. There's a quick
<a href="https://mchangama.blogs.berlingske.dk/2011/06/23/et-frontalangreb-pa-det-frie-og-abne-internet/">run-through of central
pointers</a>
on Jacob Mchangama's blog as well as arguments as to why this is insane.
Not that you would ever need arguments: the ignorance and stupidity
behind the proposed changes cannot be overlooked by anyone with a
minimum of common sense.</p>
<p>I'll post just one point from the blog: if the changes would make it
into law, foreigners would not be able to use internet cafes or hotspots
in Denmark - because they would not be able to identify themselves
properly.</p>
<p>I completely fail to understand how anyone making propositions such as
these can hold down a job in the Danish government. How can you not be
sacked for idiocy on this level? Why do we not have laws, that imprison
bureaucrats and politicians alike when they strive to implement big
brother conditions? They would be so much more useful than what the
incompetent fools have put in place over the last 10 years.</p>
<h2>International organisations</h2>
<p>On an unrelated note (perhaps not quite: the topics of laws and internet
are shared), the annual BeVolunteer (the legal organisation behind
<a href="https://www.bewelcome.org">BeWelcome</a>) general assembly is being
planned. The organisation is close to dead (there have been no meetings
of the board for the past year) and this is more or less the last chance
to do anything about it. What's needed is volunteers: people with
motivation to do something about the status quo. The old volunteers have
either burned out themselves or burned other volunteers out - so that
currently, what's left is the BW site and almost nothing more.</p>
<p>Why did things come to this? One reason was the level of bureaucracy and
fighting over what the rules/regulations specified. As in any
organisation, we had people with different motivations: some wanted to
just get going with the work, others wanted to spend countless hours
getting all the small print perfect. The main reason for the latter is
that BeWelcome was born out of frustration with the dictatorial state of
Hospitality Club - the statutes as well as the rules and regulations
were thus a product of the scare, that Hospitality Club would try to do
a hostile takeover (not entirely unfounded: read
<a href="https://bewelcome.info/">http://bewelcome.info/</a> for more).</p>
<p>The strange thing is, that all involved in BeVolunteer have the idea
that the organisation is very hard to change: you need to get a certain
amount of members to show up at the general assembly and convince them
that the statutes need changing. In fact, while talking to one of the
board directors, both of us assumed that the GA would need to be
physical, i.e. that people would actually have to show up at one place -
and that if you couldn't attend, your only choice if you wanted a say
was to delegate your vote to someone physically present. However, that
is <strong>simply not the case!</strong> The rules and regulations for BeVolunteer
specify that:</p>
<blockquote>
<p>The Board of Directors defines the way the assembly is held. It may be
done face-to-face or by virtual techniques (instant messaging / video
conference etc.).</p>
</blockquote>
<p>However, the GA has never been held like that, nor was it ever proposed.
Our (mistaken) beliefs about our own organisation actually get in the
way of changes that we need to make and could make! We never managed to
truly get away from our notions of what an organisation like BeVolunteer
is, so we never managed to find out what it can be - even though we
actually have rules in place to allow for cooperation between people
that are very far apart and have zero chances of meeting at the same
place and same time in required numbers for a GA.</p>
<p>What can one learn from this? I actually started this part of the post
firmly believing that the statutes for BeVolunteer were to blame for the
impossibility of changing it's structure - but they're not. Instead,
what has been limiting us - in my view - is our mistaken ideas of what
the limits were. We wasted much too much time trying to settle on what
we should and should not do. We did not spend nearly enough time doing.
When people started getting that "eyes glazed over" look at meetings -
that should have been warning bells telling us that now was the time to
shut up and get down to business. Maybe that way, we wouldn't have lost
as many motivated members - and maybe BeWelcome would then still have a
future.</p>New toy: Qnap NAS2011-06-04T16:19:00+02:00Peter Lindtag:plind.dk,2011-06-04:new-toy-qnap-nas.html<p>We took the consequence of a huge electricity bill today by getting a
<a href="https://qnap.com/pro_detail_feature.asp?p_id=192">Qnap NAS TS-212</a>. It's
a pretty sleek device, doesn't take up a lot of space at all, and better
still, has a very low power consumption. 21W at active state compared to
lots and lots in my 7 year old desktop with an external USB drive.</p>
<p>The Qnap has got space for two internal drives, up to 2TB each. Which is
what I decided to stuff into it, straight away (the entire setup cost me
2.650 kr / 355 euros). The device allows you to setup the drives pretty
easily, as single disks, JBOD configuration, raid 1 or raid 0. I went
for raid 1 as the device was more for backup than actually storage - as
storage space is cheap it makes more sense to pay for security.</p>
<p>Configuration and setup of the device is overall very easy - the web
interface is pretty easy to deal with. The quick install manual that
comes with the device specified to connect to the device directly if
installing from linux - however, if you plug it into a DHCP capable
router or such it'll pick up an IP automatically and you can just
connect to that, no trouble.</p>
<p>There's only one thing I'm unhappy with so far in the setup process: as
noted, I chose the raid 1 setup which means mirrored drives. For some
strange, outerworld reasoning, at the end of the configuration, the
device decided that the two drives should be sync'ed in order to mirror
correctly - which seems to mean copying 2TB of nothing from one drive to
the other. In total 5 hours 30 minutes of copying no data from one drive
to another. I honestly have no idea why it decided to waste my time
like that ...</p>
<p>Other points of interest is that the system allows for interfacing
through a large range of protocols. Personally, I'm rather happy to see
it support ssh. The device runs Qnaps own linux distro (the firmware I
downloaded contains kernel 2.6.33.2) so you can dabble with a lot of
things through that. If that's not enough, there's also the possibility
for putting other distros on the device (Debian for example), so you can
customize to your hearts content.</p>
<p>Overall, I'm happy with the device at this point. I'll try to write
another post in a month or so when I've had the time to setup backups
and play with some of the different features of the system.</p>Blog hiatus2011-05-12T19:46:00+02:00Peter Lindtag:plind.dk,2011-05-12:blog-hiatus.html<p>Once more I managed an unintended blog hiatus. Seems that happens a bit
too often - certainly not the first time it's happened to me. Anyway, I
want to pick up again, get some words out again. So, what I'll try to do
is one PHP-related post a month and one
personal/geeky/development-related post a month. I.e. two posts a month
... should be do-able (right ...).</p>
<p>Anyway, I have some things I want to write about, in part to learn
(nothing like presenting what you're learning as you learn it, to help
you learn .... learn learn learn!) and in part because writing is just
fun. PHP-related that would be getting ready for the Zend certification
(so brushing off skills and reading on things I don't know too well
atm), getting into frameworks and tools of our trade, plus some personal
projects. Personally, well, there's some roleplaying stuff (and here we
quickly veer back into the geeky parts, as that will be - among other
things - on Infosys and Danish conventions), some general life-stuff
(living in Denmark and being disgusted with the current political
climate is one topic) and the odd rant on something weird.</p>
<p>So yeah ... there you have it.</p>Webhosting woes, part 12010-09-24T09:24:00+02:00admintag:plind.dk,2010-09-24:webhosting-woes-part-1.html<p>I've recently been doing some webhosting setup and I thought I'd detail
some of the experience and knowledge gained here. In part to make sure I
don't forget myself, in part because a lot of the solutions to the
problems I encountered I found online, on blogs and what not.</p>
<h2>The situation</h2>
<p>The premise for the whole thing is that I've got a dedicated server and
I want to utilize it for hosting various sites and services. I want to
do this in a way that allows me to isolate individual sites/services so
that if one site fails/is hacked/explodes then it won't take everything
down with it. The best solution for this, as far as I know, is running
virtual containers - every site and service runs in isolation, using a
bit of the overall resources.</p>
<h2>OpenVZ</h2>
<p>The first task, the focus of this post, is getting
<a href="https://wiki.openvz.org/Main_Page" title="The OpenVZ wiki with lots of great info">OpenVZ</a>
running. I had at first been experimenting with
<a href="https://lxc.sourceforge.net/">LXC</a> - the successor to OpenVZ for newer
versions of Ubuntu - but I was stuck in the mud with nothing working
using that (I'll likely post my experiences with that at some other
point). So instead my money was on OpenVZ running on Debian Lenny -
which turned out to be a fairly good choice as installing it was a
breeze. Here's how I made it work:</p>
<h3>Step 1: getting the kernel in shape</h3>
<p>OpenVZ needs a slightly modified kernel to be able to do some of its
magic, so the first step is to install a modified kernel.</p>
<div class="highlight"><pre><span></span>sudo apt-get install linux-image-openvz-686
</pre></div>
<p>If that goes as it should (i.e. no errors) you then need to reboot the
machine. When it comes back up again, check that the kernel is now in
fact the proper one.</p>
<div class="highlight"><pre><span></span>uname -r #should show something with openvz
ps -eF | grep vz # should show [vzmond]
ifconfig -a # should show a venet0 device
</pre></div>
<h3>Step 2: change kernel parameters through sysctl</h3>
<p>On the host machine, you then need to allow packet forwarding for ipv4
addresses. The reason behind this is that you'll normally want network
connectivity for your containers - allowing them to be accessible to
each other and the host. Modify your /etc/sysctl.conf to look like this:</p>
<div class="highlight"><pre><span></span>net.ipv4.ip_forward=1
net.ipv4.conf.default.proxy_arp = 0
net.ipv4.conf.default.forwarding=1
net.ipv4.conf.all.rp_filter = 1
kernel.sysrq = 1
net.ipv4.conf.default.send_redirects = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts=1
</pre></div>
<p>As well as enabling ipv4 forwarding, you're disabling
<a href="https://en.wikipedia.org/wiki/Address_Resolution_Protocol">ARP</a>,
enabling filtering of IPs (a sanity of IP addresses), enabling
<a href="https://en.wikipedia.org/wiki/Magic_SysRq_key">SysRq</a>, ignoring icmp
echo broadcasts, and setting some basic settings for network interface
redirects (not necessarily the best, it seems - <a href="https://forum.openvz.org/index.php?t=msg&goto=3144">see this forum
post</a> on the matter).</p>
<p>After modifying the sysctl.conf file, update the kernel runtime
parameters with the values by executing this:</p>
<div class="highlight"><pre><span></span>sudo sysctl -p # update kernel runtime params with values from /etc/sysctl.conf
</pre></div>
<h3>Step 3: have fun!</h3>
<p>The host system is basically ready now to get containers up and running.
All you need to do is create or download some container templates.
Seeing as a lot of people have already been through that one, you can
easily <a href="https://wiki.openvz.org/Download/template/precreated">get your hands on
templates</a> through
the OpenVZ template downloads. Store container templates in
/var/lib/vz/template/cache - and remember not to unpack them after
downloading.</p>
<p>Before creating containers, it's worth setting some defaults for
container creation. Edit /etc/vz/vz.conf to set preferred template (from
the ones you downloaded or created), if any, and proper iptables params.
The latter is important if you feel like creating more fancy iptables
rules inside containers. I personally modify my vz.conf to:</p>
<div class="highlight"><pre><span></span>IPTABLES="ip_tables ipt_REJECT ipt_tos ipt_limit ipt_multiport iptable_filter
iptable_mangle ipt_TCPMSS ipt_tcpmss ipt_ttl ipt_length ip_conntrack
ip_conntrack_ftp ip_conntrack_irc ipt_LOG ipt_conntrack ipt_helper
ipt_state iptable_nat ip_nat_ftp ip_nat_irc ipt_TOS"
</pre></div>
<p>Then, to create a container, issue the following commands:</p>
<div class="highlight"><pre><span></span>sudo vzctl create <id> --ostemplate <osname> # <id> is an ID *you* choose for the container
# <osname> is the name of the container template, less the .tar.gz
sudo vzctl set <id> --ipadd <ip> --save # <ip> is the internal ip you want to assign the container
sudo vzctl set <id> --nameserver <ns_ip> --save # <ns_ip> is the ip of a nameserver to use
sudo vzctl set <id> --hostname <hostname> --save # <hostname> is the hostname to use for your container
</pre></div>
<p>If these commands run without errors, you should be able to start the
container with:</p>
<div class="highlight"><pre><span></span>sudo vzctl start <id>
</pre></div>
<p>You can check that the container is running by issuing:</p>
<div class="highlight"><pre><span></span>sudo vzlist
</pre></div>
<p>And you can step into the container by issuing:</p>
<div class="highlight"><pre><span></span>sudo vzctl enter <id>
</pre></div>
<p>And that's pretty much the basics of it. There are obviously a lot more
one can do, like securing the entire install with proper firewall rules,
etc - but that's for another time.</p>How to fix your sendmail woes2010-09-22T11:52:00+02:00Peter Lindtag:plind.dk,2010-09-22:how-to-fix-your-sendmail-woes.html<p>It's easily done: install postfix/exim. Whatever you do, don't bother
with sendmail - it's complete and utter crap, designed only to waste
your time as you try to figure out why it's failing at
sending/receiving/relaying/destroying email. JUST DON'T USE IT!</p>
<p>And yes, I just had to work on a server with sendmail installed. Spent 2
hours trying to convince sendmail to play nice, to work as it should.
Fail. Spent 2 minutes installing and configuring postfix. Success. That
about says it.</p>Dependency injection and Zend Framework2010-08-11T12:52:00+02:00Peter Lindtag:plind.dk,2010-08-11:dependency-injection-and-zend-framework.html<p>Lately I've been working on a site based on the Zend Framework. It's
been a good chance to get more intimate with ZF, learning the inner
workings and quirks of the framework. Today I came across the question
of how to do dependency injection for your controllers - I was looking
for a way to rid the code of the 'new' keyword as well as static
methods, coupling things more loosely.</p>
<p>Controllers aren't meant to override the __construct() method in ZF -
sure you can do it, but there are meant to be better ways. That leads to
the question: how? Well, if you do have a look at the __construct()
method of Zend_Controller_Action, you'll notice that it takes three
parameters. The last one is the interesting one here: it's an array
called invokeArgs. This should immediately set you off testing what gets
passed in - a bunch of goodies, it turns out. Among other things, you'll
be getting a copy of the Bootstrap object.</p>
<h2>ZF Controller Dependency Injection, #1</h2>
<p>The above leads to the first, not too good way of injecting things. Just
stick whatever you want injected into the bootstrap object - your
controller has access to that through the invokeArgs parameter. That's
not a very good solution though, as you'll be breaking <a href="https://en.wikipedia.org/wiki/Law_of_Demeter">the Law of
Demeter</a>: your controller
needs to know that your injected param sits inside the bootstrap
element, so you'll be accessing the injected elemented indirectly. Not
nice.</p>
<h2>ZF Controller Dependency Injection, #2</h2>
<p>I googled a bit and came across <a href="https://www.ibuildings.co.uk/blog/archives/1181-Dependency-Injection-and-Zend-Framework-Controllers.html">Dependency Injection and Zend Framework
Controllers</a>,
which has a nice solution to the problem. As the param name suggests,
invokeArgs can be modified - you can add to the args to your heart's
content. You need to use the setParam() method of
Zend_Controller_Front - so, in the bootstrap object for instance, you
can do the following:</p>
<div class="highlight"><pre><span></span><span class="o">$</span><span class="nt">front</span> <span class="o">=</span> <span class="nt">Zend_Controller_Front</span><span class="o">:</span><span class="nd">:getInstance</span><span class="o">();</span>
<span class="o">$</span><span class="nt">front-</span><span class="o">></span><span class="nt">setParam</span><span class="o">(</span><span class="s1">'factory'</span><span class="o">,</span> <span class="nt">new</span> <span class="nt">MyFactory</span><span class="o">);</span>
</pre></div>
<p>Your object will then get passed to the controller in the invokeArgs
array. You can access this either through
\$this->_invokeArgs['factory'] or the getInvokeArg() method of
Zend_Controller_Action - the latter being preferable.</p>Book review: Plone 3 Products Development Cookbook2010-07-26T06:40:00+02:00Peter Lindtag:plind.dk,2010-07-26:book-review-plone-3-products-development-cookbook.html<p><a href="http://www.packtpub.com/plone-3-3-products-development-cookbook/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003285"><img alt="Cover of Plone 3.3 Products Development
Cookbook" src="https://plind.dk/wp-content/uploads/2010/05/6729_MockupCover.jpg.png" title="6729_MockupCover.jpg" /></a>The
book I'm reviewing this time is <a href="https://www.packtpub.com/plone-3-3-products-development-cookbook/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003285" title="The book at Packt Publishings shop">Plone 3 Products Development
Cookbook</a>
by Juan Pablo Giménez and Marcos F. Romero - published by Packt
Publishing. The topic of the book is site development with the
opensource CMS Plone 3 - a python-based CMS available at
<a href="https://plone.org/">http://plone.org/</a></p>
<p>The book is a collection of recipes for how to do the various tasks
you'll need to do to get a site going using Plone 3 - typical cookbook
style. It's laid out in project-form: the authors stipulate a project
(the design of a digital newspaper website with some particular
requirements) and then go through the tasks for the project one by one
in the form of recipes. The style is the same as in the previous Packt
review I did: each chapter focuses on one particular concept, comprising
a number of recipes, with each recipe containing an introduction, How to
do it section, How it works, suggestions for further research/reading
plus a cross-reference with recipes.</p>
<h2>The book</h2>
<p>The book is done in black and white (that includes all illustrations)
and like the Typo3 book it suffers a bit from this, especially because
the printing is too rough. You'll notice this with the images in the
book - luckily there are only a few, otherwise it would have been more
annoying to look at.</p>
<p>Mentioning illustrations, one of the slightly confusing points in the
book is how some illustrations don't quite show what you want or need
from them. Most of the time they are located in the How to do it section
of a recipe and you will expect it to show how to do a specific step,
but you'll find it shows something less useful, like the page you need
to be working on (not in itself bad but you'll know that already from
the recipe anyway).</p>
<p>One personal gripe I have with the book is a recurring theme of mine:
the "download the code, we won't print it"-approach. I mentioned this in
<a href="https://plind.dk/2010/04/23/review-of-typo3-multimedia-cookbook/" title="Read my Typo3 Multimedia Cookbook review">the Typo3 book
review</a>
as well and it's still annoying to me. An example of why: I'm writing
part of this review in an airport and there are certain parts I can't
test because I cannot download the code (no, I'm not paying the absurd
prices of airport-wifi. It's a market and I choose to show the idiots
that their product is priced insanely high by not buying it).</p>
<p>Apart from that, the structure of the book makes it nice and easy to get
into: the recipes make it easy to learn and the themed structure means
reading you proceed quite naturally from topic to topic.</p>
<h2>The recipes</h2>
<p>You'll start at the very beginning with this book: installing python and
Plone. This actually works quite well given the aim of the book: to
build a site with certain characteristics and get it running. One recipe
that is quite handy is how to install python 2.4: Plone 3 doesn't run on
newer versions of python, so without this you'd be googling for a
reliable solution to the problem before being able to dig into the real
problem.</p>
<p>The recipes are typically ok explained. You obviously won't know
everything related to creating Plone 3 sites after using the book but
you'll have a good starting point and will be able to put a number of
things in place. It should be noted, though, that the book focuses on
creating Plone 3 packages (i.e. plugins) so you won't find recipes on
how to design a site in Plone 3.</p>
<p>However, some recipes skip too fast ahead - for instance, one of the
last recipes revolves around creating a production-site buildout so you
can easily push the final site live. In this recipe you'll find a lot of
interesting tools mentioned, such as Varnish - but there's little to no
info about these tools or how to best use them with Plone 3. Here, you
can likely argue that it would be outside the scope of the book (it
focuses on Plone 3 products, not other stuff) but that doesn't remove
the feeling that the book was either cut a bit short, published too
early or that the recipe is simply meant as a teaser. Whatever the
reason, it's annoying.</p>
<p>One aspect that I find particularly nice is the focus in recipes on
testing. A couple of recipes are fully devoted to testing and apart from
that most if not all recipes dealing with code also include code for
testing. That gives you the chance to start a good habit, as well as a
chance for catching errors or typos in the recipes: in case something
doesn't work quite perfectly, the tests provide further options for
debugging.</p>
<p>Another aspect I find positive is that the recipes introduce a lot of
tools and options: if you manage to read them all, you'll have quite a
few tools to work with when constructing a Plone 3 site. For instance,
the authors introduce you to Dexterity, a content type framework not yet
in stable version when the book was published - so, you'll find just one
recipe using it, showing how product development might look in the
future without spending too many resources on something that might be
radically different tomorrow. This is the sort of teaser I'm happy to
see, as opposed to the inclusion of various tools with half a line used
to explain each (read: the recipe including Varnish).</p>
<h2>Conclusion</h2>
<p>The book has a few flaws - it feels as though too little attention was
paid to the final product feel (illustrations, recipes feeling
shortened) - but overall it seems informative. As a Plone beginner
myself, this book gives me a good basis to work from. On the other hand,
I doubt that experienced Plone 3 developers will get that much out of
the book - however, because it's a cookbook, even developers with some
Plone 3 experience might gain something from it as a reference-book.</p>
<p>If you're interested in getting started with Plone 3 development, this
book is not a bad place to start. Personally, I'm not sure the Plone way
is my way: it seems to suffer from the typical problem of "let's make a
million tools to make Plone development easier" - if you need a tool to
make development in the CMS easier, then a) your CMS has problems of
it's own to start with and b) you had better make sure the tools created
to alleviate the problem really work well and don't have problematic or
annoying shortcomings, as you're putting another obstacle in the way of
the developer (not only do I have to learn Plone 3, now I also have to
learn how to use your tool. If I also have to learn how to fix the
problems created by your tool, I've not gained anything, I've just lost
time). Having said that, I'll also note that this cookbook has actually
given me the motivation to try out the CMS: something in the books
favor, I'm sure.</p>Updates and CSS32010-06-18T20:22:00+02:00admintag:plind.dk,2010-06-18:updates-and-css3.html<p>Once more I find myself spending lots of time not posting on the blog, a
shame really. Basically I have way too many things going on right now,
which naturally takes time away from writing about developing (or any
other rant area). While that's bad for this blog it's great for me
though.</p>
<p>I do feel like posting a quick thought, though. I'm currently working on
a nice project, fairly big - building a site from scratch for a specific
community. I'm also doing the design for it (rather, my fiancee and I
are doing the design) and as part of this I've been struggling with
Ubuntu. So far, for designing, I've been using Gimp. It's a great tool
but there are some areas in which it really lags far behind other tools
of the trade. To name just a couple: layers (no grouping capability) and
text (if you want a piece of text to have different formats, you have to
break it into several pieces each of which must be formatted separately
- as far as I can make out). So, while making a design is more or less
ok, tweaking it on the other hand is massive pain.<br />
I've specifically come up against this in the project I'm working on
now. I've got some content boxes, round corners with some drop shadows.
Changing the size of these boxes is the least fun I've tried for a long
time. With Gimp you can lock layers with respect to each other so that
you can move things around - but there's no way to easily stretch or
shrink a box; you have to manually modify every little thing inside it.<br />
Naturally that makes a developer look for solutions that allows for
working more clever. What I found makes me rather happy: first, in CSS3
there are possibilities for having rounded corners and drop shadows
using just CSS. This is not really news as such as CSS3 has been on it's
way for a while. Previously, it wouldn't even have made more than shrug
but when you add the stats from
<a href="https://www.w3counter.com/globalstats.php">http://www.w3counter.com/globalstats.php</a>
into the mix you get a nice combo: the major shares of the "good"
browsers support the features in CSS3 that I need. Of course, one should
still make sure things look good in IE, but as far as I can tell this is
fairly easily done using IE specific styling (filters as it happens) and
should work without problems on IE7+ (and likely IE6 too but given the
usage stats for that I don't even have to care any more). That means
tweaking mockups much faster than using apps like Gimp - just move an
element or two around a bit and tweak some CSS properties and done.</p>
<p>Am I a happy camper? Yes!</p>Second Plone 3 review2010-05-31T12:02:00+02:00admintag:plind.dk,2010-05-31:second-plone-3-review.html<p>Just wanted to post a quick update on the Plone 3 reviews I'll be doing:
have received the second book now, <a href="https://www.packtpub.com/plone-3-3-multimedia-website/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003311">Plone 3
Multimedia</a>
- mentioned in <a href="https://plind.dk/2010/05/18/another-plone-book-review/">Another Plone book
review</a> - and aim
for getting a review of this book up here soon as well. The book looks
interesting from a quick skim of the pages, and it's done in a different
style than both the Typo3 Multimedia book I reviewed and the other Plone
3 book I'm reviewing (both were done as cookbooks, this one's not). I
look forward to reading my way through it :)</p>Plone 3 review2010-05-24T15:05:00+02:00admintag:plind.dk,2010-05-24:plone-3-review.html<p>I've received my reviewers copy of the <a href="http://www.packtpub.com/plone-3-3-products-development-cookbook/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003285">Plone 3 Product Development
Cookbook</a>
now and hope to have a review ready in a week or two (yeah, I know,
shouldn't make guesses as to when it'll be done as I'll just disappoint
:P ).</p>
<p>On a related point, I was told by the publisher that they're kicking off
the publishing of the book (and the publishing of Plone 3 Multimedia,
the other Plone book from Packt that I'm reviewing) with a campaign
offer: if you buy one of the two within the first two weeks of the
publishing date, you'll join a lucky draw where you've got the chance to
win an iTunes voucher or Amazon Gift Card. Nice of them, I should think
- more media is always great.</p>Sorting algorithms: Heap sort2010-05-23T16:09:00+02:00admintag:plind.dk,2010-05-23:sorting-algorithms-heap-sort.html<p>It's been a while since I did the last post (<a href="http://plind.dk/2010/01/10/sorting-algorithms-merge-sort/">merge
sort</a>) in my
sorting algorithms in PHP series (new readers start with <a href="https://plind.dk/2009/11/06/sorting-algorithms-bubblesort/">Sorting
algorithms:
bubblesort</a>)
so I thought it about time that I add the next part. This time I'm going
for <a href="https://en.wikipedia.org/wiki/Heapsort">heap sort</a>. This algorithm
has some similarities with merge sort, mainly the recursive nature, the
divide and conquer idea: heap sort treats an array like a tree of
elements instead of just one big pool of elements. Other than that, like
some of the other sort algorithms presented it can be sorted in place,
leading to less memory use - however it is not a stable sort, as the
elements can quite easily change order.</p>
<p>In details, heap sort works by imposing a hierarchical tree structure on
the set of elements and that the structure satisfies the heap
requirement: that any given node be of equal to or less value than it's
parent. This is actually only a requirement for one type of heaps, the
max-heaps - the other type, min-heaps, work in the opposite way, namely
by having a parent node be always equal to or less than a child node.
The reason this is significant is that you will always know which
element is the largest: it's the root node (in a max-heap, still).
Hence, if we have a heap in proper order, we can then take the root node
out of the heap and put it at the end of the array to sort. After
bringing the heap in order again, we can repeat this, popping off the
next largest element and putting it at the second to last position in
the array.</p>
<p>Laying it out in steps, it looks like:</p>
<ol>
<li>heapify array</li>
<li>pop root element off and store at end of array</li>
<li>heapify remaining heap</li>
<li>repeat step 2-3 while constantly moving one step back when storing
popped element</li>
</ol>
<p>And that's it! Well, it obviously gets a bit harder once you get into
the details - and without those, no sorting :) As with merge sort, it's
the recursive function that's most important to the algorithm: the
heapify thing. This is where the time will be spent so this is where
it's most important that things are optimized and that the algorithm
works out.</p>
<p>So how does the heapify function work? It's rather simple: it compares a
node with it's two leaves and if one of the is (or both are) larger than
the root a swap is made - the largest leaf takes the place of the root
node and vice versa. Because of the hierarchical structure of the heap,
every node can have at most two leaves. If a swap is made, the heapify
function recursively calls itself with the index of the swapped out leaf
(the previous root, now a leaf) because this may now be a new root node
in another local heap which then has to be checked. If on the other hand
a swap is not made the heapify function simply returns: the heap is in
order.</p>
<p>In terms of sorting, this means that you first heapify the array once
and then of course once per element you pop off the array. As the
algorithm swaps out the root node (the one to be popped off the heap)
with the last leaf in the array the heapify function then has to be
called to get the heap back into shape. Because the last element of the
heap is smaller than any of it's ancestors, the heapify function will
recursively call itself a maximum of log~2~(n)-1 times. As the heap
grows smaller, the number of recursions drops but this obviously happens
on a log~2~ scale as well so this is not a major factor in the speed -
the biggest speed factor lies in a combination of the fact that sorting
a heap when a new root is introduced, given that the heap has previously
been heapified, only takes log~2~(n) moves and that the first
heapification of an array is quite fast too.</p>
<p>Enough talk, now code.</p>
<h2>The Code</h2>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">HeapSort</span> <span class="k">extends</span> <span class="nx">BaseSort</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">sortFunction</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$array</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">;</span>
<span class="nv">$heap_length</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$array</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">buildHeap</span><span class="p">(</span><span class="nv">$array</span><span class="p">,</span> <span class="nv">$heap_length</span><span class="p">);</span>
<span class="k">while</span> <span class="p">(</span><span class="nv">$heap_length</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$temp</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$heap_length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="nv">$heap_length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$temp</span><span class="p">;</span>
<span class="nv">$heap_length</span><span class="o">--</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">heapify</span><span class="p">(</span><span class="nv">$array</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">$heap_length</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">store</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">function</span> <span class="nf">buildHeap</span><span class="p">(</span><span class="o">&</span><span class="nv">$array</span><span class="p">,</span> <span class="nv">$heap_length</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="nb">floor</span><span class="p">(</span><span class="nv">$heap_length</span> <span class="o">/</span> <span class="mi">2</span><span class="p">);</span> <span class="nv">$i</span> <span class="o">></span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span><span class="o">--</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">heapify</span><span class="p">(</span><span class="nv">$array</span><span class="p">,</span> <span class="nv">$i</span><span class="p">,</span> <span class="nv">$heap_length</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">function</span> <span class="nf">heapify</span><span class="p">(</span><span class="o">&</span><span class="nv">$array</span><span class="p">,</span> <span class="nv">$index</span><span class="p">,</span> <span class="nv">$heap_length</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$largest</span> <span class="o">=</span> <span class="nv">$index</span><span class="p">;</span>
<span class="nv">$comp</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$largest</span><span class="o">-</span><span class="mi">1</span><span class="p">];</span>
<span class="nv">$r</span> <span class="o">=</span> <span class="nv">$index</span> <span class="o"><<</span> <span class="mi">1</span><span class="p">;</span>
<span class="nv">$l</span> <span class="o">=</span> <span class="nv">$r</span><span class="o">++</span><span class="p">;</span>
<span class="k">if</span> <span class="p">((</span><span class="nv">$l</span> <span class="o"><=</span> <span class="nv">$heap_length</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="nv">$array</span><span class="p">[</span><span class="nv">$l</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">></span> <span class="nv">$comp</span><span class="p">))</span>
<span class="p">{</span>
<span class="nv">$largest</span> <span class="o">=</span> <span class="nv">$l</span><span class="p">;</span>
<span class="nv">$comp</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$l</span><span class="o">-</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">((</span><span class="nv">$r</span> <span class="o"><=</span> <span class="nv">$heap_length</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="nv">$array</span><span class="p">[</span><span class="nv">$r</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">></span> <span class="nv">$comp</span><span class="p">))</span>
<span class="p">{</span>
<span class="nv">$largest</span> <span class="o">=</span> <span class="nv">$r</span><span class="p">;</span>
<span class="nv">$comp</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$r</span><span class="o">-</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$largest</span> <span class="o">!=</span> <span class="nv">$index</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$temp</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$index</span><span class="o">-</span><span class="mi">1</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="nv">$index</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$comp</span><span class="p">;</span>
<span class="nv">$array</span><span class="p">[</span><span class="nv">$largest</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$temp</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">heapify</span><span class="p">(</span><span class="nv">$array</span><span class="p">,</span> <span class="nv">$largest</span><span class="p">,</span> <span class="nv">$heap_length</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Quick walkthrough: first, the heap is built (or rather, the array is
treated like a heap that's out of order) after which the main loop is
entered. Here, with each iteration, the root node is popped of the array
(shifted, in PHP terms) and the heap is heapified again. The main action
takes part in the heapify() method: it's called with the heap, the index
of the node to heapify and the length of the heap. The method checks the
indexed node against it's leaves and if any of them are larger than it a
swap is made and the method is called recursively.</p>
<p>I've experimented a bit with optimizing the code and the version above
is the fastest I've got it to. The biggest payoffs came from passing the
array by reference instead of using a property of the class as well as
passing the array length into functions - I hadn't expected either to be
a speed improvement but going that way shaved off 5 (five) seconds of
the running time when sorting 100,000 elements. Which, I guess, leads me
to the results.</p>
<h2>Results</h2>
<div class="highlight"><pre><span></span>1000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.000878 seconds. Memory used: 64360 bytes
Running Heapsort sort. Algorithm took: 0.089941 seconds. Memory used: 64360 bytes
</pre></div>
<p>At 1,000 elements the algorithm it's running in about 1.25 times the
time of the merge sort implementation I made.</p>
<div class="highlight"><pre><span></span>10000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.012138 seconds. Memory used: 665804 bytes
Running Heapsort sort. Algorithm took: 1.208370 seconds. Memory used: 665800 bytes
</pre></div>
<p>At 10,000 elements it's running in 1.5 times the merge sort - pretty
good but not quite the same.</p>
<div class="highlight"><pre><span></span>100000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.222491 seconds. Memory used: 6524576 bytes
Running Heapsort sort. Algorithm took: 14.997653 seconds. Memory used: 6524560 bytes
</pre></div>
<p>With 100,000 elements the pattern is fairly clear in terms of how the
algorithm is doing - a bit less than a straight O(n log n). Final test
is 1,000,000 elements.</p>
<div class="highlight"><pre><span></span>1000000 elements to sort.
Sanity check. PHP native sort() algorithm took: 3.602368 seconds. Memory used: 64194572 bytes
Running Heapsort sort. Algorithm took: 188.843620 seconds. Memory used: 64194576 bytes
</pre></div>
<p>Increasing the number of elements to sort by an order of magnitude
increases the run time by 12.5 - which is pretty good (compare to the
internal PHP algorithm which increases by a factor 15).</p>Another Plone book review2010-05-18T07:28:00+02:00Peter Lindtag:plind.dk,2010-05-18:another-plone-book-review.html<p><a href="http://www.packtpub.com/plone-3-3-multimedia-website/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003311"><img alt="Cover of Plone 3
Multimedia" src="https://plind.dk/wp-content/uploads/2010/05/7665_MockupCover.jpg.png" title="7665_MockupCover.jpg" /></a>I've
been given the opportunity to review another Plone book - this time it's
<a href="https://www.packtpub.com/plone-3-3-multimedia-website/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003311"><em>Plone 3
Multimedia</em></a>
by <strong>Tom Gross</strong>. The book will be a nice followup to the <a href="https://www.packtpub.com/plone-3-3-products-development-cookbook/book?mid/040510r71kzn">Plone 3.3
Products Development
Cookbook</a>
<a href="https://plind.dk/2010/05/13/more-reviews-to-come/">I'll be reviewing
shortly</a> - my plan is
to use the cookbook as a foundation and then use the multimedia book on
top of that. That should hopefully provide a good basis for evaluating
both books and thus for reviewing them.</p>
<p>The teaser for the multimedia book states:</p>
<blockquote>
<p>From watermarked images, integrated Silverlight-applications over
geotagged content and rich podcasts to protected video-on-demand
solutions this book provides a rich repository of tools and techniques
to add full multimedia power to Plone. This step-by-step guide will
show you how to collaborate with many external web resources to build
a powerful interactive Plone site that perfectly meet your needs.</p>
</blockquote>
<p>If it's caught your interest already, check the publishers site for
details - links are above.</p>Typo3 site fail2010-05-15T13:45:00+02:00admintag:plind.dk,2010-05-15:typo3-site-fail.html<p>Have a look at the image below - a screenshot from a website I came
across. It's a quite clear, massive fail (unless you're fluent in
whatever font is used). This is something that can happen to any site,
if you're messing with I18N or L10N - something goes completely screwy.
However, it's also one of those things that - when using a professional
CMS - just should work without massive headaches or hidden traps. Typo3
wants be a professional CMS, so, following the logic, this shouldn't
happen (or the site author should have massive warning bells ringing).</p>
<p><a href="https://www.typo3experts.com"><img alt="Website used to promote TYPO3
Kochbuch" src="https://plind.dk/wp-content/uploads/2010/05/typo3site.jpg" title="typo3site" /></a></p>
<p>What went wrong here? Well, several things. First off, the site uses
Typo3 to generate images containing text - and uses that for links. In
itself a horrible idea that's very bad for SEO but is used by Typo3 so
users can get "that special font". It wouldn't be so bad though if it
wasn't for the fact the there's either a mismatch between encoding
somewhere in the system or the font needed can't be found or used (I'm
assuming that the install hasn't been hacked and someone switched the
font used to wingdings). If the system had just been outputting text and
setting the wrong encoding then at least users would be able to manually
switch. As is, there's just no way of figuring out what the text is
supposed to be. Your best bet is looking at the html source and trying
to find out by looking at the link titles - not exactly what you want
users to do.</p>
<p>Now, to add an extra bit of irony to this: the site name is Typo3
Experts and they're hyping their Typo3 cookbook. Would you buy it after
seeing this site?</p>More reviews to come2010-05-13T18:30:00+02:00Peter Lindtag:plind.dk,2010-05-13:more-reviews-to-come.html<p><a href="http://www.packtpub.com/plone-3-3-products-development-cookbook/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003285"><img alt="Cover of Plone 3.3 Products Development
Cookbook" src="https://plind.dk/wp-content/uploads/2010/05/6729_MockupCover.jpg.png" title="6729_MockupCover.jpg" /></a>Not
the books referred to <a href="https://plind.dk/2010/05/11/geek-birthdays/">in my last
post</a> although it could be
(once I've read them :) ). Nope, I'll be reviewing another book from
<a href="https://www.packtpub.com/">Packt Publishing</a> (my previous review was of
<a href="https://plind.dk/2010/04/23/review-of-typo3-multimedia-cookbook/">TYPO3 Multimedia
Cookbook</a>):
this one is <em><a href="https://www.packtpub.com/plone-3-3-products-development-cookbook/book?mid/040510r71kzn">Plone 3 Products Development
Cookbook</a></em>
by <strong>Juan Pablo Giménez</strong> and <strong>Marcos F. Romero</strong>. It looks very
interesting as far as I can tell: the strategy of the book is to create
a working news site step by step, with each step being a recipe. So you
can use bits from the book if you just happen to have a specific problem
but your site is doing fine otherwise, or you can take it as a guide on
how to go about creating sites with Plone.</p>
<p>With a bit of luck, I should have the book in two weeks and knowing my
schedule it'll be between three and four weeks after that before the
review is on the site here.</p>Geek birthdays2010-05-11T18:30:00+02:00Peter Lindtag:plind.dk,2010-05-11:geek-birthdays.html<p>As a kid I always liked the hard packages the most, for birthdays and
christmas. Those were the ones you could play with and immerse yourself
in right away. Fun fun fun! As opposed to clothes, which you can ...
wear. And that's about it. As I grew older, the softer presents got more
interesting, but there's enough of the child left me to appreciate a
gook set of hard, geeky birthday presents: books! Strictly speaking
they're not entirely hard (a bit wobbly, I suppose) but they're firmly
in the "hard" packages category.</p>
<p>So what am I on about? My recent birthday, I got <em>designing with web
standards</em> by <strong>Jeffrey Zeldman</strong>and <strong>Ethan Marcotte</strong>, <em>Mastering
Regular Expressions</em> by <strong>Jeffrey E.F. Friedl</strong>, and <em>Learning Python</em>
by <strong>Mark Lutz</strong>. I'm a happy little boy :D If I manage to, might write
a review or two as well :)</p>Awesome Command-fu2010-04-29T11:10:00+02:00Peter Lindtag:plind.dk,2010-04-29:awesome-command-fu.html<p>A good friend of mine mentioned that SSH supports escape sequences -
something I had never thought of. Or rather, something I had wished for
every time I'd been faced with a dead SSH connection. Turns out, you can
easily close any SSH connection (dead or alive) by issuing the following
sequence:</p>
<div class="highlight"><pre><span></span><CR>~.
</pre></div>
<p>The CR in there is a carriage return, i.e. pressing the enter key to
create a new line. One caveat though: the \~ is the default escape
character for SSH but it can be changed (or removed) so you need to make
sure you're using the proper escape character when issuing escape
sequences - otherwise, no fun/profit.</p>
<p>Closing an SSH connection is not the only thing you can do: you can also
suspend an SSH connection by doing</p>
<div class="highlight"><pre><span></span><CR>~^Z
</pre></div>
<p>where \^Z is \<ctrl>+z. But wait, there's more! You can also change the
SSH connection you're in to add or remove port forwards, by opening a
command line:</p>
<div class="highlight"><pre><span></span><CR>~C
</pre></div>
<p>And a personal favourite: if you're using several SSH connections in
serial, you can forward an escape sequence by adding another \~ after
the first. Closing connection #2 then becomes:</p>
<div class="highlight"><pre><span></span><CR>~~.
</pre></div>
<p>And in the same fashion, suspending the second connection becomes:</p>
<div class="highlight"><pre><span></span><CR>~~^Z
</pre></div>
<p>This, dear reader, is <strong>awesome!</strong> There's just no end to the fun :)</p>
<p>The moral: read your man pages (so much good stuff hidden away in
there)!</p>PLSEO2010-04-26T13:00:00+02:00admintag:plind.dk,2010-04-26:plseo.html<p>Aaaaaaaaaand ... another tool put up for the general public to enjoy.
This time it's PLSEO, and <a href="https://github.com/Fake51/PLSEO" title="Browse or download PLSEO">it's available at
GitHub</a> as
per usual. It's a collection of SEO tools written in PHP. Well, for now
it's a collection of one tool: some classes to query search engines to
get position of a site given a keyword. You can <a href="https://plind.dk/plseo/" title="Read the details of PLSE">read
more</a> on the PLSEO
page.</p>Review of TYPO3 Multimedia Cookbook2010-04-23T10:44:00+02:00admintag:plind.dk,2010-04-23:review-of-typo3-multimedia-cookbook.html<p>As mentioned a while ago in <a href="http://plind.dk/2010/02/05/typo3-book-review/" title="Read the Typo3 Book Review post">another blogpost of
mine</a>,
I received a copy of a Typo3 book for reviewing. Tons of time has passed
and I have now finally managed to do the review (yes, I know, it's a
month and half late ... or more). Anyway ... on to the reading.</p>
<h2>Introduction</h2>
<p>The book I'm reviewing here is <a href="https://www.packtpub.com/typo3-4-3-multimedia-cookbook/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003215" title="View the details of the book on the Packt site">TYPO3 4.3 Multimedia
Cookbook</a>
by Dan Osipov, published by Packt Publishing. The title gives you a good
idea as to the content but if you were in doubt, the sub title spells it
out: ”Over 50 great recipes for effectively managing multimedia content
to create an organized website in TYPO3”. The aim of the book is thus to
help you take care of the more or less typical problems connected with
handling multimedia in Typo3, by giving you step-by-step recipes for
putting solutions in place. How well the book achieves that is what I'll
be looking at here.</p>
<h2>The book</h2>
<p>Before I dive into the recipes, there are a two general points to make.
First, for a book about multimedia, it unfortunately comes across a bit
lacking: the images in the book (of which there are a few) are
consistently too dark. It might have been a tradeoff to make sure the
graphics stands out better, but it just doesn't look good and hence
gives the book a slightly unfinished or rushed feel.</p>
<p>Another thing to note is the approach to code sections the author has
taken. A book like this could easily fill up most of it's pages with
code, leading either to a very large book or very short descriptions.
The approach chosen here is another: most of the recipes that involve
code of some size direct you to download the appropriate code, after
which the most important parts are explained. Whether you see this as a
positive thing or not depends mainly on taste, I suppose – personally I
prefer being able to look at all the code that makes up a given recipe,
instead of having to move to another media to get an overview.</p>
<h2>The recipes</h2>
<p>The book is divided into eight categories: Getting Started, Managing
Digital Assets, Operating with Metadata in Media Files, Rendering
Images, Rendering Video and Audio, Connecting to External APIs, Creating
Services, and Automating Processes. As should be obvious given the list,
it's covering a lot of different situations, which is both positive and
negative. It's great because it gives the book a lot of diversity and
you're almost certain to get inspired by some of the recipes – too much
ground is covered for there not to be something interesting for
everyone.</p>
<p>However, among all the things the book covers you'll also find things
that seem irrelevant to the topic. For instance, the first chapter has a
total of eight recipes – only three of these actually deal with Typo3.
The rest are short recipes on how to setup a web server or an NFS share
– not exactly everyday topics when running your Typo3 site. The three
recipes that do deal with Typo3 focus on setting Typo3 up and creating a
template – again, not something you're likely to be looking for when
you're having multimedia problems. It seems as though the first chapter
aims to create a common ground, so the Typo3 newbie can join the party
as well – but that's not the premise of the book. The second chapter is
better but also has some chapters that suffer from this. The third
chapter is a mix: two recipes explain how to set metadata in image and
audio files using tools like Photoshop, while the rest explain how you
can extract the data in Typo3. After that, the rest of the book is on
topic.</p>
<p>That's about the negative I have to note about the book – the rest is
positive. The remaining recipes range from ”Good to know” or ”I can use
that” to ”Hey, that's pretty cool!”. The chapters on rendering images
and audio/videofall into the first category, while the last three
chapters (Connecting to External APIs, Creating Services and Automating
Processes) are in the latter. For instance, the recipes on using Amazons
S3 services are pretty nifty as are the recipes on creating services
(especially the detailed walkthrough of how to convert audio using a
service).</p>
<p>Overall, the recipes are well written up: they all follow the same
useful layout (preparation, step-by-step guide, explanation, further
points, references to other recipes) which helps you grasp the
information quicker, the language is easy to dig into, and the recipes
contain helpful screenshots of interfaces you'll be dealing with (one of
the things that might otherwise overwhelm you). As pointed out, most of
the recipes do not include all of the code needed for functionality but
they do explain the key points and they use enough space to make sure
you understand what's going on – which makes it a fair chance you'll be
able to use what you learn in other contexts.</p>
<h2>Conclusion</h2>
<p>I was happy to receive a copy of the Multimedia Cookbook to review:
after reading it, I think it's a good guide to multimedia issues and I'm
pretty sure I'll be using some of the recipes. As such, it's given me
some practical hints on how to do things. It's also given me a better
insight into some things Typo3 though, which is another bonus from
reading it. I would expect the payoff for experienced Typo3 developers
to be less on both accounts, but I still imagine it would be an ok
addition to the library, as the cookbook layout makes it ideal for
reading whenever you come across a specific multimedia related problem.</p>
<p>That said, it's not the best handbook I've even com across – the
problems mentioned (irrelevant recipes and minor layout problems)
coupled with the ”get the code online then return to the book”-approach
subtract somewhat from it, unfortunately. I still think it's a good
guide though and have no trouble recommending it. So if you're wondering
how to handle multimedia in Typo3 or just feel like expanding your Typo
knowledge, I'd say give it a read.</p>.htaccess voodoo2010-04-08T23:16:00+02:00Peter Lindtag:plind.dk,2010-04-08:htaccess-voodoo.html<p>Every once in a while I have to do some
<a href="https://en.wikipedia.org/wiki/.htaccess" title="Wikipedia on .htaccess">.htaccess</a>
rewriting and every time I end up deeply fascinated at the possibilities
that it offers. This time round the situation was as follows: for a
client we had done some advanced search functionality, which uses fairly
detailed URLs to store the search (the search is parsed into an abstract
syntax tree, then serialized to a URL). The problem now was that we had
rewritten the serialized syntax ... and somehow a famous search engine
had picked up on the URLs and was getting bad results from them. What to
do?</p>
<p>The solution was URL rewriting using .htaccess. However, it wasn't
straight forward URL rewriting - the serialized search was encoded in
the query string, and that was the only part that needed a rewrite. How
does the Apache Rewrite module handle this? Very well, it turns out.</p>
<h2>Solution - part 1</h2>
<p>The first thing to realise is that
<a href="https://httpd.apache.org/docs/1.3/mod/mod_rewrite.html#RewriteRule" title="RewriteRule specs">RewriteRules</a>
by themselves won't do any good - they only work on the base part of the
URL, ignoring the query string. This means you have to turn to the
second part of the rewrite: the
<a href="https://httpd.apache.org/docs/1.3/mod/mod_rewrite.html#RewriteCond" title="RewriteCond specs">RewriteCond</a>.
Now, this presented the first part of my eye-opening experience:
RewriteCond allows for using regexes. This means you can do the
following:</p>
<div class="highlight"><pre><span></span>RewriteCond %{QUERY_STRING} ^id=([0-9]+)
</pre></div>
<p>And you'll match on query strings that start with id = some number of
digits. You can use pretty much any extended regular expression you
desire ... which makes it very powerful!</p>
<h2>Solution - part 2</h2>
<p>As you can probably guess from the above code bit, you can also use
capturing groups in the RewriteCond regexes. Not only that, though: you
can reference these captured groups in a RewriteRule. It's done slightly
different from capture reference in RewriteRule regexes (these are done
using \$) in that a reference to a captured group from a RewriteCond
uses a % as prefix. Hence, you can do:</p>
<div class="highlight"><pre><span></span>RewriteCond %{QUERY_STRING} ^id=([0-9]+)
RewriteRule ^product.php /product/%1? [R=301,L]
</pre></div>
<p>And you'll be redirecting product.php?id=123 to product/123 using a 301.
Notice the ? at the end of the rewritten URL - it's there to make sure
ModRewrite doesn't append the original query string.</p>
<p>At this point, my woes were almost over - there was just one obstacle
left.</p>
<h2>Solution - part 3</h2>
<p>When the Rewrite module does redirects, it normally also escapes
characters in the URL, that could otherwise turn out problematic. One
such character is %. However, this escaping is itself very problematic
upon redirects, because any url_encode()d URL will contain lots of %
characters followed by a character code. When ModRewrite is done with
the URL, it'll have substituted all %2F with %252F, for instance ... not
what you want.</p>
<p>There's a very simple solution to this, though: you can set a flag to
stop the Rewrite module from doing any escaping. What you do is:</p>
<div class="highlight"><pre><span></span>RewriteCond %{QUERY_STRING} ^id=([0-9]+)
RewriteRule ^product.php /product/%1? [R=301,L,NE]
</pre></div>
<p>This stops ModRewrite from escaping the URL, leaving you with whatever
was in the original.</p>
<p>Using the above bits and pieces you can rewrite a URL like</p>
<div class="highlight"><pre><span></span>/search?blah=hum%20dinger%20and%what%20not%3Aliteral
</pre></div>
<p>to</p>
<div class="highlight"><pre><span></span>/search?q=hum%20dinger%20and%what%20not%3Aliteral&rewritten=true
</pre></div>
<p>It's voodoo for sure, but damned cool.</p>Geonames library2010-03-22T23:47:00+01:00Peter Lindtag:plind.dk,2010-03-22:geonames-library-2.html<p>A couple of days I was doing some work on
<a href="https://www.bewelcome.org/">BeWelcome</a>, updating some code to fetch geo
data from <a href="https://www.geonames.org">Geonames.org</a>. Our old code had been
using a mixture of a spaf maps class and php functions and trying to get
it to work properly seemed to demand quite a bit of tinkering - plus,
there was the chance I might break something. I then had a look at the
Geonames site and there are quite a few different libraries for querying
Geonames - but nothing in plain PHP. However, looking at the
documentation of their API it didn't seem like it would be very hard to
knock something up - so that's what I did.</p>
<p>After about 2 hours of coding, I had some very basic classes that
provide the most useful functionality of Geonames (useful for BeWelcome
anyway): doing a fulltext wildcard search, fetching details for one
specific location, and fetching hierarchies of locations. The library
provides some static functions for generating location objects, and
these location objects can then in turn be worked with using some of the
same methods that the library provides (such as getting child locations
or parent locations).</p>
<p>The usage is currently extremely simple - some examples:</p>
<div class="highlight"><pre><span></span><span class="o">//</span> <span class="nt">searching</span> <span class="nt">for</span> <span class="nt">a</span> <span class="nt">location</span>
<span class="o">$</span><span class="nt">results</span> <span class="o">=</span> <span class="nt">GeoNamesService</span><span class="o">:</span><span class="nd">:search</span><span class="o">($</span><span class="nt">searchterm</span><span class="o">,</span> <span class="o">$</span><span class="nt">rowcount</span> <span class="c">/* optional, defaults to 100 */</span><span class="o">);</span>
<span class="o">//</span> <span class="nt">fetching</span> <span class="nt">parent</span> <span class="nt">hierarchy</span> <span class="nt">for</span> <span class="nt">a</span> <span class="nt">location</span>
<span class="o">$</span><span class="nt">hierarchy</span> <span class="o">=</span> <span class="nt">GeoNamesService</span><span class="o">:</span><span class="nd">:hierarchy</span><span class="o">($</span><span class="nt">id</span><span class="o">);</span>
<span class="o">//</span> <span class="nt">getting</span> <span class="nt">the</span> <span class="nt">parent</span> <span class="nt">of</span> <span class="nt">an</span> <span class="nt">existing</span> <span class="nt">GeoNamesObject</span>
<span class="o">$</span><span class="nt">parent</span> <span class="o">=</span> <span class="o">$</span><span class="nt">geo_object-</span><span class="o">></span><span class="nt">getParent</span><span class="o">();</span>
</pre></div>
<p>As you can probably guess, the main reasoning was taking out the
boilerplate code and removing the stuff we just didn't use. As a
consequence, this library doesn't currently offer that much extra
functionality. This will be built in later though, so you're able to use
some of the options when searching, for instance. Also, a number of the
other ways of interacting with Geonames are worth looking at, like
finding a location by postcode.</p>
<p>After I had spent a bit of time on the project and got it to a working
basic (including some unit-tests), I figured I'd breathe some life into
it and so I put it on GitHub - so anyone can take it and use it if they
have a need for it. It's available here: <a href="https://github.com/Fake51/GeoNames-Library">PHP Geonames
Library</a> and the license is a
GPL v.3 so you can just grab and use in any OS project as you see fit.</p>PHP tips for optimizing2010-03-17T19:03:00+01:00admintag:plind.dk,2010-03-17:php-tips-for-optimizing.html<p><a href="http://www.google.com/">Google</a> published a set of tips on <a href="http://code.google.com/speed/articles/optimizing-php.html">how to
increase the performance of
PHP</a> a while
ago under the heading "Let's make the web faster". I recently came
across the article again, and it's been changed a lot since last summer
when it was published. At that time, a number of the tips were
<a href="https://groups.google.com/group/make-the-web-faster/browse_thread/thread/ddfbe82dd80408cc">massively
criticized</a>
- a lot of
<a href="https://php100.wordpress.com/2009/06/26/php-performance-google/">blog</a>
<a href="https://www.sitepoint.com/blogs/2009/06/26/a-note-on-googles-so-called-best-practises/">posts</a>
were written, comments made, etc. Seemingly, this has led to the article
being changed, which is good, as some of the info on it previously was
rather misleading. Most of what is left is alright, although one or two
of those that have been allowed to stay are still dubious:</p>
<blockquote>
<h3>Don't copy variables for no reason</h3>
<p>Sometimes PHP novices attempt to make their code "cleaner" by copying
predefined variables to variables with shorter names before working
with them. What this actually results in is doubled memory consumption
(when the variable is altered), and therefore, slow scripts. In the
following example, if a user had inserted 512KB worth of characters
into a textarea field. This implementation would result in nearly 1MB
of memory being used.</p>
</blockquote>
<p>Although the above statement is correct (due to the 'when the variable
is altered' which I'm presuming has been added later) the example that
succeeds it does not in any way double the memory used. The example
copies a variable and then echoes the copy - which doesn't amount to a
change and hence won't actually create a copy of the variable. It would
be nice to see the whole text revised instead of a bit of extra info
inserted in a parenthesis ... oh well.</p>
<p>However, that's not the main point that struck me as odd. No, what got
me paying attention is this bit:</p>
<blockquote>
<h3>Avoid writing naive setters and getters</h3>
<p>When writing classes in PHP, you can save time and speed up your
scripts by working with object properties directly, rather than
writing naive setters and getters. In the following example, the<code>dog</code>
class uses the <code>setName()</code> and <code>getName()</code> methods for accessing
the <code>name</code> property.</p>
</blockquote>
<p>Then follows an example with a class with <strong>public(!)</strong> properties and
get/set methods. And of course the tip of not using getters and setters
like that. However, is that really the tip that one should give
programmers? What happened to encapsulation? Not to mention: who would
ever write getter and setter functions for public properties?</p>
<p>In the theoretical world that people are being taught, you're told that
keeping properties offlimits to the outside world is a good thing: it
limits the knowledge the world can possibly have of your object and
hence minimizes the chance for tight coupling due to someone figuring
out that \$obj->property is possible and easier than
\$obj->getProperty(). Why are people being told this? Because when
later down the line you realise that you have to change obj::\$property
to obj::\$some_other_property, you'll wish you never had the bright
idea of making use of public properties. Specifically, going through
code in many different places changing \$obj->property to
\$obj->some_other_property really makes you annoyed ... there are
much better ways to spend your time.</p>
<p>Of course, there's an alternative to fixing references to
obj::\$property. It's called __get(), it's a magic method, and it's
<strong>much</strong>slower than setter/getter functions. In other words, using the
hint from Google - which is supposed to speed up your apps - you're
implicitly going down the road of tight coupling. Which means that:</p>
<blockquote>
<p>Setting and calling the <code>name</code> property directly can run <a href="https://pastie.org/638732">up to 100%
faster</a>, as well as cutting down on
development time.</p>
</blockquote>
<p>is a typical short-term view of things. Sure, development time is
shorter if you don't have to write the ten extra lines of code for
getters and setters, but development time is massively extended when you
have to refactor your code to not use public properties any more. Not to
mention that if you the mitigate that with __get() your code suddenly
won't run up to 100% faster but more likely 100% slower (the code
dealing with these properties, that is).</p>
<p>What should you do? You should design your objects properly, is what you
should do. If some properties are core to the object and don't need a
check when being set, then obviously you can go without getters and
setters. However, if you're looking at an object that might change in
the future or if you're dealing with properties that should be validated
before being set (<a href="https://leepoint.net/notes-java/principles_and_practices/40fail-early-fail-often.html">failing
early</a>
is often better than failing late) or if you just want to minimize the
signature of the object, then use setters and getters.</p>Back from OSD2010-03-10T07:29:00+01:00Peter Lindtag:plind.dk,2010-03-10:back-from-osd.html<p>The <a href="http://www.opensourcedays.org/2010/">OpenSourceDays</a> conference is
through now (has been since Sunday, actually, but work has been keeping
me very busy) and a quick reflection tells me that it was great to be
there. Some of the highlights:</p>
<ul>
<li>A good presentation from <a href="https://www.signaldigital.com/">Signal
Digital</a> about
<a href="https://www.signaldigital.com/referencer/dingting-et-biblioteksprojekt/">ding.TING</a>:
a new open source frontend for libraries in Denmark, using Drupal</li>
<li>Part of a presentation of Groovy on Grails - it definitely piqued my
interest (I only got part because the presenter was taken ill
half-way through)</li>
<li>Meeting Typo3 people: Christian Jul Jensen from <a href="https://www.mocsystems.com/">MOC
Systems</a>, Stig Kjeldsen from
<a href="https://www.opengate.dk/">OpenGate</a>, and Søren Malling from
<a href="https://www.dkm.dk/">DKM</a></li>
<li>Helping out a bit in transforming the site of the Danish Typo3 user
group. It currently looks like <a href="https://www.t3ug.dk/">an ad for washing
powder</a> and that's going to end.</li>
<li>Meeting a guy with an idea for a site project and agreeing that we
should make this happen sometime soon.</li>
</ul>
<p>So, overall, it was very enjoyable and a bit of an eye-opener (it was my
first IT conference). There's a big chance I'll be going again next
year, that's for sure - the helping out bit was actually rather fun,
especially the second part.</p>
<p>However, this means that <a href="https://plind.dk/2010/02/05/typo3-book-review/">the Typo3 Multimedia Cookbook review I've
mentioned</a> has been moved
back about a week or so - it needs a bit of time for reading and
fairness.</p>Ubuntu and swap partitions2010-03-04T10:30:00+01:00admintag:plind.dk,2010-03-04:ubuntu-and-swap-partitions.html<p>This is a note to self and any others that may find themselves in a
similar situation. Should you happen to one day start your computer,
wait for your linux distro to boot up and then see the following message</p>
<div class="highlight"><pre><span></span><span class="n">swap</span><span class="o">:</span> <span class="n">waiting</span> <span class="k">for</span> <span class="n">UUID</span><span class="o">=</span><span class="n">xxxxxxxx</span><span class="o">-</span><span class="n">xxxx</span><span class="o">-</span><span class="n">xxxx</span>
</pre></div>
<p>without anything else happening, then don't panic! At least not yet.
Before you start pulling hair out of your head, try the following:</p>
<ul>
<li>boot into recovery mode, using grub, a live-cd or the excellent
<a href="https://www.sysresccd.org/Main_Page">system rescue cd</a></li>
<li>
<p>check which UUIDs your disks <strong>actually</strong> use (and by now, you
should have an idea of where this is going)</p>
<ul>
<li>easiest is: ls /dev/disk/by-uuid/ - however, if you have many
disks then the output will be unwieldy</li>
<li>easy as well: sudo blkid - this will list all disks with extra
info</li>
</ul>
</li>
<li>
<p>mount your linux boot partition, then check the fstab file on it</p>
<ul>
<li>if you mounted it on /mnt/disk then do vim /mnt/disk/etc/fstab
or nano /mnt/disk/etc/fstab</li>
</ul>
</li>
<li>
<p>check the partition that's marked as swap - compare the UUID it's
loading by against the list obtained above</p>
</li>
<li>if the UUID matches, go ahead, panic. If the UUID in the fstab file
<strong>doesn't match</strong> then change the fstab file (either to load the
partition by /dev/sda-something or the proper UUID) and then try
rebooting.</li>
</ul>
<p>As I didn't have access to the above list when this happened, I panicked
(after the third or fourth reboot with the same result). Luckily I had
access to another computer and could google for solutions - and came
across this in a forum which I figured needed to be spread some more.</p>
<p>Why did the UUID of the swap partition suddenly change? I have no idea.
Perhaps it wasn't even the UUID changing, it was something else in the
system that suddenly started caring about the mismatch (that's rather
farfetched, but so is the whole situation). My best bet is that it was
down to a bootup to Windows and some work there - perhaps trusty old
WinXP decided to play nasty and change stuff at random. No matter what,
though ... the relief when I finally got the thing working was massive
(among other things, I have some webdev work to hand in soon).</p>Open Source Days2010-03-03T15:05:00+01:00Peter Lindtag:plind.dk,2010-03-03:open-source-days.html<p>One thing I've completely forgotten to mention here is the fact that
I'll be going to the <a href="https://www.opensourcedays.org/2010/" title="Read more on the OSD conference">Open Source
Days</a>
conference in Copenhagen the 5th and 6th. The schedule looks nice with
<a href="https://www.opensourcedays.org/2010/node/266" title="Check the OSD schedule for Friday">Friday</a>
probably being more along the line of my immediate interests - although
the <strong>Development aid & eGov</strong>activities on <a href="https://www.opensourcedays.org/2010/node/267" title="Check the OSD schedule for Saturday">Saturdays
schedule</a>
also look interesting as do the various tutorials.</p>
<p>I'll be missing out on some stuff, though, as I'll be working at the
conference: my ticket is paid for by two stints in the wardrobe/handing
out T-shirts. It's not going to take away too much, though, especially
as part of my goal is to meet some other people at the conference, do a
bit of networking if possible ("<em>here's you jacket, sir, and my card</em>").</p>Criminal design2010-03-01T15:25:00+01:00Peter Lindtag:plind.dk,2010-03-01:criminal-design.html<p><a href="http://www.flickr.com/photos/fake51/4394202143/"><img alt="" src="http://farm5.static.flickr.com/4017/4394202143_f273b7d760_m.jpg" title="Stealing my money" /></a>At
the end of my recent holiday stay at Gran Canaria, my fiancee and I came
across a machine that was designed so poorly, it bordered on the
criminal. The machine in question was a small money-changer, placed
right next to vending machines (both of which are operated by the
company AMFM). The combination of vending machine plus money-changer is
a very nice combo for tourists (and pretty much anyone, really)
especially in the environment we found it: the airport on Gran Canaria,
after going through security. Imagine being in the "secured zone", with
a ten euro note and no small change, needing a bottle of water - a
money-changing machine suddenly becomes your best friend (sorry Fido).</p>
<p>How does one turn this scenario from great to awful? One way, of course,
is by having an "out of order" sign on the money-changing machine.
However, while this would make you somewhat bitter, it's what can happen
- not enough to make you really angry. No, what's needed for that is
that the machine steals your money.</p>
<p>If you look at the picture on the right, you'll see a machine that does
exactly that. It's designed in a way that makes it steal money every now
and then. It's not that it consistently short-changes you - that would
be too much and would probably get them in the spotlight of authorities.
No, what the machine does is completely lack proper feedback mechanisms.
It seems that the only bit of feedback incorporated into the machine is
"I've run out of money to hand out to the customer". In other words, the
machine will hand out as much money as it can, up till the amount you
put into it.</p>
<p>How does this amount to stealing? In case you hand it €10 and it only
has €4, that's what you get: €4. And a nice blinking message to tell you
that it owes you €6. Which you're not going to get. In other words, the
machine is telling you "Sucks to be you, doesn't it?".</p>
<p>This in itself is of course so poor design that people should be
spanked. The fact that this machine is located in the secured area of an
airport just aggravates things: nobody that I talked to would take any
responsibility for the machine not working. Instead, the pointed me to
the airport authority ... the office of which was located outside the
<strong>secured</strong> area. In other words, you need to go through security twice,
to be able to contact them about the problem. And your plane leaves
when, exactly?</p>
<h2>Solution</h2>
<p>What should have been done? What would a proper situation look like?
First off, a machine like this should never be allowed to accept money
if it cannot hand out the proper change. That amounts to a proper
feedback mechanism: when change is inserted into the machine, it should
count it, and then it should check available funds against its store,
before accepting any notes. Secondly, there should be no refusing of
responsibility in airports. As is, people are being treated as
second-rate citizens in airports. This is just not the way one should do
business: treat your customers properly, listen to them.</p>Router scripting2010-02-27T12:59:00+01:00admintag:plind.dk,2010-02-27:router-scripting.html<p>Another thing that's been bugging me for a while on the tech side of
things at home is my router. It's a Zyxel 2602 and while the interface
is shiny and the setup is fairly easy, the damn thing has a tendency to
atrophy and die after some days of uptime ... if there's been quite a
bit of traffic going through it. In <a href="https://plind.dk/2010/02/26/back-from-holidays/">the spirit of new
energy</a> I wanted to do
something about it.</p>
<p>Now, although I'm clearly a geek and like my gadgets, I'm not loaded
with money, so buying a new shiny toy is not an option. Apart from that,
I have a hard time accepting that it shouldn't be possible to make the
Zyxel do the job properly. Well, maybe not properly - I have no
intension of messing with the firmware (apart from updates whenever
one's available/needed).</p>
<p>No, what I had in mind was scripting the router to reboot regularly. As
I've just moved my company site off the server at home there's no
problem if the router goes down for about a minute a day, in the middle
of the night. So the question became one of: how to script it?</p>
<h2>Possible Solution 1</h2>
<p>I've been using <a href="https://seleniumhq.org/">Selenium</a> for various projects,
testing frontends and general functionality. Selenium allows you to
script actions and it's basically ideal for a task like this: log into
the webclient, find the right tab, click the right button, done.</p>
<p>Only problem is that installing this to run headless on my server every
night means installing SeleniumRC and various other things ... in short,
much too much work for just scripting a router reboot. If I already had
Selenium installed there, then no problem - but unfortunately I don't.</p>
<h2>Possible Solution 2</h2>
<p>Instead of installing Selenium, one could also give <strong>wget</strong> a try -
installing is a simple</p>
<div class="highlight"><pre><span></span>sudo apt-get install wget
</pre></div>
<p>if the program isn't already installed, that is. After that, toying with
various command line options got me logged into the routers webclient
and requesting the reboot ... but no dice. For anyone interested, the
options you'll need are</p>
<ul>
<li>--save-cookies file / --load-cookies file</li>
<li>--keep-session-cookies</li>
<li>--post-data='blah=blah&moreblah=moreblah'</li>
</ul>
<p>It didn't particularly look as if Zyxel actually cares about the
cookies, but you never know if they'll come in handy later.</p>
<h2>Possible Solution 3</h2>
<p>At this point I started looking for alternative ideas - and that's when
I remembered that Zyxel routers provide quite a few different protocols
for access (like most other routers). So I checked out the telnet
interface and quickly found myself wondering exactly what was going on
... the only help provided is lists of commands. No description, no
anything. In my battle to find out how to reset things, this led me
among other things to a factory reset of settings ... fairly annoying
(don't issue <strong>sys default</strong> unless you've got settings backed up!).
However, googling for info on Zyxel routers finally led me to a pdf with
some goodies, although for a different model. It was worth a shot
though, as the command looked as if it might work - and indeed, the
Zyxel 2602 will happily respond to a <strong>sys reboot</strong> issued over telnet.
That meant the task was down to scripting telnet - which turned out
quite easy. Here's the oneliner I ended up with</p>
<div class="highlight"><pre><span></span>(cat /path/to/passwordfile; sleep 1; echo sys reboot; sleep 1) | telnet 192.168.1.1
</pre></div>
<p>The first bit echoes the details needed for the router - after the pipe
there's the actual connection. The sleep commands are there to allow the
router to echo back a response before reacting on it - without them you
wouldn't do anything. To avoid having the password stored in the shell
history I use a file for storing it in ... not that much more secure,
but at least you don't get surprised by where it moves to.</p>
<h2>Done</h2>
<p>A little bit of researching, some vim'ing and there: a script to restart
my router every night at 5. Only thing left is scheduling the cron job
:)</p>Back from holidays2010-02-26T16:41:00+01:00Peter Lindtag:plind.dk,2010-02-26:back-from-holidays.html<p>After great holidays on the Canarian Islands I'm now back in front of
the trusty computer. The trip was super, resulting in some much needed
extra energy and motivation for ... well, lots of things, really :) The
first bigger thing I've done after coming back is moving
<a href="https://plphp.dk/">plphp.dk</a> from a server I have at home to my VPS. The
reason is rather simple, really: the connection here is simply not
stable enough for me to have my company site running over it (it's a
combination of crappy router plus wrong linux install, I think).</p>
<p>Before moving stuff I thought it might take a bit of effort getting
things working without problems but it turned out I had no reason to
worry. A combination of git and gitosis made the basic transfer easy and
the rest was handled with a database dump + import and a new virtualhost
for Apache. Replicating the install looked like this:</p>
<h2>Step 1 - setup local repo</h2>
<p>Make sure the site you're moving is in a git working repo. Skip this if
you're in a working git repo, otherwise just do something like</p>
<div class="highlight"><pre><span></span>git init
git add --all
git commit -am "setting up git repo for myproject"
</pre></div>
<p>Your repo is now ready to move - need a place to move it to, though.</p>
<h2>Step 2 - add group to gitosis</h2>
<p>If you've not already setup gitosis on your server (local or remote)
<a href="https://scie.nti.st/2007/11/14/hosting-git-repositories-the-easy-and-secure-way" title="Hosting Git repositories, The Easy (and Secure) Way">have a read on the scie.nti.st
blog</a>.
When gitosis is running, you need to modify your gitosis.conf to add a
new group for the site you want to move (unless you've got one group for
everything, which I wouldn't recommend though). In your gitosis.conf,
add</p>
<div class="highlight"><pre><span></span><span class="k">[group mygroup]</span>
<span class="na">writable</span> <span class="o">=</span> <span class="s">myprojectname</span>
<span class="na">members</span> <span class="o">=</span> <span class="s">user1 user2 user3@host</span>
</pre></div>
<p>Save and move on.</p>
<h2>Step 3 - add remote repo to your local working directory</h2>
<p>If like me your gitosis daemon is running on a remote server, you need
to do something like</p>
<div class="highlight"><pre><span></span>git remote add remote_repo_name ssh://git@host/myproject.git
</pre></div>
<p>If gitosis is running locally, you can do away with the ssh protocol bit</p>
<h2>Step 4 - push</h2>
<p>Time to do the actual copy. Do</p>
<div class="highlight"><pre><span></span>git push remote_repo_name master
</pre></div>
<p>Your repo is now copied to gitosis.</p>
<h2>Step 5 - clone</h2>
<p>Check out a working copy by cloning the site to where you need it - i.e.
do</p>
<div class="highlight"><pre><span></span>cd /var/www
git clone /path/to/gitosis/repos mysite
</pre></div>
<p>If your new site is on the same server as the gitosis server, that is.
Otherwise, use the ssh protocol for the path.</p>
<h2>Step 6 - dump database</h2>
<p>PL PHP is running on MySQL - hence, dumping the database and copying it
across to the new site can be done by</p>
<div class="highlight"><pre><span></span>mysqldump -u user -p database > dump.sql && scp dump.sql user@host:~/
</pre></div>
<h2>Step 7 - setup proper user</h2>
<p>Setup a user with access to the database using the same details as on
the current site - a create 'user'@'localhost' will probably suffice.</p>
<h2>Step 8 - import database</h2>
<p>Create the database, then import using your sql</p>
<div class="highlight"><pre><span></span>mysql> create database mydb default charset utf8;
</pre></div>
<p>and</p>
<div class="highlight"><pre><span></span>bash:~/$ mysql -u user -p -D mydb < dump.sql
</pre></div>
<p>And done</p>
<h2>Step 9 - create new virtualhost</h2>
<p>Get Apache to serve up the site by creating a new virtualhost file. Then
check your Apache config with apache2ctl configtest - if it's working
fine, then restart apache.</p>
<h2>Step 10 - change DNS records</h2>
<p>Last thing is to switch DNS records so your domain points to the new IP.</p>
<p>That's it - 10 steps and PL PHP was migrated to a different server with
the added benefit of having a repo copy added to my gitosis daemon. If
you skip the gitosis bit then you can truncate step 1-5 - just clone the
repo straight away.</p>
<p>On the other hand, if your site is bigger than PL PHP, you probably want
to add some extra steps, such as having your current site proxy the new
site, so that no traffic will hit the current site (and thus the current
database). An alternative to this would be to setup the new site to use
the current database and only migrate the database when you're sure DNS
records have been changed (i.e. when you no longer see hits on the old
IP).</p>
<p>With this out of my mind, I can focus on other interesting stuff, such
as <a href="https://plind.dk/2010/02/05/typo3-book-review/">the Typo3 book review I'll be
doing</a> (expect that in a
little over a week) and an interesting project focusing on the craft
community which I'm doing with my fiancee :)</p>Typo3 book review2010-02-05T10:12:00+01:00admintag:plind.dk,2010-02-05:typo3-book-review.html<p><a href="http://plind.dk/wp-content/uploads/2010/02/Typo-4.3.png"><img alt="Typo3 4.3 Multimedia
Cookbook" src="https://plind.dk/wp-content/uploads/2010/02/Typo-4.3.png" title="Typo 4.3" /></a>I've
been given the chance to review the new <a href="https://www.packtpub.com/typo3-4-3-multimedia-cookbook/book/mid/040510r71kzn?utm_source=plind.dk&utm_medium=affiliate&utm_content=blog&utm_campaign=mdb_003215" title="Typo3 Multimedia Cookbook at the Packt store">Typo3 Multimedia
Cookbook</a>
that Packt is publishing. Right now it should be in the mail on it's way
to me and hopefully will arrive in a week or so. After that, I'll be
enjoying some serious tech reading on Typo3 :) I found the <a href="https://typo3book.packtpub.com">Typo3:
Enterprise Content Management</a> book from
Packt quite useful, so I'm hoping the Multimedia Cookbook will be even
better - we'll see. The description of the book certainly does inspire a
certain appetite for reading:</p>
<blockquote>
<p>The book gives you a step-by-step process for organizing an effective
multimedia system. It also gives solutions to commonly encountered
problems, and offers a variety of tools for dealing with multimedia
content.</p>
</blockquote>
<p>There'll be a minor delay before the review goes online, though, as I'm
off on holidays next week for two weeks - so I expect I'll be done with
the review start March or so. Come back for more reading then :)</p>User management fail 22010-01-18T09:44:00+01:00admintag:plind.dk,2010-01-18:user-management-fail-2.html<p>Hot on the heels of yesterday's <a href="http://plind.dk/2010/01/17/user-management-fail/" title="Read my User management fail post">User management
fail</a>
post comes User management fail 2! More shocking discoveries! Blood!
Gore!</p>
<p>Well, perhaps not, but still a fairly massive security fail.</p>
<h2>Background</h2>
<p>I'm interviewing for a project doing Zimlets for Zimbra and as part of
the preparation I'm looking at existing Zimlets to see how they're done
(the Zimlets are located at
<a href="https://gallery.zimbra.com/">gallery.zimbra.com</a> should you be
interested in having a look). One of the Zimlets I looked at seemed to
have code useful to the project and I wanted to download it - but found
no download button. Instead, I spied a login/register combo and figured
I had to register first, which I then did. Back on the original page, I
then tried to login with my new info.</p>
<h2>Fail</h2>
<p>When trying to log in, I got an error message, something about lacking
permissions for a given area. Not a huge fail, but not really smart
either (I registered using the button right next to the login button
that doesn't let me in - if I cannot register for the area the login
button controls, then DON'T DISPLAY a register button there). No, the
real fail lies with the error output - have a look at the image below:</p>
<p><a href="https://plind.dk/wp-content/uploads/2010/01/Screenshot.png"><img alt="Displays a problematic error message from Zimbra's login
form" src="https://plind.dk/wp-content/uploads/2010/01/Screenshot-300x158.png" title="Zimbra login fail" /></a></p>
<p>It's always good to have descriptive error outputs, and if you're
debugging stuff, then extra output is fantastic. But even though I have
seemingly managed to get the 'eleet' ID, it's less than stellar
performance that they choose to tell me. And the fact that they're
outputting password hash is downright stupid. Though, of course, the
password hash itself wouldn't get you far, because at Zimbra, passwords
are also protected by salts, making a bruteforce attack very hard ...
unless you know the salt. Which Zimbra happily displays right alongside
the password hash on the login fail screen.</p>
<p>There is a factor mitigating this: the output is only displayed for
logins that are successful, i.e. you actually have to know the proper
user/pass combination to get it, so you cannot just start leeching
password hashes from the site. The good news only goes so far, though:
any man-in-the-middle has free access to all the data, as the login
happens over http and not https.</p>
<p>Note: I have contacted Zimbra about this.</p>User management fail2010-01-17T16:55:00+01:00admintag:plind.dk,2010-01-17:user-management-fail.html<p>I recently subscribed gocomics/ucomics - wanting to get
<a href="https://www.gocomics.com/doonesbury">Doonesbury</a>, <a href="http://www.gocomics.com/nonsequitur">Non
Sequitur</a>, etc by email delivery (I
subscribe to the feeds but forget to read them). And I did finally get
en email with Doonesbury ... but as I didn't get all the strips I wanted
it was a failure. Not a failure worthy of a blog post, though.</p>
<p>However, trying to get out of the gocomics system has proven hard. The
emails that gocomics send out do contain links both for unsubscribing
and logging in, however:</p>
<ul>
<li>trying to unsubscribe yields: no success, something when wrong</li>
<li>trying to login yields: username or password wrong</li>
<li>trying to get a new password sent results in: email address unknown</li>
</ul>
<p>Now, the fun part comes when you then look at the unsubscribe link in
the email sent to you. Normally, for unsubscribe emails, you either
expect username/email (insecure, but would work) or a temporary token
(the norm). Here's the link from gocomics:</p>
<div class="highlight"><pre><span></span>https://www.gocomics.com/email_manager?user_code=abcdefghijklmnopqrstuvwxyz123456
</pre></div>
<p>Yup, that's right. Gocomics keep sending emails to people without any
way for them to unsubscribe. Something tells me this policy runs danger
close of being spam ... and that's exactly what I've told Gmail that
these emails are.</p>
<p>Sending out emails with broken unsubscribe links: fail!</p>An end to censorship2010-01-13T10:58:00+01:00Peter Lindtag:plind.dk,2010-01-13:an-end-to-censorship.html<p><a href="http://googleblog.blogspot.com/2010/01/new-approach-to-china.html">Google announced
yesterday</a>
that they will stop censoring search results on google.cn. There can
only be one response to that: finally! It is definitely the right way to
go, as the Chinese government is hell-bent on ignoring all concerns from
foreigners (as well as from critical voices in their own country),
meaning that any "influencing them slowly" is futile.</p>Alarms in Ubuntu: update2010-01-11T11:58:00+01:00Peter Lindtag:plind.dk,2010-01-11:alarms-in-ubuntu-update.html<p>In <a href="http://plind.dk/2010/01/05/alarms-in-ubuntu/" title="Read the original post">Alarms in
Ubuntu</a>,
I published a script that lets you set an alarm from the command line,
nice and easy. One thing was lacking though: visual notification of the
alarm, so if you happen to be away from the computer when the alarm
sounds you'll still see the dialog box. To achieve that I've modified
the script, added an extra one, so here's the new and shinier alarm
script.</p>
<p>First, the alarm script:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">=</span> <span class="s1">''</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nb">echo</span> <span class="s2">"No arguments for for alarm! Supply with time and optionally message</span>
<span class="s2">example:</span>
<span class="s2"> alarm 7:45</span>
<span class="s2"> alarm 19:59</span>
<span class="s2"> alarm '3pm + 3 day'</span>
<span class="s2"> alarm 2010-09-18</span>
<span class="s2"> alarm 'now + 5 minutes' 'go do ... stuff'</span>
<span class="s2"> "</span>
<span class="nb">exit</span> 1
<span class="k">else</span>
<span class="nv">message</span><span class="o">=</span><span class="s2">"alarm time reached"</span>
<span class="o">[</span> <span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="o">=</span> <span class="s1">''</span> <span class="o">]</span> <span class="o">&&</span> <span class="nv">message</span><span class="o">=</span><span class="nv">$2</span>
<span class="k">if</span> <span class="sb">`</span><span class="nb">echo</span> aplay -q /home/fake51/Downloads/gqold.wav <span class="se">\&\&</span> ddisplay <span class="se">\"</span><span class="nv">$message</span><span class="se">\"</span> <span class="p">|</span> at <span class="nv">$1</span> 2><span class="se">\&</span><span class="m">1</span> > /dev/null<span class="sb">`</span>
<span class="k">then</span>
<span class="nb">exit</span> 0
<span class="k">else</span>
<span class="nb">echo</span> <span class="s2">"Setting alarm failed"</span>
<span class="nb">exit</span> 1
<span class="k">fi</span>
<span class="k">fi</span>
</pre></div>
</td></tr></table>
<p>Now, the major difference to the previous script lies in the script
accepting a message, setting a default message, and then using ddisplay
to display a message box.<br />
Now, ddisplay is not a linux command - it's the second part of this
scripting excercise.</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8
9</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">""</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nb">exit</span> 1
<span class="k">else</span>
<span class="nb">export</span> <span class="nv">DISPLAY</span><span class="o">=</span>:0
zenity --warning --text<span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
<span class="nb">exit</span> 0
<span class="k">fi</span>
</pre></div>
</td></tr></table>
<p>This script makes use of the zenity command - which basically displays
GTK+ dialogs. The 'warning' option makes zenity display a normal dialog
box on top of everything, while the 'text' option is obviously the text
to display. Hence, pass a text string to ddisplay and you'll get a
dialog box with it - and that's what the first script does, thus playing
the alarm sound and popping up a dialog box when the sound is done.<br />
The reason for adding the extra script is that 'at' schedules commands
to run - so putting the dialog box code in a function in the alarm
script isn't an option. One could try sticking the zenity command
straight in the 'echo' piped to 'at', but to run 'zenity' from a script,
you typically need to set a few environment variables (these are set if
you run zenity straight from the command line, but not necessarily if
run by cron or at). On the plus side, ddisplay can be reused for other
scripts as well, simplifying them.</p>Sorting algorithms: Merge sort2010-01-10T21:00:00+01:00admintag:plind.dk,2010-01-10:sorting-algorithms-merge-sort.html<p>The next sorting algorithm in this series is the <a href="http://en.wikipedia.org/wiki/Merge_sort" title="Wikipedia article on the merge sort algorithm">Merge
Sort</a>.
Of the algorithms covered so far, this algorithm comes closest to the
<a href="https://plind.dk/2009/11/30/sorting-algorithms-shell-sort/" title="Read my post on the Shell Sort algorithm">Shell Sort
algorithm</a>.
The reason for this lies in how the comparisons are done in both
algorithms: instead of working on the entire array, both the shell sort
and the merge sort work by breaking the array to sort into smaller
arrays and then sorting those first.</p>
<p>To be more specific, merge sort works by taking an array, breaking it
down to the smallest parts, and then combining these parts. When
combining the parts, there's a good chance that one part can just be
appended to the other, which means that there's one comparison and a
merge to be done. However, when the second part cannot be appended to
the first part, the two parts must be merged together. Here, however,
merge sort has an advantage in that both parts to be merged are already
sorted, which makes the merge easier.</p>
<p>The algorithm looks roughly like this:</p>
<ol>
<li>Check if the array contains more than 1 element.</li>
<li>If yes, break the array into two parts and go back to 1.</li>
<li>If no, return the array - it is now sorted (there's 1 element in it,
so it's trivially sorted).</li>
<li>Compare the last element of the first returned array with the first
element of the second return array.</li>
<li>If the first array element from step 4 is less or equal to the
second array element from step 4, then append the second array on to
the first array.</li>
<li>If the first array element from step 4 is not less or equal to the
second array element, then merge the two arrays.</li>
<li>Return the appended/merged array - it is now sorted.</li>
</ol>
<p>The sub-part - the merging function - looks as follows:</p>
<ol>
<li>Initialize an empty array.</li>
<li>Check if both arrays to merge have elements. If not, skip to 4.</li>
<li>Compare the first elements of both arrays. Remove the lower one from
it's array and append it to the result array. Continue from 2.</li>
<li>If either array to be sorted still has elements left, append it to
the result array.</li>
<li>Return the result array - it's a sorted merge of the two provided
arrays.</li>
</ol>
<p>As can be gathered from this description, there are two parts to the
algorithm: a recursive function, that breaks up the provided array into
ever smaller parts, before recombining them, and a merge function, that
sorts and merges the arrays to be recombined.</p>
<h2>The Code</h2>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">MergeSort</span> <span class="k">extends</span> <span class="nx">BaseSort</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">sortFunction</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">store</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">breakdownArray</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">breakdownArray</span><span class="p">(</span><span class="k">array</span> <span class="nv">$array</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$count</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$array</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$count</span> <span class="o">></span> <span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$half</span> <span class="o">=</span> <span class="nv">$count</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
<span class="nv">$left</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">breakdownArray</span><span class="p">(</span><span class="nb">array_slice</span><span class="p">(</span><span class="nv">$array</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">$half</span><span class="p">));</span>
<span class="nv">$right</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">breakdownArray</span><span class="p">(</span><span class="nb">array_slice</span><span class="p">(</span><span class="nv">$array</span><span class="p">,</span> <span class="nv">$half</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">end</span><span class="p">(</span><span class="nv">$left</span><span class="p">)</span> <span class="o"><</span> <span class="nb">reset</span><span class="p">(</span><span class="nv">$right</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nb">array_merge</span><span class="p">(</span><span class="nv">$left</span><span class="p">,</span> <span class="nv">$right</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">arraySortMerge</span><span class="p">(</span><span class="nv">$left</span><span class="p">,</span> <span class="nv">$right</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$array</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">arraySortMerge</span><span class="p">(</span><span class="k">array</span> <span class="nv">$left</span><span class="p">,</span> <span class="k">array</span> <span class="nv">$right</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$return</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
<span class="nv">$count_left</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$left</span><span class="p">);</span>
<span class="nv">$count_right</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$right</span><span class="p">);</span>
<span class="nv">$l</span> <span class="o">=</span> <span class="nv">$r</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="nv">$l</span> <span class="o"><</span> <span class="nv">$count_left</span> <span class="o">&&</span> <span class="nv">$r</span> <span class="o"><</span> <span class="nv">$count_right</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$left</span><span class="p">[</span><span class="nv">$l</span><span class="p">]</span> <span class="o"><=</span> <span class="nv">$right</span><span class="p">[</span><span class="nv">$r</span><span class="p">])</span>
<span class="p">{</span>
<span class="nv">$return</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$left</span><span class="p">[</span><span class="nv">$l</span><span class="p">];</span>
<span class="nv">$l</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="nv">$return</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$right</span><span class="p">[</span><span class="nv">$r</span><span class="p">];</span>
<span class="nv">$r</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$l</span> <span class="o"><</span> <span class="nv">$count_left</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nb">array_merge</span><span class="p">(</span><span class="nv">$return</span><span class="p">,</span> <span class="nb">array_slice</span><span class="p">(</span><span class="nv">$left</span><span class="p">,</span> <span class="nv">$l</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nb">array_merge</span><span class="p">(</span><span class="nv">$return</span><span class="p">,</span> <span class="nb">array_slice</span><span class="p">(</span><span class="nv">$right</span><span class="p">,</span> <span class="nv">$r</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Here the structure of the algorithm should be clear: the recursive
function at the top, then the merge function below.</p>
<p>The most important part to optimize in this algorithm is the merge
function - this is where most of the time in the sorting will be spent.
When I wrote up the algorithm, this was the hardest part to create: a
function that merges two already sorted arrays and takes advantage of
this fact. First, I considered extracting the first variables of the
arrays, and then reinsert the one not used (only one variable at a time
should be append to the result array, the other should be checked again
next loop). This was much slower than what the algorithm should run
like, so I went over it a couple of times, trying to improve. The next
version took advantage of the
<a href="https://dk.php.net/array_shift" title="PHP manual on array_shift">array_shift</a>
function - this function shifts out the first element of an array and
then reorders the array, so the new first element can be reference via
\$array[0]. This improved the speed of the algorithm by a factor 10, but
it was still running very slow. In fact, the algorithm was running at
O(n^2^) and not the O(n log n) it should. So, I went back to the code
and rethought things: instead of changing the array, it might be faster
to just use indexes for the merge - which indeed was the case. The code
you can see above actually runs in much better time - it's in O(n log n)
now.</p>
<p>The moral should be clear: if you're doing algorithms like this and
you'll be throwing a lot of data after them, you need to do as little
modifying to data structures as possible. Creating a new array by
appending elements doesn't take a lot of time, but shifting elements out
of one and reordering it will.</p>
<p>There's another lesson to be learned as well: the algorithm was running
in O(n2) even with a proper merge function! So, while in theory the
worst case scenario of merge sort is O(n log n) this very much depends
upon the implementation.</p>
<h2>Results</h2>
<p>At 1,000 elements</p>
<div class="highlight"><pre><span></span>1000 elements to sort.Sanity check. PHP native sort() algorithm took: 0.002649 seconds. Memory used: 48264 bytes
Running MergeSort sort. Algorithm took: 0.022447 seconds. Memory used: 48264 bytes
</pre></div>
<p>This is pretty good time, less than 10 times slower than PHP itself.
Next, 10,000 elements:</p>
<div class="highlight"><pre><span></span>10000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.035824 seconds. Memory used: 505704 bytes
Running MergeSort sort. Algorithm took: 0.268344 seconds. Memory used: 505744 bytes
</pre></div>
<p>About 11 times slower for 10 times as many elements. 100,000 elements:</p>
<div class="highlight"><pre><span></span>100000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.541093 seconds. Memory used: 4924456 bytes
Running MergeSort sort. Algorithm took: 3.329802 seconds. Memory used: 4924504 bytes
</pre></div>
<p>About 12 times slower, at 10 times as many elements. The algorithm is
running steady with running times incrementing as expected for an O(n
log n) sort. Final test, 1,000,000 elements:</p>
<div class="highlight"><pre><span></span>1000000 elements to sort.
Sanity check. PHP native sort() algorithm took: 7.504231 seconds. Memory used: 48194472 bytes
Running MergeSort sort. Algorithm took: 41.222942 seconds. Memory used: 48194504 bytes
</pre></div>
<p>Sorting one million elements in 41 seconds is not that bad, considering
that the implementation is in PHP, an interpreted language!<br />
Like the shell sort algorithm, this one could actually be used in a
production environment (if you can't use the sort() functions in php).
It's slightly faster than the shell sort as well, so we're clearly
moving into the realm of effective algorithms here.</p>Checking Apache logs2010-01-07T19:56:00+01:00Peter Lindtag:plind.dk,2010-01-07:checking-apache-logs.html<p>Had an interesting find today as I was looking over the Apache logs for
<a href="https://plphp.dk/" title="PL PHP: hjemmeside programmering og PHP udvikling">PL
PHP</a>:</p>
<div class="highlight"><pre><span></span>85.190.0.3 - - [07/Jan/2010:09:55:55 +0100] "CONNECT 213.92.8.7:31204 HTTP/1.0" 200 4679 "-" "-"
85.190.0.3 - - [07/Jan/2010:09:55:58 +0100] "POST https://213.92.8.7:31204/ HTTP/1.0" 404 1311 "-" "-"
</pre></div>
<p>I had a couple of those in the logs and was rather wondering what it was
- seemed fairly odd. Some quick googling didn't show anything apart from
others having the same confusion over this. The explanation turned out
to be simple, though: if you're connected to
<a href="https://freenode.net/irc_servers.shtml" title="Info on Freenode's IRC servers">freenode</a>
(an IRC network), they'll scan your IP checking for open proxies. They
do that to defend themselves against DDoS attacks, so it's hard to get
annoyed by it ... would be nice if the user agent would specify
something like it, though.</p>
<p>How I found out? Whois the ip the traffic comes from, and you'll see the
following message:</p>
<div class="highlight"><pre><span></span>remarks: ****************************************************
remarks: ****************************************************
remarks: If you see portscans/abuse from 85.190.0.3
remarks: Please read https://freenode.net/policy.shtml#proxies
remarks: ****************************************************
remarks: ****************************************************
</pre></div>
<p>Good old whois, always providing for the info-needy.</p>Sorting algorithms: Selection sort2010-01-07T13:12:00+01:00admintag:plind.dk,2010-01-07:sorting-algorithms-selection-sort.html<div id="_mcePaste">
1000 elements to sort.
</div>
<div id="_mcePaste">
Sanity check. PHP native sort() algorithm took: 0.002461 seconds. Memory
used: 48264 bytes
</div>
<div id="_mcePaste">
Running SelectionSort sort. Algorithm took: 0.273260 seconds. Memory
used: 48272 bytes
</div>
<p>1000 elements to sort.Sanity check. PHP native sort() algorithm took:
0.002461 seconds. Memory used: 48264 bytesRunning SelectionSort sort.
Algorithm took: 0.273260 seconds. Memory used: 48272 bytesNext in line
of this series: the <a href="https://en.wikipedia.org/wiki/Selection_sort" title="Wikipedia article on Selection Sort">Selection
Sort</a>.
This algorithm is not too unlike <a href="https://plind.dk/2009/11/16/sorting-algorithms-insertion-sort/" title="Read my post on the Insertion Sort">Insertion
Sort</a>,
in that it works by putting one element in the right place, at a time.
More specifically, selection sort works by making n-1 passes over an
n-sized array, each time finding the smallest element and putting it in
position n of the array. On the surface it might also look like the
bubble sort algorithm: like that, the selection sort does a load of
comparisons per pass over the array. However, where bubble sort makes
n-1 comparisons per pass of the array, selection sort makes (n-1)-m
comparisons per pass, where m is the current pass. In other words, the
more passes done, the less comparisons to do.</p>
<p>As a set of steps:</p>
<ul>
<li>Step 1: set index of array to 0</li>
<li>Step2: find smallest element of array, searching from array index
and up</li>
<li>Step 3: swap smallest element with element at array index</li>
<li>Step 4: increase array index</li>
</ul>
<h2>The code</h2>
<p>The code for this algorithm is fairly simple - as one might expect. It
looks like:</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">SelectionSort</span> <span class="k">extends</span> <span class="nx">BaseSort</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">sortFunction</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$count</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$comparison</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$i</span><span class="p">];</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nv">$i</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$ii</span> <span class="o">=</span> <span class="nv">$i</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span> <span class="nv">$ii</span> <span class="o"><</span> <span class="nv">$count</span><span class="p">;</span> <span class="nv">$ii</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$ii</span><span class="p">]</span> <span class="o"><</span> <span class="nv">$comparison</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$index</span> <span class="o">=</span> <span class="nv">$ii</span><span class="p">;</span>
<span class="nv">$comparison</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$ii</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$index</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$i</span><span class="p">];</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$comparison</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Again, there's the typical two-loop structure: outer loop and inner
loop, matching position in array and sorting comparisons. Looking a bit
closer, one can see that this algorithm is stable and that it doesn't
use extra memory to sort in (apart from a few extra variables).</p>
<h2>Results</h2>
<p>1,000 elements first:</p>
<div class="highlight"><pre><span></span>1000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.002461 seconds. Memory used: 48264 bytes
Running SelectionSort sort. Algorithm took: 0.273260 seconds. Memory used: 48272 bytes
</pre></div>
<p>At 1,000 elements, it's 13 times slower than the shell sort. It's about
twice as slow as the insertion sort algorithm. Next try is 10,000
elements.</p>
<div class="highlight"><pre><span></span>10000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.035268 seconds. Memory used: 505704 bytes
Running SelectionSort sort. Algorithm took: 27.733544 seconds. Memory used: 505704 bytes
</pre></div>
<p>100 times slower than at 1,000 elements - the algorithm runs in O(n^2^)
as one would expect, given how it works. This is not an algorithm to
choose if you've got a lot of elements to sort and limited time.
However, the memory footprint does make it nice if you've got limited
resources.<br />
Compared to the other algorithms, selection sort runs in about half the
time of bubble sort and twice the time of insertion sort. And obviously,
massively slower than shell sort.</p>Alarms in Ubuntu2010-01-05T13:26:00+01:00Peter Lindtag:plind.dk,2010-01-05:alarms-in-ubuntu.html<p>I've been looking for a while for a good, easy to use, easy to install,
no fuss alarm clock. Just something I can set to go off in 10 minutes.
Or at 2 o'clock. Or tomorrow. I haven't found any apps that did that in
a nice and easy fashion. I have found several that would do lots of
things, but none of them were easy to use and handle - almost all of
them were bloated, with bad UI. So, once more, I googled, but this time
I came across another solution. 'Look ye to the command line!' I read.
'Use the at command, Luke!' And so I issued a 'man at' and quickly came
upon good stuff.</p>
<p>From the man page of at:</p>
<div class="highlight"><pre><span></span>DESCRIPTION
at and batch read commands from standard input or a specified
file which are to be executed at a later time, using /bin/sh.
</pre></div>
<p>Which, in my ears, sound awesome! You see, what you get here is close to
the power of crontab, but single-use, i.e. you setup a command to run
once, at a given time, and that's it. In other words, a command that
will let you set up alarms easily.</p>
<p>The next step was turning at into an actual alarm. I prefer the sound
method: play something that sounds like an actual alarm, and you'll be
sure you don't overlook the thing. You can combine with something visual
too, of course, like a dialog box. For the sound, I found <a href="https://policeinterceptor.com/navysounds.htm">AUTHENTIC
NAVY ALARM SOUNDS PAGE</a>
which hosts a few different wave files. In particular, the general alarm
(old style) works well for me. To play it, all you need to do is</p>
<div class="highlight"><pre><span></span>aplay -q sound.file
</pre></div>
<p>Combined with at, this then becomes</p>
<div class="highlight"><pre><span></span>echo "aplay -q sound.file" | at [time]
</pre></div>
<p>One of the beauties of at is that it will accept lots of different time
inputs. So, you can do 'at 7:40', 'at 3pm', 'at now + 5 minutes', 'at
today + 5 days', etc.</p>
<p>With things put together into a script, it might look like</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">=</span> <span class="s1">''</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nb">echo</span> <span class="s2">"No argument for for alarm! Supply with time</span>
<span class="s2">example:</span>
<span class="s2"> alarm 7:45</span>
<span class="s2"> alarm 19:59</span>
<span class="s2"> alarm 3pm + 3 day</span>
<span class="s2"> alarm 2010-09-18</span>
<span class="s2"> "</span>
<span class="nb">exit</span> 1
<span class="k">else</span>
<span class="k">if</span> <span class="sb">`</span><span class="nb">echo</span> aplay -q /home/fake51/Downloads/gqold.wav <span class="p">|</span> at <span class="nv">$1</span> 2><span class="se">\&</span><span class="m">1</span> > /dev/null<span class="sb">`</span>
<span class="k">then</span>
<span class="nb">exit</span> 0
<span class="k">else</span>
<span class="nb">echo</span> <span class="s2">"Setting alarm failed"</span>
<span class="nb">exit</span> 1
<span class="k">fi</span>
<span class="k">fi</span>
</pre></div>
</td></tr></table>
<p>Save the above script as alarm in \~/bin/, chmod to 0755 and you can
then set alarms like</p>
<div class="highlight"><pre><span></span>alarm 11:40
alarm "2010-01-06 12:00"
alarm "now + 5 minutes"
</pre></div>
<p>Should you want to remove the alarms before they run, you can see
commands set to run with atq and remove them with atrm [number]</p>Comparing CMS/blog systems, part 3.2: CMS Made Simple day to day2009-12-17T12:46:00+01:00Peter Lindtag:plind.dk,2009-12-17:comparing-cmsblog-systems-part-3-2-cms-made-simple-day-to-day.html<p>Having gone through the setup of my new site for PL PHP in <a href="http://plind.dk/2009/12/14/comparing-cmsblog-systems-part-3-1-typo3-day-to-day/">Comparing
CMS/blog systems, part 3.1: Typo3 day to
day</a>
I next wanted to try going through the same with CMS Made Simple. As
noted in <a href="https://plind.dk/2009/11/08/comparing-cmsblog-systems-part-2-cms-installation/">Comparing CMS/blog systems, part
2</a>
I didn't get CMS Made Simple installed the first time round, but as I
lowered my
<span style="text-decoration: line-through;">expectations</span> PHP
version I got it working without problems. So after a few minutes, it
was time for my second try at creating PL PHP</p>
<h2>Hands dirty</h2>
<p>Part of installing CMS Made Simple is typically installing default
templates and content. So, unlike Typo3 where the first thing you see of
your site is an error, with CMSMS you see a working site with content.
This is good and bad: it gives you a good idea of what CMSMS can do, but
it also means you have to figure out how to change what's already there
- sometimes a clean slate is easier to work with.</p>
<p>It's not a big problem in any way, though. Diving into the work without
checking any docs quickly led me to the templates point under the layout
menu. From here, you can easily create a new template as well as have a
look at existing ones to learn from. However, when you create a new
template, CMSMS prefills it with various goodies that you'll typically
need - so, I didn't have any need for looking at other templates to get
me going. Looking at the syntax of the prefilled template (as well as
looking under the hood) CMSMS uses Smarty for its templates. I haven't
worked with Smarty before, so that put me in pretty much the same
position as when trying to create a template in Typo3. Only, Smarty
apparently uses a mixture of HTML and pseudo-PHP, so the learning curve
is a lot less steep. It took me all of 20 minutes to copy in the HTML I
already had for PL PHP and put the replacement markers in the proper
spots. That gave me basic template structure:</p>
<div class="highlight"><pre><span></span>{process_pagedata}
<span class="cp"><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"></span>
<span class="nt"><html</span> <span class="na">xmlns=</span><span class="s">"https://www.w3.org/1999/xhtml"</span> <span class="na">xml:lang=</span><span class="s">"en"</span> <span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><title></span>{sitename} - {title}<span class="nt"></title></span>
{metadata}
{stylesheet}
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">'main_frame'</span><span class="nt">></span>
<span class="c"><!-- start header --></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">'header'</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">'logo'</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">'logo_text'</span><span class="nt">><a</span> <span class="na">href=</span><span class="s">'/'</span><span class="nt">></span>{sitename}<span class="nt"></a></div></span>
<span class="nt"></div></span>
<span class="c"><!-- start menu --></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">'menu'</span><span class="nt">><span</span> <span class="na">class=</span><span class="s">'splitter'</span><span class="nt">></span><span class="ni">&nbsp;</span><span class="nt"></span></span>
{menu}
<span class="nt"></div></span>
<span class="c"><!-- end menu --></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">'float-clear'</span><span class="nt">></div></span>
<span class="nt"></div></span>
<span class="c"><!-- end header --></span>
<span class="c"><!-- start content --></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"content"</span><span class="nt">></span>
<span class="nt"><h1></span>{title}<span class="nt"></h1></span>
{content}
<span class="nt"></div></span>
<span class="c"><!-- end content --></span>
<span class="c"><!-- start footer --></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">'footer'</span><span class="nt">></span>
<span class="ni">&copy;</span> 2009 Peter Lind<span class="nt"><span</span> <span class="na">class=</span><span class="s">'splitter'</span><span class="nt">></span><span class="ni">&nbsp;</span><span class="nt"></span></span>PL PHP<span class="nt"><span</span> <span class="na">class=</span><span class="s">'splitter'</span><span class="nt">></span><span class="ni">&nbsp;</span><span class="nt"></span></span>
<span class="nt"></div></span>
<span class="c"><!-- end footer --></span>
<span class="nt"></div></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</pre></div>
<p>As should be visible, most of the template is just straightforward HTML.
The rest is fairly self-explanatory as well, given the names used for
the placeholders: menu, content, metadata, stylesheet, etc. What
actually goes into those placeholders? A quick test shows it's pretty
much what you'd expect - which means, the next steps are reduced to
making sure the right content is output.</p>
<p>Next is taking care of the stylesheet. This is more or less the same
procedure as with the template: create a new stylesheet, copy the
contents from the already created version, then set the media type and
submit. Before you can actually see the changes made by the new template
and stylesheet, you'll need to set both as default for the pages. This
is done through the Templates and Stylesheets menupoints, point and
click. One thing to remember is you need to set the template as default
and set the pages you already have created to your new template.
Otherwise you'll either not see current pages using the new template or
not see new pages using it.</p>
<p>At this point, the site has working template and stylesheet. The menu is
still all out of whack and the content needs to be fixed, so next up is
looking at generating a menu. This is handled under Layout -> Menu
Manager and once again, you click the button to create new, then start
filling in details. Here though, you'll be working from nothing so
copying an existing menu structure and modifying can make it easier.</p>
<p>This was probably the part that took the longest, as it makes the most
use of Smartys pseudo-php. The menu structure I use for PL PHP is fairly
simple though, so it was mainly a question of analyzing the existing
setup and then taking the parts I needed. It came out like this:</p>
<div class="highlight"><pre><span></span>{if $count > 0}
{foreach from=$nodelist item=node}
{if $node->current}
<span class="nt"><a</span> <span class="na">href=</span><span class="s">'{$node->url}'</span> <span class="na">class=</span><span class="s">'active-menupoint'</span> <span class="na">title=</span><span class="s">'{$node->menutext}'</span><span class="nt">></span>{$node->menutext}<span class="nt"></a></span>
{else}
<span class="nt"><a</span> <span class="na">href=</span><span class="s">'{$node->url}'</span> <span class="na">title=</span><span class="s">'{$node->menutext}'</span><span class="nt">></span>{$node->menutext}<span class="nt"></a></span>
{/if}
<span class="nt"><span</span> <span class="na">class=</span><span class="s">'splitter'</span><span class="nt">></span><span class="ni">&nbsp;</span><span class="nt"></span></span>
{/foreach}
{/if}
</pre></div>
<p>If you know PHP you can guess what's going on here. If you don't, it
might be somewhat confusing but probably not too much: node->url and
node->menutext should give it away. It's just a basic loop over the
pages, creating a link for each it finds.</p>
<p>After saving this part, you should once again remember to set it as
default, as otherwise the menus won't be rendered using it. Having done
that, my site now looks like it's supposed to - apart from actual
content. This is handled from the Pages point in the Content menu. First
part is to delete the existing pages, then create new ones with proper
names and placing. Two minutes later, my site now has the proper content
structure and I can get busy writing copy - using the ubiquitous TinyMCE
editor.</p>
<p>One thing missing is the pretty urls. The setup for that is not as easy
to find as the other things, so I turn to Google for some help, which
instantly brings up some ok looking hits. The <a href="https://wiki.cmsmadesimple.org/index.php/FAQ/Installation/Pretty_URLs">CMSMS
wiki</a>
has the info needed, and it turns out that you just need to edit your
config.php (specifically, setting \$config['url_rewriting'] to
'mod_rewrite') and add a .htaccess file that routes all requests to
index.php. After that's done, CMSMS happily accepts pretty urls. And
like with Typo3, you can change which urls are used for your pages -
that's handled for each page individually when editing it (under the
options tab).</p>
<h2>Done</h2>
<p>So, about three hours after I started implementing PL PHP in CMS Made
Simple I have a working site looking the way I want it to, with pretty
urls and what not. Although I am a PHP developer, I'm not sure if it
would have been much harder for non-tech people - the interface is
fairly intuitive. Especially if you're happy to go with the design
provided by the default templates of CMSMS you'll be up and running with
a site in very little time.</p>
<p>Comparing the experience to <a href="https://plind.dk/2009/12/14/comparing-cmsblog-systems-part-3-1-typo3-day-to-day/">that of
Typo3</a>
this was easy on a level Typo3 can only dream of - but probably don't.</p>Comparing CMS/blog systems, part 3.1: TYPO3 day to day2009-12-14T23:38:00+01:00Peter Lindtag:plind.dk,2009-12-14:comparing-cmsblog-systems-part-3-1-typo3-day-to-day.html<p>The individual posting on the blogs and cms' will be bigger, given that
there's simply more to do than for installing. Hence, I've decided to
split the posts into smaller chunks, dealing with one product at a time.
On top of that, the ordering has changed a tad - I wanted to start with
the blogs again but as I'm working on a site for PL PHP and thought that
I would do that in <a href="https://www.typo3.org/">Typo3</a>, it was a nobrainer to
do this blog post alongside the new site. So, I'm reversing the order,
taking the CMS systems first, then the blogs - one by one. Oh, and, just
to completely change the idea, I've reverted to using PHP 5.2 instead of
5.3 - typo3 cannot handle 5.3 and some of the other systems weren't too
happy with it either, so to keep the list systems I'm using 5.2.</p>
<h2>Designing</h2>
<p>Before I started implementing the site in Typo3 I made a design for the
site. I had a browse at <a href="https://www.oswd.org/">OSWD</a> (sadly inactive -
it's a great resource) and found a design that would work well for what
I had in mind. I didn't feel like just grabbing the design (a perfectly
valid option) but wanted some minute changes - so I started out with
GIMP, got some graphics together, then quickly mocked up an HTML
template frame. Here's a screenshot of what the site will look like:</p>
<p><img alt="PL PHP site
design" src="https://plind.dk/wp-content/uploads/2009/12/site.png" title="PL PHP site design" /></p>
<p>In terms of HTML and CSS it's a pretty easy layout, everything is
wrapped in a div for easy styling and separation.</p>
<h2>Hands dirty</h2>
<p>So, with the HTML template in hand, I started with Typo3. Unfortunately,
I didn't have any proper docs on how to go about things, so I started
off thinking that it would be easy to get a site working, that Typo3
must be setup to easily do what it's meant to do, and that I would be
able to google what I couldn't figure out on my own. Quite a bit of time
later I had learned that Typo3 necessitates fairly detailed docs, is not
created with any defaults in mind, and generally makes tasks much more
complex than they need be. There are many interesting and cool parts to
the system, but they are hard to find for the uninitiated, it seems.</p>
<p>Instead of just ranting, time to show how I went about things. The first
step, I thought, was that of making a template. Typo3 is a CMS: content
management system. You manage content, displaying it within templates.
So, of course, you make a template, then make some content, then display
your content in templates, right? No. You cannot make templates in the
system before you've created pages. So, step 1 is to create your first
page in the system, then you can go on and make a template for that
page.</p>
<p>So, having gotten this far, I then dived into creating my site template
from scratch. Now, more seasoned Typo3 users/developers would probably
stop here and ask: why didn't you just install the TemplaVoila!
extension? Or import your HTML template using the tools for that? Well,
I wanted to learn how Typo3 actually works. Also, a system that only
works if you add plugins/extensions is simply not a working system -
it's a skeleton with no functionality. That aside, getting started with
TypoScript wasn't hard in any way. The idea of the script is pretty
straight forward, the syntax the same and if you look at some examples
you should be able to pick up fundamentals fairly easy.</p>
<p>When starting with templates, you first create the template, give it a
name and set options. You really only need to set the name to be able to
edit it - but there is one other <strong>very important part</strong> to do, which
unfortunately is not a default or even pointed out in the template
creator. This important part is to include a static template that gives
you access to content from pages and/or other places. Without this
static template there's a fair chance you'll get nothing back when
trying to insert content in your template. Now, there's probably a good
reason why one of the static templates giving you access to content
isn't by default included - it's most likely because there are several
templates that do so and the creators of Typo3 don't want to make
choices on behalf of users. Nonetheless, it seems to me that if you need
one of these static templates in more than 66% of cases, then you at the
very least make a big, fat, red arrow that points to this option.
Normally, I'd think it best to include something by default if it's used
a lot, and then let people deselect it if it's not used - unfortunately,
that's not really an option for Typo3, as some templates exclude others.</p>
<p>Cutting this short: if you're making a site like me, styling it with CSS
(i.e. no tables to position things) you most likely want to include the
'css_styled_content' template. This then gives you access to content
in pages through a simple TypoScript copy: element \<
styles.content.get.</p>
<p>After setting up the basic stuff, you can go on to creating the template
structure. This is done through mainly two parts of the template editor:
constants and setup. Constants are, as the name implies, constants.
These can be included in the setup, which is where the template is
actually made. The upshot of this is that you can change a constant in
one place and see changes in many, i.e. doing away with code
duplication. Where this comes in handy is mainly when you're using one
template in many places - no need to change template code, you just set
slightly different constants for each page.</p>
<p>My first try at creating my site template in Typo3 was fairly crude. It
looked something like this:</p>
<div class="highlight"><pre><span></span>page = PAGE
page.bodyTag = <span class="nt"><body></span>
page.stylesheet = fileadmin/styles/main.css
page {
10 = HTML
10.value = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'main_frame'</span><span class="nt">></span>
11 = HTML
11.value = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'header'</span><span class="nt">></span>
12 = HTML
12.value = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'logo'</span><span class="nt">></span>
13 = TEXT
13.value = PL PHP
13.wrap = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'logo_text'</span><span class="nt">></span>|<span class="nt"></div></span>
14 = HTML
14.value = <span class="nt"></div></span>
... etc
}
</pre></div>
<p>Yes, at first I only knew about three elements in TypoScript: PAGE, TEXT
and HTML. I was pretty sure there was a better way to do what I wanted
but I find it's a better way to learn to first give things a try, then
improve your first idea. So, using the above method I got most of the
template working. I then took some time out to study TypoScript a bit
more, learned about a few more elements and rewrote the template to:</p>
<div class="highlight"><pre><span></span>page = PAGE
page.bodyTag = <span class="nt"><body></span>
page.stylesheet = fileadmin/styles/main.css
page {
10 = COA
10.wrap = <span class="nt"><div</span> <span class="na">id=</span><span class="s">"main_frame"</span><span class="nt">></span>|<span class="nt"></div></span>
10 {
10 = COA
10.wrap = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'header'</span><span class="nt">></span>|<span class="nt"></div></span>
10 {
10 = COA
10.wrap = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'logo'</span><span class="nt">></span>|<span class="nt"></div></span>
10 {
10 = TEXT
10.value = <span class="nt"><a</span> <span class="na">href=</span><span class="s">'/'</span><span class="nt">></span>PL PHP<span class="nt"></a></span>
10.wrap = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'logo_text'</span><span class="nt">></span>|<span class="nt"></div></span>
}
20 = TEXT
20.value =
20.wrap = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'menu'</span><span class="nt">></span>|<span class="nt"></div></span>
30 = TEXT
30.wrap = <span class="nt"><div</span> <span class="na">class=</span><span class="s">'float-clear'</span><span class="nt">></span>|<span class="nt"></div></span>
}
20 <span class="nt">< styles.content.get</span>
<span class="na">20.wrap =</span> <span class="s"><div</span> <span class="na">id=</span><span class="s">'content'</span><span class="nt">></span>|<span class="nt"></div></span>
30 = TEXT
30.value (
<span class="ni">&copy;</span> 2009 Peter Lind<span class="nt"><span</span> <span class="na">class=</span><span class="s">'splitter'</span><span class="nt">></span><span class="ni">&nbsp;</span><span class="nt"></span></span>PL
PHP<span class="nt"><span</span> <span class="na">class=</span><span class="s">'splitter'</span><span class="nt">></span><span class="ni">&nbsp;</span><span class="nt"></span></span>
)
30.wrap = <span class="nt"><div</span> <span class="na">id=</span><span class="s">'footer'</span><span class="nt">></span>|<span class="nt"></div></span>
}
}
</pre></div>
<p>The advantage in the above should be clear: elements are no longer split
- instead, elements are contained within each other as in the actual
HTML.</p>
<p>TypoScript is basically a collection of objects that are typically
modeled on HTML or PHP counterparts. The TEXT and HTML elements, for
instance, resolve to text outputs - the difference between the two is
that the properties you set on the TEXT element will be set on it's
stdWrap property (a TEXT element is close to being a stdWrap element)
while the same doesn't go for the HTML element. Will this affect you
when starting out with Typo3? Not very likely ...</p>
<p>Anyway, a quick run through: first, a PAGE element is declared. This
represents the entire page (don't confuse this with the page you created
for the template. The only elements of the page created that will be
inserted automatically into the template are those that affect the
\<head> - so, the PAGE element doesn't import any content
automatically) and you can set various properties such as stylesheet and
bodyTag to make basic layout decisions. To get further structure to the
template, you then need to add elements to the PAGE element. This is
done in the above code with the HTML, TEXT and COA elements. The COA
element is an array of cObjects - content objects. Better still, it
allows you to wrap the contents as you wish - hence, you can see it as
an HTML element that allows for a hierarchy (the HTML element cannot
contain other elements).</p>
<p>Three other elements worth mentioning in the code are: first, the
notation:</p>
<ul>
<li>= is simple assignment</li>
<li>\< means copy</li>
<li>=\< means reference (not used above)</li>
<li>> means empty the element (not used above. Basic usage is if you're
using an element already declared)</li>
<li>{} serves to reduce typing. Left hand elements inside the block are
treated as properties of the element to the left of the {</li>
<li>() serves to contain multiple lines. You cannot have anything to the
right of the ( and you cannot have anything to the left or right of
the )</li>
</ul>
<p>Secondly, the styles.content.get part. This refers, as one might guess
from my rant above, to the included static template. If one was to use
the template analyzer, one would see that css_styled_content appears
as a template extension to the main template. In this extension
styles.content.get is declared and it's properties filled. In your own
template you can then copy the contents of styles.content.get with \< -
and the content defined in the page of the template will appear inside
the template when rendered.</p>
<p>Lastly, as can be guessed from the numbering in the template, elements
are rendered according to numerical position. That is, 10 is rendered
before 20, which in turn is rendered before 30. Elements are resolved in
full before later elements are resolved: 10.10 is resolved before 10.20,
regardless of where 10.20 is declared.</p>
<p>Finally, to get the template working properly for the site, a menu is
needed. This bit can cause a bit of confusion, mainly because the
elements are more complex than other TypoScript elements. What's needed
is an HMENU element and then either TMENU or GMENU element - the first
would be Text Menu, the second Graphical Menu. As I'm opting for a css
based menu and handling most things myself, I opted for the TMENU. The
menu ended up looking like this:</p>
<div class="highlight"><pre><span></span>10 = HMENU
10.entryLevel = 0
10 {
1 = TMENU
1 {
NO {
after = <span class="nt"><span</span> <span class="na">class=</span><span class="s">'splitter'</span><span class="nt">></span><span class="ni">&nbsp;</span><span class="nt"></span></span>
}
ACT <span class="nt">< .NO</span>
<span class="err">ACT</span> <span class="err">{</span>
<span class="na">ATagParams =</span> <span class="s">class='active-menupoint'</span>
<span class="err">}</span>
<span class="na">ACT =</span> <span class="s">1</span>
<span class="err">}</span>
<span class="err">}</span>
</pre></div>
<p>The HMENU gets things started. The entryLevel bit sets the start level
of the menu to just root level (which, strangely, means that root level
pages should NOT be included - entryLevel seems to mean 'include things
below this level'). The next part determines the layout of the following
menus - which means that the numerical order here takes on a different
sense than in the other rendering parts, as here each successive level
describes a menu sublevel. Hence, HMENU.1 is menu level 1, HMENU.2 is
menu level 2. For each menu level described, a number of properties must
be set for them to render. For instance, the NO property of the TMENU
element must be set for the menuitems to render.</p>
<p>Luckily, that's about all it takes to get a menu going, no need to
include templates or extensions - just have to use the proper elements,
declare them properly and then have a proper page hierarchy (the reason
I use 'proper' so many times in a row is that I didn't ... chose to
learn the hard way, I did).</p>
<p>Now, the templating part is done. It's time to skip to content creating,
which is much easier (or harder, depending upon how creative you are) in
Typo3. The backend site includes an ok JavaScript rich text editor that
will do all the typical formatting tricks of the day, so there's not
really much to say regarding that: it works and it works quite easy.</p>
<p>Then, to stick some icing on the cake, nice urls would be good. To
achieve this, it's necessary to install an extension and mess about with
the .htaccess file in the main site folder. Going by the instructions
for the <a href="https://typo3.org/documentation/document-library/extension-manuals/realurl/1.7.0/view/">Realurl
extension</a>
it's not too bad though - compared to getting content displaying on the
page in the first place it was actually a breeze. There is one pitfall
to be aware of - you need to download the extension, then upload it to
your site. The 'discover' feature of Typo3 doesn't work, it imports an
old version of the extension instead of the newest one. Apart from that,
the extension works pretty well soon as you have edited your template
(the setup part needs three lines) and your .htaccess file - you can
even specify custom names for pretty urls.</p>
<h2>Done</h2>
<p>Getting a basic site up and running to a point where I can look at it
and say "that's alright" took a lot longer than expected. Some parts of
Typo3 are overly complex for no apparent reason. Others seem to work
fine and don't need any tweaking. The moral of the story seems to be
that you shouldn't go into Typo3 land without a map and a guide - you
probably won't find your way around.</p>
<p>Here's the list of things I used to get by:</p>
<ul>
<li><a href="https://typo3.org/documentation/document-library/">Documentation at
Typo3</a> - You can
find tutorials, videos and more here. It could be a great wealth of
info but unfortunately it's not. A lot of the data is outdated, a
lot of it is not detailed enough and a lot of it simply assumes to
much of the reader. Still, you'll find answers to some questions
here.</li>
<li>The
<a href="https://typo3.org/documentation/document-library/core-documentation/doc_core_tsref/current/">TSref</a><ul>
<li>documentation of TypoScript elements and other stuff. Beware of
the examples: they seem to make points but generally don't.</li>
</ul>
</li>
<li><a href="https://www.amazon.co.uk/Typo3-Enterprise-Management-Rene-Fritz/dp/1904811418/ref=sr_1_1?ie=UTF8&s=books&qid=1260793050&sr=8-1">Typo3 Enterprise
Management</a><ul>
<li>probably the best guide you can find, although it's a couple of
years old.</li>
</ul>
</li>
</ul>
<p>One thing I'm rather undecided about is the whole idea of TypoScript. I
came across the idea that PHP frameworks are not just a bonus - they can
be problematic as well. Rasmus Lerdorf <a href="https://toys.lerdorf.com/archives/38-The-no-framework-PHP-MVC-framework.html">voiced that idea and similar
ones
one</a>
his blog a while ago: his point being that PHP in itself actually works
as a framework and that if you stick a framework on top that, you're
distancing yourself from what you actually need. TypoScript gives you
the same feeling: why has HTML been recreated in this way? Why not just
allow actual HTML as templates and mix in some PHP? What are the
problems solved with TypoScript? What do we gain from representing
templates as TypoScripts? This concern is amplified by the fact that
extensions/plugins have been developed to take your HTMLand turn it into
a Typo3 template: if TypoScript solves my problems why was TemplaVoila!
created?</p>
<p>Some positive sides to it should be visible: templates won't be the
typical mash of HTML and PHP - they'll be a unique type that encompasses
both. This might make it easier to do some PHP without knowing the
language fully. It can also simply writing HTML as some things will be
done for you. Still, I wonder: do we need another set of building blocks
on top of HTML and PHP?</p>Input validation fail2009-12-10T11:49:00+01:00Peter Lindtag:plind.dk,2009-12-10:input-validation-fail.html<p>I just couldn't help it, I had to post this little nugget of failure.</p>
<p>On the website of a Danish newspaper -
<a href="https://www.politiken.dk/">Politiken</a> - they do blogs as well as
newsbits (and I mean bits, most of the stuff is just Reuters/AP). On
their blogs, comments are of course allowed. So far, so good. Now the
problems start when Politiken accept the comments. What they <strong>should be
doing</strong> is check the various fields for meaningful and/or default input.
What they <strong>are doing</strong> is allowing anything that looks remotely
meaningful. This leads to beautiful bits of html like the following:</p>
<div class="highlight"><pre><span></span><span class="o"><</span><span class="n">a</span> <span class="k">class</span><span class="o">=</span><span class="s">"url"</span> <span class="n">rel</span><span class="o">=</span><span class="s">"external nofollow"</span> <span class="n">href</span><span class="o">=</span><span class="s">"https://Website"</span><span class="o">></span><span class="p">[</span><span class="n">name</span> <span class="n">removed</span><span class="p">]</span><span class="o"></</span><span class="n">a</span><span class="o">></span>
</pre></div>
<p>Failing to check for the default value your own website put into the
field is simply pathetic. More pathetic than not checking whether the
referenced site is actually a valid domain but not by much.</p>Changes2009-12-09T07:45:00+01:00Peter Lindtag:plind.dk,2009-12-09:changes.html<p>I've decided to try moving my career more in the direction of
freelancing consulting. It's party based on necessity (haven't found a
full-time job yet) and partly on want (consulting/freelancing is very
interesting). First step has been to make some info available about it -
currently only in Danish but I'll get it up in English soon as well.</p>
<p>The information about me as a consultant is available from the
<a href="https://plind.dk/freelance-webudvikling/">Freelance Webudvikling</a> page.
There's some stuff about what I do, what I have worked on and what I
charge. Hopefully, there should also soon be my Danish CVR number.
That's right: I've started my own company as well, PL PHP!</p>Batteries2009-12-02T15:20:00+01:00Peter Lindtag:plind.dk,2009-12-02:batteries.html<p>Not long ago, the battery in my Dell XPS M1330 decided it was time to
die. Apparently, I had hit the wall: my battery which would run fine for
an hour and 20 minutes, just died - one day to the next. No warning
signs. Nothing. What's a geek to do? Look at <a href="https://accessories.euro.dell.com/sna/productlisting.aspx?c=dk&category_id=2999&chassisid=-1&cs=dkdhs1&l=da&mfgpid=1396741">Dells
offers</a>?
With the cheapest, 4-cell battery coming in at 780 kr. (\$156) that
didn't seem like an option. Instead, good old
<a href="https://www.ebay.com">eBay</a> seemed more interesting. With <a href="http://shop.ebay.com/i.html?_nkw=dell+xps+battery+m1330&_sacat=0&_trksid=p3286.m270.l1313&_dmpt=Laptop_Batteries&_odkw=dell+xps+battery&_osacat=0">800+ results
for Dell M1330
batteries</a>
there was bound to be something good. Just one minor problem ... pretty
much all of the results are fake batteries. Which leads to the dilemma:
cheap, fake battery or expensive, original battery? Well, I decided to
give the Chinese fakers a chance and sent for a battery from Hong Kong,
figuring that paying the total amount of 400 kr (\$80) was the better
option. Right I was! Haven't had a single problem with the battery (a
9-cell, compared to my previous 6-cell) and I'm now mobile once more.
All I can say is, suck it, Dell! The expensive batteries and other
accessories are a sham, on a par with the printer inks that cost more
than perfume.</p>
<p>Going with this spirit, I decided to look into replacing my iPod
battery. Of late, the charge on it had been reduced from 3-4 hours to 10
minutes, not really acceptable. Buying a new iPod when the old one works
just fine is also not acceptable (I'm sorry but no, I simply do not buy
into the ridiculous consumerism advanced by Apple, MicroSoft and
others). Some googling on the net led to the following site:
<a href="https://www.ipodhowtovideo.com/video/">ifixipodsfast.com</a> (strange
title, as that's not the domain it's hosted on). The videos of switching
out the battery made it seems so easy that I couldn't stop myself from
purchasing a new battery from <a href="https://www.batteribyen.dk">BatteriByen</a>
(at 100 kr - \$20). Before I knew it, I had a knife to my iPod, trying
to pry it open. Took me about 30 mins, using improper tools, then the
insides of it were showing and I was looking around for something
looking like a battery. A bit more time, and the iPod closed up again
with just a few marks of my barbaric butchering to show that I'd been
messing with the stuff Apple never wanted to see the light of day. Then
today the battery arrived, along with a set of proper tools, and I could
switch out the battery in about 1 minute flat. One charge later and my
iPod has now been playing for a couple of hours, non-stop.</p>
<p>The moral of the story, you ask? Screw the vendor lock-in, go for the
competitors that make the accessory you need. Don't waste your money,
support the businesses that actually give you what you want without
ripping you off. Oh, and make sure you use proper tools when prying your
precious hardware open.</p>Sorting algorithms: Shell sort2009-11-30T22:14:00+01:00admintag:plind.dk,2009-11-30:sorting-algorithms-shell-sort.html<p>Third in the line of posts on sorting algorithms (first was <a href="http://plind.dk/2009/11/06/sorting-algorithms-bubblesort/">Bubble
Sort</a> and the
second was <a href="https://plind.dk/2009/11/16/sorting-algorithms-insertion-sort/">Insertion
Sort</a>) is
the <a href="https://en.wikipedia.org/wiki/Shell_sort">Shell Sort</a>. This is
basically a variant of the insertion sort, with the difference that
you're sorting the array multiple times. Another way of looking at this
is that the shell sort is really a second level sort, wrapped around the
actual sorting algorithm. The inner algorithm here is the insertion sort
but it could technically be anything, owing to what the shell sort
actually does: running a number of smaller sorts, gradually sorting the
array, before finally going over the whole array.</p>
<p>What makes the shell sort work is that you first divide the array into n
smaller arrays and sort them (technically, you don't divide the array
into smaller pieces, you just operate on sub-parts of it). After this,
you divide this, now half-sorted, array into n/2 arrays, sort each of
these in turn. Iterate till you're working on the whole array. The trick
is that each time you sort one of the sub-parts, this also sorts the
array. Because using insertion sort on an already sorted array
approaches O(n), the final sort of the entire array in shell sort is
much faster than the equivalent insertion sort on an unsorted array.</p>
<h2>The Code</h2>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">ShellSort</span> <span class="k">extends</span> <span class="nx">BaseSort</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">sortFunction</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$count</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">);</span>
<span class="nv">$array</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">;</span>
<span class="nv">$gap</span> <span class="o">=</span> <span class="nb">floor</span><span class="p">(</span><span class="nv">$count</span> <span class="o">/</span> <span class="mi">3</span><span class="p">);</span>
<span class="k">do</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$gap</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$temp</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="nv">$array</span><span class="p">[</span><span class="nv">$i</span><span class="p">]);</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$ii</span> <span class="o">=</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">+</span> <span class="nv">$gap</span><span class="p">),</span> <span class="nv">$a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nv">$ii</span> <span class="o"><</span> <span class="nv">$count</span><span class="p">;</span> <span class="nv">$ii</span> <span class="o">+=</span> <span class="nv">$gap</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$value</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$ii</span><span class="p">];</span>
<span class="nv">$b</span> <span class="o">=</span> <span class="nv">$a</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="nv">$b</span> <span class="o">></span> <span class="mi">0</span> <span class="o">&&</span> <span class="nv">$temp</span><span class="p">[</span><span class="nv">$b</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">></span> <span class="nv">$value</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$temp</span><span class="p">[</span><span class="nv">$b</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$temp</span><span class="p">[</span><span class="nv">$b</span><span class="o">-</span><span class="mi">1</span><span class="p">];</span>
<span class="nv">$b</span><span class="o">--</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$temp</span><span class="p">[</span><span class="nv">$b</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$value</span><span class="p">;</span>
<span class="nv">$a</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$ii</span> <span class="o">=</span> <span class="nv">$i</span><span class="p">,</span> <span class="nv">$a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$ii</span> <span class="o"><</span> <span class="nv">$count</span><span class="p">;</span> <span class="nv">$ii</span> <span class="o">+=</span> <span class="nv">$gap</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$array</span><span class="p">[</span><span class="nv">$ii</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$temp</span><span class="p">[</span><span class="nv">$a</span><span class="p">];</span>
<span class="nv">$a</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">while</span> <span class="p">(</span><span class="nv">$gap</span> <span class="o">=</span> <span class="nb">floor</span><span class="p">(</span><span class="nv">$gap</span> <span class="o">/</span> <span class="mi">2</span><span class="p">));</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">store</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>The difference to the insertion sort should be readily seen: the outer
loop dividing the main array into smaller arrays wraps around an
insertion sort.</p>
<p>A note on the algorithm: the sub-parts are taken from the original array
by picking every n element. This means that when you move an element in
the array, you move it n steps at a time. Compare this to the bubble
sort, where an element is only moved one step at a time, and you can
probably see why this would be a lot faster - even if the inner loop is
the bubble sort.</p>
<p>Some notes on the code: it could obviously do with some proper variable
naming - it's the result of a couple of iterations trying to improve the
efficiency. At first I had opted to run the inner insertion sort inline
on the array but after getting some fairly bad results I opted for using
a temporary array before reinserting the elements into the array to
sort.</p>
<h2>Results</h2>
<p>First, the 1.000 elements:</p>
<div class="highlight"><pre><span></span>1000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.003565 seconds. Memory used: 64464 bytes
Running ShellSort sort. Algorithm took: 0.044342 seconds. Memory used: 64376 bytes
</pre></div>
<p>At 1.000 elements, the shell sort is about nine times faster than
insertion sort. The next test is 10.000 elements.</p>
<div class="highlight"><pre><span></span>10000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.044411 seconds. Memory used: 665912 bytes
Running ShellSort sort. Algorithm took: 0.633066 seconds. Memory used: 665816 bytes
</pre></div>
<p>Still running slower than the built in PHP sort but nothing remotely
close to what could be seen with insertion sort. At 10.000 elements,
shell sort is running 50 times faster than insertion sort and only 15
times slower than the run at 1.000 elements. The insertion and bubble
sorts were so slow that there was no point trying to run them for
100.000 elements but here it makes sense.</p>
<div class="highlight"><pre><span></span>100000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.600952 seconds. Memory used: 6524656 bytes
Running ShellSort sort. Algorithm took: 8.780088 seconds. Memory used: 6524588 bytes
</pre></div>
<p>Again, adding 10 times the elements ups the execution time by 15 times -
nothing like the O(n2) of the insertion sort or bubble sort.</p>
<p>With shell sort, we've reached something that would actually work in a
normal environment. There's of course no reason whatsoever to use it if
the PHP sort function does the job - but if it doesn't here's a
plausible algorithm to use. Not, as I expect to show later, as efficient
as other algorithms, but definitely worth a look.</p>Doors not to open2009-11-23T17:59:00+01:00Peter Lindtag:plind.dk,2009-11-23:doors-not-to-open.html<p>Extending my knowledge on SEO I was getting acquainted with some FireFox
plugins (<a href="https://tools.seobook.com/firefox/seo-for-firefox.html">SEO for
Firefox</a>,
<a href="https://addons.mozilla.org/en-US/firefox/addon/2336">yExplore</a>, and
<a href="https://seopen.com/firefox-extension/">SEOpen</a>) when I happened upon the
related search. Specifically, I did a
<a href="https://www.google.com/search?q=related%3Ahttp://plind.dk/">related:http://plind.dk/</a>
search on Google. The result? Hit #9 is <a href="https://www.metanoia.org/suicide">'Suicide: Read This
First</a>' and #10 is <a href="https://women.timesonline.co.uk/tol/life_and_style/women/families/article4332635.ece">'I had sex with my
brother but I don't feel
guilty</a>'.
I'm not quite sure I understand this ...</p>Screen blues2009-11-20T13:45:00+01:00admintag:plind.dk,2009-11-20:screen-blues.html<p>Because I'm freelancing for my previous employer, telecommuting in
effect, I've become a big fan of screen. I didn't see the benefit of
using the program at first, but when you're logging into a machine with
ssh and then have to log into one or two more, after that, you soon
become very happy about the ability to just open another terminal window
on the machine - no need to ssh again (to be fair, you could of course
use a master connection for your ssh connections and then just hop
through logins with multiple ssh commandlines in one go). Even better,
screen lets you detach from a session, with that session still running.
You can then drop your connection, come back half an hour later,
re-attach to your screen session and find everything as you left it. No
wonder screen is such a loved tool by so many people.</p>
<p>One thing has been bugging me though: one of my favourite keymappings
for vim stopped working after I started using screen. Specifically, I
could no longer get \<S-TAB> to work: shift-tab was gone! I couldn't
see any way to get it to work and I just left it like that - annoyed
that it didn't work but not annoyed enough to really do something about
it. For some strange reason, this is one of those issues that seems to
have affected only me and one other user (could not find any more pages
on Google, just one guy lamenting that the key-combo stopped working).
The alternative is of course that everyone that ever came across this
instantly knew how to fix it. Something I'm obviously not ready to
accept ... though I might have to.</p>
<p>In the end I did manage to find my fix and like so often before, I'm
almost ashamed of not having thought of it before. The problem was down
to screen not knowing which terminal to emulate and so the fix consists
in telling screen to use xterm.</p>
<div class="highlight"><pre><span></span>term xterm-256color
</pre></div>
<p>If you put the above line (assuming you're using the 256 color version
of xterm and not just xterm) into your .screenrc file, screen happily
accepts shift-tab as a key-press combination (it's actually more likely
that the problem lies with how Vim interprets the input from screen. I
tested the output of shift-tab and it was identical for using screen and
not using screen. Vim however had no idea what was going on). If
specifying the terminal in your .screenrc is not an option, you can
specify it on the command line:</p>
<div class="highlight"><pre><span></span>screen -T xterm-256color
</pre></div>
<p>Anyway, I needed to tell the world about this on the off-chance that
someone else finds themselves in the same position. I would have loved
to find the answer after two minutes on google rather than after 1 hour
(there's another gripe: googling for tips/hints/help on screen is nigh
impossible using google, as you won't get any useful results, just stuff
about monitors or tv screens).</p>Sorting algorithms: Insertion Sort2009-11-16T09:31:00+01:00admintag:plind.dk,2009-11-16:sorting-algorithms-insertion-sort.html<p>Second up in the line of sorting algorithms I'll run through is the
<a href="https://en.wikipedia.org/wiki/Insertion_sort" title="Wikipedia article on the topic">insertion sort
algorithm</a>.
It's fairly different from the bubblesort algorithm in that bubblesort
will go through every element that you're trying to sort and compare it
with every other element (meaning, you're looking at about n * n
comparisons) while insertion sort will try to minimize the comparison by
ordering the elements better. Essentially, insertion sort takes one
element at a time and inserts it into another array in it's right place.
For each element, you only compare it with other elements until you find
one that's smaller than the element you're trying to insert.</p>
<p>In terms of steps, this means something like:</p>
<ol>
<li>Set a to value of element to insert.</li>
<li>Set x to index of last element in sorted array.</li>
<li>Compare a to sorted_array[x].</li>
<li>If a is bigger than sorted_array[x], insert a at
sorted_array[x+1]. Done.</li>
<li>If a is smaller than sorted_array[x], shift value at x one place up
in the sorted array and decrement x.</li>
<li>Repeat from 3 till done.</li>
</ol>
<h2>The code</h2>
<p>It's probably easier to figure out what's going on by looking at the
code - it's really rather simple.</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">InsertionSort</span> <span class="k">extends</span> <span class="nx">BaseSort</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">sortFunction</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$count</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">);</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
<span class="nv">$result</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$count</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$value</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$i</span><span class="p">];</span>
<span class="nv">$j</span> <span class="o">=</span> <span class="nv">$i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="nv">$j</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$value</span> <span class="o"><</span> <span class="nv">$result</span><span class="p">[</span><span class="nv">$j</span><span class="p">])</span>
<span class="p">{</span>
<span class="nv">$result</span><span class="p">[</span><span class="nv">$j</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$result</span><span class="p">[</span><span class="nv">$j</span><span class="p">];</span>
<span class="nv">$j</span><span class="o">--</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$result</span><span class="p">[</span><span class="nv">$j</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$value</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">store</span> <span class="o">=</span> <span class="nv">$result</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Note: The code here makes use of the small test framework I created it
<a href="https://plind.dk/2009/11/07/sorting-algorithms-quick-update/">Sorting algorithms: quick
update</a>.</p>
<p>Quick run through: first, some housekeeping. An array for the result is
created, a count of the elements to sort is done and the first element
in the sorted array is inserted (it will always be the same element, so
better initialize it like this). Then the actual sorting takes place,
looping over every element and inserting it into the results array. This
is the more interesting part: the element to sort is compared to the top
of the results array and if it's smaller, the results array then gets
shifted. This keeps on going till a proper place is found for the
element to sort.</p>
<h2>Results</h2>
<p>First, a test with 1.000 elements.</p>
<div class="highlight"><pre><span></span>1000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.003070 seconds. Memory used: 64464 bytes
Running InsertionSort sort. Algorithm took: 0.326779 seconds. Memory used: 64344 bytes
</pre></div>
<p>At 1.000 elements, it's running in 3/5 the time of the bubblesort
algorithm.</p>
<div class="highlight"><pre><span></span>10000 elements to sort.
Sanity check. PHP native sort() algorithm took: 0.039953 seconds. Memory used: 665928 bytes
Running InsertionSort sort. Algorithm took: 32.702462 seconds. Memory used: 665788 bytes
</pre></div>
<p>Scaling up by a factor of 10 results in 100 times longer execution time.
This is as expected: insertion sort should run in O(n^2^). The reason
for this is that while insertion sort minimizes the amount of
comparisons to be done compared with bubblesort, it has exactly the same
problem: for every element you add that needs to be sorted you're also
adding a large number of comparisons to do, even if only half as many as
for bubblesort.</p>Just in time2009-11-11T08:04:00+01:00Peter Lindtag:plind.dk,2009-11-11:just-in-time.html<p><a href="http://mashable.com/2009/11/10/big-brother-uk/">BIG BROTHER: UK to Mandate Personal Data
Storage</a></p>
<p>Seems like Maria and I managed to leave the country just in time. This
is so sick it's impossible to believe, especially coming from the
country of the author that gave us 1984. Seems he ended up being more
right than he thought he would be.</p>Comparing CMS/blog systems, part 2: CMS installation2009-11-08T12:00:00+01:00Peter Lindtag:plind.dk,2009-11-08:comparing-cmsblog-systems-part-2-cms-installation.html<p>After installing three different blog systems (and trying to install a
fourth), it's now time to install five different CMS packages. The five
are:</p>
<ul>
<li><a href="https://www.cmsmadesimple.org/">CMS Made Simple</a></li>
<li><a href="https://www.joomla.org/">Joomla!</a></li>
<li><a href="https://modxcms.com/">modx</a></li>
<li><a href="https://drupal.org/">Drupal</a></li>
<li><a href="https://typo3.org/">Typo3</a></li>
</ul>
<p>The basic goal in this post is to see how easy it is to get these
systems installed and ready for use. What obstacles will you have to
overcome and how confused will you get before you can start using your
CMS? Those are the questions.</p>
<h2>Environment</h2>
<p>The environment for the systems is the same as in <a href="https://plind.dk/2009/10/24/comparing-cmsblog-systems-part-1-blog-installation">the first blog post
in this
series</a>,
but I'll run through it again for good measure. The basic setup is
Apache 2, PHP 5.3 and MySQL 5.0. With PHP 5.3 this series also becomes a
test of how well the packages manage in a more up-to-date environment.
Each package gets a separate bit of space, separate database and user,
plus own VirtualHost. Finally, I'll try to make use of localisations
where possible.</p>
<h2>CMS Made Simple</h2>
<ul>
<li>Version tested: 1.6.6</li>
<li>Requirements:<ul>
<li>PHP 4.3+ up to 5.2.x</li>
<li>ImageMagick/GD support</li>
<li>MySQL 4.1+/PostgreSQL 7+</li>
</ul>
</li>
</ul>
<p>Although the package requirements stated that CMS Made Simple does not
work under PHP 5.3 I figured I'd give it the benefit of the doubt - the
latest package was date October 2009 giving it a theoretical chance -
and try the install. I first downloaded and extracted the files:</p>
<div class="highlight"><pre><span></span>mkdir cms && cd cms
wget https://s3.amazonaws.com/cmsms/downloads/4470/cmsmadesimple-1.6.6-full.tar.gz && tar -zxvvf cmsmadesimple-1.6.6-full.tar.gz
</pre></div>
<p>Note the extra steps above: the package doesn't come in it's own folder,
so unless you're careful, you'll end up with a mess in your www
directory.</p>
<p>After extracting things, I had a read through /doc/README.txt and
/doc/INSTALL.txt. The latter proved to be a very good primer on the
installation process, detailing how to get everything setup. I proceeded
to setup a database and a user for the package and when everything was
ready, aimed FF at the proper place.</p>
<p>Turns out the CMS Made Simple documentation is up to date: the system
won't run on PHP 5.3. I could not get the installation process started
as too many error outputs blocked the session handling. Moral of the
story: don't go for CMS Made Simple if you want to use PHP 5.3.</p>
<ul>
<li>Ease-of-installation: 0-4*/5</li>
<li>Instructions given: 4/5</li>
<li>Confusion level: low*</li>
</ul>
<p>* seeing as I couldn't actually run through the installation process,
it wasn't easy - and the score is 0. However, the part that I did go
through was OK handled and presented little problems. Similarly, the
confusion level was low, up until the installation process crashed.</p>
<h2>Joomla!</h2>
<ul>
<li>Version tested: 1.5.15</li>
<li>Requirements:<ul>
<li>PHP 4.3.10+</li>
<li>MySQL 3.23+</li>
<li>Apache 1.3+</li>
</ul>
</li>
</ul>
<p>The Joomla! site is pretty well done, getting the info you need from it
is easy. A minute after browsing to it I was downloading the source
files while looking at the requirements. The following should work fine:</p>
<div class="highlight"><pre><span></span>mkdir joomla; cd joomla; wget https://joomlacode.org/gf/download/frsrelease/11396/45610/Joomla_1.5.15-Stable-Full_Package.zip -O joomla.zip && unzip joomla.zip
</pre></div>
<p>You should now have the Joomla! sources unzipped in a folder, ready to
get on with the installing. You'll need to setup a database for Joomla!
as well as a user and grant all (not really needed but it's easier than
getting into details) privileges to that user. You'll also need to set
up a virtualhost file for it. And finally, make sure that apache can
write to the files in the folder you installed Joomla! in. After that's
done, pointing your browser at the web root of the install should kick
off the remaining bits of the installation.</p>
<p>There's not much to say about the web-based install other than that
using it is very easy and there's lots of documentation handed to you.
Very well done.</p>
<p>The last step of the installation is removing the installation directory
- if this is not done, the system won't function properly. It's a
typical precaution to take, but one does wonder a bit why this can't be
automated. PHP does have the capability to remove files and folders ...
however, with a simple</p>
<div class="highlight"><pre><span></span>rm -rf installation
</pre></div>
<p>you're through, and you can enjoy your working Joomla! install.</p>
<ul>
<li>Ease-of-installation: 4/5</li>
<li>Instructions given: 4/5</li>
<li>Confusion level: None</li>
</ul>
<h2>MODx</h2>
<ul>
<li>Version tested: 1.0.2</li>
<li>Requirements:<ul>
<li>PHP 4.3.11+</li>
<li>MySQL 4.1.20+ (but not 5.0.51, it seems)</li>
</ul>
</li>
</ul>
<p>MODx looks to be a nice and well-maintained system - latest release was
dated November 4th '09. In general, the usability of the MODx site
deserves a thumbs up: getting the needed info was very easy.</p>
<p>Getting started with the install should amount to</p>
<div class="highlight"><pre><span></span>wget https://modxcms.com/download/ga/modx-1.0.2.tar.gz && tar -zxvvf modx-1.0.2.tar.gz
</pre></div>
<p>However, the otherwise friendly people of MODx for some strange reason
decided to throw obstacles in the way of users, and you have to register
on their site to be able to download the sources of MODx. Fairly
pointless exercise and I'm sure they get tons of useless signups that
never lead to any site-activity.</p>
<p>After unpacking the files, setting up a virtualhost file, creating a
database and database user, and setting permissions on the install
folder of MODx, it's time to point the browser to the install folder.
This will provide a page with a big, fat, red box notifying you that
MODx is not installed and asking you if you want to, perhaps, install
now. Clicking that will take you to the somewhat nicer designed install
guide.</p>
<p>Like with Joomla! the install guide of MODx has been cared for. It's a
breeze to get through it, with some javascript effects to make it even
tastier. There's not quite as much documentation, but you won't really
find yourself in need of it.</p>
<p>At the end of the install guide, there's a nice touch: the option to
automatically remove the install folder. After setting up the few first
bits such as Apache config files and MySQL user/database, you won't need
to get into the dirty details again - MODx takes care of it (well, as
long as you did a proper job the first time round).</p>
<ul>
<li>Ease-of-installation: 4*/5</li>
<li>Instructions given: 3/5</li>
<li>Confusion level: low</li>
</ul>
<p>* while the install was easy, the download wasn't, as noted. Seeing as
the competition doesn't block users from downloading, there's really no
excuse for it.</p>
<h2>Drupal</h2>
<ul>
<li>Version tested: 6.1.4</li>
<li>Requirements:<ul>
<li>PHP 4.3.5+</li>
<li>MySQL 4.1+ / PostgreSQL 7.4+</li>
</ul>
</li>
</ul>
<p>One of the most hyped CMS products, this package is supported by a huge
community. The major advantage to this is the amount of plugins and
modules available for the system. Something to toy with later - but
first, the install.</p>
<p>The Drupal site is slightly overpacked with information. Finding the
requirements list for Drupal 6.1.4 involved clicking through 5-6 ... a
bit much when you're just at the step of wondering if it'll even work
for you (having the requirements or at least a link to them along with a
download link would be a good idea).</p>
<p>After downloading and unpacking</p>
<div class="highlight"><pre><span></span>wget https://ftp.drupal.org/files/projects/drupal-6.14.tar.gz && tar -xzvvf drupal-6.14.tar.gz
</pre></div>
<p>you need to spend some time with the online documentation or the
INSTALL.txt file. Specifically, you'll need to copy the Drupal example
settings file from sites/default/default.settings.php to
sites/default/settings.php. After that, give Apache write permission to
the sites/default/ folder so the install guide will be able to create
what it needs. As always, you also need to set up a database and a
database user with proper rights. Not to mention a virtualhost entry for
Apache. After that, point the browser to the root of the installed
folder.</p>
<p>Unfortunately, unlike what is specified on <a href="https://drupal.org/requirements">the page of
requirements</a> for Drupal, it does NOT
work with PHP 5.3. Well, at least the installer doesn't work - but
seeing as there is no Drupal without installation, that amounts to the
same thing. The problem is the use of the ereg() function - deprecated
in PHP 5.3 along with all other ereg* functions. So, Drupal is a no-go,
unfortunately.</p>
<ul>
<li>Ease-of-installation: 0-3*/5</li>
<li>Instructions given: 3/5</li>
<li>Confusion level: medium</li>
</ul>
<p>* the part of the installation I could go through was ok, though too
manual. Seeing as the installation crashed it's not really possible to
rate it, hence the 0-3</p>
<h2>Typo3</h2>
<ul>
<li>Version tested: 4.2.10</li>
<li>Requirements:<ul>
<li>PHP ?</li>
<li>MySQL ? / others ?</li>
</ul>
</li>
</ul>
<p>While it's easy to find the download link on typo3.org, it's seemingly
impossible to find a page on requirements. So, it's very hard to say if
the installation will prove to be a waste of time or not. Only one way
to find out - trial and error.</p>
<p>So first thing is grabbing the files and unpacking them. Typo3 has an
unusual setup - you need to download a skeleton site as well as the
sources. You can get both in one packaged file though, so it's not too
bad.</p>
<div class="highlight"><pre><span></span>wget https://downloads.sourceforge.net/project/typo3/TYPO3%20Source%20and%20Dummy/TYPO3%204.2.10/typo3_src%2Bdummy-4.2.10.zip?use_mirror=dfn -O typo3.zip && unzip typo3.zip
</pre></div>
<p>You should now have the files needed to setup a Typo3 site, and while
the site doesn't offer you helpful info straightaway, the INSTALL.txt
and README.txt have lots of good stuff. Among other things, they go
through all the steps of installing Apache, PHP and MySQL ... so, all
the way from scratch. Luckily, I don't need to do that - I just need to
set up a database and a user for Typo3, set the proper permissions on
the unpacked files (specifically allowing for writing to /typo3temp,
/typo3/temp, /typo3conf/localconf.php) and then add the virtualhost
record. After that, the install should work as per the typical web
install guide.</p>
<p>And so after these steps, it's time to point the browser to the
installed CMS. The first thing to greet you when you do is a big JS
alert box, notifying you that you really should change the default
password. Obviously good to know, but this is not really the way
information like this should be presented to the user. The next pages
are better done and quite simple in their presentation which is good -
it's the typical setup pages asking for database connection details. You
don't want any unnecessary info here, confusing the user.</p>
<p>At the end of the web install guide things do get more confusing,
though. Specifically, the user is told that a given install folder is a
big security problem. You're then presented with three options for
fixing this problem ... none of which involve clicking a link and seeing
it get done. In fact, the install guide recommends that you carry on
setting up the Typo3 installation instead of tending to the security
threat. That's not proper handling of priorities - either don't tell me
about the problem now or make me fix it straight away.</p>
<p>Choosing to carry on with the installation I'm then greeted with a
fairly confusing install tool, the purpose of which is hard to guess. Is
it used for reinstalls? For configuring advanced stuff? For letting me
know what I chose? Apparently all of the above. Sensing the confusion
level rising I then opted to have a look at the installed site. The
frontpage greeted me with PHP warnings and a Typo3 error to the effect
that no pages on rootlevel could be found - regardless of the install of
skeleton site. Looking at the admin pages, things didn't get much
better. Tons of PHP warnings make it pretty much unusable - you either
have to switch off PHP warnings or get to work with search and replace
taking out ereg* calls. Not a good start for the Typo3 install, but at
least it didn't crash completely - there's a possibility it could work.</p>
<ul>
<li>Ease-of-installation: 3/5</li>
<li>Instructions given: 4/5</li>
<li>Confusion level: medium</li>
</ul>
<h2>Conclusion</h2>
<p>Three out of five CMS systems aren't ready yet for the latest stable
version of PHP. I find that rather surprising actually - PHP 5.3 has
been on it's way for some time, there's been plenty of time to weed out
the problematic parts. My best guess is that the forces behind these
projects figure that web-hosts probably won't upgrade to 5.3 for a while
and thus the majority of users won't see the problems. It's fair
reasoning but still a bad excuse - most deprecated functions can be
fixed in very little time.</p>
<p>Overall, the web guided installations have really come far and most of
these products treat the user to a nice and only slightly confusion
ride. Some could still do with some polishing but overall it's not that
hard anymore installing your own CMS. The biggest annoyance comes from
the need to remove various files or copy them round during installation:
why is this a demand? Why don't the installers check if the needed
permissions are there? With the installers being so user-friendly it's
just strange that they're not taken the last step.</p>Sorting algorithms: quick update2009-11-07T17:53:00+01:00admintag:plind.dk,2009-11-07:sorting-algorithms-quick-update.html<p>In <a href="http://plind.dk/2009/11/06/sorting-algorithms-bubblesort/">Sorting algorithms:
bubblesort</a> I
started working on implementations of different sorting algorithms. The
first obviously being bubble sort. I hadn't worked up a proper framework
for the implementations though, so the implementation was just a flat
file - not great for reusing code. Seeing as I plan to do about 10 or
more algorithms I thought it might be best to get the framework done
before I work on other algorithms. So here's the code for it.</p>
<h2>Base code</h2>
<p>The base code includes stuff for setting the test up and outputting the
result. It also includes the base sort class which sort implementations
will extend.</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">]))</span>
<span class="p">{</span>
<span class="nv">$sortclass</span> <span class="o">=</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">];</span>
<span class="nv">$sortfile</span> <span class="o">=</span> <span class="nb">strtolower</span><span class="p">(</span><span class="nv">$sortclass</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_file</span><span class="p">(</span><span class="nv">$sortfile</span> <span class="o">.</span> <span class="s2">".php"</span><span class="p">))</span>
<span class="p">{</span>
<span class="k">die</span> <span class="p">(</span><span class="s2">"Could not find </span><span class="si">{</span><span class="nv">$sortfile</span><span class="si">}</span><span class="s2">.php, quitting</span><span class="se">\n</span><span class="s2">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">require_once</span> <span class="s2">"</span><span class="si">{</span><span class="nv">$sortfile</span><span class="si">}</span><span class="s2">.php"</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"No comparison done, running just native PHP sort()</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$rand</span> <span class="o">=</span> <span class="nb">file_get_contents</span><span class="p">(</span><span class="s1">'rand.txt'</span><span class="p">);</span>
<span class="nv">$random_array</span> <span class="o">=</span> <span class="nb">explode</span><span class="p">(</span><span class="s1">','</span><span class="p">,</span> <span class="nv">$rand</span><span class="p">);</span>
<span class="k">echo</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$random_array</span><span class="p">)</span> <span class="o">.</span> <span class="s2">" elements to sort.</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="nv">$a</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">BaseSort</span><span class="p">(</span><span class="nv">$random_array</span><span class="p">);</span>
<span class="nv">$a</span><span class="o">-></span><span class="na">runSort</span><span class="p">();</span>
<span class="nb">printf</span> <span class="p">(</span><span class="s2">"Sanity check. PHP native sort() algorithm took: %f seconds. Memory used: %d bytes</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nv">$a</span><span class="o">-></span><span class="na">timeTaken</span><span class="p">(),</span> <span class="nv">$a</span><span class="o">-></span><span class="na">memUsed</span><span class="p">());</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">class_exists</span><span class="p">(</span><span class="nv">$sortclass</span><span class="p">))</span>
<span class="p">{</span>
<span class="nv">$b</span> <span class="o">=</span> <span class="k">new</span> <span class="nv">$sortclass</span><span class="p">(</span><span class="nv">$random_array</span><span class="p">);</span>
<span class="nv">$b</span><span class="o">-></span><span class="na">runSort</span><span class="p">();</span>
<span class="nb">printf</span> <span class="p">(</span><span class="s2">"Running </span><span class="si">{</span><span class="nv">$sortclass</span><span class="si">}</span><span class="s2"> sort. Algorithm took: %f seconds. Memory used: %d bytes</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nv">$b</span><span class="o">-></span><span class="na">timeTaken</span><span class="p">(),</span> <span class="nv">$b</span><span class="o">-></span><span class="na">memUsed</span><span class="p">());</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$b</span><span class="o">-></span><span class="na">validateResult</span><span class="p">())</span>
<span class="p">{</span>
<span class="k">echo</span> <span class="s2">"</span><span class="si">{</span><span class="nv">$sortclass</span><span class="si">}</span><span class="s2"> sort implementation failed.</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">class</span> <span class="nc">BaseSort</span>
<span class="p">{</span>
<span class="k">protected</span> <span class="nv">$store</span><span class="p">;</span>
<span class="k">private</span> <span class="nv">$_start_time</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">private</span> <span class="nv">$_end_time</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">private</span> <span class="nv">$_start_memuse</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">private</span> <span class="nv">$_end_memuse</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">__construct</span><span class="p">(</span><span class="k">array</span> <span class="nv">$array</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">store</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">;</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * returns the amount of time used by the sort function</span>
<span class="sd"> *</span>
<span class="sd"> * @access public</span>
<span class="sd"> * @return float</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">timeTaken</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_end_time</span> <span class="o">-</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_start_time</span><span class="p">;</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * returns the amount of memory used by the sort function</span>
<span class="sd"> *</span>
<span class="sd"> * @access public</span>
<span class="sd"> * @return float</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">memUsed</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_end_memuse</span> <span class="o">-</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_start_memuse</span><span class="p">;</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * runs the sort function</span>
<span class="sd"> *</span>
<span class="sd"> * @access public</span>
<span class="sd"> * @return void</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">runSort</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_start_memuse</span> <span class="o">=</span> <span class="nb">memory_get_usage</span><span class="p">();</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_start_time</span> <span class="o">=</span> <span class="nb">microtime</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">sortFunction</span><span class="p">();</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_end_time</span> <span class="o">=</span> <span class="nb">microtime</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_end_memuse</span> <span class="o">=</span> <span class="nb">memory_get_usage</span><span class="p">();</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * the actual sort function to use</span>
<span class="sd"> *</span>
<span class="sd"> * @access public</span>
<span class="sd"> * @return void</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">sortFunction</span><span class="p">()</span>
<span class="p">{</span>
<span class="nb">sort</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">);</span>
<span class="p">}</span>
<span class="sd">/**</span>
<span class="sd"> * validates the result</span>
<span class="sd"> *</span>
<span class="sd"> * @access public</span>
<span class="sd"> * @return bool</span>
<span class="sd"> */</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">validateResult</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span> <span class="o">></span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">[</span><span class="nv">$i</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2>Subclasses</h2>
<p>The base sort class is then to be extended by sorting algorithm
implementations in the following manner.</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">BubbleSort</span> <span class="k">extends</span> <span class="nx">BaseSort</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">sortFunction</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$count</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">);</span>
<span class="nv">$array</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">store</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$ii</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$ii</span> <span class="o"><</span> <span class="nv">$count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nv">$ii</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$array</span><span class="p">[</span><span class="nv">$ii</span><span class="p">]</span> <span class="o">></span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$ii</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span>
<span class="p">{</span>
<span class="nv">$temp</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$ii</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="nv">$ii</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">[</span><span class="nv">$ii</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="nv">$ii</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$temp</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">store</span> <span class="o">=</span> <span class="nv">$array</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Sorting an array using this then comes down to instantiating a subclass
and doing \$sort->runSort(). The implementation could have been using
composition rather than subclassing (using the <a href="https://en.wikipedia.org/wiki/Strategy_pattern">strategy design
pattern</a>) but for this
exercise there's no reason for that.</p>Sorting algorithms: bubblesort2009-11-06T16:24:00+01:00admintag:plind.dk,2009-11-06:sorting-algorithms-bubblesort.html<p>I decided recently that it was time to add to my knowledge of sorting
algorithms. I didn't just want to read some quick descriptions on names
of algorithms but to actually prod my knowledge-muscle with some stuff.
So, I started out on Wikipedia, reading <a href="https://en.wikipedia.org/wiki/Sorting_algorithm">the article on sorting
algorithms</a> while taking
notes. It gives a good overview of things and has tons of stuff leading
off to other articles and the web. The next step was getting more into
details with the various algorithms, to get some idea of how they really
work and what makes them efficient or not. Again, I mainly used
Wikipedia for this, although for a few of the articles I found it rather
helpful to google around.</p>
<p>The last part of the exercise, and the most fun, has now come: implement
the algorithms and test them :)</p>
<h2>The setup</h2>
<p>I don't want this to be a massively complicated affair - the idea is to
get some hands-on with a number of sorting algorithms. Hence, I'm
limiting myself to working on an array of (pseudo)random integers in the
range of 1 - 1.000. The array will be of varying length to determine
efficiency in scaling - however, to get some sort of fair test the array
will be the same for the different algorithms (well, it will in the end
when I've done all of them).</p>
<p>The first step was then to build a random-array generator. Not exactly
rocket-science, but still needs to be done. To be able reuse the array
of random numbers, I opted to write it out to a file, so it would be
easier to run separate tests on it. Here's the code I ended with:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="x">#!/usr/bin/php</span>
<span class="cp"><?php</span>
<span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">])</span> <span class="o">||</span> <span class="o">!</span><span class="p">(</span><span class="nv">$count</span> <span class="o">=</span> <span class="nb">intval</span><span class="p">(</span><span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'argv'</span><span class="p">][</span><span class="mi">1</span><span class="p">])))</span>
<span class="p">{</span>
<span class="k">die</span><span class="p">(</span><span class="s2">"Input value is zero, need a proper number of random numbers to generate.</span><span class="se">\n</span><span class="s2">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$fh</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="s2">"rand.txt"</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">);</span>
<span class="nv">$first</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
<span class="nv">$write</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$count</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$first</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$write</span> <span class="o">.=</span> <span class="s2">","</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$write</span> <span class="o">.=</span> <span class="nb">mt_rand</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1000</span><span class="p">);</span>
<span class="nv">$first</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">%</span> <span class="mi">100</span><span class="p">)</span>
<span class="p">{</span>
<span class="nb">fwrite</span><span class="p">(</span><span class="nv">$fh</span><span class="p">,</span> <span class="nv">$write</span><span class="p">);</span>
<span class="nv">$write</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$write</span> <span class="o">!=</span> <span class="s1">''</span><span class="p">)</span>
<span class="p">{</span>
<span class="nb">fwrite</span><span class="p">(</span><span class="nv">$fh</span><span class="p">,</span> <span class="nv">$write</span><span class="p">);</span>
<span class="p">}</span>
<span class="nb">fclose</span><span class="p">(</span><span class="nv">$fh</span><span class="p">);</span>
</pre></div>
</td></tr></table>
<p>This will happily chug out random numbers separated with a comma. This
can then be read from the file and explode()d - and you've got an array
of random integers.</p>
<h2>Bubblesort</h2>
<p>This algorithm is one of the most basic sorts there is, taught to
beginners so they can grasp the basic ideas of algorithms and sorting.
In terms of efficiency it's one of the worst sorting algorithms one can
find. Hence, a good starting point :)</p>
<p>Here's the code I came up with. It ain't pretty but it does the job.</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="nv">$rand</span> <span class="o">=</span> <span class="nb">file_get_contents</span><span class="p">(</span><span class="s1">'rand.txt'</span><span class="p">);</span>
<span class="nv">$random_array</span> <span class="o">=</span> <span class="nb">explode</span><span class="p">(</span><span class="s1">','</span><span class="p">,</span> <span class="nv">$rand</span><span class="p">);</span>
<span class="nv">$random_array_copy</span> <span class="o">=</span> <span class="nv">$random_array</span><span class="p">;</span>
<span class="nv">$start_time</span> <span class="o">=</span> <span class="nb">microtime</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="nb">sort</span><span class="p">(</span><span class="nv">$random_array_copy</span><span class="p">);</span>
<span class="nv">$end_time</span> <span class="o">=</span> <span class="nb">microtime</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="k">echo</span> <span class="s2">"Sanity check: PHP native sort() algorithm took: "</span> <span class="o">.</span> <span class="p">(</span><span class="nv">$end_time</span> <span class="o">-</span> <span class="nv">$start_time</span><span class="p">)</span> <span class="o">.</span> <span class="s2">" seconds</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="nv">$start_time</span> <span class="o">=</span> <span class="nb">microtime</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="nv">$count</span> <span class="o">=</span> <span class="nb">count</span><span class="p">(</span><span class="nv">$random_array</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$ii</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$ii</span> <span class="o"><</span> <span class="nv">$count</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="nv">$ii</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$random_array</span><span class="p">[</span><span class="nv">$ii</span><span class="p">]</span> <span class="o">></span> <span class="nv">$random_array</span><span class="p">[</span><span class="nv">$ii</span><span class="o">+</span><span class="mi">1</span><span class="p">])</span>
<span class="p">{</span>
<span class="nv">$temp</span> <span class="o">=</span> <span class="nv">$random_array</span><span class="p">[</span><span class="nv">$ii</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
<span class="nv">$random_array</span><span class="p">[</span><span class="nv">$ii</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$random_array</span><span class="p">[</span><span class="nv">$ii</span><span class="p">];</span>
<span class="nv">$random_array</span><span class="p">[</span><span class="nv">$ii</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$temp</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nv">$end_time</span> <span class="o">=</span> <span class="nb">microtime</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="k">echo</span> <span class="s2">"Bubble sort of </span><span class="si">${</span><span class="nv">$count</span><span class="si">}</span><span class="s2"> elements took: "</span> <span class="o">.</span> <span class="p">(</span><span class="nv">$end_time</span> <span class="o">-</span> <span class="nv">$start_time</span><span class="p">)</span> <span class="o">.</span> <span class="s2">" seconds</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nv">$count</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$random_array</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span> <span class="o">!=</span> <span class="nv">$random_array_copy</span><span class="p">[</span><span class="nv">$i</span><span class="p">])</span>
<span class="p">{</span>
<span class="k">die</span><span class="p">(</span><span class="s2">"Bubble sort failed"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Before I do the next algorithm, I need to get the framework code out of
this - there's no point to copypaste stuff in your files. It works
though, which was the basic requirement for this to be a fun experiment
:)</p>
<h2>Results</h2>
<p>Testing the implementation gave the following results:</p>
<div class="highlight"><pre><span></span>Sanity check: PHP native sort() algorithm took: 0.00237798690796 seconds
Bubble sort of 1000 elements took: 0.577649116516 seconds
</pre></div>
<p>For 1.000 elements it's more than 20 times slower than the PHP sort()
function.</p>
<div class="highlight"><pre><span></span>Sanity check: PHP native sort() algorithm took: 0.0431900024414 seconds
Bubble sort of 10000 elements took: 59.5861978531 seconds
</pre></div>
<p>Scaling up by a factor of 10 changes the result drastically - the time
used is 100 times bigger than for 1.000 elements. In comparison, the PHP
sort() function only doubled it's run time.</p>
<p>So yeah, there you have it. Bubblesort running in O(n^2^). Definitely
not something to use, though.</p>
<p>Note: the reason behind comparing with PHP's built-in sort() function is
mainly to keep the general focus. Don't ever use a custom built sort
function in PHP if the built-in will do the trick. The bubblesort used
here is 1379 times slower than the PHP sort() function ...</p>What lobbying gets you ...2009-11-04T12:25:00+01:00admintag:plind.dk,2009-11-04:what-lobbying-gets-you.html<p>If businesses insist on lobbying, the logical conclusion is to exclude
businesses completely from any political decision-making, in any form.</p>
<p>If we accept lobbying, <a href="https://arstechnica.com/open-source/news/2009/11/eu-waffles-on-open-standards-in-interoperability-guideline.ars?utm_source=rss&utm_medium=rss&utm_campaign=rss">the results will be citizens getting screwed
over</a>,
again and again.</p>
<p>A quote from <a href="https://www.bigwobber.nl/wp-content/uploads/2009/11/European-Interoperability-Framework-for-European-Public-Services-draft.pdf">the
paper</a>:</p>
<p>While there is a correlation between openness and interoperability, it
is also true that interoperability can be obtained without openness, for
example via homogeneity of the ICT systems, which implies that all
partners use, or agree to use, the same solution to implement a European
Public Service ...</p>
<p>Read: if we all just use Microsoft Word, then that's great.</p>
<p>Generally, I'm all for the EU, but sometimes, someone manages to do
something so unbelievably stupid, that the idea of the EU gets lost.
This is one of those times.</p>PHP switch script2009-10-31T15:27:00+01:00admintag:plind.dk,2009-10-31:php-switch-script.html<p>As promised in <a href="../2009/10/30/php-5-2-x-and-5-3-x-side-by-side/" title="Permanent Link to PHP 5.2.x and 5.3.x side by side">PHP 5.2.x and 5.3.x side by
side</a>here's
a script that switches not just the php module for Apache but also the
command line version</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="c1"># This script relies on you having two copies of libphp5 in /usr/lib/apache2/modules/</span>
<span class="c1"># - libphp52.so and libphp53.so</span>
<span class="c1"># as well as two copies of php in /usr/bin/</span>
<span class="c1"># - php52 and php53</span>
<span class="c1"># it will try to determine current version by checking the mentioned files and then</span>
<span class="c1"># switch to the other</span>
<span class="nv">currentlib</span><span class="o">=</span><span class="sb">`</span>ls -la /usr/lib/apache2/modules/libphp5.so <span class="p">|</span> awk <span class="s1">'{print $10}'</span> <span class="p">|</span> awk -F <span class="s1">'/'</span> <span class="s1">'{print $6}'</span> <span class="p">|</span> sed <span class="s1">'s/[a-z\.\/]//g'</span><span class="sb">`</span>
<span class="nv">currentbin</span><span class="o">=</span><span class="sb">`</span>ls -la /usr/bin/php <span class="p">|</span> awk <span class="s1">'{print $10}'</span> <span class="p">|</span> sed <span class="s1">'s/[a-z\.\/]//g'</span><span class="sb">`</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$currentlib</span><span class="s2">"</span> !<span class="o">=</span> <span class="s2">"</span><span class="nv">$currentbin</span><span class="s2">"</span> <span class="o">]</span>
<span class="k">then</span>
echo <span class="s2">"PHP bin and lib are not in sync"</span>
exit 1
<span class="k">fi</span>
<span class="k">if</span> <span class="o">[</span> ! -h <span class="s2">"/usr/lib/apache2/modules/libphp5.so"</span> -o ! -h <span class="s2">"/usr/bin/php"</span> <span class="o">]</span>
<span class="k">then</span>
echo <span class="s2">"libphp5.so or /usr/bin/php is NOT a symbolic link as it should be"</span>
exit 1
<span class="k">fi</span>
<span class="k">if</span> ! <span class="sb">`</span>rm /usr/lib/apache2/modules/libphp5.so /usr/bin/php<span class="sb">`</span>
<span class="k">then</span>
<span class="nb">echo</span> <span class="s2">"Could not delete old libphp5.so or php bin link"</span>
exit 1
<span class="k">fi</span>
<span class="k">case</span> <span class="nv">$currentlib</span> in
<span class="s2">"53"</span><span class="o">)</span>
ln -s /usr/lib/apache2/modules/libphp52.so /usr/lib/apache2/modules/libphp5.so
/etc/init.d/apache2 restart
ln -s /usr/bin/php52 /usr/bin/php
<span class="nb">echo</span> <span class="s2">"Switched to PHP 5.2"</span><span class="p">;;</span>
<span class="s2">"52"</span><span class="o">)</span>
ln -s /usr/lib/apache2/modules/libphp53.so /usr/lib/apache2/modules/libphp5.so
/etc/init.d/apache2 restart
ln -s /usr/bin/php53 /usr/bin/php
<span class="nb">echo</span> <span class="s2">"Switched to PHP 5.3"</span><span class="p">;;</span>
*<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"Something went wrong, current file doesn't match what it should: </span><span class="nv">$currentlib</span><span class="s2">"</span>
<span class="nb">exit</span> 1<span class="p">;;</span>
<span class="k">esac</span>
<span class="nb">exit</span> 0
</pre></div>
</td></tr></table>
<p>As the note says in the script, you need a setup with multiple version
of libphp5x and php5x in the /usr/lib/apache2/module/ and /usr/bin/
folders. The script will do SOME sanity checking before switching but
there's obviously still the potential for strange things happening.</p>PHP 5.2.x and 5.3.x side by side2009-10-30T14:33:00+01:00admintag:plind.dk,2009-10-30:php-5-2-x-and-5-3-x-side-by-side.html<p>A while ago I ripped out my PHP 5.2 installation (nicely handled by
apt-get) in favour of manually installing 5.3 so I could play around.
Yesterday I then installed Ubuntu 9.10 (<a href="https://www.ubuntu.com/">info and download at
ubuntu.com</a>) and after quite a lot of installing
I suddenly found myself without a working development environment. The
cause was an upgrade to mysql that upped the library number from 15 to
16 - and seeing as I had manually configured and compiled php it was
compiled against version 15 of the library.</p>
<p>After some cursing and swearing I then removed my 5.3 install of php and
let aptitude do it's work installing tons of php stuff for 5.2. And once
again, I had a working dev environment ... but without the niceness of
5.3. Seeing as all work and no play makes Jack a dull boy, something had
to be done, and I settled on installing 5.2 and 5.3 on the same box and
switching between them as needed. That should let me play with 5.3 while
always having a working backup in 5.2 when things get upgraded/updated.
The prospect of installing php 5.3 wasn't nice though, as it had taken
me some time first round and obviously resulted in trashing my install
(with aptitude swearing I didn't need about 100 packages here and
there). This time it had to work better.</p>
<p>First, I googled installing php 5.3. <a href="https://www.brandonsavage.net/installing-php-5-3-on-ubuntu/">Brandon Savage's post on the
topic</a> came
up and I figured it might do the job, which, with some minor changes,
was indeed the case. The main thing to do different than Brandon is
follow the advice about using the --prefix option. After that, you just
need to add some shell scripting to get you going.</p>
<p>In details, here's the plan:</p>
<ol>
<li>Install PHP in the latest version from apt-get (5.2.10 when writing
this) and apache/mysql if you need them.</li>
<li>Install the other dependencies listed on Brandons blog, if you don't
have them already</li>
<li>Check to make sure you've got a working PHP 5.2 install, if not, you
need to fix that before going further.</li>
<li>
<p>Make a backup of libphp5.so (this is the 5.2 version, which you'll
need for switching)</p>
<ol>
<li>Easiest is just copying it with sudo cp
/usr/lib/apache2/modules/libphp5.so
/usr/lib/apache2/modules/libphp52.so.orig</li>
</ol>
</li>
<li>
<p>Follow Brandons blog on installing 5.3 up to the point of
configuring it. Then make sure you add an alternative directory for
your 5.3 installation (say, /opt/php5.3 or so).</p>
</li>
<li>
<p>Go through with the installation till you're done with make -i
install. Then head into /usr/lib/apache2/modules/ and backup
libphp5.so again (it's now version 5.3).</p>
<ol>
<li>This time, following the scheme from above, copy it to
libphp53.so.orig.</li>
</ol>
</li>
<li>
<p>Copy the backup files to libphp52.so and libphp53.so respectively.
Don't move them, copy them (otherwise there's a fair chance they'll
be overwritten on subsequent PHP updates).</p>
</li>
<li>Remove libphp5.so from /usr/lib/apache2/modules/ and make a symbolic
link in that directory to either libphp52.so or libphp53.so
depending on what you want.</li>
<li>You're almost done, just need a way of switching back and forth. To
achieve that, you can use the script below.</li>
</ol>
<p>Copy the following script and paste it into a file. Then chmod the file
to 0755 so you can execute it. Make sure you either stick file in your
include path or make note of where it goes.</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="ch">#!/bin/bash</span>
<span class="nv">current</span><span class="o">=</span><span class="sb">`</span>ls -la /usr/lib/apache2/modules/libphp5.so <span class="p">|</span> awk <span class="s1">'{print $10}'</span><span class="sb">`</span>
<span class="k">if</span> <span class="o">[</span> ! -h <span class="s2">"/usr/lib/apache2/modules/libphp5.so"</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nb">echo</span> <span class="s2">"libphp5.so is NOT a symbolic link as it should be"</span>
<span class="nb">exit</span> 1
<span class="k">fi</span>
rm /usr/lib/apache2/modules/libphp5.so
<span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> -ne <span class="m">0</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nb">echo</span> <span class="s2">"Could not delete old link"</span>
<span class="nb">exit</span> 1
<span class="k">fi</span>
<span class="nb">cd</span> /usr/lib/apache2/modules
<span class="k">case</span> <span class="nv">$current</span> in
<span class="s2">"libphp53.so"</span><span class="o">)</span>
ln -s libphp52.so libphp5.so
/etc/init.d/apache2 restart
<span class="nb">echo</span> <span class="s2">"Switched to PHP 5.2"</span><span class="p">;;</span>
<span class="s2">"libphp52.so"</span><span class="o">)</span>
ln -s libphp53.so libphp5.so
/etc/init.d/apache2 restart
<span class="nb">echo</span> <span class="s2">"Switched to PHP 5.3"</span><span class="p">;;</span>
*<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"Something went wrong, current file doesn't match what it should: </span><span class="nv">$current</span><span class="s2">"</span>
<span class="nb">exit</span> 1<span class="p">;;</span>
<span class="k">esac</span>
<span class="nb">exit</span> 0
</pre></div>
</td></tr></table>
<p>This file will switch between PHP versions for your apache install and
will restart the apache server so the change is instant. You need to run
the file with sudo, as otherwise you won't be able to remove the
symbolic link or create the new one.</p>
<p>In much the same way, you'll be able to switch the CLI version of PHP.
I'll post an updated version of the switch script in a followup post at
some point.</p>
<p>One last thing: check that things are working by loading a phpinfo()
output page in your server. You should see the difference in the version
reported (5.2.10 vs. 5.3).</p>
<p><strong>Edit:</strong> There's a better version of the script in <a href="https://plind.dk/2009/10/31/php-switch-script/">PHP switch
script</a> - it switches
both CLI and Apache module version.</p>Comparing CMS/blog systems, part 1: blog installation2009-10-24T12:13:00+02:00Peter Lindtag:plind.dk,2009-10-24:comparing-cmsblog-systems-part-1-blog-installation.html<p>In order to get to know more about the various open source CMS and blog
systems out there, I've decided to fill up some hard disk space by
installing a bunch of them and comparing them as I go along. I'll be
detailing all my progress here with notes thrown in left and right. The
basic setup of this small series of blog posts will be:</p>
<ul>
<li>Installation</li>
<li>Day to day tasks</li>
<li>Going beyond basics</li>
<li>Hacking it</li>
</ul>
<p>The series may turn out longer or shorter, but that's the opening
strategy. Now onto the players (I got inspiration for the list by
checking out <a href="https://php.opensourcecms.com/">OpenSourceCMS</a> - very nice
site):</p>
<ul>
<li>
<p>Blogs</p>
<ul>
<li><a href="https://textpattern.com/">Textpattern</a></li>
<li><a href="https://www.nucleuscms.org/">Nucleus</a></li>
<li><a href="https://www.s9y.org/">Serendipity</a></li>
<li><a href="https://wordpress.org/">WordPress</a></li>
</ul>
</li>
<li>
<p>CMS systems</p>
<ul>
<li><a href="https://www.cmsmadesimple.org/">CMS Made Simple</a></li>
<li><a href="https://www.joomla.org/">Joomla!</a></li>
<li><a href="https://modxcms.com/">modx</a></li>
<li><a href="https://drupal.org/">Drupal</a></li>
<li><a href="https://typo3.org/">Typo3</a></li>
</ul>
</li>
</ul>
<h2>Environment</h2>
<p>Environment is Apache 2, PHP 5.3 and MySQL 5.0. Given the use of PHP 5.3
this is, in part, also a test of how up to date the software packages
are. Apart from PHP it won't be a test of how the software performs in
an up to date environment, though.</p>
<p>Each of the packages will be setup in it's own space on my local machine
(running ubuntu on an XPS m1330) and each will get it's own virtualhost
in Apache, to make things easy as well as fair/square. Something to the
effect of:</p>
<div class="highlight"><pre><span></span><span class="nt"><VirtualHost</span> <span class="err">*</span><span class="nt">></span>
ServerName subject
DocumentRoot /var/www/subject
ErrorLog /var/log/apache2/subject.log
<span class="nt"><Directory</span> <span class="err">/var/www/subject</span><span class="nt">/></span>
Options FollowSymlinks MultiViews
AllowOverride all
Order deny,allow
Deny from all
Allow from 127.0.0.1
<span class="nt"></Directory></span>
<span class="nt"></VirtualHost></span>
</pre></div>
<p>Each will also get it's own database with access from own user,
amounting to something like:</p>
<div class="highlight"><pre><span></span>CREATE DATABASE subject DEFAULT CHARACTER SET utf8;
CREATE USER 'subject'@'localhost' IDENTIFIED BY 'subject';
GRANT ALL ON subject.* TO 'subject'@'localhost';
</pre></div>
<p>If present in the product, I'll make use of localisations, specifically
Danish (mainly to test it, as I prefer English).</p>
<p>Todays subject is blog installation, so, the first four on the above
list. In the next post I'll be looking at installing the CMS systems.</p>
<h2>Textpattern</h2>
<ul>
<li>Version tested: 4.2.0</li>
<li>Requirements listed:<ul>
<li>PHP 4.3+</li>
<li>MySQL 3.23+</li>
<li>mysql + xml extensions for PHP</li>
</ul>
</li>
</ul>
<p>I started by <a href="https://textpattern.com/download">downloading the source files from the textpattern
site</a> and unpacking them using</p>
<div class="highlight"><pre><span></span>wget https://textpattern.com/file_download/56/textpattern-4.2.0.tar.gz && tar -xzvvf textpattern-4.2.0.tar.gz
</pre></div>
<p>It's worth noting here that it's also possible to download the source
files using SVN which can come in very handy when updating core files.
There's a slight difference between the SVN files and the latest package
but mainly in terms of file hiearchy.</p>
<p>Onwards with the installation, I had a look at the accompanying
README.txt file which was fairly short. 1) drop files in web root or
somewhere else, 2) create a database and 3) load /textpattern/setup/ in
a browser. The shortness of instructions either means that things are
easy to do or that only little time was spent writing them up. To figure
out which I created a 'textpattern' user and database, set up the Apache
VirtualHost and then pointed FF to
https://textpattern/textpattern/setup/. Which worked without further ado,
even let me select my very own localised language.</p>
<p>However, having selected a non-English language, I got a slight
let-down: nothing was translated into my language, instead I just got
raw string keys like 'welcome_to_textpattern'. That's not overly
impressive - I can understand some words not being translated in the
main product, but nothing on the first page you click into in the
installer? After entering details on the MySQL connection, I got another
disappointment: I was shown a textarea with config details I should copy
and paste into a config file. Probably due to the localisation problems
the page didn't actually let me know what file to use but did show me
that I needed to put it in /textpattern/.</p>
<div class="highlight"><pre><span></span>vim /var/www/textpattern/textpattern/config.php
</pre></div>
<p>Two things worth noting here: PHP has no problem writing data to a file
and while copypasting isn't rocketscience it doesn't take many extra
tabs from a browser to screw up a file, especially when the best
practice of not using end PHP tags is not followed.</p>
<p>The third step consists of setting up an admin user and here things are
nicer. No double email to validate things, no hidden password. Good
good. This appeared to be the last step in the process as I was told
index.php would be accessible after this, but unfortunately I wasn't so
lucky: the first thing to greet me from https://textpattern/index.php was
a big fat error message in xdebug orange to the effect that some columns
did not exist although the code assumed they did. This went away after a
page refresh though, but in the meantime I also managed to log into a
non-functioning admin area - both experiences very confusing for a new
user and fairly unnecessary. After that, textpattern seemed to work ok
with no further work needed.</p>
<ul>
<li>Ease-of-installation: 4/5</li>
<li>Instructions given: 2/5</li>
<li>Confusion level: Medium to High</li>
</ul>
<h2>Nucleus CMS</h2>
<ul>
<li>Version tested: 3.50</li>
<li>Requirements:<ul>
<li>PHP 5.0.6+</li>
<li>MySQL 3.23+</li>
</ul>
</li>
</ul>
<p>First, grab source files and unpack. In this case, that includes
grabbing the extra language files, which translates to</p>
<div class="highlight"><pre><span></span>wget https://downloads.sourceforge.net/project/nucleuscms/1.%20Nucleus%20Core/Nucleus%20v3.50/nucleus3.50.zip?use_mirror=dfn && unzip nucleus3.50.zip
cd nuclues3.50/nucleus/language/ && wget https://prdownloads.sourceforge.net/nucleuscms/danish-3.22.zip?download && unzip danish-3.22.zip
</pre></div>
<p>Next, I had a look for a readme file and found a readme.html in the main
folder. I set up the VirtualHost entry as the next thing and then
directed FF to https://nucleus/readme.html which worked fine - and the
documentation on installing that followed was well written and easy to
follow, which was cool. A fly in the ointment was the advice on
chmod'ing files and folders with 0666 or 0777 - this should be a last
resort, not the first suggested point!</p>
<p>After these points on file setup I was directed to install.php which
should take care of the rest. After loading this, it seemed time to
create another user/database pair, as the first questions I got were
about MySQL connections. Having done this, I could go on to filling in
all the details for the blog ... some of which were slightly confusing
as there were no explanations for them (such as a ping plugin).</p>
<p>At the end of the form there was a minor annoyance: a warning that the
user should submit the form once and <strong>only once!</strong> While this is
normally not a bad point, it's a whole lot better to make sure the user
only <strong>can</strong> submit the form once, instead of trying to work around this
problem. After that surprise there was the positive point of seeing
Nucleus remind people to change access properties of the config.php file
back to something secure.</p>
<p>After the install was done, there was a bit of maintenance to be done by
hand: the install files must be removed</p>
<div class="highlight"><pre><span></span>rm install.*
</pre></div>
<p>After removing these files, the blog was setup and functional.</p>
<ul>
<li>Ease-of-installation: 4/5</li>
<li>Instructions given: 3/5</li>
<li>Confusion level: Low</li>
</ul>
<h2>Serendipity</h2>
<ul>
<li>Version tested: 1.4.1</li>
<li>Requirements:<ul>
<li>PHP 4.3.0+</li>
<li>MySQL/PostgreSQL/SQLite</li>
<li>Apache</li>
</ul>
</li>
</ul>
<p>First, grab the source code and extract files:</p>
<div class="highlight"><pre><span></span>wget https://prdownloads.sourceforge.net/php-blog/serendipity-1.4.1.tar.gz?download && tar -zxvvf serendipity-1.4.1.tar.gz
</pre></div>
<p>Serendipity also lets you get the source files through SVN like
textpattern, an obvious plus.</p>
<p>Next step: check documentation. Serendipity doesn't come with
installtion documentation, which is a minus in my book. Why do I have to
check their site for further info, when I already have downloaded their
stuff? On the plus side though, the documentation is very good, and
definitely prepares you for the installation.</p>
<p>After adding a user/database and setting up the VirtualHost, it's time
to run the installer, which is done by aiming the browser at the main
folder of the Serendipity install (here https://serendipity/). The
installer first does a check to see what works and what needs some work,
which is a great feature. After tweaking some file permissions, I got
the choice of easy install or advanced install - I chose the latter, as
I wanted to see what one could set. I wasn't disappointed, there was a
whole lot of settings to work with. Unfortunately, this also meant that
the admin user setup got tucked away in the middle of the setup, and as
the values all came prefilled, I was close to just scrolling by that
bit. There was only one thing bugging me in the options: you have to set
a prefix for the database tables of Serendipity, even if you're giving
it a separate database to play with.</p>
<p>After checking the form, I hit 'complete installation' and then
instantly had errors in my face. Turns out that Serendipity 1.4.1 is
<strong>NOT</strong> compatible with PHP 5.3 - quite a few deprecated functions were
used. As the installer ended by claiming that things were in fact
working, I decided to brush the warnings aside and have a look at
things. That, unfortunately, turned out impossible, as Serendipity
really is incompatible with PHP 5.3 - neither the main site nor the
admin part works. I presume the cause of this is that the 1.4.1 version
is from January (PHP 5.3 was released half a year later) but that
doesn't really help, especially as the documentation claims Serendipity
is fully 5.x compatible (<a href="https://www.s9y.org/36.html#A3">third paragraph under
requirements</a>). The alternative is to try
the beta (latest release from August) or a nightly ... neither of which
is really acceptable to someone that wants a stable blog.</p>
<ul>
<li>Ease-of-installation: 0-3*/5</li>
<li>Instructions given: 4/5</li>
<li>Confusion level: Maximum</li>
</ul>
<p>* if the installer had worked, it would have been a 3. Seeing as now
I'll have to either install something else or muck about in .php files
to get it working, it's a 0</p>
<h2>WordPress</h2>
<ul>
<li>Version tested: 2.8.5</li>
<li>Requirements:<ul>
<li>PHP 4.3+</li>
<li>MySQL 4.0+</li>
</ul>
</li>
</ul>
<p>As with the other blog packages, first grab the sources and extract them
from their package:</p>
<div class="highlight"><pre><span></span>wget https://wordpress.org/latest.tar.gz && tar -zxvvf latest.tar.gz
</pre></div>
<p>After that's done, it's time to set up the VirtualHost for WordPress.
Like Nucleus, WordPress comes with a readme.html, so that's the first
thing to point the browser at once you can. This file will then tell you
to manually edit wp-config.php and after that run wp-admin/install.php
... maybe a famous 5-minute install but could have been done easier. You
have to enter variables for the DB connection as well as authentication
keys, and especially the last could do with some more documentation or a
better interface.</p>
<p>After that, the install is pretty much done - you only have to enter a
name for your blog and an email address, and you're done. Well, at least
I thought so. On the <a href="https://wordpress.org/">wordpress.org</a> site it
states that you'll be downloading version 2.8.5 when you get the
sources. However, after installing I found myself looking at a notice
wanting me to update my install to version 2.8.5 ... If I didn't just
install that version, what did I install?! Judging by the WordPress
dashboard, it appears the package you download is actually 2.8.4 and to
finish installing 2.8.5 you have to update the install through the
dashboard. So much for 5 minutes.</p>
<p>I opted for the automatic install option but quickly found myself
staring at a screen asking for ftp login details ... for which site I
have no idea. The download option, on the other hand, just offered me a
.zip file ... which didn't help all that much (does that mean reinstall?
Upgrade?). Browsing the WordPress documentation on upgrading told me
that the Apache user needs to own the WordPress source files (having
write access is not enough, it seems) or you'll get the ftp connection
details screen when auto-updating. Strange way to present an error to
the user, if you ask me. However, as soon as I had changed ownership of
the files, the auto upgrade went smooth.</p>
<p>Overall, the install presented more work than it should have, and
seemingly for no reason. The process was fairly short, though.</p>
<ul>
<li>Ease-of-installation: 4/5</li>
<li>Instructions given: 3/5</li>
<li>Confusion level: medium</li>
</ul>
<h2>Conclusion</h2>
<p>Of the four blog systems checked, Nucleus probably was the easiest to
install, with the least confusion following. While you may not choose
your blog by the installer, you probably wish you had when installing it
... especially in the case of Serendipity. It's something that should
not be overlooked, seeing as no one will be able to use your blog
software if they can't install it.</p>
<p>It seems that most of the blog packages have realised that there are
many things you can do to ease a users first experience with your
software: they have realised that it's more important to get the basic
stuff set up, let the user use the blog, and then let people make layout
and other advanced decisions later. It would be nice to see more use of
simple forms with documentation at your fingertips instead of somewhere
on the web. And lastly, you definitely need to review your documentation
on a regular basis, to avoid confusing people (and you obviously also
need to make sure that the download named 'latest' IS IN FACT the latest
version of your software).</p>
<p>Apart from Serendipity, that failed miserably, the install process went
OK for all the blogs. In general, it's not hard to install a blog and
getting it ready for use - especially if you're a knowledged user.</p>
<p>Next time: installing the CMS systems.</p>
<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 253px; width: 1px; height: 1px;">
https://typo3.org/
</div>The good and the bad2009-10-21T16:27:00+02:00admintag:plind.dk,2009-10-21:the-good-and-the-bad.html<p>Random stuff always seems to happen in a non-random fashion. When you're
hanging low, for instance, more bad things seem to happen to you. I
suppose that's partly the case with me, currently: I'm currently out of
a job so money is an issue (not currently that much of an issue but it's
there) hence my perspective is tainted by that. Which brings us to the
event: the battery in my Dell m1330 just died on me, from one day to the
next. No degrading or anything first, to give me a warning, it just
up'ed and died. And how do I know? The incessantly blinking light on the
front panel, the fact that the bios suddenly tells me that, although the
battery is 100% charged, there's just no communicating with the battery
(great assumption there).</p>
<p>So, what's a guy to do? Check dell.com of course! You wouldn't want one
of those non-dell batteries, now would you? Well, seeing as Dell is
selling it's batteries at about 800 kr. a piece (for the 6 cell variant)
I'd MUCH rather take my money elsewhere (and no, I also do not EVER buy
original printer toner cartridges - I'm not bending over for these
companies). So, what's a guy to do? Ebay! Turns out that a lot of
sellers in Hong Kong (!) will ship a battery to Denmark ... for just
about \$50. And that's including shipping. And we're talking the 9-cell
battery.</p>
<p>Am I worried about the battery I'll get? Sure, to a degree. However,
given the lifetime of the Dell battery I had, I would be just as worried
about buying a new one from dell. And on Ebay, I have the possibility to
check feedback from buyers whereas with Dell there's no way of knowing
(other than googling) how many bad customer experiences there are.</p>
<p>So, that's the bad. The good was a repeat of a hacking experience I had
a while ago. Specifically, hacking my Nokia N73. Hacking might be
overstating it, seeing as I just applied a third party tool on the
phone, then updated it's firmware. Nonetheless, it did give me great
pleasure to be able to switch the firmware on the phone from the basic
European I had set it to a while ago (to get rid of T-Mobile's
ridiculous advertising on my phone) to a Scandinavian firmware that
includes a Danish dictionary.</p>
<p>The basic steps included in the process (that runs on Windows,
unfortunately):</p>
<ol>
<li>Find, download and unzip the Nemesis Service Suite (<a href="https://www.b-phreaks.co.uk/software.htm" title="B-Phreaks Nemesis Service Suite">should be
here</a>).</li>
<li>Find, download and install the Nokia Software Updater (<a href="https://europe.nokia.com/get-support-and-software/download-software/device-software-update" title="Download Nokia software updater">should be
here</a>).
Then reboot (seriously Nokia, why??)</li>
<li>Make sure Windows sees your phone.</li>
<li>Backup stuff on your phone</li>
<li>Scan for devices with Nemesis Service Suite.</li>
<li>Select your device, read the values from it, click enable on the
product code, then change it to what you want (<a href="https://www.allaboutsymbian.com/forum/showthread.php?t=53992" title="Nokia N73 product codes">here's a list of
product codes for the
N73</a>).</li>
<li>Write the value to your phone with Nemesis.</li>
<li>Enjoy your new firmware and sticking it to Nokia (no, they don't
like it when you change the firmware without asking them).</li>
</ol>Infosys activity2009-10-14T09:28:00+02:00Peter Lindtag:plind.dk,2009-10-14:infosys-activity.html<p>Lately I've been working a lot on the Infosys code, so I figured I'd
post some updates I've done here.</p>
<ul>
<li>
<p>Refactored core code:</p>
<ul>
<li>Turned singletons into normal classes, using static variables
for shared data if needed</li>
<li>Introduced exceptions instead of bool return values in many
places</li>
<li>Changed framework/MVC integration to be based on dependency
injection</li>
<li>Changed output to use template files rather than huge view
classes</li>
<li>Refactored request handling to use objects and delegate work
more</li>
<li>Improved the database class to use more proper functions with
better sanity checks</li>
</ul>
</li>
<li>
<p>Wrote tests</p>
<ul>
<li>So far have 206 tests spread over framework classes, MVC models
and data entities</li>
</ul>
</li>
<li>
<p>Delegated work to 3rd parties</p>
<ul>
<li>Infosys now uses SwiftMailer and PHPTAL</li>
</ul>
</li>
<li>
<p>Implemented a database migration tool</p>
</li>
</ul>
<p>A whole lot more is planned - <a href="https://github.com/Fake51/Infosys/issues" title="Infosys issues list">see the issues list for
details</a>.
There are also some more abstract things that might happen, such as
turning Infosys into a more modular system - the reasoning behind this
being that other cons have shown a bit of interest in using the system
but would probably need it modified, yet don't have massive amounts of
time for modifications, etc. The nature of Infosys should make it fairly
easy to generate an install tool that allows for choosing which parts of
the system are active plus making other customizations.</p>Beginning actionscript2009-10-09T12:04:00+02:00Peter Lindtag:plind.dk,2009-10-09:beginning-actionscript.html<p>Set off by a job interview I got, I've started learning
<a href="https://en.wikipedia.org/wiki/Actionscript" title="WikiPedia on ActionScript">actionscript</a>.
It's not something I've dealt with before apart from decompiling flash
scripts to debug them. There's been some obstacles before I could really
dig into it - the first being getting an actionscript compiler installed
on Ubuntu. As always, though, someone else had already had the same
problem, solved it, and <a href="https://www.williambrownstreet.net/wordpress/?p=78" title="William Brown Street on actionscripting in Ubuntu">blogged about
it</a>.
turns out all you have to do is <a href="https://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+3" title="Flex SDK download">grab the Adobe Flex
SDK</a>,
unzip, chmod the mxmlc file and you're set on your way.</p>
<p>Of course, some people demand an IDE and it turns out that Adobe has
been heeding their calls: you can get the Adobe Flex Builder as a plugin
for Eclipse. Getting that to work on Ubuntu 9.04 was a bit more work
than the above, but again:
<a href="https://www.insideria.com/2008/04/step-by-step-setting-up-flex-b.html">others</a>
<a href="https://kbala.com/2009/03/install-adobe-flex-builder-linux-alpha-in-ubuntu/">have</a>
<a href="https://blog.tripu.info/item/flex-linux">already</a>
<a href="https://www.markvandenbergh.com/archives/83/installing-flex-builder-on-ubuntu-904/">solved</a>
that problem. Get a Java runtime environment, get the newest build of
Eclipse (the one in the Ubuntu default sources is too old), <a href="https://labs.adobe.com/downloads/flexbuilder_linux.html">get the
plugin from
Adobe</a>.</p>
<p>After that, I could start having a look at the language, and it is
indeed obvious that it's based on EcmaScript: it's fairly
straightforward if you know JavaScript, which is obviously a plus for
me. There are some big differences from the start, though, such as the
strange need to package things ... but allowing for stuff outside the
package in files. Also, if only one class is allowed inside a package,
there's no need whatsoever to enforce naming to be the same as the file
containing it - whatever the public class inside the package in the
file, THAT'S what is instantiated.</p>
<p>I'm guessing that whoever designed ActionScript liked a lot of
JavaScript but was scared witless by all the weak typing and the passing
around of functions, etc., so strong typing was (almost) enforced on all
variables (the actionscript compiler will complain about missing types
but will compile nonetheless ...) as well as classes and what have you.</p>
<p>So far, the best part about actionscript is the ease with which you'll
get going. And the worst part is my lack of a debugger (can't blame the
language on that though). On that note, I find it rather strange that
Flex Builder provides me with rubbish error output whereas running the
command line compiler pinpoints the problems.</p>
<p>Anyway, here's my first try at writing something in actionscript:
<a href="/downloads/line_drawing.swf">line-drawing with random colors</a> [flash
file].</p>
<p>And here's the code for it:</p>
<div class="highlight"><pre><span></span><span class="n">package</span>
<span class="p">{</span>
<span class="kn">import</span> <span class="nn">flash.display.Sprite</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">flash.events.Event</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">flash.events.MouseEvent</span><span class="p">;</span>
<span class="n">public</span> <span class="k">class</span> <span class="nc">drawing</span> <span class="n">extends</span> <span class="n">Sprite</span>
<span class="p">{</span>
<span class="n">private</span> <span class="n">var</span> <span class="n">_mouse_moving</span><span class="p">:</span><span class="n">Boolean</span><span class="p">;</span>
<span class="n">private</span> <span class="n">var</span> <span class="n">_line</span><span class="p">:</span><span class="n">Line</span><span class="p">;</span>
<span class="n">public</span> <span class="n">function</span> <span class="n">drawing</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">stage</span><span class="o">.</span><span class="n">addEventListener</span><span class="p">(</span><span class="n">Event</span><span class="o">.</span><span class="n">ENTER_FRAME</span><span class="p">,</span> <span class="n">init</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">private</span> <span class="n">function</span> <span class="n">init</span><span class="p">(</span><span class="n">event</span><span class="p">:</span><span class="n">Event</span><span class="p">):</span><span class="n">void</span>
<span class="p">{</span>
<span class="n">stage</span><span class="o">.</span><span class="n">removeEventListener</span><span class="p">(</span><span class="n">Event</span><span class="o">.</span><span class="n">ENTER_FRAME</span><span class="p">,</span> <span class="n">init</span><span class="p">);</span>
<span class="n">stage</span><span class="o">.</span><span class="n">addEventListener</span><span class="p">(</span><span class="n">MouseEvent</span><span class="o">.</span><span class="n">MOUSE_DOWN</span><span class="p">,</span> <span class="n">mouseDown</span><span class="p">);</span>
<span class="n">stage</span><span class="o">.</span><span class="n">addEventListener</span><span class="p">(</span><span class="n">MouseEvent</span><span class="o">.</span><span class="n">MOUSE_UP</span><span class="p">,</span> <span class="n">mouseUp</span><span class="p">);</span>
<span class="n">stage</span><span class="o">.</span><span class="n">addEventListener</span><span class="p">(</span><span class="n">MouseEvent</span><span class="o">.</span><span class="n">MOUSE_MOVE</span><span class="p">,</span> <span class="n">mouseMove</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">public</span> <span class="n">function</span> <span class="n">mouseMove</span><span class="p">(</span><span class="n">event</span><span class="p">:</span><span class="n">MouseEvent</span><span class="p">):</span><span class="n">void</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_mouse_moving</span> <span class="o">==</span> <span class="n">true</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_line</span><span class="o">.</span><span class="n">drawLine</span><span class="p">(</span><span class="n">event</span><span class="o">.</span><span class="n">stageX</span><span class="p">,</span> <span class="n">event</span><span class="o">.</span><span class="n">stageY</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">public</span> <span class="n">function</span> <span class="n">mouseDown</span><span class="p">(</span><span class="n">event</span><span class="p">:</span><span class="n">MouseEvent</span><span class="p">):</span><span class="n">void</span>
<span class="p">{</span>
<span class="n">_mouse_moving</span> <span class="o">=</span> <span class="n">true</span><span class="p">;</span>
<span class="n">var</span> <span class="n">color</span><span class="p">:</span><span class="nb">int</span> <span class="o">=</span> <span class="n">uint</span><span class="p">(((</span><span class="n">Math</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">255</span><span class="p">)</span> <span class="o"><<</span> <span class="mi">16</span><span class="p">)</span> <span class="o">+</span> <span class="p">((</span><span class="n">Math</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">255</span><span class="p">)</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">)</span> <span class="o">+</span> <span class="p">((</span><span class="n">Math</span><span class="o">.</span><span class="n">random</span><span class="p">()</span> <span class="o">*</span> <span class="mi">255</span><span class="p">)))</span>
<span class="n">_line</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Line</span><span class="p">(</span><span class="n">color</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="n">event</span><span class="o">.</span><span class="n">stageX</span><span class="p">,</span> <span class="n">event</span><span class="o">.</span><span class="n">stageY</span><span class="p">);</span>
<span class="n">addChild</span><span class="p">(</span><span class="n">_line</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">public</span> <span class="n">function</span> <span class="n">mouseUp</span><span class="p">(</span><span class="n">event</span><span class="p">:</span><span class="n">MouseEvent</span><span class="p">):</span><span class="n">void</span>
<span class="p">{</span>
<span class="n">_mouse_moving</span> <span class="o">=</span> <span class="n">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kn">import</span> <span class="nn">flash.display.Sprite</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">flash.display.Shape</span><span class="p">;</span>
<span class="kn">import</span> <span class="nn">flash.events.Event</span><span class="p">;</span>
<span class="k">class</span> <span class="nc">Line</span> <span class="n">extends</span> <span class="n">Shape</span>
<span class="p">{</span>
<span class="n">private</span> <span class="n">var</span> <span class="n">_color</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="n">private</span> <span class="n">var</span> <span class="n">_thickness</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="n">private</span> <span class="n">var</span> <span class="n">_start_x</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="n">private</span> <span class="n">var</span> <span class="n">_start_y</span><span class="p">:</span><span class="nb">int</span><span class="p">;</span>
<span class="n">public</span> <span class="n">function</span> <span class="n">Line</span><span class="p">(</span><span class="n">color</span><span class="p">:</span><span class="nb">int</span><span class="p">,</span> <span class="n">line_thickness</span><span class="p">:</span><span class="nb">int</span><span class="p">,</span> <span class="n">start_x</span><span class="p">:</span><span class="nb">int</span><span class="p">,</span> <span class="n">start_y</span><span class="p">:</span><span class="nb">int</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">_color</span> <span class="o">=</span> <span class="n">color</span><span class="p">;</span>
<span class="n">_thickness</span> <span class="o">=</span> <span class="n">line_thickness</span><span class="p">;</span>
<span class="n">_start_x</span> <span class="o">=</span> <span class="n">start_x</span><span class="p">;</span>
<span class="n">_start_y</span> <span class="o">=</span> <span class="n">start_y</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">public</span> <span class="n">function</span> <span class="n">drawLine</span><span class="p">(</span><span class="n">end_x</span><span class="p">:</span><span class="nb">int</span><span class="p">,</span> <span class="n">end_y</span><span class="p">:</span><span class="nb">int</span><span class="p">):</span><span class="n">void</span>
<span class="p">{</span>
<span class="n">graphics</span><span class="o">.</span><span class="n">clear</span><span class="p">();</span>
<span class="n">graphics</span><span class="o">.</span><span class="n">lineStyle</span><span class="p">(</span><span class="n">_thickness</span><span class="p">,</span> <span class="n">_color</span><span class="p">);</span>
<span class="n">graphics</span><span class="o">.</span><span class="n">moveTo</span><span class="p">(</span><span class="n">_start_x</span><span class="p">,</span> <span class="n">_start_y</span><span class="p">);</span>
<span class="n">graphics</span><span class="o">.</span><span class="n">lineTo</span><span class="p">(</span><span class="n">end_x</span><span class="p">,</span> <span class="n">end_y</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>Ranting about PHP2009-10-05T11:45:00+02:00admintag:plind.dk,2009-10-05:ranting-about-php.html<p>Came across <a href="http://www.prodevtips.com/2009/09/30/this-is-whats-wrong-with-php/">this blog post on
PHP</a>
today. Seems to me the author needs a fair amount of sleep and then a
cop of coffee or two ...</p>
<p>First,</p>
<blockquote>
<p>... it all started with me trying to be clever with <strong>array_map</strong> and
<strong>array_filter</strong>.</p>
</blockquote>
<p>Being clever is not a good thing. In my experience it always leads to
crappy code that you'll regret ever having done when time comes round to
code maintenance. Whenever the thought strikes you - "I'll come up with
a clever solution for this" - think twice, then step back and redesign.</p>
<p>Secondly,</p>
<blockquote>
<p>I knew array_filter existed and what it was all about since before,
however I started working with something requiring array_map first,
all well and OK, array_map looks like this: array_map(’callback’,
Array). So then I assumed I could use array_filter in the same
fashion, big mistake.</p>
</blockquote>
<p>As Henrik goes on to point out, PHP is not consistent in function
definitions, which is a problem. A much bigger problem is that something
with 5 years of self-proclaimed PHP-hacking experience doesn't check
function definitions but wastes time assuming either that 1) function
definitions are the same for similar functions (which apparently he
knows is not the case) or that 2) he knows the functions already when he
doesn't. How many tools are there for checking PHP function definitions
easily? A simple search key in FireFox would have solved this in 30
secs.</p>
<p>Thirdly,</p>
<blockquote>
<p>So the class method containing both the array_map and array_filter
callbacks has only one argument: \$name, which contains a partial
string to match against users’ full names or usernames. I wanted to
use that variable inside the array_filter callback.</p>
<p>What was I thinking? I <strong>knew</strong> that it wouldn’t work, of course it
wouldn’t since callbacks are just an ugly hack bolted on top of PHP,
hell you’re calling the name of the callback in the form of a string.
I’ve got no idea how the interpreter is actually evaluating this stuff
and I don’t want to know, I’d rather keep what I have in my stomach
where it is.</p>
</blockquote>
<p>Yes, callbacks are nasty but they provide for quite a lot of
functionality. You can use the name of a function, an object method, the
result of create_function or even (from PHP 5.3) a PHP closure. But of
course, that doesn't directly solve Henriks problem: passing that extra
variable to array_filter or array_map. And here there obviously is a
problem that could use a nice and simple design like allowing for
passing an extra variable. Workarounds are possible, though:</p>
<div class="highlight"><pre><span></span><span class="cp"><?php</span>
<span class="k">class</span> <span class="nc">dummy</span>
<span class="p">{</span>
<span class="k">private</span> <span class="nv">$_partial</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">getFriendsByPartial</span><span class="p">(</span><span class="nv">$partial</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_partial</span> <span class="o">=</span> <span class="nv">$partial</span><span class="p">;</span>
<span class="k">return</span> <span class="nb">array_filter</span><span class="p">(</span><span class="nv">$input_array</span><span class="p">,</span> <span class="k">array</span><span class="p">(</span><span class="nv">$this</span><span class="p">,</span> <span class="s1">'callback'</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">callback</span><span class="p">(</span><span class="nv">$input</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// test $input against $this->_partial here</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Is that pretty? No. Does it work? Pretty sure it does. Does it keep
things inside the class? Yup. Did it take me longer to write than it
took me to read Henriks post? No.</p>
<p>However, this was apparently not clean enough, so instead of doing
something simple like this, the alternative was writing up a function
using foreach and stripos. Only Henrik couldn't remember (after 5 years)
that there's strpos and stripos (and, again, checking function lists was
not an option) so he wasted more time.</p>
<blockquote>
<p>I probably lost over an hour just on that one and it’s happened to me
at least 5 times, yes roughly one time per year, just this fucking
shitty little function. Why oh why are there two of them? You have to
be rainman to navigate this swamp, of course I should’ve used
<strong>stripos()</strong>! What an idiot I am right? Why couldn’t my pea brain
remember that, especially since it’s happened to me so many times
before? I must truly be a complete moron.</p>
</blockquote>
<p>I don't think he's a moron for not remembering the difference between
strpos and stripos (or rather that both exist). If there's anything at
play here, it's the belief that you can actually remember all the
functions of PHP. So when the ironic morale comes</p>
<blockquote>
<p>When it comes to PHP, don’t try to be clever, and most importantly of
all, know your functions, all 5000 of them.</p>
</blockquote>
<p>it's very hard not to just shrug it off and think that the programmers
arrogance got to Henrik. Don't ever think you can know all of the
functions in PHP - look them up before you start abusing, that way
you'll avoid spending more than 5 minutes on the thing that cost Henrik
hours.</p>
<p>I'm glad that in the end though, he did gain the insight of not trying
to be clever.</p>Refactoring Infosys2009-09-29T11:32:00+02:00admintag:plind.dk,2009-09-29:refactoring-infosys.html<p>The last week or more has seen quite a bit of activity for
<a href="https://github.com/Fake51/Infosys/" title="GitHub for Infosys">Infosys</a> - I've
been frenzied, to put it mildly :) Almost every core class has had major
changes and the framework is fundamentally different now. It's been lots
of fun, lots of learning involved - here are some thoughts on the
process:</p>
<h3>1. Get some overview, create a gameplan</h3>
<p>As with all other areas of development (and most things work, really),
step 1 should be getting an overview of the current state of things and
what needs to be done. If the project is your own (and Infosys, at this
point, is pretty much 100% me) there's a fair chance you'll want to just
'dig in' - start refactoring to improve the framework straight away,
because 'you know how it works already and how to improve it'. This is a
bad idea, because it will inevitably lead straight back to where you are
now: refactoring things you're not quite happy with.</p>
<p>In the case of Infosys, lots of core classes were
<a href="https://en.wikipedia.org/wiki/Singleton_pattern" title="Wikipedia article on the Singleton pattern">singletons</a>
- something I don't have a problem with as such (see <a href="plind.dk/2009/07/20/lists-of-x">my post on the
matter</a>) but was thinking of moving away
from in favour of
<a href="https://en.wikipedia.org/wiki/Dependency_injection" title="Wikipedia on dependencey injection">dependency</a>
<a href="https://www.martinfowler.com/articles/injection.html" title="Martin Fowler on dependency injection">injection</a>.
My main reasons for not using Singletons anymore were related to testing
(using <a href="https://www.phpunit.de/">PHPUnit</a>, to be specific) where
singletons can be a pain (although they don't have to be) and to closing
classes off as much as possible, decoupling them from one another.</p>
<p>I started out by looking at the core code and looked at what would
function better as pure objects and decided on a limited set of classes
- moving some functionality out of the request handler into a request
object, changing session and log classes. However, I started refactoring
things sooner than I should have, believing that I had found a new and
better design. The problem was that I didn't actually look at the entire
framework core with an objective eye - specifically, I opted to keep the
database class a singleton (this app will, for any foreseeable future,
ONLY NEED ONE CONNECTION! SO DONT EVEN BOTHER!). I thought it
problematic to pass a single database object around (you want to create
it when initializing your app if it's database driven, instead of
finding out very late in the process that you don't have a database
connection) because it meant handing it to other objects that had no
reason to have a copy (a request handler shouldn't touch the database)
in order to pass it to the ones that need it. As you can probably guess,
though, I got over this problem, because the alternative is a) using a
registry (which I had been doing but also refactored, again to improve
decoupling and testability) or b) direct access to the singleton (which
I did for a short period until I decided that really wasn't a very good
idea).</p>
<p>The moral here is that I ended up doing more work than I should have
done. I should have realised that it would suit Infosys better to not
use singletons at all and gone for a more unified refactoring of the
core classes. Instead I first refactored code to access the database
through the classic getInstance method of the database class, and then
afterwards refactored code to just pass an instance of the database
class around. The end product is the same, I just spent more time doing
it.</p>
<h3>2. Decide on the overall design</h3>
<p>One thing that I've learned from handling several different frameworks
and frameworks in different versions is that consistency rules. Creating
new features or fixing bugs in old ones is so much easier if the code
and surrounding framework is in harmony and if the framework has a
driving idea behind it.</p>
<p>One problem with creating a framework over time is that as you learn,
you'll be adding new stuff to it. Perhaps some new ways of doing things,
new code that works better for some needed things. Unless you really
strive to keep everything consistent, you'll end up with having
different ways of doing related things in the framework - and this makes
creating new features a hassle. This was one of my concerns when
refactoring the core classes of the Infosys framework: if half the core
classes were refactored to be normal classes and the other half stayed
singletons, I would constantly have to guess at which was which. Should
I create a new instance of Log? Or should I ask the Registry for the
instance? As noted before, I refactored the singletons, but still I have
a minor problem here:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="n">Someclass</span>
{
<span class="n">public</span> <span class="n">function</span> <span class="n">test</span>()
{
<span class="nv">$log</span> = <span class="nb">new</span> <span class="n">Log</span>(<span class="nv">$this-</span>><span class="n">db</span>);
}
}
</pre></div>
<p>The issue in the code above is that the Log class handles both writing
to file and writing to the database (the first is used for errors and
debug stuff, while the latter is used for logging normal application
actions) so the Log class needs a database connection. However, because
I'm only dealing with one connection that I want to reuse, the database
object is always passed around, while log objects are just created. As
both classes constitute core framework code, it would be easier if they
worked the same way: less to remember. I could obviously just have opted
for creating one instance of the Log class but there wouldn't be much
point in passing it around: it does very little and does not need to
persist (quite the contrary, in fact).</p>
<h3>3. Simplicity over cleverness</h3>
<p>Part of refactoring is to me the idea of bringing everything together in
a consistent idea. You're removing the clutter, bringing things in line
with how they should be. Implicit in this idea is also that you should
go for the simple design - that clever idea you got a some point is
probably the reason you're refactoring things now. I would even take
this one step further: if you ever have the choice between clever and
simple, avoid shooting yourself in the foot by choosing clever. There's
a 95% chance you'll regret going with clever later.</p>
<p>An example of this from Infosys would be the reuse of a searchform.
Prior to refactoring, MVC apps used view classes to store all output in
- not in itself a clever solution, but unfortunately not a very good one
either as some view classes reached the 2k+ lines. Also, controllers
called view functions and then returned to the request handler - instead
of just telling the request handler which template should be used and
then be done with it. The clever part came in reusing template bits,
specifically a fairly big searchform containing lots of inputs. The
reuse was handled by having a private method in a view class - so the
template would just call the private method.</p>
<p>Now, when refactoring to use templates loaded into a generic page class
rather than having a view class for each MVC, this becomes a problem:
how to get the shared template in a nice and easy way? Templates could
always require it, but it would really be nasty to see a require fail in
the middle of rendering a template. It would also be possible to hand
over the searchform to a helper class, which might in the end be the
best solution - however, then the searchform will be removed from it's
normal place, which is in the templates folder for the given app.</p>
<p>Now, I may very well go with the latter idea (haven't quite decided yet)
as I'm already using a view helper and keeping other bits of template
for reuse in there. The problem is obviously that I didn't go with this
path for all the reused templates from the start: had I done that, I
wouldn't be facing this problem now but would just be able to move on to
the next bit of code that needs work. Again, a decision to go for a more
clever solution instead of simplicity and consistency has ended up
biting me in the rear.</p>
<h3>4. Test, test, test</h3>
<p>One of the key things I wanted to implement in this drive to improve
Infosys is unit tests. Again, seeing as Infosys is the product of pretty
much just me alone, I have no problem diving into the code and changing
some fundamental thing to make it better. This is a good and a bad thing
- but luckily the bad consequences can be pretty much nullified with
tests. First, it's obviously a good thing because it means Infosys
doesn't have to stick to using bad code (well, it obviously has to stick
with however good the code I create is but the point is that if I know
some designs or ideas to be better than the existing Infosys code, I'll
just change it). As a consequence, using Infosys is now easier and more
consistent than just two weeks ago. On the bad side, I've completely
broken Infosys many times over during the last two weeks. If I didn't at
the same time implement unit tests for all the things I change, I'd have
no idea if I was introducing bugs in the code while refactoring it.</p>
<p>So, what I've been doing is creating tests every time I've changed code.
When refactoring the database class I created a database test class to
go along with it, so I'd know that it would still work the same way,
even if the insides changed radically. At times this approach created
some minor problems - I was refactoring one class when I had to stop
that in order to refactor a dependency. The end effect is truly awesome
though. So far I've 154 tests done and can be fairly certain that if
something underlying or unexpected changes in the tested classes, I'll
know about it as soon as I run the tests - which happens everytime I
change code.</p>
<p>Better still, in case someone else at some point decides to join in on
the development of Infosys it will be a breeze to install the system and
then do a systems test to make sure that everything is working as it
should. If any tests break, you'll know immediately that there's a
problem in the system - and you won't need to dig around for hours
looking for the problem.</p>
<p>And of course, there's another added bonus: while writing tests for the
Infosys code, I've come across things that weren't quite as I would
expect them to be. In other words, writing tests have helped me fix bugs
I didn't even know I had and that didn't get picked up in the code. One
particularly nasty bug went something like this:</p>
<div class="highlight"><pre><span></span><span class="x">...</span>
<span class="p">$</span><span class="nv">select</span><span class="x">->setWhere(field, =, value);</span>
<span class="x">return </span><span class="p">$</span><span class="nv">this</span><span class="x">->findBySelectMany(</span><span class="p">$</span><span class="nv">select</span><span class="x">);</span>
<span class="x">...</span>
</pre></div>
<p>There's nothing wrong as such with the above code - in fact, it hasn't
changed at all. However, the underlying code worked in a way that made
the above code dangerous. Specifically, if something was bad with the
input, the function would fail with a bool return code. Which the above
code obviously just ignores. I had no idea this code was flawed until I
created a test to check it. Now, however, the underlying code throws
exceptions if the input is bad - no longer any way for code to ignore
errors silently.</p>
<h3>5. Keep it together</h3>
<p>Refactoring Infosys has been a ton of fun so far, I've learned a lot
through it. However, while doing it it's important to keep in mind that
there's a reason for doing this: refactoring in itself is pointless, it
has to be done to achieve something. For Infosys this something is the
need to be able to implement a greater range of features and do so more
easily (the issue list on GitHub is fairly long). If I didn't try to
attain a specific goal, I would fall into the trap of the DIY person:
there's always more to do and it can always be done better. Sooner or
later you'll tire of your neverending project and put it aside. This is
something to avoid and the way to do that is create clear goals and
attain them one by one. Then you can measure your progress and you will
actually stand a chance of getting done.</p>Migrating servers2009-09-13T15:30:00+02:00Peter Lindtag:plind.dk,2009-09-13:migrating-servers.html<p>This week and the weekend before that I've had the chance to experience
the joy of migrating servers. <a href="https://www.bewelcome.org">BeWelcome</a> has
opted for a new production server, as a) the new contract is cheaper and
b) the new server has better hardware. Based on that experience, there
are a number of conclusions to draw:</p>
<ul>
<li><strong>Before you do anything that looks like migrating, make sure that
everything on the server is documented.</strong></li>
<li><strong>Detail everything that needs to be done before starting.</strong></li>
<li><strong>After that, make sure that everything is backed up.</strong></li>
<li><strong>Start moving things to the new server in good time. Last minute
means errors.</strong></li>
<li><strong>Make sure you go through all the phases: preparation, migration,
cleanup.</strong></li>
</ul>
<p>Getting ready for the server migration, I tried to get down details of
everything that needed to be moved. This proved VERY hard, because we
had no documentation on what was running on the server. Sure, I know
that we're running a website using an Apache webserver and utilizing PHP
and MySQL. However, what do we use to send out emails? How are we
handling normal backups? Which users are needed, which are outdated and
should be removed? Most of these things weren't documented anywhere, so
there was a whole lot of detective work to do. And while this can be
interesting, you don't want to lose data because of something as simple
as this.</p>
<p>There are obviously also further consequences to the lack of
documentation: which scripts should be running automatically? Those that
run now or less? Should they be running with the privileges they have
now or should they actually be restrained? While you don't necessarily
want to deal with this during a server migration, this is actually the
time when you're most likely to look closer at what's running and how -
unless you're doing an actual integrity check of the server (and how
often are you doing those? As often as you should or less?).</p>
<p>Another of the points that didn't exactly to plan for our move was
backing things up. I didn't give it too much thought, as the data would
be present on the old server during the migration and afterwards too.
However, we were handing the old server back to the provider, so the
data would obviously not be available for long - in fact, the data was
available for one day only after the migration was done. We didn't
manage to lose anything critical, but there were a few things we didn't
manage to get set up properly before the old settings were gone. This
relates to the point on documentation: there were some things that were
not documented properly and so we didn't know they were needed on the
new server. Hence, when we found out, it was too late to go back and
check.</p>
<p>Now, one way of handling backup would of course be to just dd the entire
disk and be done with it. Which is a good idea if you have the space for
it and can transfer it off your server in good time. Even easier, if
you're just migrating servers and will be mirroring the old setup, then
you can just copy the old server and stick it on the new. In our case,
though, we were moving from Debian 4 to 5. Doing a copy of the system
wasn't an option, and to make sure that apps were working properly, we
needed to get them set up first. Exactly what do you backup then? On a
Linux setup, probably /etc, /home, some of /var ... and again, you see
the problem of lacking documentation. Guesswork isn't really good enough
here.</p>
<p>Because we weren't done with the migration till a day before handing the
old server back, there wasn't a lot of time for cleaning up either. This
wasn't too big a problem with regards to the old server: files were
deleted, the system was reinstalled a couple of times, nothing should
remain unless you're either reading the sectors manually or going for
the magnetic trace of things - neither of which are too likely. With
regard to the new server, there is more cleaning up to do: because of
the lacking documentation, some things were setup in less than perfect
ways and some data was copied over without ending in the right places.
In other words, there's quite a bit of cleanup to do. And because the
migration took place too late, there's no original setup to look at for
comparison.</p>
<h2>For next time</h2>
<p>One thing is for sure - I've definitely learned my lesson from this
experience. I'm currently busy writing up documentation on what we're
running, where everything is, how to go about setting it up, etc. It
should result in two things:</p>
<ol>
<li>Proper documentation of what we use and what we have</li>
<li>Proper documentation on how to move it</li>
</ol>
<p>Together, the two should allow for a fairly easy time when handling the
next migration. Not sure I want to be part of that, but at the very
least I should make sure anyone taking up the job after me has an easier
time. Chances are, though, that I'll be needing the documentation myself
before another migration given the daily maintenance tasks ...</p>PHP optimizing2009-09-07T06:10:00+02:00admintag:plind.dk,2009-09-07:php-optimizing.html<p>I've read <a href="http://progtuts.info/55/php-optimization-tips/">various</a>
<a href="https://code.google.com/speed/articles/optimizing-php.html">tips</a>
<a href="https://stackoverflow.com/questions/127765/php-optimization-tips">about</a>
<a href="https://www.alexatnet.com/node/196">optimizing</a> PHP code and at first I
happily took them in. Later on, having read other
<a href="https://php100.wordpress.com/2009/06/26/php-performance-google/">points</a>
of <a href="https://www.codinghorror.com/blog/archives/001218.html">view</a>, I
started to wonder a bit about some of the optimizations and later still
I realised that some things are not just dubious but plain wrong. Yes,
I'm talking about strings here.</p>
<p>If you've looked at optimizing PHP code and browsed the net regarding
the topic, you will invariably have come across tips amounting to use
single quotes wherever possible instead of double quotes. You will
almost certainly also have come across articles or blog posts telling
you that this is bogus. Initially, looking at the advice, you might
think it makes sense: PHP evaluates double-quoted strings to see if they
contain variables that need to be inserted into the string. However, if
you roll your own tests of this advice, you'll soon see two things: 1)
it is not clear in any way that using single quoted strings is faster
than double quoted strings and 2) if there is any gain in speed, you
won't notice it until you're running millions of iterations. Literally,
millions of string operations.</p>
<p>With this in mind, I don't care at all about single or double quoted
strings, I just use whatever ... which turns out to be mainly
double-quoted strings, because of inserted variables. You see, the thing
that I do care about is code legibility. Take the following two pieces
of code - which is more legible?</p>
<div class="highlight"><pre><span></span><span class="x">$string = "some text and " . $variable;</span>
<span class="x">$string = "some text and </span><span class="cp">{</span><span class="nv">$variable</span><span class="cp">}</span><span class="x">";</span>
</pre></div>
<p>To me there's no doubt: the latter is by far more readable. I don't have
to jump in and out of strings, I know that it's all one string and I can
read it as such. I know that a lot of others have a different coding
style though, preferring the former. This got me thinking: is the case
the same for the two strings above as for strings without variables? In
other words: is there a difference in performance when variables enter
the picture?</p>
<p>To answer my question, I wrote a quick test:</p>
<div class="highlight"><pre><span></span><span class="o">$</span><span class="nt">count</span> <span class="o">=</span> <span class="nt">20000000</span><span class="o">;</span>
<span class="o">$</span><span class="nt">start</span> <span class="o">=</span> <span class="nt">microtime</span><span class="o">(</span><span class="nt">true</span><span class="o">);</span>
<span class="o">$</span><span class="nt">string</span> <span class="o">=</span> <span class="s1">''</span><span class="o">;</span>
<span class="nt">for</span> <span class="o">($</span><span class="nt">i</span> <span class="o">=</span> <span class="nt">0</span><span class="o">;</span> <span class="o">$</span><span class="nt">i</span> <span class="o"><</span> <span class="o">$</span><span class="nt">count</span><span class="o">;</span> <span class="o">$</span><span class="nt">i</span><span class="o">++)</span>
<span class="p">{</span>
<span class="err">$</span><span class="n">string</span> <span class="o">=</span> <span class="s2">"test string"</span> <span class="o">.</span> <span class="err">$</span><span class="n">i</span> <span class="o">.</span> <span class="s2">"test string"</span> <span class="o">.</span> <span class="err">$</span><span class="n">i</span> <span class="o">.</span> <span class="s2">"test string"</span> <span class="o">.</span> <span class="err">$</span><span class="n">i</span> <span class="o">.</span> <span class="s2">"some string"</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">$</span><span class="nt">middle</span> <span class="o">=</span> <span class="nt">microtime</span><span class="o">(</span><span class="nt">true</span><span class="o">);</span>
<span class="o">$</span><span class="nt">string</span> <span class="o">=</span> <span class="s1">''</span><span class="o">;</span>
<span class="nt">for</span> <span class="o">($</span><span class="nt">i</span> <span class="o">=</span> <span class="nt">0</span><span class="o">;</span> <span class="o">$</span><span class="nt">i</span> <span class="o"><</span> <span class="o">$</span><span class="nt">count</span><span class="o">;</span> <span class="o">$</span><span class="nt">i</span><span class="o">++)</span>
<span class="p">{</span>
<span class="err">$</span><span class="n">string</span> <span class="o">=</span> <span class="s2">"test string{$i}test string{$i}test string{$i}some string"</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">$</span><span class="nt">end</span> <span class="o">=</span> <span class="nt">microtime</span><span class="o">(</span><span class="nt">true</span><span class="o">);</span>
<span class="nt">echo</span> <span class="s2">"First 20000000 iterations took: "</span> <span class="o">.</span> <span class="o">($</span><span class="nt">middle</span> <span class="nt">-</span> <span class="o">$</span><span class="nt">start</span><span class="o">)</span> <span class="o">.</span> <span class="nt">PHP_EOL</span><span class="o">;</span>
<span class="nt">echo</span> <span class="s2">"Last 20000000 iterations took: "</span> <span class="o">.</span> <span class="o">($</span><span class="nt">end</span> <span class="nt">-</span> <span class="o">$</span><span class="nt">middle</span><span class="o">)</span> <span class="o">.</span> <span class="nt">PHP_EOL</span><span class="o">;</span>
</pre></div>
<p>I ran it first with a million iterations and just one variable and got
about a second for each loop. Seeing as this is rather low, I upped the
count to 20 million, which gave a more interesting result - there was
about a second difference between the two loops. Trying a couple more
times, the difference stayed, so I wondered if there was something
significant there. I figured that if anything, then more variables
should make it more obvious - which it did: the first test took about 20
secs for both loops, the second set of iterations (two variables) took
33 and 34 secs. Adding another variable took me to 46 and 48 seconds for
each 20 million loops - seemed that finally I might be seeing some
actual difference. Until I switched the places of the loops, when
suddenly the loop that was faster before was the slowest.</p>
<p>At this point, there are two options: look at the memory usage of the
loops to see how that plays into things (maybe the difference seen
previously can be tracked to that) or call it quits. Looking at the
results, calling it quits is the only reasonable thing to. 20 million
iterations and no clear result to show for it ... other than that when
variables are in the picture, strings take a lot longer to process.
Which doesn't leave you with a lot, as you'd have to reduce the amount
of variables in the strings to improve speed.</p>
<p>And again: 1 million iterations with 1 variable amounts to 1 (one)
second. 3 variables puts it at 2.1 seconds. Don't ever bother with
string optimizations. Not worth it unless you're doing many, MANY
millions of iterations.</p>Twin of the bloodless model2009-08-24T19:39:00+02:00admintag:plind.dk,2009-08-24:twin-of-the-bloodless-model.html<p>For a couple of weeks now I've been working on refactoring an MVC app in
the <a href="https://www.bewelcome.org">BeWelcome</a> code I'm working on. The
reasons behind the refactoring are that 1) it's built on an old part of
our codebase (yes, we have several separate parts and yes, it's a
nightmare) and 2) it's using some code and database tables that we want
to get rid of. Hence, I'm refactoring the app to get it up to date and
to remove any references to the problem code.</p>
<p>While doing this, I've had lots of chances to get familiar with the twin
of the
<a href="https://blog.astrumfutura.com/archives/373-The-M-in-MVC-Why-Models-are-Misunderstood-and-Unappreciated.html">bloodless</a>
<a href="https://monstersgotmy.net/post/Anemic-Domain-Model-Anti-Pattern.aspx">model</a>
<a href="https://www.martinfowler.com/bliki/AnemicDomainModel.html">anti-pattern</a>:
the anorectic controller. I'm sure lots of people will not see this as
an anti-pattern but if you ask me, it's right up there with the anemic
model. If you're looking at your controller and wondering exactly what's
going on and where the code and data is, you might just be looking at
this.</p>
<p>What I'm objecting to is a controller where the route method is called
by the framework only for the controller to then call one method on
another object - and this object then pulls data out of requests, sends
it here and there and probably does cooking and cleaning too while it's
at it. This is not a controller - this is lack of control. It means that
the primary contact to the surrounding world in a PHP MVC setup is NOT
responsible for the contact to the surrounding. And consequently that
you're coupling your OTHER classes (whether the model or, as in this
case, the view) to the surrounding world. And whoops, there goes the
idea of changing how you send variables to the MVC right along with ease
of maintenance.</p>
<p>To me it's simple: the controller is the surrounding worlds point of
contact to the MVC; it's also the MVC's point of contact to the
surrounding world. There: ONE interface between surrounding world and
MVC. Not so hard to locate anymore, is it?</p>
<p>Of course this doesn't mean that you have to suck the blood out of the
model to get some meat on your controller. All it means is that you
separate things into where they should go: data processing and business
logic in the model, control over program flow and contact with
surrounding code in the controller, and UI in the view. Don't move
program flow control into the view and don't move contact to the
surrounding world into the model. Just. Don't.</p>Beginning unittests2009-08-15T16:06:00+02:00Peter Lindtag:plind.dk,2009-08-15:beginning-unittests.html<p>I've lately started implementing unit-tests for the BeWelcome codebase
and from the get-go it's been a joy. Well, almost. Spent a little time
figuring out how to get the framework working and making it accessible,
but after that's it's been sweet. Just running the tests gives a basic
sort of joy, seeing the dots come up one by one as the assertions are
tested.</p>
<p>It's been interesting to get started on, though. We never used
unit-tests before (there are a few starts here and there, but nothing
substantial) and we never designed for it. Basically this means that
there are a number of dependcncies that make it harder to write tests
for the BeWelcome codebase. So far I'm just writing tests for the
entities in the framework, as they are smaller, better written and have
less dependencies than most of the rest of the code (well, that's how I
designed them - hopefully it's the case as well).</p>
<p>So, as a shortcut in order to get tests working I'm instantiating the BW
framework and then running the tests with dependencies. The entities
depend on a DB connection and possibly other entities - and that's it.
That makes them fairly easy to test without refactoring them to use
dependency injection - the rest of the code might not be as easy to deal
with.</p>
<p>Two things got me started on implementing unit-tests: at my work, we've
been making an effort lately to get more tests into place. Seeing how
that worked was good inspiration. The second thing was reading the
following blog post:
<a href="https://codeutopia.net/blog/2009/06/05/unit-testing-introduction/">http://codeutopia.net/blog/2009/06/05/unit-testing-introduction/</a>
- it's the first part of a good series on doing unit-tests. After
reading that, the documentation for
<a href="https://www.phpunit.de/" title="Site of the PHPUnit testing app">PHPUnit</a> was
quite helpful as well.</p>
<p>The one bit of trouble I had was creating test-suites to group tests
together. The PHPUnit documentation wasn't worth a whole lot (see
<a href="https://www.phpunit.de/manual/current/en/organizing-tests.html">Chapter
7</a>) as
although it does mention the various ways of creating test suites, it
doesn't give any examples of putting several tests in a a testsuite. In
the end, I went with the addTestFile method of testsuites to get things
working - this spared me trouble of requiring the test files as well.</p>
<p>Should anyone have the same trouble as me, here's my method:</p>
<div class="highlight"><pre><span></span><span class="x">require_once("PHPUnit/Framework.php");</span>
<span class="x">class EntityTests extends PHPUnit_Framework_TestSuite</span>
<span class="err">{</span><span class="x"></span>
<span class="x"> public static function suite()</span>
<span class="x"> </span><span class="err">{</span><span class="x"></span>
<span class="x"> </span><span class="p">$</span><span class="nv">suite</span><span class="x"> = new EntityTests;</span>
<span class="x"> </span><span class="p">$</span><span class="nv">suite</span><span class="x">->addTestFile('GroupEntityTest.php');</span>
<span class="x"> </span><span class="p">$</span><span class="nv">suite</span><span class="x">->addTestFile('GroupMembershipEntityTest.php');</span>
<span class="x"> </span><span class="p">$</span><span class="nv">suite</span><span class="x">->addTestFile('MemberEntityTest.php');</span>
<span class="x"> return </span><span class="p">$</span><span class="nv">suite</span><span class="x">;</span>
<span class="x"> }</span>
<span class="x">}</span>
</pre></div>Offline mode2009-07-31T09:06:00+02:00Peter Lindtag:plind.dk,2009-07-31:offline-mode.html<p>Lately I've been doing my share of work-on-your-way-to-work (technically
it's a hobby-project, but when your hobby includes doing exactly the
same as your work, it's hard to see the big difference) and this has led
to some annoyance on my part. The situation: I work a bit at home,
suspend my computer and stick it in my bag, then open it again when I'm
on the tube. Obviously, at that point there's no network connection so
the browser won't find any interwebs. Not a problem though, as I'm
working on local data, accessed through the server running on my laptop.
So, where's there problem? Well, Firefox - in it's infinite wisdom -
decides that since I'm not connected to the webs, then I'm not allowed
to view web-pages through https://. Just like that: a big fat warning
screen telling me that I'm now offline. And then I have to enable
browsing again by setting a tick-box in the file-menu, telling firefox
that 'no, really, I know what I'm doing and I want to work'.</p>
<p>When software thinks it knows better than you, that's when it's time to
spank developers. There should be no difference for me between working
'online' and working 'offline' - if the page I want to access is
accessible then display it, stop whining non-sensically! Really, what's
the point of putting obstacles in my way? 'Sure you can load the locally
accessible page you want, but you have to tick this little box first,
because we're not quite that intelligent developers'. The most annoying
part is that this behaviour actually changed for a short period, in a
recent beta version of FF but then some idiot re-inserted this bug.</p>Ryanair ...2009-07-26T10:15:00+02:00admintag:plind.dk,2009-07-26:ryanair.html<p>There can be just one judgement on this airline: they massively suck!
They are so incredibly poor at customer relations your mind will almost
refuse to believe it. And now, they have proven even worse at handling
security. Here are the happy details of complete security-management
failure:</p>
<ul>
<li>You now HAVE to check-in online. There is no other option. Hence,
not much chance for Ryanair to catch anybody cheating with boarding
passes. Not only that, you're charged for this (sorry, had to put
this in as it baffles the mind how you can be charged extra for
something you cannot opt out of).</li>
<li>You can only check-in online up to four (4) hours before your
flight. Yet, part of this online check-in is making sure that you
have answered the ubiquitous security questions: has anybody meddled
with your luggage prior to boarding? How the fuck would I know, I'm
not boarding yet!</li>
<li>When checking in for a return flight, this is made so much worse by
the fact that you'll be checking in for both flights at the same
time. You could, perhaps, be expected to not lose sight of your
luggage for four hours but not for one, two or three weeks ... or
longer.</li>
<li>Better still, Ryanair now wants you to supply them with security
details, like your passport details. Ofcourse, you would have done
something similar before, but the difference is that now, a company
that optimises profits at the expense of anything and anyone will be
storing your details in a database. The chances of these details
being encrypted are so infinitessimaly small that there's no point
to even consider them - security is obviously an extra cost to
Ryanair and they have chosen to hand it over to you, completely.</li>
<li>Before you get as far as entering your passport details, though, you
have to survive the threat to your mental health that comes from
their check-in authentication system. To verify that you are you,
you just have to know 4 bits of info: departure date, email address,
departure location and arrival location. That's all it takes to get
into the system where you can then enter passport details and print
out boarding passes. And of course, once passport details have been
entered, they cannot be changed unless you call Ryanair.</li>
<li>Finally, there's the question of airport security. I haven't tried
flying with their new system in place yet (only intermediate
version) but a good guess is that the check-in desk will not ask you
for your passport (and that's assuming you have bags to check in -
no bags means you go straight to the security area where they do not
check your passport).</li>
</ul>
<p>From a security standpoint, there can be only one judgment on this
system: complete and total failure. My girlfriend and I have taken the
consequences: we're not flying with Ryanair again.</p>Coming of (some sort of) age2009-07-22T11:34:00+02:00Peter Lindtag:plind.dk,2009-07-22:coming-of-some-sort-of-age.html<p>I've read many times in many different places written by many different
people that learning the programming trade means that when you look back
at previous code you wrote, you'll wince and go 'Eeeeewwwwww!'. Well,
I've read something to that effect, maybe not exactly that. Various
people (like <span><a href="https://www.codinghorror.com">Jeff Atwood</a>) have
written on the notable differences in coding style between
<strong>me-then</strong>and <strong>me-now</strong>. Examples:</span></p>
<ul>
<li><span>Telling a story in the comments (i.e. over explaining) vs. to
the point comments</span></li>
<li><span>Coding as much as possible vs. trying to keep it to a
minimum</span></li>
<li><span>Using things (like patterns) for their own reason vs. using
them because they work</span></li>
</ul>
<p>In itself, I don't find this a very noteworthy thing. To be sure, on a
personal level, it's something to marvel at: 'Look! I've evolved! I've
learned! I'm not at the same level as before! I'm better now'. But I
think that this particular way of looking at your progress makes sense
only as anecdotes - telling the story of how you've been looking at old
code and had a laughing fit. There is another, more important side to
consider if you ask me:</p>
<p><strong>Being able to judge code better</strong></p>
<p>An unhelpful metaphor: when you start playing chess, you have no idea
what you're doing. Pretty much no matter who you're playing, they'll be
better than you and their moves will seem smarter in comparison to
yours. As you learn to play the game and get better, you'll be able to
recognize better or worse moves and strategies. You'll be able to
recognize people that play better or worse than you with a bigger degree
of accuracy: you'll know not just that your opponent is better but that
he's only slightly better than you, not the genius you thought he was a
month ago. It's the same in all areas of learning: as you get a better
grasp of the subject, you get a better overall knowledge and you gain
skills that are not directly related to the task at hand (in chess:
winning, in development: finishing your project).</p>
<p>The skills connected to what you're learning can be very valuable as
well, is my point. In development, being able to judge code, i.e. being
able to measure what standard it lives up to, how error-prone it is, how
easy it will be to maintain, etc, are incredibly useful skills when
you're evaluating contributions to a project. They're also very useful
skills for judging the capability of others: which is why, in an
interview situation, it is pointless to ask a person that doesn't have
some sort of proficiency in development to conduct the interview. It
also shows a proper way of conducting interviews: based on something the
person has actually done as opposed to weird questions that have no
relation to the real world.</p>
<p>In short: make the most of experience. When you've reached that point
where you can look at some code and have an idea about the standards of
it, you've gained valuable insights.</p>Lists of x2009-07-20T12:32:00+02:00Peter Lindtag:plind.dk,2009-07-20:lists-of-x.html<p>A little while ago I came across the following blog posts by John Kleijn
at <a href="https://www.phpfreaks.com/">phpfreaks</a>:</p>
<ul>
<li>
<p><a href="https://www.phpfreaks.com/blog/10-signs-of-crappy-php-software">10 Signs of Crappy PHP
Software</a></p>
</li>
<li>
<p><a href="https://www.phpfreaks.com/blog/10-ways-to-avoid-writing-crappy-code">10 Ways to Avoid Writing Crappy
Code</a></p>
</li>
</ul>
<p>Both of the posts are good examples of good and bad sides to blogging
about developing. First, the good parts:</p>
<ul>
<li>It's great to have someone list of a number of things that you can
check your own code for. Am I committing these mistakes? Could I
avoid the mentioned problems?</li>
<li>It's also great to have some idea of where others are taking
development. Is there cool new stuff I should be trying?</li>
</ul>
<p>The medium of the numbered list makes these things very accessible - you
glance through and read a bit closer if it catches your attention. You
get motivated to find out more. However:</p>
<ul>
<li>Why 10 points? Are there only 10 signs of crappy PHP software? Are
there just 10 ways to avoid writing crappy code?</li>
<li>Is point 1 more important than point 10? Or?</li>
</ul>
<p>In other words, these lists have the look of roadmaps: how to do this,
how to avoid that, get laid in 5 easy steps, blah blah. Another blog I
follow a bit, <a href="https://boagworld.com/">Boagworld</a>, does this quite a lot.
What they should do is give you an idea of things but instead they often
end up looking like The Truth. Which leads me to a personal gripe.</p>
<p><strong>1 sign that you're an arrogant coder thinking you know better than the
rest when in fact you don't</strong></p>
<ul>
<li>Yelling and screaming at people for using or advising others not to
use singletons.</li>
</ul>
<p>Singletons have to be the most misunderstood design pattern for all the
wrath they're getting. It's absurd to see the amount of abuse it gets
... and for what? The singleton is a pattern to make sure only one
instance of a class gets created. It is designed specifically to handle
the situations WHERE ONLY ONE INSTANCE should exist. So you know
instantly when you hear criticism like:</p>
<blockquote>
<p>A database connection is probably the poorest example you could have
possibly chosen. What if I need two simultaneous connections?</p>
</blockquote>
<p>Well, then you bloody well wouldn't have chosen the singleton pattern
would you now? ONLY ONE INSTANCE! HOW. HARD. IS. THAT. TO. UNDERSTAND??</p>
<p>I find it incredible that 'experienced' or 'senior' developers rail
against this thing - it's as if they've never used it but only seen
apparitions of the singletons evil brother. When you have a situation in
which a given class should only be created once but access to it is
needed in all parts of your app, the singleton pattern is not a bad
design - on the contrary, it's a good design. The point about design
patterns is not knowing them but knowing when and how to use them -
railing about the evil of the singleton pattern shows lack of knowledge
about it.</p>
<p>So please, if you're ever considering ranting on about how a given
design pattern (or something else) is evil then please, take the time to
actually understand it first. Then write your blog/article about how the
thing is actually not evil but should be used with care. Or go ahead and
write that guns kill people and if there were just no guns, there would
be no dead people.</p>Personal take on MVC2009-07-03T12:35:00+02:00admintag:plind.dk,2009-07-03:personal-take-on-mvc.html<p>I've been working with MVC for a while now and it's generally a great
idea. I first got acquainted with it when starting to work on the
BeWelcome code (have a look at
<a href="https://www.bevolunteer.org/trac/" title="Check the BeWelcome project documentation">http://www.bevolunteer.org/trac/</a>
if you're interested in an open source hospitality exchange project).
After that I used it in personal frameworks, based on what I'd seen in
BeWelcome and reading from articles like <a href="https://st-www.cs.uiuc.edu/users/smarch/st-docs/mvc.html" title="Read more on the MVC pattern"><em>How to use
Model-View-Controller
(MVC)</em></a>
by Steve Burbeck. My first frameworks were, as one might expect, not
providing a whole lot of usability and code reuse. As a colleague of
mine said, after having had a look at the code: there were a lot of
design patterns present but they didn't seem to be doing much in my
code.</p>
<p>After that, I've worked with the MVC pattern in a number of sites as
part of my job at Aardvark Media as well as more mature versions of my
framework, and it's let me to some personal opinions on the pattern.</p>
<ul>
<li>The main point of the MVC pattern seems to me to be modularising the
code. The reason it's called MVC is that you're dealing with Model,
View and Controller - it's a triad of connections. In order to get
the maximum decoupling, this triad needs to be able to work on it's
own, i.e. you need to be able to rip out an MVC triad without having
the entire code break down.</li>
<li>Most of the uses of MVC that I have seen means coupling the model of
the MVC with other classes. The reason for this is simple: the
models provide data access and lots of places can need access to
data that doesn't appear in the MVC they're in. From my point of
view this poses a problem, though, because it means you can no
longer switch out MVC triads as you please.</li>
<li>The MVC design works best, in my opinion, when all access to the MVC
happens through the controller. In web apps, this means running the
framework and then picking an MVC triad to run and then be done with
it (possibly running several MVCs but running them independently of
each other). It's also possible to have MVCs use each, but still
using only the controller as the entry point to the code. This
ensures exposing methods in just one place.</li>
<li>This leads to the obvious question: how to avoid code duplication
and get proper code reuse, if models shouldn't be shared among MVCs?
My personal answer to this is data objects, based on the ORM
pattern. The idea here is that the model in an MVC will normally
contain general code that does general data lookup and is used by
many places (your typical load user code, for instance). The model
will also contain some specific code only used by the MVC it is
placed in. If the general code is refactored out into a smaller
object, that only contains general code, it's possible to make some
very flexible data objects based on the ORM pattern that at the same
time fulfill all the data access needs of the project.</li>
<li>This leads to architechture along the following lines: 1) MVCs that
do the work of applications and 2) data objects that do the majority
of the data grunt work. Data objects are shared among all MVCs -
because they only hold generic methods and data, they don't
overexpose methods or properties. MVC models only expose methods to
the MVC they're in, so there won't be any tight coupling between
individual MVCs.</li>
<li>This means MVCs become more like modules - you can swap them in and
out as you please, turn them on or off during debugging, etc.</li>
</ul>
<p>Switching to a style like this is not very hard. You can go about it
piecemeal: first setup a base class for the data objects with some
generic features for getting data out of the DB, then setup some object
classes mirroring the DB setup. At first, all you want is just an empty
child class of the data object class: just enough detail to tie it with
the DB table but no more. Test that you can get data out of the proper
table and when this works, you can start refactoring models to use the
data object instead: instantiate the data object in the model and move
the generic data functions into that instead. And ... you're done :)</p>Development in teams2009-06-29T12:53:00+02:00Peter Lindtag:plind.dk,2009-06-29:development-in-teams.html<p>Working in teams is great. And a massive pain. It depends on a lot of
things: the other team members, the team lead, the project itself, your
motivation for being in the team, the structuring of the team, plus the
general environment of the team. Obviously, in a normal job, the
individual doesn't have too much influence on most of these factors:
your team lead is a given, project is set by your boss, you've got a
given position determined by what others think is best, the environment
is set ... most of the factors you'll have no influence on.</p>
<p>Things are supposed to be different in an open source project. You have
much more of a say in things: you determine what, if anything, you
contribute. You determine what parts of the project you're working on,
if any. The structure of the team is - with a bit of luck - also
something you can have effect on. Open source projects could thus be a
dream come true in terms of development experience: people that want to
work together on a project they want to participate in. What could go
wrong?</p>
<p>Needless to say, tons of things could make things problematic in OS
projects. My personal experiences come from working with
<a href="https://www.bewelcome.org">BeWelcome</a>, a hospitality/cultural exchange
organisation and website. Founded on the bad experiences a number of
people had with other sites (notably
<a href="https://hospitalityclub.org">HospitalityClub</a> and
<a href="https://www.couchsurfing.org">Couchsurfing</a>) these people set out to
avoid the problems they saw with the other sites. This, unfortunately,
led to defining BeWelcome first negatively in terms of the other sites
and only secondly to try defining it positively. In terms of the
organisation it has led to a lot of bureaucracy - something that has
also been felt in the various teams, including the dev team. The main
problem has been a lack of clearly defined leadership. There have been
team leads, for sure, great ones as well, but an overall problem with
leadership in the project has influenced all teams as well (a team
environment factor: the outside world influencing the team because the
team feels it should mimic the outside world).</p>
<p>By far the biggest problem that I have experienced till now, though, has
been lacking a team lead. After the last guy stopped no one stepped up
to the plate so we've been working without one for a while now and boy!
does that suck if there are ever differences between members in the
team. Lacking a clear way to resolve differing opinions, point out the
way or decide upon the priority of tasks is a major problem. Even just a
thing like processes in the team needs a team lead to enforce them: if
one person doesn't like the way things are done and starts working in a
different way, who's to stop her?</p>
<p>While it's true that in an OS project anyone can step up and take on
ownership, human nature would have it that not everyone does. In a
project that runs on volunteer motivation, taking part already costs
energy ... and taking on more responsibility can easily be too much.
There's no extra pay to sweeten the deal, no perks, just more
responsibility, more people calling on you to help them with bug fixes
and feature requests. Obviously, seeing the project take the right turns
and head in the right direction is a reward, but is it enough to
outweigh the extra weight on your shoulders? I think the amount of
people fighting to take on the role of team lead reflects just how much
status is attached to the project - in most situations, it's not going
to be many people.</p>
<h3></h3>
<h3>Then what?</h3>
<p>The possibilities for dealing with leadership in development teams fall
in three groups:</p>
<ol>
<li>having a team lead</li>
<li>sharing the team lead role between several members</li>
<li>not having a team lead</li>
</ol>
<p><strong>1.</strong> This is the obvious possibility and the best in my opinion. In
order for it to work properly, a good team lead is required but it's a
role in which people can learn and rise to the task.</p>
<p><strong>2.</strong>This means cutting the role down to size, sharing out
responsibilities and making sure the resulting roles are not too big or
too small. If the team gets the roles right and make sure there are no
overlapping responsibilities, this can probably work almost as good as
option #1. It's a requirement that the roles are clearly defined and
that the people sharing the team lead role can work together without
problems.</p>
<p><strong>3.</strong> An alternative to having a lead is having clearly defined
processes and sticking to them. If the group can agree to some clearly
defined procedures for working and follow them, most differences in the
every run of things can be resolved by pointing to the rules. Bigger
differences in opinion or view of goals and mission can be resolved
through a democratic show of hands in the group. This solution might
work if all the people in the team shares a mindset and like to work
together, but if not it's doomed from the outset.</p>
<p>In BeWelcome we're currently going with option #3 and my personal
experience is that it's not working and it's not worth using - we're
spending way too much time discussing and arguing things, and there's no
option for resolving differences over working processes. Too many
confrontations which takes time away from development.</p>
<p>So, time to change things.</p>SVN status cheatsheet2009-06-26T13:09:00+02:00admintag:plind.dk,2009-06-26:svn-status-cheatsheet.html<p>Recently I was looking for info on status messages from SVN and while
googling for 'svn status cheatsheet' did turn up quite a few results,
they were all useless: none of them went beyond the status messages you
get in the first row from SVN when doing svn st. Seeing as I was up
against 's' and 'uu' that didn't help. The <a href="https://svnbook.red-bean.com/en/1.5">SVN
manual</a> didn't prove very helpful
either, at first - the status messages are spread out through the manual
and finding the ones I needed took me very long. Hence, for future
reference:</p>
<table border="0">
<tbody>
<tr>
<th colspan="2">
First column - file added or changed
</th>
</tr>
<tr>
<td>
</td>
<td>
no changes
</td>
</tr>
<tr>
<td>
A
</td>
<td>
item will be added
</td>
</tr>
<tr>
<td>
C
</td>
<td>
item has conflicts
</td>
</tr>
<tr>
<td>
D
</td>
<td>
item will be deleted
</td>
</tr>
<tr>
<td>
E
</td>
<td>
file existed before the update
</td>
</tr>
<tr>
<td>
M
</td>
<td>
item has been modified
</td>
</tr>
<tr>
<td>
R
</td>
<td>
item has been replaced
</td>
</tr>
<tr>
<td>
X
</td>
<td>
item is present due to an externals definition
</td>
</tr>
<tr>
<td>
?
</td>
<td>
item is not in repository
</td>
</tr>
<tr>
<td>
I
</td>
<td>
item is being ignored
</td>
</tr>
<tr>
<td>
!
</td>
<td>
item is missing
</td>
</tr>
<tr>
<td>
\~
</td>
<td>
item is versioned as one thing but replaced by another
</td>
</tr>
<tr>
<th colspan="2">
Second column - properties changed
</th>
</tr>
<tr>
<td>
</td>
<td>
no changes
</td>
</tr>
<tr>
<td>
M
</td>
<td>
properties have changed
</td>
</tr>
<tr>
<td>
C
</td>
<td>
properties for the item is in conflict with those in repository
</td>
</tr>
<tr>
<th colspan="2">
Third column - locks
</th>
</tr>
<tr>
<td>
</td>
<td>
no locks
</td>
</tr>
<tr>
<td>
L
</td>
<td>
item is locked
</td>
</tr>
<tr>
<th colspan="2">
Fourth column - for addition-with-history scheduled items
</th>
</tr>
<tr>
<td>
</td>
<td>
no history scheduled with commit
</td>
</tr>
<tr>
<td>
+
</td>
<td>
history scheduled with commit
</td>
</tr>
<tr>
<th colspan="2">
Fifth column - switching relative to parents
</th>
</tr>
<tr>
<td>
</td>
<td>
item is child of its parent directory
</td>
</tr>
<tr>
<td>
S
</td>
<td>
item is switched
</td>
</tr>
<tr>
<th colspan="2">
Sixth column - lock information
</th>
</tr>
<tr>
<td>
</td>
<td>
item is not locked
</td>
</tr>
<tr>
<td>
K
</td>
<td>
item is locked in this working copy
</td>
</tr>
<tr>
<td>
O
</td>
<td>
item is locked by another user or in another working copy
</td>
</tr>
<tr>
<td>
T
</td>
<td>
item was locked in this working copy but the lock is invalid. Item is
still locked in repository
</td>
</tr>
<tr>
<td>
B
</td>
<td>
item was locked in this working copy but the lock is invalid. Item is no
longer locked
</td>
</tr>
<tr>
<th colspan="2">
Seventh column - out-of-date information
</th>
</tr>
<tr>
<td>
</td>
<td>
item is uptodate
</td>
</tr>
<tr>
<td>
\*
</td>
<td>
newer version of item exists in repository
</td>
</tr>
</tbody>
</table>
<p>Most of this stuff won't show unless you do 'svn st --show-updates' or
'svn st -v' but it's very useful to know from time to time.</p>PHP magic methods2009-06-25T20:08:00+02:00admintag:plind.dk,2009-06-25:php-magic-methods.html<p>I can't ever recall the magic methods of PHP and sometimes google
doesn't seem to display them as the first couple of hits, so for my own
sake, a cheat-sheet of them with a few notes:</p>
<ul>
<li><strong>__construct(/* arguments */)</strong> : called on construction of the
object</li>
<li><strong>__destruct()</strong> : called when the object is destroyed</li>
<li><strong>__get(\$var)</strong> : called when the object does NOT contain the
property \$var or when \$var is private or protected. ALSO called on
empty(\$object->\$var) if \$var is private or protected</li>
<li><strong>__set(\$var, \$value)</strong> : called when the object does NOT
contain the property \$var or when \$var is private or protected.</li>
<li><strong>__call(\$method, \$args)</strong> : called when the object does NOT
contain the method \$method. Unlike __get and __set it doesn't
hide private or protected methods.</li>
<li><strong>__callStatic(\$method, \$args)</strong> : equivalent to __call but
for static calls. New as of 5.3.</li>
<li><strong>__clone()</strong> : called on clone object.</li>
<li><span><strong>__isset(\$var):</strong>called when empty() or isset() is called
on a non-existing or private or protected property.<strong><br />
</strong></span></li>
<li><strong>__unset(\$var)</strong> : called on unset when object does NOT contain
\$var or \$var is private or protected.</li>
<li><strong>__sleep()</strong> : called if serialize() is called on the object.
Should return array of properties that will be serialized before the
object is destroyed. Allows object to do cleanup before
serialization.</li>
<li><strong>__wakeup()</strong> : called if unserialize() is called on the object.
Notifies the object that it is being brought back to life.</li>
<li><strong>__toString()</strong> : let's a class decide what to return if it is
converted to string.</li>
<li><strong>__invoke(/* arguments */)</strong> : called if the objectt is called
as a function. New as of 5.3</li>
<li><strong>__set_state(\$array)</strong> : static method called for classes
exported by var_export.</li>
</ul>
<p>And, the <a href="https://us3.php.net/manual/en/language.oop5.magic.php" title="Magic methods in PHP">manual page describing the
methods</a>.</p>Know the geek2009-06-21T10:16:00+02:00Peter Lindtag:plind.dk,2009-06-21:know-the-geek.html<p>I have a theory that you can tell a geek by the comics he reads. I don't
know yet what the individual geek types are, but some day I'll figure
that one out too. In the meantime, the question becomes one of "separate
the geeks from the non-geeks by looking at their comic reads". Here are
mine:</p>
<ul>
<li><a href="https://www.sinfest.net">Sinfest</a></li>
<li><a href="https://kevinandkell.com/">Kevin and Kell</a></li>
<li><a href="https://www.gocomics.com/calvinandhobbes/">Calvin and Hobbes</a></li>
<li><a href="https://www.gocomics.com/nonsequitur/">Non Sequitur</a></li>
<li><a href="https://www.giantitp.com/comics/oots0001.html">Order of the Stick</a></li>
<li><a href="https://garfieldminusgarfield.net/">Garfield minus Garfield</a></li>
<li><a href="https://tmcm.com/">Too Much Coffee Man</a></li>
<li><a href="https://www.xkcd.com/">xkcd</a></li>
</ul>
<p>Hmmm ... maybe the length of the list plays a role too ...</p>Microsoft ...2009-06-20T11:32:00+02:00Peter Lindtag:plind.dk,2009-06-20:microsoft.html<p>Couldn't help but comment on this:</p>
<p><a href="https://www.microsoft.com/windows/internet-explorer/get-the-facts/browser-comparison.aspx" title="Microsoft browser comparison chart">Microsoft browser comparison
chart</a></p>
<p>We're told that</p>
<blockquote>
<p>Internet Explorer 8 takes the cake with better phishing and malware
protection, as well as protection from emerging threats.</p>
</blockquote>
<p>Did Microsoft miss the point about Chrome running every tab as a new
process, nicely sandboxed? When you defeat IE, you get ACCESS. If you
manage to defeat Chrome, you don't.</p>
<p>Furthermore:</p>
<blockquote>
<p>Internet Explorer 8 has the most comprehensive developer tools built
in, including HTML, CSS and JavaScript editing, but also JavaScript
profiling; other browsers have developer tools available, but either
require you to download them separately, or aren't as complete.</p>
</blockquote>
<p>True, you do have to download FireBug to get it working FF. But Safari
and Opera come with pretty good dev support too and my money is on both
of those as being quite a bit better than what MS has to offer.</p>
<p>Strangely, MS thinks support for web standards is a tie:</p>
<blockquote>
<p>Firefox and Chrome have more support for emerging standards like HTML5
and CSS3, but Internet Explorer 8 invested heavily in having
world-class, consistent support for the entire CSS2.1 specification.</p>
</blockquote>
<p>Too little too late: CSS2.1 is no longer the frontier, everyone else has
been supporting it for long. The <a href="https://acid3.acidtests.org/" title="Acid Test 3">Third Acid
Test</a><br />
is a nice example of that. Sure, FF doesn't get a 100% on that but IE8
comes in at 20%!</p>
<p>It's not that I'm unhappy about the work being done on IE8 - it's just
that IE is not there yet. And MS is <em>still</em> trying hard, to ruin the
experience of the common user + developer by introducing strange and
meaningless changes to the standards of the 'net (two references:
introducing <a href="https://www.alistapart.com/articles/theyshootbrowsers/" title="A List Apart article on IE">version targetting in
IE8</a>
and breaking JavaScript from the get go), so why be anything but
cynical?</p>Learning the hard way2009-06-20T09:00:00+02:00Peter Lindtag:plind.dk,2009-06-20:learning-the-hard-way.html<p>Setting up a wordpress blog is not hard, even if you're tired and
enjoying a very nice glass of Laphroaig. Remembering your new admin
password, on the other hand, can be very hard in that situation. So what
to do when you have forgetten it? You could try having your wordpress
install reset it, but if you're as lucky as me it doesn't actually send
you the new password (or the activation key before that). You could also
try to install this:
<a href="https://www.village-idiot.org/archives/2007/05/22/wp-emergency-password-recovery/" title="http://www.village-idiot.org/archives/2007/05/22/wp-emergency-password-recovery/">http://www.village-idiot.org/archives/2007/05/22/wp-emergency-password-recovery/</a>
if you have access to the code and you trust it (I would). However, if
you're a bit more in the debugging spirit and you can mess with the
code, you could also have a look round the wordpress script files for
interesting functions. In particular, the <strong>wp_check_password</strong>
function in<em>wp-includes/pluggable.php</em> checks if a password matches the
input - stick a <strong>return true;</strong> in the beginning of the function and
you'll be flying by with any character jumble used for password.</p>
<p>... remember to remove this hack or you WILL regret it.</p>In the beginning ...2009-06-19T23:06:00+02:00Peter Lindtag:plind.dk,2009-06-19:in-the-beginning.html<p>I've taken a bite of the bitter apple and realised that I'm not going to
get site back up anytime soon - that is, I'm not going to get the custom
framework based site up and running. I'm also not going to get a
Zend-based site up and running soon though I'm close to switching
priorities to that. At some point I'll still do it, I think, just don't
have the time at the moment (and no, I don't think I can do a better
job, I don't think I can create a superior CMS, but creating my first
blog was how I learned about N-to-N database relationships ... without
having read about them first. Yes, it's all about learning).</p>
<p>Still need an outlet for ranting, though ... too many things that just
don't make sense for me to not comment on them. Hence: @ fake's</p>