Advanced Application Development with Nennius (part 2)


This article is part two of an introduction to application development for the Nennius engine. If you have not yet read Part 1, you should do so before continuing. We will be enhancing the News Manager application already discussed in the previous tutorial, so you will need a copy of the source code for that application if you want to work along.

Update Database

Before we can start, we will once again need to run a few SQL queries to populate the application's database with the appropriate information. These queries simply build on top of the database created in Part 1:
-- create table to hold news release file attachments (images in this case)
CREATE TABLE `news_attachments` (
	`filename` VARCHAR( 50 ) NOT NULL ,
	PRIMARY KEY ( `id` ) ,
	INDEX ( `news_id` )

-- create table to hold record history (& associate to users & news tables via foreign keys)
CREATE TABLE `news_history` (
	`datetime` DATETIME NOT NULL ,
	`description` TEXT NOT NULL ,
	PRIMARY KEY ( `id` ) ,
	INDEX ( `news_id` , `user_id` )

-- create comments table to hold user comments
CREATE TABLE `comments` (
	`datetime` DATETIME NOT NULL ,
	`name` VARCHAR( 50 ) NOT NULL ,
	`subject` VARCHAR( 100 ) NOT NULL ,
	`comment` TEXT NOT NULL ,
	PRIMARY KEY ( `id` ) ,
	INDEX ( `news_id` )

-- create notes table for moderators store private messages regarding user comments in
CREATE TABLE `comment_notes` (
	`id` int(4) unsigned zerofill NOT NULL auto_increment,
	`news_id` int(3) unsigned zerofill NOT NULL default '000',
	`user_id` int(2) unsigned zerofill NOT NULL default '00',
	`datetime` datetime NOT NULL default '0000-00-00 00:00:00',
	`description` text NOT NULL,
	PRIMARY KEY  (`id`),
	KEY `news_id` (`news_id`,`user_id`)

-- insert addition user record with threshold of USER for later use
INSERT INTO `users` ( `id` , `username` , `password` , `name` , `email` , `threshold_id` )
	VALUES ( '', 'user', 'user', '', '', '03' );
As you can see, we've created several additional SQL tables (which will be explained later in the article) as well as an additional user record: nothing too complex. Now we're ready to start modifying our application.

Advanced Application Configuration

Begin by opening the 'application.defaults.php' file that we created in Part 1. Currently this file contains only those configuration variables required by Nennius. Our first step in exploring the advanced configuration options available to Nennius developers will be to modify this file. The text below shows the additional variables we will be adding to our configuration. We will take a closer look at each variable later, but for now just add the following text to your 'application.defaults.php' file:
# Advanced configuration options - - - - - - - - - - - - - - - - - - - -
# relative location of (optional) application branding logo
$GLOBALS['nennius_branding_logo']	= 'graphics/news_manager.gif';

# designate whether to store files in DB (BLOB) or on file system
$GLOBALS['attachment_storage_mode']	= 'FILE_SYSTEM';	# ex. 'DB_BLOB', 'FILE_SYSTEM'
# default page user is directed to upon successful login
$GLOBALS['nennius_default_page']	= 'index.php';

# log all failed DB queries (for debugging purposes only)
$GLOBALS['nennius_debug_mode'] 		= TRUE;
$GLOBALS['nennius_debug_file'] 		= 'logs/debug.txt';

# log all failed user login attempts (for security purposes)
$GLOBALS['nennius_logging_mode'] 	= TRUE;
$GLOBALS['nennius_logging_file'] 	= 'logs/logging.txt';
As you can see above, most of the variables we have added to our configuration file are self-explanatory, but let's take a short look at each just to avoid any possible confusion.

Our first variable, 'nennius_branding_logo', specifies the relative path to the web application's branding image. Branding images should generally be no more than 200 pixels wide and 40 pixels tall. For our example application, we have developed a simple branding image that displays the name of our web application in graphical format. (Note: if no branding image is provided, the default Nennius logo will be used.)

Next we told Nennius to store file attachments on the file system instead of in our database; we could have just as easily chosen to store files in the database (using 'DB_BLOB'). Depending on the application, it will often make more sense to use one method of storage over the other. If neither method is specified, Nennius will default to storing files on the file system within a '/webapps/application_name/files/' folder.

The 'nennius_default_page' is fairly straight forward. If our application contains more than one component, we are able to specify the default component for users who are logging in. Upon a successful login, Nennius will re-direct the user to the component page specified here.

Our last two variables deal with logging information. Nennius offers a variety of logging options; we have chosen the two most common for our application: 'nennius_debug_mode' & 'nennius_logging_mode'. By enabling debug mode and specifying a log file ('nennius_debug_file') we are telling Nennius that anytime a SQL query fails it should write that query (and the error message returned) to a log file. This is a very handy feature for web applications, especially during the initial development stages. Logging mode is similar; it records failed login attempts. It is recommended that logging mode be enabled for any Nennius application managing sensitive data.

Note: When creating the 'application.defaults.php' file, make certain that there is no trailing space before or after the PHP brackets. This is a fairly common oversight to make, but it will prevent the authentication class from setting a user's login cookie.

Expanding Our Application Menu

In Part 1 of this article we created a simple News management application. That application contained only two menu options: one to direct an authenticated user to a form for managing news releases, and another to direct all users to our imaginary company's news release page. That was a great introduction to Nennius development, but not very practical. More often than not, when it comes to data management tools, a wide variety of data is being managed. Because of this, most Nennius applications will deal with more than one component. To add additional components to our example application, we will first start by expanding our menu configuration file as follows:
# include config_defaults file for threshold values
# NOTE: ignore a failed include for the purposes of the admin GUI
@ include_once '../../config.defaults.php';

# intiialize nennius menu array
$nennius_menu_array	= NULL;

# Manage Releases - links to Nennius page for adding & removing news releases
$admin_options_array[] 	= ( array(	'menu_display_name'	=> 'Manage Releases',
					'menu_hyperlink'	=> 'index.php',
					'menu_threshold'	=> $GLOBALS['ADMIN'] ) );

# Manage Comments - links to Nennius page for adding & removing user comments 
$admin_options_array[] 	= ( array(	'menu_display_name'	=> 'Manage User Comments',
					'menu_hyperlink'	=> 'comments.php',
					'menu_threshold'	=> $GLOBALS['USER'] ) );

# Corporate Website - links to external corporate website to view changes in realtime
$public_menu_array[]	= ( array(	'menu_display_name'	=> 'Corporate Website',
					'menu_hyperlink'	=> '',
					'menu_threshold'	=> $GLOBALS['PUBLIC'] ) );
# add all menus to nennius menu array
$nennius_menu_array['Admin Options']	= $admin_options_array;
$nennius_menu_array['Public Menu']	= $public_menu_array;

# globalize nennius menu array for access by Display Template
$GLOBALS['nennius_menu_array']		= $nennius_menu_array;
The menu configuration file now specifies two menu categories: 'Admin Options' (shown only if a user is logged into the application) and 'Public Menu'. In addition we have also added a new menu link: 'Manage User Comments'. (For the purposes of this article, we will assume that our application's front end allows users to attach comments to a news entry. This new menu option will then allow us to moderate the comments if necessary.) It will also show us another important aspect of Nennius development: how to create meaningful component relationships.

Creating the User Comments Component

Now that we have finished configuring the application, let's create our basic Comments component. Just like last time, we'll start by creating the entry-point file. Name that file 'comments.php' and place it within the '/nennius/webapps/news/' directory. Then copy the following content into it:
# define location of component & descriptor files
$component_file		= 'components/comments.php';
$descriptor_file	= 'descriptors/comments.php';

# include parent nennius_component class
include_once "../../nennius.component.php";
include_once $component_file;

# create a new object to kick things off
new comments( $descriptor_file );
Next let's move on to the Comments descriptor file ('/descriptor/comments.php'):
# set	display name for component
$GLOBALS['g_main_display_name']		= 'User Comments';

# set primary db table name
$GLOBALS['g_db_primary_table']		= 'comments';

# set primary key for main db table
$GLOBALS['g_db_primary_table_key']	= 'id';

# set (optional) primary display field for db table
$GLOBALS['g_db_primary_table_name']	= 'subject';

# set required min. threshold for overall access
$GLOBALS['g_threshold_overall']		= $GLOBALS['USER'];
So far things should look pretty familiar, which is good. At their most basic level, all Nennius components will look very similar. The components file ('/components/comments.php') is no exception. Let's create it next:
# basic comments component
class comments extends nennius_component {

	# store class-wide reference to control object
	# NOTE:	this object is created by the nennius_component class
	var $nennius_control				= NULL;

	# sets up necessary internal vars, creates new control object 
	# and passes self-reference to it
	function comments(	$p_component_descriptor_file	) {
		# call out to constructor of parent class with descriptor file
		$this->nennius_component(	$p_component_descriptor_file );
	} # END comments
	# retrieves and returns assoc. array containing db column info
	function get_info_db_array() {	
		$f_db_info_array	= NULL;
		# define primary key attribute for comments table
		$f_db_info_array['id'] = (
			array (
				'db_field_name' 	=> 'id',
				'db_field_type' 	=> 'int',
				'db_field_length' 	=> 3,
				'db_primary_key'	=> TRUE,
				# field is hidden from visibility at all times
				'hidden_all' 		=> TRUE
		# news id - links comment entries to parent news release entry
		$f_db_info_array['news_id'] = (
			array (
				'db_field_name' 	=> 'news_id',
				'db_field_display' 	=> 'News Release',
				'db_field_type' 	=> 'int',
				'db_field_length' 	=> 36,
				'db_field_required'	=> TRUE,

				# foreign key DB information
				'db_foreign_key'	=> 'id',
				'db_foreign_display'	=> 'title',
				'db_foreign_table'	=> 'news'
		# date & time when comment was posted
		$f_db_info_array['datetime'] = (
			array (
				'db_field_name'		=> 'datetime',
				'db_field_display'	=> 'Date / Time',
				'db_field_type'		=> 'datetime',
				'db_field_length' 	=> 19,
				'db_field_required'	=> TRUE,
				# set default value to be current date & time
				'update_default'	=> date( 'Y-m-d H:i:s' ),
				# set attribute to be searchable by min & max date range
				'search_date_range'	=> TRUE
		# commentor's name
		$f_db_info_array['name'] = (
			array (
				'db_field_name' 	=> 'name',
				'db_field_display' 	=> 'Name',
				'db_field_type' 	=> 'char',
				'db_field_length' 	=> 50,
				# set field to be searchable using a partial-match criteria
				'input_search_type'	=> 'TEXT',
				'search_partial'	=> TRUE,
		# comment's subject
		$f_db_info_array['subject'] = (
			array (
				'db_field_name' 	=> 'subject',
				'db_field_display' 	=> 'Subject',
				'db_field_type' 	=> 'char',
				'db_field_length' 	=> 100,
				# set field to be searchable using a partial-match criteria
				'input_search_type'	=> 'TEXT',
				'search_partial'	=> TRUE,
		# comment body / text
		$f_db_info_array['comment'] = (
			array (	
				'db_field_name' 	=> 'comment',
				'db_field_display'	=> 'Comment',
				'db_field_type' 	=> 'char',
				# define size of textarea input
				'input_update_type'	=> 'TEXTAREA',
				'input_update_rows'	=> 10,

				# hide field from search & view modes
				'hidden_search'		=> TRUE,
				'hidden_view'		=> TRUE
		# after all attributes have been setup, return array
		return $f_db_info_array;
	} # END get_info_db_array()
	# retrieves and returns assoc. array containing display headers
	function get_info_header_array() {	
		$f_header_info_array	= NULL;
		# append default header messages
		$f_header_info_array['news_id'] = 'News Information';
		$f_header_info_array['name']	= 'User Information';
		$f_header_info_array['subject']	= 'Comment';
		# return header array
		return $f_header_info_array;
	} # END get_info_header_array()

} # END comments class
Things should still look somewhat familiar. As you can see we've simply described the SQL table that stores our User Comments information, and set some basic configuration options about each of the component's attributes (aka SQL columns). However, a very important attribute was introduced with the 'news_id' column: the foreign key. As previously stated, most data management systems will not only need to manage various data components but also control those components in relation to one another. Nennius allows for several methods of interaction between components, one of which is the foreign key attribute we have just identified. By specifying a foreign table, key, and display column we are providing Nennius with all of the information it needs to transparently link the Comments component to the News component. The result of this (in this example) is a drop-down menu containing all News entries, listed by title, that a user may pick from when creating or modifying a Comment record. This creates an association between Comments and News records.

We also introduced a new function in this component file: 'get_info_header_array()'. This function defines headers that are displayed above certain attributes when a record is being created or modified. This is a very useful tool when creating components with a high number of attributes. By using the 'get_info_header_array()' function a developer is easily able to categorize and organize the various form elements - making the resulting update form more user-friendly.

Creating Component Dependencies

Now that we have created an association between our Comments component and the previously created News component, a potential problem presents itself. What happens if a user deletes a News entry which contains one or more Comment entries (attached)?

Nennius offers a simple solution for this scenario as well. Simply update the following data found in the DB Field array in the News component file ('/components/news.php'):
# define primary key attribute for news table
$f_db_info_array['id'] = (
	array (
		'db_field_name' 	=> 'id',
		'db_field_type' 	=> 'int',
		'db_field_length'	=> 3,
		'db_primary_key'	=> TRUE,
		# field is hidden from visibility at all times
		'hidden_all' 		=> TRUE
	# setup dependency information to clean all associated comments as well upon deletion
	'db_dependency_tables'		=> array(
						array (	'db_table_name'		=> 'comments', 
							'db_field_name'		=> 'news_id', 
							'db_display_name'	=> 'Comment' )
	'db_dependency_flush'		=> TRUE
As you can see we've added two array keys to the 'id' descriptor array: 'db_dependency_tables' and 'db_depencency_flush'. The first, 'db_dependency_tables', contains an array of arrays - each array specifying information about a unique record dependency. Since only one dependency exists in our example application, only one array has been specified. The second variable, 'db_depencency_flush', contains a boolean value that tells Nennius how to act in the event that a News record (with one or more Comments records attached) is deleted. In our case we specified 'TRUE', which means that all associated Comments will also be deleted. (Note: If we would have specified 'FALSE' then Nennius would have refused to delete any News releases with one or more User Comments attached.)

Component Notes

Our Comments component is now fully functional. However, we have not yet created any method for moderators to communicate regarding User Comments: this could be an unfortunate oversight. If one moderator is unsure of whether a comment should be edited/removed but has no way of asking another moderator for advice, what will happen? In order to avoid this scenario, let's allow our moderators to attach private messages, or notes, to each User Comment. This can be done simply by adding the following lines to the end of the '/descriptors/comments.php' file:
# set record notes table info (if notes desired)
$GLOBALS['g_optional_notes_db_table']		= 'comment_notes';
$GLOBALS['g_optional_notes_db_key']		= 'id';
$GLOBALS['g_optional_notes_db_index']		= 'news_id';
$GLOBALS['g_optional_notes_db_user_id']		= 'user_id';
$GLOBALS['g_optional_notes_db_datetime']	= 'datetime';
$GLOBALS['g_optional_notes_db_text']		= 'description';
Now ADMIN users will have the ability to attach private messages to user-submitted comments. These messages may be modified or deleted at a later time.

Help Files & User Documentation

We may also want to provide our moderators with basic instructions on how to properly create news entries and regulate comments. We may do this by creating basic HTML help files (we'll call ours 'news.htm' and 'comments.htm') and specifying their location within our descriptor files as well. Let's start by creating the help files and placing them within a new directory named '/help/'. For our 'news.htm' file, insert the following content:
The <strong>News Management</strong> page allows ADMINs to create and modify news releases.
Images may also be attached to releases to create a photo gallery.
Next we will need to tell Nennius which help file to use. We can do this by appending the following to our '/descriptors/news.php' file:
# specify location of help & support file for component
$GLOBALS['g_optional_help_file']					= 'help/news.htm';
Now that we've setup our News help file, try creating your own help file for the Comments component and then tie it into the system using that component's descriptor file.

File Attachments & Component History

Now that our Comments component is finished, let's re-visit our News component. Begin by opening the news component's descriptor file, '/descriptors/news.php'. Once again our file contains only the required information, but by making a few simple modifications our component can gain a great deal of added functionality. For instance: what if we wanted to add functionality for admin users to attach images to news releases, so that the our front-end system can display a photo gallery? What if we wanted the system to keep records of when each release was posted and modified, and by whom? We could do that very simply by adding a few lines to the end of our existing defaults file:
# set file attachment associations for component
# NOTE:	these fields allows for zero to an infinite number of file attachments
#	to be associated with component via a seperate indexing table.
$GLOBALS['g_optional_attachments_db_table']	= 'news_attachments';
$GLOBALS['g_optional_attachments_db_key']	= 'id';
$GLOBALS['g_optional_attachments_db_index']	= 'news_id';
$GLOBALS['g_optional_attachments_db_filename']	= 'filename';

# set record history table info (to record record creation & modification)
$GLOBALS['g_optional_history_db_table']		= 'news_history';
$GLOBALS['g_optional_history_db_key']		= 'id';
$GLOBALS['g_optional_history_db_index']		= 'news_id';
$GLOBALS['g_optional_history_db_user_id']	= 'user_id';
$GLOBALS['g_optional_history_db_datetime']	= 'datetime';
$GLOBALS['g_optional_history_db_description']	= 'description';

Using Hook Functions

At this point our example 'news' application is complete, but our learning exercise is not. We still have not discussed another powerful feature available to Nennius developers: hook functions.

Nennius supports a fairly robust set of pre-defined hook functions, allowing developers to enhance or override default behavior without modifying the Nennius engine directly. A hook function isn't needed for this example application, but we'll create one anyway in order to familiarize ourselves with how to do so. Our hook function will simply prevent the modification or deletion of any news article older than one week.

Please begin by opening the '/components/news.php' file and adding the following information:
# returns TRUE / FALSE, indicating if record is locked for edit / delete
function is_edit_lock() {
	$f_component_details	= NULL;
	# retrieve necessary API objects from control class
	$db_api	=& $this->nennius_control->get_db_api();
	# retrieve component's primary key
	$f_component_details	= $this->nennius_control->get_component_details();
	$news_id		= $f_component_details['id'];
	# run query to determine the date of record creation
	$db_query = "	SELECT	datetime 
			FROM	`news` 
			WHERE	id = $news_id 
			  AND	datetime < NOW() - 5184000";
	$db_api->query( $db_query );
	# if a result has been found, then record is too old to edit
	if ( $db_api->get_num_rows() > 0 )
		return TRUE;
	# if no result has been found, record is editable
	else	return FALSE;
} # END is_edit_lock()
As you can see, the above function retrieves the current component's details from the Nennius Control class, runs a SQL query to determine if the record in question is older than one week, and then returns TRUE or FALSE, indicating whether the record is locked for deletion. This is a rather simple example, but it suggests the power available to Nennius developers through the use of hook functions.


Although this article has taken a brief look at several of the advanced features available to Nennius developers, it is in no way a comprehensive list. The main purpose of the example application we have constructed was to demonstrate a variety of common ways Nennius may be configured to handle data. You are encouraged to take the finished product, and modify it to gain a better understanding of the configuration options we have discussed. You may also want to refer to the Nennius developer manual for a more detailed list of hook functions and configuration options.

A completed version of the application we have just created is available online. You may also download the source code for the application. If you have any questions or would like to learn more about the Nennius application engine, please visit the official Nennius website or check out the Nennius forums.