Thursday, December 23

Mailing with Joomla: HTML and Text

Joomla has its own built in version of PHPMailer called JMail. Hey, we set all these email configurations in the setup, might as well use those.

$mail =& JFactory::getMailer();
$config =& JFactory::getConfig(); //this is where we grab info from the config file
$mail->useSMTP(
$config->getValue('config.smtpauth'),
$config->getValue('config.smtphost'),
$config->getValue('config.smtpuser'),
$config->getValue('config.smtppass')
);
Now we can define more stuff for useSMTP, but unless your server settings aren't standard, you don't really need to. But it's all in the config file too, easy way to see it all is just to print_r($config).

Now, I want to send this email as html because it looks nicer, but I have to make sure to set a text only one for those people reading the emails on their phones, etc.
$HTMLmsg = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Message title</title>
</head>
<body>
<p>Here is some text. It looks nice.</p>
</body></html>';
$textmsg = "Here is some text. It looks ok.";
$mail->setBody(&HTMLmsg);
$mail->isHTML(true);
$mail->AltBody=&textmsg;
AltBody is a variable, not a function. It was carried over from PHPMailer. Setting this automatically sets the email to multipart/alternative. It is read by clients that don't have HTML email (like my phone), but clients that can read HTML will see the normal body.

Notice I used double quotes on $textmsg. This is in case I want to use newlines, php will escape them (I mentioned this earlier).

We're almost done. Just need a recipient, sender and subject. We can use another Joomla config value to set who the email is sent from.
$mail->addRecipient('example@example.com');
$mail->setSender($config->getValue('config.mailfrom'));
$mail->setSubject('Test email');
Well this seems good right? I spent a few minutes with this all set up, testing, but no email was sent. Oh yeah, forgot one very important line.
$mail->send();
Resources:

Friday, December 10

Ajax Username Check

I don't know about you, but I love cool ajaxy stuff. A lot of times, it's really simple to add this to a form. All I'm doing is checking to see if the requested username is in use or not.

So first, the html is the simple part. Just give your input box and id and add a section after it to hold the results:
<input type="text" name="uname" id="uname"><span id="checkuname">
Then I make a little php file with the to check for the username.
$u = $_GET['u'];
require_once('db.php');
if($u <> ""){ //we don't want to display anything if the field is blank
$checkname = query("SELECT * FROM table WHERE username = '".$u."'");
if(num_rows($checkname) <> 0){
$desc = 'Already in use';
}else{
$desc = 'Ok to use!';
}
}
echo $desc;
I style $desc to be something nice looking, or use an image of a checkbox or something, depending on how the rest of the form looks.

So then in the scripts, I look in the uname id and display in the checkuname id.
$('#uname').keyup(function(){
$('checkuname').load('checkuser.php?u='+$(this).val(), function(response, status, xhr){
if(status == "error"){
var msg = "There was an error: ";
$('#checkuname').html(msg + xhr.status + " " + xhr.statusText);
}
});
});

Thursday, December 9

Style a button as a link

I have an invisible form that I want the user to submit with a link, keeping the illusion that it's not a form. I know you could submit the form with javascript (onclick="document.formName.submit();), but then if the user doesn't have javascript enabled, I'd still have to use a noscript tag and they'd see it as a form button. The solution is to style the button with CSS. something like:

.sublink {
color: #00f;
background-color: transparent;
text-decoration: underline;
border: none;
cursor: pointer;
}
Then the html would be:
<input type="submit" class="sublink" value="Click this">

Tuesday, November 30

Valid Dates

OMG, two in one day!

A user uses the datepicker to enter a date, but it's still a text field, they could type in some nonexistent date. JQuery validation checks to make sure it's a correct date format, but not whether it's a valid date. ie: 30/30/2009 is a valid date.

Php checkdate() will make sure it's a valid date. Easy if your date comes in in separate variables, but mine came in from datepicker or from the database, so it looks like either 11/30/2010 or Nov 30, 2010.

So first of all, if the date comes in from the database, I make sure to format it before I echo it. I could do this with the sql query, but it's long and I don't want to. So I do:
if($row['indate'] == "Jan 01 1900 12:00AM") echo "";
else echo date("n/d/Y", strtotime($row['indate']));
This also takes care of MSSql's default dates, we just don't want those to show up at all.
Then when submitting the form, the validation is:
if($_POST['indate'] <> ''){  
   $arr=split("/",$_POST['indate']);
   $mm=$arr[0];
   $dd=$arr[1];
   $yy=$arr[2];
   if(!checkdate($mm,$dd,$yy)){
     $errormsg = 'Input is an invalid date: '.$_POST['indate'];
   }
}

References:
php.net
plus2net.com

Validate Integer Input

Php has an is_int() function, but it doesn't work with strings, unlike is_numeric() which will validate a numeric string. This was a big problem for me. The database required an int. My original solution was to force a numeric string to be an int with intval(), but the QA tester felt the integrity loss of data was too severe.

Finally found a solution in a php function I'd never seen before: filter_var(). This can validate a number of data types, but the important one for me was FILTER_VALIDATE_INT. So my validation function became:
if(!filter_var($_POST['input'], FILTER_VALIDATE_INT)){
echo "validation failed, input must be an integer";
}

Thursday, November 18

Datepicker with Dynamic Fields

Previously, I added rows to an input table. I realised later, the datepicker fields I added didn't work on these dynamic fields. Looking at the form details with the firefox web developer toolbar, I noticed the original datepicker fields had an id assigned to them of dp+randomnumber, but the new ones didn't.

I added a single line to my row adding function that solved this.
$('.datepicker').not('.hasDatePicker').datepicker();

Wednesday, November 17

255 varchar limit

Again, I have to use an MSSql database with php. I was having a problem: I'd insert a large amount of text into a varchar(1000) field, but when I tried to display it, only 255 characters came out. The problem has something to do with the drivers linux uses for php to connect to MSSql. There were two ways to handle this.

If I didn't have access to the database, I can cast the data as a text.
SELECT CAST(details as TEXT) from table
But since I had access to the database, I just changed the datatype of that field to text. Now all 1000 (even up to 2^31-1) characters will display.

Thursday, July 29

Add Rows with Unique Name

A table of input boxes will be put into a database. The user may need to add more to the table.

HTML:
<table id="infotable">
<tr>
<th>Individual Name</th>
<th>Type</th>
<th>Number</th>
<th>Expires</th>
</tr>
<tr>
<td><input type='text' name='info[0][indname]' /></td>
<td><input type='text' name='info[0][type]' /></td>
<td><input type='text' name='info[0][num]' /></td>
<td><input type='text' name='info[0][exp]' /></td>
</tr>
<tr>
<td><input type='text' name='info[1][indname]' /></td>
<td><input type='text' name='info[1][type]' /></td>
<td><input type='text' name='info[1][num]' /></td>
<td><input type='text' name='info[1][exp]' /></td>
</tr>
</table>
JQuery:
$('#addinfo').click(function(){
var getcount = $('input[name^="info"]:last').attr('name');
var ncount = parseInt(getcount.substring(4,getcount.length-6))+1;
$("#infotable > tbody").append("<tr><td><input type='text' name='info["+ncount+"][indname]' size='28' /></td><td><input type='text' name='info["+ncount+"][type]' </td><td><input type='text' name='info["+ncount+"][num]' /></td><td><input type='text' name='info["+ncount+"][exp]' class='datepicker' /></td></tr>");
});
The added tr has to be all on one like because javascript doesn't like like breaks.

Wednesday, May 26

Easiest Datepicker Ever

There is a fast, easy way to add a date picker calendar to any form. jQuery! More specifically, jQueryUI.

Include this stuff:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/themes/base/jquery-ui.css" type="text/css" media="all" />
Then in your $(document).ready(function(){ add
$('.datepicker').datepicker();
and any text field with the datepicker class will make a beautiful awesome calendar.

Thursday, May 6

jQuery is Fun!

I'm having fun with jQuery and Fancybox. Not only can I do lightbox style effect with images and galleries, but I can use it for other things too.

I have a client who wants a diagram and all the sections of it to have an explanation box. I'm using fancybox with the elastic transition for these explanations to pop out. The coolest part is they elastic out and back in in the shape of the link.

Javascript:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="/fancybox/jquery.fancybox-1.3.1.pack.js"></script>
<script type="text/javascript" src="/fancybox/jquery.easing-1.3.pack.js"></script>
<link rel="stylesheet" href="/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" />
<script>
$(document).ready(function(){
$(".more").fancybox({
'titlePosition' : 'inside',
'transitionIn' : 'elastic',
'transitionOut' : 'elastic'
});
});
</script>
HTML:
<a class="more" href="#popup1">HTML Popup</a>
<div style="display:none">
<div id="popup1" style="width:500px; height:220px; overflow:auto;">
<h2>Title</h2>
<p>Text text</p>
</div>
</div>

Tuesday, May 4

Creating an Org Chart with CSS

The trick is to use LOTS of divs. Every box and every line is its own div... and the whole thing is in another div.

Another thing they wanted was some of the boxes to expand to show more details. This was really easy to do with javascript and I used jQuery for the animations

So here's my divs:

<div id="container">
<div id="box1">
<h3 class="titles">Box 1</h3>
</div>
<div id="line1"></div>
<div id="box2">
<h2 class="titles">Box 2</h2>
</div>
<div id="line2"></div>
<div id="box3">
<h3 class="titles">Box 3</h3>
</div>
<div class="clear"></div>
<div id="line3"></div>
<div id="box4">
<h2 class="titles"><a class="show" href="#">Expandable</a></h2>
<div id="expand">
I am the contents of an expandable box
</div>
<p class="clear"></p>
</div>

<div id="box5">
<h2 class="titles">Box 5 has a longer sentence</h2>
</div>
<div id="line4"></div>
<div id="box6">
<strong>Box 6</strong>
</div>
<div id="line5"></div>
<div id="line6"></div>
<div class="clear"></div>
<div id="line7"></div>
<div id="line8"></div>
<div id="line9"></div>
<div class="clear"></div>
<div id="box7"><strong>Box 7</strong></div>
<div id="box8"><strong>Box 8</strong></div>
<div id="box9"><strong>Box 9</strong></div>
</div>
You'll notice box 4 is expandable. Script below the css.

The css:
* { padding:0; margin:0; }
body { font-family: Arial,Helvetica,sans-serif; font-size: 80%; color: #000;}
h1{ font-size: 133%;}
h2 { font-size: 116%;}
h3{ font-size: 108%;}
h4 { font-size: 104%;}

#expand{ display:none; text-align:center; }
#container{
width:950px;
text-align:center;
margin:auto;
margin-top: 10px;
}
.clear { clear:both;}
.titles { text-align:center; padding:10px; }
#box1,#box2,#box3{
display:inline;
border: 1px solid #000;
float:left;
height:55px;
width:250px;
}
#line1,#line2{
font-size:0;
display:inline;
width: 96px;
height:1px;
float:left;
margin-top:23px;
background-color:#000;
}
#line3,#line5{
font-size:0;
width:1px;
height:35px;
background-color:#000;
margin:auto;
}
#box4 {
border: 1px solid #000;
width:200px;
height:55px;
margin:auto;
margin-bottom:10px;
text-align:left
}
#box5{
border:1px solid #000;
width:760px;
height:30px;
margin:auto;
}
#line4{
font-size:0;
height:20px;
width:1px;
background-color:#000;
margin:auto;
}
#box6{
border:1px solid #000;
width:300px;
height:75px;
margin:auto;
background-color:#FF0;
}
#line6{
font-size:0;
height:1px;
width:599px;
background-color:#000;
margin:auto;
}
#line7{
font-size:0;
height:20px;
width:1px;
background-color:#000;
margin-left: 175px;
display:inline;
float:left;
}
#line8,#line9{
font-size:0;
height:20px;
width:1px;
background-color:#000;
margin-left: 298px;
display:inline;
float:left
}
#box7{
border:1px solid #000;
width:200px;
height:40px;
margin-left:100px;
display:inline;
float:left;
padding-top:15px;
}
#box8{
border:1px solid #000;
width:150px;
height:40px;
margin-left:100px;
display:inline;
float:left;
padding-top:15px;
}
#box9{
border:1px solid #000;
width:150px;
height:40px;
margin-left:130px;
display:inline;
float:left;
padding-top:15px;
}
And finally the JS:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script>
$(document).ready(function(){
// expandable
$("a.show").toggle(function(){
$("#box4").animate({width: '940px', height: '225px'});
$("#expand").slideDown('medium');
},function(){
$("#box4").animate({width: '200px', height: '55px'});
$("#expand").slideUp('medium');
});
});
</script>
jQuery was great for this because of their toggle function so I could use the same link to expand and contract the box.

Now all I need to do is make a way to automate the whole thing!

Tuesday, March 16

Accept Disclaimer and Redirect or Sessions in Joomla

Customer wants users to accept a disclaimer one time each visit to the site, then be redirected to the page. So if the user accepts the disclaimer, a session variable will be written and they will be redirected. This is a Joomla site, so the framework is already built for sessions and I wouldn't want to use regular php sessions.


So first, we'll put a checker on the page to see if they've accepted the disclaimer. I just included this php page in a custom code module on the menu item.
//load joomla files
require_once ( JPATH_BASE .DS.'includes'.DS.'defines.php' );
require_once ( JPATH_BASE .DS.'includes'.DS.'framework.php' );

$mainframe =& JFactory::getApplication('site');
$mainframe->initialise();

// get the session
$session =& JFactory::getSession();
$defaultvalue = 'reject';
$discl = $session->get('discl', $defaultvalue, 'disclaimer'); //get discl session value or 'reject' if not set

if($discl <> 'accept'){
$link = JRoute::_('index.php?option=com_content&view=article&id=43&Itemid=52');
$mainframe->redirect($link);//route them back to disclaimer page
}
So we have to load the Joomla files or none of the built in stuff will work. Then we get the session with JFactory::getSession. When doing a ->get, we can define a default variable. It's not necessary as if it's not set, the value will be null, but I set one just because. So then we test and see if the value is set to accept, and if not, route them to the disclaimer page. JRoute is pretty neat cause you put in the real url and it'll change it to the SEF url if you have that enabled. Pretty!

Now on the disclaimer page, they will accept and we will write the session variable.
// load joomla files
require_once ( JPATH_BASE .DS.'includes'.DS.'defines.php' );
require_once ( JPATH_BASE .DS.'includes'.DS.'framework.php' );

$mainframe =& JFactory::getApplication('site');
$mainframe->initialise();

// get the session
$session =& JFactory::getSession();

$session->set('discl', 'accept', 'disclaimer');//set accept
$link = JRoute::_('index.php?option=com_content&view=article&id=1&Itemid=49');
$mainframe->redirect($link);//route them to page
Get the session then add our own variable using ->set('sessionName', 'sessionValue', ['uniqueNameSpace']). You may have noticed the namespace defined in the last script. The namespace isn't required, if you don't define it, any session variables you state here will be added to the default namespace.

The way I implemented this in the Joomla site was create a page, add a menu item, then add a custom code module including this file to the page. It redirects so quickly, it's barely even noticeable.

So the result is user comes to site, session variable isn't 'accept' so they are redirected to the disclaimer. They accept and are directed to the site. They are free to move about the site until they close the browser or the session times out, at which point, when they come back, they must accept again.

Thursday, February 11

Updating Submit Disable to Include isNaN

The datanumber should be numeric, so I wanted to check on the frontend, before they even submit it. It was easy to add an else to the previous javascript function SearchVal().

function searchVal(){
if(document.getElementById('fname').value == 0
&& document.getElementById('lname').value == 0
&& document.getElementById('datanum').value == 0){
msg = 'At least one search field must be filled';

}else if(isNaN(document.getElementById('datanum').value)){
msg = 'Data number must be numeric';

}else{
dis = false;
msg = '';
}

if(dis == true){
document.getElementById('sub').disabled = true;
}else{
document.getElementById('sub').disabled = false;
}
document.getElementById('validmsg').innerHTML = msg;
}

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.