Wednesday, July 07, 2010

Colonary Surgery

I don't get the opportunity to do a vast amount of XML parsing, but when I do I use SimpleXML because it's normally built into PHP and is just so...well, simple. Unfortunately, there are some things SimpleXML can't handle. One of these is colons in tag names. Google's API's use colons in abundance, such as when giving the ID of a YouTube video: <yt:videoid>.


For now, I don't have time to learn another parser, so here's a quick and dirty solution I found in a few places on the web: remove the colons!

$newXmlStr = preg_replace("/(<\/?)(\w+):([^>]*>)/", "$1$2$3", $xmlStr);


Works well for me, though I'm sure if you are one of those who parse vast amounts of XML, this solution would be too low-performance. But for the occasional fetching of a YouTube video or Picasa album, it can be a very useful line of code.

Labels: , ,

Wednesday, December 16, 2009

Foreach His Own

I quite often use the foreach() loop in PHP to easily modify an array by passing the array elements by reference, like so:
$testArray = array('one','two','three');

foreach($testArray as &$v) {
// whatever
}


But today this got me into a little trouble. The variables created by the foreach() loop, whether passed by reference or not, continue to exist after the loop has been closed. Thus in the previous example, after the loop is finished $v will continue to point to the last element of $testArray, by reference. Using another foreach() with the variable $v will alter the values in $testArray.
$testArray = array('one','two','three');

foreach($testArray as $k => $v) {
echo "$k: $v<br />";
}

foreach($testArray as &$v) {
// whatever
}

foreach($testArray as $k => $v) {
echo "$k: $v<br />";
}


In this example, the first foreach() echoes the original content of the array. The second foreach() does nothing except pass the array elements by reference. The third foreach() attempts to echo the content of the array but inadvertently alters its last element.

Output:
0: one
1: two
2: three
0: one
1: two
2: two


The first foreach() uses $v to store values and thus leaves it containing the value of the last element of the array. The second foreach() uses $v to hold references and thus leaves it containing a reference to the last element of the array. The third foreach() tries to store values in $v just like the first one did, but this time it contains a reference, so the values are actually stored in the last element of the array, overwriting whatever value was there before.

To make sure this doesn't happen to you like it did to me, simply unset the variables your loops use for references:
foreach($testArray as &$v) {
// whatever
}
unset($v);

Labels:

Tuesday, November 17, 2009

Checkbox: Finding the TRUE value of "checked"

Today I had a familiar scenario that turned not-so-familiar, so I'll post my results here for future reference. The core problem is that the value of this.checked within jQuery's .click(function({})) changes depending on what triggered the click event.

The setup: In a form, I had one checkbox which, when clicked, would set some fields based on the values of other fields (think "Click here if same as contact info"). The request: put in a couple more checkboxes for the same fields (now also think "Click here if same as Director info" and "Click here if same as Housing info"). The problem: 1) I can't simply copy field values. I need to save the old ones in case they uncheck the box. 2) Only one checkbox can be checked at a time, i.e., these really need to work more like radio buttons, but at the time, I didn't want to use radio buttons.

Still, it should be pretty simple. I would use jQuery to set the click event function of each checkbox which would save the old values and insert the new. But before it did that, it would need to uncheck any previously checked boxes. I figured I'd do this last bit by using jQuery's .click() to trigger the click event of any checked boxes. And there I found a surprise.

Example HTML:
<form name="newForm" action="" method="post">
<input name="checkbox1" id="checkbox1" value="1" type="checkbox" />
<label for="checkbox1">Checkbox #1</label>
<input name="checkbox2" id="checkbox2" value="2" type="checkbox" />
<label for="checkbox2">Checkbox #2</label>
</form>


Example Javascript:
$(function() {

$('#checkbox1').click(function(e) {
if(this.checked) {

// uncheck the other box by triggering its click event
if($('#checkbox2').attr('checked'))
$('#checkbox2').click();

// do something important
}
});

$('#checkbox2').click(function(e) {
if(this.checked) {

// uncheck the other box by triggering its click event
if($('#checkbox1').attr('checked'))
$('#checkbox1').click();

// do something important
}

});

});


Theoretically, checking one box should uncheck the other (or leave it unchecked if it already is). However, that doesn't actually happen. The value of this.checked changes depending on how what triggered the event.

If the click event of the checkbox is triggered by a mouse click on that same checkbox, this.checked is the new value. But if the click event of the checkbox is triggered by jQuery's .click(), as in the above example, this.checked is the old, previous value that will be changed in a few milliseconds. Thus, if the user has just checked the box with a real mouse click, this.checked equals true. But if your script just checked the box with .click(), this.checked equals false.

If, for whatever reason, you need to know whether a checkbox is being checked or unchecked, and the click event may have been triggered by jQuery's .click() method, you cannot simply rely on this.checked.

What I did to get around this was to pass as an argument to the click function a reference to the node where the click occurred. If the event was triggered by another event, I toggle the value of checked.
$('#checkbox1').click(function(e,node) {
var checked = this.checked;
if(typeof(node)!='undefined')
if(node.getAttribute('id')!='checkbox1')
checked = !checked;
....
});

$('#checkbox2').click(function(e,node) {
....
$('#checkbox1').trigger('click',this);
});

Labels: ,

Javascript Toggle Boolean

I'll put this here for future reference. Found it on the blog of Jerad Bitner.

var bool = true;
bool = !bool // it's been toggled!

Labels:

Monday, August 04, 2008

The Threat of shell_exec()

Just built a website? Think it's safe? If you're using shared hosting, think again. On many shared servers, it's a cinch to get a list of every file the web server can see, including configuration files. Hey and guess what, if you've got the config files for every site on the server, you know every site's database login credentials and could wipe out all their data or, better yet, make yourself admin accounts on every site. Everyone on your server has this power.

Are you vulnerable? Try it and find out. Log into your FTP account, and create (or find) a web accessible directory that is also world-writeable. Make a PHP file in that directory and put this in it (btw, this is Linux specific):


<?php
$f = fopen('list.txt',w);
if (fwrite($f,shell_exec('ls -AR /'))) echo '<a href="list.txt">download list</a>';
fclose($f);
?>


This is a big task, so on servers with lots of sites it could time out. You may want to try "echo shell_exec('pwd')" first, so you know what directory the sites are hosted in, and then change the ls command to only read that directory (i.e., 'ls -AR /www' instead of 'ls -AR /'). Or maybe even remove the 'R' from the command.

Now go through that list looking for config files, and try some file_get_contents(). ;-) (Actually, don't do that. You could get in trouble.)

Labels:

Sunday, July 20, 2008

VirtualBox: openSUSE in Mac OS X

In the summer of 2005, Linux came into my life. I know to many of you that doesn't seem like so long ago, but in the years since I have developed quite an attachment to Linux; without it nearby I feel lost and trapped, and somehow computers just aren't as fun. But now I have a MacBook(!), and I must confess, Leopard is quite a piece of software. Still, Linux is Linux. 'Nuf said. Normally I'd just dual boot, but this time I thought I'd try virtualization. With a Core 2 Duo 2.4 GHz and 2 GB of RAM, why not?

Following is an instructional record of how I've installed openSUSE 11 in VirtualBox 1.6 on Mac OS X Leopard. (Ok so that was a mouthful).

Prerequisites:

First and foremost, you'll need to download and install VirtualBox. While you're getting that, also grab the 32-bit version of openSUSE 11, but don't burn it to a CD. I'm using the the KDE 4 LiveCD.

Creating a VM

Once you have VirtualBox up and running, go ahead and create a new virtual machine by clicking the "New" button. The wizard is very straightforward and easy to use. We'll call this new machine "SUSE" and set the OS Type as "openSUSE." We'll give it 512 MB of RAM instead of the default 256.
Part of setting up your new virtual machine is creating a virtual hard disk to go with it. VirtualBox allows you to make either a dynamically expanding drive which grows as you use more space, or a drive of fixed size. I've chosen to create a fixed-size drive of just 8 GB (of my 160) for openSUSE. Later on we'll create a shared folder for ~/Documents, so that anything we save in Linux will be easily accessible in OS X.

openSUSE Installation

Finish the wizard, and oila! there's your new machine. Power it on by hitting the "Start" button, and you'll arrive at another wizard. For "Media Type," choose CD/DVD-ROM, and for for "Media Source," click "Image File." Then browse for and select the openSUSE CD image you downloaded earlier. Finish the wizard and you'll soon see the familiar green of openSUSE.

Important!! For now, while your VM's window has focus it will capture your keyboard, and if you click inside it, it will also capture your mouse. To regain the use your mouse and keyboard in OS X, hit the left command button.


Close the initial welcome screen in openSUSE, and click the Install icon. The YaST2 installer should open. When setting your VM's time, be sure to clear the "Hardware Clock Set to UTC" checkbox. Otherwise your it'll be way off.


For partitioning, click the "Edit Partition Setup" button. Delete /dev/sda3 and resize /dev/sda2 to fill the resulting space. Then continue on with the wizard.

When the installer asks you to reboot and to eject the CD, just hit OK and reboot. As soon as it begins to boot again, click the little CD symbol at the bottom of the VM window to unmount the CD. If you don't get it before the VM starts reading the disk, don't worry. Just unmount it and boot from the hard disk. You'll probably get an error and the VM will reboot, this time not off the CD.



Pretty soon, you have a brand new install of openSUSE 11 completed. Congrats!


Guest Additions

Now I don't know about you, but the first thing I wanted to do was get openSUSE to fill my screen like Leopard--800x600 just doesn't cut it. To do this (and other things) correctly, you'll need to install VirtualBox's Guest Additions on the guest (openSUSE). Interestingly enough, openSUSE 11 probably installed Guest Additions automatically (at least it did for me). For whatever reason, the Guest Additions which come with openSUSE 11 don't seem to work, even if fully updated. So go ahead and uninstall them by going to YaST->Software Management (see screenshots). Search for "virtualbox", and get rid of 'em.

While you're at it, install gcc, make, automake, and kernel-source by searching for them and checking their boxes. When you're done, perform a full online update with YaST->Online Update (Guest Additions won't install without it).

Note: after the update, you should have a pretty good virtual install of openSUSE. Part of the fun of virtualization is that you can mess around as much as you want without really hurting things. Of course, if you do break your virtual machine, it'll still be a time consuming process to reinstall the OS, do an update, etc.. So before you go any further, make a snapshot of your VM's current state by first selecting your virtual machine in the main VirtualBox window, and then going to the Snapshots tab and clicking the camera button. That way, if your VM does go down the tubes, you you can just revert to your snapshot. You'll be up and having fun again in no time.

Now to install the ones that work, make sure your openSUSE window has focus, then click the "Devices" menu on the OS X menu bar, and click "Install Guest Additions" at the bottom. You'll probably notice some text appear on the VM status bar saying, "Mount the Guest Additions installation image."

So let's do that. Again in the Devices menu, go to Mount CD/DVD-ROM->CD/DVD-ROM Image. You should see VBoxGuestAdditions.iso. Select it. Now go into your SUSE VM. You'll need a command line, so hit fn+ctrl+option+F1 (just ctrl+option+F1 if you don't use your function keys for brightness controls, etc.). Login as root with the password you entered on installation, and enter these commands:

cd /mnt
mkdir cd
mount /dev/sr0 cd
cd/VBoxLinuxAdditions.run
reboot


Once openSUSE has rebooted, hit left command+F and left command+G. You'll probably need to logout before you see the effect, but that should make your openSUSE install recognize the correct resolution of your monitor and resize accordingly (hitting left command+F will take you back out of fullscreen mode).

Now the last thing we need to do is to make your Documents folder in Linux accessible in Mac OS X. First, let's make the shared folder. In the Devices menu, go to Shared Folders and in the Shared Folders window, click the folder icon with the plus sign to add one. Remember what you name your new share--you'll need to enter it into one of the following commands.


In openSUSE, get a command line again (fn+ctrl+option+F1 and login as root), and issue this command:

mount -t vboxsf [insert sharename] /home/[insert username]/Documents

Hit fn+ctrl+option+F7 to get back to the GUI. Congratulations! You've just found another use for your multiple cores! ;-)

Labels: , ,

Tuesday, July 18, 2006

Wonder of Wonders

My tri-boot system is up and running again, but with some serious improvements! Previously I was using Windows 98SE, SUSE Linux 10.0, and Ubuntu Linux 5.10. I've kept Ubuntu as is, but did some major remodeling in Windows and SUSE, the most important change being...I finally have Windows XP! And you know what? I really like it!


So far I've been very impressed by XP's speed. Based on my experience working with and repairing other people's XP machines, I had assumed that XP would barely run on my old box (AMD Duron 800 MHz, 320 MB RAM). After all, if a factory restore on a 2+ GHz, 512 MB RAM PC runs only moderately fast, or even somewhat slow, what chance would I have with such a fossil? To my amazement, it actually runs pretty well. Some tasks are even faster in XP than in Linux! I guess the problem with those factory restores wasn't XP. Maybe it was the factory...


On the topic of speed, I noticed something rather interesting. Generally speaking, both SUSE and Ubuntu are somewhat sluggish on my machine, Ubuntu usually being the faster of the two. ("Sluggish" is a relative term: I like fast computers :-P) But they're both consistent. File browsing, web browsing, software installation, starting large programs (Open Office), all proceed at their own predictable speed.


Windows 98 was typically faster than Linux, but was never as predictable. After years of usage, I could usually guess what tasks would be faster than others, but I'd still be surprised on occassion when a seemingly simple task would bog down my system. If anything, Windows XP is much worse. It'll zip along for a while at a pace noticeably faster than what I'm used to in Linux. Then without warning, it'll slow down to the point of being unusable. Given a minute or so, it'll be back to normal. It's more of a slight annoyance than a serious problem, but hey, I'm picky. :-D It'd be nice to figure out what causes that.


So overall, I'm excited about entering the world of the modern MS operating system. But just in case you're wondering, I'm still in love with Linux. :-D

Sunday, July 16, 2006

Reality

Ok. Here's the big test. I'm always saying Blogger looks better than Xanga, but is it really? Now I can find out the truth.