1<?php
2/**
3 * Joomla! Content Management System
4 *
5 * @copyright  Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
6 * @license    GNU General Public License version 2 or later; see LICENSE.txt
7 */
8
9namespace Joomla\CMS\Table;
10
11defined('JPATH_PLATFORM') or die;
12
13/**
14 * Table class supporting modified pre-order tree traversal behavior.
15 *
16 * @since  1.7.0
17 */
18class Asset extends Nested
19{
20	/**
21	 * The primary key of the asset.
22	 *
23	 * @var    integer
24	 * @since  1.7.0
25	 */
26	public $id = null;
27
28	/**
29	 * The unique name of the asset.
30	 *
31	 * @var    string
32	 * @since  1.7.0
33	 */
34	public $name = null;
35
36	/**
37	 * The human readable title of the asset.
38	 *
39	 * @var    string
40	 * @since  1.7.0
41	 */
42	public $title = null;
43
44	/**
45	 * The rules for the asset stored in a JSON string
46	 *
47	 * @var    string
48	 * @since  1.7.0
49	 */
50	public $rules = null;
51
52	/**
53	 * Constructor
54	 *
55	 * @param   \JDatabaseDriver  $db  Database driver object.
56	 *
57	 * @since   1.7.0
58	 */
59	public function __construct($db)
60	{
61		parent::__construct('#__assets', 'id', $db);
62	}
63
64	/**
65	 * Method to load an asset by its name.
66	 *
67	 * @param   string  $name  The name of the asset.
68	 *
69	 * @return  integer
70	 *
71	 * @since   1.7.0
72	 */
73	public function loadByName($name)
74	{
75		return $this->load(array('name' => $name));
76	}
77
78	/**
79	 * Assert that the nested set data is valid.
80	 *
81	 * @return  boolean  True if the instance is sane and able to be stored in the database.
82	 *
83	 * @since   1.7.0
84	 */
85	public function check()
86	{
87		$this->parent_id = (int) $this->parent_id;
88
89		if (empty($this->rules))
90		{
91			$this->rules = '{}';
92		}
93
94		// Nested does not allow parent_id = 0, override this.
95		if ($this->parent_id > 0)
96		{
97			// Get the \JDatabaseQuery object
98			$query = $this->_db->getQuery(true)
99				->select('1')
100				->from($this->_db->quoteName($this->_tbl))
101				->where($this->_db->quoteName('id') . ' = ' . $this->parent_id);
102
103			if ($this->_db->setQuery($query, 0, 1)->loadResult())
104			{
105				return true;
106			}
107
108			$this->setError(\JText::_('JLIB_DATABASE_ERROR_INVALID_PARENT_ID'));
109
110			return false;
111		}
112
113		return true;
114	}
115
116	/**
117	 * Method to recursively rebuild the whole nested set tree.
118	 *
119	 * @param   integer  $parentId  The root of the tree to rebuild.
120	 * @param   integer  $leftId    The left id to start with in building the tree.
121	 * @param   integer  $level     The level to assign to the current nodes.
122	 * @param   string   $path      The path to the current nodes.
123	 *
124	 * @return  integer  1 + value of root rgt on success, false on failure
125	 *
126	 * @since   3.5
127	 * @throws  \RuntimeException on database error.
128	 */
129	public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = null)
130	{
131		// If no parent is provided, try to find it.
132		if ($parentId === null)
133		{
134			// Get the root item.
135			$parentId = $this->getRootId();
136
137			if ($parentId === false)
138			{
139				return false;
140			}
141		}
142
143		$query = $this->_db->getQuery(true);
144
145		// Build the structure of the recursive query.
146		if (!isset($this->_cache['rebuild.sql']))
147		{
148			$query->clear()
149				->select($this->_tbl_key)
150				->from($this->_tbl)
151				->where('parent_id = %d');
152
153			// If the table has an ordering field, use that for ordering.
154			if (property_exists($this, 'ordering'))
155			{
156				$query->order('parent_id, ordering, lft');
157			}
158			else
159			{
160				$query->order('parent_id, lft');
161			}
162
163			$this->_cache['rebuild.sql'] = (string) $query;
164		}
165
166		// Make a shortcut to database object.
167
168		// Assemble the query to find all children of this node.
169		$this->_db->setQuery(sprintf($this->_cache['rebuild.sql'], (int) $parentId));
170
171		$children = $this->_db->loadObjectList();
172
173		// The right value of this node is the left value + 1
174		$rightId = $leftId + 1;
175
176		// Execute this function recursively over all children
177		foreach ($children as $node)
178		{
179			/*
180			 * $rightId is the current right value, which is incremented on recursion return.
181			 * Increment the level for the children.
182			 * Add this item's alias to the path (but avoid a leading /)
183			 */
184			$rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId, $level + 1);
185
186			// If there is an update failure, return false to break out of the recursion.
187			if ($rightId === false)
188			{
189				return false;
190			}
191		}
192
193		// We've got the left value, and now that we've processed
194		// the children of this node we also know the right value.
195		$query->clear()
196			->update($this->_tbl)
197			->set('lft = ' . (int) $leftId)
198			->set('rgt = ' . (int) $rightId)
199			->set('level = ' . (int) $level)
200			->where($this->_tbl_key . ' = ' . (int) $parentId);
201		$this->_db->setQuery($query)->execute();
202
203		// Return the right value of this node + 1.
204		return $rightId + 1;
205	}
206}
207