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',
        ],
    ],
];

Source

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() ?>