builder::string_to_nestedset()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 18
nc 5
nop 3
dl 0
loc 30
ccs 6
cts 6
cp 1
crap 5
rs 9.3554
c 0
b 0
f 0
1
<?php
2
3
/**
4
 *
5
 * @package sitemaker
6
 * @copyright (c) 2013 Daniel A. (blitze)
7
 * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
8
 *
9
 */
10
11
namespace blitze\sitemaker\services\tree;
12
13
/**
14
 * Manage nested sets
15
 */
16
abstract class builder extends \phpbb\tree\nestedset
17
{
18
	/**
19
	 * Set additional sql where restrictions
20 2
	 * @param string $sql_where
21
	 * @return $this
22 2
	 */
23
	public function set_sql_where($sql_where)
24 2
	{
25 2
		$this->sql_where = $sql_where;
26 2
27 2
		return $this;
28 2
	}
29 2
30 2
	/**
31 2
	 * Get item data
32 2
	 * @param int $item_id
33
	 * @return mixed
34 2
	 */
35 2
	public function get_item_info($item_id = 0)
36 2
	{
37 2
		$sql = 'SELECT * FROM ' . $this->table_name . ' ' . $this->get_sql_where('WHERE') .
38
			(($item_id) ? " AND {$this->column_item_id} = " . (int) $item_id : '');
39 2
40 2
		$result = $this->db->sql_query($sql);
41
		$row = $this->db->sql_fetchrow($result);
42
		$this->db->sql_freeresult($result);
43
44
		return $row;
45
	}
46
47 50
	/**
48
	 * Update a single item
49 50
	 *
50
	 * @param	integer	$item_id		The ID of the item to update.
51 50
	 * @param	array	$sql_data		Other item attributes to insert in the database ex. array('title' => 'Item 1')
52
	 * @return	array
53
	 */
54
	public function update_item($item_id, array $sql_data)
55
	{
56
		$sql = "UPDATE {$this->table_name}
57
			SET " . $this->db->sql_build_array('UPDATE', $sql_data) . "
58
			WHERE $this->column_item_id = " . (int) $item_id;
59 14
		$this->db->sql_query($sql);
60
61 14
		return $sql_data;
62 14
	}
63
64 14
	/**
65 14
	 * Update tree
66 14
	 *
67
	 * @param	array	$tree 	Array of parent-child items
68 14
	 * @return	array
69
	 */
70
	public function update_tree(array $tree)
71
	{
72
		$items_data = $this->get_all_tree_data();
73
74
		/**
75
		 * Remove any new nodes in the tree that did not exist before
76
		 * The intent of this method is to update an existing tree, not add new nodes
77
		 */
78 4
		$tree = array_intersect_key($tree, $items_data);
79
80 4
		// we do it this way because array_merge_recursive, would append numeric keys rather than overwrite them
81 4
		foreach ($tree as $key => $data)
82 4
		{
83 4
			$tree[$key] = array_merge($items_data[$key], $data);
84
		}
85 4
86
		$this->acquire_lock();
87
		$this->db->sql_transaction('begin');
88
89
		// Rather than updating each item individually, we will just delete all items
90
		// then add them all over again with new parent_id|left_id|right_id
91
		$this->db->sql_query("DELETE FROM {$this->table_name} " . $this->get_sql_where('WHERE'));
92
93
		// Now we add it back
94 3
		$sql_data = $this->add_sub_tree($tree, 0);
95
96 3
		$this->db->sql_transaction('commit');
97
		$this->lock->release();
98
99
		return $sql_data;
100
	}
101
102 3
	/**
103
	 * Takes a new line delimited string and returns an associative array of parent/child relationships
104
	 * that can be saved to a database or converted to a nested set model
105 3
	 *
106
	 * @param	string	$structure		The structure to build ex:
107 3
	 *										Home|index.php
108 3
	 *										News|index.php?p=news
109
	 *											Texas|index.php?p=news&cat=Texas
110 3
	 *										About Us|index.php?p=about
111 3
	 * @param	array	$table_fields	The expected information to get for each line (order is important) ex: array('title' => '', 'url' => '')
112
	 *									This will then assign 'Home' to 'title' and 'index.php' to 'url' in example above (line 1)
113
	 * @param array     $data
114
	 * @return	array					associative array of parent/child relationships ex: the above examples will produce
115 3
	 *										array(
116
	 *											1   => array('title' => 'Home', 'url' => 'index.php', parent_id => 0),
117
	 *											2   => array('title' => 'News', 'url' => 'index.php?p=news', parent_id => 0),
118 3
	 *											3   => array('title' => 'Texas', 'url' => 'index.php?p=news&cat=Texas', parent_id => 2),
119
	 *											4   => array('title' => 'About Us', 'url' => 'index.php?p=about', parent_id => 0),
120 3
	 *										)
121 3
	 */
122
	public function string_to_nestedset($structure, array $table_fields, $data = array())
123 3
	{
124
		$field_size = sizeof($table_fields);
125
		$fields = array_keys($table_fields);
126
		$values = array_fill(0, $field_size, '');
127
		$lines = array_filter(explode("\n", $structure));
128
		$max_id = $this->get_max_id($this->column_item_id, false);
129
130
		$adj_tree = $parent_ary = array();
131
		foreach ($lines as $i => $string)
132
		{
133
			$depth = strspn($string, "\t");
134
			$parent_id = (isset($parent_ary[$depth - 1])) ? $parent_ary[$depth - 1] : 0;
135
136
			if ($depth && !$parent_id)
137
			{
138
				throw new \RuntimeException($this->message_prefix . 'MALFORMED_TREE');
139
			}
140
141
			$key = $i + $max_id + 1;
142
			$field_values = array_map('trim', explode('|', trim($string))) + $values;
143
144
			$adj_tree[$key] = array_merge($data, (array) array_combine($fields, $field_values));
145
			$adj_tree[$key][$this->column_item_id] = $key;
146 7
			$adj_tree[$key]['parent_id'] = $parent_id;
147
148 7
			$parent_ary[$depth] = $key;
149 7
		}
150 7
151 7
		return $adj_tree;
152 7
	}
153
154 7
	/**
155 7
	 * Adds a new branch to the tree from an array
156
	 *
157 7
	 * @param	array	$branch		Array of nodes to add to tree of form:
158 7
	 * 									array(
159
	 * 										1   => array('title' => 'Home', 'url' => 'index.php', parent_id => 0),
160 7
	 * 										2   => array('title' => 'News', 'url' => 'index.php?p=news', parent_id => 0),
161 7
	 * 										3   => array('title' => 'Texas', 'url' => 'index.php?p=news&cat=Texas', parent_id => 2),
162 1
	 * 										4   => array('title' => 'About Us', 'url' => 'index.php?p=about', parent_id => 0),
163
	 * 									);
164
	 * @param	int		$parent_id  Parent id of the branch we're adding
165 6
	 * @return	int[]	ids of newly added items
166 6
	 */
167
	public function add_branch(array $branch, $parent_id = 0)
168 6
	{
169 6
		$this->acquire_lock();
170 6
		$this->db->sql_transaction('begin');
171
172 6
		$this->add_sub_tree($branch, $parent_id);
173 6
174
		$this->db->sql_transaction('commit');
175 6
		$this->lock->release();
176
177
		return array_keys($branch);
178
	}
179
180
	/**
181
	 * @param array $branch
182
	 * @param int   $parent_id
183
	 * @return mixed
184
	 */
185
	protected function add_sub_tree(array $branch, $parent_id = 0)
186
	{
187
		$sql_data = $this->prepare_branch($parent_id, $branch);
188
189
		$this->db->sql_multi_insert($this->table_name, $sql_data);
190
191 6
		return $sql_data;
192
	}
193 6
194 6
	/**
195
	 * @param int   $parent_id
196 6
	 * @param array $branch
197
	 * @return array
198 5
	 */
199 5
	protected function prepare_branch($parent_id, array $branch)
200
	{
201 5
		$starting_data = $this->get_starting_data($parent_id, $branch);
202
203
		$depth = $starting_data['depth'];
204
		$right_id = $starting_data['right_id'];
205
206
		$sql_data = array();
207
		foreach ($branch as $i => $row)
208
		{
209 9
			$left_id	= $right_id + 1;
210
			$right_id   = $right_id + 2;
211 9
212
			$sql_data[$i] = $row;
213 8
			$sql_data[$i]['parent_id']	= $parent_id;
214
			$sql_data[$i]['left_id']	= $left_id;
215 8
			$sql_data[$i]['right_id']	= $right_id;
216
			$sql_data[$i]['depth']		= $depth;
217
218
			if ($row['parent_id'])
219
			{
220
				$left_id	= $sql_data[$row['parent_id']]['right_id'];
221
				$right_id   = $left_id + 1;
222
223 9
				$sql_data[$i]['parent_id']	= $row['parent_id'];
224
				$sql_data[$i]['depth']		= $sql_data[$row['parent_id']]['depth'] + 1;
225 9
				$sql_data[$i]['left_id']	= $left_id;
226
				$sql_data[$i]['right_id']	= $right_id;
227 8
228 8
				$this->update_right_side($sql_data, $right_id, $row['parent_id'], $branch);
229
			}
230 8
		}
231 8
232
		return array_values($sql_data);
233 8
	}
234 8
235
	/**
236 8
	 * @param int   $parent_id
237 8
	 * @param array $branch
238 8
	 * @return array
239 8
	 * @throws \RuntimeException
240 8
	 */
241
	protected function get_starting_data($parent_id, array $branch)
242 8
	{
243 8
		if ($parent_id)
244 7
		{
245 7
			$new_parent = $this->get_item_info($parent_id);
246
247 7
			if (!$new_parent)
248 7
			{
249 7
				$this->db->sql_transaction('rollback');
250 7
				$this->lock->release();
251
252 7
				throw new \RuntimeException($this->message_prefix . 'INVALID_PARENT');
253 7
			}
254 8
255
			// adjust items in affected branch
256 8
			$this->prepare_adding_subset(array_keys($branch), $new_parent);
257
258
			return array(
259
				'depth'		=> $new_parent['depth'] + 1,
260
				'right_id'	=> --$new_parent['right_id'],
261
			);
262
		}
263
		else
264
		{
265 9
			return array(
266
				'depth'		=> 0,
267
				'right_id'	=> $this->get_max_id($this->column_right_id),
268 9
			);
269 3
		}
270
	}
271 3
272 3
	/**
273 1
	 * @param string $column
274 1
	 * @param bool   $use_sql_where
275
	 * @return int|mixed
276 1
	 */
277
	protected function get_max_id($column, $use_sql_where = true)
278
	{
279
		$sql = "SELECT MAX($column) AS $column
280 2
			FROM {$this->table_name} " .
281
			(($use_sql_where) ? $this->get_sql_where('WHERE') : '');
282
		$result = $this->db->sql_query($sql);
283 2
		$max_id = $this->db->sql_fetchfield($column);
284 2
		$this->db->sql_freeresult($result);
285 2
286
		return ($max_id) ? $max_id : 0;
287
	}
288
289
	/**
290 6
	 * Update right side of tree
291 6
	 * @param array $data
292 6
	 * @param int   $right_id
293
	 * @param int   $index
294
	 * @param array $branch
295
	 */
296
	protected function update_right_side(array &$data, &$right_id, $index, array $branch)
297
	{
298
		$right_id++;
299
		$data[$index]['right_id'] = $right_id;
300
301 10
		if ($branch[$index]['parent_id'])
302
		{
303 10
			$this->update_right_side($data, $right_id, $branch[$index]['parent_id'], $branch);
304 10
		}
305 10
	}
306
}
307