Tuesday, February 9

Get Age From Birthday

function GetAge($Birthdate){

// Explode the date into meaningful variables
list($BirthYear,$BirthMonth,$BirthDay) = explode("-", $Birthdate);

// Find the differences
$YearDiff = date("Y") - $BirthYear;
$MonthDiff = date("m") - $BirthMonth;
$DayDiff = date("d") - $BirthDay;

// If the birthday has not occured this year
if (($MonthDiff < 0) || ($MonthDiff == 0 && $DayDiff < 0))
$YearDiff--;
return $YearDiff;
}
Shamelessly stolen from:
Geekpedia

edited: apparently neither
($MonthDiff >=0 && $DayDiff >= 0)
nor
($DayDiff < 0 || $MonthDiff < 0)
were taking all the math correctly into account. We managed to figure out something better.

Friday, February 5

Setting up DB2 for PHP on CentOS

Using a remote DB2 8.1 database with php 5.1.6 and Apache 2.2 on CentOS 5.4 (64 bit)

To download the IBM drivers for DB2, go to the IBM page and click on DB2 Client. If you don't have an account, you can request a free one. After logging in, download the IBM Data Server Client for linux. I also installed Express-C. I think this gave me some of the scripts I needed, though I'm not exactly sure. It was pretty self explanatory, download, extract, run the install script.

These instructions assume you've installed apache (httpd) via yum

# yum install php-pear
# mkdir /opt/ibm/db2/
# cp ./ibm_data_server /opt/ibm/db2/
# cd /opt/ibm/db2
# tar -xzf ibm_data_server
# cd dsdriver
# sh ./installDSDriver
# pecl download ibm_db2
# pecl install ./ibm_db2
when it asks for the installation dir, tell it: /opt/ibm/db2/dsdriver

Now we set up php to use the driver.
# vi /etc/php.ini
Find the section of Dynamic Extensions and add
extension=ibm_db2.so
To make sure it worked, run:
# php -i | grep -i db2
and you should get some results like:
ibm_db2
IBM DB2, Cloudscape and Apache Derby support => enabled
Binary data mode (ibm_db2.binmode) => DB2_BINARY
DB2 instance name (ibm_db2.instance_name) =>
PWD => /opt/ibm/db2/dsdriver
OLDPWD => /opt/ibm/db2/dsdriver/bin
.... and so on

So this is where I got stuck for a long time. At this point, db2_ commands will work from php, but it won't be able to find any database. All the documentation I read said things like "if you've created a db2 instance, point to it in your php.ini file" and stuff like that. It took me much longer to find how to actually create the instance.

Add the user db2inst1 (this is the default name php looks for an instance, but it can really be whatever.) I also put the user in a group I made (called db2grp1), but I don't think you need to.
# useradd db2inst1
# ./opt/ibm/db2/V8.1/instance/db2icrt db2inst1
This creates the instance, which basically adds sqllib and bin dirs to the home directory, and changes the .bashrc. I also don't know where this script came from, maybe from Express-C.

You can see all instances using:
# /home/db2inst1/sqllib/bin/db2ilist
If you want, you can go ahead and add this to the php.ini right now. I couldn't find anywhere that said where it had to be, so I just stuck it at the end before the end tag.
ibm_db2.instance_name=db2inst1
This will update the results when you do a php -i|grep -i db2 as well.

Now we have an instance, php knows where it is, but the instance doesn't know where the database is. If you're on a local database, it's easy, but I'm not so I have to configure the local instance to go find the remote db.
su to the instance user.
# su db2inst1
$ db2
=> catalog tcpip node remoteinst remote hostname.or.ip server 50000
=> catalog database testdb as remotedb at node remoteinst authentication server
=> terminate
This created the alias to the server and to the database. Everything in italics should be your information. To test this has worked type
=> connect to testdb user username using password
Your connection should succeed and tell you database information:
Database Connection Information

Database server = DB2/6000 8.1.6
SQL authorization ID = USERNAME
Local database alias = TESTDB
If it fails, it should tell you why.
SQL30082N  Attempt to establish connection failed with security reason "24"
("USERNAME AND/OR PASSWORD INVALID"). SQLSTATE=08001
Now we're almost done (finally). We just need to put the alias info into the db2dsdriver.cfg where php looks for it. To do this, there's another fancy IBM script, yay! This one should be in /opt/ibm/db2/dsdriver/bin, assuming you followed my instructions up above.
# cd /opt/ibm/db2/dsdriver/bin
# ./db2dsdcfgfill -i db2inst1 -o /home/db2inst1/sqllib/cfg
That should fill in the db2dsdriver.cfg file. It took mine a few tries to succeed, but I don't think i did anything differently, so I'm not sure why it was failing. If it keeps failing, try moving the script to /home/db2inst1/sqllib. Maybe I did that to make it work.

One last thing before it'll work (and yes, I forgot this at first)
# httpd -k restart
That should be it. In php using the connection string, for the database, use the alias you created with the catalog commands.
<?php
$conn = db2_connect('testdb', 'user', 'password');
if (!$conn) {
echo "Connection failed.". db2_conn_errormsg();
}?>
Resources:
php.net
IBM Library
db2ude
sqlrelay
and 100 more google search results

Anomaly:
Since the db2dsdriver can only be updated (in my experiences) using
db2dscfgfill -i db2inst1 -o /home/db2inst1/sqllib/cfg
that means the db2dsdriver located in /opt/ibm/db2/dsdriver/cfg will need to be updated manually. Even though php knows the instance is db2inst1, it still seems to look in the opt location for the driver. When I updated the driver, I just copied and overwrote the one in opt, then restarted the httpd server and all was fine. Before I overwrote the old one, it wasn't finding the alias.

Thursday, January 14

Disable Submit Button until Valid

The Case:
There are three search options, first name, last name or id number. You can search by any one (or more) but at least one has to be filled. I want to disable the submit button until one is filled, and have a little message that says you need to fill one.

The Javascript:

function searchVal(){

if(document.getElementById('fname').value.length == 0
&& document.getElementById('lname').value == 0
&& document.getElementById('datanum').value == 0){
dis = true;
}else dis = false;
if(dis == true){
document.getElementById('sub').disabled = true;
msg = 'At least one search field must be filled';
}else{
document.getElementById('sub').disabled = false;
msg = '';
}
document.getElementById('validmsg').innerHTML = msg;
}

function formVal(){
document.getElementById('sub').disabled = true;
document.getElementById('validmsg').innerHTML = 'At least one search field must be filled';

document.getElementById('fname').onchange = searchVal;
document.getElementById('lname').onchange = searchVal;
document.getElementById('num').onchange = searchVal;
}
The HTML:
<form action="results.php" class="pform" method="get">
<ol>
<li><label for="lname">Last Name</label><input type="text" name="lname" id="lname" /></li>
<li><label for="fname">First Name</label><input type="text" name="fname" id="fname" /></li>
<li><label for="num">ID Number</label><input type="text" name="num" id="num" /></li>
<li id="validmsg"></li>
<li><label for="submit"></label><input type="submit" name="sub" id="sub" value="Search" /></li>
</ol>

Other Comments:
The form items are in a list to look pretty as suggested by this ALA Article. The js is two functions because I'm using this handy dandy window onload manager.

Maybe tomorrow I'll be able to figure out how to check that the ID number is numeric.

Note: I found out later that even though the button is disabled, you can still submit the form using Enter.

Wednesday, December 30

The File You're Looking for is...

/templates/system/html/modules.php

This is where you can change the module titles from h3 to h2. Also from tables to not tables. I tried to copy this like normal to the template/html folder, but I get some error that makes me angry, so just forget about that.

I changed all the tables to divs too cause tables make me sad. I think pretty much the one that my site uses is like this:

function modChrome_xhtml($module, &$params, &$attribs)
{
if (!empty ($module->content)) : ?>
<div class="moduletable->get('moduleclass_sfx'); ?>">
<?php if ($module->showtitle != 0) : ?>
<h2><?php echo $module->title; ?></h2>
<?php endif; ?>
<?php echo $module->content; ?>
</div>
}
<?php endif;
}

Wednesday, September 9

Literal... Interpreted... Quotes Matter

Something I never before (but probably should have) read that took hours to figure out:

If the string is enclosed in double-quotes ("), PHP will interpret more escape sequences for special characters. -via php.net
This really matters!!

For example. When you are sending mail, new lines are usually \r\n. If you have your message body in single quotes (') the \r\n will show up in the message!

Even more importantly. When using Pear mime, you have to define eol. Just remember $crlf = '\r\n' WILL NOT WORK!!! it has to be $crlf = "\r\n" If I had read to the bottom of the user comments on the pear page, it would have saved me a lot of time.

And yes, this post is actually about doing Mail_Mime with Pear :D

Resources:
PEAR Mail_Mime
PHP Maniac Examples

Tuesday, September 8

getdate() Without Time

I am rewriting the front end of a site, but can't change the back end or functionality. And so I must learn how to use an MSSql db via php. My first impression... they are not friends. Okay, moving on.

Problem:
Table has a datetime field, but the backend freaks out because of some 10yr old stored procedure. We traced it down to that the datetime can't contain the time.

Solution:
Now, I could have just used php date, but the day I found out that databases can insert their own date, I swore I'd never use it again for a current timestamp. So instead, my insert statement has this completely inelegent line:

convert(varchar(8), getdate(), 112)
That inserts the date with no time. Or more specifically, the time is 12:00AM. The actual contents is something like Sep 08 2009 12:00AM.

Wednesday, September 2

Buttons Change on Hover

First time I've done this (I think). Short sweet code.

<a href="link"><img src="images/button.jpg"
onmouseover="this.src='images/buttonhover.jpg';"
onmouseout="this.src='images/button.jpg';" /></a>
It would not surprise me if there were instances where this fails. But it's also pretty nice because you don't have to worry about a no js version; it just doesn't do anything without js.

If you're using large images, you might want to preload them.