Completed
Push — master ( 788440...382d5e )
by Daniel
08:36
created

builder   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 7
Bugs 2 Features 0
Metric Value
wmc 25
c 7
b 2
f 0
lcom 1
cbo 1
dl 0
loc 283
ccs 132
cts 132
cp 1
rs 10

12 Methods

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