Tải bản đầy đủ (.pdf) (81 trang)

MySQL /PHP Database Applications Second Edition phần 7 docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (523.91 KB, 81 trang )

Chapter 13
Problem-Tracking System
IN THIS CHAPTER

Designing a problem-tracking system

Protecting yourself from redundant data

Using the IntegratedTemplate class from the PEAR class libraries

Creating a site that has both public and private portions
GOT PROBLEMS? Don’t worry, we’ve all got problems. Relationships falter, bosses
make capricious demands, and family— oh, we all know about family. Sadly, in the
crazy lives that we all live, PHP and MySQL can do nothing to make your girl/
boyfriend love you more or make your dealings with your parents or in-laws any
easier. But no scripting language or relational database is better equipped in these
areas.
But if you’re working for a company that sells or otherwise dispenses goods, it is
a virtual guarantee that someone somewhere is going to be unhappy with what he
or she has received. When that person complains, you are going to want to have a
place in which to record the problems and the steps required for resolution.
The problem-tracking application in this chapter can be used for that purpose.
What we have here is fairly generic, and depending on the goods involved with
your business, it is likely that you are going to want some fields that apply to your
specific products. Anyhow, this application should get you moving in the right
direction.
Determining the Scope and Goals of
the Application
This problem-tracking system should have aspects that are publicly available and
others that only someone with the proper authorization can view. It makes sense to
have a form that users can access over the Web in order to report their problems.


Alternatively, someone on the support staff should be able to report problems — for
example, while taking a phone call from a dissatisfied customer.
Once the problem is entered, it should be tracked by the staff. Each action taken
in the attempt to solve the problem should be noted. And the tracking should have
441
a public and a private realm — actions that you want the user to see must be differ-
entiated from those that you do not want the user to see.
Those with problems should be able to keep track of them in two ways. They
should be emailed whenever a publicly viewable update is made to their case, and
a Web page detailing their problem should be available.
What do you need?
The first thing you need is a form into which people can enter their complaints.
What we present in Figure 13-1 is fairly generic; remember that for your own appli-
cations you will probably want to add information regarding specific products.
Figure 13-1: Problem entry form
Once a problem is entered, there must be a place for the staff to work on the
complaint. It should include all the information about the user, the history of the
complaint, and a place to enter new information. This problem-update form would
look something like the one in Figure 13-2.
The support-staff members need a home, a place where they can log in and see
both unassigned tasks and those that are assigned to them and are still open. The
staff page would look something like the one in Figure 13-3.
442 Part IV: Not So Simple Applications
Figure 13-2: Problem update form
Figure 13-3: Staff page
Chapter 13: Problem-Tracking System 443
If you want to see if any of your users are hypochondriacs, you can use the user-
history page shown in Figure 13-4, which lists all problems associated with a user.
Figure 13-4: User history page
What do you need to prevent?

In setting up this part of the application, you’re concerned with gathering informa-
tion efficiently and in a way that’s pleasant for the user. Therefore, your worries are
more of an interface-design nature, and thus more in the realm of Web design than
application development.
Developers, though, are concerned with making sure that the data collected is
valid, and complies with database limitations. You might want to endow your
forms with some client-side scripting that checks values for obvious problems
before sending them in.
Designing the Database
As you can see from Figure 13-5, the problems table is at the center of the schema.
444 Part IV: Not So Simple Applications
Figure 13-5: Tracking system schema
admin
username
password
problems
problem_id
customer_id
status_id
staff_id
summary
problem
entered_by
source_id
entry_dt
modify_dt
last_public_entry
last_entry_id
history
entry_id

problem_id
entry_type_id
entered_by
source_id
entry_dt
notes
status
status_id
customers
customer_id
customer_code
firstname
lastname
address
address2
city
state
zip
zip4
email
day_area
day_prefix
day_suffix
day_ext
day_start
day_end
eve_area
eve_prefix
eve_suffix
eve_ext

eve_start
eve_end
staff
staff_id
username
password
staff_name
active
entry types
entry_type_id
entry_type
sources
source_id
source
Chapter 13: Problem-Tracking System 445
Here are some design considerations we had to keep in mind as we designed our
database:

Each customer can have one or many problems. The history table records
the steps taken to remedy the problem or problems.

The status table is a lookup table, containing the possible states of a
problem, notably open, closed, in processing, and so on.

The sources table is another lookup table, which records where the prob-
lem was originally recorded. If a user enters a complaint over the Web, the
sources table will record that; complaints received by the support staff
might originate from a phone call, email, or flaming arrow.

The entry_types table notes whether a specific item in the history table

should be public or private. If it is private, it will not be available on the
Web page when the user comes to view the progress of the problem, and
an email will not be sent to the user when an update takes place. The pub-
lic updates will be viewable and the user will receive email notification.
Now for a couple of notes on this schema and the
create statements that follow.
Depending on how you plan on running your site, you may wish to add a table or
change a column definition or two.
Notice that we have a problem_code column in the problems table. However, if
you will be emailing users regarding the status of problems, you may want some-
thing a little less transparent than the following: />problems.php?problem_id=7
.
In Chapter 9 we take some precautions when we run into a similar situation. We
didn’t want people to gain access to restricted parts of our data simply by guessing
at variable names in the URL. Here we adopt the same technique we used there in
the survey application, creating a random 8-character alphanumeric string from the
md5() and uniqueid() functions. It’s true that we run a risk of the same random
number coming up twice, and in fact this approach might not be right for a very
large application. But it works here.
Listing 13-1 shows the create statements for the tables we used in this applica-
tion. In addition to the create statements, this listing includes some of the default
data you will need to start the application. Note that if you install this application
from the CD-ROM you will have a full set of dummy data you can play with.
Listing 13-1: create Statements Used in the Problem-Tracking System
drop table if exists status;
create table status
(
status_id tinyint not null auto_increment
, status varchar(20) not null
, primary key (status_id)

446 Part IV: Not So Simple Applications
)
type=InnoDB
;
insert into status (status) values (‘Opened’);
insert into status (status) values (‘In Progress’);
insert into status (status) values (‘Closed’);
insert into status (status) values (‘Re-opened’);
drop table if exists status_seq;
create table status_seq
(
id tinyint not null auto_increment
, primary key (id)
)
type=InnoDB
;
insert into status_seq (id) select max(status_id)+1 from status;
drop table if exists sources;
create table sources
(
source_id tinyint not null auto_increment
, source varchar(10) not null
, primary key (source_id)
)
type=InnoDB
;
insert into sources (source) values (‘web’);
insert into sources (source) values (‘email’);
insert into sources (source) values (‘phone’);
insert into sources (source) values (‘in-store’);

insert into sources (source) values (‘staff’);
insert into sources (source) values (‘program’);
drop table if exists source_seq;
create table source_seq
(
id int not null auto_increment
, primary key (id)
)
type=InnoDB
;
insert into source_seq (id) select max(source_id)+1 from sources;
drop table if exists entry_types;
create table entry_types
(
Continued
Chapter 13: Problem-Tracking System 447
Listing 13-1 (Continued)
entry_type_id tinyint not null auto_increment
, entry_type varchar(10) not null
, primary key (entry_type_id)
)
type=InnoDB
;
insert into entry_types (entry_type) values (‘private’);
insert into entry_types (entry_type) values (‘public’);
drop table if exists entry_type_seq;
create table entry_type_seq
(
id tinyint not null auto_increment
, primary key (id)

)
type=InnoDB
;
insert into entry_type_seq (id) select max(entry_type_id)+1 from
entry_types;
drop table if exists staff;
create table staff
(
staff_id int not null auto_increment
, username varchar(20) not null
, password varchar(255) not null
, staff_name varchar(50) not null
, active tinyint default 1
, primary key (staff_id)
, unique (username)
)
type=InnoDB
;
insert into staff (username,password,staff_name) values
(‘fred’,password(‘fred’),’Fred Flintstone’)
, (‘barney’,password(‘barney’),’Barney Rubble’)
;
drop table if exists staff_seq;
create table staff_seq
(
id int not null auto_increment
448 Part IV: Not So Simple Applications
, primary key (id)
)
type=InnoDB

;
insert into staff_seq (id) select max(staff_id)+1 from staff;
drop table if exists customers;
create table customers
(
customer_id integer not null auto_increment
, customer_code varchar(8)
, firstname varchar(40)
, lastname varchar(40)
, address varchar(40)
, address2 varchar(40)
, city varchar(20)
, state char(2)
, zip char(5)
, zip4 char(5)
, email varchar(255)
, day_area char(3)
, day_prefix char(3)
, day_suffix char(4)
, day_ext char(5)
, day_start char(8)
, day_end char(8)
, eve_area char(3)
, eve_prefix char(3)
, eve_suffix char(4)
, eve_ext char(5)
, eve_start char(8)
, eve_end char(8)
, primary key (customer_id)
)

type=InnoDB
;
drop table if exists customer_seq;
create table customer_seq
(
id int not null auto_increment
, primary key (id)
)
Continued
Chapter 13: Problem-Tracking System 449
Listing 13-1 (Continued)
type=InnoDB
;
drop table if exists problems;
create table problems
(
problem_id integer not null auto_increment
, customer_id integer not null
, problem_code char(8) not null
, status_id tinyint null
, staff_id integer null
, summary text
, problem text
, entered_by varchar(20) null
, source_id tinyint null
, entry_dt datetime
, modify_dt timestamp
, last_public_entry_id int null
, last_entry_id int null
, primary key (problem_id)

, key (customer_id)
, foreign key (customer_id) references customers (customer_id) on
delete cascade
, key (status_id)
, foreign key (status_id) references status (status_id) on delete
set null
, key (source_id)
, foreign key (source_id) references sources (source_id) on delete
set null
, key (staff_id)
, foreign key (staff_id) references staff (staff_id) on delete set
null
, unique (problem_code)
)
type=InnoDB
;
drop table if exists problem_seq;
create table problem_seq
(
id int not null auto_increment
, primary key (id)
)
type=InnoDB
450 Part IV: Not So Simple Applications
;
drop table if exists history;
create table history
(
entry_id integer not null auto_increment
, problem_id integer not null

, entry_type_id tinyint not null
, entered_by varchar(20) null
, source_id tinyint not null
, entry_dt timestamp
, notes text
, primary key (entry_id)
, key (problem_id), foreign key (problem_id) references problems
(problem_id) on delete cascade
, key (entry_type_id), foreign key (entry_type_id) references
entry_types (entry_type_id) on delete cascade
, key (source_id), foreign key (source_id) references sources
(source_id) on delete cascade
)
type=InnoDB
;
drop table if exists history_seq;
create table history_seq
(
id int not null auto_increment
, primary key (id)
)
type=InnoDB
;
drop table if exists admin;
create table admin
(
username varchar(50) not null
, password varchar(255) not null
, primary key (username)
)

type=InnoDB
;
insert into admin values (‘jay’,password(‘rules’));
delete from mysql.db where Db = ‘tracking’;
Continued
Chapter 13: Problem-Tracking System 451
Listing 13-1 (Continued)
grant delete, insert, select, update
on tracking.*
to nobody@localhost identified by ‘ydobon’
;
flush privileges;
Code Overview
The only really new part of this example is that it uses the PEAR Integrated
Template
class. Templates are a common and useful way to separate an applica-
tion’s code from its design. Since these are frequently built by two different sets of
people, working on different schedules, keeping these two parts of your application
(not to mention the designers and the coders) at a distance from each other can make
your life a lot easier. Plus, looking ahead, a site’s design is something that is going
to change much more frequently than its basic functionality, so maintenance
becomes easier as well.
The idea behind a template is pretty easy to pick up. In one file you write all the
HTML for a page (or part of a page — such as the navigational elements). Elements
of the page that will be filled in with data from a database, or with values resulting
from a calculation like “Total Amount Due,” are represented by some kind of stan-
dardized placeholder, like so:
<tr>
<td><b>Total Amount Due:</b></td>
<td align=”right”><b>{total_due}</b></td>

</tr>
We’ve picked the simplest templating system readily available, the
IntegratedTemplate (or IT) class from PEAR. It looks just like the preceding exam-
ple, shockingly enough, and also has some capacity for loops so you can repeat part
of a template, like a row in a parts table, as many times as you have rows of data to
fill it. There are lots of other, more advanced and complicated templating systems
out there. Try changing this example around some until you get frustrated at not
being able to do something — that’s how you’ll know what to look for.
You’ll find IntegratedTemplate and its documentation here:
/>If IT isn’t to your liking, you may want to investigate FastTemplate and
Smarty, two other template engines.
452 Part IV: Not So Simple Applications
Code Breakdown
This application makes more liberal use of includes than some of the previous ones
you have seen. It contains a couple of very long forms that could really clutter up
a page. They have been pushed off to templates.
Reusable functions from /book/tracking/
functions.php
The base function set, described in Chapter 9, will be used here once again. The first
few of these functions are for convenience. The ones a little further down do some
pretty heavy and cool work.
fetch_staff()
If you’ve looked at some of the other applications in this book, this type of function
should be clear. Basically, this function takes a series of parameters that it uses to
modify a generic SELECT query that’s run against the staff table. If no parameters
are sent to this function, the most basic SELECT is run:
SELECT * FROM staff;
If arguments exist, the code uses them to modify the SELECT statement. It
employs a bit of intelligence here. If the arguments are numeric — the function uses
is_numeric to figure this out — the code adjusts the SELECT statement to examine

the staff_id field, like this:
SELECT * FROM staff; WHERE staff_id LIKE argument_value;
Alternatively, if the argument is a string (is_string comes into play for the job)
the SELECT statement looks at the username field. For example:
SELECT * FROM staff; WHERE username LIKE argument_value;
Here’s the full function.
function fetch_staff()
{
$params = func_get_args();
$wheres = array();
$bind = array();
foreach ($params as $arg)
{
if (is_string($arg))
{
Chapter 13: Problem-Tracking System 453
$wheres[] = ‘ username like ? ‘;
$bind[] = $arg;
}
elseif (is_numeric($arg))
{
$wheres[] = ‘ staff_id like ? ‘;
$bind[] = $arg;
}
else
{
user_error(
‘Invalid argument to fetch_staff()
:’.var_export($arg,TRUE)
, E_USER_NOTICE

);
}
}
$query = ‘select * from staff ‘;
if (count($wheres) > 0)
{
$query .= ‘ where ‘.implode(‘ or ‘, $wheres);
}
$result = db_connect()->getAll($query, $bind,
DB_FETCHMODE_ASSOC);
if (is_array($result) && count($result) == 1)
{
$result = array_shift($result);
}
return $result;
}
fetch_customer()
This function works very much like fetch_staff(), except that there’s no need to
determine whether the argument is a string or a numeric value. Because we are
building the query based on named parameters from the $params argument, we can
look explicitly for the column values we need. So if we’ve been given a customer ID
value, we use that. If not, we check for a customer code string, and if we have one,
use that. If we don’t have values for either of these columns, then we can’t run the
query, and we just error out of the function. DB lets us supply the column name
we’re using as a parameter by using the ! token character in its place in the query.
Here’s the full function.
function fetch_customer(&$params)
{
$query = ‘select * from customers where ! = ?’;
454 Part IV: Not So Simple Applications

if (!empty($params[‘customer_id’]))
{
$bind = array(‘customer_id’, (int)$params[‘customer_id’]);
}
elseif (!empty($params[‘customer_code’]))
{
$bind = array(‘customer_code’, $params[‘customer_code’]);
}
else
{
user_error(
‘Could not fetch customer - no ID or code specified’
, E_USER_ERROR
);
return FALSE;
}
$dbh = db_connect();
$record = $dbh->getRow($query, $bind, DB_FETCHMODE_ASSOC);
if ($record === FALSE)
{
$private_error = ‘fetch_customer: error in query: ‘
. $dbh->last_query
;
user_error(
‘Could not fetch customer from database’
, E_USER_ERROR
);
return FALSE;
}
$params = array_merge($params, $record);

return TRUE;
}
fetch_problem()
The difference between fetch_problem() and fetch_customer() is at the end of
the function. When you get a problem from the database, you want more than the
problem record itself. You want information about the customer who has the prob-
lem, and about the history of our work on the problem to date. So in addition to the
usual query against the problems table, fetch_problem() also runs the
fetch_customer() and fetch_history() functions. Here’s the code:
function fetch_problem(&$params)
{
$query = ‘select * from problems where ! = ?’;
if (!empty($params[‘problem_id’]))
Chapter 13: Problem-Tracking System 455
{
$bind = array(‘problem_id’, $params[‘problem_id’]);
}
elseif (!empty($params[‘problem_code’]))
{
$bind = array(‘problem_code’, $params[‘problem_code’]);
}
else
{
user_error(
‘Could not fetch problem: no ID or code specified’
, E_USER_ERROR
);
return FALSE;
}
$dbh = db_connect();

$record = $dbh->getRow($query, $bind, DB_FETCHMODE_ASSOC);
if (!$record)
{
$private_error = ‘fetch_problem: error with query: ‘
. $dbh->last_query
;
user_error(
‘Could not fetch problem from database’
, E_USER_ERROR
);
return FALSE;
}
$params = array_merge($params, $record);
if (empty($params[‘source’]) && !empty($params[‘source_id’]))
{
$params[‘source’] = source($params[‘source_id’]);
}
if (!fetch_customer($params) or !fetch_history($params))
{
return FALSE;
}
return TRUE;
}
456 Part IV: Not So Simple Applications
find_customer()
Remember that you would like to enable users to report their problems over the
Web. In this application, we’ve decided that while a numeric primary key exists for
each user, the application should be able to identify the user by either a phone
number or an email address. So when a user enters information, you will need to
check if someone with an identical email address or phone number has come along.

function find_customer($email=””
,$day_area=’’,$day_prefix=’’,$day_suffix=’’
,$eve_area=’’,$eve_prefix=’’,$eve_suffix=’’
)
{
$wheres = array();
$bind = array();
if ($day_prefix != ‘’)
{
// there must be a prefix for this to be a valid phone
number
$wheres[] = ‘(day_area like ? and day_prefix like ? and
day_suffix like ?)’;
$bind[] = $day_area;
$bind[] = $day_prefix;
$bind[] = $day_suffix;
}
if ($eve_prefix != ‘’)
{
// there must be a prefix for this to be a valid phone
number
$wheres[] = ‘(eve_area like ? and eve_prefix like ? and
eve_suffix like ?)’;
$bind[] = $eve_area;
$bind[] = $eve_prefix;
$bind[] = $eve_suffix;
}
if ($email != ‘’)
{
$wheres[] = ‘(email like ?)’;

$bind[] = $email;
}
if (count($wheres) == 0)
{
// nothing to look for
user_error(
‘find_customer: no wheres supplied’
Chapter 13: Problem-Tracking System 457
, E_USER_NOTICE
);
return FALSE;
}
// run a query with the constructed qualification
// and return the result.
// separate each part of the qualification with OR -
// any part constitutes a valid match.
$query = ‘select * from customers where ‘
. implode(‘ or ‘, $wheres)
. ‘ order by customer_id ‘
;
$results = db_connect()->getAll($query, $bind,
DB_FETCHMODE_ASSOC);
return $results;
}
With this function you will know if the user has an existing record that can be
used or that might need to be updated. Figure 13-6 shows the form for updating
customer data.
Figure 13-6: Form for updating customer information
458 Part IV: Not So Simple Applications
If you are interested, you can set a cookie to make identifying the user a bit

easier.
history_entry()
When a staff member enters an update on a problem, the step is stored in the his-
tory table. If the entry is public the user will be informed of the update by email; if
not, no email will be sent.
function history_entry($problem_id=NULL
, $entry_type_id=NULL
, $entered_by=NULL
, $source=NULL
, $notes=NULL
)
{
if (empty($problem_id))
{
user_error(‘Error: no problem ID for history entry’,
E_USER_ERROR);
return FALSE;
}
if (empty($entered_by)) { $entered_by = ‘customer’; }
$entry_type = entry_type($entry_type_id);
$source_id = source_id($source);
// create a record in the history table
$dbh = db_connect();
$entry_id = $dbh->nextId(‘history’);
$query = ‘insert into history
(entry_id,problem_id,entry_type_id,entered_by,source_id,notes)
values (?,?,?,?,?,?)
‘;
$bind = array($entry_id,$problem_id,$entry_type_id,$entered_by
,$source_id,$notes

);
$result = $dbh->query($query,$bind);
if (!$result)
Chapter 13: Problem-Tracking System 459
{
$private_error = ‘error: could not create history entry: ‘
.’<li>query=’.$query
.’<li>result=’.var_export($result,TRUE)
.’<li>last_query=’.$dbh->last_query
;
user_error(‘Error: could not create history entry’,
E_USER_ERROR);
return FALSE;
}
// update the problem record
$query = ‘update problems set last_entry_id=? ‘;
$bind = array($entry_id);
if ($entry_type == ‘public’)
{
$query .= ‘, last_public_entry_id=? ‘;
$bind[] = $entry_id;
}
$query .= ‘ where problem_id = ? ‘;
$bind[] = $problem_id;
$dbh->query($query,$bind);
// get the email address of the customer who opened this call
// if this was a public history entry, and if the email address
// is not empty
if ($entry_type == ‘public’)
{

$query = ‘select c.email, p.problem_code from problems p,
customers c
where p.problem_id = ? and p.customer_id = c.customer_id
and trim(ifnull(c.email,””)) <> “”
‘;
$email = NULL;
list($email,$problem_code) = $dbh->getRow(
$query
, array($problem_id)
, DB_FETCHMODE_ORDERED
);
if ($email)
{
// we have a valid email address - use it to
// notify the customer that the call record
// has been updated.
460 Part IV: Not So Simple Applications
notify_customer($problem_id,$email,$notes,$problem_code);
}
}
return TRUE;
}
notify_customer()
This function constructs an email and sends it. The email informs the user that his
or her problem is being tracked and provides a link to the page that gives the status
of the problem in the system.
function notify_customer (
$problem_id=NULL
, $email=NULL
, $notes=NULL

, $problem_code=NULL
)
{
// remove any HTML tags from $notes.
$notes = cleanup_text($notes);
$dbh = db_connect();
if (!$problem_code)
{
$problem_code = $dbh->getOne(
‘select problem_code from problems where problem_id = ?’
, array($problem_id)
);
if (!$problem_code)
{
$problem_code = create_problem_code();
$dbh->query(
‘update problems set problem_code = ? where
problem_id = ?’
, array($problem_code, $problem_id)
);
}
}
// build an absolute URL calling the problem_status.php page
// to check on this problem
$problem_url = regular_url(
‘problem.php?problem_code=’.$problem_code
Chapter 13: Problem-Tracking System 461
);
if (strpos($problem_url, ‘/staff’) !== FALSE)
$problem_url = str_replace(‘/staff’, ‘’, $problem_url);

// set the body of the email
$msgtext = <<<EOQ
Problem Update:
$notes
You can check the current status of this problem at
$problem_url
Thanks for your patience.
EOQ;
// set the headers of the email
// the Apache variable $_SERVER[‘SERVER_NAME’] is the name
// of the server we’re running on, minus any port number.
$headers = ‘From: webmaster@’.$_SERVER[‘SERVER_NAME’].”\n”
. ‘Reply-To: webmaster@’.$_SERVER[‘SERVER_NAME’].”\n”
. ‘X-Mailer: PHP/’.phpversion()
. ‘Bcc: webmaster@’.$_SERVER[‘SERVER_NAME’].”\n”
;
// send the email
return mail($email, ‘Problem Update’, $msgtext, $headers);
}
PHP will have to be able to find sendmail or another SMTP-compliant mail
server in order for this to work. Check your php.ini file if you’re having
problems.
status_change()
The status of a problem is going to be something like “open,” “closed,” or “pending.”
If it changes you are going to want to mark the exact change and record something
like “Status changed to closed by John.” The change should be recorded in the his-
tory table.
462 Part IV: Not So Simple Applications
function status_change($problem_id=NULL
, $entered_by=’customer’

, $new_status_id=NULL
, $old_status_id=NULL
)
{
$error = NULL;
if (empty($problem_id))
{
$error = ‘No problem ID supplied for status change’;
}
elseif (empty($new_status_id))
{
$error = ‘No new status ID supplied for status change’;
}
elseif (!($new_status = status($new_status_id)))
{
$error = “New status ID $new_status_id is not valid.”;
}
if ($error)
{
user_error($error, E_USER_WARNING);
return FALSE;
}
// just return if no change - not an error condition,
// just a no-op
if ($old_status_id == $new_status_id)
{
return TRUE;
}
if (empty($entered_by)) { $entered_by = ‘customer’; }
// get the ID of the entry_type ‘public’, and construct

// a string containing the new status value and either
// the real name of the staff member who made the change,
// or the value of $entered_by if no matching staff
// member is found. for example, if the staff member Joe Blow
// closes a call, the notes field will be set to
// ‘Status set to Closed by Joe Blow’. if a customer
// re-opens a call, notes will be set to
Chapter 13: Problem-Tracking System 463
// ‘Status set to Re-opened by customer’.
$entry_type_id = entry_type_id(‘public’);
$notes = “Status set to $new_status by “;
if ($entered_by != ‘customer’ && isset($GLOBALS[‘staff_name’]))
{
$notes .= $_GLOBALS[‘staff_name’];
}
else
{
$notes .= $entered_by;
}
history_entry($problem_id, $entry_type_id, $entered_by,
‘program’, $notes);
}
create_problem_code()
This function creates a unique and highly random 8-character alphanumeric code.
function create_problem_code()
{
return substr(md5(uniqid(rand())),0,8);
}
Scripts
Here are the pages that are actually called by URLs and include statements.

problem.php
This page does little but call either the enter_problem() or update_problem()
function.
require_once(‘header.php’);
$params = $_REQUEST;
$params[‘entered_by’] = ‘customer’;
$params[‘source’] = ‘web’;
if (empty($params[‘problem_code’]))
{
enter_problem($params);
}
464 Part IV: Not So Simple Applications
else
{
update_problem($params);
}
problem_entry_form.php
Mostly this form makes calls to the functions in your /book/functions/ folder. It
prints the form shown in Figure 13-1 and determines the default information in the
form. The call_entry.php page will include this page.
The interesting part of this script is its use of a template class to define the
appearance of the generated HTML document. The variable $tpl is defined as a
template_object():
$tpl = template_object();
It is then loaded with an HTML template (problem_entry.html) that includes sev-
eral named variables in its code.
These named variables come in handy when it’s time to enter dynamic informa-
tion into the HTML document. The general procedure for writing to a named block
is this:
$tpl->setCurrentBlock(block_name’);

$tpl->setVariable(‘template_variable_name’,$local_variable_name);
$tpl->parseCurrentBlock();
This strategy enables you to enter programmatically determined values — typi-
cally from database lookups — into templates, and to have the templates apply stan-
dardized formatting. It insulates you from formatting issues, which can be no end
of trouble. Here is the complete listing:
function problem_entry_form(&$params)
{
global $default_page_title;
$tpl = template_object();
if ($tpl->loadTemplatefile(‘problem_entry.html’) === FALSE)
{
user_error(
‘Could not load problem entry template’
, E_USER_ERROR
);
Chapter 13: Problem-Tracking System 465

×