diff --git a/docs/general-concepts/table/_assets/nested-set-model.png b/docs/general-concepts/table/_assets/nested-set-model.png new file mode 100644 index 00000000..ddccf1c7 Binary files /dev/null and b/docs/general-concepts/table/_assets/nested-set-model.png differ diff --git a/docs/general-concepts/table/advanced-table.md b/docs/general-concepts/table/advanced-table.md new file mode 100644 index 00000000..c6b45f72 --- /dev/null +++ b/docs/general-concepts/table/advanced-table.md @@ -0,0 +1,364 @@ +--- +title: Advanced Table Functionality +sidebar_position: 2 +--- + +This page covers more advanced functionality of the Joomla Table class. + +The API definitions are at [Joomla Table APIs](cms-api://classes/Joomla-CMS-Table-Table.html). + +## Checkout / Checkin + +Checkout capability avoids the unexpected results which can arise when 2 users edit the same record simultaneously. +The Joomla pattern is that whenever a component displays an "edit" form for a selected record (allowing the user to edit the record), +then at that time the record is updated to set the `checked_out` field to the userid of the user editing the record, +and the `checked_out_time` field to the current date/time. +Another user viewing the same record then sees a padlock symbol against the "locked" record and is prevented from editing it. + +When the user editing the record successfully saves his/her changes, or cancels the edit, +then the record is "checked in" by resetting the `checked_out` and `checked_out_time` fields. + +### Checkout + +```php +checkOut(integer $userId, mixed $pk = null) +``` + +If the table has columns called `checked_out` and `checked_out_time` +then this function sets the `checked_out` field to the passed-in `$userId` +and the `checked_out_time` field to the current date/time for the record identified by the primary key `$pk` +(or if `$pk` is null then the currently stored primary key is used, eg from the last `load()` call). + +### Checkin + +```php +checkIn(mixed $pk = null) +``` + +If the table has columns called `checked_out` and `checked_out_time` +then this function resets the `checked_out` and `checked_out_time` fields for the record identified by the primary key `$pk` +(or if `$pk` is null then the currently stored primary key is used, eg from the last `load()` call). + +### isCheckedOut + +```php +isCheckedOut(integer $userid) +``` + +You can use this method to find if it's "safe" for the current user to update the currently loaded record. This method returns false if: + +- the `checked_out` field of this record indicates not checked out (eg field has a value of 0), or, +- the `checked_out` field has the same value as `$userid` (ie it's checked out to this user), or, +- the `checked_out` field indicates that the record is checked out to another user, but that user doesn't have a current session record. + +In these cases it's arguably "safe" to update the values in the record and check it in. + +(The absence of a session record indicates that the user has logged out, +or that his/her session has expired and Joomla has subsequently performed a cleanup operation on the session records. +But if that user had been editing a record and left it in a checked out state, +then it will continue to be checked out if the user logs out or just closes the browser tab, +and the record is still shown with a padlock symbol against it. +However, you can argue that in that case it's safe to override the checkout.) + +The Joomla Table class doesn't include checkIn or checkOut functionality when you call methods such as `load()`, `bind()` or `store()`, +but if you use `save()` as a shorthand for these then `checkIn()` is attempted within that method. + +On the other hand, if your component controller and model inherit (either directly or indirectly) from the Joomla MVC `FormController` and `FormModel` +then you are likely to find that the presence of columns called `checked_out` and `checked_out_time` in your table +will result in checkout / checkin functionality being incorporated automatically. + +## ACL Assets + +As described in the [ACL documentation](../acl/index.md) Joomla provides the ACL (Access Control List) framework for defining permissions +which specify whether or not each user group may create, update or delete items. +These permissions may be set within the admin backend within the permissions tabs of the edit forms, and can be at several levels: + +- at an overall component level (specified on the Global Configuration page) +- for individual categories of a component (specified when you edit a category of that component) +- for individual items of a component (eg article, contact), specified when editing that component item + +Joomla stores all these permissions as "rules" within the assets table, +and the `name` field in that assets table defines which of the above 3 cases the asset record refers to. +For example, for `com_content` possible values of the `name` field are as follows: + +- "com_content" – the ACL rules related to performing actions upon articles in general +- "com_content.category.2" – the ACL rules related to articles having a category which has id=2 +- "com_content.article.4" – the ACL rules related to the article with id=4. + +If a component allows setting ACL permissions at the individual record level, then the record should have a column called `asset_id`, +and the value in this field of a record will be the `id` of the associated record in the assets table +(ie the `asset_id` field is a foreign key, pointing to the asset record in the assets table). + +The Table class has 2 public functions and 3 protected functions associated with managing assets. + +### getRules + +```php +getRules() +``` + +You might expect that after you had called `load()` to load a record you could use this function to find the associated asset rules. +However it doesn't (it just returns null unless a previous `setRules()` call has been made) +and to find the existing rules you need to read the associated assets record yourself. +So it's arguably of little value outside core Joomla development. + +You can, of course, implement a more sophisticated getRules method within your own table class that extends Joomla Table. + +### setRules + +```php +setRules(mixed $input) +``` + +This function enables you to define the ACL rules which should be applied against this record. +In array form a set of rules would be like the example below: + +```php +array("core.edit" => array(10 => 1, 11 => 0), "core.delete" => array(10 => 0, 11 => 1)) +``` + +where 10 and 11 are ids of usergroups, and 1 is Allowed and 0 is Denied. In this example: + +- core.edit entry: usergroup 10 is allowed to edit the item, while usergroup 11 is denied edit access +- core.delete entry: usergroup 11 can delete the item, while usergroup 10 is denied delete access + +The `$input` parameter can be rules in the above array format, or in the equivalent json-encoded format, +or in the Joomla Rules object format which is generated from passing either form to the Rules class constructor. + +If you're developing a component which has ACL permissions on individual items, +then use the [standard form field "rules"](../forms-fields/standard-fields/rules.md) in your XML edit file. + +```xml + +``` + +Then in `bind()` method you can call (where `$data` holds the array of values sent in the HTTP POST request): + +```php +$this->setRules($data['rules']); +``` + +If you call `setRules()` to set the ACL permissions which should be applied to the record, +and then call `store()` to save the record to the database, +then the Table class functionality will update also the associated record in the assets table; you don't have to do this yourself. + +### Get Asset helper functions + +In writing new assets records the Table class needs to know an additional 3 items of information, +which you can see by browsing the assets records using phpmyadmin, for example. +These 3 items of information it requests by calling 3 protected functions, +which Joomla expects that you will provide in your own table class which inherits from Joomla Table. These are + +- `_getAssetName()` – to provide the name field – this is usually of the form "com_example.item.12" +- `_getAssetTitle()` - to provide the title field – this is usually the title of the com_example item +- `_getAssetParentId()` – to provide the position of the asset record in the asset hierarchy +(implemented as a tree structure in the assets table using the Nested Model), by returning the parent asset id. +For a com_example item this would usually be the record with just "com_example" as the name, +ie the general ACL permissions for the com_example component. + +Examples of these are in the sample module code at the end of this guide. + +## Ordering + +Many Joomla items include the concept of ordering, for example, modules have an order in which they are displayed in each possible template position. +This is governed by a field called `ordering` in the database table, +and when items are selected from the database the SQL query includes an ORDER BY `ordering` clause if they're to be output in order. + +The Joomla Table class has 3 methods which relate to ordering. + +### getNextOrder + +```php +getNextOrder(string $where) +``` + +This determines the max value of the ordering field among the records selected by the optional where clause `$where`, and returns this max + 1. + +Don't include the word "WHERE" in the `$where` string. + +This function is useful if you're inserting a record into your table +and you want it to appear at the end of the group of records identified in the where clause - +you can set your ordering value to what is returned by `getNextOrder()`. +If you want it to appear at the start you can set the ordering field to 0 (zero) and then use `reorder()` below. + +### Reorder + +```php +reorder(string $where) +``` + +This function reads the set of N records defined by the where clause `$where`, using ORDER BY `ordering` +and then writes them back with the ordering fields nicely numbered from 1 to N. + +### Move + +```php +move(integer $delta, string $where) +``` + +This function basically finds the record with the next greater (if `$delta` is positive) or lesser (if `$delta` is negative) +value of the ordering field, and it then swaps the ordering values of the two records. + +Despite what the Joomla API definition says, the magnitude of `$delta` is irrelevant – +the function always just moves the record by 1 in the ordering sequence. + +This function might be useful if you provided users with a user interface which allowed them to move records up or down by one. +However, Joomla provides a more sophisticated mechanism built using javascript for dragging a record to a new position. +When a record is dragged the javascript recalculates the ordering values of all the displayed records +and sends the updated ordering values to the server in an ajax request, and they are then written to the database. +So if you used that approach, then you wouldn't need this `move` function. + +## Publish + +```php +publish(mixed $pks = null, integer $state = 1, integer $userId) +``` + +This sets the `publish` field to `$state` for the set of records identified by the `$pks` primary key(s) array. +These publish states in Joomla are conventionally identified by integer values, eg: + +- 0 - unpublished +- 1 - published +- 2 - archived +- -2 - trashed + +It also sets the `publish_up` field (if it exists) to the current date/time. +However the condition in the source code for doing this is if the *currently loaded record* has a blank `publish_up` value +(rather than if one of the records identified by `$pks` has a blank `publish_up` value). (This looks like a bug). + +If `$pks` is an `array(field1 => value1, field2 => value2, …)` +then this gets mapped to the SQL where clause WHERE field1 = value1 AND field2 = value2, … +to identify the records to be updated. If `$pks` is absent then just the current loaded record is affected. + +Any records in this set which are checked out to a user other than `$userId` are excluded. +If there are no such records then any records of the affected set which are checked out to the user identified by `$userId` are checked in. + +Note that this API isn't widely used within the core Joomla application, and you may wish to follow the example of main Joomla +components and use the `publish()` method with the MVC AdminModel class, rather than use this API. + +## Hits + +```php +hit(mixed $pk = null) +``` + +A hits counter is usually used to count the number of times a webpage is visited. +In Joomla you can record how many times a component is displayed on the frontend by using adding a hits column to the component's database table, +and calling this function each time the component is displayed. + +The function simply increments the value in the hits field in the record identified by the `$pk` key, +or in the currently loaded record if `$pk` is null. + +## Reflection Methods + +There are several reflection-like methods which provide information about the table which is being accessed + +**`getTableName`** – returns the name of the table, "#__modules" for example + +**`getKeyName`** – returns the name(s) of the primary key field(s) + +**`getPrimaryKeys`** – returns an array of the primary key(s) and value(s) + +**`getFields`** – returns an array of the names of the columns of the database table + +**`hasPrimaryKey`** – checks if the primary key has a value set + +**`hasField($name)`** – checks if the table has a field of that `$name` + +## Reserved Column Names and Aliases + +Much of the functionality associated with the Table class relies upon certain columns being given specific names, +such as "ordering", "checked_out" and "asset_id". +The presence of a column named with one of these special names is sufficient to switch on functionality in the Table class. + +For a number of these names it is possible to set up an alias, +so that if the field in which you store your published state is called "state" +then you can use setColumnAlias() to set up an alias, for example, + +```php +$table->setColumnAlias('published', 'state'); +``` + +The corresponding getter function is, + +```php +$alias = $table->getColumnAlias('published'); +``` + +which using the example above would return the string "state". + +In this way you are free to follow your own column naming convention but at the same time get access to Joomla core functionality. +However, it's my opinion that you shouldn't take that approach, +and instead you should name your columns to follow exactly the Joomla approach. +The reason is that while Joomla helps you out with aliases here, +there are other areas of code where aliases for those fields may not be supported, either now or in the future. + +In particular Joomla core javascript code assumes that certain fields are given certain names, and doesn't always support aliases. +An exception to the above is using the name "state" for the published state of a record instead of "published". +Because com_content and other core Joomla components use "state" you are pretty safe taking that approach, +because new code will always have to support those core components. + +For reference, the fields with special meanings are listed in the table below + +| Field names | Meaning | +|-------------|---------| +| **The following fields can be aliased in the Table class** || +| checked_out, checked_out_time | the userid to which the record is checked out, and the checkout date/time | +| hits | the number of hits for that item | +| ordering | the integer reflecting the record's order | +| published | the published state of the item (Unpublished / Published / Trashed etc). Some core Joomla components use "state" for this column | +| publish_up | the date/time the record was first published | +| **The following fields have special meanings within Table, but cannot be aliased** || +| asset_id | the foreign key pointing to the associated assets record | +| **The following fields have special meanings elsewhere in Joomla (this isn't an exhaustive list)** || +| id | the autogenerated id of the table | +| title | the title of the item | +| alias | the alias of the item | +| language | the language code | +| access | the id of the Access Level | +| params | a JSON-encoded string of parameters | +| created_user_id, created_time | userid of user who created the item, and date/time | +| modified_user_id, modified_time | userid of user who modified the item, and date/time | +| catid | id of the assigned category | +| parent_id, lft, rgt, level, path | fields associated with nested tables | + +## Sample Module + +The example module below incorporates several aspects described above. +It's the same module as in the previous section describing basic table operations. + +### Module Code + +Download the module code from the Manual Examples (tbd). + +### Module Installation + +Zip up the mod_table_example directory to create mod_table_example.zip. + +Within your Joomla administrator go to Install Extensions and via the Upload Package File tab +select this zip file to install this sample mod_table_example module. + +Make this module visible by editing it (click on it within the Modules page) then: + +- making its status Published +- selecting a position on the page for it to be shown +- on the menu assignment tab specify the pages it should appear on + +### Using the module + +When you navigate to your site page where the module is displayed, then the module code will run. + +The module contains functionality relating to this page (in the function `doAdvancedTableOperations`), +as well as the function `doBasicTableOperations` which relates to the previous section. + +It uses its own record in modules table to illustrate Table method calls, specifically: + +- using `isCheckedOut()` to check if a record is checked out or not +- calling `getRules()` to obtain the ACL, but as explained above, this returns null +- defining the ACL rules for the module record using `setRules()`, including defining the 3 protected functions to provide values for fields in the assets table +- use of the reflection method `getTableName()` +- use of the some of the ordering methods diff --git a/docs/general-concepts/table/basic-table.md b/docs/general-concepts/table/basic-table.md new file mode 100644 index 00000000..53bb409a --- /dev/null +++ b/docs/general-concepts/table/basic-table.md @@ -0,0 +1,198 @@ +--- +title: Basic Table Functionality +sidebar_position: 1 +--- + +This page covers basic functionality of the Joomla Table class. + +The API definitions are at [Joomla Table APIs](cms-api://classes/Joomla-CMS-Table-Table.html). + +Introduction +============ + +The Joomla Table class (Joomla\CMS\Table\Table in libraries/src/Table/Table.php) +provides a framework which enables you to do CRUD operations (and more) on database tables. + +The usual case is that you are developing a component with its own database table, +and you use the Table class to implement administrator CRUD operations for data in your database table. + +The Joomla Table class is an abstract class, so you must create your own table class (for your component's database table) which extends the Joomla Table class. + +## Instantiating your table class + +If you're developing a component with its own database table or tables, +then you should use the [Joomla MVC pattern](../../building-extensions/components/mvc/index.md), +and use the MVC Factory for creating controller, view, model and table instances. + +Then within your model you can use + +```php +$table = $this->getTable(); // if your Table class matches the view= parameter in the HTTP request +$table = $this->getTable('example'); // to get the ExampleTable class +``` + +:::note[TODO] + Add an appropriate link to the updated component development tutorial when it's written. +::: + +If you're developing a module or plugin then you don't have access to the MVC Factory, +and the easiest way is to instantiate it directly, passing the database object: + +```php +$table = new ExampleTable($dbo); +``` + +See the sample module code at the end of this page for an example. + +Your table class defines 2 important items of data: + +1. The name of your database table, in the format "#__example". Joomla adds the prefix specified for the database + +2. The primary key (one or more fields) of the database table + +## Basic Table functions + +There are several basic functions within the Table class. +These are described below for a database table with 2 fields: + +- `id` - the primary key +- `title` - a descriptive text field + +### load() + +The `load()` method reads a record from the database and makes the fields of the record available as properties of the table object. + +```php +$id = 1; // set the id field to some value (usually based on an HTTP GET parameter) +$result = $table->load($id); // loads the record with id=1 +if ($result) // $result is true/false depending on whether the record was loaded successfully or not +{ + echo $table->title; // outputs the title associated with the currently loaded record +} +``` + +### bind() + +Use `bind($data)` when you have `$data` which you want to set into the record. +The `$data` should be in the form of an associative array. + +```php +$id = 1; +$table->load($id); +$data = array("title" => "first"); +$table->bind($data); +echo $table->title; // outputs "first" +``` + +The `bind` call has the effect of replacing the properties of the table object: + +- prior to `bind` call the `$table->title` property would have whatever was read from the database. +- after the `bind` call the `$table->title` property has whatever is passed in the `$data` array for element "title". + +### check() + +Use `check()` to perform any validation on your record before you save it to the database. +The Joomla Table class doesn't provide any default validation - it's up to you to specify what you want. + +### store() + +Use `store()` to write the database record from the table class instance. +Joomla maps the properties of the instance object to fields of the database record, and then stores the record. + +The Joomla default `store()` method examines the properties of the object relating to the primary key of the database table + +- if the primary key is not set (or id = 0 in the case of a numeric id primary key) then it issues SQL INSERT + +- if the primary key is set then it issues SQL UPDATE + +### save() + +The `save()` method is really just a concatenation of `bind()`, `check()` and `store()`. + +### delete() + +You can use the `delete()` method with or without a parameter: + +- `delete()` - deletes the record which was previously read using a `load()` call + +- `delete($key)` - deletes the record whose primary key matches `$key`. + +## CRUD Operations + +To perform CRUD operations you string together calls to the above methods + +- READ + + - `load($key)` - passing the value of the primary key + +- UPDATE + + - `load($key)` + - `bind($data)` - bind the new data + - `check()` - perform validation + - `store()` - issue the SQL UPDATE + +- CREATE + + - `bind($data)` - bind the data (a previous `load` isn't appropriate here) + - `check()` - perform validation + - `store()` - issue the SQL INSERT + +- DELETE + + - `load($key)` + - `delete()` + - or instead you can just do `delete($key)` + +## Sample Module + +This section contains the code for a simple Joomla module which you can install and run to demonstrate use of the basic Table functionality. +If you are unsure about development and installing a Joomla module +then [Module Development Tutorial](../../building-extensions/modules/module-development-tutorial/index.md) should help. + +### Module Code + +Download the module code from the Manual Examples (tbd). + +### Module Installation + +Zip up the mod_table_example directory to create mod_table_example.zip. + +Within your Joomla administrator go to Install Extensions and via the Upload Package File tab +select this zip file to install this sample mod_table_example module. + +Make this module visible by editing it (click on it within the Modules page) then: + +- making its status Published +- selecting a position on the page for it to be shown +- on the menu assignment tab specify the pages it should appear on + +### Using the module + +When you navigate to your site page where the module is displayed, then the module code will run. + +The module contains functionality relating to this page (in the function `doBasicTableOperations`), +as well as the function `doAdvancedTableOperations` which relates to the next section. + +The basic functionality demonstrates reading a record from the modules table then writing an update back to that record in the database. + +The modules table contains the module instances which are displayed on the Joomla site or administrator, +and you'll find it useful to view the table through phpmyadmin, for example. + +Some notes on the functionality of this module: + +- The module record is loaded from the database record and the title of the module is output +- The module params is json decoded into an associative array and the header tag is then set to 'h2' +(which controls how the header title is displayed in the module on the web page) +- The new note to be written to the module record is read from the `demonote` parameter in the URL, +so you can set this yourself by adding `?demonote=something` in the URL +- The new note and params are written back to the database record using `bind()` and `store()` commands. +- The params will be converted back to a json string because of the line `protected $_jsonEncode = array('params');` in our table class +- The `check()` function in our table class appends a string " added via module" to the note. + +You can verify the changes to the database record by viewing the module record within the admin back-end or by using phpmyadmin. + +:::warning + This sample module is purely for demonstrating some of the functionality of the Table class. + In general you should not alter Joomla core tables in this way, as you could corrupt the integrity of the Joomla database. +::: \ No newline at end of file diff --git a/docs/general-concepts/table/index.md b/docs/general-concepts/table/index.md new file mode 100644 index 00000000..73985291 --- /dev/null +++ b/docs/general-concepts/table/index.md @@ -0,0 +1,17 @@ +--- +title: Table +--- + +The Joomla Table class provides a framework which enables you to do CRUD operations (and more) on database tables. +It is an implementation of the [Active Record](https://en.wikipedia.org/wiki/Active_record_pattern) design pattern. +It's mainly used for the case where you're developing a Joomla component with its associated database table, +and you're providing admin functionality for managing the data in the database table +or displaying site pages associated with individual database records. + +The Table functionality can be used for batch operations if the batch operation is split into individual record updates. +However it doesn't support SQL operations on multiple records (eg selecting several records from a database table); +for this see the [Joomla Database documentation](../database/index.md). + +The Basic and Advanced table functionality relate to the standard Joomla Table class. + +The Nested Set Tables descriptions relate to tables which have a tree structure. \ No newline at end of file diff --git a/docs/general-concepts/table/nested.md b/docs/general-concepts/table/nested.md new file mode 100644 index 00000000..940ac56a --- /dev/null +++ b/docs/general-concepts/table/nested.md @@ -0,0 +1,290 @@ +--- +title: Nested Set Tables +sidebar_position: 3 +--- + +Introduction +============ + +## Joomla Tree Structures + +Joomla implements menuitems and categories as tree structures. +This means, for example, that menuitems on a menu can have submenus off them (as can be seen on the Joomla admin menu) +and those submenuitems can in turn have submenus off them, and so on. +The submenus are ordered, so you can define which order the submenuitems appear. + +Joomla uses the following terminology: + +- each of the records is a **node** within the tree structure +- there is a **root node** which occupies the highest position in the hierarchy; this root node is defined as being at **level 0** +- the root node has a number of **children**, and these will be at **level 1**. Each of these children's **parent** is the root node +- these children may in turn have children of their own, at level 2, and so on +- if a node has no children, then it is known as a **leaf node** + +## Nested Sets + +To implement this tree structure within a relational database Joomla uses the [Nested Set Model](https://en.wikipedia.org/wiki/Nested_set_model). + +![Nested Set Model](./_assets/nested-set-model.png "Nested Set Model")] + +| Node | Left | Right | +|------|------|-------| +|Clothing|1|22| +|Men's|2|9| +|Suits|3|8| +|Slacks|4|5| +|Jackets|6|7| +|Women's|10|21| +|Dresses|11|16| +|Evening Gowns|12|13| +|Sun Dresses|14|15| +|Skirts|17|18| +|Blouses|19|20| + +This involves assigning to each record a left (in Joomla lft) and a right (in Joomla rgt) field, +as shown in the diagram and associated table. +You should be able to see how using the lft and rgt values you can determine for any node its level in the tree, its parent, +and for nodes with the same parent, the ordering of those nodes. + +While having lft and rgt values may be sufficient to define a tree completely in theory, +in practice adding additional fields gives more opportunity for navigating the tree efficiently, +and in Joomla there are 5 fields associated with a tree structure: + +- lft and rgt fields +- parent id – the id of the parent node +- level - the level in the hierarchy, level 0 (Clothing in the example above) being the highest +- path + +The path is like the path in a directory structure, and is a join of the alias values going from the root node to the node in question, +with a slash separating the aliases. +For example, referring to the diagram above, if Clothing is the root node, then the path for Jackets would be Men's/Suits/Jackets. + +Notice how the additional fields provide us with efficient mechanisms for navigating the tree. +For instance, we can do a query selecting records WHERE parent is Suits, and ORDER BY lft, +and that will give us Slacks and Jackets in order. +These sorts of operations are generally done on the front end, where we want the site to perform well. +By contrast, admin operations can involve many updates. +For example, if we move Blouses to be the first child under Men's +then it will involve updating the lft and rgt values of nearly all the records in the database table. + +This document describes the library functions with Joomla provides to support managing tree data implemented in a nested set. + +The API is defined at [Joomla Nested APIs](cms-api://classes/Joomla-CMS-Table-Nested.html). + +## Instantiating your table class + +If your extension implements a tree hierarchy in its associated database table, +then you should define your table class to inherit from Joomla\CMS\Table\Nested instead of from Joomla\CMS\Table\Table. +Then you can instantiate an instance in the same way as described in [Basic Table instantiation](../basic-table/#instantiating-your-table-class). + +In the descriptions below, it's assumed that `$table` is a reference to your table instance, eg: + +```php +$table = $this->getTable('example'); // to get the ExampleTable class +``` + +## Root Node + +### Setting the Root Node + +Joomla doesn't provide an API call to set a root node, and the easiest way to accomplish this for a component +is to insert the root node record when you create the database table in your install SQL file. +You should set: + +- parent_id - 0 +- lft - 0 +- rgt - 1 (assuming you're not inserting other nodes at the same time) +- level - 0 +- alias - "root" (for example) +- path - "" + +### Getting the Root Node + +Use `getRootId()`: + +```php +$rootId = $table->getRootId(); +``` + +## Tree nodes and traversal + +### Reading a node + +To retrieve a single node you use the `load()` method, passing the primary key, as usual: + +```php +$table->load($pk); +``` + +The fields of the node record are then available as properties of the `$table` object. + +### Identifying a leaf node + +A leaf node is one that has no child nodes beneath it. +To determine if a node is a leaf node, use code like the following: + +```php +if ($table->isLeaf($pk)) +{ + echo 'This is a leaf node'; +} +else +{ + echo 'This is not a leaf node'; +} +``` + +If you don't specify the `$pk` parameter then the currently loaded record is assumed. + +### Retrieving a Subtree + +To retrieve an entire subtree given the `id` of the base node of the subtree, use code like the following: + +```php +// If $id is the id of a node, retrieve the subtree with this node as its root. +$subtree = $table->getTree($id); +print_r($subtree); +``` + +This will retrieve an array of all the nodes in the subtree. +The array is one-dimensional and lists the nodes in [preorder traversal order](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR). +Note that if your table is large then calling `getTree()` from the root node will retrieve the entire table and may cause memory problems. +Use it with caution. + +### Retrieving a Path + +To retrieve all the nodes from the root node along the path leading to a specified node, you can use code like this: + +```php +$pathNodes = $table->getPath($pk); +print_r($pathNodes); +``` + +This will retrieve a one-dimensional array of all the nodes from the root node along the path leading to the node specified by `$pk`. + +## Node CRUD Operations + +### Inserting a node + +Inserting a node is more complex in this case because you need to define where in the tree the new record should be placed. +To do this you call `setLocation()` to specify the location with reference to a node defined in `$referenceId`: + +```php +$table->setLocation($referenceId, $position); +``` + +where `$position` can be: + +- "before" - the new node will be inserted before the reference node but at the same level. +- "after" - the new node will be inserted after the reference node but at the same level. +- "first-child" - the new node will be inserted as the first child node of the reference node. +- "last-child" - the new node will be inserted as the last child node of the reference node. + +After you set the position you insert the record using the same functions as for Joomla Table: + + +```php +$table->setLocation($referenceId, $position); +$table->bind($data); // for an insert the primary key within $data should not already exist in the database table +$table->check(); +$table->store(); +``` + +In order to maintain the integrity of the data structure, the table is locked during a node insertion operation. +Adding a new node to a large table is an expensive operation as the table could potentially be locked for a considerable period. +Take this into account when designing your application. + +### Updating a node + +If you are updating the data of a node but not changing its position in the tree, +then you do this in exactly the same way as you would update a record in a regular database table using the Table class. + +```php +$table->bind($data); +$table->check(); +$table->store(); +``` + +If you are also updating the position of the node in the tree then you must call `setLocation()` first, as described above: + +```php +$table->setLocation($referenceId, $position); +$table->bind($data); +$table->check(); +$table->store(); +``` + +Note that if you're updating the position of a node, and that node has a subtree of children, +then those children will get moved with that node. + +#### Publishing state changes + +There are special considerations for changing the published state of a record, +as you would generally not expect a child node to have a "Published" state if the state of its parent is "Unpublished". +Joomla provides the `publish` method to help with this: + +```php +$table->publish($pks, $state, $userId); +``` + +where: + +- `$pks` is an array of the primary keys of the affected records +- `$state` is the state to be applied - Joomla applies consistency checks to this value +(eg to avoid a node being set to published if one of its parents is unpublished), +and recursively applies this state down through nodes of the subtree below the node. +- `$userid` is the `$userid` of the user performing the change - +if the database table supports checkin/checkout +then Joomla checks that none of the subtree nodes are checked out to other users + +### Deleting a node + +If you delete a node in the tree then you must consider what to do with the child nodes in the subtree below it. + +```php +$table->delete($pk, true); // deletes the node identified by $pk, and deletes all of its children as well +$table->delete($pk, false); // deletes the node identified by $pk, and moves all of its children up a level in the tree +``` + +## Rearranging a tree + +### moveByReference + +```php +$table->moveByReference($referenceId, $position, $pk, $recursiveUpdate) +``` + +This moves the node which has primary key `$pk` and all of its children to a new position in the tree. +The new position is specified by the `$referenceId` and `$position`, as described for `setLocation` +in [Inserting a node](./#inserting-a-node) above. + +The API defines the 4th parameter as "Flag indicate that method recursiveUpdatePublishedColumn should be call." +This function will adjust the published state of the moved nodes so that they are not inconsistent with the new parents, +similar to as described in [Publishing state changes](./#publishing-state-changes) above. + +### Order up and Order down + +These functions move a node to the left or right at the same level: + +```php +$table->orderUp($pk); // moves a node one position to the left at the same level +$table->orderDown($pk); // moves a node one position to the right at the same level +``` + +### move + +This is a more generalised function which allows you to move a node to another position at the same level. +Note that you don't specify a `$pk` as a parameter, and the function moves the currently loaded node. + +```php +$table->move($delta, $where); +``` + +The `$where` is a SQL WHERE clause which limits where you are going to move the node. +The `move` method finds the records at the same level which match your WHERE clause. + +If `$delta > 0` then it finds the first record to the right, and moves the currently loaded node to the right of it. + +If `$delta <= 0` then it finds the first record to the left, and moves the currently loaded node to the left of it. + +Despite what the API definition says, the magnitude of `$delta` is irrelevant. \ No newline at end of file