Hi,

People have noticed (and some have complained about) the dearth of 'developer' info for those willing to get their hands dirty and delve into making custom addons etc.

Although this shouldn't be surprising for a CMS that focuses on a non-coder audience, I'll try to post here on topics that would appeal to those interested in hard core development.

As the first post, following is something about how the routes in admin-panel work.
Some time back @trendoman inquired about this for some use-cases he had in mind. I am quoting him verbatim -
I would like to create several 'spaces' in the backend -

(a) a greeting area, where admins/superadmin will be welcomed to CouchCMS and perhaps given instructions. Transferring that from install to install requires visiting 'greeting.php' as superadmin. I would like to skip that part.

(b) Configuration space where I will edit CMS settings - I can do that now only after registering a template 'config.php'.

(c) Htaccess edit screen - again, I must create a template for that instead of dropping a custom addon for that into addons or a folder in a custom theme.

(d) Dashboard with some server/user/CMS stats gathered on a single screen - "you have 145 posts in your blog, here is an admin link to create a new post".

(e) Space with help pages

(f) Some other spaces for my experiments - list and create/edit/remove templates from backend, edit snippets codes etc.

(g) A chat app available from the ground up install of CMS (if custom theme or chat addon enabled) - all without user placing snippets and registering template first.

So, I believe that if I could avoid creating a physical template file for each of 'space', that would be awesome and allow us to take further control over what we see in backend. Will this be too difficult?

Following was my reply (again verbatim) -
=================================
Hi,

I am attaching a very simple addon that should serve to clarify how routes and sidebar links work with addons.
config.zip
(2.85 KiB) Downloaded 621 times

The name of this addon is 'config' and therefore the folder has been named 'config' (just a convention followed by all code Couch addons).
If you take a look into the folder, you'll find two files - both named after the addon; one is 'config.php' and the other is 'edit-config.php'( once again, this is only a convention used by all core addons).

'config.php' - this is the file the path of which is included in kfunctions.php to activate the addon. It serves as the single entry point into the addon code and registers all routes, sidebar links etc.

'edit-config.php' is the actual file where all the action lies.

The purpose of using two files is to keep the code that is included into each run of PHP minimal - the bulk of the addon's code is kept in the main (edit-xxx.php) file while the stub (xxx.php) file included in kfunctions.php contains only the code that is needed at each run (e.g. sidebar links and routes). Once a route that requires the main file is selected, that file gets automatically included.

Apart from these two files, you'll also find a folder named 'theme' - this, again by convention, holds all HTML snippets used to render the addon's output.

Let us begin by taking a look at the entry file 'config.php'.
Begin by taking a look at its very bottom where you'll find the following event handlers -
Code: Select all
    // Register UDFs, event handlers, routes etc.
    // routes
    if( defined('K_ADMIN') ){
        $FUNCS->add_event_listener( 'register_admin_routes', array('MyConfig', 'register_routes') );
        $FUNCS->add_event_listener( 'register_admin_menuitems',  array('MyConfig', 'register_menuitems'), -10 );
    }

The admin-panel upon booting fires these two events to ask all addons for the routes and sidebar menuitems they wish to register.
Our addon is using two static functions (from the 'MyConfig' class contained in the file we are examining) to register those.

Let us take a look at the function registering the routes -
Code: Select all
    // routes
    static function register_routes(){
        global $FUNCS;

        $FUNCS->register_route( 'greeting', array(
            'name'=>'edit_view',
            'path'=>'',
            'include_file'=>K_ADDONS_DIR.'config/edit-config.php',
            'class'=> 'MyConfigAdmin',
            'action'=>'greeting',
            'module'=>'config', /* owner module of this route */
        ));

        $FUNCS->register_route( 'dashboard', array(
            'name'=>'edit_view',
            'path'=>'',
            'include_file'=>K_ADDONS_DIR.'config/edit-config.php',
            'class'=> 'MyConfigAdmin',
            'action'=>'dashboard',
            'module'=>'config', /* owner module of this route */
        ));
    }

As you can see, it is registering two routes (both named 'edit_view' - an arbitrary choice).
One route is for a 'masterpage' named 'greeting' and the other for a 'masterpage' named 'dashboard'.
Please notice that unlike the masterpages we register through frontend Couch templates, these two masterpages do not have a '.php' suffix.

When we access any screen in the admin panel, notice that the URL always contains a parameter named 'o' - this is the masterpage's name we specify while registering the routes.
For frontend templates, the URL might look like -
http://localhost/couch/?o=blog.php&q=list


Whereas for non PHP template masterpages (e.g. the 'users', 'comments' and 'drafts' sections the URL would be like -
http://localhost/couch/?o=users&q=list


Although we are defining only a single route each for our two masterpages, a masterpage can have multiple routes e.g. it is normal for all PHP templates to have routes named 'list_view', 'edit_view', 'create_view' etc.
What differentiates one route from any other of the same masterpage (apart from their names, of course) is the 'path' parameter we use while registering the routes.

[
If it interests you, you may examine the 'couch/theme/_system/register.php' file to see how Couch registers routes for all frontend masterpages and the core 'users', 'comments', 'drafts' sections mentioned above).

Also, you might find the discussion on routes at https://www.couchcms.com/docs/custom-routes/ useful. While the mentioned discussion pertains to frontend routes, the core principles remain exactly the same with admin-panel routes too.
]

The 'q' parameter in the URLs above contains what we specify in the 'path' parameters of the registered routes.
In the two routes we are registering, the 'path' parameter is empty so our two URLs would end us being as follows -
http://localhost/couch/?o=greeting
http://localhost/couch/?o=dashboard


Coming to the other parameters used while defining the routes -
the 'action' parameter specifies which function to invoke when the route gets selected.
If the 'class' parameter is also specified, it tells Couch that the function is contained in a PHP class that needs to be instantiated first.
So, in our case, Couch will first do a 'new MyConfigAdmin();' and then invoke 'greeting()' method for the first route.

Had the 'class' parameter not been specified, Couch would have tried to invoke 'greeting()' directly (and, most likely, failed).
On the other hand, had the definition been as follows -
Code: Select all
'action'=>array('MyConfigAdmin', 'greeting'),

Couch would have tried to invoke a 'static' function named 'greeting' from the 'MyConfigAdmin' class (i.e. not instantiate the class).

Since the class and function specified in the route is contained in a separate file, the 'include_file' parameter instructs Couch to first include that file before trying to invoke the function.

If you have activated this addon, you may try invoking those functions by typing the URLs mentioned above manually in the browsers address bar.
For better accessibility, however, usually we provide a link to those URLs in the sidebar.

That is what the second method in our MyConfig does -
Code: Select all
    // links from sidebar
    static function register_menuitems(){
        global $FUNCS;

        $FUNCS->register_admin_menuitem(
            array(
                'name'=>'greeting',
                'title'=>'Greeting',
                'weight'=>'-10',
                'icon'=>'chat',
                'parent'=>'_templates_',
                'route' => array( 'masterpage'=>'greeting', 'name'=>'edit_view' ),
            )
        );

        $FUNCS->register_admin_menuitem(
            array(
                'name'=>'dashboard',
                'title'=>'Dashboard',
                'weight'=>'-9',
                'icon'=>'document',
                'parent'=>'_templates_',
                'route' => array( 'masterpage'=>'dashboard', 'name'=>'edit_view' ),
            )
        );
    }

The important part in the code above is the 'route' parameter which automatically generates the URLs given the required info about the routes.

OK, so now we can move on to take a look at the functions that are invoked upon hitting our routes.
As we know, those are defined in the 'edit-config.php' file so let us examine that.

You may try placing the following in that file and the routes would still work -
Code: Select all
<?php
    if ( !defined('K_ADMIN') ) die(); // cannot be loaded directly

    class MyConfigAdmin{

        // actions
        function greeting(){
            $html = 'hello!';

            return $html;
        }

        function dashboard(){
            $html = 'dashboard!';

            return $html;
        }
    } // end class

As you can see, essentially it consists of nothing more than the two 'action' functions we specified in the registered routes.

So at this point, you may use any code you wish to return the HTML to be outputted.
It could be using raw PHP or, as most of the core addons do, we may use Couch snippets.

By making the following amendment, we can now use regular snippets placed in the 'theme' folder of the addon to generate the output -
Code: Select all
<?php
    if ( !defined('K_ADMIN') ) die(); // cannot be loaded directly

    class MyConfigAdmin{

        // action
        function greeting(){
            global $FUNCS, $CTX;

            $html = $this->_embed( 'greeting.html' );

            return $html;
        }

        // action
        function dashboard(){
            global $FUNCS, $CTX;

            $html = $this->_embed( 'dashboard.html' );

            return $html;
        }

        function _embed( $snippet ){
            $filepath = K_ADDONS_DIR.'config/theme/' . ltrim( trim($snippet), '/\\' );
            $html = @file_get_contents( $filepath );
            if( $html===FALSE ) return;

            $parser = new KParser( $html );
            return $parser->get_HTML();
        }
    } // end class

As you can figure out, it is akin to using 'custom_admin_views' where we use snippets to render the listing or the form.

For example, is we place the following in the 'dashboard.html' snippet (making sure to set a valid 'masterpage' parameter for the cms:form and a valid name for the bound cms:input first), we should be able to edit an existing template -
Code: Select all
<cms:if "<cms:get_flash 'submit_success' />" >
    <cms:admin_add_js>
        $(function() {
            toastr.success("Your changes have been saved", "Success", { // TODO: localize
                "positionClass": "toast-bottom-right",
                "showDuration": "250",
                "hideDuration": "250",
                "timeOut": "2500",
                "extendedTimeOut": "500",
                "icon": '<cms:show_icon 'check' />'
            });
        });
    </cms:admin_add_js>
</cms:if>

<cms:form
    masterpage = 'some-masterpage.php'
    mode = 'edit'
    enctype = 'multipart/form-data'
    method = 'post'
    anchor = '0'
    add_security_token = '1'
    id = 'k_admin_frm'
    name = 'k_admin_frm'
    >

    <div class="tab-pane fade active in" id="tab-pane-edit-<cms:show k_route_module />">
   
        <cms:if k_success >
           
            <cms:db_persist_form />
           
            <cms:if k_success >
                <cms:set_flash name='submit_success' value='1' />
                <cms:redirect k_qs_link />
            </cms:if>
        </cms:if>

        <cms:if k_error >
            <cms:show_error>
                <cms:each k_error >
                    <cms:show item /><br>
                </cms:each>
            </cms:show_error>
        </cms:if>
       
       
        <!-- custom editable regions begin -->
        <div class="field k_element">
            <label class="field-label"><span>Field Name</span></label><br>
            <cms:input type='bound' name='some-field'  />
        </div>
        <!-- custom editable regions end -->
       
       
        <div class="ctrl-bot">
            <!-- buttons -->
            <a class="btn btn-primary" id="btn_submit" href="#" onclick="$('#btn_submit').trigger('my_submit'); $('#k_admin_frm').submit(); return false;">
            <span class="btn-icon"><cms:show_icon 'circle-check' /></span>Save</a>
        </div>
       
        <input type="hidden" id="k_custom_action" name="k_custom_action" value="">
    </div>
</cms:form>


At this point you know how the routes and sidebar items work.

The snippet we used to render the form above would have, however, made one thing apparent -
while it is 100% possible to craft a form that is identical to those you see in the default edit screens of the admin-panel, it would be a chore to output all the fields (particularly the type 'group' and 'row').

To make this part more manageable and flexible to changes, the class defined in 'edit-xxx.php' of all Couch core addons extends 'KBaseAdmin' (defined in 'couch/base.php'). You might find it useful to study e.g. 'couch/edit-pages.php' and how it piggybacks on 'base.php'.
Point being, use of 'KBaseAdmin' is not essential but it makes things considerably easier.

How that works, however, is something you'll have to figure out yourself by using a debugger - it would too extensive a task to go line by line for me.

Finally, from your PM, it appeared that one requirement was to avoid using a physical PHP template to create editable regions.
While that is certainly possible (e.g. the 'users' core module does this by defining fields through PHP),, it would be extremely cumbersome compared to using the <cms:editable> tags.

One suggestion that comes to my mind is that instead of using several templates, you may define all regions in one single template and then create several different 'virtual' masterpages as we did above. In each of those, you then edit/use the same template but with a different subset of fields (condition fields can come in handy here if 'required' fields are involved).

This way you'll only need to visit a single template to register editable regions for all the virtual masterpages you are planning to add to the sidebar.

How does that sound? Let me know.

Hope you find this tutorial useful.
=============================
Hope others do too :)