PSB™ PHP Code for Multiple PSB™ Hosts
Multiple PSB™ Hosts, also called hosts of administrators, host the PSB™ administrators, each of which administers (manages) a PSB™. Administrators may choose to sign themselves up as a PSB™ user if they wish—this will include them on the PSB™ (personal status board) display. Here is a diagram named The Personal Status Board (PSB™) Hosting Diagram that will help you see how all the parts fit together: Servers, databases, MySQL, host of administrators, administrators, users, and PSB™s. Check it out.
This PHP code gets the login user name and password from a user, checks it with the data in the "members" table, and admits users with valid data into the PSB™ page whose script code is listed below. Note that everyone in a group uses the administrator's user name (which can be thought of as their group's name) and the users password to login. No one but the administrator has to sign up for a PSB™ on the host's Registration page, so there are no other user names in any one group, even though their group's MySQL PSB™ table includes all members' first names, their statuses, and their comments. Only the administrator can change the status code meanings for the 100 status codes, since only he has the required administrator password. But any user can login and change any status or comment of anyone—trust is implied. Users are represented, therefore, only by first names which may be nicknames or whatever as long as only 12 or less alphanumeric characters and underline are used—no spaces allowed. Using cryptic names would be counterproductive, since the idea of a PSB™ is to see everyone's status at a glance, and "Joan" is a lot easier to comprehend than "J_girl_yum1", so one can assume he'll Just enter Joan, or JoanG or Joan_G if there are 2 Joans. The administrator is the only one allowed to enter or edit or delete or add member names, and in order to be part of the PSB™ group, he has to enter his first name along with the others in the Registration script. Otherwise he is merely a nonparticipating manager.
When members change their statuses and comments, this utilizes a script called update_psb.php which updates the MySQL table containing the Personal Status Board (PSB™) statuses and comments for PSB™s, and, like other server-side scripts (PHP, CGI, ASP), can support many PSB™ users simultaneously. The code grabs the ID, status, comment, and table name that was posted to it by the main PSB™ page, psb.php, listed below. It uses this data to do the updating of statuses and comments. The table name will contain the user name of the administrator.
Users and administrators never see the file below—only the PSB™ host sees it, so he will have to replace the "yourusername", "yourpassword" and "yourdatabase" with legitimate names that will enable the MySQL connection to be made and the database to be chosen. Only one database will be used to host all PSB™s, and each administrator's data will be entered into the host's "members" table (that contains users password, administrator password, administrator user name, email address, date of joining, and IP of administrator). Each administrator gets 2 other tables as well that are for his group only. The first table contains his group's statuses and comments, which any group member may change, as well as all first names of group members, which only the administrator can edit. The second table contains the current meanings of the 100 status codes—which the administrator may change if his group wants it.
Note that the script below submits updated status and comment values to update_psb.php and does the updating immediately, and transparently to the users, and returns the action back to psb.php as soon as the table is edited and a couple of field values get posted back to psb.php. You can see this POSTing being received below. The flag set to "1" means "returning from updating the PSB™" and the table name gets sent because even though the psb.php script below just sent this value to the update script, the only way it will continue to have the correct table name is if we post it back with POST, send it as a cookie, send it with GET, or use session variables. By posting it back and forth, once a user signs in, his user name will not be subsequently forgotten, since it's now part of the table name. Note the need for JSON in json_encode($table) to convert the table name received in PHP to JavaScript so it could be stuck in the form's hidden field value for posting back and forth to the update script. There's no other convenient way to do this conversion, although there is a cheap and dirty way to pull off this string conversion. JSON is also used for converting the PHP arrays (pulled out of the two MySQL tables for this group) from PHP to JavaScript, as there is no other reasonable way to pull this stunt off. PHP's and JavaScript's data types are not compatible, and the only conversions not needing JSON are with simple numbers (integer or floating point), since numeric arrays, strings and string arrays are not compatible.
<html><head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<TITLE>The Personal Status Board (PSB™)</TITLE>
<meta name="description" content="The Personal Status Board (PSB™) is our contribution to social connectedness, parenting, and ultimately social evolution.">
<meta name="keywords" content="Personal Status Board,PSB,PHP empowered communication,PHP and Javascript based web pages,parenting,social evolution,social connectedness,php,javascript, dhtml, DHTML">
<script language="javascript">
var sub_title=new Array("Alone","Nurturing","P.E.T. or Other Authoritative Parenting Activity","Talk or Play","Coordinate MC Activities","Coordinate Outside Activities","I Am Out","Want Ride","Study or Projects","Custom Codes or Mobile");
var member=-1;Status="";Comment="";
var memstatus=new Array();
var fname=new Array();
var psb=new Array();
var ids=new Array();
mactest=(navigator.userAgent.indexOf("Mac")!=-1) //My browser sniffers
is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1
Netscape=(navigator.appName.indexOf("Netscape") != -1)
msafari=(navigator.userAgent.indexOf("Safari")!= -1)
wsafari=0; if(!mactest&&msafari){wsafari=1;msafari=0}
is_opera = 0; if(window.opera){is_opera=1}
is_ie_mac = 0; is_ie=0;if(document.all){is_ie=1}
if(is_ie&&mactest){is_ie_mac=1}
Above, we declare the subtitle array for the status code meanings chart, initialize a few arrays, and do browser sniffing because they will attempt to display this PSB™ page with slight but significant differences, and we had to adjust our parameters accordingly.
Below, we respond to a click of a "Change Status" button with the function newstatus(). First it finds the current status value of the member whose button was clicked and puts it into the status field of the form so users will be reminded of their current status code—which they may now change by entering 2 digits to replace the old code. Then the value of the group's PSB™ table name in the MySQL database is stuffed into the form's hidden field for POSTing to the update file. The ID value, also in a hidden field, is similarly readied for POSTing. Finally, the member's first name is stuck in the text next to the status update input box.
The function validate() is triggered when the form is submitted, using onSubmit. First, it checks to see that a button has been clicked—if the member number is -1 that's a flag for lack of a click, so it cancels the submit by use of "return false". Next, it looks at the status code. If it's not 2 digits it cancels the submit by use of "return false". The function isNaN() detects nondigit characters. The comment field is checked and its input parsed for nasties that could allow MySQL injections. The ' and + characters are especially bad, but ' is needed in contractions. We could have escaped it but chose to replace it with a ; instead because escaping would add a character to the string and increase the chance of the display being corrupted as well as the possibility that the last character would be dumped from the string, since the MySQL table is configured to allot only 55 characters to this field. The ; character used to be a nasty until PHP 3.2 or thereabouts decided to stop allowing multiple MySQL commands in queries. These used to be separated by ; characters but this is no longer allowed so ; is okay. Another very dangerous entity is 2 hyphens in a row. This got replaced twice with one space character and one hyphen. Why twice? Because an odd number of hyphens could still let "--" leak through. The other very dangerous entity is the = sign. Regular expressions allowed us to replace this, and any other questionable character with a space character. We could have disallowed the submit if they entered weird stuff but chose to do replacements instead to keep the flow going.
The function validatepassword() is a standard form validation script for passwords and user names. If unacceptable characters are entered, the regular expressions detect it, the function returns false, and the user gets a do-over.
function newstatus(){
document.PSBform.status.value=memstatus[member];
document.PSBform.table.value=tbl;
document.PSBform.ID.value=ids[member];
document.getElementById('newstatus').innerHTML = fname[member]+" ";}
function validate(){
if(member<0){alert("Please click a 'Change Status' button first.");return false}
d=document.PSBform;
Status=d.status.value;
if (Status.length!=2 || isNaN(Status)) {alert("Please input any two digits for the personal status code.");return false}
Comment=d.comment.value;
Comment=Comment.replace(/'/g,";");
Comment=Comment.replace(/--/g," -");
Comment=Comment.replace(/--/g," -");
Comment=Comment.replace(/[@#$%\^&\*\(\)\+\|\\=\{\}\[\]:"'\/><]/g," ");
d.comment.value=Comment;
return true;}
function validatepassword(){
var ck_password = /^[A-Za-z0-9!@#$%^&*()_]{4,20}$/;
if (document.formpw.txtPassword.value.search(ck_password)==-1)
{alert("Please only enter letters, numbers and these for password: !@#$%^&*()_");return false}
var ck_username = /^[A-Za-z0-9_]{1,20}$/;
if (document.formpw.txtUsername.value.search(ck_username)==-1)
{alert("Please only enter letters, numbers and underline for user name.");return false}
return true;}
</script>
<style type="text/css">
BODY {margin-left:0; margin-right:0; margin-top:0;text-align:left;background-color:#88f;}
p, li {font:13px Verdana; color:black;text-align:left;margin-top:0.2em;margin-bottom:0}
h1 {font:bold 28px Verdana; color:black;text-align:center}
.arrow {font:18px bold 'Arial Black'; color:black;}
.psb_info {padding:10px;border:1px solid black;width:350px;position:absolute;top:410px;left:0px;background-color:#ccc;}
input {background-color:#eee;}
th {background-color:#ccc;}
.f {background-color:#f88;}
.g {background-color:#666;color:white;}
#container {width:1024px;text-align:left;position:absolute;top:0px;left:50%;margin-left:-502px;}
#logo {width:118px;height:47px;position:absolute;top:3px;left:13%;}
#user {width:12%;height:14px;font-size:14px;font-weight:bold;position:absolute;top:16px;left:4px;}
</style>
</head>
<body>
<div id='container'>
<div id='logo'><IMG SRC="PSB_.jpg" WIDTH=118 HEIGHT=47 BORDER=0></div>
Above, we wrap the entire web page in a container with CSS styles: width:1024px;text-align:left;position:absolute;top:0px;left:50%; and margin-left:-502px. This has little effect on a 1024-pixel-wide screen, but on all wider screens, it centers the page content in the center in this 1024-wide div. The left:50% tries to stick the left edge of the div in the center of the screen, but then the margin-left:-502px takes half the width of the container div and brings the left edge of the div leftward by that much, which exactly centers the div. Well, we had to account for the width of the scrollbar by have margin-left: be 10 less than half the container div width.
Below, we grab the POSTed value entry, flag, username, password, and table. In truth, once the password and username have been entered, the form that solicits these values POSTs the page right back to itself. Next, we check if the $U variable that had the user name in it after the login still has a value, since if we have updated a status the value will have been lost when we go to the 'update_psb.php' script for MySQL table updating. But the $table variable will still have the user name plus '_psb' in it. So we use the PHP string replace function to replace '_psb' with nothing in $table, then store this result in $U. This gets echoed in the top left corner of the screen to let the user know we know him or her. The script then looks at whether this is the correct password (the "users password") and user name (the administrator's user name which is also this GROUP's user name), and even whether or not such a user name exists. If the user has submitted the form, the $Entry flag will be set and his group's administrator-entered data will be located in the "members" table as long as this administrator user name exists—a warning will pop up if it is not found, or if the username/password pair is invalid. Note that in checking through the "members" table for the record in which the user's group user name matches the one entered into the form, we use MySQL's SELECT statement in PHP. The $N flag is set if their entry attempt is NO GOOD—which returns them to try again. $Z temporarily gets the table's users password to compare with their input.
<?php
$Entry=$_POST['entry'];
$FLAG=$_POST['flag'];
$U=$_POST['username'];
$P=$_POST['password'];
$table=$_POST['table'];
$N=0;
$Z='';
$Q=0;
if(empty($U)){$U = str_replace("_psb", "", $table);}
// Make a MySQL Connection
mysql_connect("localhost", "yourusername", "yourpassword") or die(mysql_error());
mysql_select_db("yourdatabase") or die(mysql_error());
if($Entry==1){
$check_user_data = mysql_query("SELECT * FROM members WHERE username = '$U'") or die(mysql_error());
if(mysql_num_rows($check_user_data) == 0)
{echo '<script language="javascript">alert("This user name does not exist. Please try again.")</script>;';$N=1;}
else {$get_user_data = mysql_fetch_array($check_user_data);}
}
if($N==0 && $Entry==1){$Z=$get_user_data['users_password'];}
if($Z != $P && $Entry==1)
{echo '<script language="javascript">alert("Username/password pair is invalid. Please try again.")</script>;';$N=1;}
if(($N==1||$Entry==0) && empty($FLAG)){ ?>
<h1>Personal Status Board (PSB™)</h1>
<div id='pw' style='position:absolute;top:410px;left:200px;width:300px'><table style="background-color:#8aa;border-color:#00f" border='6' cellspacing=0 cellpadding=6><tr><td>
<form id='formpw' name="formpw" method="post" action="psb.php" onsubmit="return validatepassword()">
<label for="User Name"><b>User Name: </b><input type="text" name="username" size="20" maxlength="30" value=""></label>
<label for="Password"><b>Password: </b><input type="password" name="password" size="20" maxlength="20" value=""></label><br><br>
<input type="hidden" name="flag" value="0">
<input type="hidden" name="entry" value="1">
<input type="submit" value="Submit">
<input type="reset" value="Reset"></form></td></tr></table>
</div>
<?php
} else {$FLAG=1;$Entry=0;
?>
<div id='s' style='position:absolute;top:53px;left:2px;border:1px solid black;background-color:#eee;'>
<?php
if(empty($table)){$table=$U."_psb";}
$meaning = str_replace("_psb", "_meaning", $table);
Above is the form that receives the user input for logging in. Note the hidden fields that are used to POST flags to the PHP POST function, requiring a page reload. It's very convenient that forms can send data to their own pages like this, since JavaScript cannot send data to PHP without POST, GET, or cookies. PHP, on the other hand, can send anything to JavaScript on the same page with JSON, or if it's simple numbers, without need of JSON. Note that the PHP else statement after the form uses the entire rest of the page as its "else." In creating the table name for the logged in user, the empty() function looks to see if the table name is already known, and it is known if the name has content. If it is empty, it gets created with the concatenation of the entered user name and the string "_psb". Let's call the administrator Joe and his user name got entered as "Joes_bunch", when he used the Registration script, got his general data entered into the "members" table, and 2 tables were created for his group, "Joes_bunch_psb" and "Joes_bunch_meaning". The former contains the list of first names (which he entered when he registered his group) and their current statuses and comments. The latter contains all the current meanings of the 100 status codes, which the administrator can change if it is desired by the group.
Below, the "Joes_bunch_psb" table (or whatever) is loaded as a MySQL Resource and PHP's while statement is used to pull the arrays from this result and the echo statment is used to print them into a table on the screen. Then arrays are defined and the table's Firstname and Status and ID fields are dumped into these arrays using a while statement and the array_push function. Since the database is still open, we also dump into an array the meaning field from the group's $meaning table (i.e., "Joes_bunch_meaning"). Finally we close the connection, jump into JavaScript, and use JSON to dump these PHP arrays into JavaScript ones, since we'll be using DHTML to dynamically manipulate this data, as well as using the update_psb.php file to update the MySQL table (i.e., "Joes_bunch_psb") when statuses and/or comments are changed.
// Make a MySQL Connection
mysql_connect("localhost", "yourusername", "yourpassword") or die(mysql_error());
mysql_select_db("yourdatabase") or die(mysql_error());
// Retrieve all the data from their table as a MySQL Resource
$result = mysql_query("SELECT * FROM $table") or die(mysql_error());
echo "<table border='1' width='578'>";
echo "<tr><th width='116'>Name</th><th width='25'>ID</th><th width='25'>Status</th><th width='412'>Comment</th></tr>";
// keeps getting the next row until there are no more to get
while($row = mysql_fetch_array($result)) { // Print out the contents of each row into a table
echo "<tr><td>";
echo $row['Firstname'];
echo "</td><td>";
echo $row['ID'];
echo "</td><td>";
echo $row['Status'];
echo "</td><td>";
echo $row['Comment'];
echo "</td></tr>";
}
echo "</table>";
$number=mysql_num_rows($result);
$myArray=array();
$res = mysql_query("SELECT Firstname FROM $table") or die(mysql_error());
while ($row = mysql_fetch_row($res)) {
array_push ($myArray, $row[0]);
}
$myArray1=array();
$res = mysql_query("SELECT ID FROM $table") or die(mysql_error());
while ($row = mysql_fetch_row($res)) {
array_push ($myArray1, $row[0]);
}
$myArray2=array();
$res = mysql_query("SELECT Status FROM $table") or die(mysql_error());
while ($row = mysql_fetch_row($res)) {
array_push ($myArray2, $row[0]);
}
$myArray3=array();
$res = mysql_query("SELECT meaning FROM $meaning") or die(mysql_error());
while ($row = mysql_fetch_row($res)) {
array_push ($myArray3, $row[0]);
}
mysql_close();
?>
</div>
<script language="javascript">
var fname = <?php echo json_encode($myArray); ?>;
number_records_in_table = <?php echo $number; ?>;
var ids = <?php echo json_encode($myArray1); ?>;
var memstatus = <?php echo json_encode($myArray2); ?>;
var psb = <?php echo json_encode($myArray3); ?>;
var tbl = <?php echo json_encode($table); ?>;
var Us = <?php echo json_encode($U); ?>;
a='82px';b='25px';h='25px';n=622;if(is_ie_mac){a='82px';b='23px';h='23px';n=772;}
if(Netscape&&!mactest){a='83px';b='26px';h='25px';};
if(Netscape&&mactest){a='78px';b='22px';h='22px';n=722;};
if(msafari){a='77px';b='21px';h='21px';n=676;};
if(wsafari&&!is_chrome){n=659;};
if(is_chrome){n=633;};
if(is_opera){n=615;};
Above, after the JSON action, we set variables according to browser, since the display differences were real—these browsers are in general agreement about how to display content, but they disagree on the details. Below, note that document.write() functions do all the heavy lifting. It turned out that regular HTML statements inside the PHP "else" brackets didn't work right for the page to work, but JavaScript statements worked nicely. First, we printed the page title, then an HTML table that displayed the meaning of the current statuses of each PSB™ member, using the psb() array it got from the JSON encoding of the PHP array gotten from the $meaning table, above. Next, a bunch of z-filled variables are set up with values that compensate for the number of group members—the more there are, the farther down the page it's necessary to put the rest of the page content.
Next we needed to get people to stop typing whenever their comment inputs filled the input box, so we warned them about it. Capital "W" takes up a lot more room than small "i" does, for example, so even though the field will hold 55 characters, if they're wide ones, users need to type fewer of them or wreck the display once the update happens. It won't hurt anything but it will look bad. We could have solved the problem by using Courier for the font, but it's too hard to read and unaesthetic. At first blush one might think we merely needed to limit the characters to 55, but wide ones, as we said, would wreck things. At second blush, one would think we'd stop the entry once it reached the end of the box by some JavaScript trick of reading the characters' screen positions. There IS no such JavaScript trick possible—it's easy to read the mouse position but it takes the C language to figure out the screen position of a character. If we are in error about this, we would LOVE to hear from readers who know a magical technique!
Anyway, notice that the form created on the page (using document.write) for receiving statuses and comments also has hidden fields for posting the user name's ID and the table name (along with the status and comments) to the update_psb.php file during updates. It also uses an onSubmit to run the function validate() to validate the comment and status code inputs, as discussed earlier. The next script prints out the status code meanings in a table of 100 items, complete with subtitles. Finally, we print out a clickable button for each group member which users utilize to indicate the member whose data they wish to change—usually their own, but older people might be helping out younger people who aren't ready to enter data to change their statuses. For example, a person caretaking a toddler may need to go somewhere so she changes the toddler's status from "I am being nurtured" code to "Want nurturing" and changes her own status from "I am a scheduled caregiver" to "Want Ride north - P.M." or whatever. Note that in the creation of the column of buttons, we add an onClick event to each button in which the person's group ID is stuck in the "member" variable, so the PSB™ knows which member the button refers to. Note that ID numbers are whatever for group members, but the ids[] array knows the IDs, so when the newstatus() function gets called, this array gets used to get a value to dump into the form so the correct ID gets POSTed. In addition to calling that function, the code also includes a command to focus the cursor in the status code input box on the form. Finally, notice that we use a different (shorter) button if the user is on a Mac, since they often use shorter table rows than Windows machines. Note the </div> at the end of the script—this is the far end of the container div that holds the entire page. Also note the "Refesh often" message that gets printed at the top right corner of the screen. This reminds users that other users might be updating statuses but such changes will not happen on their page unless they refresh the screen to pull the very latest data from the MySQL table.
document.write("<div id='user'>"+Us+"</div>");
document.write("<div style='absolute;top:0px;left:200px'><h1>Personal Status Board (PSB™)</h1></div>");
document.write("<div id='q' style='border:1px solid black;background-color:#eee;position:absolute;top:53px;left:582px'><table width=\"418\" border=\"1\" cellpadding=\"1\">");
document.write("<tr><th width='50' height='+h+'>Change</th><th width='373'>Meanings</th></tr>");
for (i=0;i<number_records_in_table;i++){document.write( "<tr><td>" + " " + "</td><td>" + psb[parseInt(memstatus[i],10)] + "</td></tr>");}
document.write ("</table></div>");
z=410;zz=360;zzz=340;zzzz=391;zzzzz=145;if (number_records_in_table>10){z=410+((number_records_in_table - 10)*30);zz=z-50;zzz=z-73;};if(Netscape||is_opera){zzzz=402;zzzzz=123;}
document.write ("<div style='position:absolute;top:"+zzz+"px;left:"+n+"px'><i>Please cease typing when you get here</i><span class='arrow'><b>↓</b></span></div>");
document.write ("<form style='position:absolute;top:"+zz+"px;left:10px;width:989px;' id='status' name='PSBform' method='post' action='update_psb.php' onsubmit='return validate()'><b id='newstatus'>Member </b><label for='status'><b>Status: </b><input type='text' name='status' size='2' maxlength='2' value=''></label><label for='comment' style='position:absolute;top:0px;left:370px;width:629px;'><b>Comment (optional): </b><input type='text' name='comment' size='55' maxlength='55'></label><input type='hidden' name='ID' value=' '><input type='hidden' name='table' value=' '><input type='submit' value='Update PSB' class='f'><input type='reset' value='Reset' class='g'></form>");
document.write("<div id='r' style='background-color:#eee;position:absolute;top:"+z+"px;left:"+zzzz+"px'><table border=\"1\" cellpadding=\"1\">");
document.write("<tr><th>Status Code</th><th>Meaning</th></tr>");
f=0;u=" ";
for (i=0;i<100;i++){
u=i.toString();if (u.length<2){u="0"+u}
if(u.charAt(1)=="0"){document.write( "<tr><th colspan=2>" + sub_title[f] + "</th></tr>");f=f+1}
document.write( "<tr><td>" + u + "</td><td>" + psb[i] + "</td></tr>")}
document.write ("</table><br><br><br></div><br><br><br>");
document.write("<div id='g' style='position:absolute;top:"+a+";left:587px'><table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">");
if(!mactest){for (i=0;i<number_records_in_table;i++){document.write( "<tr><td style='height:"+h+";'><a HREF='#' onclick='member="+i+",newstatus(),document.PSBform.status.focus()'><div style='height:"+b+";'><IMG SRC='change.png' WIDTH=40 HEIGHT=22 BORDER=0></a></div></td></tr>");}
}else{
for (i=0;i<number_records_in_table;i++){document.write( "<tr><td style='height:"+h+";'><a HREF='#' onclick='member="+i+",newstatus(),document.PSBform.status.focus()'><div style='height:"+b+";'><IMG SRC='change-.png' WIDTH=40 HEIGHT=18 BORDER=0></a></div></td></tr>");}}
document.write ("</table></div>");
document.write ("<div style='color:white;font-size:18px;position:absolute;left:80%;top:10px'><b>Refresh often [F5]</b></div>");
</script>
<?php
}
?>
</div>
</body></html>