[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
?
Solved

Content-Type Display Error

Posted on 2005-04-06
13
Medium Priority
?
339 Views
Last Modified: 2013-12-25
Hi All,

I have just started learning perl two weeks ago so you can say that i am a real newbie.  The problem is that every page I use subroutines the following message is displayed at the bottom of each screen:

Content-type: text/html; charset=ISO-8859-1 1

I have included the two modules and a script that I use when the error occurs.  Any help would be most appreciated, also if I am making any fundemental errors with coding please let me know.  

Thanks.



package tkdb;
# tkdb.pm

use strict;
use CGI qw(:standard);
use CGI;
use DBI;
use Apache::Session::MySQL;

my $cgi = new CGI;

sub connect
{
my $db_name = "dbname";              # database name
my $host_name = "localhost";      # database hostname
my $user_name = "username";                        # database username
my $password = "password";                                    # database password
my $dsn = "DBI:mysql:host=$host_name;database=$db_name";

      return (DBI->connect ($dsn, $user_name, $password,
                                          { PrintError => 0, RaiseError => 1}));
}

sub site_header
{
      my $cookie_id = cookie("PERLSESSID");
      my $dbh = tkdb::connect ();
      my ($id, $lnk_uid);
      # **************** Start of Title ****************
      my $title = "Tekmos Database";
      # **************** End of Title ****************
      my $sth = $dbh->prepare("SELECT id, lnk_uid FROM sessions WHERE (id='$cookie_id')");
      $sth->execute();
      ($id, $lnk_uid) = $sth->fetchrow_array();
      
      if($id ne $cookie_id)
            {
                  print "Content-type: text/html\n\n";
                  print "<html><head>
                        <SCRIPT language='JavaScript1.1'>
                        <!--
              location.replace('../../logout.pl');
                //-->
                        </SCRIPT></HEAD>";
            }
      else
            {
      # Declaring global variables
                  use vars qw($level $team $handle);
                  
                  my $sql = $dbh->prepare("SELECT level, team, handle FROM authuser WHERE (uid='$lnk_uid')");
                  $sql->execute();
                  ($level, $team, $handle) = $sql->fetchrow_array();
                  $main::level = $level;
                  $main::team = $team;
                  $main::handle = $handle;
                  print "Content-type: text/html\n\n";
                  print "<html><head><title>",$title,"</title><link rel='stylesheet' href='../../style.css'></head><body>";
            }
}

sub site_footer
{
      print "</td></tr></table>";
      print "</body></html>";
}

# ****************** Standard Table Structure *****************
sub top_menu {
      print "<table class='main_table'>\n";
      print "<tr height='20'>\n";
      print "<td width='150' class='topmenu'><center><font color='#ff0000'>", $main::handle ,"</font></center></td>\n";
      print "<td class='topmenu' align='center'><a href='../auth/main.pl'>Main Page</a> | <a href='../customer/index.pl'>Customer</a> | <a href='../../logout.pl'>Logout</a></td>";
      print "</tr>";
}

# ****************** Standard Left Table Structure *****************
sub customer_left_menu_top {
      print "<tr>";
      print "<td class='topmenu' valign='top'>";
      print "<center><b>Customer Menu</b></center><br><a href='index.pl?cmd=new' class='topmenu'>New Customer</a><br><br><a href='index.pl?cmd=search' class='topmenu'>Search Customer</a><hr>";
}

sub customer_left_menu_bottom {
      print "</td><td valign='top'>";
}

1;


customer.pm:
# customer.pm

package customer;
use base qw( CGI::Application );

use strict;
use warnings;
use CGI::Carp qw(fatalsToBrowser);

use CGI qw(:standard);
use Apache::Session::MySQL;
use tkdb;
my $dbh = tkdb::connect();

sub setup {
      my $self = shift;
      $self->start_mode('index');
      $self->mode_param('cmd');
      $self->run_modes(
            'index' => 'showIndexPage',
            'new' => 'showNewCustomerPage',
            'search' => 'showSearchCustomerPage',
      );
}

sub showIndexPage {
tkdb::site_header();
tkdb::top_menu();
my $self = shift;

tkdb::customer_left_menu_top();

tkdb::customer_left_menu_bottom();
# ********************** Start of Main Body Text **********************

print "Index Page";

# ********************** End of Main Body Text **********************
tkdb::site_footer;

exit (0);
}

sub showNewCustomerPage {
tkdb::site_header();
tkdb::top_menu();
my $self = shift;

tkdb::customer_left_menu_top();

tkdb::customer_left_menu_bottom();
# ********************** Start of Main Body Text **********************

print "New Customers";

# ********************** End of Main Body Text **********************
tkdb::site_footer();

}

sub showSearchCustomerPage {
tkdb::site_header();
tkdb::top_menu();
my $self = shift;

tkdb::customer_left_menu_top();

tkdb::customer_left_menu_bottom();
# ********************** Start of Main Body Text **********************

print "Search Customers";

# ********************** End of Main Body Text **********************
tkdb::site_footer();

}

1;


#!/usr/bin/perl -w
# index.pl

use customer;
my $webapp = customer->new();
$webapp->run();
0
Comment
Question by:falthorn
  • 10
  • 3
13 Comments
 
LVL 18

Expert Comment

by:kandura
ID: 13715600
I could give you an answer that would take away the header you're seeing at the bottom of the screen, but I'm not going to do that. You have the code flow inside out right now, and I'd rather you do it the right way than continue on this path.

There can only be one CGI object at all times during one run. CGI::Application creates one when you call customer->new(), and you should be using that one throughout your code.
The one you instantiate in tkdb.pm will not contain any data that was posted to your script, since it's already processed by the one in customer.
Furthermore, CGI::Application asks that you never print anything to stdout. The print statements in tkdb will come before the customer-app has a chance to build up the page. Since tkdb::site_header already prints a http header, the one from customer will end up in the page.

The biggest problem with your code is that you have not cleanly separated the data and presentation layers: you should treat tkdb as your Model, and customer as your Controller. This means that tkdb should not be generating output. Instead, tkdb should simply return the data to customer, which should incorporate it in its output.

I suggest you take out all the CGI and html related code in tkdb, and move it to customer where needed. Anything that creates html should be in the customer module, and tkdb should simply return the values it creates.

You're also repeating code that's the same everywhere. That could be refactored too. Every run mode does the same thing, except a small string in the middle.

I'm not really sure where to begin with advising you how to do this right. Your choice of CGI::Application is a very good one (I use it in several very large web applications), but you haven't really understood its operation.
So I'm going to try and mock up some instructions.




0
 
LVL 18

Expert Comment

by:kandura
ID: 13716064
Ok, here's a stab at it. Let's look at what you've got sofar.

Your application consists of three parts: a presentation layer (the html), a data layer (which retrieves values from a database), and a controller layer that connects the presentation and data layers. This is a classic example of the MVC (Model-View-Controller) paradigm.
customer.pm is your controller, and tkdb.pm is your model. the view is built up inside the controller. The controller should use incoming information from CGI to query tkdb for the correct data, and integrate that into the display.
Since you're using CGI::Application, you also get HTML::Template for free. I suggest you use it to separate the html from the controller.

First up, we're going to rewrite tkdb so that it only queries the database, based on input parameters. I'll create two methods, based on your current site_header: get_link_uid() and get_auth_user().
Here's the rewritten tkdb:


    package tkdb;
    # tkdb.pm
   
    use strict;
    use DBI;
   
    sub connect
    {
        my $db_name = "dbname";            # database name
        my $host_name = "localhost";     # database hostname
        my $user_name = "username";                    # database username
        my $password = "password";                              # database password
        my $dsn = "DBI:mysql:host=$host_name;database=$db_name";
   
        return (DBI->connect ($dsn, $user_name, $password,
                                       { PrintError => 0, RaiseError => 1}));
    }
   
    # input: $cookie_id
    # output: $id, $lnk_uid
    sub get_lnk_uid
    {
        my $cookie_id = shift;
   
        my $dbh = tkdb::connect();
        my $sth = $dbh->prepare("SELECT id, lnk_uid FROM sessions WHERE id=?");
        $sth->execute($cookie_id);
        my ($id, $lnk_uid) = $sth->fetchrow_array();
   
        return ($id, $lnk_uid);
    }
   
    # input: $lnk_uid
    # output: ($level, $team, $handle)
    sub get_auth_user
    {
        my $lnk_uid = shift;    
   
        my $dbh = tkdb::connect();
        my $sql = $dbh->prepare("SELECT level, team, handle FROM authuser WHERE uid=?");
        $sql->execute($lnk_uid);
        my ($level, $team, $handle) = $sql->fetchrow_array();
   
        return ($level, $team, $handle);
    }
   
    1;
   
   
See how I've taken out all references to html and cgi? Now this little module can be used outside cgi scripts as well. Note that we connect() in every sub; that is something we should cleanup later.

Next up, we're going to add the html code from tkdb.pm to customer.pm, and make customer.pm use the new methods in tkdb.

0
 

Author Comment

by:falthorn
ID: 13716131
Hi Kandura,

I have removed from what I can see the html from the tkdb.pm and placed it into the customer.pm.  I know that I have repeated some parts a few times.  Every time I remove the repeats, the script ends up displaying a page with missing parts.  Below is a copy of the updated scripts.

package tkdb;
# tkdb.pm

use strict;
use CGI qw(:standard);
use DBI;
use Apache::Session::MySQL;

sub connect
{
my $db_name = "dbname";            # database name
my $host_name = "localhost";     # database hostname
my $user_name = "username";                    # database username
my $password = "password";                              # database password
my $dsn = "DBI:mysql:host=$host_name;database=$db_name";

      return (DBI->connect ($dsn, $user_name, $password,
                                          { PrintError => 0, RaiseError => 1}));
}

sub pre_header
{
      my $cookie_id = cookie("PERLSESSID");
      my $dbh = tkdb::connect ();
      my ($id, $lnk_uid);
      my $sth = $dbh->prepare("SELECT id, lnk_uid FROM sessions WHERE (id='$cookie_id')");
      $sth->execute();
      ($id, $lnk_uid) = $sth->fetchrow_array();
      
      # Declaring global variables
      use vars qw($level $team $handle);
      my $sql = $dbh->prepare("SELECT level, team, handle FROM authuser WHERE (uid='$lnk_uid')");
      $sql->execute();
      ($level, $team, $handle) = $sql->fetchrow_array();
      $main::level = $level;
      $main::team = $team;
      $main::handle = $handle;
}

1;      # return true

package customer;
# customer.pm

use base qw( CGI::Application );

use strict;
use warnings;
use CGI::Carp qw(fatalsToBrowser);
require tkdb;
use CGI qw(:standard);
use Apache::Session::MySQL;
my $dbh = tkdb::connect();

tkdb::pre_header();
header();

sub setup {
      my $self = shift;
      $self->start_mode('index');
      $self->mode_param('cmd');
      $self->run_modes(
            'index' => 'showIndexPage',
            'new' => 'showNewCustomerPage',
            'search' => 'showSearchCustomerPage',
      );
}

sub showIndexPage {

my $self = shift;
top_menu();
customer_left_menu_top();
print "Sub Menu";
customer_left_menu_bottom();
# ********************** Start of Main Body Text **********************

print "Index Page";

# ********************** End of Main Body Text **********************
footer();
}

sub showNewCustomerPage {
my $self = shift;
top_menu();
customer_left_menu_top();
print "Sub Menu";
customer_left_menu_bottom();
# ********************** Start of Main Body Text **********************

print "New Customers";

# ********************** End of Main Body Text **********************
footer();
}

sub showSearchCustomerPage {
my $self = shift;
top_menu();
customer_left_menu_top();
print "Sub Menu";
customer_left_menu_bottom();
# ********************** Start of Main Body Text **********************

print "Search Customers";

# ********************** End of Main Body Text **********************
footer();
}


# ****************** Standard Table Structure *****************
sub header {
      print "Content-type: text/html\n\n";
      print "<html><head><title>Tekmos Database</title><link rel='stylesheet' href='../../style.css'></head><body>";
}

sub top_menu {
      print "<table class='main_table'>\n";
      print "<tr height='20'>\n";
      print "<td width='150' class='topmenu'><center><font color='#ff0000'>", $main::handle ,"</font></center></td>\n";
      print "<td class='topmenu' align='center'><a href='../auth/main.pl'>Main Page</a> | <a href='../customer/index.pl'>Customer</a> | <a href='../../logout.pl'>Logout</a></td>";
      print "</tr>";
}
# ****************** Standard Left Table Structure *****************
sub left_menu_top {
      print "<tr>";
      print "<td class='topmenu' valign='top'>";
      print "<center><b>Admin Menu</b></center><br><a href='#' class='topmenu'>Change Password</a><br><br><hr>";
}

sub left_menu_bottom {
      print "</td><td valign='top'>";
}
# ****************** Customer Left Table Structure *****************
sub customer_left_menu_top {
      print "<tr>";
      print "<td class='topmenu' valign='top'>";
      print "<center><b>Customer Menu</b></center><br><a href='index.pl?cmd=new' class='topmenu'>New Customer</a><br><br><a href='index.pl?cmd=search' class='topmenu'>Search Customer</a><hr>";
}

sub customer_left_menu_bottom {
      print "</td><td valign='top'>";
}

sub footer
{
      print "</td></tr></table>";
      print "</body></html>";
}

1; # return true


#!/usr/bin/perl -w
# index.pl

use strict;
use customer;
my $webapp = customer->new();
$webapp->run();
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
LVL 18

Expert Comment

by:kandura
ID: 13716244
Ok, I wasn't quite finished with my tutorial yet, but that's okay ;^)

Let me explain how CGI::Application operates first, because that's where most problems come from at the moment. The first thing you should realise is that you are expected to *never* use print() in your runmodes. Instead, you return the generated html, and then cgi-app will do the right thing with headers and document.

This is what happens in a run:

    $customer = customer->new();

This creates a new object, initializes it and calls the following methods on the object:

    $self->cgiapp_init(@args); # @args are arguments to new()
    $self->setup();            # this will be your customer::setup()

Next, you call run:

    $customer->run();

cgi-app now calls the following methods in order.

    $self->cgiapp_prerun();
    $self->$current_run_mode();
    $self->cgiapp_postrun();

After postrun() is called, the http headers and the document are sent to the browser.

Finally, cgi-app performs some cleanup by calling

    $self->teardown();

The contents that you generate in your runmodes can still be modified by postrun(), which is why you should always "return" contents, and never print, because print interrupts the flow of the application.
0
 
LVL 18

Expert Comment

by:kandura
ID: 13716250
We need to rewrite the methods for the page parts, so we add a couple of subs to customer for those. The runmodes will call these subs to build up the complete page.
Here's the new code.

    package customer;
    use base qw( CGI::Application );
   
    use strict;
    use warnings;
    use CGI::Carp qw(fatalsToBrowser);
   
    use tkdb;
   
    sub setup {
         my $self = shift;
         $self->start_mode('index');
         $self->mode_param('cmd');
         $self->run_modes(
              'index' => 'showIndexPage',
              'new' => 'showNewCustomerPage',
              'search' => 'showSearchCustomerPage',
         );
    }
   
    sub showIndexPage {
        my $self = shift;
   
        my $output;
        $output .= $self->site_header();
       
        # a side effect of site_header is that we get the level, team and handle stored in $self->param.
        # we pass the handle on to top_menu:
        $output .= $self->top_menu( $self->param('handle') );
   
        $output .= $self->left_menu_top();
        $output .= $self->left_menu_bottom();
   
        # ********************** Start of Main Body Text **********************
   
        $output .= "Index Page";
   
        # ********************** End of Main Body Text **********************
   
        $output .= $self->site_footer();
       
        return $output;
    }
   
    sub showNewCustomerPage {
        my $self = shift;
   
        my $output;
        $output .= $self->site_header();
   
        # a side effect of site_header is that we get the level, team and handle stored in $self->param.
        # we pass the handle on to top_menu:
        $output .= $self->top_menu( $self->param('handle') );
   
        $output .= $self->left_menu_top();
        $output .= $self->left_menu_bottom();
   
        # ********************** Start of Main Body Text **********************
   
        $output .= "New Customers";
   
        # ********************** End of Main Body Text **********************
   
        $output .= $self->site_footer();
       
        return $output;
    }
   
    sub showSearchCustomerPage {
        my $self = shift;
   
        my $output;
        $output .= $self->site_header();
   
        # a side effect of site_header is that we get the level, team and handle stored in $self->param.
        # we pass the handle on to top_menu:
        $output .= $self->top_menu( $self->param('handle') );
   
        $output .= $self->left_menu_top();
        $output .= $self->left_menu_bottom();
   
        # ********************** Start of Main Body Text **********************
   
        $output .= "Search Customers";
   
        # ********************** End of Main Body Text **********************
   
        $output .= $self->site_footer();
       
        return $output;
    }
   
    sub site_header {
        my $self = shift;
        # **************** Start of Title ****************
        my $title = "Tekmos Database";
        # **************** End of Title ****************
   
        my $cookie_id = $self->query->cookie("PERLSESSID");    # this is where we use the built-in cgi object
   
        my ($id, $lnk_uid) = tkdb::get_lnk_uid($cookie_id);
       
        if($id ne $cookie_id)
          {
             return q{
                 <html><head>
                    <SCRIPT language='JavaScript1.1'>
                    <!--
                     location.replace('../../logout.pl');
                    //-->
                    </SCRIPT></HEAD>
                };
          }
   
        my ($level, $team, $handle)    = tkdb::get_auth_user($lnk_uid);
        ### we need to store level, team, and handle somewhere so we can use them in other parts of the page.
        ### CGI::Application has a storage area in $self->param, so we use that:
       
        $self->param(level  => $level );
        $self->param(team   => $team  );
        $self->param(handle => $handle);
       
        return qq{
            <html>
                <head>
                    <title>$title</title>
                    <link rel='stylesheet' href='../../style.css'>
                </head>
                <body>
            };
       
    }
   
    sub site_footer {
        my $self = shift;
        my $mt = q{    
            </td></tr></table>
             </body></html>
        };
       
        return $mt;
    }
   
    sub top_menu {
        my $self = shift;
        my $handle = shift;
       
        my $mt = qq{
            <table class='main_table'>
            <tr height='20'>
            <td width='150' class='topmenu'><center><font color='#ff0000'> $handle </font></center></td>
            <td class='topmenu' align='center'><a href='../auth/main.pl'>Main Page</a> | <a href='../customer/index.pl'>Customer</a> | <a href='../../logout.pl'>Logout</a></td>
            </tr>
        };
       
        return $mt;
    }
   
    sub left_menu_top {
        my $self = shift;
        my $mt = q{
            <tr>
            <td class='topmenu' valign='top'>
            <center><b>Customer Menu</b></center><br><a href='index.pl?cmd=new' class='topmenu'>New Customer</a><br><br><a href='index.pl?cmd=search' class='topmenu'>Search Customer</a><hr>
        };
       
        return $mt;
    }
   
    sub left_menu_bottom {
        my $self = shift;
        return "</td><td valign='top'>";
    }
   
    1;

There are still several improvements to be made:
- the sub site_header is responsible for querying the database, and following subs depend on it for their content. The side effects are not very clear, so that logic should be taken out of site_header and put somewhere else.
- the runmodes look very similar, and duplicate a lot of code. That should be rewritten as well. In fact, we should not be generating snippets of html at all; it would be much better if we had a template for each runmode, fill that template with values specific to the runmode, and return the template output.

0
 
LVL 18

Expert Comment

by:kandura
ID: 13716362
The first thing I would do is to take the database specific code out of site_header, and place it in cgiapp_prerun.
Since part of that code is to determine whether the user is logged in, and if not perform a redirect, prerun is the appropriate place to do it.
We need to add a new runmode in setup(), called 'logout'.


    sub cgiapp_prerun {
        my $self = shift;
        my $cookie_id = $self->query->cookie("PERLSESSID");    # this is where we use the built-in cgi object
   
        my ($id, $lnk_uid) = tkdb::get_lnk_uid($cookie_id);
       
        if($id ne $cookie_id)
        {
            # we need to redirect to logout.pl
            # to do this, we switch runmodes to logout
            $self->prerun_mode('logout');
            return;
        }

        my ($level, $team, $handle)    = tkdb::get_auth_user($lnk_uid);
        ### we need to store level, team, and handle somewhere so we can use them in other parts of the page.
        ### CGI::Application has a storage area in $self->param, so we use that:
       
        $self->param(level  => $level );
        $self->param(team   => $team  );
        $self->param(handle => $handle);
    }
   
    sub logout {
        my $self = shift;
        $self->header_type('redirect');
        $self->header_props(-url => '../../logout.pl');
        return "redirecting to logout.pl";
    }

    sub site_header {
        my $self = shift;
        # **************** Start of Title ****************
        my $title = "Tekmos Database";
        # **************** End of Title ****************

        return qq{
            <html>
                <head>
                    <title>$title</title>
                    <link rel='stylesheet' href='../../style.css'>
                </head>
                <body>
            };
    }

0
 
LVL 18

Expert Comment

by:kandura
ID: 13716508
Next, we are in the position to clean up the main run modes.
There are several ways we can do this, but the main point is that each runmode should only have to concern itself with its own content, and rely on other parts of the application to put the pieces together.
One way would be to add a new method that does that:

    sub construct_page {
        my $self = shift;
        my $runmode_contents = shift;

        my $output = $self->site_header();
        $output .= $self->top_menu( $self->param('handle') );
        $output .= $self->left_menu_top();
        $output .= $self->left_menu_bottom();

        $output .= $runmode_contents;
   
        $output .= $self->site_footer();
       
        return $output;
    }

Now each runmode can look like this:

    sub showSearchCustomerPage {
        my $self = shift;
        my $content = "Search Customers";
       
        return $self->construct_page($content);
    }

Another way is to override cgiapp_postrun. That way we wrap the runmode contents with the site-specific stuff at the end of the run:

    sub cgiapp_postrun {
        my $self = shift;
        my $outputref = shift;  # this is a *reference* to the contents

        my $output = $self->site_header();
        $output .= $self->top_menu( $self->param('handle') );
        $output .= $self->left_menu_top();
        $output .= $self->left_menu_bottom();

        $output .= $$outputref;
   
        $output .= $self->site_footer();

        $outputref = \$output;
    }


Now each runmode can look like this:

    sub showSearchCustomerPage {
        my $self = shift;
        my $content = "Search Customers";
        return $content;
    }

and that is even simpler.

For the last installment of this tutorial, I'll show you how to use HTML::Template, which will make your applications even cleaner.
0
 
LVL 18

Expert Comment

by:kandura
ID: 13716595
First, here's the latest version of customer.pm, so you can see it all together:


    package customer;
    use base qw( CGI::Application );
   
    use strict;
    use warnings;
    use CGI::Carp qw(fatalsToBrowser);
   
    use tkdb;
   
    sub setup {
         my $self = shift;
         $self->start_mode('index');
         $self->mode_param('cmd');
         $self->run_modes(
              'index'  => 'showIndexPage',
              'new'    => 'showNewCustomerPage',
              'search' => 'showSearchCustomerPage',
              'logout' => 'logout',
         );
    }
   
    sub cgiapp_prerun {
        my $self = shift;
        my $cookie_id = $self->query->cookie("PERLSESSID");    # this is where we use the built-in cgi object
   
        my ($id, $lnk_uid) = tkdb::get_lnk_uid($cookie_id);
   
        if($id ne $cookie_id)
        {
            # we need to redirect to logout.pl
            # to do this, we switch runmodes to logout
            $self->prerun_mode('logout');
            return;
        }
   
        my ($level, $team, $handle)    = tkdb::get_auth_user($lnk_uid);
        ### we need to store level, team, and handle somewhere so we can use them in other parts of the page.
        ### CGI::Application has a storage area in $self->param, so we use that:
   
        $self->param(level  => $level );
        $self->param(team   => $team  );
        $self->param(handle => $handle);
    }
   
    sub cgiapp_postrun {
        my $self = shift;
        my $outputref = shift;  # this is a *reference* to the contents
   
        my $output = $self->site_header();
        $output .= $self->top_menu( $self->param('handle') );
        $output .= $self->left_menu_top();
        $output .= $self->left_menu_bottom();
   
        $output .= $$outputref;
   
        $output .= $self->site_footer();
   
        $$outputref = $output;
    }
   
    sub logout {
        my $self = shift;
        $self->header_type('redirect');
        $self->header_props(-url => '../../logout.pl');
        return "redirecting to logout.pl";
    }
   
    sub showIndexPage {
        my $self = shift;
        my $content = "Index Page";
        return $content;
    }
   
    sub showNewCustomerPage {
        my $self = shift;
        my $content = "New Customers";
        return $content;
    }
   
    sub showSearchCustomerPage {
        my $self = shift;
        my $content = "Search Customers";
        return $content;
    }
   
    sub site_header {
        my $self = shift;
        my $title = "Tekmos Database";
   
        return qq{
            <html>
                <head>
                    <title>$title</title>
                    <link rel='stylesheet' href='../../style.css'>
                </head>
                <body>
            };
    }
   
    sub site_footer {
        my $self = shift;
        my $mt = q{
            </td></tr></table>
            </body></html>
        };
       
        return $mt;
    }
   
    sub top_menu {
        my $self = shift;
        my $handle = shift;
       
        my $mt = qq{
            <table class='main_table'>
            <tr height='20'>
            <td width='150' class='topmenu'><center><font color='#ff0000'> $handle </font></center></td>
            <td class='topmenu' align='center'><a href='../auth/main.pl'>Main Page</a> | <a href='../customer/index.pl'>Customer</a> | <a href='../../logout.pl'>Logout</a></td>
            </tr>
        };
       
        return $mt;
    }
   
    sub left_menu_top {
        my $self = shift;
        my $mt = q{
            <tr>
            <td class='topmenu' valign='top'>
            <center><b>Customer Menu</b></center><br><a href='index.pl?cmd=new' class='topmenu'>New Customer</a><br><br><a href='index.pl?cmd=search' class='topmenu'>Search Customer</a><hr>
        };
       
        return $mt;
    }
   
    sub left_menu_bottom {
        my $self = shift;
        return "</td><td valign='top'>";
    }
   
    1;
   
0
 
LVL 18

Accepted Solution

by:
kandura earned 2000 total points
ID: 13716758
This is how every page looks, with the variables in place:

            <html>
                <head>
                    <title>$title</title>
                    <link rel='stylesheet' href='../../style.css'>
                </head>
                <body>
           
            <table class='main_table'>
            <tr height='20'>
            <td width='150' class='topmenu'><center><font color='#ff0000'> $handle </font></center></td>
            <td class='topmenu' align='center'><a href='../auth/main.pl'>Main Page</a> | <a href='../customer/index.pl'>Customer</a> | <a href='../../logout.pl'>Logout</a></td>
            </tr>
           
            <tr>
            <td class='topmenu' valign='top'>
            <center><b>Customer Menu</b></center><br><a href='index.pl?cmd=new' class='topmenu'>New Customer</a><br><br><a href='index.pl?cmd=search' class='topmenu'>Search Customer</a><hr>
           
            </td><td valign='top'>

            $runmode_content

            </td></tr></table>
            </body></html>

HTML::Template enables us to take this html, and put it in a separate file. The things that vary for each runmode can be passed in, and a complete page can be generated.
Have a look at the documentation of HTML::Template at http://search.cpan.org/author/SAMTREGAR/HTML-Template-2.7/Template.pm

Here's the template I would use:

            <html>
                <head>
                    <title> <tmpl_var title> </title>
                    <link rel='stylesheet' href='../../style.css'>
                </head>
                <body>
           
            <table class='main_table'>
            <tr height='20'>
            <td width='150' class='topmenu'><center><font color='#ff0000'> <tmpl_var handle> </font></center></td>
            <td class='topmenu' align='center'><a href='../auth/main.pl'>Main Page</a> | <a href='../customer/index.pl'>Customer</a> | <a href='../../logout.pl'>Logout</a></td>
            </tr>
           
            <tr>
            <td class='topmenu' valign='top'>
            <center><b>Customer Menu</b></center><br><a href='index.pl?cmd=new' class='topmenu'>New Customer</a><br><br><a href='index.pl?cmd=search' class='topmenu'>Search Customer</a><hr>
           
            </td><td valign='top'>

            <tmpl_var runmode_content>

            </td></tr></table>
            </body></html>

Save this in a file called tekmos.html.

Now we can use this in our application. All we need to do is instantiate a template object for this file, fill in the details, and generate the output.
I'm going to do this in our overridden cgiapp_postrun:

sub cgiapp_postrun {
    my $self = shift;
    my $outputref = shift;  # this is a *reference* to the contents

    my $template = $self->load_tmpl('tekmos.html');
    $template->param(
            title           => $self->param('title'),
            level           => $self->param('level'),
            team            => $self->param('team'),
            handle          => $self->param('handle'),
            runmode_content => $$outputref,
        );

    $$outputref = $template->output;
}

We still need to set $self->param('title') somewhere. I suggest you do that in setup().
All the subs site_header, site_footer, top_menu, left_menu_top and left_menu_bottom are now superfluous, so we can remove them.
Here's the new version of customer.pm:


    package customer;
    use base qw( CGI::Application );
   
    use strict;
    use warnings;
    use CGI::Carp qw(fatalsToBrowser);
   
    use tkdb;
   
    sub setup {
         my $self = shift;
         $self->start_mode('index');
         $self->mode_param('cmd');
         $self->run_modes(
              'index'  => 'showIndexPage',
              'new'    => 'showNewCustomerPage',
              'search' => 'showSearchCustomerPage',
              'logout' => 'logout',
         );
    }
   
    sub cgiapp_prerun {
        my $self = shift;
        my $cookie_id = $self->query->cookie("PERLSESSID");    # this is where we use the built-in cgi object
   
        my ($id, $lnk_uid) = tkdb::get_lnk_uid($cookie_id);
   
        if($id ne $cookie_id)
        {
            # we need to redirect to logout.pl
            # to do this, we switch runmodes to logout
            $self->prerun_mode('logout');
            return;
        }
   
        my ($level, $team, $handle)    = tkdb::get_auth_user($lnk_uid);
        ### we need to store level, team, and handle somewhere so we can use them in other parts of the page.
        ### CGI::Application has a storage area in $self->param, so we use that:
   
        $self->param(level  => $level );
        $self->param(team   => $team  );
        $self->param(handle => $handle);
    }
   
    sub cgiapp_postrun {
        my $self = shift;
        my $outputref = shift;  # this is a *reference* to the contents
   
        my $template = $self->load_tmpl('tekmos.html');
        $template->param(
                title           => $self->param('title'),
                level           => $self->param('level'),
                team            => $self->param('team'),
                handle          => $self->param('handle'),
                runmode_content => $$outputref,
            );
   
        $$outputref = $template->output;
    }
   
    sub logout {
        my $self = shift;
        $self->header_type('redirect');
        $self->header_props(-url => '../../logout.pl');
        return "redirecting to logout.pl";
    }
   
    sub showIndexPage {
        my $self = shift;
        my $content = "Index Page";
        return $content;
    }
   
    sub showNewCustomerPage {
        my $self = shift;
        my $content = "New Customers";
        return $content;
    }
   
    sub showSearchCustomerPage {
        my $self = shift;
        my $content = "Search Customers";
        return $content;
    }
   
    1;
   

See? much cleaner, and much easier to maintain and extend.
0
 

Author Comment

by:falthorn
ID: 13717267
Thanks Kandura,

You have been awesome setting aside time to teach perl/cgi and how to properly layout the code.

I have read through all the code and I now understand so much more about perl and cgi::application.  The cgi::application all makes sense now.

I have updated all the code and seem to have stumbled acrros a problem.

The error message that I get when running the script is as follows:

DBD::mysql::st execute failed: called with 1 bind variables when 0 are needed at /usr/lib/perl5/5.8.3/tkdb.pm line 27

This line is my:

$sth = $dbh->prepare("SELECT id, lnk_uid FROM sessions WHERE (id='d42b8f235c980ed4d5411b174e5747e2')");

The long string is the actual cookie id that I am just using for testing.  Initally I used the following:

my $sth = $dbh->prepare("SELECT id, lnk_uid FROM sessions WHERE (id='$cookie_id')");

But both have given the same error message.  Is there something that I am doing wrong?
0
 
LVL 18

Expert Comment

by:kandura
ID: 13717518
falthorn,
> You have been awesome setting aside time to teach perl/cgi and how to
> properly layout the code.

Thank you :-)
I happen to like CGI::Application a lot, and think it's important to show you how to make the best use of it.

> Is there something that I am doing wrong?

not really. Did you use the code changes I made to those queries? I was using bind values, instead of using the variable $cookie_id directly in the sql:

        my $sth = $dbh->prepare("SELECT id, lnk_uid FROM sessions WHERE id=?");
        $sth->execute($cookie_id);
        my ($id, $lnk_uid) = $sth->fetchrow_array();

See how the query now contains a question mark? That's called a placeholder, and we only give it the proper value in the call to execute. This is both faster in persistent environments (such as mod_perl) because the database can cache this query; and it is a bit safer too, because there's now no possibility of sql injection.

Maybe you did copy the "$sth->execute($cookie_id)" but didn't have the placeholder in the sql?
0
 

Author Comment

by:falthorn
ID: 13717932
Hi Kandura,

It was my fault I didn't read carefully what you had written.

All the scripting works a treat.

Thanks again.  I am truly grateful for all your help.

I will increase the much deserved points and assign them to you.

Regards

Falthorn
0
 
LVL 18

Expert Comment

by:kandura
ID: 13718446
Thank you, Falthorn, for being such a wonderful student :)
If you ever have more questions, don't hesitate to ask them here!
0

Featured Post

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Recently I have been answering a lot of questions like this in IT forums that I frequent. The question posed is usually something along the lines of "We have software X installed and need to uninstall it for reason Y" or some other variant of the sa…
Active Directory replication delay is the cause to many problems.  Here is a super easy script to force Active Directory replication to all sites with by using an elevated PowerShell command prompt, and a tool to verify your changes.
The viewer will learn the basics of jQuery, including how to invoke it on a web page. Reference your jQuery libraries: (CODE) Include your new external js/jQuery file: (CODE) Write your first lines of code to setup your site for jQuery.: (CODE)
In this fifth video of the Xpdf series, we discuss and demonstrate the PDFdetach utility, which is able to list and, more importantly, extract attachments that are embedded in PDF files. It does this via a command line interface, making it suitable …
Suggested Courses
Course of the Month19 days, 7 hours left to enroll

873 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question