Файловый менеджер - Редактировать - /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/updating 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ʀf f behavior/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 ! ���%� � behavior/assets.phpnu �[��� PK ! �Tʀf f � behavior/contenthistory.phpnu �[��� PK ! �J�[ [ �! behavior/tags.phpnu �[��� PK ! �+u�� � . behavior.phpnu �[��� PK ! ��|� G dispatcher/behavior.phpnu �[��� PK ! ����G� G� zI nested.phpnu �[��� PK ! �=� � � �1 relations.phpnu �[��� PK ! �y��c �c O� table.phpnu �[��� PK �
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0.18 |
proxy
|
phpinfo
|
Настройка