27 min read

After a quick look at the file and folder structure of a module, we then begin creating a new module for PHP-Nuke. This module will allow items of content to be submitted for modules that do not support user-submitted content. In this article, we will code functionality for users to submit encyclopedia entries for administrator approval. However, this will not involve making any modifications to the Encyclopedia module, and can be extended to cover other modules as well.

What Happens When a Page is Requested?

Let’s see what happens when a visitor wants to view an article on the site, and the process that PHP-Nuke goes through to construct the page containing this information. Viewing an article means that the visitor will be navigating to a page similar to this:

http://localhost/nuke/modules.php?name=News&file=article&sid=1

The page requested by the visitor is modules.php, so let’s have a look at that. Note that although there are many PHP files in the PHP-Nuke installation, there are only four files actually requested in the normal course of interaction with the site:

  • index.php
  • modules.php
  • admin.php
  • backend.php

The other files in the PHP-Nuke installation are used by these files when required.

Where Does PHP-Nuke Get Information From?

PHP-Nuke is able to collect information about what module the visitor wants to see, what operation they want to perform, and details of the user from request variables coming from these places:

  • The query string in the URL: The URL of the page holds information that tells PHP-Nuke which module to select, which part of the module to use, what item of content to show, and so on. The query string information is used to select the page from the system that needs to be shown to the visitor.
  • Posted variables: When a user enters information in a form, and submits this back to the server, this information will be available to PHP-Nuke. This posted information is how PHP-Nuke gets input from the user to create items of content, enter terms to search for, and so on.
  • Cookie variables: There is user account information stored in a cookie (and administrator account information if the user has such an account). This is used to identify the user, so they do not have to keep logging on every time they view a page or come to the site. When the user logs out, this information is deleted from the cookie.

The information that PHP-Nuke gets from these sources has to be treated very carefully within the system. These sources are the only means through which visitors communicate with the site, and are also the channels through which hacks or attacks might be conducted on the site. The patches we applied in Article 2 while installing the system address precisely this issue, and they make sure that the data PHP-Nuke collects from a visitor is in exactly the right form for working with.

Requesting a Page

Once the modules.php page is requested, the first step followed is to include the mainfile.php file. This file does the following things:

  • It checks and processes the request variables (namely the input to the application), to avoid possibly harmful tags, or other indicators of some form of SQL injection attack.
  • It creates global variables for each request variable.
  • It sets up a connection to the database.
  • It gets the site configuration such as the site name, site logo, and so on, from the database.

The mainfile.php file also contains a number of core functions such as checking if the user is logged in or is an administrator, choosing the blocks to display, and filtering text, among others. These will be used at different points in the creation of the page.

After this file has been included, the next thing to happen in modules.php is that PHP-Nuke gets the requested module from the $name global variable, which corresponds to the name query string variable (as in modules.php?name=News), and checks if this module is active. If the module isn’t active, and the visitor isn’t an administrator, a ‘Module Not Active’ message is displayed, and the page output is done.

If the module is active, then PHP-Nuke checks if the visitor has rights to access this module. PHP-Nuke checks to see if the access is restricted to a particular user group, and if so, is the user a member of that group? PHP-Nuke also checks if the module is for subscribers only, and if so, is the user a subscriber to the site? If the visitor doesn’t have the right permissions to view the module, then a ‘No Access’ message is displayed, and the page output is done.

If the module selected by the visitor is active, and they do have permission to view it, then PHP-Nuke can get on with passing control to that module. Control is passed to the selected module by attempting to include the index.php file in the folder of the selected module. However, if there is a file variable in the query string, then the file with that name is included instead. If these files can’t be found, a ‘Module Cannot Be Found’ error is displayed to the visitor.

Thus if the user requests a page like modules.php?name=News&file=article&sid=1, the article.php file in the News folder will be included by PHP-Nuke. If the user requests a page like modules.php?name=News&sid=1, then the index.php file in the News folder will be included. Attempting to request a page like modules.php?name=News&file=nosuchfile returns a page with a ‘No such page’ message, since there is no file called nosuchfile.php in the News folder. The ‘No such page’ message is generated by PHP-Nuke, since it’s in control of the process.

If the user has selected an active module for which they have view permission, and are requesting a page that is part of the module, then control passes to the module, and it’s up to that module to do its work and create the page. We’ll see how this works later in the article, but for now, our overview of how PHP-Nuke gets the page creation underway is complete.

Seeing how PHP-Nuke works isn’t particularly exciting, what is more exciting is seeing how we can extend the power of PHP-Nuke by creating new blocks and modules. Along the way, we’ll see most of the components required for ‘programming’ with PHP-Nuke, and you’ll get a good idea of how to go about your own development projects.

Creating a Block

Our development efforts begin with creating a File block. A File block is a PHP script that is stored in the blocks folder. It must have a filename of the form block-NAME.php, where NAME will be used by PHP-Nuke as the title for the block. The filename should not contain any spaces.

The goal of a block is simple. It just has to create one variable, $content, that holds the content of the block. After that, the PHP-Nuke core will bring the theme into play to take care of displaying the block.

The block we will create is a better version of the Dinosaur of the Day static HTML block we created in Article 4. The block will display the name of the Dinosaur of the Day, and a thumbnail image of the lucky lizard. However, on the next day, a different dinosaur will be chosen, and the block display will be updated.

This is how the block works:

  • We will create a database table to hold the date, the title of the dinosaur for that day, and a link to the thumbnail image of that dinosaur.
  • We will create a text data file that will contain the name of a dinosaur and a link to its thumbnail image on each line. The data in this file will be the dinosaur pool from which the dinosaur of the day is chosen at random.
  • When the block code is executed, it will look in the database table to see if there is any information for the current date. If there is, it will retrieve it and build the block output.
  • If there is no information for the current date, the data from the text file will be loaded in. One of the entries in that file will be selected at random, and that data will be inserted into the database. This will become the Dinosaur of the Day. That data will then be used to create the block output.

We will use the text file to hold the ‘Dinosaur of the Day’ candidates rather than a database table so that we do not have to create a separate administration feature to add these details. To add more dinosaurs to the list, we simply upload a new version of the text file.

Make sure that you copy the dinosaur thumbnails from the code download into the imagesdinosaurstnails folder of your PHP-Nuke installation root.

Time For Action—Creating the Database Table

  1. Open up phpMyAdmin in your web browser, and select the nuke database from the drop-down list in the left-hand panel.
  2. Click on the SQL tab, and enter the following code into the textbox, then click on Go.
CREATE TABLE dinop_dinoportal_dotd (
id INT( 10 ) NOT NULL AUTO_INCREMENT ,
day VARCHAR( 16 ) NOT NULL ,
title VARCHAR( 128 ) NOT NULL ,
image VARCHAR( 250 ) NOT NULL ,
PRIMARY KEY ( 'id' )
) TYPE = MYISAM ;

What Just Happened?

We just created our database table. There is only one table needed, with a simple design. There are four fields in the table. The id field will hold an auto-incrementing unique numerical value and the other fields will hold the current date, the title of the dinosaur, and the link to the image of the dinosaur.

Time For Action—Creating the Text File

  1. Open up your text editor, and enter the following:
    Tyrannosaurus Rex,images/dinosaurs/tnails/tyro.gif
    Stegosaurus,images/dinosaurs/tnails/stego.gif
    Triceratops,images/dinosaurs/tnails/triceratops.gif
  2. Save this file as dotd_list.txt in the blocks folder.

What Just Happened?

The dotd_list.txt file will be the data source for choosing a new Dinosaur of the Day image. You will notice that we are storing the data here in the form ‘name of the dinosaur’, ‘path to the image’, so it will be easy to extract the information when we need it.

Time For Action—Creating the Block Code

  1. Open up your text editor, and enter the following code into a blank file:
    <?php

    if ( !defined('BLOCK_FILE') )
    {
    Header("Location: ../index.php");
    die();
    }

    global $prefix, $db;

    $today = date('d-m-Y');

    $sql = "SELECT * from ".$prefix."_dinoportal_dotd WHERE day='$today'";

    $result = $db->sql_query($sql);
    $content = "";
    $dino_title = "";
    $image = "";

    $numrows = $db->sql_numrows($result);
    if ($numrows)
    {
    $row = $db->sql_fetchrow($result);
    $dino_title = $row['title'];
    $image = $row['image'];
    }
    else
    {
    $filename = "blocks/dotd_list.txt";
    $possibles =@ file($filename);

    if ($possibles)
    {
    $choice = rand(1, count($possibles));
    $imp = explode("," , $possibles[$choice-1]);
    $dino_title = $imp[0];
    $image = $imp[1];
    $sql = "INSERT INTO ".$prefix."_dinoportal_dotd(day,title,image)
    VALUES ('$today', '$dino_title', '$image')";
    $result = $db->sql_query($sql);
    }
    $choice = rand(1, count($possibles));

    $imp = explode("," , $possibles[$choice-1]);
    $dino_title = $imp[0];
    $image = $imp[1];
    }
    if ($dino_title)
    {
    $content = "Today's dinosaur
    is:<br><center><b>$dino_title</b><center><br>";
    $content .= "<center><img src="$image"
    alt="$dino_title"></center><br>";
    }
    ?>
  2. Save this file as block-DinosaurOfTheDay.php in the blocks folder of your PHP-Nuke installation.

What Just Happened?

We just entered the code for the Dinosaur of the Day block, and we’ll step through it now.

This first part of the code stops this file being requested directly by a visitor— the BLOCK_FILE constant is defined in mainfile.php, and without that constant being defined, the visitor would be redirected back to the homepage of the site. Block files are never requested directly by the visitor, they are included by PHP-Nuke. These first few lines of code are found in every block file:

if ( !defined('BLOCK_FILE') )
{
Header("Location: ../index.php");
die();
}

Now we can get started. First, we set ourselves to access some of the global variables of the application, and we will have our first look at the objects to access data from the database. The only global variables we need here are the ones for data access—$prefix, which holds the value of the database tables prefix, and $db, which is used to actually perform database operations.

global $prefix, $db;

Next, we grab today’s date, formatted as digits like this 24-05-2005.

$today = date('d-m-Y');

Now we set up the SQL statement to retrieve the information corresponding to this date from the database:

$sql = "SELECT * from ".$prefix."_dinoportal_dotd WHERE day='$today'";

Now we execute the SQL statement:

$result = $db->sql_query($sql);

It is possible that there is no data corresponding to today’s date, so we check the number of rows returned from this last query. If there are zero rows, there will be no information.

$numrows = $db->sql_numrows($result);

If there are some rows returned, we can start creating the block output. We use the sql_fetchrow() method to retrieve a row of data from the result of the query. This row is returned as an array, and we set some variables from the array. We’ll only grab one row. If for some reason, there is more than one entry for today’s date, we simply ignore the others.

if ($numrows)
{
$row = $db->sql_fetchrow($result);
$dino_title = $row['title'];
$image = $row['image'];
}

Now we move on to the situation where there is no information for today’s date, and we have to create it. The first thing we do is to read the contents of the dotd_list.txt file into an array—there will be one entry in the array for each line in the text file. However, we have to consider what will happen if there is some kind of problem reading the file.

else
{
$filename = "blocks/dotd_list.txt";

Note that the path to the file for the dodt_list.txt file is blocksdotd_list.txt. This may seem strange, since both this file and the block file are in the same blocks folder. However, PHP will be looking for this file from the executing script, which will be one of index.php, modules.php, or admin.php, all of which are outside the blocks folder. Thus we need to add the blocks folder in the path to the dotd_list.txt file.

Now we try to grab the file itself:

$possibles =@ file($filename);

The file function opens the named file, and reads the input into an array called $possibles. The use of the @ character here will suppress any error messages—if there is a problem opening or reading the file, no untidy error messages will be displayed to the user, and execution can continue. Of course, if there is a problem reading the file then there will be a problem with $possibles. So we check this next—if there has been some problem reading the file then $possibles will be false:

if ($possibles)
{

If there is something stored in $possibles, then this check will be passed, and we can proceed to choose one element from it at random. We choose a random number, between 1 and the number of lines in the text file.

$choice = rand(1, count($possibles));

All we have to do now is choose that element from the array (we have to subtract one from the random number because the first element of the array is 0, rather than 1), and then split up that line to get at the title and the path to the image.

$imp = explode("," , $possibles[$choice-1]);
$dino_title = $imp[0];
$image = $imp[1];

We split the line using the explode() function. The explode() function converts a string to an array by splitting it at certain characters. We will split the string at the ‘,’ character, and we get an array with two entries. The first entry is the name of the dinosaur; the second is the path of the image.

Now we have the details of our Dinosaur of the Day, we can add it to the database using an INSERT SQL statement.

$sql = "INSERT INTO ".$prefix."_dinoportal_dotd(day,title,image)
VALUES ('$today', '$dino_title', '$image')";
$result = $db->sql_query($sql);
}
}

At this point, we should have a Dinosaur of the Day, one way or another, and so we can finalize the block output. However, we check the value of the $dino_title variable just in case there has been some problem either retrieving data or creating the new Dinosaur of the Day. If there has been a problem with either of these, there will be no value for the $dino_title variable, and if so, this code will ensure that the block content will remain empty, rather than producing some useless output.

if ($dino_title)
{
$content = "Today's dinosaur is:<br><center><b>$dino_title</b><center><br>";
$content .= "<center><img src="$image"
alt="$dino_title"></center><br>";
}

That’s it, our block is complete! The key points of this block were the initial few lines that stopped the file from being requested directly, and this was also our first encounter with the data access code. Another thing to note from this example is the effort we made to ensure that the block output was only created if everything went smoothly. We suppressed errors when trying to read in a file, we checked that the reading of a file had actually given us some data, and then we didn’t create any output if there was a problem with dino_title variable, which would be an indicator of some problem.

All this means that if there is a problem, the visitor will not be confronted with lots of error messages, which could disturb the visitor and lead to a poor impression of your site, or even break the layout of the page, or reveal some information about your code that could be used against you.

All that remains now is to set up this File block using the steps we saw in Article 4, and we are away!

Data Access in PHP-Nuke

In the code for creating the block we had a couple of lines with data access functions:

$result = $db->sql_query($sql);
$numrows = $db->sql_numrows($result);
$row = $db->sql_fetchrow($result);

PHP-Nuke uses a ‘data abstraction layer’, which means that you call functions against an object, which translates them into specific calls against the database of your choice. Generally, MySQL is the database used with PHP-Nuke, but you could choose another database server to power your site. A more pertinent advantage is that you don’t need to use database-specific functions to access data in PHP-Nuke; you only need to learn about the functions of the data access object (You will still need to know some SQL to create queries that will be executed on the database).

The code for the data access object is found in the file dbmysql.php. In fact, the db folder contains code for different types of database server, but this file is the one selected by default by PHP-Nuke for working with the MySQL database server.

The data access object is a genuine object, that is, it’s an instance of a class, sql_db in this case. Classes are one of the basics of object-oriented programming, but other than this instance PHP-Nuke does not make much use of object-oriented programming. A discussion of object-oriented programming in PHP is beyond the scope of this article series, and it won’t particularly help here since PHP-Nuke makes so little use of it. All that we need to know is how to access the methods (functions) of the data access object.

Object-oriented programming is covered in more detail in any book on PHP programming, and you can read an article about it at http://www.devarticles.com/c/a/PHP/Object-Oriented-Programming-in-PHP/.

The data-access object provides a number of methods that you can use to execute a query, retrieve a row of data, check the number of rows returned by the query, or get the last inserted auto-increment field. Working with the object follows a similar process to the standard way of working with data in PHP using functions like mysql_query() or mysql_fetch_field().

To access data in PHP-Nuke, you will need two global variables, $prefix and $db. The $prefix variable holds the table prefix of the database tables, and this needs to be used in your SQL queries. The $db variable is the data access object itself.

In our block example, we had these lines to create a SQL query and then execute it:

$sql = "SELECT * from ".$prefix."_dinoportal_dotd WHERE day='$today'";
$result = $db->sql_query($sql);

Note the $db->sql_query() syntax. This syntax is used in PHP to call a method on an object, in this case the sql_query() method of the $db object. The sql_query() method executes an SQL query as its name suggests. You provide a string with the query that’s to be executed as a parameter.

Following the execution of a query, you can retrieve a row using the sql_fetchrow() method:

$row = $db->sql_fetchrow($result);

This method returns an array, and you can refer to the fields in the data using $row[‘fieldname’], as we do in the block example to get the title and image fields:

$dino_title = $row['title'];
$image = $row['image'];

If you want to insert or update data, you need to create the relevant SQL query and then use the sql_query() function to do it:

$sql = "INSERT INTO ".$prefix."_dinoportal_dotd(day,title,image)
VALUES ('$today', '$dino_title', '$image')";
$result = $db->sql_query($sql);

This is only a small selection of the methods of the data access object. Another interesting one is the sql_nextid() method, which you can use after an INSERT statement to get the value of the last auto-increment field created. However, these are the methods that you will see the most of as you look around the code in PHP-Nuke.

Module File and Folder Structure

Before we get started creating a new module, let’s have a look at the file structure of a typical module. A module is simply a collection of files (usually only PHP files) contained in a folder that goes in the modules folder in the root of the PHP-Nuke installation. The name of the folder is the name that PHP-Nuke will recognize the module by.

However, we can’t just place the files into the module folder in any order. There is an organization of files, subfolder names, and filenames that modules need to follow in order to function properly with PHP-Nuke.

The image below shows the contents of the News module folder:

Programming PHP-Nuke

We have already seen how PHP-Nuke switches between files in the module folder based on the value of the file query string variable. If there is no value for this variable, the index.php file of the module is used. Files that sit inside the module folder are the ‘front-end’ files, which will be used during a standard user’s visit to the module.

The code for the administration part of a module resides in the admin folder within the module folder. In earlier versions of PHP-Nuke (before 7.5), the administration code for any module would have to go into the admin folder (the one in the root of the PHP-Nuke installation), and would be mixed with the ‘core’ administration code. The decision to have a module’s administration code contained within the module folder itself means that the module is much more self-contained, keeps people away from the core code itself, and generally makes the module easier to set up, install, and maintain. We’ll see more about what is found in the admin folder when we create the administration area of our new module later in this article.

We saw in Article 4 that the Language block allows you to change PHP-Nuke’s user interface language. This ability to switch languages is something that has made PHP-Nuke so popular all around the world. This multi-language support is achieved by module developers avoiding coding any ‘localizable’ text into the module output. Localizable text is text that needs to be translated if a different interface language is selected. Instead of coding the text, PHP constants are used, with the values of the constants defined in a language file. The language files are found in the language folder of the module, and there is a separate language file for each language, each with a filename of the form lang-LANGUAGE.php. All PHP-Nuke needs to do is select the correct file based on the desired language.

Creating a User Submissions Module

Writing a new module allows you to extend PHP-Nuke to get it to do exactly what you want it to do. What we will do here is to create a general-purpose module that will allow users to submit content for modules that do not support user-submitted material. We’ll call it UserSubmissions. It will work in the way the Submit News module works for stories:

  • The user will submit the material through a form.
  • The administrator will be notified of the new submission by email.
  • The administrator will be able to see a list of the submitted material in the administration area, and can edit, delete, or approve the material to go into the database.

The point of this module is that it does not touch the modules for which it allows the submission of content; everything will happen in the UserSubmissions module. In this article, we will only code in functionality for users to submit encyclopedia entries. It is straightforward to extend this to allow submissions for the Content module, or questions for the FAQ module.

Conceptually what the module does is to:

  • Present a form to the user similar to the one the administrator would use for entering an encyclopedia entry.
  • Take the user’s input and store it in a ‘general’ way in a single database table.
  • After the administrator checks the input, the data is then stored in the encyclopedia’s database tables using the same kind of code that the Encyclopedia module uses.

We will see exactly what the ‘general’ way we store the data in is later. Basically, the module will take all the parts of the encyclopedia entry—the title, the text, the encyclopedia ID—and put them all together into one bundle, which can then be easily retrieved and broken out to form the individual pieces of data for the administrator to view, approve, or delete.

Rather than presenting the development as a series of steps for you to follow, we will break the code up into various tasks, and then examine each piece of code or activity. You can type in the code as it is presented, although it is probably easiest to grab the example code for this module from the code download, and refer to it as we go.

Module Development Steps

The steps that we will follow to create the module are these:

  • Create the module folder
  • Create the database tables
  • Code the front end (visitor view) of the module
  • Adapt the code for multi-language support
  • Set up module administration
  • Code the administration area

Rather unusually, we’re going to start by coding the front end of the site. The reason this is unusual is that modules typically display some kind of data (that is what all the modules we have encountered in the article series do), and you would first need to enter this data into the database. This data is usually entered by the administrator, through the administration interface. With some example data in place, the front end of the site will be able to display it. It will be difficult to test the front end if it is supposed to display data and there is no data to display!

This module does not require any existing data in the database to work. In fact, the data is entered by a standard visitor, and the administrator only has to look at this data, and edit or delete it. There is no facility in the administrator part of the module for the administrator to add new data into the module, which would rather defeat the point of this module! Thus we can start on the front end of the module quite happily.

Let’s start off with creating the module folder.

Creating the Module Folder

Create a new folder in the modules folder called UserSubmissions. This will be our module folder. Within this folder, create two new folders called admin and language. The admin folder will contain our administration code, and the language folder will contain the user interface language files. We create another folder, inside the admin folder, also called language. This folder will hold the language files for the module’s administration interface.

That’s the first step out of the way, so let’s move on to the database tables.

Creating the Database Tables

The module has only one database table. The table will be called <prefix>_usersubmissions. You can follow the same steps in phpMyAdmin as we did earlier for creating the block database table to create this table:

CREATE TABLE dinop_usersubmissions (
id int(11) NOT NULL auto_increment,
data text NOT NULL,
parent_id int(11) NOT NULL default '0',
type varchar(36) NOT NULL default '1',
user_id int(11) NOT NULL default '0',
date timestamp NOT NULL default
CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
title varchar(255) NOT NULL default '',
user_name varchar(250) NOT NULL default '',
PRIMARY KEY (id)
) COMMENT='Table for holding user submitted content' ;

Each row in this table will represent a single item of submitted content. The row will be identified by its id field.

With only one table, you may be wondering how this module is going to be able to hold data from different modules. The answer is that the submitted data will be bundled up into a single object, then ‘serialized’ and stored in the data field. When the data is needed for viewing, it will be unbundled, and ‘unserialized’ back into a form that can be worked with. The ‘title’ of the submission will be stored in the title field.

The type of module that we are storing data for will be held in the type field of the row. The details of the submitter will be stored in the user_id and user_name fields. We actually only use the user_name field in this version of the module, but we store both for future use. The date the item is submitted is held in the field called date. This field is a MySQL TIMESTAMP, and whenever a row is inserted, the current date and time will be inserted into the field automatically by the database, so we will not need to record the date and time ourselves.

The final field is parent_id. Recall how an encyclopedia entry belongs to an Encyclopedia; a content page belongs to a Content category; a FAQ question belongs to a particular category, and so on. For each of these types of content, you needed to provide a ‘parent’ object that the content would belong to. That is where our parent_id field comes in. The ID of the parent object will be stored in this field. For an encyclopedia entry, this will be the ID of the chosen Encyclopedia.

LEAVE A REPLY

Please enter your comment!
Please enter your name here