Blog Archive

Thursday 3 May 2012

Attaching documents to pages in FUEL CMS

The FUEL CMS user guide has a good example tutorial of creating a one to many relationship between simple modules. Recently I tried to do something similar, but using the FUEL admin model for pages, such that you could attach multiple objects (documents in this example) to a page in the admin, using a combo select widget, and display them on the site.

Since this wasn’t as straightforward as I’d hoped, I thought I’d jot down the procedure. I’ve tried to avoid adding all the features you’d need to do this as a real-world project, and have instead concentrated on the essentials. This tutorial relates to v0.93 of FUEL CMS.

Step one

The obvious starting place is to create a couple of database tables: “documents” and “documents_to_pages”.

CREATE TABLE `documents` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `summary` varchar(255) DEFAULT '',
  `file` varchar(255) NOT NULL DEFAULT '',
  `publish_date` datetime,
  `author` varchar(255) NULL default '',
  `tags` varchar(1024) NULL default '',
  `date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `active` enum('yes','no') DEFAULT 'no',
  PRIMARY KEY (`id`),
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `documents_to_pages` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `page_id` int(11) NOT NULL,
  `document_id` int(11) NOT NULL, 
  PRIMARY KEY (`id`),
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

These are just examples, you may want more or less fields, or a different charset. The "documents" table will hold the file details, including a publishing date and file name, and the “documents_to_pages” is a plain vanilla look-up table to relate the key (id) of the document against the key (also named id) of the fuel_pages table (this is FUEL’s core page table - many of the layout fields are stored in “fuel_page_variables”).

Once these are added to your FUEL database schema, we can go on with the models for the 2 tables, and set about the model for pages.

Step two

Here’s the document model in full:

   1:  <?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
   2:   
   3:  require_once(FUEL_PATH.'models/base_module_model.php');
   4:   
   5:  class Documents_model extends Base_module_model {
   6:       
   7:      public $record_class = 'Document';
   8:       
   9:      function __construct()
  10:      {
  11:          parent::__construct('documents');
  12:      }
  13:    
  14:      function form_fields($values = array())
  15:      {
  16:          $fields = parent::form_fields($values);
  17:          $upload_path = assets_server_path('_uploads/documents/');   
  18:          $fields['file_upload'] = array('type' => 'file',
  19:                          'upload_path' => $upload_path, 'overwrite' => FALSE);
  20:          return $fields;
  21:      }
  22:       
  23:      function on_after_delete($where)
  24:      {
  25:          $CI =& get_instance();
  26:          $CI->load->model('documents_to_pages_model');
  27:          if (is_array($where) && isset($where['id']))
  28:          {
  29:              $where = array('document_id' => $where['id']);
  30:              $CI->documents_to_pages_model->delete($where);
  31:          }
  32:       }
  33:  }
  34:   
  35:  class Document_model extends Base_module_record {
  36:   
  37:  }

Mostly this is standard FUEL CMS fare. In the form_fields() method we set an upload path where the file will be stored – you may want this to be different. We also configure the “dummy”upload field (dummy in the sense it doesn’t exist in the database, but by convention, it will store the file name in the field "file").

Using the on_after_delete() hook, we can erase any look-up records related to the deleted document. Not necessary but tidy!

Step three

The "documents_to_pages_model" can look like this:

   1:  <?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
   2:   
   3:  class Documents_to_pages_model extends MY_Model {
   4:   
   5:      public $record_class = 'Document_to_page';
   6:   
   7:      function __construct()
   8:      {
   9:          parent::__construct('documents_to_pages');
  10:      }
  11:   
  12:      function _common_query()
  13:      {
  14:          $this->db->select('documents_to_pages.*, documents.title');
  15:          $this->db->join('documents', 'documents_to_pages.document_id = documents.id', 'left');
  16:          $this->db->join('fuel_pages', 'documents_to_pages.page_id = fuel_pages.id', 'left');        
  17:      }
  18:   
  19:   function getItemsByPage($page_id) 
  20:  {
  21:        $this->db->select("documents.id, documents.title, documents.file, documents.summary, 
             DATE_FORMAT(documents.publish_date,'%Y-%m-%d'), 
             UPPER(REVERSE(SUBSTRING_INDEX(REVERSE(documents.file),'.',1))) AS file_type", FALSE);
  22:        $this->db->join("documents", "documents_to_pages.document_id = documents.id", "left");
  23:        $this->db->join("fuel_pages", "documents_to_pages.page_id =  fuel_pages.id", "left");
  24:        $this->db->where("documents.active", "yes");
  25:        $this->db->where("documents.publish_date <=", "CURRENT_TIMESTAMP", FALSE);
  26:        $this->db->where("documents_to_pages.page_id", $page_id);
  27:        $result = $this->db->get("documents_to_pages");
  28:        return $result;
  29:        }
  30:  }
  31:   
  32:  class Document_to_page_model extends Data_record {
  33:  }

Nothing much unusual here. The getItemsByPage() method will be used to display a list of records on a given page. The active record methods (select, join, where) ensure the records are active and published.

Step four

Next we need to modify the pages_model.php file found in the Fuel directory (/fuel/modules/fuel/models/). We have to add the combo select field which will allow files to be selected and “attached” to the page. To do this, we edit the form_fields() method:

   1:  function form_fields($values = array())
   2:  {
   3:      $related = array('documents' => 'documents_to_pages_model');
   4:      $fields = parent::form_fields();
   5:      $fields['date_added']['type'] = 'hidden';
   6:          
   7:  // ******************* NEW RELATED DOCUMENT FIELD BEGIN 
   8:  // NB pages controller needs updating as of v0.93 to ensure $values is passed to form fields
   9:  // eg $fields = $this->model->form_fields($saved);
  10:      $CI =& get_instance();
  11:      $CI->load->model('documents_model');
  12:      $CI->load->model('documents_to_pages_model');
  13:      $document_options = $CI->documents_model->options_list('id'
           'title', array('active' => 'yes'), 'title');
  14:      $document_values = (!empty($values['id'])) ? 
           array_keys($CI->documents_to_pages_model->find_all_array_assoc('document_id'
           array('page_id' => $values['id']))) : array();
  15:      $fields['documents'] = array('label' => 'Documents', 'type' => 'array',  
           'class' => 'add_edit documents', 'options' => $document_options,  
           'value' => $document_values, 'mode' => 'multi');
  16:  // ******************* NEW RELATED DOCUMENT FIELD END
  17:      
  18:      $yes = lang('form_enum_option_yes');
  19:      $no = lang('form_enum_option_no');
  20:      $fields['cache']['options'] = array('yes' => $yes, 'no' => $no);
  21:      return $fields;
  22:  }

The comments indicate the new field "documents" we are inserting into this method, but line 3 also indicates the relationship with the documents_to_pages_model. The values of this new field will be whatever is retrieved with the find_all_array_assoc() method of the MY_Model class we extended with the Documents_to_pages_model defined earlier. Note that there is a comment about the pages controller on line 8. It's commonly the case that the form_fields() argument $values contains the saved form data, but as of v0.93 of FUEL, this isn’t so of the pages model. So we must add the argument $values = array() as per line 1, and then the pages_controller also needs to be edited to send that argument in the first place, and that’s simple enough to do. Open up /fuel/modules/fuel/controllers/pages.php and find this line (line 117 of the current code):

$fields = $this->model->form_fields();

and change it to:

$fields = $this->model->form_fields($saved);

Easy!

We have not finished with the pages_model however. We add this hook:

   1:  function on_after_save($values)
   2:  {
   3:      $data = (!empty($this->normalized_save_data['documents'])) ? 
           $this->normalized_save_data['documents'] : array();
   4:      $this->save_related('documents_to_pages_model'
           array('page_id' => $values['id']), array('document_id' => $data));        
   5:  }

This inserts the document id's and page id into the look-up table. And then we should delete related look-up records when a page is deleted, so we add a line to the on_after_delete() hook:

   1:  function on_after_delete($where)
   2:  {
   3:      $this->delete_related(array(FUEL_FOLDER => 'pagevariables_model'), 'page_id', $where);
   4:      $this->delete_related(documents_to_pages_model, 'page_id', $where);
   5:  }

Line 4 is the addition.

Step five


The admin will not display the new document model yet, not until this line is added to MY_fuel_modules.php (located in application/config):

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

It should now be possible to add some test documents to the documents module in FUEL's admin. Once you have done that, take a look at the pages module too, and create or edit a page - you should see the combo select widget with the files you added to the documents module on the left. Use the combo and add one or more documents to the right hand side, and hit save. You should now see the saved files remain on the right hand selection area.

That’s about all on this topic! Bear in mind that the lookup table could be made more generic, such that documents could be associated with any module (not just pages), or even to be a store for the relationship of any module to any other module. I’ll also leave aside how to display the related documents, but that’s relatively straightforward.

Thanks to David McReynolds, lead developer of the FUEL CMS project, for helping me out with some of the issues I encountered. The forums for FUEL are massively helpful, and I recommend visiting there for help and advice.

No comments:

Post a Comment

My top artists