Learning FUEL CMS PART 3: Creating Modules


In Part 3 of this blog series we will discuss how to create simple modules and will use the demo site as a point of reference.

Before we go any further though, make sure you have read the introduction post about the blog series and have downloaded and installed the FUEL CMS 0.91 branch from GitHub.

The user guide provides another example of creating simple modules and it is recommended that this be read as well.

What is a Module?

To pull directly from our user guide:

Modules are a way for you to extend the functionality of FUEL. The term modules is used rather loosely and it is entirely possible to nest modules within another module (sometimes called sub-modules). We refer to these types of modules as advanced modules. In fact, FUEL is no more then an advanced module itself.

Most of the time however, modules are simply just data models (simple modules) with a form interface to change values.

For the purpose of this tutorial, we will focus on the two custom simple modules we created for the demo site — the projects module and the quotes module.

Create Your Database Table

The first step of creating a module involves distilling down the data points that need to be captured in a database table to render the page. Below is the SQL to create the tables behind the projects and quotes modules respectively. Run this SQL in your FUEL install database:

MySQL tables for projects and quotes module
CREATE TABLE `projects` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(255) collate utf8_unicode_ci NOT NULL COMMENT 'The name of the project',
  `slug` varchar(255) collate utf8_unicode_ci NOT NULL COMMENT 'If left blank, one will automatically be generated for you',
  `client` varchar(255) collate utf8_unicode_ci NOT NULL,
  `description` text collate utf8_unicode_ci NOT NULL,
  `website` varchar(255) collate utf8_unicode_ci NOT NULL,
  `launch_date` date default NULL,
  `image` varchar(100) collate utf8_unicode_ci NOT NULL,
  `featured` enum('yes','no') collate utf8_unicode_ci NOT NULL default 'yes',
  `precedence` int(11) NOT NULL default '999' COMMENT 'The higher the number, the more important',
  `published` enum('yes','no') collate utf8_unicode_ci NOT NULL default 'yes',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


CREATE TABLE `quotes` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `content` text collate utf8_unicode_ci NOT NULL,
  `name` varchar(50) collate utf8_unicode_ci NOT NULL,
  `title` varchar(100) collate utf8_unicode_ci NOT NULL,
  `precedence` int(11) NOT NULL default '0',
  `published` enum('yes','no') collate utf8_unicode_ci NOT NULL default 'yes',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Create Your Models

Now that the tables are created, we will create models to map to those tables. Each model file is made up of 2 specific class files — the table class and it's corresponding record class. The Table class is used for marshaling data back and forth from the table (table level operations). The record class is the equivalent to a row within the table but allows you to create your own custom methods to extend it's functionality.

For the table classes, we will extend the Base_module_model class (which itself extends the MY_Model class). Below the table class, we create the record level class which extends the Base_module_record class. We will now review each module's model below.

The Projects Model

The Projects model is found in the fuel/applications/models/projects_model.php. Let's deconstruct it.

Properties

At the top of the file, we include the field name in the $filter array so the name field is included when searching in the FUEL admin on the list view of the module. We also add the field name to the required array so that it will only save if that value is not empty.

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
require_once(FUEL_PATH.'models/base_module_model.php');

class Projects_model extends Base_module_model {

	public $filters = array('name');
	public $required = array('name');

list_items()

The list_items() method is used by the FUEL admin to list all the records of a module. The first field in the select should be the tables unique key, followed by any other additional fields you want to display. We use the native CodeIgniter active record select to manipulate the select statement before we call the parent::list_items(). Without specifying a select statement, the method will return all fields of the table.

function list_items($limit = null, $offset = null, $col = 'precedence', $order = 'desc')
{
	$this->db->select('id, name, client, precedence, published', FALSE);
	$data = parent::list_items($limit, $offset, $col, $order);
	return $data;
}

form_fields()

The form_fields() method is used to return the form information needed for the Form_builder class to render the create/edit form of a record. FUEL will automatically turn any field that ends with image or img into an asset select and a file upload form field. However, by default, the asset select form field will look in the image folder and we want it to look in the images/projects folder so we alter the class field property. Similarly, we want the images to be uploaded into the images/projects instead of just the images folder, so we alter the upload_path on the image_upload field. Additionally, we alter the after_html property of the field so that the preview image will pull from the images/projects folder instead of the images.

function form_fields($values = array())
{
	$fields = parent::form_fields($values);
	
	// to limit the image folder to just the projects folder for selection
	$fields['image']['class'] = 'asset_select images/projects';
	
	// put all project images into a projects subfolder
	$fields['image_upload']['upload_path'] = assets_server_path('projects', 'images');
	
	// fix the preview by adding projects in front of the image path since we are saving it in a subfolder
	if (!empty($values['image']))
	{
		$fields['image_upload']['before_html'] = '<div class="img_display"><img src="'.img_path('projects/'.$values['image']).'" style="float: right;"/></div>';
	}
	return $fields;
}

on_before_validate()

The on_before_validate() hook method allows us to create or alter values before they get saved. In this case, we create the slug value if it is left blank, by using the url_title() function on the name value:

function on_before_validate($values)
{
	// if slug is left blank, then we generate it based on the name
	if (empty($values['slug'])) $values['slug'] = url_title($values['name'], 'dash', TRUE);
	return $values;
}

on_after_post()

The on_after_post() hook method is used primarily to do post processing of assets (like images). In this case, we want to automatically resize the image and create a thumbnail. We use CodeIgniter's File Upload Class to do the image uploading, and we use it's data() method to retrieve the uploaded information in this case.

function on_after_post($values)
{
	$CI =& get_instance();
	$CI->load->library('image_lib');
	
	// create the thumbnail if an image is uploaded
	if (!empty($CI->upload))
	{
		$data = $CI->upload->data();
		if (!empty($data['full_path']))
		{
			$thumb_img = assets_server_path('projects/'.$this->thumb_name($data['file_name']), 'images');
			
			// resize to proper dimensions
			$config = array();
			$config['source_image'] = $data['full_path'];
			$config['create_thumb'] = FALSE;
			//$config['new_image'] = $thumb_img;
			$config['width'] = 240;
			$config['height'] = 140;
			$config['master_dim'] = 'auto';
			$config['maintain_ratio'] = TRUE;
			$CI->image_lib->clear();
			$CI->image_lib->initialize($config);
			if (!$CI->image_lib->resize())
			{
				$this->add_error($CI->image_lib->display_errors());
			}
			
			// create thumb
			$config = array();
			$config['source_image'] = $data['full_path'];
			$config['create_thumb'] = FALSE;
			$config['new_image'] = $thumb_img;
			$config['width'] = 100;
			$config['height'] = 80;
			$config['master_dim'] = 'auto';
			$config['maintain_ratio'] = TRUE;
			$CI->image_lib->clear();
			$CI->image_lib->initialize($config);
			if (!$CI->image_lib->resize())
			{
				$this->add_error($CI->image_lib->display_errors());
			}
		}
	}

	return $values;
}

If you are wanting to upload to a folder that is dependent on a field value, you can use field placeholders like below:

// put all project images into a projects subfolder.
$fields['image_upload']['upload_path'] = assets_server_path('projects/{image_folder}/', 'images');

on_before_delete()

The on_before_delete() method is used to clean up any images that may have been uploaded for the project.

function on_before_delete($where)
{
	$id = $this->_determine_key_field_value($where);
	$data = $this->find_by_key($id);
	$files[] = assets_server_path('projects/'.$data->image, 'images');
	$files[] = assets_server_path('projects/'.$this->thumb_name($data->image), 'images');
	foreach($files as $file)
	{
		if (file_exists($file))
		{
			@unlink($file);
		}
	}
}

thumb_name()

The thumb_name() is just a convenience method we created to get the projects thumbnail image name.

function thumb_name($image)
{
	return preg_replace('#(.+)(\.jpg|\.png)#U', '$1_thumb$2', $image);
}

Project Record Class

Below the Projects_model class, is the Project_model class (no s on Project) that represents a project record. In this class, we've added some additional methods to give the record some additional properties. Record methods beginning with get_ can also be accessed as properties without the get_ (e.g. $project->get_url() == $project->url).

class Project_model extends Base_module_record {
	
	function get_url()
	{
		return site_url('showcase/project/'.$this->slug);
	}

	function get_image_path()
	{
		return img_path('projects/'.$this->image);
	}

	function get_thumb()
	{
		$thumb = $this->_parent_model->thumb_name($this->image);
		return img_path('projects/'.$thumb);
	}
	
}

The Quotes Model

The Quotes model is found in the fuel/applications/models/quotes_model.php. It's much simpler then the projects_model, but we'll point out a couple differences. Below is the model in it's entirety:


<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
require_once(FUEL_PATH.'models/base_module_model.php');

class Quotes_model extends Base_module_model {

	public $required = array('content');
	
	function __construct()
	{
		parent::__construct('quotes'); // table name
	}

	function list_items($limit = NULL, $offset = NULL, $col = 'name', $order = 'asc')
	{
		$this->db->select('id, SUBSTRING(content, 1, 200) as content, name, title, precedence, published', FALSE);
		$data = parent::list_items($limit, $offset, $col, $order);
		return $data;
	}
	
	function _common_query()
	{
		parent::_common_query(); // to do active and published
		$this->db->order_by('precedence desc');
	}
}

class Quote_model extends Base_module_record {
	
}
?>

list_items()

One thing you'll notice that is different between the quotes_model and the projects_model is that the list_items() method uses a MySQL function SUBSTRING to create the content column. Similarly, this is often done with dates using the DATE_FORMAT MySQL function.

_common_query()

The _common_query() method is intended to help reduce the amount of active record code used by the find_ methods of the model. We take advantage of this method in the quotes_model by specifying we want all results ordered by the precedence column in descending order.

Enable It in FUEL

Now that our models are completed, we need to create the module in FUEL. To do this, we add them to the fuel/application/config/MY_fuel_modules.php file like so:

$config['modules']['quotes'] = array(
	'preview_path' => 'about/what-they-say',
);

$config['modules']['projects'] = array(
	'preview_path' => 'showcase/project/{slug}',
);

For both modules, we specify the preview_path which is the URI path you can use to preview the module on your site. The projects module uses the placeholder {slug} which will merge the slug value of the record into the preview link.

For a complete list of ways to customize a simple module, click here.

After enabling the modules, you can now login to the FUEL admin as a super admin user (e.g. http://mydomain.com/fuel) and they will appear on the menu in the left.

Setting up Permissions

Our last step in setting up a module is to create the permissions so that other users can be granted access to the project and quotes module. To add permissions, you will need to log into the FUEL admin as a user that has access to create permissions and manage users (the default super admin does have these permissions). Once logged in, you need to click on the permissions menu icon on the lower left of the screen and then click create. The create page should have three fields: name, description and active. The name field is the FUEL URI path to the module and in our case that is projects and quotes respectively. For the description, you can add a friendly description of what the permission is used for.

After creating the permission, you need to click on the users menu icon on the lower left area of the page. This will bring up the list of users in the system. Clicking on a user will bring up their user information along with the permissions they are associated with (if they are not a super admin user). If the only user is the super admin user, you can create a new user. Either way, you will now see your new permissions you created which you can now assign to the user.

That's it for Part 3: Creating Modules. We encourage you to join the community, sign up for our newsletter below, and/or follow us on Twitter, to stay informed on the latest FUEL CMS news. Stay tuned for Part 4: Using the Admin.


Comments

Was just trying out the custom module feature.

Notice the projects module didn’t work. Then I realised that the model is missing a constructor comparing to the quotes model.

It would be nice to mentioned that in the projects model section. =]


    function __construct()
    {
        parent::__construct('projects'); // table name
    }

CreativeJ, Jan 19, 2011

I can see how that would be confusing. To others reading this, make sure to look at the actual model file to help avoid that confusion.

David McReynolds, Jan 19, 2011

Comments have been turned off for this post.


  Back to Top