Fuel CMS
Fuel CMS has been around for a long time and is built on Code Igniter 3. Since Code Igniter fell out of favor (even if version 4 is more modern), Fuel CMS can be seen as a legacy CMS, built on an older tech stack. The backend looks a bit dated, and it's definitely not following more modern coding paradigms. But once you got through configuration hell, opened every freaking folder, and stepped through the classes with XDebug, Fuel CMS revealed itself as pretty robust, flexible, and powerful. Database table admin, complicated relationships, and tons of magic methods make developers' lives easy, but you have to go through this configuration hell!
Snippets
Setup a Simple Module with a Legacy Database
Don't bother trying to use Fuels generated posting pages as described here: https://docs.getfuelcms.com/modules/simple#post_pages because it assumes too much of your data! See [Fuel Post Class and Legacy Database Table]
1. Create Model
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
require_once(FUEL_PATH.'models/Base_module_model.php');
class Products_model extends Base_module_model {
protected $key_field = 'legacy_products_id'; // expects 'id' as PK field, else set the tables PK. can be an array if compound key
public $name = 'products';
public $record_class = 'Product'; //adds `_model` magically -> see below.
public $foreign_keys = array(
'category_id' => array(
FUEL_FOLDER => 'fuel_categories_model',
)
);
public $unique_fields = ['slug'];
public $auto_date_add = ['legacy_products_created'];
public $auto_date_update = ['legacy_products_changed'];
public $publish_date_field = 'legacy_products_changed'; // field name for the publish date
public $order_by_field = 'legacy_products_changed'; // field name for the publish date
public $serialized_fields = [
'data'
];
function __construct()
{
parent::__construct('legacy_products');
}
/**
* Refine Formfields
*
* See [Setting Up the Articles Module Form](http://docs.getfuelcms.com/modules/tutorial)
*
* @link <http://docs.getfuelcms.com/modules/tutorial>
* @link <http://docs.getfuelcms.com/general/forms>
* @param array $values field definitions from db schema
* @param array $related ???
* @return array to use in Forms Class
*/
function form_fields($values = array(), $related = array())
{
$fields = parent::form_fields($values, $related);
// Exclude some fields
$this->form_builder->set_params(['exclude' => [
'legacy_products_id',
'legacy_products_produktgruppe',
]]);
// Redefine some fields
$fields = array_replace_recursive($fields, [
'legacy_products_name_de' => [
'label' => 'Produktbezeichnung'
],
]);
// Sort Order of Fields
$sort = [
'legacy_products_id',
'legacy_products_name_de',
'legacy_products_name_en',
];
// Sort fields
$fields = array_merge(array_flip($sort), $fields);
#ddd($fields);
return $fields;
}
}
class Product_model extends Base_module_record {
protected $lang;
public function __construct($table = NULL, $params = NULL)
{
parent::__construct($table, $params);
$this->lang = $this->_CI->fuel->language->detect();
}
}
2. Create Variables
/view/_variables/products.php
3. Create Controller
/controllers/products.php
<?php defined('BASEPATH') OR exit('No direct script access allowed');
class Products extends CI_Controller
{
public $data = [];
public function __construct()
{
parent::__construct();
$this->load->model('products_model');
$this->vars = $this->fuel->pagevars->retrieve('products');
}
/**
* Redirect if needed, otherwise display the user list
*/
public function index()
{
$where = [
'legacy_products_category != ' => 0,
'legacy_products_status =' => 1
];
$limit = null;
$this->vars['products'] = $this->products_model->find_all($where, 'legacy_products_sort', $limit);
// @link https://forum.getfuelcms.com/discussion/3057/advanced-module-and-layout-file/
// @link http://docs.getfuelcms.com/libraries/fuel_pages
$output = $this->fuel->pages->render('index', $this->vars,[
// https://docs.getfuelcms.com/libraries/fuel_pages#fuel_page
'view_module' => 'app',
'language' => detect_lang()
], true);
#ddd($output, $this->products_model);
$this->output->set_output($output);
}
public function detail($id,$type=null)
{
$this->vars['product'] = $this->products_model->find_one(['legacy_products_id =' => $id]);
$output = $this->fuel->pages->render('detail', $this->vars,[
// https://docs.getfuelcms.com/libraries/fuel_pages#fuel_page
'view_module' => 'app',
'language' => detect_lang()
], true);
$this->output->set_output($output);
}
}
4. Wire up Routes
$route['products/(:any).(pdf|json)'] = 'products/detail/$1/$2';
$route['products/(:any)'] = 'products/detail/$1';
Access Language in Base_module_record
class Product_model extends Base_module_record {
protected $lang;
public function __construct($table = NULL, $params = NULL)
{
parent::__construct($table, $params);
$this->lang = $this->_CI->fuel->language->detect();
}
public function title()
{
$method = sprintf('product_%s', $this->lang);
return $this->{$method};
}
}
Using Layout Blocks as Fields in you own Model
The docs assume you are using the CMS when describing Block Layouts. But if you configure blocks that are based on your own model you have to define that model.
$products['headline'] = new Fuel_block_layout('headline');
$products['headline']->set_label('Header');
$products['headline']->set_description('Description');
// Set Model in case you query data from a module.
$products['headline']->set_model('Products_model');
$products['headline']->add_field('headline', ['type' => '']);
// NOTE THIS IS ADDED TO THE 'blocks' key and not the 'layouts' key !!!!
$config['blocks']['headline'] = $products['headline'];
This makes sure, that Fuel pulls the right data from that field in the backend.
Use Select2 Element with Options
If you want to send Options to the Select2 Element you have to create a custom element in fuel/application/config/custom_fields.php
// See \app\fuel\application\config\custom_fields.php:96
// See \app\fuel\application\config\MY_fuel.php:204
// See \fuel\modules\fuel\assets\js\fuel\custom_fields.js:1520
// overwrite Select 2 to add options
// Select2 field
$fields['select2_ajax'] = [
'class' => [FUEL_FOLDER => 'Fuel_custom_fields'],
'function' => 'select2',
'js' => [
FUEL_FOLDER => [
'jquery/plugins/select2.min',
],
],
'js_exec_order' => 1,
'js_function' => 'fuel.fields.select2',
// this adds options to select2
'js_params' => [
'foo' => 'bar',
],
'css' => [
FUEL_FOLDER => [
'select2',
],
],
];
Use Select2 with Ajax
fuel/application/config/custom_fields.php
<?php
/*
|--------------------------------------------------------------------------
| Form builder
|--------------------------------------------------------------------------
|
| Specify field types and other Form_builder configuration properties
| This file is included by the fuel/modules/fuel/config/form_builder.php file
*/
$fields['my_custom_field'] = [];
// Select2 Ajax field
$fields['select2_ajax'] = [
'class' => [FUEL_FOLDER => 'Form_builder'],
// create a hidden field instead of a <select>, because the docs say so:
// @see https://select2.github.io/select2/#data
// this is only true for select2 v3.x
// @see https://stackoverflow.com/a/16345339/814031
'function' => 'create_hidden',
'js' => [
FUEL_FOLDER => [
'jquery/plugins/select2.min',
// custom field init code!
'fuel/my_custom_fields.js'
],
],
'js_exec_order' => 1,
'js_function' => 'fuel.fields.select2_ajax',
'js_params' => [
'foo' => 'bar',
'width' => '80%',
'ajax' => [
'url' => 'https://api.github.com/search/repositories',
'dataType' => 'json',
],
],
'css' => [
FUEL_FOLDER => [
'select2',
],
],
];
include(FUEL_PATH . 'config/custom_fields.php');
/* End of file form_builder.php */
/* Location: ./application/config/form_builder.php */
/fuel/modules/fuel/assets/js/fuel/my_custom_fields.js
fuel.fields.select2_ajax = function(context, options){
console.log('select2_ajax', options);
if (options){
var options = $.extend({}, options);
} else {
options = {};
}
$('.select2_ajax_applied', context).select2('destroy').removeClass('select2_ajax_applied');
$('.select2_ajax', context).addClass('select2_ajax_applied').select2(options);
fuel.fields.inline_edit_field();
}
Init the field in Formbuilder
$field['key'] = [
'type' => 'select2_ajax',
'class' => 'select2_ajax', // the selector in fuel.fields.select2_ajax
'style' => 'width: 300px', // set an initial width
'first_option' => '',
'module' => 'datastore',
'model' => 'datastore',
'model_params' => ['key', 'key_name_de'],
],
Troubleshooting
Getting an Array back from a Model
You are free in naming everything as you like, but you are damned to find all the right vars to make it work then. The best is you database is called products
your Products_model
has classes called class Products_model extends Base_posts_model
and class Products_item_model extends Base_post_item_model
, because the record class (Base_post_item_model
) is expected to be: {Table_name}_item_model
- if not you have to set: public $record_class = 'Products_item';
.
class Products_model extends Base_posts_model {
public $record_class = 'Products_item'; //_model -> see below.
function __construct()
{
parent::__construct('mod_produkte_something');
}
}
class Products_item_model extends Base_post_item_model {
}
Now you can use the magic methods in the frontend!
https://forum.getfuelcms.com/discussion/comment/10671/#Comment_10671
Fuel Post Class and Legacy Database Table
Don't even try! If you have legacy data and don't want to or won't be able to change column names, don't try to get that running. Fuel Post assumes you have the following columns:
id
title
content
publish_date
slug
and you can't map them onto your legacy fields!
https://forum.getfuelcms.com/discussion/3482/using-fuel-posts-class-without-expected-fields
Assets Helper and Custom Upload Folders
By default, Fuel CMS uploads assets in predefined folders as defined in application/config/assets.php $config['assets_folders']
and by using the assets_path()
helper function (or the underlaying Assets method) you are able to use the function like this:
//assets_path(['$file'=NULL], ['$path'=NULL], ['$module'=NULL], ['$absolute'=NULL])
assets_path('myimage.jpg', 'image');
// result: /assets/image/myimage.jpg
But if you upload your image in a custom folder like
[
//'upload_path' => FCPATH . 'assets/'.$this->name,
'upload_path' => FCPATH . 'assets/products',
]
you can't expect /assets/products/myimage.jpg
by using this:
assets_path('myimage.jpg', 'products');
unless you add to application/config/assets.php
$config['assets_folders']['products'] = 'products/';
But that is less flexible, if you upload assets in folders depending on your model (see $name
var in the model).
Instead use it this way:
assets_path('products/myimage.jpg');
Here is a full example how to use the model name in the most flexible way as possible:
require_once(FUEL_PATH.'models/Base_module_model.php');
class Products_model extends Base_module_model {
public $name = 'products';
function __construct()
{
parent::__construct('legacy_products');
}
function form_fields($values = array(), $related = array())
{
$fields = parent::form_fields($values, $related);
// Redefine some fields
$fields = array_replace_recursive($fields, [
'image' => [
'type' => 'file',
'upload_path' => FCPATH . 'assets/'.$this->name,
'replace_values' => $values, // an array of key/value pairs that can be used to replace any placeholder values in the upload path
'file_name' => '{slug}',
],
]);
return $fields;
}
}
class Product_model extends Base_module_record {
protected $lang;
public function __construct($table = NULL, $params = NULL)
{
parent::__construct($table, $params);
$this->lang = $this->_CI->fuel->language->detect();
}
function image()
{
$img = sprintf('%s/%s', $this->_CI->products_model->name, $this->image);
if ($this->image && $this->_CI->asset->asset_exists($img)) {
return $this->_CI->asset->assets_path($img, null, null, true);
}
return null;
}
}
In your view/products.php
use something like the following to access the image.
<?= $product->image() ?>