Blog Archive

Sunday 1 March 2015

Using the Fuel CMS cache library

I've only recently made use of the cache library in Fuel CMS, so I thought I'd post about it. In the past I've imported other cache libraries to do the task, but why bother when the native one is so useful?

If you want to save on model queries you can do this, in a controller for example:


        // try cache
        $cache_id = 'mycache';
        if(!$data = $this->fuel->cache->get($cache_id)) {
            $this->load->model('data_model');
            $data = $this->data_model->find_all_array(NULL, 'last_modified DESC');
            $this->fuel->cache->save($cache_id, $data, NULL, 3600);
        }
        // ....use the $data as intended


So all that happens here is a test to see if the cached item is valid, if not, run the query and create a new cache item. Note that $data is is populated in either case. The $cache_id  will be MD5'd in the application/cache directory, but if you identify the file by timestamp for example, you'll see that the data is serialized, like most other cache libraries.




Wednesday 19 February 2014

Creating an authentication system for Fuel CMS using the Ion Auth library and Codeigniter hooks

Fuel CMS has an excellent system for publishing pages to a website. But what if you wanted some of those pages to be "members only", protected on the front end by an authentication layer? There is no in-built method to do this, but there are plenty of ways in the underlying Codeigniter framework to enable it.

In this tutorial we will install and use the excellent Ion Auth library written for Codeigniter by Ben Edmunds to create just such an authentication layer for Fuel CMS pages.

The Ion Auth library is available as a zip from GitHub, and is already packaged in the Codeigniter directory hierarchy, so just unpack it into your Fuel CMS directory, in the /application/ folder, and run the SQL provided. We will be making use of it just as if it were running in a plain vanilla CI site.

Preparing Fuel CMS for front end authentication

From Fuel’s CMS perspective, we are going to alter the default layout (“main”) for “pages”, but we want to set Fuel’s page mode to “auto” for our purposes, so change $config['fuel_mode'] (in /application/config/MY_fuel.php) if it is set to anything else.

A bit of background on the way Fuel CMS stores "pages" might be useful to understand the concept of "layouts".

"Pages" are stored in 2 database tables in Fuel:
  1. Fuel_pages is a relatively small table which stores the id, location (uri), published status, cache mode and related author and timestamp data. This is the “parent” table.
  2. Fuel_page_variables (the “child” table) stores the actual content of the page, related by a foreign key to Fuel_pages. What makes the Pages module in Fuel so flexible is that the "layout" fields are stored as name and value pairs, which means they are easily extendable. More fields = more rows in this table.
So the HTML input fields that are evident in the Pages module of the Fuel admin system are actually configured by "layouts", which are really just PHP arrays. These array templates are then stored, in name value pairs in the Fuel _pages_variables table. Importantly, each pair is also classified by a "type", so an input field might be text, multi, date, file and so on (more info here). This means if you want to add to the fields/inputs for a page layout, you don't need to alter the SQL structure of the storage table, you just add to the PHP arrays! This is a HUGE timesaver!

If you need to have different content layouts in your site (eg your "Home" page has need of video urls, or your"About" page differs from other pages as it has formatted staff biographies) then one way to tackle these content differences is by making use of one or more layouts. The choice of layout is actually there in the page (Fuel_pages) definition. Try swapping between "main" and "alias" layouts in the pages module and see how the field layout alters completely!

To change or create new layouts, we need to edit MY_fuel_layouts.php (found in the /application/config folder). Note that if you add another layout to the MY_fuel_layouts file, then you should also provide a new layout view file in /application/views/_layouts of the same name as the config index..

However, we are going to keep things relatively simple here and just alter the "main" layout. We want simple “boolean” authentication across all the pages we create, and for the sake of this tutorial we will stick with one layout that incorporates this. So find the array definition $config['layouts']['main'] and add to the end of the "fields" sub-array this:

'auth' => array(
'type' => 'enum',
'options' => array('0' => 'No', '1' => 'Yes'))


Here we are adding a field called "auth" to the main layout. It's an enum type, so Fuel's Form Builder Class will render a radio button array (as there are only 2 values) and the values will be 0 and 1, with labels "No" and "Yes" respectively.
admin_auth_field

Save MY_fuel_layouts.php and call up the pages module in the Fuel admin to see the result. Note that because we added the value  after the Body fieldset definition, the new field "auth" will appear last under the "Body" tab in the "Layout Variables" section of the page. Select “Yes” for the new field. Now when you save the page, the selected value (1) will be stored in the Fuel_page_variables table. Try running the following SQL in your database client to verify the saved value (your page_id value may differ):

select * from fuel_page_variables where page_id = 2 and name = 'auth';

The result may look something like:

page_variable_auth_sql


Note that if you re-save that page, but with “No” selected, you will observe from running the SQL query again that the “No” value (0) is actually saved as an empty value.

Ok, so we can now flag which pages are to be protected. But how to actually check the value in the page against Ion Auth?

Using Codeigniter’s hooks

There are a few ways to implement Ion Auth in Codeigniter – we could use the controller system to create an authentication controller, and extend from that when we need to employ login credential checks. But for this tutorial we will use Codeigniter’s Hooks.

As the name implies, Hooks are means of intercepting processes in Codeigniter at crucial points. With our authentication system, we want to check at the Controller level, or very near it. One hook “post_controller_constructor” suits our requirements. It will be almost as good as writing the Ion Auth authentication into each and every controller’s constructor method. Note that doing this may introduce performance penalties as it will be run on every controller call in the application, so it is important to tread lightly using this technique!

To create a Hook, we need to add an entry to /application/config/hooks.php:

include(FUEL_PATH . 'config/fuel_hooks.php');
 
/* Our hook code for Ion Auth */
$hook['post_controller_constructor'][] = array(
    'class' => 'Auth_hooks',
    'function' => 'cms_auth',
    'filename' => 'Auth_hooks.php',
    'filepath' => 'hooks',
    'params' => array(),
    'module' => 'app',
);

Note that we add this hook definition after the include for Fuel’s own hooks, and that we employ the “multiple calls to the same hook” extra array index (this is because Fuel is already using this Hook point, so we are adding to it with the extra array dimension).

So we need to have a class called Auth_hooks.php in /application/hooks, with a method “cms_auth()”. We don’t have to use a class, but we will!

class Auth_hooks {

    function cms_auth() {
        $CI = &get_instance();
        $redirect_to = 'auth/login';
        $pagevars = $CI->fuel->pagevars->retrieve(uri_path());
        if(isset($pagevars)) {
            if(isset($pagevars['auth'])) {
                $CI->load->library('ion_auth');
                if(!$CI->ion_auth->logged_in() && $pagevars['auth'] == 1) {
                        redirect($redirect_to);
                }
            }
        }
    }
}


This is a very simple means of protecting CMS content using hooks. If the page is set to require authentication ($pagevars['auth'] == 1) and Ion_Auth detects no login credentials, then we redirect the user to the auth/login controller (which should have been added when the Ion Auth zip was unpacked). Note that we use the Pagevars class to find any variables for the given url. One other important note: turn off caching for authenticated pages - otherwise you may inadvertently store login information.

There are a number of improvements we could make to this basic authentication handling. For example, we could make it more fine grained and authenticate against Ion Auth’s groups (ie a user must be logged in and a member of group or groups x,y,z…). We could make the redirect a bit more helpful, such that after logging in the user is redirected to the original page he or she was rejected from.

All of this is possible and may be the subject of a further post!

Saturday 5 October 2013

WAMP, virtual hosts & subdomains

WAMP is my development environment of choice, and I usually have simultaneous projects in progress. Here's my solution to keeping them all separate & discrete during that process. It involves serving projects as subdomains of the local server, the advantage of this being primarily that all projects behave as if they were at the root level, meaning no special .htaccess editing per project. Transferring these projects to a staging or live server is then usually transparent.


  1. Open httpd.conf and un-comment the Virtual hosts line, if it is not already enabled. It should look something like:

    Include conf/extra/httpd-vhosts.conf

  2. Open that file up (httpd-vhosts.conf) and edit it so it is something like:

    NameVirtualHost *:80
    <VirtualHost *:80>
    ServerName localhost.dev
    ServerAlias *.localhost.dev
    VirtualDocumentRoot c:\wamp\www\subdomains\%1
    ErrorLog "logs/subdomain_errors.log"
    <directory "c:\wamp\www\subdomains\%1">
    Options Indexes FollowSymLinks
    AllowOverride all
    Order Deny,Allow
    Deny from all
    Allow from all
    </directory>
    </VirtualHost>

    In this example I am going to keep all my projects in a sub-folder of the WAMP www directory named "subdomains". It can be named whatever you like.

    The virtual host is being set up to serve "anything.localhost.dev" from the subdomains folder where "anything" matches a folder inside of that. So "project1.localhost.dev" would actually serve files from "c:\wamp\www\subdomains\project1". If you keep your WAMP directory somewhere other than the c: drive, you will need to change the path accordingly.

  3. Next, the hosts file will need to understand that "anything.localhost.dev" refers to the loopback address, same as "localhost". The hosts file is usually found on a Windows system in "C:\Windows\System32\drivers\etc". I like to use an editor like HostsMan to do this, although notepad will do too, and you will probably need to edit the hosts file as an administrator. Add a line for each project in this fashion:

    127.0.0.1 project1.localhost.dev
    127.0.0.1 project2.localhost.dev
    ...

    Incidentally, I chose the suffix ".dev" simply because it is not a valid DNS one, and clearly indicates "development".

  4. To enable the hosts change you will probably need to flush the DNS cache. HostsMan has a menu setting to do this, although yo can do it from the command line with

    ipconfig /flushdns

  5. To enable the changes to httpd.conf and httpd-vhosts.conf you will need to restart WAMP, if you haven't already.Make sure the rewrite and vhosts modules are enabled.

Your browser should now be able to serve http://project1.localhost.dev as expected, from files in "c:\wamp\www\subdomains\project1".

Wednesday 1 May 2013

Putting the Form Builder class to work in FUEL CMS

Building forms in HTML is rarely a pleasure, so any functionality that aids that chore is a boon. Codeigniter has some form helper functions that are handy, and separately, the Validation class to prepare form data for processing. FUEL CMS builds on these to create the seriously useful Form Builder class.

In this tutorial I will show how useful it can be by building a form with CAPTCHA capabilities, and error responses.  I will be using v1.0 of FUEL CMS. I will use the CAPTCHA library found here CodeIgniter-Captcha

The form

 The source fields for the Form Builder class can be stored as an array, so why not create a config file for forms? In the application/config folder, create a file imaginatively called "forms.php". This can store one or more forms, and in this example it will have a "contact us" type form like so:


/* 
* CONTACT FORM
 */
$config['contact']['form'] = array(
  'required_text' => '* required fields',
  'css_class' => 'regular',
  'form_attrs' => array('method' => 'post'),
  'submit_value' => 'Send'
);

/* Setup fields */
$config['contact']['fields'] = array(
  'name' => array('type' => 'text', 'label' => 'Your name', 'required' => TRUE),
  'email' => array('type' => 'text', 'label' => 'Your email', 'required' => TRUE),
  'telephone' => array('label' => 'Your telephone'),
  'enquiry' => array('type' => 'textarea', 'label' => 'Your enquiry', 'required' => TRUE) 
);


The configuration for Form Builder is in 2 parts - the form itself, and then the fields. The keys for the fields are self explanatory, the form configuration keys are more complex - some are attributes, others are HTML snippets.

How do we put these to work in Form Builder? We will need to load the config file in our controller, and supply the "form" array as the configuration parameters to the class. So in a controller we can do:

class Contact extends CI_Controller {
    
  public $form_data;

  public function __construct() 
  {
    parent::__construct();
    // load contact config
   $this->load->config('forms');
   $this->form_data = $this->config->item('contact');
  }
...


For convenience I am putting the loaded "contact" array into a public variable. Later in the controller, Form Builder can be configured like so:

$this->load->library('form_builder', $this->form_data['form']);
$this->load->library('captcha');
$this->load->library('form_validation');


This configuration can occur in a method of your choosing - since this will be for an index page, my code was in the index() method. Note also I am loading the CAPTCHA library referred to above, and the form validation library.

As per the typical Codeigniter form validation, this same controller method should use some validation rules:

/* validators */
$this->form_validation->set_rules('name', 'Name', 'trim|required|max_length[128]|xss_clean');   
$this->form_validation->set_rules('email', 'Email', 'trim|required|max_length[256]|valid_email|xss_clean');
$this->form_validation->set_rules('telephone', 'Telephone', 'trim|max_length[24]|xss_clean');   
$this->form_validation->set_rules('enquiry', 'Enquiry', 'trim|min_length[8]|max_length[1024]|xss_clean');

// captcha rule - use codeigniter validation callback feature! NB we set function beginning with
// underscore so it won't get actioned by URL request - hence double underscore in callback 
// (callback convention + controller method convention)!
$this->form_validation->set_rules('captcha', 'CAPTCHA', 'required|callback__captcha_check');
$this->form_validation->set_message('_captcha_check', 'Text did not match the characters in the image');


The validations for the fields loaded by the forms.php config file are unremarkable, but something special is happening regarding the CAPTCHA input field - another method is being called (_captcha_check) within the rules argument. The method call is prefixed with "callback_" which is Codeigniter's way of implementing a function call for callbacks. So there will need to be another method in the controller like:

// CAPTCHA callback function - used by validation 
function _captcha_check($str)
{
  if($this->captcha->check($str))
     return TRUE;     
  else 
    return FALSE;   
}


The method follows the Codeigniter convention of beginning with an underscore so that it is not available as a URI request. The callback gets the string from the supplied input and uses the Captcha library to test if it matches the session string. A boolean return completes the validation.

So far, nothing has happened about the tiresome form construction! Here's how it will happen: we only need show the form either on initial load, or if the form has been submitted and failed validation, so inside of the validation test for "no validation"

if($this->form_validation->run() == FALSE) {

  // create CAPTCHA img and field
  if($this->captcha->create()){
    $captcha = $this->captcha->html_data;
  } else {
    $captcha = 'Captcha : ' . $this->captcha->debug;
  }
  // append captcha html to text field of same name
  $captcha_html = "<p title='Input the characters in this image in the field below'>";
  $captcha_html .= img($captcha) ."</p>";
  $this->form_data['fields']['captcha']['before_html'] = $captcha_html;
    
  // See if submit has occurred (and failed) & populate fields
  // with submitted data, and append error too.
  if($this->input->post('Send'))
  {
    foreach($this->form_data['fields'] as $key => $val)
    {
      $this->form_data['fields'][$key]['value'] = $this->input->post($key);
      $this->form_data['fields'][$key]['after_html'] = form_error($key);
    }
  }
  $data['form'] = $this->form_builder->render_divs($this->form_data['fields']);
}



This is where Form Builder really comes into its own. Remember that earlier we created a class property array $this->form_data? The last line in the code snippet above builds the entire form from the config file array "fields" using this property. Using the class method render_divs(), it constructs the form using div tags to separate the label / input rows! That's it! Providing your CSS is up to snuff, the form should look fine already.

But what else is going on here? Two things: a CAPTCHA image is created using the library we referenced earlier, and inserted into an HTML sandwich, which is pre-pended to the CAPTCHA field. What CAPTCHA field?? We've been sneaky here and made a form field ("captcha") on the fly - and then been doubly sneaky by bolting it on with Form Builder's 'before_html' key so that the image appears above the text input field that will be validated by the rules we set earlier.

But wait, there's more! We then loop over the fields from the config array 'fields' index, and append any form errors using the key 'after_html', whilst also adding the submitted value back into the correct input's 'value' attribute. Double whammy!

And that is all contained in $data['form'], which is going to be part of the array we send to the view in Codeigniter fashion like so:

// use Fuel_page to render so it will grab all opt-in variables and do any necessary parsing  
$this->fuel->pages->render($view, $data);   


This view rendering is part of FUEL CMS' Pages class, but it's the same principle as if we used the Codeigniter load->view('view', $data) arrangement.

Thus all we need do in the view itself is echo out the form with:

<?php echo $form; ?>


So we should have:
  1. A nicely constructed form
  2. A captcha image and input field within it
  3. A form that validates itself, and reports errors and displays the submitted values


Wash, rinse, repeat.

Thursday 7 February 2013

The ins and outs of inline editing in FUEL CMS

FUEL CMS automatically allows inline editing of CMS content from the front end of a site. If you are logged in as an administrator,a tool bar will appear on the front end allowing you to edit content such as page titles, meta descriptions and body content there and then, thus making a trip to the admin redundant. Content is made editable via modal dialogue forms. The editable fields are defined in application/config/MY_fuel_layouts.php and the array key defining a layout should match a view file in views/_layouts.

Controllers and CMS content


It is of course possible to create controllers and views that also mix in CMS content - just fetch the CMS fields for a given url and combine the controller content (whatever that might be - from a model, or variables) and make sure the view can show both.

For example a (v1.0) controller like the following:

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

class Rendercms extends CI_Controller {
  
    public function __construct() {
        parent::__construct();
    }
  
    public function index()
    {
        $params;
        $view = 'rendercms/index';
        $vars = array();
        $cms_vars = array();      
        $vars['some_controller_content'] = "Me? I'm controller content!";
        $cms_vars = $this->fuel->pagevars->retrieve($this->uri->uri_string());
        $all_vars = array_merge($cms_vars, $vars);
      
        if($this->input->get('cms') == 'true') {
            $params = array('render_mode' => 'cms');      
        }

        $this->fuel->pages->render($view, $all_vars, $params);  
    }

}

 
combined with a view file (views/rendercms/index.php) such as:

<?php echo fuel_var('body', 'No CMS content here, sorry'); ?>
<hr>
<?php echo $some_controller_content; ?>
<hr>
<?php echo $some_variables_content; ?> 

<hr> 
<p>And I'm humble view HTML</p>
 
can demonstrate the features of FUEL CMS's abilities. The view file will show any CMS content at the url /rendercms (or simply default to 'No CMS content here...'), it can also show the 'some_controller_content' created by the controller, and, as a bonus, it picks up a variable defined in views/_variables/rendercms.php:

<?php

$vars['some_variables_content'] = "I'm only variable content";

?>


So far so good! However, notice the FUEL toolbar on this page (you need to be logged in to the admin, remember, to see this). It's there, but there is nothing it can do to edit the CMS content! Those features, the "toggle editable areas" and "toggle page's publish status" and the rest, are unavailable.

Depending on your needs, this may be only a mild inconvenience. But is there a way round it - a way to get everything!?

The answer is yes, but with some drawbacks.

Everything


In the controller above, you will notice I added a conditional to check for a parameter "cms=true".

Add that parameter to the url (/rendercms?cms=true) and refresh the page (NB make sure in config.php you have allowed GET: $config['allow_get_array']  = TRUE;).

Now only the CMS content is visible, in whatever layout file the CMS has defined (probably 'main' if nothing else). The variables and controller content and even the view file itself are now ignored because the render_mode => 'cms' specification (the default value is 'views'). The 'main' layout file alone is being used, and that, of course, lacks your custom controller content!

So, if there was a layout file that could echo the controller, variable AND CMS content, we'd win!

Make a file ("rendercms.php") in the views/_layouts folder that includes all these variable placeholders eg:

<html>
<head><title><?php echo fuel_var('page_title'); ?></title></head>
<body style="font-family: Georgia;">
<h1>This is a layout file</h1>
<?php echo fuel_var('body', 'No CMS content here, sorry'); ?>
<hr>
<?php echo $some_controller_content; ?>
<hr>
<?php echo $some_variables_content; ?>
</body>
</html>


Now go back to your CMS page in the admin, for "rendercms". In the layout field (you may need to refresh the admin page to reload the options) you should see "rendercms" as a layout. Select it, and save the page. Now when you return and refresh /rendercms?cms=true you should see CMS fields that are editable from the toolbar, controller content and variables content combined.

The additional drawback to this solution, however, is that to make the 'layout variables' fields appear in the admin, you will also need to add an array for "rendercms" in MY_fuel_layouts.php. eg:

$config['layouts']['rendercms'] = $config['layouts']['main'];

Here for example, we copy all the fields for the 'main' layout to the new 'rendercms' layout.

So to make a consistent inline editing experience for the user, where you have CMS & controller content combined, you need to

  1. make a layout file and 
  2. a layout configuration array to support it, in addition to 
  3. the controller, 
  4. view 
  5. (and optionally) the variables file.

It's conceivable though, that you could make just one layout file, and make it contain all the possible controller content it might need to accommodate, eg by adjusting its own layout according to which (controller) url it is responding too. Conceivable, but possibly monstrous! The viability of that solution would depend on the scale, complexity and number of your controllers.

If however inline editing isn't such a priority, the original method outlined above is pretty simple to effect. If you want, that functionality can be moved to MY_Controller, and controllers that combine CMS and their own content can be extended from that.
My top artists