Spade

Mini Shell

Directory:~$ /home/lmsyaran/public_html/joomla4/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ /home/lmsyaran/public_html/joomla4/table.zip

PK!��`��behavior/assets.phpnu�[���<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba
Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 * @note        This file has been modified by the Joomla! Project and no
longer reflects the original work of its author.
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior class for assets
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFTableBehaviorAssets extends FOFTableBehavior
{
	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table       The table which calls this event
	 *
	 * @return  boolean  True to allow saving
	 */
	public function onAfterStore(&$table)
	{
		$result = true;

		$asset_id_field	= $table->getColumnAlias('asset_id');

		if (in_array($asset_id_field, $table->getKnownFields()))
		{
			if (!empty($table->$asset_id_field))
			{
				$currentAssetId = $table->$asset_id_field;
			}

			// The asset id field is managed privately by this class.
			if ($table->isAssetsTracked())
			{
				unset($table->$asset_id_field);
			}
		}

		// Create the object used for inserting/updpating data to the database
		$fields     = $table->getTableFields();

		// Let's remove the asset_id field, since we unset the property
above and we would get a PHP notice
		if (isset($fields[$asset_id_field]))
		{
			unset($fields[$asset_id_field]);
		}

		// Asset Tracking
		if (in_array($asset_id_field, $table->getKnownFields()) &&
$table->isAssetsTracked())
		{
			$parentId = $table->getAssetParentId();

            try{
                $name     = $table->getAssetName();
            }
            catch(Exception $e)
            {
                $table->setError($e->getMessage());
                return false;
            }

			$title    = $table->getAssetTitle();

			$asset = JTable::getInstance('Asset');
			$asset->loadByName($name);

			// Re-inject the asset id.
			$this->$asset_id_field = $asset->id;

			// Check for an error.
			$error = $asset->getError();

            // Since we are using JTable, there is no way to mock it and
test for failures :(
            // @codeCoverageIgnoreStart
			if ($error)
			{
				$table->setError($error);

				return false;
			}
            // @codeCoverageIgnoreEnd

			// Specify how a new or moved node asset is inserted into the tree.
            // Since we're unsetting the table field before, this
statement is always true...
			if (empty($table->$asset_id_field) || $asset->parent_id !=
$parentId)
			{
				$asset->setLocation($parentId, 'last-child');
			}

			// Prepare the asset to be stored.
			$asset->parent_id = $parentId;
			$asset->name      = $name;
			$asset->title     = $title;

			if ($table->getRules() instanceof JAccessRules)
			{
				$asset->rules = (string) $table->getRules();
			}

            // Since we are using JTable, there is no way to mock it and
test for failures :(
            // @codeCoverageIgnoreStart
			if (!$asset->check() || !$asset->store())
			{
				$table->setError($asset->getError());

				return false;
			}
            // @codeCoverageIgnoreEnd

			// Create an asset_id or heal one that is corrupted.
			if (empty($table->$asset_id_field) || (($currentAssetId !=
$table->$asset_id_field) && !empty($table->$asset_id_field)))
			{
				// Update the asset_id field in this table.
				$table->$asset_id_field = (int) $asset->id;

				$k = $table->getKeyName();

                $db = $table->getDbo();

				$query = $db->getQuery(true)
				            ->update($db->qn($table->getTableName()))
				            ->set($db->qn($asset_id_field).' = ' .
(int) $table->$asset_id_field)
				            ->where($db->qn($k) . ' = ' . (int)
$table->$k);

				$db->setQuery($query)->execute();
			}

			$result = true;
		}

		return $result;
	}

	/**
	 * The event which runs after binding data to the table
	 *
	 * @param   FOFTable      &$table  The table which calls this event
	 * @param   object|array  &$src    The data to bind
	 *
	 * @return  boolean  True on success
	 */
	public function onAfterBind(&$table, &$src)
	{
		// Set rules for assets enabled tables
		if ($table->isAssetsTracked())
		{
			// Bind the rules.
			if (isset($src['rules']) &&
is_array($src['rules']))
			{
                // We have to manually remove any empty value, since they
will be converted to int,
                // and "Inherited" values will become
"Denied". Joomla is doing this manually, too.
                // @todo Should we move this logic inside the setRules
method?
                $rules = array();

                foreach ($src['rules'] as $action => $ids)
                {
                    // Build the rules array.
                    $rules[$action] = array();

                    foreach ($ids as $id => $p)
                    {
                        if ($p !== '')
                        {
                            $rules[$action][$id] = ($p == '1' ||
$p == 'true') ? true : false;
                        }
                    }
                }

				$table->setRules($rules);
			}
		}

		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   integer   $oid     The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	public function onBeforeDelete(&$table, $oid)
	{
		// If tracking assets, remove the asset first.
		if ($table->isAssetsTracked())
		{
            $k = $table->getKeyName();

            // If the table is not loaded, let's try to load it with
the id
            if(!$table->$k)
            {
                $table->load($oid);
            }

            // If I have an invalid assetName I have to stop
            try
            {
                $name = $table->getAssetName();
            }
            catch(Exception $e)
            {
                $table->setError($e->getMessage());
                return false;
            }

			// Do NOT touch JTable here -- we are loading the core asset table which
is a JTable, not a FOFTable
			$asset = JTable::getInstance('Asset');

			if ($asset->loadByName($name))
			{
                // Since we are using JTable, there is no way to mock it
and test for failures :(
                // @codeCoverageIgnoreStart
				if (!$asset->delete())
				{
					$table->setError($asset->getError());

					return false;
				}
                // @codeCoverageIgnoreEnd
			}
			else
			{
                // I'll simply return true even if I couldn't
load the asset. In this way I can still
                // delete a broken record
				return true;
			}
		}

		return true;
	}
}
PK!�Tʀffbehavior/contenthistory.phpnu�[���<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba
Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior class for content History
 *
 * @package  FrameworkOnFramework
 * @since    2.2.0
 */
class FOFTableBehaviorContenthistory extends FOFTableBehavior
{
	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow saving without an error
	 */
	public function onAfterStore(&$table)
	{
		$aliasParts = explode('.', $table->getContentType());
		$table->checkContentType();

		if
(JComponentHelper::getParams($aliasParts[0])->get('save_history',
0))
		{
			$historyHelper = new JHelperContenthistory($table->getContentType());
			$historyHelper->store($table);
		}

		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	public function onBeforeDelete(&$table, $oid)
	{
		$aliasParts = explode('.', $table->getContentType());

		if
(JComponentHelper::getParams($aliasParts[0])->get('save_history',
0))
		{
			$historyHelper = new JHelperContenthistory($table->getContentType());
			$historyHelper->deleteHistory($table);
		}

		return true;
	}
}
PK!�J�[[behavior/tags.phpnu�[���<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba
Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 * @note        This file has been modified by the Joomla! Project and no
longer reflects the original work of its author.
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior class for tags
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFTableBehaviorTags extends FOFTableBehavior
{
	/**
	 * The event which runs after binding data to the table
	 *
	 * @param   FOFTable  		&$table  	The table which calls this event
	 * @param   object|array  	&$src  		The data to bind
	 * @param  	array 			$options 	The options of the table
	 *
	 * @return  boolean  True on success
	 */
	public function onAfterBind(&$table, &$src, $options = array())
	{
		// Bind tags
		if ($table->hasTags())
		{
			if ((!empty($src['tags']) && $src['tags'][0]
!= ''))
			{
				$table->newTags = $src['tags'];
			}

			// Check if the content type exists, and create it if it does not
			$table->checkContentType();

			$tagsTable = clone($table);

			$tagsHelper = new JHelperTags();
			$tagsHelper->typeAlias = $table->getContentType();

			// TODO: This little guy here fails because JHelperTags
			// need a JTable object to work, while our is FOFTable
			// Need probably to write our own FOFHelperTags
			// Thank you com_tags
			if (!$tagsHelper->postStoreProcess($tagsTable))
			{
				$table->setError('Error storing tags');
				return false;
			}
		}

		return true;
	}

	/**
	 * The event which runs before storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true)
or just skipped over (false)?
	 *
	 * @return  boolean  True to allow saving
	 */
	public function onBeforeStore(&$table, $updateNulls)
	{
		if ($table->hasTags())
		{
			$tagsHelper = new JHelperTags();
			$tagsHelper->typeAlias = $table->getContentType();

			// TODO: JHelperTags sucks in Joomla! 3.1, it requires that tags are
			// stored in the metadata property. Not our case, therefore we need
			// to add it in a fake object. We sent a PR to Joomla! CMS to fix
			// that. Once it's accepted, we'll have to remove the atrocity
			// here...
			$tagsTable = clone($table);
			$tagsHelper->preStoreProcess($tagsTable);
		}
	}

	/**
	 * The event which runs after deleting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record which was deleted
	 *
	 * @return  boolean  True to allow the deletion without errors
	 */
	public function onAfterDelete(&$table, $oid)
	{
		// If this resource has tags, delete the tags first
		if ($table->hasTags())
		{
			$tagsHelper = new JHelperTags();
			$tagsHelper->typeAlias = $table->getContentType();

			if (!$tagsHelper->deleteTagData($table, $oid))
			{
				$table->setError('Error deleting Tags');
				return false;
			}
		}
	}
}
PK!�+u���behavior.phpnu�[���<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba
Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior class. It defines the events which
are
 * called by a Table.
 *
 * @codeCoverageIgnore
 * @package  FrameworkOnFramework
 * @since    2.1
 */
abstract class FOFTableBehavior extends FOFUtilsObservableEvent
{
	/**
	 * This event runs before binding data to the table
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   array     &$data   The data to bind
	 *
	 * @return  boolean  True on success
	 */
	public function onBeforeBind(&$table, &$data)
	{
		return true;
	}

	/**
	 * The event which runs after binding data to the table
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   object|array  &$src  The data to bind
	 *
	 * @return  boolean  True on success
	 */
	public function onAfterBind(&$table, &$src)
	{
		return true;
	}

	/**
	 * The event which runs after loading a record from the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   boolean  &$result  Did the load succeeded?
	 *
	 * @return  void
	 */
	public function onAfterLoad(&$table, &$result)
	{

	}

	/**
	 * The event which runs before storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true)
or just skipped over (false)?
	 *
	 * @return  boolean  True to allow saving
	 */
	public function onBeforeStore(&$table, $updateNulls)
	{
		return true;
	}

	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow saving without an error
	 */
	public function onAfterStore(&$table)
	{
		return true;
	}

	/**
	 * The event which runs before moving a record
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true)
or just skipped over (false)?
	 *
	 * @return  boolean  True to allow moving
	 */
	public function onBeforeMove(&$table, $updateNulls)
	{
		return true;
	}

	/**
	 * The event which runs after moving a record
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow moving without an error
	 */
	public function onAfterMove(&$table)
	{
		return true;
	}

	/**
	 * The event which runs before reordering a table
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   string  $where  The WHERE clause of the SQL query to run on
reordering (record filter)
	 *
	 * @return  boolean  True to allow reordering
	 */
	public function onBeforeReorder(&$table, $where = '')
	{
		return true;
	}

	/**
	 * The event which runs after reordering a table
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow the reordering to complete without an
error
	 */
	public function onAfterReorder(&$table)
	{
		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	public function onBeforeDelete(&$table, $oid)
	{
		return true;
	}

	/**
	 * The event which runs after deleting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record which was deleted
	 *
	 * @return  boolean  True to allow the deletion without errors
	 */
	public function onAfterDelete(&$table, $oid)
	{
		return true;
	}

	/**
	 * The event which runs before hitting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record to hit
	 * @param   boolean  $log  Should we log the hit?
	 *
	 * @return  boolean  True to allow the hit
	 */
	public function onBeforeHit(&$table, $oid, $log)
	{
		return true;
	}

	/**
	 * The event which runs after hitting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record which was hit
	 *
	 * @return  boolean  True to allow the hitting without errors
	 */
	public function onAfterHit(&$table, $oid)
	{
		return true;
	}

	/**
	 * The even which runs before copying a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record being copied
	 *
	 * @return  boolean  True to allow the copy to take place
	 */
	public function onBeforeCopy(&$table, $oid)
	{
		return true;
	}

	/**
	 * The even which runs after copying a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record which was copied
(not the new one)
	 *
	 * @return  boolean  True to allow the copy without errors
	 */
	public function onAfterCopy(&$table, $oid)
	{
		return true;
	}

	/**
	 * The event which runs before a record is (un)published
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer|array  &$cid     The PK IDs of the records being
(un)published
	 * @param   integer        $publish  1 to publish, 0 to unpublish
	 *
	 * @return  boolean  True to allow the (un)publish to proceed
	 */
	public function onBeforePublish(&$table, &$cid, $publish)
	{
		return true;
	}

	/**
	 * The event which runs after the object is reset to its default values.
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow the reset to complete without errors
	 */
	public function onAfterReset(&$table)
	{
		return true;
	}

	/**
	 * The even which runs before the object is reset to its default values.
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow the reset to complete
	 */
	public function onBeforeReset(&$table)
	{
		return true;
	}
}
PK!��|�dispatcher/behavior.phpnu�[���<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba
Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior dispatcher class
 *
 * @codeCoverageIgnore
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFTableDispatcherBehavior extends FOFUtilsObservableDispatcher
{

}
PK!����G�G�
nested.phpnu�[���<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba
Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A class to manage tables holding nested sets (hierarchical data)
 *
 * @property int    $lft   Left value (for nested set implementation)
 * @property int    $rgt   Right value (for nested set implementation)
 * @property string $hash  Slug hash (optional; for faster searching)
 * @property string $slug  Node's slug (optional)
 * @property string $title Title of the node (optional)
 */
class FOFTableNested extends FOFTable
{
	/** @var int The level (depth) of this node in the tree */
	protected $treeDepth = null;

	/** @var FOFTableNested The root node in the tree */
	protected $treeRoot = null;

	/** @var FOFTableNested The parent node of ourselves */
	protected $treeParent = null;

	/** @var bool Should I perform a nested get (used to query
ascendants/descendants) */
	protected $treeNestedGet = false;

	/** @var   array  A collection of custom, additional where clauses to
apply during buildQuery */
	protected $whereClauses = array();

	/**
	 * Public constructor. Overrides the parent constructor, making sure there
are lft/rgt columns which make it
	 * compatible with nested sets.
	 *
	 * @param   string          $table  Name of the database table to model.
	 * @param   string          $key    Name of the primary key field in the
table.
	 * @param   FOFDatabaseDriver &$db    Database driver
	 * @param   array           $config The configuration parameters array
	 *
	 * @throws \RuntimeException When lft/rgt columns are not found
	 */
	public function __construct($table, $key, &$db, $config = array())
	{
		parent::__construct($table, $key, $db, $config);

		if (!$this->hasField('lft') ||
!$this->hasField('rgt'))
		{
			throw new \RuntimeException("Table " .
$this->getTableName() . " is not compatible with FOFTableNested: it
does not have lft/rgt columns");
		}
	}

	/**
	 * Overrides the automated table checks to handle the 'hash'
column for faster searching
	 *
	 * @return boolean
	 */
	public function check()
	{
		// Create a slug if there is a title and an empty slug
		if ($this->hasField('title') &&
$this->hasField('slug') && empty($this->slug))
		{
			$this->slug = FOFStringUtils::toSlug($this->title);
		}

		// Create the SHA-1 hash of the slug for faster searching (make sure the
hash column is CHAR(64) to take
		// advantage of MySQL's optimised searching for fixed size CHAR
columns)
		if ($this->hasField('hash') &&
$this->hasField('slug'))
		{
			$this->hash = sha1($this->slug);
		}

		// Reset cached values
		$this->resetTreeCache();

		return parent::check();
	}

	/**
	 * Delete a node, either the currently loaded one or the one specified in
$id. If an $id is specified that node
	 * is loaded before trying to delete it. In the end the data model is
reset. If the node has any children nodes
	 * they will be removed before the node itself is deleted.
	 *
	 * @param   integer $oid       The primary key value of the item to delete
	 *
	 * @throws  UnexpectedValueException
	 *
	 * @return  boolean  True on success
	 */
	public function delete($oid = null)
	{
		// Load the specified record (if necessary)
		if (!empty($oid))
		{
			$this->load($oid);
		}

        $k  = $this->_tbl_key;
        $pk = (!$oid) ? $this->$k : $oid;

        // If no primary key is given, return false.
        if (!$pk)
        {
            throw new UnexpectedValueException('Null primary key not
allowed.');
        }

        // Execute the logic only if I have a primary key, otherwise I
could have weird results
        // Perform the checks on the current node *BEFORE* starting to
delete the children
        if (!$this->onBeforeDelete($oid))
        {
            return false;
        }

        $result = true;

		// Recursively delete all children nodes as long as we are not a leaf
node and $recursive is enabled
		if (!$this->isLeaf())
		{
			// Get all sub-nodes
			$table = $this->getClone();
			$table->bind($this->getData());
			$subNodes = $table->getDescendants();

			// Delete all subnodes (goes through the model to trigger the observers)
			if (!empty($subNodes))
			{
				/** @var FOFTableNested $item */
				foreach ($subNodes as $item)
				{
                    // We have to pass the id, so we are getting it again
from the database.
                    // We have to do in this way, since a previous child
could have changed our lft and rgt values
					if(!$item->delete($item->$k))
                    {
                        // A subnode failed or prevents the delete,
continue deleting other nodes,
                        // but preserve the current node (ie the parent)
                        $result = false;
                    }
				};

                // Load it again, since while deleting a children we could
have updated ourselves, too
                $this->load($pk);
			}
		}

        if($result)
        {
            // Delete the row by primary key.
            $query = $this->_db->getQuery(true);
            $query->delete();
            $query->from($this->_tbl);
            $query->where($this->_tbl_key . ' = ' .
$this->_db->q($pk));

            $this->_db->setQuery($query)->execute();

            $result = $this->onAfterDelete($oid);
        }

		return $result;
	}

    protected function onAfterDelete($oid)
    {
        $db = $this->getDbo();

        $myLeft  = $this->lft;
        $myRight = $this->rgt;

        $fldLft = $db->qn($this->getColumnAlias('lft'));
        $fldRgt = $db->qn($this->getColumnAlias('rgt'));

        // Move all siblings to the left
        $width = $this->rgt - $this->lft + 1;

        // Wrap everything in a transaction
        $db->transactionStart();

        try
        {
            // Shrink lft values
            $query = $db->getQuery(true)
                        ->update($db->qn($this->getTableName()))
                        ->set($fldLft . ' = ' . $fldLft .
' - '.$width)
                        ->where($fldLft . ' > ' .
$db->q($myLeft));
            $db->setQuery($query)->execute();

            // Shrink rgt values
            $query = $db->getQuery(true)
                        ->update($db->qn($this->getTableName()))
                        ->set($fldRgt . ' = ' . $fldRgt .
' - '.$width)
                        ->where($fldRgt . ' > ' .
$db->q($myRight));
            $db->setQuery($query)->execute();

            // Commit the transaction
            $db->transactionCommit();
        }
        catch (\Exception $e)
        {
            // Roll back the transaction on error
            $db->transactionRollback();

            throw $e;
        }

        return parent::onAfterDelete($oid);
    }

	/**
	 * Not supported in nested sets
	 *
	 * @param   string $where Ignored
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	public function reorder($where = '')
	{
		throw new RuntimeException('reorder() is not supported by
FOFTableNested');
	}

	/**
	 * Not supported in nested sets
	 *
	 * @param   integer $delta Ignored
	 * @param   string  $where Ignored
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	public function move($delta, $where = '')
	{
		throw new RuntimeException('move() is not supported by
FOFTableNested');
	}

	/**
	 * Create a new record with the provided data. It is inserted as the last
child of the current node's parent
	 *
	 * @param   array $data The data to use in the new record
	 *
	 * @return  static  The new node
	 */
	public function create($data)
	{
		$newNode = $this->getClone();
		$newNode->reset();
		$newNode->bind($data);

		if ($this->isRoot())
		{
			return $newNode->insertAsChildOf($this);
		}
		else
		{
			return $newNode->insertAsChildOf($this->getParent());
		}
	}

	/**
	 * Makes a copy of the record, inserting it as the last child of the given
node's parent.
	 *
	 * @param   integer|array  $cid  The primary key value (or values) or the
record(s) to copy. 
	 *                               If null, the current record will be
copied
	 * 
	 * @return self|FOFTableNested	 The last copied node
	 */
	public function copy($cid = null)
	{
		//We have to cast the id as array, or the helper function will return an
empty set
		if($cid)
		{
			$cid = (array) $cid;
		}

        FOFUtilsArray::toInteger($cid);
		$k = $this->_tbl_key;

		if (count($cid) < 1)
		{
			if ($this->$k)
			{
				$cid = array($this->$k);
			}
			else
			{
				// Even if it's null, let's still create the record
				$this->create($this->getData());
				
				return $this;
			}
		}

		foreach ($cid as $item)
		{
			// Prevent load with id = 0

			if (!$item)
			{
				continue;
			}

			$this->load($item);
			
			$this->create($this->getData());
		}

		return $this;
	}

	/**
	 * Method to reset class properties to the defaults set in the class
	 * definition. It will ignore the primary key as well as any private class
	 * properties.
	 *
	 * @return void
	 */
	public function reset()
	{
		$this->resetTreeCache();

		parent::reset();
	}

	/**
	 * Insert the current node as a tree root. It is a good idea to never use
this method, instead providing a root node
	 * in your schema installation and then sticking to only one root.
	 *
	 * @return self
	 */
	public function insertAsRoot()
	{
        // You can't insert a node that is already saved i.e. the
table has an id
        if($this->getId())
        {
            throw new RuntimeException(__METHOD__.' can be only used
with new nodes');
        }

		// First we need to find the right value of the last parent, a.k.a. the
max(rgt) of the table
		$db = $this->getDbo();

		// Get the lft/rgt names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$query = $db->getQuery(true)
			->select('MAX(' . $fldRgt . ')')
			->from($db->qn($this->getTableName()));
		$maxRgt = $db->setQuery($query, 0, 1)->loadResult();

		if (empty($maxRgt))
		{
			$maxRgt = 0;
		}

		$this->lft = ++$maxRgt;
		$this->rgt = ++$maxRgt;

		$this->store();

		return $this;
	}

	/**
	 * Insert the current node as the first (leftmost) child of a parent node.
	 *
	 * WARNING: If it's an existing node it will be COPIED, not moved.
	 *
	 * @param FOFTableNested $parentNode The node which will become our parent
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function insertAsFirstChildOf(FOFTableNested &$parentNode)
	{
        if($parentNode->lft >= $parentNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the parent node');
        }

		// Get a reference to the database
		$db = $this->getDbo();

		// Get the field names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));
		$fldLft = $db->qn($this->getColumnAlias('lft'));

        // Nullify the PK, so a new record will be created
        $pk = $this->getKeyName();
        $this->$pk = null;

		// Get the value of the parent node's rgt
		$myLeft = $parentNode->lft;

		// Update my lft/rgt values
		$this->lft = $myLeft + 1;
		$this->rgt = $myLeft + 2;

		// Update parent node's right (we added two elements in there,
remember?)
		$parentNode->rgt += 2;

		// Wrap everything in a transaction
		$db->transactionStart();

		try
		{
			// Make a hole (2 queries)
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($fldLft . ' = ' . $fldLft . '+2')
				->where($fldLft . ' > ' . $db->q($myLeft));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($fldRgt . ' = ' . $fldRgt . '+ 2')
				->where($fldRgt . '>' . $db->q($myLeft));
			$db->setQuery($query)->execute();

			// Insert the new node
			$this->store();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			// Roll back the transaction on error
			$db->transactionRollback();

			throw $e;
		}

		return $this;
	}

	/**
	 * Insert the current node as the last (rightmost) child of a parent node.
	 *
	 * WARNING: If it's an existing node it will be COPIED, not moved.
	 *
	 * @param FOFTableNested $parentNode The node which will become our parent
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function insertAsLastChildOf(FOFTableNested &$parentNode)
	{
        if($parentNode->lft >= $parentNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the parent node');
        }

		// Get a reference to the database
		$db = $this->getDbo();

		// Get the field names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));
		$fldLft = $db->qn($this->getColumnAlias('lft'));

        // Nullify the PK, so a new record will be created
        $pk = $this->getKeyName();
        $this->$pk = null;

		// Get the value of the parent node's lft
		$myRight = $parentNode->rgt;

		// Update my lft/rgt values
		$this->lft = $myRight;
		$this->rgt = $myRight + 1;

		// Update parent node's right (we added two elements in there,
remember?)
		$parentNode->rgt += 2;

		// Wrap everything in a transaction
		$db->transactionStart();

		try
		{
			// Make a hole (2 queries)
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($fldRgt . ' = ' . $fldRgt . '+2')
				->where($fldRgt . '>=' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($fldLft . ' = ' . $fldLft . '+2')
				->where($fldLft . '>' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Insert the new node
			$this->store();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			// Roll back the transaction on error
			$db->transactionRollback();

			throw $e;
		}

		return $this;
	}

	/**
	 * Alias for insertAsLastchildOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $parentNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 */
	public function insertAsChildOf(FOFTableNested &$parentNode)
	{
		return $this->insertAsLastChildOf($parentNode);
	}

	/**
	 * Insert the current node to the left of (before) a sibling node
	 *
	 * WARNING: If it's an existing node it will be COPIED, not moved.
	 *
	 * @param FOFTableNested $siblingNode We will be inserted before this node
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function insertLeftOf(FOFTableNested &$siblingNode)
	{
        if($siblingNode->lft >= $siblingNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the sibling node');
        }

		// Get a reference to the database
		$db = $this->getDbo();

		// Get the field names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));
		$fldLft = $db->qn($this->getColumnAlias('lft'));

        // Nullify the PK, so a new record will be created
        $pk = $this->getKeyName();
        $this->$pk = null;

		// Get the value of the parent node's rgt
		$myLeft = $siblingNode->lft;

		// Update my lft/rgt values
		$this->lft = $myLeft;
		$this->rgt = $myLeft + 1;

		// Update sibling's lft/rgt values
		$siblingNode->lft += 2;
		$siblingNode->rgt += 2;

		$db->transactionStart();

		try
		{
			$db->setQuery(
				$db->getQuery(true)
					->update($db->qn($this->getTableName()))
					->set($fldLft . ' = ' . $fldLft . '+2')
					->where($fldLft . ' >= ' . $db->q($myLeft))
			)->execute();

			$db->setQuery(
				$db->getQuery(true)
					->update($db->qn($this->getTableName()))
					->set($fldRgt . ' = ' . $fldRgt . '+2')
					->where($fldRgt . ' > ' . $db->q($myLeft))
			)->execute();

			$this->store();

            // Commit the transaction
            $db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

		return $this;
	}

	/**
	 * Insert the current node to the right of (after) a sibling node
	 *
	 * WARNING: If it's an existing node it will be COPIED, not moved.
	 *
	 * @param FOFTableNested $siblingNode We will be inserted after this node
	 *
	 * @return $this for chaining
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function insertRightOf(FOFTableNested &$siblingNode)
	{
        if($siblingNode->lft >= $siblingNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the sibling node');
        }

		// Get a reference to the database
		$db = $this->getDbo();

		// Get the field names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));
		$fldLft = $db->qn($this->getColumnAlias('lft'));

        // Nullify the PK, so a new record will be created
        $pk = $this->getKeyName();
        $this->$pk = null;

		// Get the value of the parent node's lft
		$myRight = $siblingNode->rgt;

		// Update my lft/rgt values
		$this->lft = $myRight + 1;
		$this->rgt = $myRight + 2;

		$db->transactionStart();

		try
		{
			$db->setQuery(
				$db->getQuery(true)
					->update($db->qn($this->getTableName()))
					->set($fldRgt . ' = ' . $fldRgt . '+2')
					->where($fldRgt . ' > ' . $db->q($myRight))
			)->execute();

			$db->setQuery(
				$db->getQuery(true)
					->update($db->qn($this->getTableName()))
					->set($fldLft . ' = ' . $fldLft . '+2')
					->where($fldLft . ' > ' . $db->q($myRight))
			)->execute();

			$this->store();

            // Commit the transaction
            $db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

		return $this;
	}

	/**
	 * Alias for insertRightOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 */
	public function insertAsSiblingOf(FOFTableNested &$siblingNode)
	{
		return $this->insertRightOf($siblingNode);
	}

	/**
	 * Move the current node (and its subtree) one position to the left in the
tree, i.e. before its left-hand sibling
	 *
     * @throws  RuntimeException
     *
	 * @return $this
	 */
	public function moveLeft()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

		// If it is a root node we will not move the node (roots don't
participate in tree ordering)
		if ($this->isRoot())
		{
			return $this;
		}

		// Are we already the leftmost node?
		$parentNode = $this->getParent();

		if ($parentNode->lft == ($this->lft - 1))
		{
			return $this;
		}

		// Get the sibling to the left
		$db = $this->getDbo();
		$table = $this->getClone();
		$table->reset();
		$leftSibling =
$table->whereRaw($db->qn($this->getColumnAlias('rgt')) .
' = ' . $db->q($this->lft - 1))
			->get(0, 1)->current();

		// Move the node
		if (is_object($leftSibling) && ($leftSibling instanceof
FOFTableNested))
		{
			return $this->moveToLeftOf($leftSibling);
		}

		return false;
	}

	/**
	 * Move the current node (and its subtree) one position to the right in
the tree, i.e. after its right-hand sibling
     *
     * @throws RuntimeException
	 *
	 * @return $this
	 */
	public function moveRight()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

		// If it is a root node we will not move the node (roots don't
participate in tree ordering)
		if ($this->isRoot())
		{
			return $this;
		}

		// Are we already the rightmost node?
		$parentNode = $this->getParent();

		if ($parentNode->rgt == ($this->rgt + 1))
		{
			return $this;
		}

		// Get the sibling to the right
		$db = $this->getDbo();

		$table = $this->getClone();
		$table->reset();
		$rightSibling =
$table->whereRaw($db->qn($this->getColumnAlias('lft')) .
' = ' . $db->q($this->rgt + 1))
			->get(0, 1)->current();

		// Move the node
		if (is_object($rightSibling) && ($rightSibling instanceof
FOFTableNested))
		{
			return $this->moveToRightOf($rightSibling);
		}

		return false;
	}

	/**
	 * Moves the current node (and its subtree) to the left of another node.
The other node can be in a different
	 * position in the tree or even under a different root.
	 *
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function moveToLeftOf(FOFTableNested $siblingNode)
	{
        // Sanity checks on current and sibling node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

        if($siblingNode->lft >= $siblingNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the sibling node');
        }

		$db    = $this->getDbo();
		$left  = $db->qn($this->getColumnAlias('lft'));
		$right = $db->qn($this->getColumnAlias('rgt'));

		// Get node metrics
		$myLeft  = $this->lft;
		$myRight = $this->rgt;
		$myWidth = $myRight - $myLeft + 1;

		// Get sibling metrics
		$sibLeft = $siblingNode->lft;

		// Start the transaction
		$db->transactionStart();

		try
		{
			// Temporary remove subtree being moved
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set("$left = " . $db->q(0) . " - $left")
				->set("$right = " . $db->q(0) . " - $right")
				->where($left . ' >= ' . $db->q($myLeft))
				->where($right . ' <= ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Close hole left behind
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' - ' .
$db->q($myWidth))
				->where($left . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' - ' .
$db->q($myWidth))
				->where($right . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Make a hole for the new items
			$newSibLeft = ($sibLeft > $myRight) ? $sibLeft - $myWidth : $sibLeft;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' + ' .
$db->q($myWidth))
				->where($right . ' >= ' . $db->q($newSibLeft));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' + ' .
$db->q($myWidth))
				->where($left . ' >= ' . $db->q($newSibLeft));
			$db->setQuery($query)->execute();

			// Move node and subnodes
			$moveRight = $newSibLeft - $myLeft;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $db->q(0) . ' - ' .
$left . ' + ' . $db->q($moveRight))
				->set($right . ' = ' . $db->q(0) . ' - ' .
$right . ' + ' . $db->q($moveRight))
				->where($left . ' <= 0 - ' . $db->q($myLeft))
				->where($right . ' >= 0 - ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

        // Let's load the record again to fetch the new values for lft
and rgt
        $this->load();

		return $this;
	}

	/**
	 * Moves the current node (and its subtree) to the right of another node.
The other node can be in a different
	 * position in the tree or even under a different root.
	 *
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function moveToRightOf(FOFTableNested $siblingNode)
	{
        // Sanity checks on current and sibling node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

        if($siblingNode->lft >= $siblingNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the sibling node');
        }

		$db    = $this->getDbo();
		$left  = $db->qn($this->getColumnAlias('lft'));
		$right = $db->qn($this->getColumnAlias('rgt'));

		// Get node metrics
		$myLeft  = $this->lft;
		$myRight = $this->rgt;
		$myWidth = $myRight - $myLeft + 1;

		// Get parent metrics
		$sibRight = $siblingNode->rgt;

		// Start the transaction
		$db->transactionStart();

		try
		{
			// Temporary remove subtree being moved
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set("$left = " . $db->q(0) . " - $left")
				->set("$right = " . $db->q(0) . " - $right")
				->where($left . ' >= ' . $db->q($myLeft))
				->where($right . ' <= ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Close hole left behind
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' - ' .
$db->q($myWidth))
				->where($left . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' - ' .
$db->q($myWidth))
				->where($right . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Make a hole for the new items
			$newSibRight = ($sibRight > $myRight) ? $sibRight - $myWidth :
$sibRight;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' + ' .
$db->q($myWidth))
				->where($left . ' > ' . $db->q($newSibRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' + ' .
$db->q($myWidth))
				->where($right . ' > ' . $db->q($newSibRight));
			$db->setQuery($query)->execute();

			// Move node and subnodes
			$moveRight = ($sibRight > $myRight) ? $sibRight - $myRight :
$sibRight - $myRight + $myWidth;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $db->q(0) . ' - ' .
$left . ' + ' . $db->q($moveRight))
				->set($right . ' = ' . $db->q(0) . ' - ' .
$right . ' + ' . $db->q($moveRight))
				->where($left . ' <= 0 - ' . $db->q($myLeft))
				->where($right . ' >= 0 - ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

        // Let's load the record again to fetch the new values for lft
and rgt
        $this->load();

		return $this;
	}

	/**
	 * Alias for moveToRightOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 */
	public function makeNextSiblingOf(FOFTableNested $siblingNode)
	{
		return $this->moveToRightOf($siblingNode);
	}

	/**
	 * Alias for makeNextSiblingOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 */
	public function makeSiblingOf(FOFTableNested $siblingNode)
	{
		return $this->makeNextSiblingOf($siblingNode);
	}

	/**
	 * Alias for moveToLeftOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 */
	public function makePreviousSiblingOf(FOFTableNested $siblingNode)
	{
		return $this->moveToLeftOf($siblingNode);
	}

	/**
	 * Moves a node and its subtree as a the first (leftmost) child of
$parentNode
	 *
	 * @param FOFTableNested $parentNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 */
	public function makeFirstChildOf(FOFTableNested $parentNode)
	{
        // Sanity checks on current and sibling node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

        if($parentNode->lft >= $parentNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the parent node');
        }

		$db    = $this->getDbo();
		$left  = $db->qn($this->getColumnAlias('lft'));
		$right = $db->qn($this->getColumnAlias('rgt'));

		// Get node metrics
		$myLeft  = $this->lft;
		$myRight = $this->rgt;
		$myWidth = $myRight - $myLeft + 1;

		// Get parent metrics
		$parentLeft = $parentNode->lft;

		// Start the transaction
		$db->transactionStart();

		try
		{
			// Temporary remove subtree being moved
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set("$left = " . $db->q(0) . " - $left")
				->set("$right = " . $db->q(0) . " - $right")
				->where($left . ' >= ' . $db->q($myLeft))
				->where($right . ' <= ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Close hole left behind
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' - ' .
$db->q($myWidth))
				->where($left . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' - ' .
$db->q($myWidth))
				->where($right . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Make a hole for the new items
			$newParentLeft = ($parentLeft > $myRight) ? $parentLeft - $myWidth :
$parentLeft;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' + ' .
$db->q($myWidth))
				->where($right . ' >= ' . $db->q($newParentLeft));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' + ' .
$db->q($myWidth))
				->where($left . ' > ' . $db->q($newParentLeft));
			$db->setQuery($query)->execute();

			// Move node and subnodes
			$moveRight = $newParentLeft - $myLeft + 1;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $db->q(0) . ' - ' .
$left . ' + ' . $db->q($moveRight))
				->set($right . ' = ' . $db->q(0) . ' - ' .
$right . ' + ' . $db->q($moveRight))
				->where($left . ' <= 0 - ' . $db->q($myLeft))
				->where($right . ' >= 0 - ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

        // Let's load the record again to fetch the new values for lft
and rgt
        $this->load();

		return $this;
	}

	/**
	 * Moves a node and its subtree as a the last (rightmost) child of
$parentNode
	 *
	 * @param FOFTableNested $parentNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function makeLastChildOf(FOFTableNested $parentNode)
	{
        // Sanity checks on current and sibling node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

        if($parentNode->lft >= $parentNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the parent node');
        }

		$db    = $this->getDbo();
		$left  = $db->qn($this->getColumnAlias('lft'));
		$right = $db->qn($this->getColumnAlias('rgt'));

		// Get node metrics
		$myLeft  = $this->lft;
		$myRight = $this->rgt;
		$myWidth = $myRight - $myLeft + 1;

		// Get parent metrics
		$parentRight = $parentNode->rgt;

		// Start the transaction
		$db->transactionStart();

		try
		{
			// Temporary remove subtree being moved
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set("$left = " . $db->q(0) . " - $left")
				->set("$right = " . $db->q(0) . " - $right")
				->where($left . ' >= ' . $db->q($myLeft))
				->where($right . ' <= ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Close hole left behind
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' - ' .
$db->q($myWidth))
				->where($left . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' - ' .
$db->q($myWidth))
				->where($right . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Make a hole for the new items
			$newLeft = ($parentRight > $myRight) ? $parentRight - $myWidth :
$parentRight;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' + ' .
$db->q($myWidth))
				->where($left . ' >= ' . $db->q($newLeft));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' + ' .
$db->q($myWidth))
				->where($right . ' >= ' . $db->q($newLeft));
			$db->setQuery($query)->execute();

			// Move node and subnodes
			$moveRight = ($parentRight > $myRight) ? $parentRight - $myRight - 1
: $parentRight - $myRight - 1 + $myWidth;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $db->q(0) . ' - ' .
$left . ' + ' . $db->q($moveRight))
				->set($right . ' = ' . $db->q(0) . ' - ' .
$right . ' + ' . $db->q($moveRight))
				->where($left . ' <= 0 - ' . $db->q($myLeft))
				->where($right . ' >= 0 - ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

        // Let's load the record again to fetch the new values for lft
and rgt
        $this->load();

		return $this;
	}

	/**
	 * Alias for makeLastChildOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $parentNode
	 *
	 * @return $this for chaining
	 */
	public function makeChildOf(FOFTableNested $parentNode)
	{
		return $this->makeLastChildOf($parentNode);
	}

	/**
	 * Makes the current node a root (and moving its entire subtree along the
way). This is achieved by moving the node
	 * to the right of its root node
	 *
	 * @return  $this  for chaining
	 */
	public function makeRoot()
	{
		// Make sure we are not a root
		if ($this->isRoot())
		{
			return $this;
		}

		// Get a reference to my root
		$myRoot = $this->getRoot();

		// Double check I am not a root
		if ($this->equals($myRoot))
		{
			return $this;
		}

		// Move myself to the right of my root
		$this->moveToRightOf($myRoot);
		$this->treeDepth = 0;

		return $this;
	}

	/**
	 * Gets the level (depth) of this node in the tree. The result is cached
in $this->treeDepth for faster retrieval.
	 *
     * @throws  RuntimeException
     *
	 * @return int|mixed
	 */
	public function getLevel()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

		if (is_null($this->treeDepth))
		{
			$db = $this->getDbo();

			$fldLft = $db->qn($this->getColumnAlias('lft'));
			$fldRgt = $db->qn($this->getColumnAlias('rgt'));

			$query = $db->getQuery(true)
				->select('(COUNT(' . $db->qn('parent') .
'.' . $fldLft . ') - 1) AS ' .
$db->qn('depth'))
				->from($db->qn($this->getTableName()) . ' AS ' .
$db->qn('node'))
				->join('CROSS', $db->qn($this->getTableName()) .
' AS ' . $db->qn('parent'))
				->where($db->qn('node') . '.' . $fldLft .
' >= ' . $db->qn('parent') . '.' .
$fldLft)
				->where($db->qn('node') . '.' . $fldLft .
' <= ' . $db->qn('parent') . '.' .
$fldRgt)
				->where($db->qn('node') . '.' . $fldLft .
' = ' . $db->q($this->lft))
				->group($db->qn('node') . '.' . $fldLft)
				->order($db->qn('node') . '.' . $fldLft .
' ASC');

			$this->treeDepth = $db->setQuery($query, 0, 1)->loadResult();
		}

		return $this->treeDepth;
	}

	/**
	 * Returns the immediate parent of the current node
	 *
     * @throws RuntimeException
     *
	 * @return FOFTableNested
	 */
	public function getParent()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

		if ($this->isRoot())
		{
			return $this;
		}

		if (empty($this->treeParent) || !is_object($this->treeParent) ||
!($this->treeParent instanceof FOFTableNested))
		{
			$db = $this->getDbo();

			$fldLft = $db->qn($this->getColumnAlias('lft'));
			$fldRgt = $db->qn($this->getColumnAlias('rgt'));

			$query = $db->getQuery(true)
				->select($db->qn('parent') . '.' . $fldLft)
				->from($db->qn($this->getTableName()) . ' AS ' .
$db->qn('node'))
				->join('CROSS', $db->qn($this->getTableName()) .
' AS ' . $db->qn('parent'))
				->where($db->qn('node') . '.' . $fldLft .
' > ' . $db->qn('parent') . '.' .
$fldLft)
				->where($db->qn('node') . '.' . $fldLft .
' <= ' . $db->qn('parent') . '.' .
$fldRgt)
				->where($db->qn('node') . '.' . $fldLft .
' = ' . $db->q($this->lft))
				->order($db->qn('parent') . '.' . $fldLft .
' DESC');
			$targetLft = $db->setQuery($query, 0, 1)->loadResult();

			$table = $this->getClone();
			$table->reset();
			$this->treeParent = $table
				->whereRaw($fldLft . ' = ' . $db->q($targetLft))
				->get()->current();
		}

		return $this->treeParent;
	}

	/**
	 * Is this a top-level root node?
	 *
	 * @return bool
	 */
	public function isRoot()
	{
		// If lft=1 it is necessarily a root node
		if ($this->lft == 1)
		{
			return true;
		}

		// Otherwise make sure its level is 0
		return $this->getLevel() == 0;
	}

	/**
	 * Is this a leaf node (a node without children)?
	 *
     * @throws  RuntimeException
     *
	 * @return bool
	 */
	public function isLeaf()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

		return ($this->rgt - 1) == $this->lft;
	}

	/**
	 * Is this a child node (not root)?
	 *
     * @codeCoverageIgnore
     *
	 * @return bool
	 */
	public function isChild()
	{
		return !$this->isRoot();
	}

	/**
	 * Returns true if we are a descendant of $otherNode
	 *
	 * @param FOFTableNested $otherNode
	 *
     * @throws  RuntimeException
     *
	 * @return bool
	 */
	public function isDescendantOf(FOFTableNested $otherNode)
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

        if($otherNode->lft >= $otherNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the other node');
        }

		return ($otherNode->lft < $this->lft) &&
($otherNode->rgt > $this->rgt);
	}

	/**
	 * Returns true if $otherNode is ourselves or if we are a descendant of
$otherNode
	 *
	 * @param FOFTableNested $otherNode
	 *
     * @throws  RuntimeException
     *
	 * @return bool
	 */
	public function isSelfOrDescendantOf(FOFTableNested $otherNode)
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

        if($otherNode->lft >= $otherNode->rgt)
        {
            throw new RuntimeException('Invalid position values for
the other node');
        }

		return ($otherNode->lft <= $this->lft) &&
($otherNode->rgt >= $this->rgt);
	}

	/**
	 * Returns true if we are an ancestor of $otherNode
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $otherNode
	 *
	 * @return bool
	 */
	public function isAncestorOf(FOFTableNested $otherNode)
	{
		return $otherNode->isDescendantOf($this);
	}

	/**
	 * Returns true if $otherNode is ourselves or we are an ancestor of
$otherNode
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $otherNode
	 *
	 * @return bool
	 */
	public function isSelfOrAncestorOf(FOFTableNested $otherNode)
	{
		return $otherNode->isSelfOrDescendantOf($this);
	}

	/**
	 * Is $node this very node?
	 *
	 * @param FOFTableNested $node
	 *
     * @throws  RuntimeException
     *
	 * @return bool
	 */
	public function equals(FOFTableNested &$node)
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

        if($node->lft >= $node->rgt)
        {
            throw new RuntimeException('Invalid position values for
the other node');
        }

		return (
			($this->getId() == $node->getId())
			&& ($this->lft == $node->lft)
			&& ($this->rgt == $node->rgt)
		);
	}

	/**
	 * Alias for isDescendantOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $otherNode
     *
	 * @return bool
	 */
	public function insideSubtree(FOFTableNested $otherNode)
	{
		return $this->isDescendantOf($otherNode);
	}

	/**
	 * Returns true if both this node and $otherNode are root, leaf or child
(same tree scope)
	 *
	 * @param FOFTableNested $otherNode
	 *
	 * @return bool
	 */
	public function inSameScope(FOFTableNested $otherNode)
	{
		if ($this->isLeaf())
		{
			return $otherNode->isLeaf();
		}
		elseif ($this->isRoot())
		{
			return $otherNode->isRoot();
		}
		elseif ($this->isChild())
		{
			return $otherNode->isChild();
		}
		else
		{
			return false;
		}
	}

	/**
	 * get() will return all ancestor nodes and ourselves
	 *
	 * @return void
	 */
	protected function scopeAncestorsAndSelf()
	{
		$this->treeNestedGet = true;

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('parent') . '.' .
$fldLft . ' >= ' . $db->qn('node') .
'.' . $fldLft);
		$this->whereRaw($db->qn('parent') . '.' .
$fldLft . ' <= ' . $db->qn('node') .
'.' . $fldRgt);
		$this->whereRaw($db->qn('parent') . '.' .
$fldLft . ' = ' . $db->q($this->lft));
	}

	/**
	 * get() will return all ancestor nodes but not ourselves
	 *
	 * @return void
	 */
	protected function scopeAncestors()
	{
		$this->treeNestedGet = true;

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('parent') . '.' .
$fldLft . ' > ' . $db->qn('node') . '.'
. $fldLft);
		$this->whereRaw($db->qn('parent') . '.' .
$fldLft . ' < ' . $db->qn('node') . '.'
. $fldRgt);
		$this->whereRaw($db->qn('parent') . '.' .
$fldLft . ' = ' . $db->q($this->lft));
	}

	/**
	 * get() will return all sibling nodes and ourselves
	 *
	 * @return void
	 */
	protected function scopeSiblingsAndSelf()
	{
		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$parent = $this->getParent();
		$this->whereRaw($db->qn('node') . '.' . $fldLft
. ' > ' . $db->q($parent->lft));
		$this->whereRaw($db->qn('node') . '.' . $fldRgt
. ' < ' . $db->q($parent->rgt));
	}

	/**
	 * get() will return all sibling nodes but not ourselves
	 *
     * @codeCoverageIgnore
     *
	 * @return void
	 */
	protected function scopeSiblings()
	{
		$this->scopeSiblingsAndSelf();
		$this->scopeWithoutSelf();
	}

	/**
	 * get() will return only leaf nodes
	 *
	 * @return void
	 */
	protected function scopeLeaves()
	{
		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('node') . '.' . $fldLft
. ' = ' . $db->qn('node') . '.' . $fldRgt
. ' - ' . $db->q(1));
	}

	/**
	 * get() will return all descendants (even subtrees of subtrees!) and
ourselves
	 *
	 * @return void
	 */
	protected function scopeDescendantsAndSelf()
	{
		$this->treeNestedGet = true;

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('node') . '.' . $fldLft
. ' >= ' . $db->qn('parent') . '.' .
$fldLft);
		$this->whereRaw($db->qn('node') . '.' . $fldLft
. ' <= ' . $db->qn('parent') . '.' .
$fldRgt);
		$this->whereRaw($db->qn('parent') . '.' .
$fldLft . ' = ' . $db->q($this->lft));
	}

	/**
	 * get() will return all descendants (even subtrees of subtrees!) but not
ourselves
	 *
	 * @return void
	 */
	protected function scopeDescendants()
	{
		$this->treeNestedGet = true;

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('node') . '.' . $fldLft
. ' > ' . $db->qn('parent') . '.' .
$fldLft);
		$this->whereRaw($db->qn('node') . '.' . $fldLft
. ' < ' . $db->qn('parent') . '.' .
$fldRgt);
		$this->whereRaw($db->qn('parent') . '.' .
$fldLft . ' = ' . $db->q($this->lft));
	}

	/**
	 * get() will only return immediate descendants (first level children) of
the current node
	 *
	 * @return void
	 */
	protected function scopeImmediateDescendants()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$subQuery = $db->getQuery(true)
			->select(array(
				$db->qn('node') . '.' . $fldLft,
				'(COUNT(*) - 1) AS ' . $db->qn('depth')
			))
			->from($db->qn($this->getTableName()) . ' AS ' .
$db->qn('node'))
			->from($db->qn($this->getTableName()) . ' AS ' .
$db->qn('parent'))
			->where($db->qn('node') . '.' . $fldLft .
' >= ' . $db->qn('parent') . '.' .
$fldLft)
			->where($db->qn('node') . '.' . $fldLft .
' <= ' . $db->qn('parent') . '.' .
$fldRgt)
			->where($db->qn('node') . '.' . $fldLft .
' = ' . $db->q($this->lft))
			->group($db->qn('node') . '.' . $fldLft)
			->order($db->qn('node') . '.' . $fldLft .
' ASC');

		$query = $db->getQuery(true)
			->select(array(
				$db->qn('node') . '.' . $fldLft,
				'(COUNT(' . $db->qn('parent') . '.' .
$fldLft . ') - (' .
				$db->qn('sub_tree') . '.' .
$db->qn('depth') . ' + 1)) AS ' .
$db->qn('depth')
			))
			->from($db->qn($this->getTableName()) . ' AS ' .
$db->qn('node'))
			->join('CROSS', $db->qn($this->getTableName()) .
' AS ' . $db->qn('parent'))
			->join('CROSS', $db->qn($this->getTableName()) .
' AS ' . $db->qn('sub_parent'))
			->join('CROSS', '(' . $subQuery . ') AS
' . $db->qn('sub_tree'))
			->where($db->qn('node') . '.' . $fldLft .
' >= ' . $db->qn('parent') . '.' .
$fldLft)
			->where($db->qn('node') . '.' . $fldLft .
' <= ' . $db->qn('parent') . '.' .
$fldRgt)
			->where($db->qn('node') . '.' . $fldLft .
' >= ' . $db->qn('sub_parent') . '.' .
$fldLft)
			->where($db->qn('node') . '.' . $fldLft .
' <= ' . $db->qn('sub_parent') . '.' .
$fldRgt)
			->where($db->qn('sub_parent') . '.' . $fldLft
. ' = ' . $db->qn('sub_tree') . '.' .
$fldLft)
			->group($db->qn('node') . '.' . $fldLft)
			->having(array(
				$db->qn('depth') . ' > ' . $db->q(0),
				$db->qn('depth') . ' <= ' . $db->q(1),
			))
			->order($db->qn('node') . '.' . $fldLft .
' ASC');

		$leftValues = $db->setQuery($query)->loadColumn();

		if (empty($leftValues))
		{
			$leftValues = array(0);
		}

		array_walk($leftValues, function (&$item, $key) use (&$db)
		{
			$item = $db->q($item);
		});

		$this->whereRaw($db->qn('node') . '.' . $fldLft
. ' IN (' . implode(',', $leftValues) . ')');
	}

	/**
	 * get() will not return the selected node if it's part of the query
results
	 *
	 * @param FOFTableNested $node The node to exclude from the results
	 *
	 * @return void
	 */
	public function withoutNode(FOFTableNested $node)
	{
		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));

		$this->whereRaw('NOT(' . $db->qn('node') .
'.' . $fldLft . ' = ' . $db->q($node->lft) .
')');
	}

	/**
	 * get() will not return ourselves if it's part of the query results
	 *
     * @codeCoverageIgnore
     *
	 * @return void
	 */
	protected function scopeWithoutSelf()
	{
		$this->withoutNode($this);
	}

	/**
	 * get() will not return our root if it's part of the query results
	 *
     * @codeCoverageIgnore
     *
	 * @return void
	 */
	protected function scopeWithoutRoot()
	{
		$rootNode = $this->getRoot();
		$this->withoutNode($rootNode);
	}

	/**
	 * Returns the root node of the tree this node belongs to
	 *
	 * @return self
	 *
	 * @throws \RuntimeException
	 */
	public function getRoot()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for
the current node');
        }

		// If this is a root node return itself (there is no such thing as the
root of a root node)
		if ($this->isRoot())
		{
			return $this;
		}

		if (empty($this->treeRoot) || !is_object($this->treeRoot) ||
!($this->treeRoot instanceof FOFTableNested))
		{
			$this->treeRoot = null;

			// First try to get the record with the minimum ID
			$db = $this->getDbo();

			$fldLft = $db->qn($this->getColumnAlias('lft'));
			$fldRgt = $db->qn($this->getColumnAlias('rgt'));

			$subQuery = $db->getQuery(true)
				->select('MIN(' . $fldLft . ')')
				->from($db->qn($this->getTableName()));

			try
			{
				$table = $this->getClone();
				$table->reset();
				$root = $table
					->whereRaw($fldLft . ' = (' . (string)$subQuery .
')')
					->get(0, 1)->current();

				if ($this->isDescendantOf($root))
				{
					$this->treeRoot = $root;
				}
			}
			catch (\RuntimeException $e)
			{
				// If there is no root found throw an exception. Basically: your table
is FUBAR.
				throw new \RuntimeException("No root found for table " .
$this->getTableName() . ", node lft=" . $this->lft);
			}

			// If the above method didn't work, get all roots and select the
one with the appropriate lft/rgt values
			if (is_null($this->treeRoot))
			{
				// Find the node with depth = 0, lft < our lft and rgt > our
right. That's our root node.
				$query = $db->getQuery(true)
					->select(array(
                        $db->qn('node') . '.' .
$fldLft,
						'(COUNT(' . $db->qn('parent') . '.'
. $fldLft . ') - 1) AS ' . $db->qn('depth')
					))
					->from($db->qn($this->getTableName()) . ' AS ' .
$db->qn('node'))
					->join('CROSS', $db->qn($this->getTableName()) .
' AS ' . $db->qn('parent'))
					->where($db->qn('node') . '.' . $fldLft .
' >= ' . $db->qn('parent') . '.' .
$fldLft)
					->where($db->qn('node') . '.' . $fldLft .
' <= ' . $db->qn('parent') . '.' .
$fldRgt)
					->where($db->qn('node') . '.' . $fldLft .
' < ' . $db->q($this->lft))
					->where($db->qn('node') . '.' . $fldRgt .
' > ' . $db->q($this->rgt))
					->having($db->qn('depth') . ' = ' .
$db->q(0))
					->group($db->qn('node') . '.' . $fldLft);

				// Get the lft value
				$targetLeft = $db->setQuery($query)->loadResult();

				if (empty($targetLeft))
				{
					// If there is no root found throw an exception. Basically: your table
is FUBAR.
					throw new \RuntimeException("No root found for table " .
$this->getTableName() . ", node lft=" . $this->lft);
				}

				try
				{
					$table = $this->getClone();
					$table->reset();
					$this->treeRoot = $table
						->whereRaw($fldLft . ' = ' . $db->q($targetLeft))
						->get(0, 1)->current();
				}
				catch (\RuntimeException $e)
				{
					// If there is no root found throw an exception. Basically: your table
is FUBAR.
					throw new \RuntimeException("No root found for table " .
$this->getTableName() . ", node lft=" . $this->lft);
				}
			}
		}

		return $this->treeRoot;
	}

	/**
	 * Get all ancestors to this node and the node itself. In other words it
gets the full path to the node and the node
	 * itself.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getAncestorsAndSelf()
	{
		$this->scopeAncestorsAndSelf();

		return $this->get();
	}

	/**
	 * Get all ancestors to this node and the node itself, but not the root
node. If you want to
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getAncestorsAndSelfWithoutRoot()
	{
		$this->scopeAncestorsAndSelf();
		$this->scopeWithoutRoot();

		return $this->get();
	}

	/**
	 * Get all ancestors to this node but not the node itself. In other words
it gets the path to the node, without the
	 * node itself.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getAncestors()
	{
		$this->scopeAncestorsAndSelf();
		$this->scopeWithoutSelf();

		return $this->get();
	}

	/**
	 * Get all ancestors to this node but not the node itself and its root.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getAncestorsWithoutRoot()
	{
		$this->scopeAncestors();
		$this->scopeWithoutRoot();

		return $this->get();
	}

	/**
	 * Get all sibling nodes, including ourselves
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getSiblingsAndSelf()
	{
		$this->scopeSiblingsAndSelf();

		return $this->get();
	}

	/**
	 * Get all sibling nodes, except ourselves
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getSiblings()
	{
		$this->scopeSiblings();

		return $this->get();
	}

	/**
	 * Get all leaf nodes in the tree. You may want to use the scopes to
narrow down the search in a specific subtree or
	 * path.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getLeaves()
	{
		$this->scopeLeaves();

		return $this->get();
	}

	/**
	 * Get all descendant (children) nodes and ourselves.
	 *
	 * Note: all descendant nodes, even descendants of our immediate
descendants, will be returned.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getDescendantsAndSelf()
	{
		$this->scopeDescendantsAndSelf();

		return $this->get();
	}

	/**
	 * Get only our descendant (children) nodes, not ourselves.
	 *
	 * Note: all descendant nodes, even descendants of our immediate
descendants, will be returned.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getDescendants()
	{
		$this->scopeDescendants();

		return $this->get();
	}

	/**
	 * Get the immediate descendants (children). Unlike getDescendants it only
goes one level deep into the tree
	 * structure. Descendants of descendant nodes will not be returned.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getImmediateDescendants()
	{
		$this->scopeImmediateDescendants();

		return $this->get();
	}

	/**
	 * Returns a hashed array where each element's key is the value of
the $key column (default: the ID column of the
	 * table) and its value is the value of the $column column (default:
title). Each nesting level will have the value
	 * of the $column column prefixed by a number of $separator strings, as
many as its nesting level (depth).
	 *
	 * This is useful for creating HTML select elements showing the hierarchy
in a human readable format.
	 *
	 * @param string $column
	 * @param null   $key
	 * @param string $seperator
	 *
	 * @return array
	 */
	public function getNestedList($column = 'title', $key = null,
$seperator = '  ')
	{
		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		if (empty($key) || !$this->hasField($key))
		{
			$key = $this->getKeyName();
		}

		if (empty($column))
		{
			$column = 'title';
		}

		$fldKey = $db->qn($this->getColumnAlias($key));
		$fldColumn = $db->qn($this->getColumnAlias($column));

		$query = $db->getQuery(true)
			->select(array(
				$db->qn('node') . '.' . $fldKey,
				$db->qn('node') . '.' . $fldColumn,
				'(COUNT(' . $db->qn('parent') . '.' .
$fldKey . ') - 1) AS ' . $db->qn('depth')
			))
			->from($db->qn($this->getTableName()) . ' AS ' .
$db->qn('node'))
			->join('CROSS', $db->qn($this->getTableName()) .
' AS ' . $db->qn('parent'))
			->where($db->qn('node') . '.' . $fldLft .
' >= ' . $db->qn('parent') . '.' .
$fldLft)
			->where($db->qn('node') . '.' . $fldLft .
' <= ' . $db->qn('parent') . '.' .
$fldRgt)
			->group($db->qn('node') . '.' . $fldLft)
			->order($db->qn('node') . '.' . $fldLft .
' ASC');

		$tempResults = $db->setQuery($query)->loadAssocList();
		$ret = array();

		if (!empty($tempResults))
		{
			foreach ($tempResults as $row)
			{
				$ret[$row[$key]] = str_repeat($seperator, $row['depth']) .
$row[$column];
			}
		}

		return $ret;
	}

	/**
	 * Locate a node from a given path, e.g. "/some/other/leaf"
	 *
	 * Notes:
	 * - This will only work when you have a "slug" and a
"hash" field in your table.
	 * - If the path starts with "/" we will use the root with
lft=1. Otherwise the first component of the path is
	 *   supposed to be the slug of the root node.
	 * - If the root node is not found you'll get null as the return
value
	 * - You will also get null if any component of the path is not found
	 *
	 * @param string $path The path to locate
	 *
	 * @return FOFTableNested|null The found node or null if nothing is found
	 */
	public function findByPath($path)
	{
		// @todo
	}

	public function isValid()
	{
		// @todo
	}

	public function rebuild()
	{
		// @todo
	}

	/**
	 * Resets cached values used to speed up querying the tree
	 *
	 * @return  static  for chaining
	 */
	protected function resetTreeCache()
	{
		$this->treeDepth = null;
		$this->treeRoot = null;
		$this->treeParent = null;
		$this->treeNestedGet = false;

		return $this;
	}

	/**
	 * Add custom, pre-compiled WHERE clauses for use in buildQuery. The raw
WHERE clause you specify is added as is to
	 * the query generated by buildQuery. You are responsible for quoting and
escaping the field names and data found
	 * inside the WHERE clause.
	 *
	 * @param   string $rawWhereClause The raw WHERE clause to add
	 *
	 * @return  $this  For chaining
	 */
	public function whereRaw($rawWhereClause)
	{
		$this->whereClauses[] = $rawWhereClause;

		return $this;
	}

	/**
	 * Builds the query for the get() method
	 *
	 * @return FOFDatabaseQuery
	 */
	protected function buildQuery()
	{
		$db = $this->getDbo();

		$query = $db->getQuery(true)
			->select($db->qn('node') . '.*')
			->from($db->qn($this->getTableName()) . ' AS ' .
$db->qn('node'));

		if ($this->treeNestedGet)
		{
			$query
				->join('CROSS', $db->qn($this->getTableName()) .
' AS ' . $db->qn('parent'));
		}

		// Apply custom WHERE clauses
		if (count($this->whereClauses))
		{
			foreach ($this->whereClauses as $clause)
			{
				$query->where($clause);
			}
		}

		return $query;
	}

	/**
	 * Returns a database iterator to retrieve records. Use the scope methods
and the whereRaw method to define what
	 * exactly will be returned.
	 *
	 * @param   integer $limitstart How many items to skip from the start,
only when $overrideLimits = true
	 * @param   integer $limit      How many items to return, only when
$overrideLimits = true
	 *
	 * @return  FOFDatabaseIterator  The data collection
	 */
	public function get($limitstart = 0, $limit = 0)
	{
		$limitstart = max($limitstart, 0);
		$limit = max($limit, 0);

		$query = $this->buildQuery();
		$db = $this->getDbo();
		$db->setQuery($query, $limitstart, $limit);
		$cursor = $db->execute();

		$dataCollection = FOFDatabaseIterator::getIterator($db->name, $cursor,
null, $this->config['_table_class']);

		return $dataCollection;
	}
}
PK!�=���
relations.phpnu�[���<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba
Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 * @note        This file has been modified by the Joomla! Project and no
longer reflects the original work of its author.
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

class FOFTableRelations
{
	/**
	 * Holds all known relation definitions
	 *
	 * @var   array
	 */
	protected $relations = array(
		'child'		=> array(),
		'parent'	=> array(),
		'children'	=> array(),
		'multiple'	=> array(),
	);

	/**
	 * Holds the default relations' keys
	 *
	 * @var  array
	 */
	protected $defaultRelation = array(
		'child'		=> null,
		'parent'	=> null,
		'children'	=> null,
		'multiple'	=> null,
	);

	/**
	 * The table these relations are attached to
	 *
	 * @var   FOFTable
	 */
	protected $table = null;

	/**
	 * The name of the component used by our attached table
	 *
	 * @var   string
	 */
	protected $componentName = 'joomla';

	/**
	 * The type (table name without prefix and component name) of our attached
table
	 *
	 * @var   string
	 */
	protected $tableType = '';


	/**
	 * Create a relations object based on the provided FOFTable instance
	 *
	 * @param   FOFTable   $table  The table instance used to initialise the
relations
	 */
	public function __construct(FOFTable $table)
	{
		// Store the table
		$this->table = $table;

		// Get the table's type from its name
		$tableName = $table->getTableName();
		$tableName = str_replace('#__', '', $tableName);
		$type = explode("_", $tableName);

		if (count($type) == 1)
		{
			$this->tableType = array_pop($type);
		}
		else
		{
			$this->componentName = array_shift($type);
			$this->tableType = array_pop($type);
		}

		$this->tableType = FOFInflector::singularize($this->tableType);

		$tableKey = $table->getKeyName();

		unset($type);

		// Scan all table keys and look for foo_bar_id fields. These fields are
used to populate parent relations.
		foreach ($table->getKnownFields() as $field)
		{
			// Skip the table key name
			if ($field == $tableKey)
			{
				continue;
			}

			if (substr($field, -3) != '_id')
			{
				continue;
			}

			$parts = explode('_', $field);

			// If the component type of the field is not set assume
'joomla'
			if (count($parts) == 2)
			{
				array_unshift($parts, 'joomla');
			}

			// Sanity check
			if (count($parts) != 3)
			{
				continue;
			}

			// Make sure we skip any references back to ourselves (should be
redundant, due to key field check above)
			if ($parts[1] == $this->tableType)
			{
				continue;
			}

			// Default item name: the name of the table, singular
			$itemName = FOFInflector::singularize($parts[1]);

			// Prefix the item name with the component name if we refer to a
different component
			if ($parts[0] != $this->componentName)
			{
				$itemName = $parts[0] . '_' . $itemName;
			}

			// Figure out the table class
			$tableClass = ucfirst($parts[0]) . 'Table' .
ucfirst($parts[1]);

			$default = empty($this->relations['parent']);

			$this->addParentRelation($itemName, $tableClass, $field, $field,
$default);
		}

		// Get the relations from the configuration provider
		$key = $table->getConfigProviderKey() . '.relations';
		$configRelations = $table->getConfigProvider()->get($key, array());

		if (!empty($configRelations))
		{
			foreach ($configRelations as $relation)
			{
				if (empty($relation['type']))
				{
					continue;
				}

				if (isset($relation['pivotTable']))
				{
					$this->addMultipleRelation($relation['itemName'],
$relation['tableClass'],
						$relation['localKey'], $relation['ourPivotKey'],
$relation['theirPivotKey'],
						$relation['remoteKey'], $relation['pivotTable'],
$relation['default']);
				}
				else
				{
					$method = 'add' . ucfirst($relation['type']).
'Relation';

					if (!method_exists($this, $method))
					{
						continue;
					}

					$this->$method($relation['itemName'],
$relation['tableClass'],
						$relation['localKey'], $relation['remoteKey'],
$relation['default']);
				}
			}
		}

	}

	/**
	 * Add a 1:1 forward (child) relation. This adds relations for the
getChild() method.
	 *
	 * In other words: does a table HAVE ONE child
	 *
	 * Parent and child relations works the same way. We have them separated
as it makes more sense for us humans to
	 * read code like $item->getParent() and $item->getChild() than
$item->getRelatedObject('someRandomKeyName')
	 *
	 * @param   string   $itemName    is how it will be known locally to the
getRelatedItem method (singular)
	 * @param   string   $tableClass  if skipped it is defined automatically
as ComponentnameTableItemname
	 * @param   string   $localKey    is the column containing our side of the
FK relation, default: our primary key
	 * @param   string   $remoteKey   is the remote table's FK column,
default: componentname_itemname_id
	 * @param   boolean  $default     add as the default child relation?
	 *
	 * @return  void
	 */
	public function addChildRelation($itemName, $tableClass = null, $localKey
= null, $remoteKey = null, $default = true)
	{
		$itemName = $this->normaliseItemName($itemName, false);

		if (empty($localKey))
		{
			$localKey = $this->table->getKeyName();
		}

		$this->addBespokeSimpleRelation('child', $itemName,
$tableClass, $localKey, $remoteKey, $default);
	}

	/**
	 * Defining an inverse 1:1 (parent) relation. You must specify at least
the $tableClass or the $localKey.
	 * This adds relations for the getParent() method.
	 *
	 * In other words: does a table BELONG TO ONE parent
	 *
	 * Parent and child relations works the same way. We have them separated
as it makes more sense for us humans to
	 * read code like $item->getParent() and $item->getChild() than
$item->getRelatedObject('someRandomKeyName')
	 *
	 * @param   string   $itemName    is how it will be known locally to the
getRelatedItem method (singular)
	 * @param   string   $tableClass  if skipped it is defined automatically
as ComponentnameTableItemname
	 * @param   string   $localKey    is the column containing our side of the
FK relation, default: componentname_itemname_id
	 * @param   string   $remoteKey   is the remote table's FK column,
default: componentname_itemname_id
	 * @param   boolean  $default     Is this the default parent relationship?
	 *
	 * @return  void
	 */
	public function addParentRelation($itemName, $tableClass = null, $localKey
= null, $remoteKey = null, $default = true)
	{
		$itemName = $this->normaliseItemName($itemName, false);

		$this->addBespokeSimpleRelation('parent', $itemName,
$tableClass, $localKey, $remoteKey, $default);
	}

	/**
	 * Defining a forward 1:∞ (children) relation. This adds relations to
the getChildren() method.
	 *
	 * In other words: does a table HAVE MANY children?
	 *
	 * The children relation works very much the same as the parent and child
relation. The difference is that the
	 * parent and child relations return a single table object, whereas the
children relation returns an iterator to
	 * many objects.
	 *
	 * @param   string   $itemName    is how it will be known locally to the
getRelatedItems method (plural)
	 * @param   string   $tableClass  if skipped it is defined automatically
as ComponentnameTableItemname
	 * @param   string   $localKey    is the column containing our side of the
FK relation, default: our primary key
	 * @param   string   $remoteKey   is the remote table's FK column,
default: componentname_itemname_id
	 * @param   boolean  $default     is this the default children
relationship?
	 *
	 * @return  void
	 */
	public function addChildrenRelation($itemName, $tableClass = null,
$localKey = null, $remoteKey = null, $default = true)
	{
		$itemName = $this->normaliseItemName($itemName, true);

		if (empty($localKey))
		{
			$localKey = $this->table->getKeyName();
		}

		$this->addBespokeSimpleRelation('children', $itemName,
$tableClass, $localKey, $remoteKey, $default);
	}

	/**
	 * Defining a ∞:∞ (multiple) relation. This adds relations to the
getMultiple() method.
	 *
	 * In other words: is a table RELATED TO MANY other records?
	 *
	 * @param   string   $itemName       is how it will be known locally to
the getRelatedItems method (plural)
	 * @param   string   $tableClass     if skipped it is defined
automatically as ComponentnameTableItemname
	 * @param   string   $localKey       is the column containing our side of
the FK relation, default: our primary key field name
	 * @param   string   $ourPivotKey    is the column containing our side of
the FK relation in the pivot table, default: $localKey
	 * @param   string   $theirPivotKey  is the column containing the other
table's side of the FK relation in the pivot table, default $remoteKey
	 * @param   string   $remoteKey      is the remote table's FK column,
default: componentname_itemname_id
	 * @param   string   $glueTable      is the name of the glue (pivot)
table, default: #__componentname_thisclassname_itemname with plural items
(e.g. #__foobar_users_roles)
	 * @param   boolean  $default        is this the default multiple
relation?
	 */
	public function addMultipleRelation($itemName, $tableClass = null,
$localKey = null, $ourPivotKey = null, $theirPivotKey = null, $remoteKey =
null, $glueTable = null, $default = true)
	{
		$itemName = $this->normaliseItemName($itemName, true);

		if (empty($localKey))
		{
			$localKey = $this->table->getKeyName();
		}

		$this->addBespokePivotRelation('multiple', $itemName,
$tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey,
$glueTable, $default);
	}

	/**
	 * Removes a previously defined relation by name. You can optionally
specify the relation type.
	 *
	 * @param   string  $itemName  The name of the relation to remove
	 * @param   string  $type      [optional] The relation type (child,
parent, children, ...)
	 *
	 * @return  void
	 */
	public function removeRelation($itemName, $type = null)
	{
		$types = array_keys($this->relations);

		if (in_array($type, $types))
		{
			$types = array($type);
		}

		foreach ($types as $type)
		{
			foreach ($this->relations[$type] as $key => $relations)
			{
				if ($itemName == $key)
				{
					unset ($this->relations[$type][$itemName]);

                    // If it's the default one, remove it from the
default array, too
                    if($this->defaultRelation[$type] == $itemName)
                    {
                        $this->defaultRelation[$type] = null;
                    }

					return;
				}
			}
		}
	}

	/**
	 * Removes all existing relations
	 *
	 * @param   string  $type  The type or relations to remove, omit to remove
all relation types
	 *
	 * @return  void
	 */
	public function clearRelations($type = null)
	{
		$types = array_keys($this->relations);

		if (in_array($type, $types))
		{
			$types = array($type);
		}

		foreach ($types as $type)
		{
			$this->relations[$type] = array();

            // Remove the relation from the default stack, too
            $this->defaultRelation[$type] = null;
		}
	}

	/**
	 * Does the named relation exist? You can optionally specify the type.
	 *
	 * @param   string  $itemName  The name of the relation to check
	 * @param   string  $type      [optional] The relation type (child,
parent, children, ...)
	 *
	 * @return  boolean
	 */
	public function hasRelation($itemName, $type = null)
	{
		$types = array_keys($this->relations);

		if (in_array($type, $types))
		{
			$types = array($type);
		}

		foreach ($types as $type)
		{
			foreach ($this->relations[$type] as $key => $relations)
			{
				if ($itemName == $key)
				{
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Get the definition of a relation
	 *
	 * @param   string  $itemName  The name of the relation to check
	 * @param   string  $type      [optional] The relation type (child,
parent, children, ...)
	 *
	 * @return  array
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getRelation($itemName, $type)
	{
		$types = array_keys($this->relations);

		if (in_array($type, $types))
		{
			$types = array($type);
		}

		foreach ($types as $type)
		{
			foreach ($this->relations[$type] as $key => $relations)
			{
				if ($itemName == $key)
				{
					$temp         = $relations;
					$temp['type'] = $type;

					return $temp;
				}
			}
		}

		throw new RuntimeException("Relation $itemName not found in table
{$this->tableType}", 500);
	}

	/**
	 * Gets the item referenced by a named relation. You can optionally
specify the type. Only single item relation
	 * types will be searched.
	 *
	 * @param   string  $itemName  The name of the relation to use
	 * @param   string  $type      [optional] The relation type (child,
parent)
	 *
	 * @return  FOFTable
	 *
	 * @throws  RuntimeException  If the named relation doesn't exist or
isn't supposed to return single items
	 */
	public function getRelatedItem($itemName, $type = null)
	{
		if (empty($type))
		{
			$relation = $this->getRelation($itemName, $type);
			$type = $relation['type'];
		}

		switch ($type)
		{
			case 'parent':
				return $this->getParent($itemName);
				break;

			case 'child':
				return $this->getChild($itemName);
				break;

			default:
				throw new RuntimeException("Invalid relation type $type for
returning a single related item", 500);
				break;
		}
	}

	/**
	 * Gets the iterator for the items referenced by a named relation. You can
optionally specify the type. Only
	 * multiple item relation types will be searched.
	 *
	 * @param   string  $itemName  The name of the relation to use
	 * @param   string  $type      [optional] The relation type (children,
multiple)
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  RuntimeException  If the named relation doesn't exist or
isn't supposed to return single items
	 */
	public function getRelatedItems($itemName, $type = null)
	{
		if (empty($type))
		{
			$relation = $this->getRelation($itemName, $type);
			$type = $relation['type'];
		}

		switch ($type)
		{
			case 'children':
				return $this->getChildren($itemName);
				break;

			case 'multiple':
				return $this->getMultiple($itemName);
				break;

			case 'siblings':
				return $this->getSiblings($itemName);
				break;

			default:
				throw new RuntimeException("Invalid relation type $type for
returning a collection of related items", 500);
				break;
		}
	}

	/**
	 * Gets a parent item
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use,
skip to use the default parent relation
	 *
	 * @return  FOFTable
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getParent($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['parent'];
		}

		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default parent relation for %s
not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['parent'][$itemName]))
		{
			throw new RuntimeException(sprintf('Parent relation %s for %s not
found', $itemName, $this->table->getTableName()), 500);
		}

		return
$this->getTableFromRelation($this->relations['parent'][$itemName]);
	}

	/**
	 * Gets a child item
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use,
skip to use the default child relation
	 *
	 * @return  FOFTable
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getChild($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['child'];
		}

		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default child relation for %s
not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['child'][$itemName]))
		{
			throw new RuntimeException(sprintf('Child relation %s for %s not
found', $itemName, $this->table->getTableName()), 500);
		}

		return
$this->getTableFromRelation($this->relations['child'][$itemName]);
	}

	/**
	 * Gets an iterator for the children items
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use,
skip to use the default children relation
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getChildren($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['children'];
		}
		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default children relation for
%s not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['children'][$itemName]))
		{
			throw new RuntimeException(sprintf('Children relation %s for %s not
found', $itemName, $this->table->getTableName()), 500);
		}

		return
$this->getIteratorFromRelation($this->relations['children'][$itemName]);
	}

	/**
	 * Gets an iterator for the sibling items. This relation is inferred from
the parent relation. It returns all
	 * elements on the same table which have the same parent.
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use,
skip to use the default children relation
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getSiblings($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['parent'];
		}
		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default siblings relation for
%s not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['parent'][$itemName]))
		{
			throw new RuntimeException(sprintf('Sibling relation %s for %s not
found', $itemName, $this->table->getTableName()), 500);
		}

		// Get my table class
		$tableName = $this->table->getTableName();
		$tableName = str_replace('#__', '', $tableName);
		$tableNameParts = explode('_', $tableName, 2);
		$tableClass = ucfirst($tableNameParts[0]) . 'Table' .
ucfirst(FOFInflector::singularize($tableNameParts[1]));

		$parentRelation = $this->relations['parent'][$itemName];
		$relation = array(
			'tableClass'	=> $tableClass,
			'localKey'		=> $parentRelation['localKey'],
			'remoteKey'		=> $parentRelation['localKey'],
		);

		return $this->getIteratorFromRelation($relation);
	}

	/**
	 * Gets an iterator for the multiple items
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use,
skip to use the default multiple relation
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getMultiple($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['multiple'];
		}

		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default multiple relation for
%s not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['multiple'][$itemName]))
		{
			throw new RuntimeException(sprintf('Multiple relation %s for %s not
found', $itemName, $this->table->getTableName()), 500);
		}

		return
$this->getIteratorFromRelation($this->relations['multiple'][$itemName]);
	}

	/**
	 * Returns a FOFTable object based on a given relation
	 *
	 * @param   array    $relation   Indexed array holding relation
definition.
     *                                  tableClass => name of the
related table class
     *                                  localKey   => name of the local
key
     *                                  remoteKey  => name of the remote
key
	 *
	 * @return FOFTable
	 *
	 * @throws RuntimeException
     * @throws InvalidArgumentException
	 */
	protected function getTableFromRelation($relation)
	{
        // Sanity checks
        if(
            !isset($relation['tableClass']) ||
!isset($relation['remoteKey']) ||
!isset($relation['localKey']) ||
            !$relation['tableClass'] ||
!$relation['remoteKey'] || !$relation['localKey']
        )
        {
            throw new InvalidArgumentException('Missing array index
for the '.__METHOD__.' method. Please check method
signature', 500);
        }

		// Get a table object from the table class name
		$tableClass      = $relation['tableClass'];
		$tableClassParts = FOFInflector::explode($tableClass);

        if(count($tableClassParts) < 3)
        {
            throw new InvalidArgumentException('Invalid table class
named. It should be something like FooTableBar');
        }

		$table = FOFTable::getInstance($tableClassParts[2],
ucfirst($tableClassParts[0]) . ucfirst($tableClassParts[1]));

		// Get the table name
		$tableName = $table->getTableName();

		// Get the remote and local key names
		$remoteKey = $relation['remoteKey'];
		$localKey  = $relation['localKey'];

		// Get the local key's value
		$value = $this->table->$localKey;

        // If there's no value for the primary key, let's stop
here
        if(!$value)
        {
            throw new RuntimeException('Missing value for the primary
key of the table '.$this->table->getTableName(), 500);
        }

		// This is required to prevent one relation from killing the db cursor
used in a different relation...
		$oldDb = $this->table->getDbo();
		$oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE
THE DB OBJECT. ARGH!
		$db = clone $oldDb;

		$query = $db->getQuery(true)
			->select('*')
			->from($db->qn($tableName))
			->where($db->qn($remoteKey) . ' = ' .
$db->q($value));
		$db->setQuery($query, 0, 1);

		$data = $db->loadObject();

		if (!is_object($data))
		{
			throw new RuntimeException(sprintf('Cannot load item from relation
against table %s column %s', $tableName, $remoteKey), 500);
		}

		$table->bind($data);

		return $table;
	}

	/**
	 * Returns a FOFDatabaseIterator based on a given relation
	 *
	 * @param   array    $relation   Indexed array holding relation
definition.
     *                                  tableClass => name of the
related table class
     *                                  localKey   => name of the local
key
     *                                  remoteKey  => name of the remote
key
     *                                  pivotTable    => name of the
pivot table (optional)
     *                                  theirPivotKey => name of the
remote key in the pivot table (mandatory if pivotTable is set)
     *                                  ourPivotKey   => name of our key
in the pivot table (mandatory if pivotTable is set)
	 *
	 * @return FOFDatabaseIterator
	 *
	 * @throws RuntimeException
	 * @throws InvalidArgumentException
	 */
	protected function getIteratorFromRelation($relation)
	{
        // Sanity checks
        if(
            !isset($relation['tableClass']) ||
!isset($relation['remoteKey']) ||
!isset($relation['localKey']) ||
            !$relation['tableClass'] ||
!$relation['remoteKey'] || !$relation['localKey']
        )
        {
            throw new InvalidArgumentException('Missing array index
for the '.__METHOD__.' method. Please check method
signature', 500);
        }

        if(array_key_exists('pivotTable', $relation))
        {
            if(
                !isset($relation['theirPivotKey']) ||
!isset($relation['ourPivotKey']) ||
                !$relation['pivotTable'] ||
!$relation['theirPivotKey'] ||
!$relation['ourPivotKey']
            )
            {
                throw new InvalidArgumentException('Missing array
index for the '.__METHOD__.' method. Please check method
signature', 500);
            }
        }

		// Get a table object from the table class name
		$tableClass      = $relation['tableClass'];
		$tableClassParts = FOFInflector::explode($tableClass);

        if(count($tableClassParts) < 3)
        {
            throw new InvalidArgumentException('Invalid table class
named. It should be something like FooTableBar');
        }

		$table = FOFTable::getInstance($tableClassParts[2],
ucfirst($tableClassParts[0]) . ucfirst($tableClassParts[1]));

		// Get the table name
		$tableName = $table->getTableName();

		// Get the remote and local key names
		$remoteKey = $relation['remoteKey'];
		$localKey  = $relation['localKey'];

		// Get the local key's value
		$value = $this->table->$localKey;

        // If there's no value for the primary key, let's stop
here
        if(!$value)
        {
            throw new RuntimeException('Missing value for the primary
key of the table '.$this->table->getTableName(), 500);
        }

		// This is required to prevent one relation from killing the db cursor
used in a different relation...
		$oldDb = $this->table->getDbo();
		$oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE
THE DB OBJECT. ARGH!
		$db = clone $oldDb;

		// Begin the query
		$query = $db->getQuery(true)
			->select('*')
			->from($db->qn($tableName));

		// Do we have a pivot table?
		$hasPivot = array_key_exists('pivotTable', $relation);

		// If we don't have pivot it's a straightforward query
		if (!$hasPivot)
		{
			$query->where($db->qn($remoteKey) . ' = ' .
$db->q($value));
		}
		// If we have a pivot table we have to do a subquery
		else
		{
			$subQuery = $db->getQuery(true)
				->select($db->qn($relation['theirPivotKey']))
				->from($db->qn($relation['pivotTable']))
				->where($db->qn($relation['ourPivotKey']) . ' =
' . $db->q($value));
			$query->where($db->qn($remoteKey) . ' IN (' . $subQuery
. ')');
		}

		$db->setQuery($query);

		$cursor = $db->execute();

		$iterator = FOFDatabaseIterator::getIterator($db->name, $cursor, null,
$tableClass);

		return $iterator;
	}

	/**
	 * Add any bespoke relation which doesn't involve a pivot table.
	 *
	 * @param   string   $relationType  The type of the relationship (parent,
child, children)
	 * @param   string   $itemName      is how it will be known locally to the
getRelatedItems method
	 * @param   string   $tableClass    if skipped it is defined automatically
as ComponentnameTableItemname
	 * @param   string   $localKey      is the column containing our side of
the FK relation, default: componentname_itemname_id
	 * @param   string   $remoteKey     is the remote table's FK column,
default: componentname_itemname_id
	 * @param   boolean  $default       is this the default children
relationship?
	 *
	 * @return  void
	 */
	protected function addBespokeSimpleRelation($relationType, $itemName,
$tableClass, $localKey, $remoteKey, $default)
	{
		$ourPivotKey   = null;
		$theirPivotKey = null;
		$pivotTable    = null;

		$this->normaliseParameters(false, $itemName, $tableClass, $localKey,
$remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable);

		$this->relations[$relationType][$itemName] = array(
			'tableClass'	=> $tableClass,
			'localKey'		=> $localKey,
			'remoteKey'		=> $remoteKey,
		);

		if ($default)
		{
			$this->defaultRelation[$relationType] = $itemName;
		}
	}

	/**
	 * Add any bespoke relation which involves a pivot table.
	 *
	 * @param   string   $relationType   The type of the relationship
(multiple)
	 * @param   string   $itemName       is how it will be known locally to
the getRelatedItems method
	 * @param   string   $tableClass     if skipped it is defined
automatically as ComponentnameTableItemname
	 * @param   string   $localKey       is the column containing our side of
the FK relation, default: componentname_itemname_id
	 * @param   string   $remoteKey      is the remote table's FK column,
default: componentname_itemname_id
	 * @param   string   $ourPivotKey    is the column containing our side of
the FK relation in the pivot table, default: $localKey
	 * @param   string   $theirPivotKey  is the column containing the other
table's side of the FK relation in the pivot table, default $remoteKey
	 * @param   string   $pivotTable     is the name of the glue (pivot)
table, default: #__componentname_thisclassname_itemname with plural items
(e.g. #__foobar_users_roles)
	 * @param   boolean  $default        is this the default children
relationship?
	 *
	 * @return  void
	 */
	protected function addBespokePivotRelation($relationType, $itemName,
$tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey,
$pivotTable, $default)
	{
		$this->normaliseParameters(true, $itemName, $tableClass, $localKey,
$remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable);

		$this->relations[$relationType][$itemName] = array(
			'tableClass'	=> $tableClass,
			'localKey'		=> $localKey,
			'remoteKey'		=> $remoteKey,
			'ourPivotKey'	=> $ourPivotKey,
			'theirPivotKey'	=> $theirPivotKey,
			'pivotTable'	=> $pivotTable,
		);

		if ($default)
		{
			$this->defaultRelation[$relationType] = $itemName;
		}
	}

	/**
	 * Normalise the parameters of a relation, guessing missing values
	 *
	 * @param   boolean  $pivot          Is this a many to many relation
involving a pivot table?
	 * @param   string   $itemName       is how it will be known locally to
the getRelatedItems method (plural)
	 * @param   string   $tableClass     if skipped it is defined
automatically as ComponentnameTableItemname
	 * @param   string   $localKey       is the column containing our side of
the FK relation, default: componentname_itemname_id
	 * @param   string   $remoteKey      is the remote table's FK column,
default: componentname_itemname_id
	 * @param   string   $ourPivotKey    is the column containing our side of
the FK relation in the pivot table, default: $localKey
	 * @param   string   $theirPivotKey  is the column containing the other
table's side of the FK relation in the pivot table, default $remoteKey
	 * @param   string   $pivotTable     is the name of the glue (pivot)
table, default: #__componentname_thisclassname_itemname with plural items
(e.g. #__foobar_users_roles)
	 *
	 * @return  void
	 */
	protected function normaliseParameters($pivot, &$itemName,
&$tableClass, &$localKey, &$remoteKey, &$ourPivotKey,
&$theirPivotKey, &$pivotTable)
	{
		// Get a default table class if none is provided
		if (empty($tableClass))
		{
			$tableClassParts = explode('_', $itemName, 3);

			if (count($tableClassParts) == 1)
			{
				array_unshift($tableClassParts, $this->componentName);
			}

			if ($tableClassParts[0] == 'joomla')
			{
				$tableClassParts[0] = 'J';
			}

			$tableClass = ucfirst($tableClassParts[0]) . 'Table' .
ucfirst(FOFInflector::singularize($tableClassParts[1]));
		}

		// Make sure we have both a local and remote key
		if (empty($localKey) && empty($remoteKey))
		{
            // WARNING! If we have a pivot table, this behavior is wrong!
            // Infact if we have `parts` and `groups` the local key should
be foobar_part_id and the remote one foobar_group_id.
            // However, this isn't a real issue because:
            // 1. we have no way to detect the local key of a multiple
relation
            // 2. this scenario never happens, since, in this class, if
we're adding a multiple relation we always supply the local key
			$tableClassParts = FOFInflector::explode($tableClass);
			$localKey  = $tableClassParts[0] . '_' . $tableClassParts[2] .
'_id';
			$remoteKey = $localKey;
		}
		elseif (empty($localKey) && !empty($remoteKey))
		{
			$localKey = $remoteKey;
		}
		elseif (!empty($localKey) && empty($remoteKey))
		{
            if($pivot)
            {
                $tableClassParts = FOFInflector::explode($tableClass);
                $remoteKey = $tableClassParts[0] . '_' .
$tableClassParts[2] . '_id';
            }
            else
            {
                $remoteKey = $localKey;
            }
		}

		// If we don't have a pivot table nullify the relevant variables and
return
		if (!$pivot)
		{
			$ourPivotKey   = null;
			$theirPivotKey = null;
			$pivotTable    = null;

			return;
		}

		if (empty($ourPivotKey))
		{
			$ourPivotKey = $localKey;
		}

		if (empty($theirPivotKey))
		{
			$theirPivotKey = $remoteKey;
		}

		if (empty($pivotTable))
		{
			$pivotTable = '#__' . strtolower($this->componentName) .
'_' .
							strtolower(FOFInflector::pluralize($this->tableType)) .
'_';

			$itemNameParts = explode('_', $itemName);
			$lastPart = array_pop($itemNameParts);
			$pivotTable .= strtolower($lastPart);
		}
	}

	/**
	 * Normalises the format of a relation name
	 *
	 * @param   string   $itemName   The raw relation name
	 * @param   boolean  $pluralise  Should I pluralise the name? If not, I
will singularise it
	 *
	 * @return  string  The normalised relation key name
	 */
	protected function normaliseItemName($itemName, $pluralise = false)
	{
		// Explode the item name
		$itemNameParts = explode('_', $itemName);

		// If we have multiple parts the first part is considered to be the
component name
		if (count($itemNameParts) > 1)
		{
			$prefix = array_shift($itemNameParts);
		}
		else
		{
			$prefix = null;
		}

		// If we still have multiple parts we need to pluralise/singularise the
last part and join everything in
		// CamelCase format
		if (count($itemNameParts) > 1)
		{
			$name = array_pop($itemNameParts);
			$name = $pluralise ? FOFInflector::pluralize($name) :
FOFInflector::singularize($name);
			$itemNameParts[] = $name;

			$itemName = FOFInflector::implode($itemNameParts);
		}
		// Otherwise we singularise/pluralise the remaining part
		else
		{
			$name = array_pop($itemNameParts);
			$itemName = $pluralise ? FOFInflector::pluralize($name) :
FOFInflector::singularize($name);
		}

		if (!empty($prefix))
		{
			$itemName = $prefix . '_' . $itemName;
		}

		return $itemName;
	}
}PK!�y��c�c	table.phpnu�[���<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba
Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Normally this shouldn't be required. Some PHP versions, however,
seem to
 * require this. Why? No idea whatsoever. If I remove it, FOF crashes on
some
 * hosts. Same PHP version on another host and no problem occurs. Any
takers?
 */
if (class_exists('FOFTable', false))
{
	return;
}

if (!interface_exists('JTableInterface', true))
{
	interface JTableInterface {}
}

/**
 * FrameworkOnFramework Table class. The Table is one part controller, one
part
 * model and one part data adapter. It's supposed to handle operations
for single
 * records.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFTable extends FOFUtilsObject implements JTableInterface
{
	/**
	 * Cache array for instances
	 *
	 * @var    array
	 */
	protected static $instances = array();

	/**
	 * Include paths for searching for FOFTable classes.
	 *
	 * @var    array
	 */
	protected static $_includePaths = array();

	/**
	 * The configuration parameters array
	 *
	 * @var  array
	 */
	protected $config = array();

	/**
	 * Name of the database table to model.
	 *
	 * @var    string
	 */
	protected $_tbl = '';

	/**
	 * Name of the primary key field in the table.
	 *
	 * @var    string
	 */
	protected $_tbl_key = '';

	/**
	 * FOFDatabaseDriver object.
	 *
	 * @var    FOFDatabaseDriver
	 */
	protected $_db;

	/**
	 * Should rows be tracked as ACL assets?
	 *
	 * @var    boolean
	 */
	protected $_trackAssets = false;

	/**
	 * Does the resource support joomla tags?
	 *
	 * @var    boolean
	 */
	protected $_has_tags = false;

	/**
	 * The rules associated with this record.
	 *
	 * @var    JAccessRules  A JAccessRules object.
	 */
	protected $_rules;

	/**
	 * Indicator that the tables have been locked.
	 *
	 * @var    boolean
	 */
	protected $_locked = false;

	/**
	 * If this is set to true, it triggers automatically plugin events for
	 * table actions
	 *
	 * @var    boolean
	 */
	protected $_trigger_events = false;

	/**
	 * Table alias used in queries
	 *
	 * @var    string
	 */
	protected $_tableAlias = false;

	/**
	 * Array with alias for "special" columns such as ordering, hits
etc etc
	 *
	 * @var    array
	 */
	protected $_columnAlias = array();

	/**
	 * If set to true, it enabled automatic checks on fields based on columns
properties
	 *
	 * @var    boolean
	 */
	protected $_autoChecks = false;

	/**
	 * Array with fields that should be skipped by automatic checks
	 *
	 * @var    array
	 */
	protected $_skipChecks = array();

	/**
	 * Does the table actually exist? We need that to avoid PHP notices on
	 * table-less views.
	 *
	 * @var    boolean
	 */
	protected $_tableExists = true;

	/**
	 * The asset key for items in this table. It's usually something in
the
	 * com_example.viewname format. They asset name will be this key appended
	 * with the item's ID, e.g. com_example.viewname.123
	 *
	 * @var    string
	 */
	protected $_assetKey = '';

	/**
	 * The input data
	 *
	 * @var    FOFInput
	 */
	protected $input = null;

	/**
	 * Extended query including joins with other tables
	 *
	 * @var    FOFDatabaseQuery
	 */
	protected $_queryJoin = null;

	/**
	 * The prefix for the table class
	 *
	 * @var		string
	 */
	protected $_tablePrefix = '';

	/**
	 * The known fields for this table
	 *
	 * @var		array
	 */
	protected $knownFields = array();

	/**
	 * A list of table fields, keyed per table
	 *
	 * @var array
	 */
	protected static $tableFieldCache = array();

	/**
	 * A list of tables in the database
	 *
	 * @var array
	 */
	protected static $tableCache = array();

	/**
	 * An instance of FOFConfigProvider to provision configuration overrides
	 *
	 * @var    FOFConfigProvider
	 */
	protected $configProvider = null;

	/**
	 * FOFTableDispatcherBehavior for dealing with extra behaviors
	 *
	 * @var    FOFTableDispatcherBehavior
	 */
	protected $tableDispatcher = null;

	/**
	 * List of default behaviors to apply to the table
	 *
	 * @var    array
	 */
	protected $default_behaviors = array('tags',
'assets');

	/**
	 * The relations object of the table. It's lazy-loaded by
getRelations().
	 *
	 * @var   FOFTableRelations
	 */
	protected $_relations = null;

	/**
	 * The configuration provider's key for this table, e.g.
foobar.tables.bar for the #__foobar_bars table. This is set
	 * automatically by the constructor
	 *
	 * @var  string
	 */
	protected $_configProviderKey = '';

	/**
	 * The content type of the table. Required if using tags or content
history behaviour
	 *
	 * @var  string
	 */
	protected $contentType = null;

	/**
	 * Returns a static object instance of a particular table type
	 *
	 * @param   string  $type    The table name
	 * @param   string  $prefix  The prefix of the table class
	 * @param   array   $config  Optional configuration variables
	 *
	 * @return FOFTable
	 */
	public static function getInstance($type, $prefix = 'JTable',
$config = array())
	{
		return self::getAnInstance($type, $prefix, $config);
	}

	/**
	 * Returns a static object instance of a particular table type
	 *
	 * @param   string  $type    The table name
	 * @param   string  $prefix  The prefix of the table class
	 * @param   array   $config  Optional configuration variables
	 *
	 * @return FOFTable
	 */
	public static function &getAnInstance($type = null, $prefix =
'JTable', $config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		// Guess the component name
		if (!array_key_exists('input', $config))
		{
			$config['input'] = new FOFInput;
		}

		if ($config['input'] instanceof FOFInput)
		{
			$tmpInput = $config['input'];
		}
		else
		{
			$tmpInput = new FOFInput($config['input']);
		}

		$option = $tmpInput->getCmd('option', '');
		$tmpInput->set('option', $option);
		$config['input'] = $tmpInput;

		if (!in_array($prefix, array('Table', 'JTable')))
		{
			preg_match('/(.*)Table$/', $prefix, $m);
			$option = 'com_' . strtolower($m[1]);
		}

		if (array_key_exists('option', $config))
		{
			$option = $config['option'];
		}

		$config['option'] = $option;

		if (!array_key_exists('view', $config))
		{
			$config['view'] =
$config['input']->getCmd('view',
'cpanel');
		}

		if (is_null($type))
		{
			if ($prefix == 'JTable')
			{
				$prefix = 'Table';
			}

			$type = $config['view'];
		}

		$type       = preg_replace('/[^A-Z0-9_\.-]/i', '',
$type);
		$tableClass = $prefix . ucfirst($type);

		$config['_table_type'] = $type;
		$config['_table_class'] = $tableClass;

		$configProvider = new FOFConfigProvider;
		$configProviderKey = $option . '.views.' .
FOFInflector::singularize($type) . '.config.';

		if (!array_key_exists($tableClass, self::$instances))
		{
			if (!class_exists($tableClass))
			{
				$componentPaths =
FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);

				$searchPaths = array(
					$componentPaths['main'] . '/tables',
					$componentPaths['admin'] . '/tables'
				);

				if (array_key_exists('tablepath', $config))
				{
					array_unshift($searchPaths, $config['tablepath']);
				}

				$altPath = $configProvider->get($configProviderKey .
'table_path', null);

				if ($altPath)
				{
					array_unshift($searchPaths, $componentPaths['admin'] .
'/' . $altPath);
				}

                $filesystem =
FOFPlatform::getInstance()->getIntegrationObject('filesystem');

				$path = $filesystem->pathFind(
					$searchPaths, strtolower($type) . '.php'
				);

				if ($path)
				{
					require_once $path;
				}
			}

			if (!class_exists($tableClass))
			{
				$tableClass = 'FOFTable';
			}

			$component = str_replace('com_', '',
$config['option']);
			$tbl_common = $component . '_';

			if (!array_key_exists('tbl', $config))
			{
				$config['tbl'] = strtolower('#__' . $tbl_common .
strtolower(FOFInflector::pluralize($type)));
			}

			$altTbl = $configProvider->get($configProviderKey . 'tbl',
null);

			if ($altTbl)
			{
				$config['tbl'] = $altTbl;
			}

			if (!array_key_exists('tbl_key', $config))
			{
				$keyName           = FOFInflector::singularize($type);
				$config['tbl_key'] = strtolower($tbl_common . $keyName .
'_id');
			}

			$altTblKey = $configProvider->get($configProviderKey .
'tbl_key', null);

			if ($altTblKey)
			{
				$config['tbl_key'] = $altTblKey;
			}

			if (!array_key_exists('db', $config))
			{
				$config['db'] = FOFPlatform::getInstance()->getDbo();
			}

			// Assign the correct table alias
			if (array_key_exists('table_alias', $config))
			{
				$table_alias = $config['table_alias'];
			}
			else
			{
				$configProviderTableAliasKey = $option . '.tables.' .
FOFInflector::singularize($type) . '.tablealias';
				$table_alias = $configProvider->get($configProviderTableAliasKey,
false	);
			}

			// Can we use the FOF cache?
			if (!array_key_exists('use_table_cache', $config))
			{
				$config['use_table_cache'] =
FOFPlatform::getInstance()->isGlobalFOFCacheEnabled();
			}

			$alt_use_table_cache = $configProvider->get($configProviderKey .
'use_table_cache', null);

			if (!is_null($alt_use_table_cache))
			{
				$config['use_table_cache'] = $alt_use_table_cache;
			}

			// Create a new table instance
			$instance = new $tableClass($config['tbl'],
$config['tbl_key'], $config['db'], $config);
			$instance->setInput($tmpInput);
			$instance->setTablePrefix($prefix);
			$instance->setTableAlias($table_alias);

			// Determine and set the asset key for this table
			$assetKey = 'com_' . $component . '.' .
strtolower(FOFInflector::singularize($type));
			$assetKey = $configProvider->get($configProviderKey .
'asset_key', $assetKey);
			$instance->setAssetKey($assetKey);

			if (array_key_exists('trigger_events', $config))
			{
				$instance->setTriggerEvents($config['trigger_events']);
			}

			if (version_compare(JVERSION, '3.1', 'ge'))
			{
				if (array_key_exists('has_tags', $config))
				{
					$instance->setHasTags($config['has_tags']);
				}

				$altHasTags = $configProvider->get($configProviderKey .
'has_tags', null);

				if ($altHasTags)
				{
					$instance->setHasTags($altHasTags);
				}
			}
			else
			{
				$instance->setHasTags(false);
			}

			$configProviderFieldmapKey = $option . '.tables.' .
FOFInflector::singularize($type) . '.field';
			$aliases = $configProvider->get($configProviderFieldmapKey,
$instance->_columnAlias);
			$instance->_columnAlias = array_merge($instance->_columnAlias,
$aliases);

			self::$instances[$tableClass] = $instance;
		}

		return self::$instances[$tableClass];
	}

	/**
	 * Force an instance inside class cache. Setting arguments to null nukes
all or part of the cache
	 *
	 * @param    string|null       $key        TableClass to replace. Set it
to null to nuke the entire cache
	 * @param    FOFTable|null     $instance   Instance to replace. Set it to
null to nuke $key instances
	 *
	 * @return   bool              Did I correctly switch the instance?
	 */
	public static function forceInstance($key = null, $instance = null)
	{
		if(is_null($key))
		{
			self::$instances = array();

			return true;
		}
		elseif($key && isset(self::$instances[$key]))
		{
			// I'm forcing an instance, but it's not a FOFTable, abort!
abort!
			if(!$instance || ($instance && $instance instanceof FOFTable))
			{
				self::$instances[$key] = $instance;

				return true;
			}
		}

		return false;
	}

	/**
	 * Class Constructor.
	 *
	 * @param   string           $table   Name of the database table to model.
	 * @param   string           $key     Name of the primary key field in the
table.
	 * @param   FOFDatabaseDriver  &$db     Database driver
	 * @param   array            $config  The configuration parameters array
	 */
	public function __construct($table, $key, &$db, $config = array())
	{
		$this->_tbl     = $table;
		$this->_tbl_key = $key;
		$this->_db      = $db;

		// Make sure the use FOF cache information is in the config
		if (!array_key_exists('use_table_cache', $config))
		{
			$config['use_table_cache'] =
FOFPlatform::getInstance()->isGlobalFOFCacheEnabled();
		}
		$this->config   = $config;

		// Load the configuration provider
		$this->configProvider = new FOFConfigProvider;

		// Load the behavior dispatcher
		$this->tableDispatcher = new FOFTableDispatcherBehavior;

		// Initialise the table properties.

		if ($fields = $this->getTableFields())
		{
			// Do I have anything joined?
			$j_fields = $this->getQueryJoinFields();

			if ($j_fields)
			{
				$fields = array_merge($fields, $j_fields);
			}

			$this->setKnownFields(array_keys($fields), true);
			$this->reset();
		}
		else
		{
			$this->_tableExists = false;
		}

		// Get the input
		if (array_key_exists('input', $config))
		{
			if ($config['input'] instanceof FOFInput)
			{
				$this->input = $config['input'];
			}
			else
			{
				$this->input = new FOFInput($config['input']);
			}
		}
		else
		{
			$this->input = new FOFInput;
		}

		// Set the $name/$_name variable
		$component = $this->input->getCmd('option',
'com_foobar');

		if (array_key_exists('option', $config))
		{
			$component = $config['option'];
		}

		$this->input->set('option', $component);

		// Apply table behaviors
		$type = explode("_", $this->_tbl);
		$type = $type[count($type) - 1];

		$this->_configProviderKey = $component . '.tables.' .
FOFInflector::singularize($type);

		$configKey = $this->_configProviderKey . '.behaviors';

		if (isset($config['behaviors']))
		{
			$behaviors = (array) $config['behaviors'];
		}
		elseif ($behaviors = $this->configProvider->get($configKey, null))
		{
			$behaviors = explode(',', $behaviors);
		}
		else
		{
			$behaviors = $this->default_behaviors;
		}

		if (is_array($behaviors) && count($behaviors))
		{
			foreach ($behaviors as $behavior)
			{
				$this->addBehavior($behavior);
			}
		}

		// If we are tracking assets, make sure an access field exists and
initially set the default.
		$asset_id_field	= $this->getColumnAlias('asset_id');
		$access_field	= $this->getColumnAlias('access');

		if (in_array($asset_id_field, $this->getKnownFields()))
		{
			JLoader::import('joomla.access.rules');
			$this->_trackAssets = true;
		}

		// If the access property exists, set the default.
		if (in_array($access_field, $this->getKnownFields()))
		{
			$this->$access_field = (int)
FOFPlatform::getInstance()->getConfig()->get('access');
		}

		$this->config = $config;
	}

	/**
	 * Replace the entire known fields array
	 *
	 * @param   array    $fields      A simple array of known field names
	 * @param   boolean  $initialise  Should we initialise variables to null?
	 *
	 * @return  void
	 */
	public function setKnownFields($fields, $initialise = false)
	{
		$this->knownFields = $fields;

		if ($initialise)
		{
			foreach ($this->knownFields as $field)
			{
				$this->$field = null;
			}
		}
	}

	/**
	 * Get the known fields array
	 *
	 * @return  array
	 */
	public function getKnownFields()
	{
		return $this->knownFields;
	}

	/**
	 * Does the specified field exist?
	 *
	 * @param   string  $fieldName  The field name to search (it's OK to
use aliases)
	 *
	 * @return  bool
	 */
	public function hasField($fieldName)
	{
		$search = $this->getColumnAlias($fieldName);

		return in_array($search, $this->knownFields);
	}

	/**
	 * Add a field to the known fields array
	 *
	 * @param   string   $field       The name of the field to add
	 * @param   boolean  $initialise  Should we initialise the variable to
null?
	 *
	 * @return  void
	 */
	public function addKnownField($field, $initialise = false)
	{
		if (!in_array($field, $this->knownFields))
		{
			$this->knownFields[] = $field;

			if ($initialise)
			{
				$this->$field = null;
			}
		}
	}

	/**
	 * Remove a field from the known fields array
	 *
	 * @param   string  $field  The name of the field to remove
	 *
	 * @return  void
	 */
	public function removeKnownField($field)
	{
		if (in_array($field, $this->knownFields))
		{
			$pos = array_search($field, $this->knownFields);
			unset($this->knownFields[$pos]);
		}
	}

	/**
	 * Adds a behavior to the table
	 *
	 * @param   string  $name    The name of the behavior
	 * @param   array   $config  Optional Behavior configuration
	 *
	 * @return  boolean
	 */
	public function addBehavior($name, $config = array())
	{
		// First look for ComponentnameTableViewnameBehaviorName (e.g.
FoobarTableItemsBehaviorTags)
		if (isset($this->config['option']))
		{
			$option_name = str_replace('com_', '',
$this->config['option']);
			$behaviorClass = $this->config['_table_class'] .
'Behavior' . ucfirst(strtolower($name));

			if (class_exists($behaviorClass))
			{
				$behavior = new $behaviorClass($this->tableDispatcher, $config);

				return true;
			}

			// Then look for ComponentnameTableBehaviorName (e.g.
FoobarTableBehaviorTags)
			$option_name = str_replace('com_', '',
$this->config['option']);
			$behaviorClass = ucfirst($option_name) . 'TableBehavior' .
ucfirst(strtolower($name));

			if (class_exists($behaviorClass))
			{
				$behavior = new $behaviorClass($this->tableDispatcher, $config);

				return true;
			}
		}

		// Nothing found? Return false.

		$behaviorClass = 'FOFTableBehavior' .
ucfirst(strtolower($name));

		if (class_exists($behaviorClass) && $this->tableDispatcher)
		{
			$behavior = new $behaviorClass($this->tableDispatcher, $config);

			return true;
		}

		return false;
	}

	/**
	 * Sets the events trigger switch state
	 *
	 * @param   boolean  $newState  The new state of the switch (what else
could it be?)
	 *
	 * @return  void
	 */
	public function setTriggerEvents($newState = false)
	{
		$this->_trigger_events = $newState ? true : false;
	}

	/**
	 * Gets the events trigger switch state
	 *
	 * @return  boolean
	 */
	public function getTriggerEvents()
	{
		return $this->_trigger_events;
	}

	/**
	 * Gets the has tags switch state
	 *
	 * @return bool
	 */
	public function hasTags()
	{
		return $this->_has_tags;
	}

	/**
	 * Sets the has tags switch state
	 *
	 * @param   bool  $newState
	 */
	public function setHasTags($newState = false)
	{
		$this->_has_tags = false;

		// Tags are available only in 3.1+
		if (version_compare(JVERSION, '3.1', 'ge'))
		{
			$this->_has_tags = $newState ? true : false;
		}
	}

	/**
	 * Set the class prefix
	 *
	 * @param string $prefix The prefix
	 */
	public function setTablePrefix($prefix)
	{
		$this->_tablePrefix = $prefix;
	}

	/**
	 * Sets fields to be skipped from automatic checks.
	 *
	 * @param   array/string  $skip  Fields to be skipped by automatic checks
	 *
	 * @return void
	 */
	public function setSkipChecks($skip)
	{
		$this->_skipChecks = (array) $skip;
	}

	/**
	 * Method to load a row from the database by primary key and bind the
fields
	 * to the FOFTable instance properties.
	 *
	 * @param   mixed    $keys   An optional primary key value to load the row
by, or an array of fields to match.  If not
	 *                           set the instance property value is used.
	 * @param   boolean  $reset  True to reset the default values before
loading the new row.
	 *
	 * @return  boolean  True if successful. False if row not found.
	 *
	 * @throws  RuntimeException
	 * @throws  UnexpectedValueException
	 */
	public function load($keys = null, $reset = true)
	{
		if (!$this->_tableExists)
		{
			$result = false;

            return $this->onAfterLoad($result);
		}

		if (empty($keys))
		{
			// If empty, use the value of the current key
			$keyName = $this->_tbl_key;

			if (isset($this->$keyName))
			{
				$keyValue = $this->$keyName;
			}
			else
			{
				$keyValue = null;
			}

			// If empty primary key there's is no need to load anything

			if (empty($keyValue))
			{
				$result = true;

				return $this->onAfterLoad($result);
			}

			$keys = array($keyName => $keyValue);
		}
		elseif (!is_array($keys))
		{
			// Load by primary key.
			$keys = array($this->_tbl_key => $keys);
		}

		if ($reset)
		{
			$this->reset();
		}

		// Initialise the query.
		$query = $this->_db->getQuery(true);
		$query->select($this->_tbl . '.*');
		$query->from($this->_tbl);

		// Joined fields are ok, since I initialized them in the constructor
		$fields = $this->getKnownFields();

		foreach ($keys as $field => $value)
		{
			// Check that $field is in the table.

			if (!in_array($field, $fields))
			{
				throw new UnexpectedValueException(sprintf('Missing field in table
%s : %s.', $this->_tbl, $field));
			}

			// Add the search tuple to the query.
			$query->where($this->_db->qn($this->_tbl . '.' .
$field) . ' = ' . $this->_db->q($value));
		}

		// Do I have any joined table?
		$j_query = $this->getQueryJoin();

		if ($j_query)
		{
			if ($j_query->select &&
$j_query->select->getElements())
			{
				//$query->select($this->normalizeSelectFields($j_query->select->getElements(),
true));
				$query->select($j_query->select->getElements());
			}

			if ($j_query->join)
			{
				foreach ($j_query->join as $join)
				{
					$t = (string) $join;

					// Joomla doesn't provide any access to the "name"
variable, so I have to work with strings...
					if (stripos($t, 'inner') !== false)
					{
						$query->innerJoin($join->getElements());
					}
					elseif (stripos($t, 'left') !== false)
					{
						$query->leftJoin($join->getElements());
					}
					elseif (stripos($t, 'right') !== false)
					{
						$query->rightJoin($join->getElements());
					}
					elseif (stripos($t, 'outer') !== false)
					{
						$query->outerJoin($join->getElements());
					}
				}
			}
		}

		$this->_db->setQuery($query);

		$row = $this->_db->loadAssoc();

		// Check that we have a result.
		if (empty($row))
		{
			$result = false;

			return $this->onAfterLoad($result);
		}

		// Bind the object with the row and return.
		$result = $this->bind($row);

		$this->onAfterLoad($result);

		return $result;
	}

	/**
	 * Based on fields properties (nullable column), checks if the field is
required or not
	 *
	 * @return boolean
	 */
	public function check()
	{
		if (!$this->_autoChecks)
		{
			return true;
		}

		$fields = $this->getTableFields();

        // No fields? Why in the hell am I here?
        if(!$fields)
        {
            return false;
        }

        $result       = true;
        $known        = $this->getKnownFields();
        $skipFields[] = $this->_tbl_key;

		if(in_array($this->getColumnAlias('title'), $known)
			&& in_array($this->getColumnAlias('slug'), $known))
     $skipFields[] = $this->getColumnAlias('slug');
        if(in_array($this->getColumnAlias('hits'), $known))   
     $skipFields[] = $this->getColumnAlias('hits');
        if(in_array($this->getColumnAlias('created_on'),
$known))   $skipFields[] =
$this->getColumnAlias('created_on');
        if(in_array($this->getColumnAlias('created_by'),
$known))   $skipFields[] =
$this->getColumnAlias('created_by');
        if(in_array($this->getColumnAlias('modified_on'),
$known))  $skipFields[] =
$this->getColumnAlias('modified_on');
        if(in_array($this->getColumnAlias('modified_by'),
$known))  $skipFields[] =
$this->getColumnAlias('modified_by');
        if(in_array($this->getColumnAlias('locked_by'),
$known))    $skipFields[] =
$this->getColumnAlias('locked_by');
        if(in_array($this->getColumnAlias('locked_on'),
$known))    $skipFields[] =
$this->getColumnAlias('locked_on');

        // Let's merge it with custom skips
        $skipFields = array_merge($skipFields, $this->_skipChecks);

		foreach ($fields as $field)
		{
			$fieldName = $field->Field;

			if (empty($fieldName))
			{
				$fieldName = $field->column_name;
			}

			// Field is not nullable but it's null, set error

			if ($field->Null == 'NO' && $this->$fieldName ==
'' && !in_array($fieldName, $skipFields))
			{
				$text = str_replace('#__', 'COM_',
$this->getTableName()) . '_ERR_' . $fieldName;
				$this->setError(JText::_(strtoupper($text)));
				$result = false;
			}
		}

		return $result;
	}

	/**
	 * Method to reset class properties to the defaults set in the class
	 * definition. It will ignore the primary key as well as any private class
	 * properties.
	 *
	 * @return void
	 */
	public function reset()
	{
		if (!$this->onBeforeReset())
		{
			return false;
		}

		// Get the default values for the class from the table.
		$fields   = $this->getTableFields();
		$j_fields = $this->getQueryJoinFields();

		if ($j_fields)
		{
			$fields = array_merge($fields, $j_fields);
		}

		if (is_array($fields) && !empty($fields))
		{
			foreach ($fields as $k => $v)
			{
				// If the property is not the primary key or private, reset it.
				if ($k != $this->_tbl_key && (strpos($k, '_') !==
0))
				{
					$this->$k = $v->Default;
				}
			}

			if (!$this->onAfterReset())
			{
				return false;
			}
		}
	}

    /**
     * Clones the current object, after resetting it
     *
     * @return static
     */
    public function getClone()
    {
        $clone = clone $this;
        $clone->reset();

        $key = $this->getKeyName();
        $clone->$key = null;

        return $clone;
    }

	/**
	 * Generic check for whether dependencies exist for this object in the db
schema
	 *
	 * @param   integer  $oid    The primary key of the record to delete
	 * @param   array    $joins  Any joins to foreign table, used to determine
if dependent records exist
	 *
	 * @return  boolean  True if the record can be deleted
	 */
	public function canDelete($oid = null, $joins = null)
	{
		$k = $this->_tbl_key;

		if ($oid)
		{
			$this->$k = intval($oid);
		}

		if (is_array($joins))
		{
			$db      = $this->_db;
			$query   = $db->getQuery(true)
				->select($db->qn('master') . '.' .
$db->qn($k))
				->from($db->qn($this->_tbl) . ' AS ' .
$db->qn('master'));
			$tableNo = 0;

			foreach ($joins as $table)
			{
				$tableNo++;
				$query->select(
					array(
						'COUNT(DISTINCT ' . $db->qn('t' . $tableNo) .
'.' . $db->qn($table['idfield']) . ') AS '
. $db->qn($table['idalias'])
					)
				);
				$query->join('LEFT', $db->qn($table['name'])
.
					' AS ' . $db->qn('t' . $tableNo) .
					' ON ' . $db->qn('t' . $tableNo) .
'.' . $db->qn($table['joinfield']) .
					' = ' . $db->qn('master') . '.' .
$db->qn($k)
				);
			}

			$query->where($db->qn('master') . '.' .
$db->qn($k) . ' = ' . $db->q($this->$k));
			$query->group($db->qn('master') . '.' .
$db->qn($k));
			$this->_db->setQuery((string) $query);

			if (version_compare(JVERSION, '3.0', 'ge'))
			{
				try
				{
					$obj = $this->_db->loadObject();
				}
				catch (Exception $e)
				{
					$this->setError($e->getMessage());
				}
			}
			else
			{
				if (!$obj = $this->_db->loadObject())
				{
					$this->setError($this->_db->getErrorMsg());

					return false;
				}
			}

			$msg = array();
			$i   = 0;

			foreach ($joins as $table)
			{
				$k = $table['idalias'];

				if ($obj->$k > 0)
				{
					$msg[] = JText::_($table['label']);
				}

				$i++;
			}

			if (count($msg))
			{
				$option  = $this->input->getCmd('option',
'com_foobar');
				$comName = str_replace('com_', '', $option);
				$tview   = str_replace('#__' . $comName . '_',
'', $this->_tbl);
				$prefix  = $option . '_' . $tview . '_NODELETE_';

				foreach ($msg as $key)
				{
					$this->setError(JText::_($prefix . $key));
				}

				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * Method to bind an associative array or object to the FOFTable
instance.This
	 * method only binds properties that are publicly accessible and
optionally
	 * takes an array of properties to ignore when binding.
	 *
	 * @param   mixed  $src     An associative array or object to bind to the
FOFTable instance.
	 * @param   mixed  $ignore  An optional array or space separated list of
properties to ignore while binding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @throws  InvalidArgumentException
	 */
	public function bind($src, $ignore = array())
	{
		if (!$this->onBeforeBind($src))
		{
			return false;
		}

		// If the source value is not an array or object return false.
		if (!is_object($src) && !is_array($src))
		{
			throw new InvalidArgumentException(sprintf('%s::bind(*%s*)',
get_class($this), gettype($src)));
		}

		// If the source value is an object, get its accessible properties.
		if (is_object($src))
		{
			$src = get_object_vars($src);
		}

		// If the ignore value is a string, explode it over spaces.
		if (!is_array($ignore))
		{
			$ignore = explode(' ', $ignore);
		}

		// Bind the source value, excluding the ignored fields.
		foreach ($this->getKnownFields() as $k)
		{
			// Only process fields not in the ignore array.
			if (!in_array($k, $ignore))
			{
				if (isset($src[$k]))
				{
					$this->$k = $src[$k];
				}
			}
		}

		$result = $this->onAfterBind($src);

		return $result;
	}

	/**
	 * Method to store a row in the database from the FOFTable instance
properties.
	 * If a primary key value is set the row with that primary key value will
be
	 * updated with the instance property values.  If no primary key value is
set
	 * a new row will be inserted into the database with the properties from
the
	 * FOFTable instance.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are
null.
	 *
	 * @return  boolean  True on success.
	 */
	public function store($updateNulls = false)
	{
		if (!$this->onBeforeStore($updateNulls))
		{
			return false;
		}

		$k = $this->_tbl_key;

		if ($this->$k == 0)
		{
			$this->$k = null;
		}

		// Create the object used for inserting/updating data to the database
		$fields     = $this->getTableFields();
		$properties = $this->getKnownFields();
		$keys       = array();

		foreach ($properties as $property)
		{
			// 'input' property is a reserved name

			if (isset($fields[$property]))
			{
				$keys[] = $property;
			}
		}

		$updateObject = array();
		foreach ($keys as $key)
		{
			$updateObject[$key] = $this->$key;
		}
		$updateObject = (object)$updateObject;

		/**
		 * While the documentation for update/insertObject and execute() say they
return a boolean,
		 * not all of the implemtnations.  Depending on the version of J! and the
specific driver,
		 * they may return a database object, or boolean, or a mix, or toss an
exception.  So try/catch,
		 * and test for false.
		 */

		try
		{
			// If a primary key exists update the object, otherwise insert it.
			if ($this->$k)
			{
				$result = $this->_db->updateObject($this->_tbl, $updateObject,
$this->_tbl_key, $updateNulls);
			}
			else
			{
				$result = $this->_db->insertObject($this->_tbl, $updateObject,
$this->_tbl_key);
			}

			if ($result === false)
			{
				$this->setError($this->_db->getErrorMsg());

				return false;
			}
		}
		catch (Exception $e)
		{
			$this->setError($e->getMessage());
		}

		$this->bind($updateObject);

		if ($this->_locked)
		{
			$this->_unlock();
		}

		$result = $this->onAfterStore();

		return $result;
	}

	/**
	 * Method to move a row in the ordering sequence of a group of rows
defined by an SQL WHERE clause.
	 * Negative numbers move the row up in the sequence and positive numbers
move it down.
	 *
	 * @param   integer  $delta  The direction and magnitude to move the row
in the ordering sequence.
	 * @param   string   $where  WHERE clause to use for limiting the
selection of rows to compact the
	 *                           ordering values.
	 *
	 * @return  mixed    Boolean  True on success.
	 *
	 * @throws  UnexpectedValueException
	 */
	public function move($delta, $where = '')
	{
		if (!$this->onBeforeMove($delta, $where))
		{
			return false;
		}

		// If there is no ordering field set an error and return false.
		$ordering_field = $this->getColumnAlias('ordering');

		if (!in_array($ordering_field, $this->getKnownFields()))
		{
			throw new UnexpectedValueException(sprintf('%s does not support
ordering.', $this->_tbl));
		}

		// If the change is none, do nothing.
		if (empty($delta))
		{
			$result = $this->onAfterMove();

			return $result;
		}

		$k     = $this->_tbl_key;
		$row   = null;
		$query = $this->_db->getQuery(true);

        // If the table is not loaded, return false
        if (empty($this->$k))
        {
            return false;
        }

		// Select the primary key and ordering values from the table.
		$query->select(array($this->_db->qn($this->_tbl_key),
$this->_db->qn($ordering_field)));
		$query->from($this->_tbl);

		// If the movement delta is negative move the row up.

		if ($delta < 0)
		{
			$query->where($this->_db->qn($ordering_field) . ' <
' . $this->_db->q((int) $this->$ordering_field));
			$query->order($this->_db->qn($ordering_field) . '
DESC');
		}

		// If the movement delta is positive move the row down.

		elseif ($delta > 0)
		{
			$query->where($this->_db->qn($ordering_field) . ' >
' . $this->_db->q((int) $this->$ordering_field));
			$query->order($this->_db->qn($ordering_field) . '
ASC');
		}

		// Add the custom WHERE clause if set.

		if ($where)
		{
			$query->where($where);
		}

		// Select the first row with the criteria.
		$this->_db->setQuery($query, 0, 1);
		$row = $this->_db->loadObject();

		// If a row is found, move the item.

		if (!empty($row))
		{
			// Update the ordering field for this instance to the row's
ordering value.
			$query = $this->_db->getQuery(true);
			$query->update($this->_tbl);
			$query->set($this->_db->qn($ordering_field) . ' = ' .
$this->_db->q((int) $row->$ordering_field));
			$query->where($this->_tbl_key . ' = ' .
$this->_db->q($this->$k));
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the ordering field for the row to this instance's
ordering value.
			$query = $this->_db->getQuery(true);
			$query->update($this->_tbl);
			$query->set($this->_db->qn($ordering_field) . ' = ' .
$this->_db->q((int) $this->$ordering_field));
			$query->where($this->_tbl_key . ' = ' .
$this->_db->q($row->$k));
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the instance value.
			$this->$ordering_field = $row->$ordering_field;
		}
		else
		{
			// Update the ordering field for this instance.
			$query = $this->_db->getQuery(true);
			$query->update($this->_tbl);
			$query->set($this->_db->qn($ordering_field) . ' = ' .
$this->_db->q((int) $this->$ordering_field));
			$query->where($this->_tbl_key . ' = ' .
$this->_db->q($this->$k));
			$this->_db->setQuery($query);
			$this->_db->execute();
		}

		$result = $this->onAfterMove();

		return $result;
	}

    /**
     * Change the ordering of the records of the table
     *
     * @param   string   $where  The WHERE clause of the SQL used to fetch
the order
     *
     * @return  boolean  True is successful
     *
     * @throws  UnexpectedValueException
     */
	public function reorder($where = '')
	{
		if (!$this->onBeforeReorder($where))
		{
			return false;
		}

		// If there is no ordering field set an error and return false.

		$order_field = $this->getColumnAlias('ordering');

		if (!in_array($order_field, $this->getKnownFields()))
		{
			throw new UnexpectedValueException(sprintf('%s does not support
ordering.', $this->_tbl_key));
		}

		$k = $this->_tbl_key;

		// Get the primary keys and ordering values for the selection.
		$query = $this->_db->getQuery(true);
		$query->select($this->_tbl_key . ', ' .
$this->_db->qn($order_field));
		$query->from($this->_tbl);
		$query->where($this->_db->qn($order_field) . ' >= '
. $this->_db->q(0));
		$query->order($this->_db->qn($order_field));

		// Setup the extra where and ordering clause data.

		if ($where)
		{
			$query->where($where);
		}

		$this->_db->setQuery($query);
		$rows = $this->_db->loadObjectList();

		// Compact the ordering values.

		foreach ($rows as $i => $row)
		{
			// Make sure the ordering is a positive integer.

			if ($row->$order_field >= 0)
			{
				// Only update rows that are necessary.

				if ($row->$order_field != $i + 1)
				{
					// Update the row ordering field.
					$query = $this->_db->getQuery(true);
					$query->update($this->_tbl);
					$query->set($this->_db->qn($order_field) . ' = ' .
$this->_db->q($i + 1));
					$query->where($this->_tbl_key . ' = ' .
$this->_db->q($row->$k));
					$this->_db->setQuery($query);
					$this->_db->execute();
				}
			}
		}

		$result = $this->onAfterReorder();

		return $result;
	}

	/**
	 * Check out (lock) a record
	 *
	 * @param   integer  $userId  The locking user's ID
	 * @param   integer  $oid     The primary key value of the record to lock
	 *
	 * @return  boolean  True on success
	 */
	public function checkout($userId, $oid = null)
	{
		$fldLockedBy = $this->getColumnAlias('locked_by');
		$fldLockedOn = $this->getColumnAlias('locked_on');

		if (!(in_array($fldLockedBy, $this->getKnownFields())
			|| in_array($fldLockedOn, $this->getKnownFields())))
		{
			return true;
		}

		$k = $this->_tbl_key;

		if ($oid !== null)
		{
			$this->$k = $oid;
		}

        // No primary key defined, stop here
        if (!$this->$k)
        {
            return false;
        }

		$date = FOFPlatform::getInstance()->getDate();

		if (method_exists($date, 'toSql'))
		{
			$time = $date->toSql();
		}
		else
		{
			$time = $date->toMySQL();
		}


		$query = $this->_db->getQuery(true)
			->update($this->_db->qn($this->_tbl))
			->set(
				array(
					$this->_db->qn($fldLockedBy) . ' = ' .
$this->_db->q((int) $userId),
					$this->_db->qn($fldLockedOn) . ' = ' .
$this->_db->q($time)
				)
			)
			->where($this->_db->qn($this->_tbl_key) . ' = ' .
$this->_db->q($this->$k));
		$this->_db->setQuery((string) $query);

		$this->$fldLockedBy = $userId;
		$this->$fldLockedOn = $time;

		return $this->_db->execute();
	}

	/**
	 * Check in (unlock) a record
	 *
	 * @param   integer  $oid  The primary key value of the record to unlock
	 *
	 * @return  boolean  True on success
	 */
	public function checkin($oid = null)
	{
		$fldLockedBy = $this->getColumnAlias('locked_by');
		$fldLockedOn = $this->getColumnAlias('locked_on');

		if (!(in_array($fldLockedBy, $this->getKnownFields())
			|| in_array($fldLockedOn, $this->getKnownFields())))
		{
			return true;
		}

		$k = $this->_tbl_key;

		if ($oid !== null)
		{
			$this->$k = $oid;
		}

		if ($this->$k == null)
		{
			return false;
		}

		$query = $this->_db->getQuery(true)
			->update($this->_db->qn($this->_tbl))
			->set(
				array(
					$this->_db->qn($fldLockedBy) . ' = 0',
					$this->_db->qn($fldLockedOn) . ' = ' .
$this->_db->q($this->_db->getNullDate())
				)
			)
			->where($this->_db->qn($this->_tbl_key) . ' = ' .
$this->_db->q($this->$k));
		$this->_db->setQuery((string) $query);

		$this->$fldLockedBy = 0;
		$this->$fldLockedOn = '';

		return $this->_db->execute();
	}

    /**
     * Is a record locked?
     *
     * @param   integer $with            The userid to preform the match
with. If an item is checked
     *                                   out by this user the function will
return false.
     * @param   integer $unused_against  Junk inherited from JTable; ignore
     *
     * @throws  UnexpectedValueException
     *
     * @return  boolean  True if the record is locked by another user
     */
	public function isCheckedOut($with = 0, $unused_against = null)
	{
        $against     = null;
		$fldLockedBy = $this->getColumnAlias('locked_by');

        $k  = $this->_tbl_key;

        // If no primary key is given, return false.

        if ($this->$k === null)
        {
            throw new UnexpectedValueException('Null primary key not
allowed.');
        }

		if (isset($this) && is_a($this, 'FOFTable') &&
!$against)
		{
			$against = $this->get($fldLockedBy);
		}

		// Item is not checked out, or being checked out by the same user

		if (!$against || $against == $with)
		{
			return false;
		}

		$session = JTable::getInstance('session');

		return $session->exists($against);
	}

	/**
	 * Copy (duplicate) one or more records
	 *
	 * @param   integer|array  $cid  The primary key value (or values) or the
record(s) to copy
	 *
	 * @return  boolean  True on success
	 */
	public function copy($cid = null)
	{
		//We have to cast the id as array, or the helper function will return an
empty set
		if($cid)
		{
			$cid = (array) $cid;
		}

        FOFUtilsArray::toInteger($cid);
		$k = $this->_tbl_key;

		if (count($cid) < 1)
		{
			if ($this->$k)
			{
				$cid = array($this->$k);
			}
			else
			{
				$this->setError("No items selected.");

				return false;
			}
		}

		$created_by  = $this->getColumnAlias('created_by');
		$created_on  = $this->getColumnAlias('created_on');
		$modified_by = $this->getColumnAlias('modified_by');
		$modified_on = $this->getColumnAlias('modified_on');

		$locked_byName = $this->getColumnAlias('locked_by');
		$checkin       = in_array($locked_byName, $this->getKnownFields());

		foreach ($cid as $item)
		{
			// Prevent load with id = 0

			if (!$item)
			{
				continue;
			}

			$this->load($item);

			if ($checkin)
			{
				// We're using the checkin and the record is used by someone else

				if ($this->isCheckedOut($item))
				{
					continue;
				}
			}

			if (!$this->onBeforeCopy($item))
			{
				continue;
			}

			$this->$k           = null;
			$this->$created_by  = null;
			$this->$created_on  = null;
			$this->$modified_on = null;
			$this->$modified_by = null;

			// Let's fire the event only if everything is ok
			if ($this->store())
			{
				$this->onAfterCopy($item);
			}

			$this->reset();
		}

		return true;
	}

	/**
	 * Publish or unpublish records
	 *
	 * @param   integer|array  $cid      The primary key value(s) of the
item(s) to publish/unpublish
	 * @param   integer        $publish  1 to publish an item, 0 to unpublish
	 * @param   integer        $user_id  The user ID of the user
(un)publishing the item.
	 *
	 * @return  boolean  True on success, false on failure (e.g. record is
locked)
	 */
	public function publish($cid = null, $publish = 1, $user_id = 0)
	{
		$enabledName   = $this->getColumnAlias('enabled');
		$locked_byName = $this->getColumnAlias('locked_by');

		// Mhm... you called the publish method on a table without publish
support...
		if(!in_array($enabledName, $this->getKnownFields()))
		{
			return false;
		}

		//We have to cast the id as array, or the helper function will return an
empty set
		if($cid)
		{
			$cid = (array) $cid;
		}

        FOFUtilsArray::toInteger($cid);
		$user_id = (int) $user_id;
		$publish = (int) $publish;
		$k       = $this->_tbl_key;

		if (count($cid) < 1)
		{
			if ($this->$k)
			{
				$cid = array($this->$k);
			}
			else
			{
				$this->setError("No items selected.");

				return false;
			}
		}

		if (!$this->onBeforePublish($cid, $publish))
		{
			return false;
		}

		$query = $this->_db->getQuery(true)
			->update($this->_db->qn($this->_tbl))
			->set($this->_db->qn($enabledName) . ' = ' . (int)
$publish);

		$checkin = in_array($locked_byName, $this->getKnownFields());

		if ($checkin)
		{
			$query->where(
				' (' . $this->_db->qn($locked_byName) .
					' = 0 OR ' . $this->_db->qn($locked_byName) . ' =
' . (int) $user_id . ')', 'AND'
			);
		}

		// TODO Rewrite this statment using IN. Check if it work in SQLServer and
PostgreSQL
		$cids = $this->_db->qn($k) . ' = ' . implode(' OR
' . $this->_db->qn($k) . ' = ', $cid);

		$query->where('(' . $cids . ')');

		$this->_db->setQuery((string) $query);

		if (version_compare(JVERSION, '3.0', 'ge'))
		{
			try
			{
				$this->_db->execute();
			}
			catch (Exception $e)
			{
				$this->setError($e->getMessage());
			}
		}
		else
		{
			if (!$this->_db->execute())
			{
				$this->setError($this->_db->getErrorMsg());

				return false;
			}
		}

		if (count($cid) == 1 && $checkin)
		{
			if ($this->_db->getAffectedRows() == 1)
			{
				$this->checkin($cid[0]);

				if ($this->$k == $cid[0])
				{
					$this->$enabledName = $publish;
				}
			}
		}

		$this->setError('');

		return true;
	}

	/**
	 * Delete a record
	 *
	 * @param   integer $oid  The primary key value of the item to delete
	 *
	 * @throws  UnexpectedValueException
	 *
	 * @return  boolean  True on success
	 */
	public function delete($oid = null)
	{
		if ($oid)
		{
			$this->load($oid);
		}

		$k  = $this->_tbl_key;
		$pk = (!$oid) ? $this->$k : $oid;

		// If no primary key is given, return false.
		if (!$pk)
		{
			throw new UnexpectedValueException('Null primary key not
allowed.');
		}

		// Execute the logic only if I have a primary key, otherwise I could have
weird results
		if (!$this->onBeforeDelete($oid))
		{
			return false;
		}

		// Delete the row by primary key.
		$query = $this->_db->getQuery(true);
		$query->delete();
		$query->from($this->_tbl);
		$query->where($this->_tbl_key . ' = ' .
$this->_db->q($pk));
		$this->_db->setQuery($query);

		$this->_db->execute();

		$result = $this->onAfterDelete($oid);

		return $result;
	}

	/**
	 * Register a hit on a record
	 *
	 * @param   integer  $oid  The primary key value of the record
	 * @param   boolean  $log  Should I log the hit?
	 *
	 * @return  boolean  True on success
	 */
	public function hit($oid = null, $log = false)
	{
		if (!$this->onBeforeHit($oid, $log))
		{
			return false;
		}

		// If there is no hits field, just return true.
		$hits_field = $this->getColumnAlias('hits');

		if (!in_array($hits_field, $this->getKnownFields()))
		{
			return true;
		}

		$k  = $this->_tbl_key;
		$pk = ($oid) ? $oid : $this->$k;

		// If no primary key is given, return false.
		if (!$pk)
		{
			$result = false;
		}
		else
		{
			// Check the row in by primary key.
			$query = $this->_db->getQuery(true)
						  ->update($this->_tbl)
						  ->set($this->_db->qn($hits_field) . ' = (' .
$this->_db->qn($hits_field) . ' + 1)')
						  ->where($this->_tbl_key . ' = ' .
$this->_db->q($pk));

			$this->_db->setQuery($query)->execute();

			// In order to update the table object, I have to load the table
			if(!$this->$k)
			{
				$query = $this->_db->getQuery(true)
							  ->select($this->_db->qn($hits_field))
							  ->from($this->_db->qn($this->_tbl))
							  ->where($this->_db->qn($this->_tbl_key) . ' =
' . $this->_db->q($pk));

				$this->$hits_field =
$this->_db->setQuery($query)->loadResult();
			}
			else
			{
				// Set table values in the object.
				$this->$hits_field++;
			}

			$result = true;
		}

		if ($result)
		{
			$result = $this->onAfterHit($oid);
		}

		return $result;
	}

	/**
	 * Export the item as a CSV line
	 *
	 * @param   string  $separator  CSV separator. Tip: use "\t" to
get a TSV file instead.
	 *
	 * @return  string  The CSV line
	 */
	public function toCSV($separator = ',')
	{
		$csv = array();

		foreach (get_object_vars($this) as $k => $v)
		{
			if (!in_array($k, $this->getKnownFields()))
			{
				continue;
			}

			$csv[] = '"' . str_replace('"',
'""', $v) . '"';
		}

		$csv = implode($separator, $csv);

		return $csv;
	}

	/**
	 * Exports the table in array format
	 *
	 * @return  array
	 */
	public function getData()
	{
		$ret = array();

		foreach (get_object_vars($this) as $k => $v)
		{
			if (!in_array($k, $this->getKnownFields()))
			{
				continue;
			}

			$ret[$k] = $v;
		}

		return $ret;
	}

	/**
	 * Get the header for exporting item list to CSV
	 *
	 * @param   string  $separator  CSV separator. Tip: use "\t" to
get a TSV file instead.
	 *
	 * @return  string  The CSV file's header
	 */
	public function getCSVHeader($separator = ',')
	{
		$csv = array();

		foreach (get_object_vars($this) as $k => $v)
		{
			if (!in_array($k, $this->getKnownFields()))
			{
				continue;
			}

			$csv[] = '"' . str_replace('"',
'\"', $k) . '"';
		}

		$csv = implode($separator, $csv);

		return $csv;
	}

	/**
	 * Get the columns from a database table.
	 *
	 * @param   string  $tableName  Table name. If null current table is used
	 *
	 * @return  mixed  An array of the field names, or false if an error
occurs.
	 */
	public function getTableFields($tableName = null)
	{
		// Should I load the cached data?
		$useCache = array_key_exists('use_table_cache',
$this->config) ? $this->config['use_table_cache'] : false;

		// Make sure we have a list of tables in this db

		if (empty(self::$tableCache))
		{
			if ($useCache)
			{
				// Try to load table cache from a cache file
				$cacheData =
FOFPlatform::getInstance()->getCache('tables', null);

				// Unserialise the cached data, or set the table cache to empty
				// if the cache data wasn't loaded.
				if (!is_null($cacheData))
				{
					self::$tableCache = json_decode($cacheData, true);
				}
				else
				{
					self::$tableCache = array();
				}
			}

			// This check is true if the cache data doesn't exist / is not
loaded
			if (empty(self::$tableCache))
			{
				self::$tableCache = $this->_db->getTableList();

				if ($useCache)
				{
					FOFPlatform::getInstance()->setCache('tables',
json_encode(self::$tableCache));
				}
			}
		}

		// Make sure the cached table fields cache is loaded
		if (empty(self::$tableFieldCache))
		{
			if ($useCache)
			{
				// Try to load table cache from a cache file
				$cacheData =
FOFPlatform::getInstance()->getCache('tablefields', null);

				// Unserialise the cached data, or set to empty if the cache
				// data wasn't loaded.
				if (!is_null($cacheData))
				{
					$decoded = json_decode($cacheData, true);
					$tableCache = array();

					if (count($decoded))
					{
						foreach ($decoded as $myTableName => $tableFields)
						{
							$temp = array();

							if (is_array($tableFields))
							{
								foreach($tableFields as $field => $def)
								{
									$temp[$field] = (object)$def;
								}
								$tableCache[$myTableName] = $temp;
							}
							elseif (is_object($tableFields) || is_bool($tableFields))
							{
								$tableCache[$myTableName] = $tableFields;
							}
						}
					}

					self::$tableFieldCache = $tableCache;
				}
				else
				{
					self::$tableFieldCache = array();
				}
			}
		}

		if (!$tableName)
		{
			$tableName = $this->_tbl;
		}

		// Try to load again column specifications if the table is not loaded OR
if it's loaded and
		// the previous call returned an error
		if (!array_key_exists($tableName, self::$tableFieldCache) ||
			(isset(self::$tableFieldCache[$tableName]) &&
!self::$tableFieldCache[$tableName]))
		{
			// Lookup the fields for this table only once.
			$name = $tableName;

			$prefix = $this->_db->getPrefix();

			if (substr($name, 0, 3) == '#__')
			{
				$checkName = $prefix . substr($name, 3);
			}
			else
			{
				$checkName = $name;
			}

			if (!in_array($checkName, self::$tableCache))
			{
				// The table doesn't exist. Return false.
				self::$tableFieldCache[$tableName] = false;
			}
			elseif (version_compare(JVERSION, '3.0', 'ge'))
			{
				$fields = $this->_db->getTableColumns($name, false);

				if (empty($fields))
				{
					$fields = false;
				}

				self::$tableFieldCache[$tableName] = $fields;
			}
			else
			{
				$fields = $this->_db->getTableFields($name, false);

				if (!isset($fields[$name]))
				{
					$fields = false;
				}

				self::$tableFieldCache[$tableName] = $fields[$name];
			}

			// PostgreSQL date type compatibility
			if (($this->_db->name == 'postgresql') &&
(self::$tableFieldCache[$tableName] != false))
			{
				foreach (self::$tableFieldCache[$tableName] as $field)
				{
					if (strtolower($field->type) == 'timestamp without time
zone')
					{
						if (stristr($field->Default, '\'::timestamp without time
zone'))
						{
							list ($date, $junk) = explode('::', $field->Default,
2);
							$field->Default = trim($date, "'");
						}
					}
				}
			}

			// Save the data for this table into the cache
			if ($useCache)
			{
				$cacheData =
FOFPlatform::getInstance()->setCache('tablefields',
json_encode(self::$tableFieldCache));
			}
		}

		return self::$tableFieldCache[$tableName];
	}

	public function getTableAlias()
	{
		return $this->_tableAlias;
	}

	public function setTableAlias($string)
	{
		$string = preg_replace('#[^A-Z0-9_]#i', '', $string);
		$this->_tableAlias = $string;
	}

	/**
	 * Method to return the real name of a "special" column such as
ordering, hits, published
	 * etc etc. In this way you are free to follow your db naming convention
and use the
	 * built in Joomla functions.
	 *
	 * @param   string  $column  Name of the "special" column (ie
ordering, hits etc etc)
	 *
	 * @return  string  The string that identify the special
	 */
	public function getColumnAlias($column)
	{
		if (isset($this->_columnAlias[$column]))
		{
			$return = $this->_columnAlias[$column];
		}
		else
		{
			$return = $column;
		}

		$return = preg_replace('#[^A-Z0-9_]#i', '', $return);

		return $return;
	}

	/**
	 * Method to register a column alias for a "special" column.
	 *
	 * @param   string  $column       The "special" column (ie
ordering)
	 * @param   string  $columnAlias  The real column name (ie foo_ordering)
	 *
	 * @return  void
	 */
	public function setColumnAlias($column, $columnAlias)
	{
		$column = strtolower($column);

		$column                      = preg_replace('#[^A-Z0-9_]#i',
'', $column);
		$this->_columnAlias[$column] = $columnAlias;
	}

	/**
	 * Get a JOIN query, used to join other tables
	 *
	 * @param   boolean  $asReference  Return an object reference instead of a
copy
	 *
	 * @return  FOFDatabaseQuery  Query used to join other tables
	 */
	public function getQueryJoin($asReference = false)
	{
		if ($asReference)
		{
			return $this->_queryJoin;
		}
		else
		{
			if ($this->_queryJoin)
			{
				return clone $this->_queryJoin;
			}
			else
			{
				return null;
			}
		}
	}

	/**
	 * Sets the query with joins to other tables
	 *
	 * @param   FOFDatabaseQuery  $query  The JOIN query to use
	 *
	 * @return  void
	 */
	public function setQueryJoin($query)
	{
		$this->_queryJoin = $query;
	}

	/**
	 * Extracts the fields from the join query
	 *
	 * @return   array    Fields contained in the join query
	 */
	protected function getQueryJoinFields()
	{
		$query = $this->getQueryJoin();

		if (!$query)
		{
			return array();
		}

		$tables   = array();
		$j_tables = array();
		$j_fields = array();

		// Get joined tables. Ignore FROM clause, since it should not be used
(the starting point is the table "table")
		$joins    = $query->join;

		foreach ($joins as $join)
		{
			$tables = array_merge($tables, $join->getElements());
		}

		// Clean up table names
		foreach($tables as $table)
		{
			preg_match('#(.*)((\w)*(on|using))(.*)#i', $table, $matches);

			if($matches && isset($matches[1]))
			{
				// I always want the first part, no matter what
				$parts = explode(' ', $matches[1]);
				$t_table = $parts[0];

				if($this->isQuoted($t_table))
				{
					$t_table = substr($t_table, 1, strlen($t_table) - 2);
				}

				if(!in_array($t_table, $j_tables))
				{
					$j_tables[] =  $t_table;
				}
			}
		}

		// Do I have the current table inside the query join? Remove it (its
fields are already ok)
		$find = array_search($this->getTableName(), $j_tables);
		if($find !== false)
		{
			unset($j_tables[$find]);
		}

		// Get table fields
		$fields = array();

		foreach ($j_tables as $table)
		{
			$t_fields = $this->getTableFields($table);

			if ($t_fields)
			{
				$fields = array_merge($fields, $t_fields);
			}
		}

		// Remove any fields that aren't in the joined select
		$j_select = $query->select;

		if ($j_select && $j_select->getElements())
		{
			$j_fields =
$this->normalizeSelectFields($j_select->getElements());
		}

		// I can intesect the keys
		$fields   = array_intersect_key($fields, $j_fields);

		// Now I walk again the array to change the key of columns that have an
alias
		foreach ($j_fields as $column => $alias)
		{
			if ($column != $alias)
			{
				$fields[$alias] = $fields[$column];
				unset($fields[$column]);
			}
		}

		return $fields;
	}

	/**
	 * Normalizes the fields, returning an associative array with all the
fields.
	 * Ie array('foobar as foo, bar') becomes
array('foobar' => 'foo', 'bar' =>
'bar')
	 *
	 * @param   array $fields    Array with column fields
	 *
	 * @return  array  Normalized array
	 */
	protected function normalizeSelectFields($fields)
	{
		$db     = FOFPlatform::getInstance()->getDbo();
		$return = array();

		foreach ($fields as $field)
		{
			$t_fields = explode(',', $field);

			foreach ($t_fields as $t_field)
			{
				// Is there any alias?
				$parts  = preg_split('#\sas\s#i', $t_field);

				// Do I have a table.column situation? Let's get the field name
				$tableField  = explode('.', $parts[0]);

				if(isset($tableField[1]))
				{
					$column = trim($tableField[1]);
				}
				else
				{
					$column = trim($tableField[0]);
				}

				// Is this field quoted? If so, remove the quotes
				if($this->isQuoted($column))
				{
					$column = substr($column, 1, strlen($column) - 2);
				}

				if(isset($parts[1]))
				{
					$alias = trim($parts[1]);

					// Is this field quoted? If so, remove the quotes
					if($this->isQuoted($alias))
					{
						$alias = substr($alias, 1, strlen($alias) - 2);
					}
				}
				else
				{
					$alias = $column;
				}

				$return[$column] = $alias;
			}
		}

		return $return;
	}

	/**
	 * Is the field quoted?
	 *
	 * @param   string  $column     Column field
	 *
	 * @return  bool    Is the field quoted?
	 */
	protected function isQuoted($column)
	{
		// Empty string, un-quoted by definition
		if(!$column)
		{
			return false;
		}

		// I need some "magic". If the first char is not a letter, a
number
		// an underscore or # (needed for table), then most likely the field is
quoted
		preg_match_all('/^[a-z0-9_#]/i', $column, $matches);

		if(!$matches[0])
		{
			return true;
		}

		return false;
	}

	/**
	 * The event which runs before binding data to the table
	 *
	 * NOTE TO 3RD PARTY DEVELOPERS:
	 *
	 * When you override the following methods in your child classes,
	 * be sure to call parent::method *AFTER* your code, otherwise the
	 * plugin events do NOT get triggered
	 *
	 * Example:
	 * protected function onBeforeBind(){
	 *       // Your code here
	 *     return parent::onBeforeBind() && $your_result;
	 * }
	 *
	 * Do not do it the other way around, e.g. return $your_result &&
parent::onBeforeBind()
	 * Due to  PHP short-circuit boolean evaluation the parent::onBeforeBind()
	 * will not be called if $your_result is false.
	 *
	 * @param   object|array  &$from  The data to bind
	 *
	 * @return  boolean  True on success
	 */
	protected function onBeforeBind(&$from)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeBind',
array(&$this, &$from));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforeBind' .
ucfirst($name), array(&$this, &$from));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after loading a record from the database
	 *
	 * @param   boolean  &$result  Did the load succeeded?
	 *
	 * @return  void
	 */
	protected function onAfterLoad(&$result)
	{
		// Call the behaviors
		$eventResult =
$this->tableDispatcher->trigger('onAfterLoad',
array(&$this, &$result));

		if (in_array(false, $eventResult, true))
		{
			// Behavior failed, return false
			$result = false;
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			FOFPlatform::getInstance()->runPlugins('onAfterLoad' .
ucfirst($name), array(&$this, &$result));
		}
	}

	/**
	 * The event which runs before storing (saving) data to the database
	 *
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true)
or just skipped over (false)?
	 *
	 * @return  boolean  True to allow saving
	 */
	protected function onBeforeStore($updateNulls)
	{
		// Do we have a "Created" set of fields?
		$created_on  = $this->getColumnAlias('created_on');
		$created_by  = $this->getColumnAlias('created_by');
		$modified_on = $this->getColumnAlias('modified_on');
		$modified_by = $this->getColumnAlias('modified_by');
		$locked_on   = $this->getColumnAlias('locked_on');
		$locked_by   = $this->getColumnAlias('locked_by');
		$title       = $this->getColumnAlias('title');
		$slug        = $this->getColumnAlias('slug');

		$hasCreatedOn = in_array($created_on, $this->getKnownFields());
		$hasCreatedBy = in_array($created_by, $this->getKnownFields());

		if ($hasCreatedOn && $hasCreatedBy)
		{
			$hasModifiedOn = in_array($modified_on, $this->getKnownFields());
			$hasModifiedBy = in_array($modified_by, $this->getKnownFields());

			$nullDate = $this->_db->getNullDate();

			if (empty($this->$created_by) || ($this->$created_on == $nullDate)
|| empty($this->$created_on))
			{
				$uid = FOFPlatform::getInstance()->getUser()->id;

				if ($uid)
				{
					$this->$created_by =
FOFPlatform::getInstance()->getUser()->id;
				}

				$date = FOFPlatform::getInstance()->getDate('now', null,
false);

				$this->$created_on = method_exists($date, 'toSql') ?
$date->toSql() : $date->toMySQL();
			}
			elseif ($hasModifiedOn && $hasModifiedBy)
			{
				$uid = FOFPlatform::getInstance()->getUser()->id;

				if ($uid)
				{
					$this->$modified_by =
FOFPlatform::getInstance()->getUser()->id;
				}

                $date =
FOFPlatform::getInstance()->getDate('now', null, false);

				$this->$modified_on = method_exists($date, 'toSql') ?
$date->toSql() : $date->toMySQL();
			}
		}

		// Do we have a set of title and slug fields?
		$hasTitle = in_array($title, $this->getKnownFields());
		$hasSlug  = in_array($slug, $this->getKnownFields());

		if ($hasTitle && $hasSlug)
		{
			if (empty($this->$slug))
			{
				// Create a slug from the title
				$this->$slug = FOFStringUtils::toSlug($this->$title);
			}
			else
			{
				// Filter the slug for invalid characters
				$this->$slug = FOFStringUtils::toSlug($this->$slug);
			}

			// Make sure we don't have a duplicate slug on this table
			$db    = $this->getDbo();
			$query = $db->getQuery(true)
				->select($db->qn($slug))
				->from($this->_tbl)
				->where($db->qn($slug) . ' = ' .
$db->q($this->$slug))
				->where('NOT ' . $db->qn($this->_tbl_key) . ' =
' . $db->q($this->{$this->_tbl_key}));
			$db->setQuery($query);
			$existingItems = $db->loadAssocList();

			$count   = 0;
			$newSlug = $this->$slug;

			while (!empty($existingItems))
			{
				$count++;
				$newSlug = $this->$slug . '-' . $count;
				$query   = $db->getQuery(true)
					->select($db->qn($slug))
					->from($this->_tbl)
					->where($db->qn($slug) . ' = ' . $db->q($newSlug))
					->where('NOT '. $db->qn($this->_tbl_key) . ' =
' . $db->q($this->{$this->_tbl_key}));
				$db->setQuery($query);
				$existingItems = $db->loadAssocList();
			}

			$this->$slug = $newSlug;
		}

		// Call the behaviors
		$result =
$this->tableDispatcher->trigger('onBeforeStore',
array(&$this, $updateNulls));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		// Execute onBeforeStore<tablename> events in loaded plugins
		if ($this->_trigger_events)
		{
			$name       = FOFInflector::pluralize($this->getKeyName());
			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforeStore' .
ucfirst($name), array(&$this, $updateNulls));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after binding data to the class
	 *
	 * @param   object|array  &$src  The data to bind
	 *
	 * @return  boolean  True to allow binding without an error
	 */
	protected function onAfterBind(&$src)
	{
		// Call the behaviors
		$options = array(
			'component' 	=>
$this->input->get('option'),
			'view'			=> $this->input->get('view'),
			'table_prefix'	=> $this->_tablePrefix
		);

		$result = $this->tableDispatcher->trigger('onAfterBind',
array(&$this, &$src, $options));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onAfterBind' .
ucfirst($name), array(&$this, &$src));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @return  boolean  True to allow saving without an error
	 */
	protected function onAfterStore()
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterStore',
array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onAfterStore' .
ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before moving a record
	 *
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true)
or just skipped over (false)?
	 *
	 * @return  boolean  True to allow moving
	 */
	protected function onBeforeMove($updateNulls)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeMove',
array(&$this, $updateNulls));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforeMove' .
ucfirst($name), array(&$this, $updateNulls));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after moving a record
	 *
	 * @return  boolean  True to allow moving without an error
	 */
	protected function onAfterMove()
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterMove',
array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onAfterMove' .
ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before reordering a table
	 *
	 * @param   string  $where  The WHERE clause of the SQL query to run on
reordering (record filter)
	 *
	 * @return  boolean  True to allow reordering
	 */
	protected function onBeforeReorder($where = '')
	{
		// Call the behaviors
		$result =
$this->tableDispatcher->trigger('onBeforeReorder',
array(&$this, $where));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforeReorder' .
ucfirst($name), array(&$this, $where));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after reordering a table
	 *
	 * @return  boolean  True to allow the reordering to complete without an
error
	 */
	protected function onAfterReorder()
	{
		// Call the behaviors
		$result =
$this->tableDispatcher->trigger('onAfterReorder',
array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onAfterReorder' .
ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   integer  $oid  The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	protected function onBeforeDelete($oid)
	{
		// Call the behaviors
		$result =
$this->tableDispatcher->trigger('onBeforeDelete',
array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforeDelete' .
ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after deleting a record
	 *
	 * @param   integer  $oid  The PK value of the record which was deleted
	 *
	 * @return  boolean  True to allow the deletion without errors
	 */
	protected function onAfterDelete($oid)
	{
		// Call the behaviors
		$result =
$this->tableDispatcher->trigger('onAfterDelete',
array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onAfterDelete' .
ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before hitting a record
	 *
	 * @param   integer  $oid  The PK value of the record to hit
	 * @param   boolean  $log  Should we log the hit?
	 *
	 * @return  boolean  True to allow the hit
	 */
	protected function onBeforeHit($oid, $log)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeHit',
array(&$this, $oid, $log));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforeHit' .
ucfirst($name), array(&$this, $oid, $log));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after hitting a record
	 *
	 * @param   integer  $oid  The PK value of the record which was hit
	 *
	 * @return  boolean  True to allow the hitting without errors
	 */
	protected function onAfterHit($oid)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterHit',
array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onAfterHit' .
ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The even which runs before copying a record
	 *
	 * @param   integer  $oid  The PK value of the record being copied
	 *
	 * @return  boolean  True to allow the copy to take place
	 */
	protected function onBeforeCopy($oid)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeCopy',
array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforeCopy' .
ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The even which runs after copying a record
	 *
	 * @param   integer  $oid  The PK value of the record which was copied
(not the new one)
	 *
	 * @return  boolean  True to allow the copy without errors
	 */
	protected function onAfterCopy($oid)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterCopy',
array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onAfterCopy' .
ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before a record is (un)published
	 *
	 * @param   integer|array  &$cid     The PK IDs of the records being
(un)published
	 * @param   integer        $publish  1 to publish, 0 to unpublish
	 *
	 * @return  boolean  True to allow the (un)publish to proceed
	 */
	protected function onBeforePublish(&$cid, $publish)
	{
		// Call the behaviors
		$result =
$this->tableDispatcher->trigger('onBeforePublish',
array(&$this, &$cid, $publish));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforePublish' .
ucfirst($name), array(&$this, &$cid, $publish));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after the object is reset to its default values.
	 *
	 * @return  boolean  True to allow the reset to complete without errors
	 */
	protected function onAfterReset()
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterReset',
array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onAfterReset' .
ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The even which runs before the object is reset to its default values.
	 *
	 * @return  boolean  True to allow the reset to complete
	 */
	protected function onBeforeReset()
	{
		// Call the behaviors
		$result =
$this->tableDispatcher->trigger('onBeforeReset',
array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     =
FOFPlatform::getInstance()->runPlugins('onBeforeReset' .
ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * Replace the input object of this table with the provided FOFInput
object
	 *
	 * @param   FOFInput  $input  The new input object
	 *
	 * @return  void
	 */
	public function setInput(FOFInput $input)
	{
		$this->input = $input;
	}

	/**
	 * Get the columns from database table.
	 *
	 * @return  mixed  An array of the field names, or false if an error
occurs.
	 *
	 * @deprecated  2.1
	 */
	public function getFields()
	{
		return $this->getTableFields();
	}

	/**
	 * Add a filesystem path where FOFTable should search for table class
files.
	 * You may either pass a string or an array of paths.
	 *
	 * @param   mixed  $path  A filesystem path or array of filesystem paths
to add.
	 *
	 * @return  array  An array of filesystem paths to find FOFTable classes
in.
	 */
	public static function addIncludePath($path = null)
	{
		// If the internal paths have not been initialised, do so with the base
table path.
		if (empty(self::$_includePaths))
		{
			self::$_includePaths = array(__DIR__);
		}

		// Convert the passed path(s) to add to an array.
		settype($path, 'array');

		// If we have new paths to add, do so.
		if (!empty($path) && !in_array($path, self::$_includePaths))
		{
			// Check and add each individual new path.
			foreach ($path as $dir)
			{
				// Sanitize path.
				$dir = trim($dir);

				// Add to the front of the list so that custom paths are searched
first.
				array_unshift(self::$_includePaths, $dir);
			}
		}

		return self::$_includePaths;
	}

	/**
	 * Loads the asset table related to this table.
	 * This will help tests, too, since we can mock this function.
	 *
	 * @return bool|JTableAsset     False on failure, otherwise JTableAsset
	 */
	protected function getAsset()
	{
		$name     = $this->_getAssetName();

		// Do NOT touch JTable here -- we are loading the core asset table which
is a JTable, not a FOFTable
		$asset    = JTable::getInstance('Asset');

		if (!$asset->loadByName($name))
		{
			return false;
		}

		return $asset;
	}

    /**
     * Method to compute the default name of the asset.
     * The default name is in the form table_name.id
     * where id is the value of the primary key of the table.
     *
     * @throws  UnexpectedValueException
     *
     * @return  string
     */
	public function getAssetName()
	{
		$k = $this->_tbl_key;

        // If there is no assetKey defined, stop here, or we'll get a
wrong name
        if(!$this->_assetKey || !$this->$k)
        {
            throw new UnexpectedValueException('Table must have an
asset key defined and a value for the table id in order to track
assets');
        }

		return $this->_assetKey . '.' . (int) $this->$k;
	}

	/**
     * Method to compute the default name of the asset.
     * The default name is in the form table_name.id
     * where id is the value of the primary key of the table.
     *
     * @throws  UnexpectedValueException
     *
     * @return  string
     */
	public function getAssetKey()
	{
		return $this->_assetKey;
	}

	/**
	 * Method to return the title to use for the asset table.  In
	 * tracking the assets a title is kept for each asset so that there is
some
	 * context available in a unified access manager.  Usually this would just
	 * return $this->title or $this->name or whatever is being used for
the
	 * primary name of the row. If this method is not overridden, the asset
name is used.
	 *
	 * @return  string  The string to use as the title in the asset table.
	 */
	public function getAssetTitle()
	{
		return $this->getAssetName();
	}

	/**
	 * Method to get the parent asset under which to register this one.
	 * By default, all assets are registered to the ROOT node with ID,
	 * which will default to 1 if none exists.
	 * The extended class can define a table and id to lookup.  If the
	 * asset does not exist it will be created.
	 *
	 * @param   FOFTable  $table  A FOFTable object for the asset parent.
	 * @param   integer   $id     Id to look up
	 *
	 * @return  integer
	 */
	public function getAssetParentId($table = null, $id = null)
	{
		// For simple cases, parent to the asset root.
		$assets = JTable::getInstance('Asset', 'JTable',
array('dbo' => $this->getDbo()));
		$rootId = $assets->getRootId();

		if (!empty($rootId))
		{
			return $rootId;
		}

		return 1;
	}

	/**
	 * This method sets the asset key for the items of this table. Obviously,
it
	 * is only meant to be used when you have a table with an asset field.
	 *
	 * @param   string  $assetKey  The name of the asset key to use
	 *
	 * @return  void
	 */
	public function setAssetKey($assetKey)
	{
		$this->_assetKey = $assetKey;
	}

	/**
	 * Method to get the database table name for the class.
	 *
	 * @return  string  The name of the database table being modeled.
	 */
	public function getTableName()
	{
		return $this->_tbl;
	}

	/**
	 * Method to get the primary key field name for the table.
	 *
	 * @return  string  The name of the primary key for the table.
	 */
	public function getKeyName()
	{
		return $this->_tbl_key;
	}

	/**
	 * Returns the identity value of this record
	 *
	 * @return mixed
	 */
	public function getId()
	{
		$key = $this->getKeyName();

		return $this->$key;
	}

	/**
	 * Method to get the FOFDatabaseDriver object.
	 *
	 * @return  FOFDatabaseDriver  The internal database driver object.
	 */
	public function getDbo()
	{
		return $this->_db;
	}

	/**
	 * Method to set the FOFDatabaseDriver object.
	 *
	 * @param   FOFDatabaseDriver  $db  A FOFDatabaseDriver object to be used
by the table object.
	 *
	 * @return  boolean  True on success.
	 */
	public function setDBO($db)
	{
		$this->_db = $db;

		return true;
	}

	/**
	 * Method to set rules for the record.
	 *
	 * @param   mixed  $input  A JAccessRules object, JSON string, or array.
	 *
	 * @return  void
	 */
	public function setRules($input)
	{
		if ($input instanceof JAccessRules)
		{
			$this->_rules = $input;
		}
		else
		{
			$this->_rules = new JAccessRules($input);
		}
	}

	/**
	 * Method to get the rules for the record.
	 *
	 * @return  JAccessRules object
	 */
	public function getRules()
	{
		return $this->_rules;
	}

	/**
	 * Method to check if the record is treated as an ACL asset
	 *
	 * @return  boolean [description]
	 */
	public function isAssetsTracked()
	{
		return $this->_trackAssets;
	}

    /**
     * Method to manually set this record as ACL asset or not.
     * We have to do this since the automatic check is made in the
constructor, but here we can't set any alias.
     * So, even if you have an alias for `asset_id`, it wouldn't be
reconized and assets won't be tracked.
     *
     * @param $state
     */
    public function setAssetsTracked($state)
    {
        $state = (bool) $state;

        if($state)
        {
            JLoader::import('joomla.access.rules');
        }

        $this->_trackAssets = $state;
    }

	/**
	 * Method to provide a shortcut to binding, checking and storing a
FOFTable
	 * instance to the database table.  The method will check a row in once
the
	 * data has been stored and if an ordering filter is present will attempt
to
	 * reorder the table rows based on the filter.  The ordering filter is an
instance
	 * property name.  The rows that will be reordered are those whose value
matches
	 * the FOFTable instance for the property specified.
	 *
	 * @param   mixed   $src             An associative array or object to
bind to the FOFTable instance.
	 * @param   string  $orderingFilter  Filter for the order updating
	 * @param   mixed   $ignore          An optional array or space separated
list of properties
	 *                                   to ignore while binding.
	 *
	 * @return  boolean  True on success.
	 */
	public function save($src, $orderingFilter = '', $ignore =
'')
	{
		// Attempt to bind the source to the instance.
		if (!$this->bind($src, $ignore))
		{
			return false;
		}

		// Run any sanity checks on the instance and verify that it is ready for
storage.
		if (!$this->check())
		{
			return false;
		}

		// Attempt to store the properties to the database table.
		if (!$this->store())
		{
			return false;
		}

		// Attempt to check the row in, just in case it was checked out.
		if (!$this->checkin())
		{
			return false;
		}

		// If an ordering filter is set, attempt reorder the rows in the table
based on the filter and value.
		if ($orderingFilter)
		{
			$filterValue = $this->$orderingFilter;
			$this->reorder($orderingFilter ?
$this->_db->qn($orderingFilter) . ' = ' .
$this->_db->q($filterValue) : '');
		}

		// Set the error to empty and return true.
		$this->setError('');

		return true;
	}

	/**
	 * Method to get the next ordering value for a group of rows defined by an
SQL WHERE clause.
	 * This is useful for placing a new item last in a group of items in the
table.
	 *
	 * @param   string  $where  WHERE clause to use for selecting the
MAX(ordering) for the table.
	 *
	 * @return  mixed  Boolean false an failure or the next ordering value as
an integer.
	 */
	public function getNextOrder($where = '')
	{
		// If there is no ordering field set an error and return false.
		$ordering = $this->getColumnAlias('ordering');
		if (!in_array($ordering, $this->getKnownFields()))
		{
			throw new UnexpectedValueException(sprintf('%s does not support
ordering.', get_class($this)));
		}

		// Get the largest ordering value for a given where clause.
		$query = $this->_db->getQuery(true);
		$query->select('MAX('.$this->_db->qn($ordering).')');
		$query->from($this->_tbl);

		if ($where)
		{
			$query->where($where);
		}

		$this->_db->setQuery($query);
		$max = (int) $this->_db->loadResult();

		// Return the largest ordering value + 1.
		return ($max + 1);
	}

	/**
	 * Method to lock the database table for writing.
	 *
	 * @return  boolean  True on success.
	 *
	 * @throws  RuntimeException
	 */
	protected function _lock()
	{
		$this->_db->lockTable($this->_tbl);
		$this->_locked = true;

		return true;
	}

	/**
	 * Method to unlock the database table for writing.
	 *
	 * @return  boolean  True on success.
	 */
	protected function _unlock()
	{
		$this->_db->unlockTables();
		$this->_locked = false;

		return true;
	}

	public function setConfig(array $config)
	{
		$this->config = $config;
	}

	/**
	 * Get the content type for ucm
	 *
	 * @return string The content type alias
	 */
	public function getContentType()
	{
		if ($this->contentType)
		{
			return $this->contentType;
		}

		/**
		 * When tags was first introduced contentType variable didn't exist
- so we guess one
		 * This will fail if content history behvaiour is enabled. This code is
deprecated
		 * and will be removed in FOF 3.0 in favour of the content type class
variable
		 */
		$component = $this->input->get('option');

		$view =
FOFInflector::singularize($this->input->get('view'));
		$alias = $component . '.' . $view;

		return $alias;
	}

	/**
	 * Returns the table relations object of the current table, lazy-loading
it if necessary
	 *
	 * @return  FOFTableRelations
	 */
	public function getRelations()
	{
		if (is_null($this->_relations))
		{
			$this->_relations = new FOFTableRelations($this);
		}

		return $this->_relations;
	}

	/**
	 * Gets a reference to the configuration parameters provider for this
table
	 *
	 * @return  FOFConfigProvider
	 */
	public function getConfigProvider()
	{
		return $this->configProvider;
	}

	/**
	 * Returns the configuration parameters provider's key for this table
	 *
	 * @return  string
	 */
	public function getConfigProviderKey()
	{
		return $this->_configProviderKey;
	}

	/**
	 * Check if a UCM content type exists for this resource, and
	 * create it if it does not
	 *
	 * @param  string  $alias  The content type alias (optional)
	 *
	 * @return  null
	 */
	public function checkContentType($alias = null)
	{
		$contentType = new JTableContenttype($this->getDbo());

		if (!$alias)
		{
			$alias = $this->getContentType();
		}

		$aliasParts = explode('.', $alias);

		// Fetch the extension name
		$component = $aliasParts[0];
		$component = JComponentHelper::getComponent($component);

		// Fetch the name using the menu item
		$query = $this->getDbo()->getQuery(true);
		$query->select('title')->from('#__menu')->where('component_id
= ' . (int) $component->id);
		$this->getDbo()->setQuery($query);
		$component_name = JText::_($this->getDbo()->loadResult());

		$name = $component_name . ' ' . ucfirst($aliasParts[1]);

		// Create a new content type for our resource
		if (!$contentType->load(array('type_alias' => $alias)))
		{
			$contentType->type_title = $name;
			$contentType->type_alias = $alias;
			$contentType->table = json_encode(
				array(
					'special' => array(
						'dbtable' => $this->getTableName(),
						'key'     => $this->getKeyName(),
						'type'    => $name,
						'prefix'  => $this->_tablePrefix,
						'class'   => 'FOFTable',
						'config'  => 'array()'
					),
					'common' => array(
						'dbtable' => '#__ucm_content',
						'key' => 'ucm_id',
						'type' => 'CoreContent',
						'prefix' => 'JTable',
						'config' => 'array()'
					)
				)
			);

			$contentType->field_mappings = json_encode(
				array(
					'common' => array(
						0 => array(
							"core_content_item_id" => $this->getKeyName(),
							"core_title"           =>
$this->getUcmCoreAlias('title'),
							"core_state"           =>
$this->getUcmCoreAlias('enabled'),
							"core_alias"           =>
$this->getUcmCoreAlias('alias'),
							"core_created_time"    =>
$this->getUcmCoreAlias('created_on'),
							"core_modified_time"   =>
$this->getUcmCoreAlias('created_by'),
							"core_body"            =>
$this->getUcmCoreAlias('body'),
							"core_hits"            =>
$this->getUcmCoreAlias('hits'),
							"core_publish_up"      =>
$this->getUcmCoreAlias('publish_up'),
							"core_publish_down"    =>
$this->getUcmCoreAlias('publish_down'),
							"core_access"          =>
$this->getUcmCoreAlias('access'),
							"core_params"          =>
$this->getUcmCoreAlias('params'),
							"core_featured"        =>
$this->getUcmCoreAlias('featured'),
							"core_metadata"        =>
$this->getUcmCoreAlias('metadata'),
							"core_language"        =>
$this->getUcmCoreAlias('language'),
							"core_images"          =>
$this->getUcmCoreAlias('images'),
							"core_urls"            =>
$this->getUcmCoreAlias('urls'),
							"core_version"         =>
$this->getUcmCoreAlias('version'),
							"core_ordering"        =>
$this->getUcmCoreAlias('ordering'),
							"core_metakey"         =>
$this->getUcmCoreAlias('metakey'),
							"core_metadesc"        =>
$this->getUcmCoreAlias('metadesc'),
							"core_catid"           =>
$this->getUcmCoreAlias('cat_id'),
							"core_xreference"      =>
$this->getUcmCoreAlias('xreference'),
							"asset_id"             =>
$this->getUcmCoreAlias('asset_id')
						)
					),
					'special' => array(
						0 => array(
						)
					)
				)
			);

			$ignoreFields = array(
				$this->getUcmCoreAlias('modified_on', null),
				$this->getUcmCoreAlias('modified_by', null),
				$this->getUcmCoreAlias('locked_by', null),
				$this->getUcmCoreAlias('locked_on', null),
				$this->getUcmCoreAlias('hits', null),
				$this->getUcmCoreAlias('version', null)
			);

			$contentType->content_history_options = json_encode(
				array(
					"ignoreChanges" => array_filter($ignoreFields,
'strlen')
				)
			);

			$contentType->router = '';

			$contentType->store();
		}
	}

	/**
	 * Utility methods that fetches the column name for the field.
	 * If it does not exists, returns a "null" string
	 *
	 * @param  string  $alias  The alias for the column
	 * @param  string  $null   What to return if no column exists
	 *
	 * @return string The column name
	 */
	protected function getUcmCoreAlias($alias, $null = "null")
	{
		$alias = $this->getColumnAlias($alias);

		if (in_array($alias, $this->getKnownFields()))
		{
			return $alias;
		}

		return $null;
	}
}
PKˠ�[���session.phpnu�[���<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Table
 *
 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All
rights reserved.
 * @license     GNU General Public License version 2 or later; see
LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Session table
 *
 * @since       1.5
 * @deprecated  3.2  Use SQL queries to interact with the session table.
 */
class JTableSession extends JTable
{
	/**
	 * Constructor
	 *
	 * @param   JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function __construct(JDatabaseDriver $db)
	{
		JLog::add('JTableSession is deprecated. Use SQL queries directly to
interact with the session table.', JLog::WARNING,
'deprecated');
		parent::__construct('#__session', 'session_id', $db);

		$this->guest = 1;
		$this->username = '';
	}

	/**
	 * Insert a session
	 *
	 * @param   string   $sessionId  The session id
	 * @param   integer  $clientId   The id of the client application
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function insert($sessionId, $clientId)
	{
		$this->session_id = $sessionId;
		$this->client_id = $clientId;

		$this->time = time();
		$ret = $this->_db->insertObject($this->_tbl, $this,
'session_id');

		if (!$ret)
		{
			$this->setError(JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED',
strtolower(get_class($this)), $this->_db->stderr()));

			return false;
		}
		else
		{
			return true;
		}
	}

	/**
	 * Updates the session
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are
null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function update($updateNulls = false)
	{
		$this->time = time();
		$ret = $this->_db->updateObject($this->_tbl, $this,
'session_id', $updateNulls);

		if (!$ret)
		{
			$this->setError(JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED',
strtolower(get_class($this)), $this->_db->stderr()));

			return false;
		}
		else
		{
			return true;
		}
	}

	/**
	 * Destroys the pre-existing session
	 *
	 * @param   integer  $userId     Identifier of the user for this session.
	 * @param   array    $clientIds  Array of client ids for which session(s)
will be destroyed
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function destroy($userId, $clientIds = array())
	{
		$clientIds = implode(',', $clientIds);

		$query = $this->_db->getQuery(true)
			->delete($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName('userid') . ' =
' . $this->_db->quote($userId))
			->where($this->_db->quoteName('client_id') . '
IN (' . $clientIds . ')');
		$this->_db->setQuery($query);

		if (!$this->_db->execute())
		{
			$this->setError($this->_db->stderr());

			return false;
		}

		return true;
	}

	/**
	 * Purge old sessions
	 *
	 * @param   integer  $maxLifetime  Session age in seconds
	 *
	 * @return  mixed  Resource on success, null on fail
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function purge($maxLifetime = 1440)
	{
		$past = time() - $maxLifetime;
		$query = $this->_db->getQuery(true)
			->delete($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName('time') . ' <
' . (int) $past);
		$this->_db->setQuery($query);

		return $this->_db->execute();
	}

	/**
	 * Find out if a user has one or more active sessions
	 *
	 * @param   integer  $userid  The identifier of the user
	 *
	 * @return  boolean  True if a session for this user exists
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function exists($userid)
	{
		$query = $this->_db->getQuery(true)
			->select('COUNT(userid)')
			->from($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName('userid') . ' =
' . $this->_db->quote($userid));
		$this->_db->setQuery($query);

		if (!$result = $this->_db->loadResult())
		{
			$this->setError($this->_db->stderr());

			return false;
		}

		return (boolean) $result;
	}

	/**
	 * Overloaded delete method
	 *
	 * We must override it because of the non-integer primary key
	 *
	 * @param   integer  $oid  The object id (optional).
	 *
	 * @return  mixed  True if successful otherwise an error message
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function delete($oid = null)
	{
		$k = $this->_tbl_key;

		if ($oid)
		{
			$this->$k = $oid;
		}

		$query = $this->_db->getQuery(true)
			->delete($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName($this->_tbl_key) . ' =
' . $this->_db->quote($this->$k));
		$this->_db->setQuery($query);

		$this->_db->execute();

		return true;
	}
}
PK!��`��behavior/assets.phpnu�[���PK!�Tʀff�behavior/contenthistory.phpnu�[���PK!�J�[[�!behavior/tags.phpnu�[���PK!�+u���.behavior.phpnu�[���PK!��|�Gdispatcher/behavior.phpnu�[���PK!����G�G�
{Inested.phpnu�[���PK!�=���
�1relations.phpnu�[���PK!�y��c�c	P�table.phpnu�[���PKˠ�[���session.phpnu�[���PK		��+