Node

Node is an universal container class provided by CodKep to make it easy for managing different kind of data objects same way. The programmer can specify node types which identified by a "nodetype" string. Once the node type is registered in CodKep the node module provides interfaces for add delete edit or view this typed nodes.

Every node has a node identifier nid which unique between nodes and can used to access the node.

The CodKep provides built-in routes to manage nodes

Examine the CodKep users as example, because the CodKep users are also nodes with node type: user
Accordingly we can create a new users on the following url: node/user/add
The admin user usually got the nid = 1 value. If so you can edit the admin user data on the following url: node/1/edit

The programmers side the nodes are objects (with Node or Node subclass type) which can be load/save/remove uniform way and the fields of the defined node type are accessible as object properties. You can get or set these object properties freely. The changed properties can be stored into database by single save() or insert() method. You can read about the database appearance later.

Every node can contains data fields which are specified by the type of node. You can set and read this fields as object properties of node.

Create a new user with password from php codes:

$n = node_create('user');
$n->name = 'Sample Sam'; //set the node's name field
$n->login = 'sam';       //set the node's login field
$n->password = scatter_string_local('secret1');
$n->insert();

Fixed properties(fields) of the Node

Every field which defined in the specified node type are accessible as object properties of the node object. Beside this user defined properties the node has same built-in properties which are available in all typed node.

Built-in properties of node object:
(The $node is only a sample object variable here))

Methods of Node class

Constructor:

Static methods:

Methods:

Note 1: By default the save, insert and remove methods do their sql works in a separate sql transaction. They starts a transaction inside and commits after the work. In case you would like to call this methods in an already started transaction (to enclose the whole of your work included this opertaion in a large transaction) you have to set the $skip_transaction parameter to true.

Methods which does nothing (or do a minimal basic operations) in Node class. They can redefined in subclasses to achieve some special functions. See custom node object chapter:

Helper functions of node class

node_create($type)
Create and return an "empty" node with a specified node type. The returned node can be inserted to the database after the properties is set.

// Create an empty node and filled with values
$n = node_create("task");
$n->description = "Do something";
$n->cost = 22;
$n->deadline = "2017-02-11";

//Saved to the database and prints a node view link
$nid = $n->insert();
print "The task is saved, see:" . l('View','node/'.$nid);

node_load($nid)
Load and return the node from the database by NID. It returns the appropriate node object if success, otherwise null;

//Loads a node by NID
$n = node_load($nid);
//Increase the "value" by 10
$n->value = $n->value + 10;
//Save to the database
$n->save();

node_load_intype($join_id,$type)
Load and return the node from the database by NODETYPE and JOIN_ID. It returns the appropriate node object if success, otherwise null;

//Print the name of the user $uid
$u = node_load_intype($uid,'user');
print "The name of the user is " . $u->name;

node_delete($nid)
Delete the specified node.

sql_table_of_nodetype($type)
Returns the sql table creation string of the node (CREATE TABLE)

node_get_definition_of_nodetype($type)
The function returns the data definition structure of the node type in $type parameter. Returns null if not found.

node_get_field_array($type,$sqlname)
The function search and returns the definition array part of the field of the node type in $type parameter according to the $sqlname parameter. Returns empty array if not found.

node_get_field_attribute($type,$sqlname,$attributename)
The function search and returns the specified attribute of specified field of the node type in $type parameter according to the $sqlname and $attribute parameter. Returns null if not found.

node_get_field_display_value($type,$sqlname,$value)
The function converts the $value parameter to a display value in node type $type in field $sqlname. Returns the passed value unchanged if not success.

Defining a node

CodKep modules can define node types by HOOK_nodetype hook. In case you intent to define a new node type, you have to implement HOOK_nodetype in your module and return a SpeedForm's data definition structure array with the index name of the defined node type string.

Note: You can also use the SpeedForm builder to assemble this node definition array.

An example, which define the "sampletype" node type in "mymodule" module:

function hook_mymodule_nodetype()
{
  $d = [];
  $d['sampletype'] =  [
                "name" => "samplenode",
                "table" => "sample",
                "show" => "table",
                "rest_enabled" => "crudl", //Enable full REST api
                "fields" => [
                        10 => [
                            "sql" => "id",
                            "type" => "keyn",
                            "hide" => true,
                        ],
                        20 => [
                            "sql" => "name",
                            "text" => "Name",
                            "type" => "smalltext",
                            "check_noempty" => "Cannot leave empty",
                            "color" => "#ddddff",
                        ],
                        100 => [
                            "sql" => "add_btn",
                            "default" => 'Add',
                            "type" => "submit",
                            "in_mode" => "insert",
                            "centered" => "yes",
                        ],
                ],
            ];
  return $d;
}

The detailed documentation of data definition structure array is located here.

The index number of the fields are influence the sort of the fields. (The fields are sorted ascending before using)

Note: A defined node is automatically registers the necessary sql schema requirements, so after defining a node type or alter something it is strongly recommended to revisit the CodKep's sql schema check page to meet the possible new requirements. The schema editor page in documentation.

Note2: After you created a new node type you also have to specify the permissions of users on this node type. See this chapter

Dynamic node definition

Your site may use many node types, which uses a lots of resources to load. Usually a page load does not use all node types defined by your modules. To save loading time the node module has a dynamic node type loading method, which only loads the necessary definitions. You can put each node type definition to separated files, and the CodKep only loads the necessary file if the specific node type is used. This subsystem uses php spl_autoload_register function.

How to define nodetypes dynamically:

In case the Node module requires the definition of the node type it loads the class from the specified file.

Let's see the dynamic definition example of "tool" node type:

function hook_mymodule_objectnodetype()
{
    $r = [];
    $r['tool'] = [
            'defineclass' => 'ToolNode',
            'file' => 'site/definitions/ToolNode.php'
    ];
    return $r;
}

The site/definitions/ToolNode.php file (part):

// This class can be a sublcass of any class, it does not matter in case of definition.
class ToolNode
{
     public static $definition =
        [
            "name" => "tool_def",
            "table" => "tools",
            "show" => "table",
...
    ];
}

Derive node definition from another node

You can reuse the definition of another node and use as base to a new defined one. To do this you have to set the top level base attribute to the derived node's name.

All options/values of the base definition will exists in the defined one. The values of the base and the current defined structure are merged with the priority of the new values. You can use an additional fields-by-sql top level associative array which a helper field to enables you modify the derived fields without knowing the index of the field. This associative array is similar to fields where the indexes are not numbers but sql names.

function hook_mymodule_nodetype()
{
    return [
        'poweredtool' => [
            'base' => 'tool', //definition derived from "tool" node
            'table' => 'poweredtools', //change the sql table name to "poweredtools"
            'name' => 'poweredtool', //change the name
            'fields' => [  //Adds a new field
                25 => [
                    'sql' => 'powerreq',
                    'text' => 'Required power',
                    'type' => 'smalltext',
                    'color' => '#ffaaaa',
                ],
            ],
            'fields-by-sql' => [  //Modify an attribute of a derived field
                'name' => [
                    'text' => 'Names of the powered tool',
                ],
            ],
        ],
    ];
}

Note: The derived definition are not calculated (merged) at CodKep start when the node types are loaded. The merging is only processed when the derived node's definition structure is needed. For example you do a node_load() on a derived node.

Modify existing node types

The node module provides the HOOK_nodetype_alter_NODENAME($obj,$reason) hook which can alter the already defined node types. You have to substitute the desired node type with NODENAME word in hook name. This hook receives an object which contains a reference to the data definition structure modifiable way. The reason parameter can be "loaded" or "assembled".
If the $reason is "loaded" the hook is activated when the specified node type is loaded. In case of the specified node type has a derived definition array (Assembled on a base of an another node) the HOOK_nodetype_alter_NODENAME is call again when the base definition is merged with the subject definition. This case the $reason parameter is "assembled". The merge process only executed when it is needed. (The subject node type is used)

Let's see an example code where we add an email field to the CodKep's built in user node type:

function hook_mymodule_nodetype_alter_user($p,$reason)
{
    $p->def['fields'][55] = [
        'sql' => 'email',
        'text' => 'E-mail',
        'type' => 'smalltext',
        'par_sec' => 'text5',
    ];
}

Note: After implementing this hook you have to visit the sql schema check page to meet the new sql requirements.

Show the defined node types

You can get a compact table about the defined static and dynamic node types by accessing codkep_definednodes path. The table shows the node names, class types, external sql table names, primary keys and the define file in case of dynamic node. By default this page is only accessible for site admins. See the node settings below to expand or narrow this accessibility.

Database appearance of nodes

The nodes are represented by two separate sql tables in the sql database. One of them is the common "node" table which holds the common properties of nodes and the required data to join to the second table which is depends on node type and holds the data of the fields. The second table name is specified by the data definition structure and different in every node type.

The primary key of the type specific SQL table is located in the join_id field of the node table:

mysql> select * from node;
+-----+----------------+---------+--------+-------------------+---------------------+
| nid | type           | join_id | ptempl | creator           | created             |
+-----+----------------+---------+--------+-------------------+---------------------+
|   1 | user           | 1       | NULL   | <unauthenticated> | 2017-01-24 09:34:27 |
|   2 | user           | 2       | NULL   | admin             | 2017-01-24 09:35:22 |
|   3 | task           | 1       | NULL   | dave              | 2017-01-24 09:41:07 |
|   4 | task           | 2       | NULL   | dave              | 2017-01-24 09:42:21 |
+-----+----------------+---------+--------+-------------------+---------------------+
4 rows in set (0.00 sec)

It means that there is two way to identify a node:

Querying node lists from database

The CodKep has a general database CRUD interface which helps to build database operations. The node module extends this interface to customize it for node queries.

Helper function to query nodes:

node_query($nodetype)
This function receives a $nodetype textual parameter which the type of the node to query.

  1. The function will set the container table of node to query.
  2. Joins the node table appropriate way to reach node fields.
  3. Adds the base node fields to the queried fields:
    • node_nid The node identifier
    • node_type The node type
    • node_join_id The joined id

The function returns a DatabaseQuery object so that all methods can be used to extend the database query. Let's see an example where we search all users who's name is started by "A" letter.

$results = node_query("user")
            ->get("name")
            ->cond_fv("name","^A","regex")
            ->execute_to_arrays();

foreach($results as $res)
{
    print l($res["name"],'node/'.$res['node_nid']) . " user found. <br/>";
}

Node access control

You can control the access of the nodes by implementing hooks.

The HOOK_node_access($node,$op,$account) can control of the access of any type of node while the HOOK_node_access_NODETYPE($node,$op,$account) only controls the access of a specified node type.

Both hook receives same three parameters:

  1. $node The examined node object. The system queries the permissions for this node.
  2. $op The system queries the permission for this operation. Possible values are:
    • "create"
    • "precreate" (On loading the empty form to get the user input before create)
    • "delete"
    • "update"
    • "view"
  3. $account Determine the permission of this account (user node)

The hook have to return one value of these defines:

In case there is no any permission set hook in the system or received only the NODE_ACCESS_IGNORE value the system will deny every request except the following default allowed:

  1. Allows everything for admin users.
  2. Allows to view and precreate for everyone. (It can disabled anytime by sending NODE_ACCESS_DENY from a node_access hook.)

Note1: The NODE_ACCESS_DENY is always stronger than NODE_ACCESS_ALLOW, if both received the result will NODE_ACCESS_DENY

Note2: The node permissions are only checked when the users access nodes by the Node module's UI. There is no permission check if a node accessed from program code!

Note3: The precreate $op is used when the system show an empty form to the user to fill before create a node. In case you allow precreate and deny create the user can fill an empty form but cannot insert it when submitted. The other case you deny precreate the user neither can load the empty form.

Samples:

 //Allow everything on every node type for administrator
 function hook_mymodule_node_access($node,$op,$account)
 {
    if($acc->auth && $acc->role == ROLE_ADMIN)
        return NODE_ACCESS_ALLOW;
    return NODE_ACCESS_IGNORE;
 }

 //Allow add/edit/delete of "news" node for editors but view only for others
 function hook_mymodule_node_access_news($node,$op,$acc)
 {
     if($op == 'view')
         return NODE_ACCESS_ALLOW;
     if($acc->auth && $acc->role >= ROLE_EDITOR)
         return NODE_ACCESS_ALLOW;
     return NODE_ACCESS_DENY;
 }

The node_access function

node_access($node,$op,$account)
Calculate the permission of the operation on the node of the parameter passed account. (It's call the HOOK_node_access in background.)

The return value of this function can be:

REST api for nodes

The CodKep can provide an automatic working REST interface for nodes which can provide full CRUD for the defined node types without any coding work.
To enable REST for a node type you have to do the following:

Node related REST commands

These node related REST interfaces receives JSON data, and answers with JSON too.

All rest command returns application/json and the create/update commands also requires json data to post.

All node operations done through REST is processed by node access system. It means that you have to provide the necessary permissions to done the desired operations.

You can skip some fields of nodes from the REST operations by set the field's no_rest attribute. See the common field attributes of data definition structure.

You can see here a client side javascript function which create a task node through REST interface:

function create_task(description,cost,deadline)
{
    var newtask = new Object();
    newtask.description = description;
    newtask.cost = cost;
    newtask.deadline = deadline;

    jQuery.ajax({
        url: 'index.php?q=/restapi/createnode/task',
        method: 'POST',
        dataType: 'json',
        data: JSON.stringify(newtask),
        contentType: 'application/json',
        context: document.body,
        error: function(e) {
            alert('Error: The task is not created');
        },
        success: function(data) {
            alert('Task node created with nid: ' + data.node_nid );
        }
    });
}

create_task("Do something",22,"2017-02-11");

Note: If you need CORS (Cross-Origin Resource Sharing) in the built-in rest services you need to set the $site_config->cors_requests_enabled_hosts variable in site settings. In your own rest callbacks you also need to call the core_set_cors_headers() function to set the needed CORS headers.

A complete example

In this example we will create a "task" node type and an additional interface with some customisation. So the example will do:

  1. Define a 'task' node type. Go to the code...
  2. Specify the permissions of the 'task' nodes. Go to the code...
  3. Create a listing interface of 'task' nodes. Go to the code...
  4. Extend the node interface of the 'task' node. Go to the code...
  5. Set some redirection after operations (Add,Modify,Delete) Go to the code...

This code define the 'tasks' url and place it to the main menu:
(See routes and menu)

function hook_mymodule_defineroute()
{
    $r = [];
    $r[] = [
        'path' => 'tasks',
        'callback' => 'tasks',
    ];
    return $r;
}

function hook_mymodule_before_start()
{
    global $site_config;
    $site_config->mainmenu["Tasks"] = "tasks";
}

Under the 'tasks' url a callback generates the list of task and an 'Add', 'Edit' and 'Delete' links:
(See query formatter)

function tasks()
{
    global $user;
    ob_start();

    print l("Add task","node/task/add");
    print '<br/>';

    $c = [
        '#tableopts' => ['border' => '1'],
        '#fields' => ['descr','mod','del'],
        'descr' => ['headertext' => 'Description',],
        'mod' => [
            'headertext' => 'Modify',
            'valuecallback' => function($r) {
                return l('Modify','node/'.$r['nid'].'/edit');
            }
        ],
        'del' => [
            'headertext' => 'Delete',
            'valuecallback' => function($r) {
                return l('Delete','node/'.$r['nid'].'/delete');
            }
        ],
    ];

    $r = sql_exec(
        "SELECT tid,nid,descr,task.type,task.created ".
        "FROM task INNER JOIN node ON node.join_id=task.tid AND node.type='task' ".
        "ORDER BY task.created");

    print to_table($r,$c);
    return ob_get_clean();
}

The following codes specifies the permissions of the task node: All authenticated user can create/edit/delete them.

function hook_mymodule_node_access_task($node,$op,$account)
{
    if($account->auth)
        return NODE_ACCESS_ALLOW;
    return NODE_ACCESS_DENY;
}

The 'task' node type contains an 'imgshow' static field which shows the uploaded photo embed in editor table if exists. This hook will hide this image field on insert and shows the photo image in other cases if exists. (Note that the 'img' field contains the file reference while the 'imgshow' is only a static text which displayed to the user.)

function hook_mymodule_node_before_action($node,$op,$acc)
{
    if($node->node_type == 'task')
    {
        if($op == 'add')
        {
            $node->get_definition_field('imgshow')['hide'] = true;
        }
        else
        {
            $ufi = $node->img;
            if($ufi == '' || $ufi == NULL)
            {
                $node->imgshow = 'No uploaded photo';
                return;
            }
            $f = file_load($ufi,true);
            $imagepart = '<img style="max-width: 110px; height: auto;" src="'.
                            url($file->url). '" border="0"/>';
            $node->imgshow = $imagepart;
        }
    }
}

By implementing the HOOK_operation_done we can show the task list after add or modify or delete a tasks.

function hook_mymodule_operation_done($type,$op,$nid)
{
    if($type == 'task')
        goto_loc('tasks');
}

Creation of the 'task' node type:

function hook_mymodule_nodetype()
{
    $r = [];
    $r['task'] =
        [
            "name" => "task",
            "table" => "task",
            "show" => "table",
            "color" => "#ddeedd",
            "fields" => [
                10 => [
                    "sql" => "tid",
                    "text" => "Identifier",
                    "type" => "keyn",
                    "color" => "#ffcccc",
                ],
                20 => [
                    "sql" => "descr",
                    "text" => "Description",
                    "type" => "largetext",
                    "row" => 5,
                    "col" => 40,
                ],
                30 => [
                    "sql" => "urgent",
                    "text" => "Urgent",
                    "type" => "check",
                    "color" => "#ffaaaa",
                ],
                40 => [
                    "sql" => "deadline",
                    "text" => "Deadline",
                    "type" => "dateu",
                ],
                50 => [
                    "sql" => "type",
                    "text" => "Place",
                    "type" => "txtradio",
                    "values" => [
                        "h" => "Home",
                        "w" => "Work",
                    ],
                ],
                60 => [
                    "sql" => "cost",
                    "text" => "Estimated cost",
                    "type" => "float",
                ],
                70 => [
                    "sql" => "part",
                    "text" => "Participants",
                    "type" => "numselect_intrange",
                    "start" => 1,
                    "end" => 10,
                ],
                80 => [
                    "sql" => "img",
                    "text" => "Image",
                    "type" => "file",
                    "container" => "secure",
                    "subdir" => "taskimg",
                ],
                81 => [
                    "sql" => "imgshow",
                    "text" => "Image",
                    "type" => "static",
                    "default" => "",
                ],
                90 => [
                    "sql" => "created",
                    "text" => "Create time",
                    "type" => "timestamp_create",
                ],
                100 => [
                    "sql" => "modtime",
                    "text" => "Modified",
                    "type" => "timestamp_mod",
                ],
                110 => [
                    "sql" => "muser",
                    "text" => "Modifier",
                    "type" => "modifier_user",
                    "userdata" => "fullname",
                ],
                120 => [
                    "sql" => "sadd",
                    "type" => "submit",
                    "default" => "Add",
                    "in_mode" => "insert",
                    "centered" => true,
                ],
                130 => [
                    "sql" => "smod",
                    "type" => "submit",
                    "default" => "Modify",
                    "in_mode" => "update",
                    "centered" => true,
                ],
                140 => [
                    "sql" => "sdel",
                    "type" => "submit",
                    "in_mode" => "delete",
                    "centered" => true,
                    "default" => "Delete",
                ],
            ],
        ];
    return $r;
}

Custom Node objects

You can specify that Node module work with special Node subclasses for a node-type instead of Node class. In case you set the "classname" option in the data definition structure array's toplevel options the node module creates the node objects with the given classname. The node_create, node_load, node_load_intype functions are also returns the specified Node subclass.

Because the specified class is a subclass of Node, all node operations works same way. In the other hands by using of custom class you can achieve many customisations of your node.

//Defining task node type (part)
function hook_mymodule_nodetype()
{
    $r = [];
    $r['task'] = [
            "name" => "task",
            "table" => "task",
            "show" => "table",
            "classname" => "TaskNode", //Tells to use "TaskNode" object instead of "Node"
 ...
   ]
   return $r;
}

//Defining TaskNode
class TaskNode extends Node
{
    //Do not define constructor. (It uses the Node's constructor)
    //Use the m_initialized() function to initialize your own data

    //Sample method which prints a title on node create page
    public function m_before_form($op)
    {
        if($op == 'add')
            return '<h1>Creating a task</h1>';
        return '';
    }
}

You can redefine some Node methods in the created Node subclass to do special operations in a specified node type. See the "m_" prefixed methods in Node class. By sub-classing Node class you have the possibility to do very special nodes by redefining protected methods load_data,save_data,insert_data,remove_data which do the data table manipulations. (See the original codes of this methods to understand what they do)

Settings of the nodes

The node settings which can set in site settings.

namedefaultdescription
$site_config->node_unauth_triggers_login false In case of this option is true and an unauthenticated user try to access a node which needs authenticated user to access the CodKep automatically redirects to the login page.
$site_config->node_rest_api_enabled true If this value is false it completely disable the REST resources related to nodes.
$site_config->node_definednodes_available true If this value is true the node module defines the "codkep_definednodes" route, which shows the defined node types in the current system.
$site_config->node_definednodes_available_for admin Restricts availability of the "codkep_definednodes" route, which shows the defined node types in the current system. Possible values are "admin","editor","auth","all" and "none" which means the group who can access the page.

Many node settings can change in node definition structure to make possible to set different features for each node type. You can view this settings in forms module documentation here. (Purple block)

Hooks

The following hooks can be implement to interact with node module.

Note: Many of this hooks has an $obj parameter which is a container object holding references to the object and other data structures which are modifiable by the hook.

HookDescription
HOOK_nodetype()Define one or more node type.
HOOK_objectnodetype()Define one or more node type with dynamic loaded classes.
HOOK_load_nodedefclass($obj)Invoked before the Node module loads a node-type class from the specified file.
HOOK_nodetype_alter_NODETYPE($obj,$reason)You can change the definition of the specified node type by this hook.
HOOK_node_loaded($pass,$nid,$type,$join_id)Runs immediately after a node is loaded. This hook can change the node data by the $pass pass parameter.
HOOK_node_form_before($node,$op)This hook can put some content before the node add/edit/view/delete forms
The value of $op can be view,add,edit,delete. The string returned by this hook will be inserted before the form.
HOOK_node_form_after($node,$op)This hook can put some content after the node add/edit/view/delete forms
The value of $op can be view,add,edit,delete. The string returned by this hook will be inserted after the form.
HOOK_node_access($node,$op,$account)This hook controls the access to a node
The value of $op can be view,create,precreate,update,delete.
HOOK_node_access_NODETYPE($node,$op,$account)This hook controls the access to a node which type is NODETYPE
The value of $op can be view,create,precreate,update,delete.
HOOK_node_will_update($node)This hook runs immediately before a node is updated by the built-in routes of node module. (node/NID/edit or nodeintype/TYPE/JOIN-ID/edit) If you do redirect in this hook, the operation will be cancelled.
HOOK_node_before_save($obj)Runs every case before the node is saved. (Even by the node api)
HOOK_node_saved($obj)Runs every case immediately after a node is saved. (Even by the node api)
HOOK_node_will_delete($node)This hook runs immediately before a node is deleted by the built-in routes of node module. (node/NID/delete or nodeintype/TYPE/JOIN-ID/delete) If you do redirect in this hook, the operation will be cancelled.
HOOK_node_deleted($nid,$type,$join_id)Runs every case immediately after a node is deleted. (Even by the node api)
HOOK_node_will_create($obj)This hook runs immediately before a node is created by the built-in routes of node module. (node/TYPE/add) If you do redirect in this hook, the operation will be cancelled.
HOOK_node_before_insert($obj)Runs every case before the node is inserted. (Even by the node api)
HOOK_node_inserted($obj)Runs every case immediately after a node is inserted. (Even by the node api)
HOOK_node_rest_action_before($node,$request_method)Runs immediately before built-it in node REST action. If you do redirect in this hook, the operation will be cancelled.
HOOK_node_before_action($node,$op,$user)Runs on node view/add/edit/delete before the form is generated by the built-in routes of node module.
HOOK_node_operation_done($type,$op,$nid)This hook runs after an operation is done on some node. This hook is useful to do some redirection.
The value of $op can be view,add,edit,delete
HOOK_node_operation_not_permitted($node,$op,$account)This hook run when the operation is not permitted/blocked. It useful to do redirection to your custom error handling page. It is called immediately before the CodKep goes to it's own error page.
The value of $op can be view,add,edit,delete