Our first module, part 8: displaying a history of bids

In this part we will add a new tag to display a list of bids placed for an auction. We’ll call it {exp:auction:history} so we need to add a new function in mod.auction.php called history.

It was useful for the summary tag to be able to run within a channel entries tag as this would be how we would often want to use it to (ie, to display lists of auctions).

However, it is good practise in ExpressionEngine not to nest tags inside others if possible.

Nested tags can lead to you inadvertently using a large number of database queries. A tag that is included inside another tag that loops over a number of iterations will be run multiple times. Depending on the complexity of the internal tag this could be a very bad thing.

From a template processing point of view it can also lead to variable scope clashes if both tags use the same variable. EE automatically generates some variables if you use the $this->EE->TMPL->parse_variables function, such as {count}, {total_results} and {switch} and if you nest tags then it is impossible to control which version of the variable will be used (it will always be the value from the outer tag).

So where possible it is best to design tags that can be used on their own. With this in mind the history tag will be designed to used outside the channel entries tag.

<h4>History</h4>
<
table>
    <
thead>
        <
tr>
            <
th>Bidder</th>
            <
th>Amount</th>
            <
th>When</th>
        </
tr>
    </
thead>
    <
tbody>
    
{exp:auction:history url_title="{segment_3}"}
        {if no_results}
<tr><td colspan="3">No bids yet</td></tr>{/if}
        
<tr {if count==1}class="highlighted"{/if}>
            <
td>{screen_name}</td>
            <
td>&pound;{bid_amount}</td>
            <
td>{bid_date format="%D, %M %j, %Y"}</td>
        </
tr>
    
{/exp:auction:history}
    
</tbody>
</
table

The history tag will be used on single entry pages, that is, pages where only a single auction is displayed rather than a list of them. These pages will be identified by having either the auction entry’s entry id or url title in the page’s URL.

We can then use the segment variables to identify which auction’s history to display. To keep the tag flexible we’ll make it optional whether we use entry id or url title.

To get the auction’s entry_id from the entry’s url_title, we’ll need to join the exp_channel_titles table.

We also want to include some details about the bidder, so we need to join the members table to fetch the user’s screen name.

Joining tables

The $query->result_array() function returns an array of results which is exactly how $this->EE->TMPL->parse_variables likes it, so it easy to produce the final results of.

This leaves us with the final history function of:

public function history() {
    
    
// Find the url title of the auction to add the form for
    
$url_title $this->EE->TMPL->fetch_param('url_title');

    
// Find the url title of the auction to add the form for
    
$entry_id $this->EE->TMPL->fetch_param('entry_id');

    
// Check that one of them is used
    
if( $entry_id === FALSE && $url_title === FALSE {
        
return "";
    
}

    
// Find list of bids for this auction
    
$this->EE->db->select"exp_auction.bid_amount, 
        exp_auction.bid_date, 
        exp_members.member_id, 
        exp_members.screen_name, 
        exp_members.username, 
        exp_members.email" 
);
    
$this->EE->db->from"auction" );        
    
$this->EE->db->join"exp_members"
        
"exp_members.member_id = auction.member_id" );
    if( 
$entry_id !== FALSE {
        
// we have the entry_id, so we don't need exp_channel_titles table
        
$this->EE->db->where"auction.entry_id"$entry_id );
    
else {
        
// we have the url_title, so we need exp_channel_titles table
        // to find the entry_id
        
$this->EE->db->join"exp_channel_titles"
            
"exp_channel_titles.entry_id = auction.entry_id" );
        
$this->EE->db->where"exp_channel_titles.url_title"$url_title );            
    
}
    $this
->EE->db->order_by"bid_date desc" );        
    
$query $this->EE->db->get();
    
    
// If no results are found...
    
if( $query->num_rows() == {
        
// ... return the {if no_results} ... {/if} conditional
        
return $this->EE->TMPL->no_results;
    
}
    
    
// Put history data in an array and return the parsed the tag data
    
$data $query->result_array();
    
$tagdata $this->EE->TMPL->tagdata;
    return 
$this->EE->TMPL->parse_variables$tagdata$data );

 

Comments (14)

Our first module, part 7: finishing off our summary tag

Difficulty: Medium

Now we are adding the users’ bids to the database, let’s go back and finish our {exp:auction:summary} tag.

The purpose of this tag was to display the auction’s current bid price and the total number of bids.

So far we had got:

public function summary() {
    
    
// Find the entry_id of the auction to display
    
$entry_id $this->EE->TMPL->fetch_param('entry_id');
    if( 
$entry_id === FALSE {
        
return "";
    
}
    
    $tagdata 
$this->EE->TMPL->tagdata;
    
    
// Build array of our variables
    
$data = array(
        
"current_bid" => "0.00",
        
"total_bids" => 0
    
);
    
    
// Construct $variables array for use in parse_variables method
    
$variables = array();
    
$variables[] $data;

    return 
$this->EE->TMPL->parse_variables$tagdata$variables );

We were hard-coding the current_bid and total_bids values in the $data array as (at that stage) the auction database was empty.

All we need to do is to replace the ‘dummy’ values in the $data array with the correct values from the auction database table:

public function summary() {
    
    $tagdata 
$this->EE->TMPL->tagdata;
    
    
// Fetch entry id from tag parameters
    
$entry_id $this->EE->TMPL->fetch_param('entry_id');
    if( 
$entry_id === FALSE {
        
return "";
    
}
    
    
// Fetch data from database
    
$this->EE->db->select"MAX(bid_amount) as current_bid, 
        COUNT(*) as total_bids" 
);
    
$this->EE->db->where"entry_id"$entry_id );
    
$this->EE->db->group_by"entry_id" );
    
$query $this->EE->db->get"auction" );

    if( 
$query->num_rows() == {
        
// No bids exist in the table, so we'll return zeros
        
$data = array(
            
"current_bid" => "0.00",
            
"total_bids" => 0
        
);
    
else {
        
// Fetch the first (and only) result from the SQL query
        
$data $query->row_array();            
    
}

    
// Construct $variables array for use in parse_variables method
    
$variables = array();
    
$variables[] $data;

    return 
$this->EE->TMPL->parse_variables$tagdata$variables );

Here we’ve used the Active Record class to build the SQL to fetch the data:

SELECT 
    MAX
(bid_amount) as current_bid
    
COUNT(*) as total_bids
FROM exp_auction
WHERE entry_id 
= ?
GROUP BY entry_id 

One thing to note is that the database functions are intelligent enough to add (or replace) the database prefix that you configured when you installed EE. For example, my auction database table is actually called exp_auction. I could use $this->EE->db->get( "auction" ) or $this->EE->db->get( "exp_auction" ) to access it. Equally, if I used a different prefix (you can choose to do this when you install EE), the database functions will use that value. Even if you use $this->EE->db->get( "exp_auction" ) and you have chosen to use a different prefix, EE will use the appropriate table name.

We then test to see if any rows have been returned. If not, either the auction’s entry id does not exist or no bids have been placed yet. In either case, we’ll display the price and number of bids as zero.

If the SQL query does return a row, we’ll use that. The $query->row_array() will return the next (in this case, first) row’s result as an array, with columns mapped to array elements, eg:

Array
(
    
[current_bid] => 18.00
    [total_bids] 
=> 4

Summary

So we now have the {exp:auction:summary} tag working. We are now able to display an auction item with details of the current bidding situation, with a working form that allows visitors to place their own bid.

 

Comments (7)

Our first module, part 6: processing the form results

We’ve built the form to allow users to place a bid for an auction and set up an action method in our module to send the submitted data to. Let’s read that submitted data and add it to our database of bids.

In the previous article, we set up the place_bid() function to receive the form submission. To check place_bids() is actually getting the form data, we’ll add a line to display any POST-ed data:

public function place_bid() {
    print_r
$_POST );
    exit;

If we submit a bid for one our auctions we should now see:

Array
(
    
[XID] => 781a30d69d42b2e054bae0fe4a29db4767757caa
    [entry_id] 
=> 1390
    [ACT] 
=> 25032029
    [site_id] 
=> 1
    [bid_amount] 
=> 10.00
    [submit] 
=> Place bid

We can see that the inputs entry_id and bid_amount that we defined in the form are there, along with a few others. The ACT value denotes the id of the action that we set up previously (ie, the place_bids function) and ExpressionEngine has added the XID and site_id values. The site_id is used to determine which site is being used when Multiple Site Manager is installed (by default it uses the value ‘1’). We’ll explain the XID value in another part.

Fetching the data

While we can use the $_POST array to get the submitted values, EE provides a better alternative using the Input class.

The class is loaded by EE and automatically cleans up any input data (eg, POST/GET/COOKIE) provided. It also provides a handful of helper functions.

The $this->EE->input->post() function is a function to access data POSTed to the system by a form, eg:

$entry_id $this->EE->input->post("entry_id"TRUE); 

will look for entry_id element of the $_POST array and return it, or FALSE it is does not exist. The optional second parameter (TRUE) tells the function to also apply the built-in xss_clean() function to the data to remove any potential cross-site scripting (XSS) attempts.

Adding the bid to the database

If we recall in part 3 we created a database to store the details of all the bids. The database had 5 fields:

We have the entry_id and bid_amount from the form, and the id field will be automatically generated as a primary key for the row, which leaves just member_id and bid_date to find.

The form to place a bid was only available to registered and logged-in users, so we can be confident that the bid was placed by a valid member. The Session class stores information about the current member and the userdata function can be used to retrieve the visitor’s member_id:

$member_id $this->EE->session->userdata('member_id'); 

The Localization class allows you to fetch the current time:

$bid_date $this->EE->localize->now

CodeIgniter’s Active Record Database class makes inserting this bid as simple as adding the data to an array and passing it (with the table name) to the insert function:

$data = array(
    
"entry_id" => $entry_id,
    
"member_id" => $member_id,
    
"bid_amount" => $bid_amount,
    
"bid_date" => $bid_date
);
$this->EE->db->insert('auction'$data); 

(Note: we’d normally want to do some validation of the data before adding it to the database - we’ll come back to that in a future article).

What next?

After submitting the form, it’s likely we’d want to display a success message and return the user back to the site.

For the time being, we’ll keep this simple, again using some of EE’s built-in functions:

$ret $this->EE->functions->fetch_site_index();

$data = array(
    
'title' => 'Thanks for your bid',
    
'heading' => 'Thanks for your bid',
    
'content' => "Your bid has been successfully placed",
    
'link' => array($ret"Back to site")
);
$this->EE->output->show_message($data); 

The Function class’s fetch_site_index() method will return the URL of the site’s homepage. This is where we’ll send users (for now) once the have placed their bid.

The Output class has a show_message() function that will display a ‘standard’ EE message box.

ExpressionEngine output message

It takes 4 parameters:

Summary

There’s still some work to do on the form processing before we can be happy with it (eg, validation), but we now have a form that accepts the visitor’s bid and stores it in the database.

 

Permalink

Our first module, part 5: adding a form

Difficulty: Medium

Next we need to allow users to place their bids for an auction item. First, let’s remember how our module has been specified to work:

In our module class (mod.auction.php) we need to add a tag to create the form to handle the bid: {exp:auction:form}. In our mod.auction.php file we need to a form() method (remember, the module’s function name matches the tag’s third segment).

public function form() {
    

ExpressionEngine forms (for example, the comment form) assume nothing about how the form is laid out. All we need to do is add the form’s opening and closing tags and assume that the developer using the form includes all the necessary form elements (with the correct names)

{exp:auction:form}
<p>
    &
pound; <input type="text" name="bid_amount" value="0.00" size="5" />
    <
input type="submit" name="submit" value="Place bid" />
</
p>
{/exp:auction:form} 

Making sure we have all the data

Let’s make sure we have all the data we need to save the bid. When we designed the exp_auction database table earlier we decided we’d need:

The bid amount is an element of the form, so will be passed when the form is submitted.

The current user and current time we’ll set when the bid is actually submitted (rather than when the form is created) so we’ll find these later.

The only data we still need to add to the form is the item’s entry_id. There are 2 ways we could do this:

  1. we could nest the {exp:auction:form} tag within the {exp:channel:entries} tag and supply it as a parameter to form tag (like we did with the {exp:auction:summary} tag)
  2. we could get the {exp:auction:form} tag to work out which entry it should use from the current URL. This will only work if the form is used on a single-entry page, but this is often the case and is a useful technique to use.

For simplicity’s sake we’ll use the first option and pass it as a parameter. We’ll look at the second option later.

As with the summary tag we can now use $entry_id = $this->EE->TMPL->fetch_param(‘entry_id’); to fetch the entry id.

So we have all the data we require for now, so let’s build the form.

Building the form

To add the opening <form> tag we use the function: $this->EE->functions->form_declaration(). Using the provided function has a few advantages that we’ll see later.

This is how our form method in mod.auction.php will now look:

public function form() {
    
    
// Find the entry_id of the auction to add the form for
    
$entry_id $this->EE->TMPL->fetch_param('entry_id');
    if( 
$entry_id === FALSE {
        
return "";
    
}
    
    
// Build an array to hold the form's hidden fields
    
$hidden_fields = array(
        
"entry_id" => $entry_id
    
);
    
    
// Build an array with the form data
    
$form_data = array(
        
"id" => $this->EE->TMPL->form_id,
        
"class" => $this->EE->TMPL->form_class,
        
"hidden_fields" => $hidden_fields
    
);

    
// Fetch contents of the tag pair, ie, the form contents
    
$tagdata $this->EE->TMPL->tagdata;

    
$form $this->EE->functions->form_declaration($form_data) . 
        
$tagdata "</form>";

    return 
$form;

We pass the $form_data array to the form_declaration() function to construct the <form> tag. The $form_data array itself contains an array of hidden form variables (as if we’d used ` tags) which stores the value of the entry_id that we fetched from the parameter.

If we look at the HTML that this generates, we’ll see:

<form method="post" action="http://site.com/index.php">
    <
div class='hiddenFields'>
        <
input type="hidden" name="XID" value="564dfaeed1f70b0ae983d3f..." />
        <
input type="hidden" name="entry_id" value="5" />
        <
input type="hidden" name="site_id" value="1" />
    </
div>
    <
p>
        &
pound; <input type="text" name="bid_amount" value="0.00" size="5" />
        <
input type="submit" name="submit" value="Place bid" />
    </
p>
</
form

The form_declaration() function has added another 2 hidden fields: XID and site_id

The site_id refers to site if you are using the Multi Site Manager

The XID variable is used to prevent spam and protect against Cross Site Request Forgery (CSRF) - we’ll look at this in more detail later.

The other thing to note is that the form’s action parameter has been set to the site’s index.php page. Obviously, at the moment, this page is not expecting to handle the auction’s form submission. We need to tell the form where to send its data by adding an action.

Actions

An action allows us to set up a URL that will call a specific module’s function. The URL will all have the format: http://site.com/index.php?ACT=id where id will tell EE which action to use.

Actions are stored in the exp_actions table which has a simple structure containing 3 fields: id, class, method.

When EE receives a request with the ACT= query string, it will look up the id in the exp_actions table and (if it exists) call the appropriate class method it finds.

We need to add an action for our module so let’s go back to our upd.auction.php file and modify the install method to add it when the module is installed.

public function install()
{
    $mod_data 
= array(
        
'module_name' => $this->module_name,
        
'module_version' => $this->version,
        
'has_cp_backend' => "y",
        
'has_publish_fields' => 'n'
    
);
    
$this->EE->db->insert('modules'$mod_data);
    
    
$this->EE->load->dbforge();
    
    
$fields = array(
        
'id' => array(
            
'type' => 'int',
            
'constraint' => '10',
            
'unsigned' => TRUE,
            
'auto_increment'=> TRUE
        
),
        
'entry_id' => array(
            
'type' => 'int',
            
'constraint' => '10',
            
'unsigned' => TRUE,
            
'null' => FALSE
        
),
        
'member_id' => array(
            
'type' => 'int',
            
'constraint' => '10',
            
'unsigned' => TRUE,
            
'null' => FALSE
        
),
        
'bid_amount' => array(
            
'type' => 'decimal',
            
'constraint' => '7,2',
            
'default' => '0.00',
            
'null' => FALSE
        
),
        
'bid_date' => array(
            
'type' => 'int',
            
'constraint' => '10',
            
'unsigned' => TRUE,
            
'default' => '0',
            
'null' => FALSE
        
)
    );
    
$this->EE->dbforge->add_field($fields);
    
$this->EE->dbforge->add_key('id'TRUE);
    
$this->EE->dbforge->create_table('auction');
    
    
$data = array(
        
'class'     => 'Auction',
        
'method'     => 'place_bid'
    
);
    
$this->EE->db->insert('actions'$data);
    
    return 
TRUE;

When we’ve added this, remove and install the module again to create the action. This will remove any existing data (if we had any) which would obviously be a bad thing to do in a live environment, but as we are still in development it is not a problem yet. We’ll look at how to safely make updates in a later article.

This should have added a line to the exp_actions table.

This new action points to the Auction class’s method place_bid so we need to this function to the mod.auction.php file:

public function place_bid() {
    

Finally, to tell the form to send its data to this action we need to update the form() method and we do this by adding another hidden variable:

public function form() {
    
    
// Find the entry_id of the auction to add the form for
    
$entry_id $this->EE->TMPL->fetch_param('entry_id');
    if( 
$entry_id === FALSE {
        
return "";
    
}
    
    
// Build an array to hold the form's hidden fields
    
$hidden_fields = array(
        
"entry_id" => $entry_id,
        
"ACT" => $this->EE->functions->fetch_action_id'Auction''place_bid' )
    );
    
    
// Build an array with the form data
    
$form_data = array(
        
"id" => $this->EE->TMPL->form_id,
        
"class" => $this->EE->TMPL->form_class,
        
"hidden_fields" => $hidden_fields
    
);

    
// Fetch contents of the tag pair, ie, the form contents
    
$tagdata $this->EE->TMPL->tagdata;

    
$form $this->EE->functions->form_declaration($form_data) . 
        
$tagdata "</form>";

    return 
$form;

The $this->EE->functions->fetch_action_id() provides an easy way to fetch the action id given the class and method name (the action id will change if the module is re-installed, whereas the class and method name won’t).

Summary

To recap, we’ve created a template tag to build a form. We then added an action and told the form to post its results to it. On receiving the form the action then tells the system which class and method should handle the response.

In the next article we’ll add the data from the form to the auction database table.

Comments (8)

Resources

Here are some useful ExpressionEngine resources:

Permalink

 1 2 3 >  Last ›