debuggable

 
Contact Us
 
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57

Drake :: Drupal-CakePHP 1.0.1b Released

Posted on 26/2/07 by Felix Geisendörfer

Hey folks,

this is just a quick announcement that a completely new Drake has has been released by Mariano Iglesias. For those of you who don't remember: I used to have some fun with integrating CakePHP with other PHP applications around such as drupal which in term created a project called Drake. I only released a couple versions and by looking at Mariano documentation for Drakes none of them were as easy to set up.

Here is his announcement from the google group:

My fellow bakers,

After my experience with Jake, and thanks to Felix’s goodwill in giving the catchy name Drake for me to use, I’m pleased to announce the first beta release of Drake, a Drupal module that lets you execute your CakePHP applications inside Drupal.

Project home page: http://dev.sypad.com/projects/drake

Documentation: http://dev.sypad.com/projects/drake/documentation

Download: http://drupal.org/project/drake

Demo (Cheesecake Photoblog on Drupal 5.1): http://dev.sypad.com/projects/drake/demo/drake

Bake on, with Drake ;)

-MI

So if you like to work with an existing system such as Drupal go ahead and give it a try!

-- Felix Geisendörfer aka the_undefined

 

Cake 1.2's Set class eats nested arrays for breakfast!

Posted on 24/2/07 by Felix Geisendörfer

Hey folks,

I was just taking a little trip through the CakePHP core code trying to wrap my head around Acl, Model behaviors and all sorts of stuff. While doing so I saw that the core code starts to be using the Set class more and more that was added a while ago. So far this has been a little dark spot for me in the core and from my previous quick looks at the class I've never been quite able to figure out what it's exact purpose was. Until now all I knew was "well it's probably some fancy array manipulation code that is somewhat obfuscated and undocumented". Oh boy, I wish I had spent more time on this earlier. It's probably one of coolest new features in 1.2 and nobody realizes it ; ).

So before starting to drool over it too much ahead of time, let's take a look at a simple example. You have an array of $users as it could have been returned from a findAll call to your User model:

$users = array
(
    0 => array
    (
        'User' => array
        (
            'id' => 1
            , 'name' => 'Felix'
        )      
    )
    , 1 => array
    (
        'User' => array
        (
            'id' => 2
            , 'name' => 'Bob'
        )
    )
    , 2 => array
    (
        'User' => array
        (
            'id' => 3
            , 'name' => 'Jim'
        )
    )
);

What you really want however, is just a simple array containing all user 'name's: array('Felix', 'Bob', 'Jim'). Hmm. Up until today I'd probably have written some code like this to do it:

$userNames = array();
foreach ($users as $user)
{
    $userNames[] = $user['User']['name'];
}

Simple enough, right? Not any more! Using the new Set class we can achieve the exact same outcome like this:

$userNames = Set::extract($users, '{n}.User.name');

Doesn't blow you away yet? Well, let's look at another example. Let's say our User model as a hasMany associations to an Item model. Then we would get an array like this:

$users = array
(
    0 => array
    (
        'User' => array
        (
            'id' => 1
            , 'name' => 'Felix'
            , 'Item' => array
            (
                0 => array
                (
                    'id' => 1
                    , 'name' => 'Mouse'            
                )
                , 1 => array
                (
                    'id' => 2
                    , 'name' => 'KeyBoard'
                )
            )
        )      
    )
    , 1 => array
    (
        'User' => array
        (
            'id' => 2
            , 'name' => 'Bob'
            , 'Item' => array
            (
                0 => array
                (
                    'id' => 3
                    , 'name' => 'CD'
                )
            )
        )
    )
    , 2 => array
    (
        'User' => array
        (
            'id' => 3
            , 'name' => 'Jim'
            , 'Item' => array
            (
                0 => array
                (
                    'id' => 4
                    , 'name' => 'USB Stick'
                )
                , 1 => array
                (
                    'id' => 5
                    , 'name' => 'MP3 Player'
                )
                , 2 => array
                (
                    'id' => 6
                    , 'name' => 'Cellphone'
                )
            )
        )
    )
);

Now here is how I would have traditionally turned this into a 'User.name' => 'User.items' array:

$userItems = array();
foreach ($users as $user)
{
    foreach ($user['User']['Item'] as $item)
    {
        $userItems[$user['User']['name']][] = $item['name'];
    }
}

But using the new Set class this is still pretty much a simple one-liner (split up in multiple lines so you don't have to scroll):

$userItems = array_combine
(
    Set::extract($users, '{n}.User.name')
    , Set::extract($users, '{n}.User.Item.{n}.name')
);

Both methods will output:

Array
(
    [Felix] => Array
        (
            [0] => Mouse
            [1] => KeyBoard
        )

    [Bob] => Array
        (
            [0] => CD
        )

    [Jim] => Array
        (
            [0] => USB Stick
            [1] => MP3 Player
            [2] => Cellphone
        )
)

"But doesn't it cost more performance to loop through the array twice in the Set example?" I hear some of you cry. Yes it does. And? Have you built your application yet? Does it implement all features you are dreaming of? And most importantly: Do your web stats indicate you are going to have 1 million hits / day soon? If so go back into your code and remove the Set example with the less succinct foreach alternative. If not, listen to Chris Hartjes who's motto for 2007 is Just Build It, Damnit!.

Anyway, here comes my last fun thing to do with Set::extract - parsing an RSS feed for all post titles. For my example I'll use the new XML class in Cake 1.2. Right now Set::extract only supports arrays but hopefully it will either natively support Xml objects at some point, or the Xml class get it's own extract function. For now I've written a little function that can turn an Xml instance into an array that looks like this:

function xmltoArray($node)
{
    $array = array();
   
    foreach ($node->children as $child)
    {
        if (empty($child->children))
        {
            $value = $child->value;
        }
        else
        {
            $value = xmltoArray($child);
        }
       
        $key = $child->name;
       
        if (!isset($array[$key]))
        {
            $array[$key] = $value;
        }
        else
        {
            if (!is_array($array[$key]) || !isset($array[$key][0]))
            {
                $array[$key] = array($array[$key]);
            }
           
            $array[$key][] = $value;
        }
    }
   
    return $array;
}

So now let's assume we would want to extract all post titles from my feed: http://feeds.feedburner.com/thinkingphp we could leverage the Set class to make our code as succinct as:

uses('Xml');

$feed = xmltoArray(new XML('http://feeds.feedburner.com/thinkingphp'));
$postTitles = Set::extract($feed, 'rss.channel.item.{n}.title');

Which will give you a $postTitles array like this:

Array
(
    [0] => How-to: Use Html 4.01 in CakePHP 1.2
    [1] => Looking up foreign key values using Model::displayField
    [2] => Bug-fix update for SVN/FTP Deployment Task
    [3] => Access your config files rapidly (Win32 only)
    [4] => Making error handling for Model::save more beautiful in CakePHP
    [5] => Full content RSS feed
    [6] => Visual Sorting - Some Javascript fun I had last night
)

Now that's beauty right there and a good way to end this post ; ). Take a look at the Set classes source to find out about some other cool methods it has, but to me this is by far the coolest.

-- Felix Geisendörfer aka the_undefined

 

Access your config files rapidly (Win32 only)

Posted on 12/2/07 by Felix Geisendörfer

For the past 2 days I've been setting up my new desktop pc. If you are like me and you run 9999 different apps, all of them with custom configurations and also need to setup php4+php5 and mysql4+mysql5 to run parallel you can probably feel my pain ; ). Anyway while setting up my WAMP stack, I had to tweak my configuration files all the time. At some point I got tired of looking for them after having previously closed them thinking I was done and decided to automate things. I knew AutoHotkey was capable of scripting custom GUI's for a while, but never got around to use it, so I thought I should give it a try. The result is a neat little script that opens a window where I can pick my configuration file and have it opened for me in notepad (which is Notepad++ in my case):

Custom AutoHotkey Configuration File Loader

If this sounds like something you could need as well, here comes the source for it:

[text]#c::
Gosub, ConfigWindow.Show
return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Class ConfigWindow
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ConfigWindow.Show:
Gui, +LabelConfigWindow.Gui. -MinimizeBox
Gui, Add, DropDownList, Choose1 vConfigFile x6 y7 w150, hosts|httpd.conf|httpd-vhosts.conf|php.ini (php4)|php.ini (php5)
Gui, Add, Button, Default gConfigWindow.Buttons.OpenFile.click x166 y7 w90 h20 , Open File

Gui, Show, Center h35 w269, Select Configuration File
return

ConfigWindow.Buttons.OpenFile.click:
Gui, Submit

if (ConfigFile = "hosts")
{
Run notepad C:\WINDOWS\system32\drivers\etc\%ConfigFile%
}
else if (ConfigFile = "httpd.conf")
{
Run notepad %A_ProgramFiles%\Apache\Apache2.2\conf\%ConfigFile%
}
else if (ConfigFile = "httpd-vhosts.conf")
{
Run notepad %A_ProgramFiles%\Apache\Apache2.2\conf\extra\%ConfigFile%
}
else if(ConfigFile = "php.ini (php4)")
{
Run notepad C:\windows\php.ini
}
else if(ConfigFile = "php.ini (php5)")
{
Run notepad C:\php\php5\php.ini
}

ConfigWindow.Close:
ConfigWindow.Gui.Close:
Gui Destroy
return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;[/text]

All it does is to bind the hotkey [Windows-key]+[C] to a function that will generate the little GUI where you can select a configuration file and then load this file via notepad if the ok button is pressed. The source looks a little messy and this is probably partly due to the fact that I just started to get into Autohotkey scripting today (not to speak of my pathetic OOP attempts ^^), but I think the language itself isn't quite elegant to begin with either. Anyway, I certainly got the job done in less than 30 minutes and I'm very pleased with the results. So let me know what you think of little development helpers like this and how your favorite shortcut-hacks look like ; ).

-- Felix Geisendörfer aka the_undefined

 

Visual Sorting - Some Javascript fun I had last night

Posted on 26/1/07 by Felix Geisendörfer

Hey folks,

last night was one of those rare occupations where I decided to do a school assignment. Now, don't get me wrong, I'm definitely not one of the "school sucks - it's boring" people who have very little passion for acedemics. In fact, quite the opposite is true - I love to learn new things and I get exited about the work of great writers, composers, physicists, philosophers, etc. However the school that I go to is full of teachers who seem to be totally passionless about their profession. It's simply no fun if you ask a question and the teacher answers you: "I don't know, this is not part of the curriculum". So if it wasn't for the Diploma to get me into a good University, I would have probably tried to quit regular school a while back in order to teach myself.

Anyway, this was just some ranting I felt like sharing and somewhere in June I'll be finally done with this daily waste of time. Back to that school assignment I did: It was a project in my computer science class and we were given about 1 semester of time to finish it. So of course I didn't get started on it until last night. Now my initial motivation for doing it was that it would make up a significant percentage of points in that class, and my ego couldn't become friends with doing poorly in a computer class. So I started doing some research on the topic I picked: "Visualizing of sorting algorithms". Being spoiled by working with high level programming languages for most of my life I couldn't recall that I had ever needed to implement such an algorithm myself before. Well, it turned out to be less of an issue since Wikipedia had enough material on the topic, including some sample code that I could simply port to the language of my choice. In this case I decided to go with JavaScript. Now as I started to gather ideas and writing some code, the entire thing became more and more interesting. I was able to fully test my JavaScript object orientation skills and there were tons of tricky issues to overcome. I ended up getting so involved in the entire thing that I didn't get to bed before 05:30am (I usually get up at that time). But much to my surprise I felt great after waking up only ~3 hours later this morning - it seems like some good old inspired late night coding is still one of the best ways to raise your energy level ; ).

But enough talk, the result of my work can be seen here: Visual Sorting Demo. It's all in German, but the text is of no importance anyway. What is important however is that this requires JavaScript 1.7 which is only available in Firefox 2.0 right now (afaik). I decided to give this new JS version a try because it has very cool new features (such as Generators), and I can worry less about compatibility as this doesn't need to run for everybody. I've learned a lot of things while writing this code, including how to do the jQuery trick of turning a given object into an Array and I hope I'll have some time to write in more detail about this in future.

For those of you who are too lazy to read the text and click at the link (you know who your are ^^) or people without FF 2.0, here is a screenshot that should give you a good idea of what the result looks like:

Sorting Visualiziation

So anyway, I just felt like sharing the results of this little project and maybe somebody finds it interesting ; ).

-- Felix Geisendörfer aka the_undefined

 

A PHP developers guide to JavaScript - Part I

Posted on 30/12/06 by Felix Geisendörfer

Hey folks,

after the overwhelming interest in me writing a little bit about javascript on this blog, here comes my first post on that topic. It's actually the beginning of a little series (2 or 3 parts) that is going to be targeted at php developers who've only used JS by merging snippets/libraries together without really learning the language itself. For all of those posts please keep in mind that I'm mainly on the php site of town as well, but my recent trips to downtown javascript have hopefully taught me well ; ).

The JS developer toolkit

Coming from PHP you are probably spoiled with some good developer tools, especially Zend Studio or Ecplipse PHP. In javascript land there are not as many good editors out there. From the ones I've worked with I would recommend Notepad++. It's very light weight, supports numerous languages, can completely replace your windows notepad and I use it for all my JS (and CSS) needs right now. If you don't run the beautiful microsoft operating system (...) you might find Aptana interesting. It's based on eclipse and therefor runs on most major operating systems. It also has advanced support for code completion and some other gimmicks. Personally I don't use it because it's too memory hungry, especially when my php IDE is already opened. Other then that it's a really comfortable IDE to work with.

Now that you have an editor (no matter which one) you might want to start coding right away. But you shouldn't. Not because you couldn't, but because you still lack the most important JS (and web dev) tool that is available these days: Firebug. Without any question, it is the most advanced debugging tool out there. So please take some time to get to know it's features and learn how to use it, it will make your day.

The Basics

As all of you are already familiar with coding in php, I won't start at "Hello World", but I'll try to show some important differences in JS (from PHP) and point you to some resources for further reading. The first one would be gotAPI.com. If you see me using a function you are not familiar with, that's always a good place too look it up. (Side note: It also has support for the CakePHP/jQuery API).

Variables:

Just like PHP, javascript is a weakly typed language. This means you won't have to declare variable types (even so you can) and all variable types are converted to strings automatically when needed. The symbol used for gluing different strings together is '+' and not '.' like in php.

var myVariableA = 'Thinking';
var myVariableB = 'PHP'

// Logs: ThinkingPHP
console.log(myVariableA+myVariableB);

Oh and in case you wonder: 'console' is no default javascript object. It's only available when using firebug (or Firebug Lite). The usual JS way to get information is to use the alert() function. But the first time you produce and endless loop with an alert in it, you'll get to appreciate the console.log function offered by Firebug ; ). Another thing you just learned is that object functions/properties are accessed via '.' (as opposed to '->' in php), but more about this later.

Arrays

The good news is, javascript has decent support for indexed arrays. The bad news is, it's lacking associative arrays (that we php folks all know and love). But don't you worry, there is a workaround to fix this.

Working arrays in JS is very much like working with arrays in php. The only difference is that arrays (like everything else) in JS are actual objects not only variables containing literals. But that's a no-brainer, just have a look at the following examples:

// Note: var myArray = []; works just as well).
var myArray = new Array();

// Assign three values to the array.
myArray[0] = 'ThinkingPHP';
myArray[1] = 'suddenly';
myArray[2] = 'thinks';

// Now to show you we are working with an object:
console.log(myArray.length); // Will log: 3

// myArray[] = 'value'; does not work in javascipt, so we have to use Array.push
myArray.push('JavaScript');

// Will log: ThinkingPHP suddenly thinks JavaScript.
console.log(myArray.join(' ')+'.');

Now without really getting to the objects part yet, the Arrays are a good example to show another default behavior of the javascript language:

// This is notation to declare an array with one or more values (comma separated)
var varA = ['Thinking'];
var varB = varA;

// Push PHP at the end of array varB
varB.push('PHP');

// Display the results: ["Thinking", "PHP"]
console.log(varA);

In case the result of this little test surprises you, here is what happened: When executing 'var varB = varA;' you are not simply copying the value of variable 'varA', you are actually creating a reference to it. So if we would want to port this snippet to PHP, it would look like this:

$varA = array('Thinking');
$varB = &$varA;

// Push PHP at the end of array varB
array_push($varB, 'PHP');

// Prints: Array ( [0] => Thinking [1] => PHP )
print_r($varA);

So in case you actually wanted to create a copy of varA, this is how to do it in JS:

var varA = ['Thinking'];

// Clone the array
var varB = new Array();
for (var i=0; i<varA.length; i++)
{
    varB[i] = varA[i];
}

varB.push('PHP');

// Logs: ["Thinking"]
console.log(varA);

// Logs ["Thinking", "PHP"]
console.log(varB);

Of course there are ways to do this in a more elegant way by creating a clone function, but let's keep it simple for now. Let's go back to what I said at the beginning of the array section: "Javscript does not support associative arrays". Yes, this is really a bummer at first. But the workaround is fairly simple, all it takes is to use objects instead:

Objects

One of the things that really have caused my to like javascript a lot recently is the great flexibility it offers. This really shows when working with objects the first time. In PHP you think of an object as an instance of a specific (fixed) class that basically consists of functions and properties. Well this is true for objects in JS as well. They have functions and properties. Where JS is different is the way you create an object. Unlike PHP where you define types of objects as classes, in javascript you often create objects on the fly. Another important point is that JS objects often do behave like associative arrays in php as well (but they are usually called hashes in JS).

Just consider the following example:

// same as var myJsObject= {};
var myJsObject = new Object();

// This looks like a typical associative array in PHP
myJsObject['varA'] = 'Thinking';
myJsObject['varB'] = 'PHP';

// But what's that? We are attaching a function to the object on the fly
myJsObject['ThinkingWhat'] = function()
{
    // 'this' refers to the class this function resides in here. However this is not always the case in JS, more about this later.
    console.log(this.varA, this.varB);
}

// Call the ThinkingWhat function
myJsObject['ThinkingWhat']();

// is the same as
myJsObject.ThinkingWhat();

Ok, I hope you are not too confused right now. You basically have to accept that in Javascript there is no difference between the object['propery'] and object.property notation. Another good example would be the little Array snippet we've already used:

var varA = ['Thinking'];
var varB = varA;

// Instead of varB.push('PHP') we can write:
varB['push']('PHP');

// Display the results: ["Thinking", "PHP"]
console.log(varA);

Now that you know this little JS secret, it's time to show you another one. The object we created on the fly a second ago, could have equally well been written like this:

var myJsObject =
{
    varA: 'Thinking',
    varB: 'PHP',
    ThinkingWhat: function()
    {
        console.log(this.varA, this.varB);
    }
};

The two syntaxes are equal to one another. One of the most popular usages of the second syntax right now is JSON. So if you didn't know what JSON was so far, it's simply sending javascript objects via AJAX and then eval'ing them on the client site so they can directly be accessed. However, please keep in mind that in JSON you always have to put quotes around object properties (!).

Functions

Alright, even if you've not done much JS coding so far, you've probably seen a simple JS function before:

// Logs: ThinkingPHP
console.log(ThinkingWhat('PHP'));

// A simple function with one argument
function ThinkingWhat(what)
{
    // Return 'Thinking' plus the contents of the what argument
    return 'Thinking'+what;
}

Now before I'll continue and reveal javascript's dark & nasty secret about functions (don't be scared, it's actually cool once you know how to deal with it), I want to show you a little bit more about passing arguments:

// Logs: ThinkingPHP
console.log(ThinkingWhat('PHP'));

// Logs: Thinkungundefined (and display no error)
// This means: Unlike in PHP, all function arguments in JS are optional on default (!)
console.log(ThinkingWhat());

// A simple function with one argument
function ThinkingWhat(what)
{
    // Return 'Thinking' plus the contents of the what argument
    return 'Thinking'+what;
}

// Logs: ThinkingPHP
console.log(FreeThinking('Thinking', 'PHP'));

function FreeThinking()
{
    // In all functions the arguments object is available. It's an array
    return arguments[0]+arguments[1];
}

Alright, this should be simple to understand. The two main lessons learned here are that in JS all function arguments are optional on default, and you always have access to an arguments object inside a function. You can think of it similiar to "$arguments = func_get_args()" in PHP. Now one thing you might miss is the ability to assign default values to parameters (like you do with optional parameters in PHP). Here is what to do:

// Logs: ThinkingPHP
console.log(ThinkingWhat());

// Logs: ThinkungJS
console.log(ThinkingWhat('JS'));

// A simple function with one argument
function ThinkingWhat(what)
{
    // If what doesn't contain a value (that would evaluate to true) then assign 'PHP' to it
    what = what || 'PHP';
   
    // Return 'Thinking' plus the contents of the what argument
    return 'Thinking'+what;
}

I really like the elegance this has. I know I often find myself writing if statements for checking empty variables and overwriting their contents with something else in PHP, so this is definitely a pretty cool feature of the JS language.

Ok, do you still remember me talking about the JS dirty little secret about functions? Well here it comes: The things what we just have looked at as 'functions' are actually a little bit more in JS. They are fully qualified class definitions (and btw. the only way to create them). This is probably the point were PHP developers feel odd and I agree that this takes a while to get used to. Nevertheless, check out this example to see how your innocent little function becomes a class:

// Defines a class named Blog
function Blog(url, author)
{
    this.url    = url;
    this.author = author;
   
    this.visit = function()
    {
        // Redirects the user to the url of this blog
        window.location.href = this.url;
    }
}

// Creates an instance of the Blog class called ThinkingPHP
var ThinkingPHP = new Blog('http://www.thinkingphp.org', 'Felix Geisendörfer');

// Logs: 'http://www.thinkingphp.org'
console.log(ThinkingPHP.url);

// Navigates to 'http://www.thinkingphp.org'
ThinkingPHP.visit();

Now as you can see the Blog function now doesn't just serve as a static function any longer. It has become the constructor and wrapper for the class Blog that contains two member variables (url and author) as well as one function (visit).

One of the most powerful JS features related to classes is the prototype property. It is a list of all members any given class has and can also be used to modify already created classes (and all instances of it). You've probably heard about the Prototype framework which makes extensive usage of this feature by adding new functions to already existing DOM element classes. Personally I don't use this feature very often and prefer to not modify existing objects (that's also one of the reasons I prefer jQuery over Prototype), but here is how to work with the prototype property regardless of that:

// Defines a class named Blog
function Blog(url, author)
{
    this.url    = url;
    this.author = author;
}

// Creates an instance of the Blog class called ThinkingPHP
var ThinkingPHP = new Blog('http://www.thinkingphp.org', 'Felix Geisendörfer');

// This adds the function getAuthorFirstName to *all* Blog classes (also already created instances like ThinkingPHP)
Blog.prototype.getAuthorFirstName = function()
{
    // Splits the author string at ' ' and returns the first value of that array
    return this.author.split(' ')[0];
}

// Logs: 'Felix'
console.log(ThinkingPHP.getAuthorFirstName());

As you can see, even so we added the getAuthorFirstName function after we created the ThinkingPHP instance of the Blog class, it's still available in the instance of it. If you wonder of the practical usage of this: I find this feature pretty useful to modify 3rd party JS classes without actually touching the source of them. Because just like you can add class members using their prototype property, you can also overwrite existing ones.

This was part I

Alright, this was the first part of my 'A PHP developers guide to JavaScript' series. I hope you guys enjoyed it and I was able to communicate the most important aspects of JS in an easy-to-understand manner. I'm sorry it took me so long to publish this, but writing this required several hours so I had to split it up over several days. If you find errors, have questions or want to suggest anything I'd appreciate a comment ; ).

For Part II, I plan to cover things like scope, events, some JS coding techniques and will probably also start talking about jQuery a little bit. Meanwhile I'd recommend everybody to read Sergio Pereira's Quick guide to somewhat advanced JavaScript which I think has a lot of good information in it and is a little bit more detailed then my post on here.

Oh and before I forget: A Happy New Year to all of you! (Sorry I didn't care enough about Christmas to send out a greeting ^^). And also Happy Birthday to Daniel's cakebaker blog that just turned 1 year old!

-- Felix Geisendörfer aka the_undefined

 
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57