FUEL CMS User Guide : Version 1.4


Tutorial: Creating Simple Modules

The following is a tutorial on creating simple modules in FUEL. Don't let the word simple fool you though. You can still create pretty powerful modules without the need of creating your own views and controllers which is what advanced modules are for. We will actually create several simple modules to allow us to create articles and categorize them in the CMS. We will be using 3 data models (articles_model, authors_model, fuel_tags_model), and creating 2 modules (articles, authors).

Visit the Simple Modules page for more information about the new 1.3 feature to create pages automatically from your module.

These are the steps in the following tutorial:

  1. Download and Setup
  2. The Authors Module
  3. The Articles Module
  4. The FUEL Tags Module
  5. Setting Up Permissions
  6. A Little Polishing
  7. Bringing it All Into View

Download and Setup

Before we get started with this tutorial, it is recommended that you download the final files of the example and look them over briefly so it is easier to follow along.

You will need to setup an instance of FUEL including the database with a working admin login. We will use that FUEL database to store the additional tables required for our models in this tutorial.

We will be creating two database tables to support the 2 modules — an authors table and an articles table. The fuel_tags table is already created for us.

The Authors Module

We will start this tutorial by creating the authors module first. The authors module will be used to contain the list of article's authors.

In many cases, to get started creating a simple module, you will want to use the generate command line tool to create a simple module like so:

php index.php fuel/generate/simple authors

The generate command will do the following:

However, since this is a tutorial to learn about the creation of simple modules, we will skip the generate functionality and do these tasks by manually.

Setup the Authors Table

The following is the SQL code for the table used by authors model. Copy this code and run this query to generate the table in the FUEL database instance you are using for this tutorial.

CREATE TABLE `authors` (
  `id` tinyint(3) unsigned NOT NULL auto_increment,
  `name` varchar(255) collate utf8_unicode_ci NOT NULL default '',
  `email` varchar(255) collate utf8_unicode_ci NOT NULL default '',
  `bio` text collate utf8_unicode_ci NOT NULL default '',
  `avatar_image` varchar(255) collate utf8_unicode_ci NOT NULL default '',
  `published` enum('yes','no') collate utf8_unicode_ci NOT NULL default 'yes',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Note the column published. The enum columns published and active carry special meaning in FUEL and will determine whether to display the content on the website.

Create the Authors Model

After creating the table, we will create the model that links to that table. To do that, create a file in your fuel/application/model directory and name it authors_model.php. In that file we will be creating two classes. The first class, Authors_model, will extend the Base_module_model (which is an extension of MY_Model).

The second class we will create in the authors_model.php file is the custom record class, Author_model (note the singular name). This class is not required, but it will give us some extra functionality at the record level later on. If this class is not included, the data will be returned in an array format by default.

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

require_once(FUEL_PATH.'models/Base_module_model.php');

class Authors_model extends Base_module_model {

    function __construct()
    {
        parent::__construct('authors');
    }
}

class Author_model extends Base_module_record {

}

This is really all you need at the moment for the authors model so we will now focus on getting it it hooked up to the FUEL admin.

Hooking Up the Authors Module

Now that our table and model have been created, we need to tell FUEL about it. To do that, open up the application/config/MY_fuel_modules.php file and add the following line.

$config['modules']['authors'] = array(); 

The parameters for the module are empty for this example. For a complete list of parameters to pass to the module, click here.

Now login to fuel (e.g. http://mysite.com/fuel), and you should see the module on the left under the MODULES section.

If you do not see the module on the left, make sure you are logged in as an admin, or that you have the proper permissions to view that module.

The form fields should look like the following:

The Articles Module

The articles module will contain the content of the articles as well as some meta information including a foreign key to the authors table.

Setup the Articles Table

The following is the SQL code we will be using to drive the articles model. Copy this code and run this query to generate the table in the FUEL database instance you are using for this tutorial.

CREATE TABLE `articles` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `title` varchar(255) collate utf8_unicode_ci NOT NULL default '',
  `slug` varchar(255) collate utf8_unicode_ci NOT NULL default '',
  `author_id` tinyint(3) unsigned NOT NULL default '0',
  `content` text collate utf8_unicode_ci NOT NULL,
  `image` varchar(255) collate utf8_unicode_ci NOT NULL default '',
  `thumb_image` varchar(255) collate utf8_unicode_ci NOT NULL default '',
  `date_added` datetime NOT NULL default '0000-00-00 00:00:00',
  `published` enum('yes','no') collate utf8_unicode_ci NOT NULL default 'yes',
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Create the Articles Model

Now that the table is created, we will follow similar steps as above to create the articles model. So create a new file in the fuel/application/model folder and call it articles_model.php. Now add the following code to that file:

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

require_once(FUEL_PATH.'models/Base_module_model.php');

class Articles_model extends Base_module_model {

    function __construct()
    {
        parent::__construct('articles');
    }
}

class Article_model extends Base_module_record {

}

Adding a Foreign Key Relationship to Authors

Now that we have our articles model, let's start adding relationships. The first relationship we'll add is a foreign key relationship to the authors model. To do this, add a $foreign_keys property to the Articles_model with the key being the the field name in the table and the value being the model it relates to like so:

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

require_once(FUEL_PATH.'models/Base_module_model.php');

class Articles_model extends Base_module_model {
    
    public $foreign_keys = array('author_id' => 'authors_model');

    function __construct()
    {
        parent::__construct('articles');
    }
}

class Article_model extends Base_module_record {

}

This will do a couple things for us. The first is that it will automatically create a select list in the admin with the different options. The second thing it will do is allow your record objects to magically return foreign objects like so:

$articles->author->name;

An additional where parameter can be added to filter the select list by using an array syntax with a key of where like so:
public $foreign_keys = array('author_id' => array('authors_model', 'where' => array('published' => 'yes'));

Adding a Many-to-Many Relationship With Tags

The next relationship we want to add to our articles model is a many-to-many relationship with FUEL's built in Tags module. This means that an article can belong to many different tags and vice-versa. To do this, we will take advantage of FUEL 1.0's relationships and create a has_many property on the model. Below is an example of how to do this:

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

require_once(FUEL_PATH.'models/Base_module_model.php');

class Articles_model extends Base_module_model {
    
    public $foreign_keys = array('author_id' => 'authors_model');
    public $has_many = array('tags' => array(FUEL_FOLDER => 'fuel_tags_model'));

    function __construct()
    {
        parent::__construct('articles');
    }
}

class Article_model extends Base_module_record {

}

The value should be an array whose keys are the field name you want to associate with the relationship and the value being either a string with the model's name or an array with the key being the advanced module name the model belongs in and the value being the model's name. We will use the latter since this relationship is with a model in the fuel advanced module. By adding this has_many relationship, it will do a couple things. First, it will create a multi select box in the admin to associate tags with the articles. Additionally, it will allow your record objects to return a list of related data like so:

foreach($articles->tags as $tag)
{
  echo $tag->name;
}

// OR return the model object with the "where in" condition already applied
$tags_model = $articles->get_tags(TRUE);
$tags = $tags_model->find_all_assoc('id');
foreach($tags as $key => $tag)
{
  echo $tag->name;
}

In the latter example above, the record property tags can be called as a method as well by pre-appending "get_" in front of the properties name (e.g. get_tags()). This allows us to pass additional parameters.

Hooking Up the Articles Module

Similar to the authors module, we will now need to make an entry in the application/config/MY_fuel_modules.php file and add the following lines.

$config['modules']['articles'] = array(
  'preview_path'   =>  'articles/{slug}',
  'display_field'  => 'title',
  'sanitize_input' => array('template','php'),
); 

For additional list of module parameters, click here.

Changing the List View's Columns

There are a few additional things we need to do to have this module work appropriately. The first is to change the list_items method which is used to display the list of data in the admin. This method is inherited by the Base_module_model class. Below we will use CI's active record syntax and change the method so that author_id column will actually display the author's name and the content column will be truncated to 50 characters and html encoded. This is how we do it:

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

require_once(FUEL_PATH.'models/Base_module_model.php');

class Articles_model extends Base_module_model {

    public $foreign_keys = array('author_id' => 'authors_model');
    public $has_many = array('tags' => array(FUEL_FOLDER => 'fuel_tags_model'));

    function __construct()
    {
        parent::__construct('articles');
    }
  
    function list_items($limit = NULL, $offset = NULL, $col = 'name', $order = 'asc', $just_count = FALSE)
    {
        $this->db->join('authors', 'authors.id = articles.author_id', 'left');
        $this->db->select('articles.id, title, SUBSTRING(content, 1, 50) AS content, authors.name AS author, date_added, articles.published', FALSE);
        $data = parent::list_items($limit, $offset, $col, $order, $just_count);

        // check just_count is FALSE or else $data may not be a valid array
        if (empty($just_count))
        {
          foreach($data as $key => $val)
          {
            $data[$key]['content'] = htmlentities($val['content'], ENT_QUOTES, 'UTF-8');
          }
        }
        return $data;
    }

}

class Article_model extends Base_module_record {

}

Note how we precede some of the column names in the select with the table name to prevent ambiguity between the tables. Lastly, because we are using MYSQL functions in the select, we need to pass FALSE as the select's second parameter.

The key column (e.g. id) is not seen in the FUEL admin but is important to include because it is used to identify the row.

Although we show default $col and $order values, those need to be set on the default_col and default_order module to properly sort the list items.


Now login to the CMS and click on the "Articles" module in the left hand menu to see the list view of the module.

Setting Up the Articles Module Form

Now that we've edited the method for the list view in the admin, click on the Create button from the articles list view you will see the form fields for your module. The form fields are generated by the articles's form_fields method. This method is inherited from Base_module_model class and gets passed to a Form_builder instance, and should return an array with keys being the form field names and values being an array of form field parameters. More on different form field types and parameters can be found under the forms documentation.

Below is the default field type mapping generated by the form_fields method.

There are a few modifications we'd like to make. The first thing we'd like to change is the image selection/upload button on the content field's wysiwyg editor to pull from the folder assets/images/articles instead of the default assets/images folder. To make this change, you'll need to create that folder and make sure it is writable by PHP. Then you can add the following parameter to the content field in the form_fields method:

  function form_fields($values = array(), $related = array())
    {
        $fields = parent::form_fields($values, $related);
        
        // ******************* ADD CUSTOM FORM STUFF HERE ******************* 
        $fields['content']['img_folder'] = 'articles/';
        return $fields;
    }
  

The second modification is changing the folder in which the image and thumb_image select and/or upload to. To do that we add the folder parameter with the name of the asset folder:

  $fields['image']['folder'] = 'images/articles/';
  $fields['thumb_image']['folder'] = 'images/articles/thumbs/';

Again, these folders must be writable or it will default to the images folder.


Below is what the model looks like now:

<?php  if (!defined('BASEPATH')) exit('No direct script access allowed'); 

require_once(FUEL_PATH.'models/Base_module_model.php');

class Articles_model extends Base_module_model {
  
  public $foreign_keys = array('author_id' => 'authors_model');
  public $parsed_fields = array('content', 'content_formatted');
  
    function __construct()
    {
        parent::__construct('articles'); // table name
    }

    function list_items($limit = NULL, $offset = NULL, $col = 'name', $order = 'asc', $just_count = FALSE)
    {
        $this->db->join('authors', 'authors.id = articles.author_id', 'left');
        $this->db->select('articles.id, title, SUBSTRING(content, 1, 50) AS content, authors.name AS author, date_added, articles.published', FALSE);
        $data = parent::list_items($limit, $offset, $col, $order, $just_count);

        // check just_count is FALSE or else $data may not be a valid array
        if (empty($just_count))
        {
          foreach($data as $key => $val)
          {
            $data[$key]['content'] = htmlentities($val['content'], ENT_QUOTES, 'UTF-8');
          }
        }
        return $data;
    }

    function form_fields($values = array(), $related = array())
    {
        $fields = parent::form_fields($values, $related);
        
        // ******************* ADD CUSTOM FORM STUFF HERE ******************* 
        $fields['content']['img_folder'] = 'articles/';
        $fields['image']['folder'] = 'images/articles/';
        $fields['thumb_image']['folder'] = 'images/articles/thumbs/';

        return $fields;
    }
}

class Article_model extends Data_record {

}

Below is what the articles form should look like:

The FUEL Tags Module

As we alluded to earlier, we will use FUEL's built in tags module to create the many-to-many relationships model with the articles module. The fuel_tags_model, along with it's cousin the fuel_tags_categories model are part of the FUEL tags and categories modules that are new to 1.0. These modules allow you group related models together without the need to create additional models. These modules are not visible by default and must be to enabled to be visible in the CMS. To do so, go to the fuel/application/config/MY_fuel_modules.php and comment out or remove the following module overwrites:

$config['module_overwrites']['categories']['hidden'] = FALSE; // change to TRUE if you want to use the generic categories module
$config['module_overwrites']['tags']['hidden'] = FALSE; // change to TRUE if you want to use the generic categories module

Now that you've unhidden the categories module, go back to the admin and refresh. You should see a "Tags" module appear on the left menu. Click on it and click the "Create" button. You will see that a FUEL tag has the following fields:

It will also show a multi select field for all models that have a has_many link to the tags. So in this case, you should see a Belongs to Articles multi select field too.

That's it for tags.


Creating the Permissions

Now that the modules are created, you will probably want to allow others to use them. To do this, you will need to access to the permissions module to create permissions so users can be allowed to access those modules in FUEL. To do that

  1. Click on the permissions link on the left and create the permission authors. Keep the "create", "edit", "publish" and "delete" checkboxes checked for the Generate simple module permissions and click "Save". This will create a permission for users to be subscribed to.
  2. To assign the permission to a user, click the users link on the left menu, then select a user.
  3. Once in the edit user screen, check the box for the new authors permission you just created.

Repeat these step to create the articles permissions.

A Little Polishing

Now that we have the authors, articles and tags modules setup, it's time to add a little polish. This will include adding the following:

  1. Required fields
  2. Unique fields
  3. Parsed fields
  4. Filter fields
  5. Tree view
  6. Record specific methods

Required Fields

The first thing we will do is add some required fields to both the Authors and Articles model like so:

For the authors model, we will make the name and email fields required.

... 
class Authors_model extends Base_module_model {
  
  public $required = array('name', 'email');

...

For the articles model, we will make the title field required. The slug is also required however, it will be automatically generated if left blank so it is not necessary to add it.

... 
class Articles_model extends Base_module_model {
  
  public $required = array('title');
  public $foreign_keys = array('author_id' => 'authors_model');
  public $has_many = array('tags' => array(FUEL_FOLDER => 'fuel_tags_model'));

...

Unique Fields

FUEL model's can also declare fields that need to be unique in addition to the primary key. In this example, the article's slug value needs to be unique across all articles.

... 
class Articles_model extends Base_module_model {
  
  public $required = array('title');
  public $unique_fields = array('slug');
  public $foreign_keys = array('author_id' => 'authors_model');
  public $has_many = array('tags' => array(FUEL_FOLDER => 'fuel_tags_model'));

...

If you have a combination of fields that need to be unique (e.g. a compound key), you can specify an array value instead containing all the fields that need to be unique.

Parsed FIelds

By default, FUEL will not parse the data for templating syntax. To fix this you add the $parsed_fields model array property with the array values the names of fields that should be parsed upon retrieval. In this example, we'll want the article model's content field to be parsed so we add the following:

... 
class Articles_model extends Base_module_model {
  
  public $required = array('title');
  public $unique_fields = array('slug');
  public $parsed_fields = array('content');
  public $foreign_keys = array('author_id' => 'authors_model');
  public $has_many = array('tags' => array(FUEL_FOLDER => 'fuel_tags_model'));

...

Filtered Fields

The filters property allows you to specify additional fields that will be search from the list view in the admin. There is also a filters_join property you can specify either "and" or "or" which will determine how the where condition in the search query is formed (e.g. title AND content vs. title OR content). For more complicated where conditions you can specify a key value array with the key being the filter field and the value being either "or" or "and" (e.g. array('title' => 'or', 'content' => 'and')). In addition, you can specify filters() method on your model that can return an array of Form_builder fields. For this example, we will leave it as the default "or" and not use a filters method:

... 
class Articles_model extends Base_module_model {
  
  public $filters = array('content');
  public $filters_join = 'or';
  public $required = array('title');
  public $unique_fields = array('slug');
  public $parsed_fields = array('content');
  public $foreign_keys = array('author_id' => 'authors_model');
  public $has_many = array('tags' => array(FUEL_FOLDER => 'fuel_tags_model'));

...

Tree View

You may have noticed that in some of the modules like Pages and Navigation, there is a "Tree" button on the list view. This button appears when a tree method has been implemented on the model. The tree method will display the data in a hierarchical tree format as opposed to the list view. The method must return an array that follows the Menu hierarchical structure. The Base_module_model can make this tree view easy if you have implemented a foreign_keys, has_many or belongs_to property on your model. There is a protected _tree method (note the preceding underscore) that can be called in which you pass it one of those values. For this example, we want a tree view that groups the articles based on the tags.

... 
function tree()
{
    return $this->_tree('has_many');
}
...

You could create a tree view grouped by authors if you pass the _tree method "foreign_keys" instead.

The tree method will render like the following in the FUEL admin:

Adding to the Record Object

The last addition we will make to the articles model will be to add several record methods. The first will be get_url which will give us a single place to control URLs to articles. The second and third methods will be to generate proper image paths for the a view:

... 
class Article_model extends Data_record {
  
  public function get_url()
  {
    return site_url('articles/'.$this->slug);
  }

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

  public function get_thumb_image_path()
  {
    return img_path('articles/'.$this->thumb_image);
  }

}
...

In a view the code may look like this:

<img src="<?=$article->image_path?>" alt="<?=$article->title_entities?>

Note that title_entities isn't actually a field. This is using model formatters to generate entitied output.


Bringing it All Into View

Now that our modules are created for FUEL, we now can add the module's model data to our views. Although we could use controllers to marshall the data to the views, for our example, we will just incorporate that logic in the view itself (read more about the opt-in controller philosophy). In our view we will have a page that will either display articles based on a slug or list all of them grouped by tags. To start, create a file called articles.php and place it in your application/views folder. Open it up and put the following code in it.

<?php 
$slug = uri_segment(2);

if ($slug) :

  $article = fuel_model('articles', array('find' => 'one', 'where' => array('slug' => $slug)));

  if (empty($article)) :
    redirect_404();
  endif;

else:

  $tags = fuel_model('tags');

endif;

if (!empty($article)) : ?>
  
  <h1><?=fuel_edit($article)?><?=$article->title?></h1>
  <div class="author"><?=$article->author->name?></div>
  <img src="<?=$article->image_path?>" alt="<?=$article->title_entities?>" class="img_right" />
  <article><?=$article->content_formatted?></article>


<?php else: ?>

  <h1>Articles</h1>
  <?=fuel_edit('create', 'Create Article', 'articles')?>
  <?php foreach($tags as $tag) : ?>
  <h2><?=$tag->name?></h2>
  <ul>
    <?php foreach($tag->articles as $article) : ?>
    <li><?=fuel_edit($article)?><a href="<?=$article->url?>"><?=$article->title?></a></li>
    <?php endforeach; ?>
  </ul>
  <?php endforeach; ?>

<?php endif; ?>

Analyzing the View Code

The first line of code grabs the slug value from the URI path. We then check to see if that slug value exists and if it does, we use fuel_model to retrieve the article object. This line is the equivalent to:

$CI->load->model('articles_model');
$article = $CI->articles_model->find_one(array('slug' => $slug));

If an article with the passed slug value doesn't exist in the table, then it will call the redirect_404 function which will first look for any redirects and if none are found, will display the 404 error page. If an article does exist, then it continues down the page and displays the contents of the article by outputing the title, image, author name and content.

If no slug value is passed to the page, then a simple fuel_model call will be used which will automatically grab all tags associated with articles. We then loop through those tags and because of the has_many association with the tags module, we are able to easily grab all articles associated with that tag which allows us to create a linked list item for each article.

Finding the View

You may have noticed that you will still get a 404 error page if you go to articles/{slug} (where "slug" is a published records slug value in the admin). This is because we still need to tell FUEL to use this view file for any URI location of articles/{slug}. There are several ways to do this and you only need to implement one of them below:

  1. max_page_params: you can add the following to FUEL configuration parameter to fuel/application/config/MY_fuel.php
        $config['max_page_params'] = array('articles' => 1);
        
  2. auto_search_views: you can add the following to FUEL configuration parameter to fuel/application/config/MY_fuel.php
        $config['auto_search_views'] = TRUE;
        
  3. global variables file: you can add the following to fuel/application/views/_variables/global.php
        $pages['articles/:any'] = array('view' => 'articles');
        
  4. articles variables file: since this view file exists at the first URI segment of "articles" you can create a separate fuel/application/views/_variables/articles.php variables file add the following:
        $vars['view'] = 'articles';
        

Inline Editing

The very last thing we will do is add inline editing to our view. To do this we use the fuel_edit function. We'll place that function right inside the <h1> tag in the article view and inside the <li> in the list view.

...
<h2><?=fuel_edit($article)?><?=$article->title?></h2>
...

Additionally, you may add items in the context of a page. To do that, you pass create as the first parameter.

<h2><?=fuel_edit('create', 'Create', 'articles')?><?=$article->title?></h2>

Next, make sure the FUEL bar located in the upper right of the page has the pencil icon toggled on. You should now see pencil icons that when clicked, will allow you to edit or add article data from within the context of the page.

Click here for more on inline editing


That's it!

Now What?

If you are wanting to create more advanced modules, click here. If you have any question, meet us in the forum.