In the installation section to the documentation, we explained that it was necessary to tell the web server that all calls to the web site should be routed through the index.php in the root. We now need to explain in more detail how URL requests are routed so that content appears on the screen - both page content and AJAX content.


The scripting in index.php is straightforward:


// Sets Root Directory and calls Startup

$rootpath = "http://".$_SERVER['SERVER_NAME']."/";

$basedir = $_SERVER['DOCUMENT_ROOT']."/";


if(file_exists($basedir.'notinstalled')) {

       require_once 'includes/install.php';

} else {

       require_once 'includes/startup.php'; //

}


We explained in installation that if the blocking file, notinstalled is present, the system presumes the instance of Cliqon is not yet installed and proceeds to the wizard installation routine.

Startup

However if the instance of Cliqon is installed, then /includes/startup.php is executed.


// Load Debug

.......


// Miscellaneous functions

require_once $basedir.'includes/functions.php';


// Framework

loadFile('framework/Registry.php');

$clq = Registry::singleton();


// Set System variables here

.......


// Get Site Config(s)

.......


// Database - Redbean R::static or PDO - ODBC with DSN

loadFile('includes/database.php');


// Main Framework handlers

$clq->resolve('Cookie');

.......


// Setup Session with Security

$clq->resolve('Session');

.......


// Routing and Controllers

$routes = $config->cfgReadFile('config/routes.cfg', false);

$clq->resolve('Router');


// L::cLog('Startup Completed - Now Routing');

.......


Router::serve($routes);

// Startup completed


A summarised view of startup.php is shown above. From the erspective of Routing the important things to note are:


    • The execution of all the routines to setup logging and debugging, sessions, database handling, miscellaneous function and all the main Classes are loaded.
    • The Routes array is loaded from the TOML format file routes.cfg.
    • The Router Class is loaded and at the end of the script, the Router method called Serve is executed on the array of routes.


Routes array

The Router Class and Methods are a modified version of Toro Router. In the header of the Routes.Cfg, an explanation is given of how this file may be configured. The notes are shhown in their entirety below but the real routes are summarised.


; Routes File

; file is converted to $routes by Config and flipped

;

; "/" => "SplashHandler",

; "/catalog/page/([0-9]+)" => "CatalogHandler",

; "/product/([a-zA-Z0-9-_]+)" => "ProductHandler",

; "/manufacturer/([a-zA-Z]+)" => "ManufacturerHandler"

;

; class ExampleHandler {

;        function get() {}

;        function post() {}

;        function get_xhr() {}

;        function post_xhr() {}

; }

;

; ':string' => '([a-zA-Z]+)',

; ':number' => '([0-9]+)',

; ':alpha'  => '([a-zA-Z0-9-_]+)',

; ':idiom' => '([a-z]{2})',              


'/install/:idiom/:string' = 'InstallController'

'/page/:idiom/:string' = 'PageController'


; For External and Internal

'/api/:idiom/:string' = 'ApiController'

...........

; Only Internal

'/ajax/:idiom/:string' = 'AjaxController'

...........

; For External and Internal

'/plugin/:idiom/:string' = 'PluginController'

...........

'/admindesktop/:idiom/:string/:string/:string' = 'AdminController'

'/' = 'DefaultController'  


    • Thus a route to display a page for the administration system reads: /admin/en/datagrid/dbcollection/string/
    • A route to display a page on the front-end website would read: /page/en/contactus/
    • A route to GET data from the database might read: /api/es/getgriddata/dbcollection/string/?page=1&limit=10&filter=text&sortby=c_reference
    • A route to POST data to the database might read: /ajax/de/postform/dbitem/news/?ajbuster=123564 + FormData


The Router matches the URL route using regular expression matching and invokes the appropriate Controller.


The routes.cfg file is not overwritten by a Siteupdate. If we introduce any new routes we will advise you via the Newsfeed. This allows you to add and amend routes as needed.

Controllers

Cliqon Controllers are found in /controllers. Some of the Controller Classes extend the base Controller Class. Ibn some PHP frameworks, Controllers are used for all the main scripting processes in the framework. Cliqon Controllers are not programmed in that way. All the Controllers are primarily "invokers" (exceptions are DefaultController and InstallController), that is, in AdminController, as an example, presumes that the Controller will invoke a named method in the Admin Class. Similarily the PageController will invoke a named Method in the Cms Class.


There are several types of Controller:


    • Template renderers - AdminController, DefaultController, InstallController, PageController and DefaultController
    • JSON renderers - ApiController and AjaxController
    • AppController which invokes a Third Party "app" such as Adminer from the /apps subdirectory
    • PluginController which is for developers to use


The Router function can make a difference between different types of Request methods - GET, POST, XhrGET and XhrPOST (plus PUT and DELETE), however Cliqon does not explicitly need to use them. They are however left in the Controllers for completeness.

Render Templates

Let us consider the front-end website PageController. A summarised version is shown below.


class PageController

{

       private $cfg, $idiom, $page, $rq = [];


       function get($idiom, $page)

       {

               global $clq;

               $this->cfg = $clq->get('cfg');

               $idiom = '' ? $this->idiom = $this->cfg['site']['defaultidiom'] : $this->idiom = $idiom;                

               $clq->set('idiom', $idiom);

               $this->page = $page;

               $cms = $clq->resolve('Cms');

               method_exists($cms, $page) ? $method = $page : $method = "page";


               // Load Template Engine

               $tpl = new Engine(new FilesystemLoader($clq->get('basedir')."views"), $clq->get('basedir')."cache");                

               $template = $this->page.'.tpl';

               $vars = [

                       'viewpath' => $clq->get('rootpath').'views/',

                       'includepath' => $clq->get('rootpath').'includes/',

                       'page' => $this->page,

                       'cmscontent' => $cms->$method($idiom, $_REQUEST),

                       'cfg' => $this->cfg,

                       'idiom' => $this->idiom,

                       'scripts' => $clq->get('js'),

                       'rq' => $_REQUEST

               ];        

               

               echo $tpl->render($template, $vars);

               // or

               // $var = $tpl->publishtpl($template, $vars);        

       }

       ............

}


As this is the front-end multi-lingual Page Controller, we need to set the language of the page if it has not been set before. Once set, we can get to the body of the activity. PageController assumes that Class that contains all the Methods for the main website is contained in the Cms Class, so it instantiates that class. It then checks to see if the page method exists ( such as contactus() ). If it does, great, if not it presumes a method called page().


The Template engine is loaded with a template that is presumed to carry the same name as the page. It should be obvious to any PHP develop how to modify these routines to their own purposes. As explained in the previous section on Templates, various variables are setup including  $cmscontent and $scripts. It should be appreciated that using such a mechanism imposes no limitation as to what can be passed to the template. Logically the $scripts variable will contain Javascript data and functions. The $cmscontent could be a string of generated HTML content, an array of generated data or a combination of both.


The current structure of Cliqon presumes that the Template is processed and "echoed" to the Client. For completeness, we have included the instruction "publishtpl()" which visualises that the template is rendered to a string variable. What scripting and instructions are contained in Cliqon Razr templates is dealt with in more detail in System Build and Cook Book, suffice it to say that Cliqon presumes that an HTML page complete with all of the headers and meta instructions, are echoed to the Client.

JSON Templates

Let us consider the ApiController. A summarised version is shown below:


loadFile('controllers/Controller.php');


final class ApiController extends Controller

{

       protected function api_exec($idiom, $action, $table = "dbcollection", $tabletype = "")

       {

               try {

                       global $clq;

                       $api = $clq->resolve('Api');

                       $lcd = $clq->set('idiom', $idiom);

                       $clq->set('lcd', $lcd);

                       $rq = $this->inputs();


                       if($action != 'login') {if(!$_SESSION['CliqonAdminUser']) {

                               throw new Exception("Not a valid user!");

                       }};


                       method_exists($api, $action) ? $method = $action : $method = "apidefault";

                       $vars = [

                               'idiom' => $idiom,

                               'table' => $table,

                               'tabletype' => $tabletype,

                               'rq' => $rq,

                       ];

                       $result = $api->$method($vars);


                       // Development

                       $msg = [

                               'method' => $method,

                               'table' => $table,

                               'tabletype' => $tabletype,

                               'idiom' => $idiom,

                               'request' => $this->inputs()

                       ];

                       // L::cLog($msg);


                       if($result['callBack'] != "") {

                               F::echoJsonp($result['content'], $result['callBack']);

                       } else {

                               F::echoJson($result['content']);

                       }        


               } catch (Exception $e) {

                       

                       $err = [

                               'errmsg' => $e->getMessage(),

                               'action' => $action,

                               'table' => $table,

                               'tabletype' => $tabletype,

                               'idiom' => $idiom,

                               'request' => $this->inputs()

                       ];

                       L::cLog($err);

                       F::echoJson($err);

               }                

       }


       function get($idiom, $action, $table = "dbcollection", $tabletype= "") {

               global $clq; $clq->set('model', 'clean');

               return $this->api_exec($idiom, $action, $table, $tabletype);

       }

       function getxhr($idiom, $action, $table = "dbcollection", $tabletype= "") {

               global $clq; $clq->set('model', 'clean');

               return $this->api_exec($idiom, $action, $table, $tabletype);

       }

       function post($idiom, $action, $table = "dbcollection", $tabletype= "") {

               return $this->api_exec($idiom, $action, $table, $tabletype);        

       }

       function postxhr($idiom, $action, $table = "dbcollection", $tabletype= "") {

               return $this->api_exec($idiom, $action, $table, $tabletype);        

       }


}


Firstly, the ApiController extends the base Controller but may not currently be extended itself. Information provided about the PageController also applies in respect to Request type and language handling. The ApiController calls Class Api and the method called $action. Method apidefault() contains an error message. Arguments for the method are recombined, before passing them to the method. The return from an Api method is expected to be an array which will be converted to same domain or cross domain JSON depending on the nature of the AJAX call.


The REQUEST is sanitised by the cleanInputs() methods in the Controller Class before being passed to the Api Class and Methods.


Please note the following important statement. We do not expect that the production version of Cliqon, as supplied, will be used in a cross domain situation. We cannot visualise that operators could realistically enter new articles and the like from smartphones. Thus the calls to the ApiController from Cliq.Js, do not implement crossdomain callbacks. We confidently expect that developers who create third party modules and applications may want to do so. Thus the code and scripting operates as an example of how it might be achieved and could itself be modified for a specific purpose. When we come to look at the AJAX calls in Cliq.Js, we will document how the change should be made. Developers may also want to protect this type of cross domain call with additional security, which we have allowed for with the routines for JSON Web Tokens and the key generation routines.

Classes and methods called

To complete the process, we need to consider the different types of data or ciontent returned to the Controllers.

Content

Let us consider a call by the PageController to a method in class Cms. This is an extremely simple example but it demonstrates the points:


function page($idiom, $rq)

{

$js = "";

global $clq; $clq->set('js', $js);

return "Default Content in ".$idiom." language";

}


Method page() receives two arguments - language code and the REQUEST - it consumes one of these arguments. We choose to use a Setter and Getter to deal with any Javascript we want the Method to put on the rendered page. This is not essential but through experience we have discovered that this is the cleanest and safest way to get complex Javascript code and functions through the Regular Expression parser in the template engine. This assumes that you want to create Javascript code that way! You could "require" the Javascript files or use Phery etc.


You will recall that the return from the Page Method populates a template variable called $cmscontent. And in the summarised version of /views/page.tpl below, you can see how the HTML string content that the Method has generated is consumed by the Template.


@include('partials/header.tpl')

@include('partials/cookieconsent.tpl')

</head>

<body class="landing-page">


   @include('partials/nav.tpl')


   <div class="wrapper">

       <div class="page-header page-header-small">

           <div class="page-header-image" data-parallax="true" style="background-image: url('@($viewpath)img/bg6.jpg');">

           </div>

           <div class="container">

                       .........

           </div>

       </div>

       <div class="section section-about-us">

           <div class="container">

               <div class="row">

                   <div class="col-md-8 offset-md-2 text-center">

                       <h2 class="title">@(Q::uStr('2:What is Cliqon?'))</h2>

                       <h5 class="description">@(Q::uStr('3:Cliqon is a web app ....'))</h5>

                   </div>

               </div>

               <div class="separator separator-primary"></div>

               <div class="content">@raw($cmscontent)</div>

           </div>

       </div>

       @include('partials/footer.tpl')


   </div>


       <script>

               @raw($scripts)

       </script>


<!-- End of Page -->

@include('partials/end.tpl')

Data

Let us consider a call by ApiController to to a method in class Api. This is an extremely simple example but it demonstrates the points:


function getrowdata($vars)

{

       global $clq; $db = $clq->resolve('Db');

       return [

       'content' => D::getRowData($vars),

       'callBack' => $vars['rq']['callback']

       ];        

}

The following example is a made up example as the real Method is much more complex


static function getRowData(array $vars)

{

try {


               $rq = $vars['rq'];

               $sql = "SELECT * FROM $vars['table'] WHERE id = ?";

               $row = R::getRow($sql,[$rq['id']]);


               return ['flag' => 'Ok', 'data' => $row];


} catch(Exception $e) {

$err = [

'method' => self::THISCLASS.'->'.$method,

'errmsg' => $e->getMessage()

];

L::cLog($err);

return ['flag' => 'NotOk', 'msg' => $err];

}            

}


You will observe how a an AJAX call (using aja.js) with a URL string: /api/en/getrowdata/dbitem/news/, data of {id: recid} and callback {callback:'ojored'} generates a call to Method getrowdata() in Class Api{}.


The Callback is dealt with inside of the Api class, but the rest of the action uses the Class Db{} and the Method getRowData().


As a tradition within our Cliqon AJAX handling, we return an response array with either an Ok or NotOk flag plus either a stream of data or an error message.


Created with the Personal Edition of HelpNDoc: Qt Help documentation made easy