| Total Complexity | 60 | 
| Total Lines | 708 | 
| Duplicated Lines | 0 % | 
| Changes | 0 | ||
Complex classes like DBNestedSet often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use DBNestedSet, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 21 | class DBNestedSet extends \Aimeos\MW\Tree\Manager\Base  | 
            ||
| 22 | { | 
            ||
| 23 | private $searchConfig = [];  | 
            ||
| 24 | private $config;  | 
            ||
| 25 | private $conn;  | 
            ||
| 26 | |||
| 27 | |||
| 28 | /**  | 
            ||
| 29 | * Initializes the tree manager.  | 
            ||
| 30 | *  | 
            ||
| 31 | * The config['search] array must contain these key/array pairs suitable for \Aimeos\Base\Criteria\Attribute\Standard:  | 
            ||
| 32 | * [id] => Array describing unique ID codes/types/labels  | 
            ||
| 33 | * [label] => Array describing codes/types/labels for descriptive labels  | 
            ||
| 34 | * [status] => Array describing codes/types/labels for status values  | 
            ||
| 35 | * [parentid] => Array describing codes/types/labels for parentid values  | 
            ||
| 36 | * [level] => Array describing codes/types/labels for height levels of tree nodes  | 
            ||
| 37 | * [left] => Array describing codes/types/labels for nodes left values  | 
            ||
| 38 | * [right] => Array describing codes/types/labels for nodes right values  | 
            ||
| 39 | *  | 
            ||
| 40 | * The config['sql] array must contain these statement:  | 
            ||
| 41 | * [delete] =>  | 
            ||
| 42 | * DELETE FROM treetable WHERE left >= ? AND right <= ?  | 
            ||
| 43 | * [get] =>  | 
            ||
| 44 | * SELECT node.*  | 
            ||
| 45 | * FROM treetable AS parent, treetable AS node  | 
            ||
| 46 | * WHERE node.left >= parent.left AND node.left <= parent.right  | 
            ||
| 47 | * AND parent.id = ? AND node.level <= parent.level + ? AND :cond  | 
            ||
| 48 | * ORDER BY node.left  | 
            ||
| 49 | * [insert] =>  | 
            ||
| 50 | * INSERT INTO treetable ( label, code, status, parentid, level, left, right ) VALUES ( ?, ?, ?, ?, ? )  | 
            ||
| 51 | * [move-left] =>  | 
            ||
| 52 | * UPDATE treetable  | 
            ||
| 53 | * SET left = left + ?, level = level + ?  | 
            ||
| 54 | * WHERE left >= ? AND left <= ?  | 
            ||
| 55 | * [move-right] =>  | 
            ||
| 56 | * UPDATE treetable  | 
            ||
| 57 | * SET right = right + ?  | 
            ||
| 58 | * WHERE right >= ? AND right <= ?  | 
            ||
| 59 | * [search] =>  | 
            ||
| 60 | * SELECT * FROM treetable  | 
            ||
| 61 | * WHERE left >= ? AND right <= ? AND :cond  | 
            ||
| 62 | * ORDER BY :order  | 
            ||
| 63 | * [update] =>  | 
            ||
| 64 | * UPDATE treetable SET label = ?, code = ?, status = ? WHERE id = ?  | 
            ||
| 65 | * [update-parentid] =>  | 
            ||
| 66 | * UPDATE treetable SET parentid = ? WHERE id = ?  | 
            ||
| 67 | * [newid] =>  | 
            ||
| 68 | * SELECT LAST_INSERT_ID()  | 
            ||
| 69 | *  | 
            ||
| 70 | * @param array $config Associative array holding the SQL statements  | 
            ||
| 71 | * @param \Aimeos\Base\DB\Connection\Iface $resource Database connection  | 
            ||
| 72 | */  | 
            ||
| 73 | public function __construct( array $config, $resource )  | 
            ||
| 74 | 	{ | 
            ||
| 75 | 		if( !( $resource instanceof \Aimeos\Base\DB\Connection\Iface ) ) { | 
            ||
| 
                                                                                                    
                        
                         | 
                |||
| 76 | throw new \Aimeos\MW\Tree\Exception( 'Given resource isn\'t a database connection object' );  | 
            ||
| 77 | }  | 
            ||
| 78 | |||
| 79 | 		if( !isset( $config['search'] ) ) { | 
            ||
| 80 | throw new \Aimeos\MW\Tree\Exception( 'Search config is missing' );  | 
            ||
| 81 | }  | 
            ||
| 82 | |||
| 83 | 		if( !isset( $config['sql'] ) ) { | 
            ||
| 84 | throw new \Aimeos\MW\Tree\Exception( 'SQL config is missing' );  | 
            ||
| 85 | }  | 
            ||
| 86 | |||
| 87 | $this->checkSearchConfig( $config['search'] );  | 
            ||
| 88 | $this->checkSqlConfig( $config['sql'] );  | 
            ||
| 89 | |||
| 90 | $this->searchConfig = $config['search'];  | 
            ||
| 91 | $this->config = $config['sql'];  | 
            ||
| 92 | $this->conn = $resource;  | 
            ||
| 93 | }  | 
            ||
| 94 | |||
| 95 | |||
| 96 | /**  | 
            ||
| 97 | * Returns a list of attributes which can be used in the search method.  | 
            ||
| 98 | *  | 
            ||
| 99 | * @return \Aimeos\Base\Criteria\Attribute\Iface[] List of search attribute items  | 
            ||
| 100 | */  | 
            ||
| 101 | public function getSearchAttributes() : array  | 
            ||
| 102 | 	{ | 
            ||
| 103 | $attributes = [];  | 
            ||
| 104 | |||
| 105 | 		foreach( $this->searchConfig as $values ) { | 
            ||
| 106 | $attributes[] = new \Aimeos\Base\Criteria\Attribute\Standard( $values );  | 
            ||
| 107 | }  | 
            ||
| 108 | |||
| 109 | return $attributes;  | 
            ||
| 110 | }  | 
            ||
| 111 | |||
| 112 | |||
| 113 | /**  | 
            ||
| 114 | * Creates a new search object for storing search criterias.  | 
            ||
| 115 | *  | 
            ||
| 116 | * @return \Aimeos\Base\Criteria\Iface Search object instance  | 
            ||
| 117 | */  | 
            ||
| 118 | public function createSearch() : \Aimeos\Base\Criteria\Iface  | 
            ||
| 119 | 	{ | 
            ||
| 120 | return new \Aimeos\Base\Criteria\SQL( $this->conn );  | 
            ||
| 121 | }  | 
            ||
| 122 | |||
| 123 | |||
| 124 | /**  | 
            ||
| 125 | * Creates a new node object.  | 
            ||
| 126 | *  | 
            ||
| 127 | * @return \Aimeos\MW\Tree\Node\Iface Empty node object  | 
            ||
| 128 | */  | 
            ||
| 129 | public function createNode() : \Aimeos\MW\Tree\Node\Iface  | 
            ||
| 130 | 	{ | 
            ||
| 131 | return $this->createNodeBase();  | 
            ||
| 132 | }  | 
            ||
| 133 | |||
| 134 | |||
| 135 | /**  | 
            ||
| 136 | * Deletes a node and its descendants from the storage.  | 
            ||
| 137 | *  | 
            ||
| 138 | * @param string|null $id Delete the node with the ID and all nodes below  | 
            ||
| 139 | * @return \Aimeos\MW\Tree\Manager\Iface Manager object for method chaining  | 
            ||
| 140 | */  | 
            ||
| 141 | public function deleteNode( string $id = null ) : Iface  | 
            ||
| 142 | 	{ | 
            ||
| 143 | $node = $this->getNode( $id, \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );  | 
            ||
| 144 | |||
| 145 | $stmt = $this->conn->create( $this->config['delete'] );  | 
            ||
| 146 | $stmt->bind( 1, $node->left, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 147 | $stmt->bind( 2, $node->right, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 148 | $stmt->execute()->finish();  | 
            ||
| 149 | |||
| 150 | $diff = $node->right - $node->left + 1;  | 
            ||
| 151 | |||
| 152 | $stmt = $this->conn->create( $this->config['move-left'] );  | 
            ||
| 153 | $stmt->bind( 1, -$diff, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 154 | $stmt->bind( 2, 0, $this->searchConfig['level']['internaltype'] );  | 
            ||
| 155 | $stmt->bind( 3, $node->right + 1, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 156 | $stmt->bind( 4, 0x7FFFFFFF, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 157 | $stmt->execute()->finish();  | 
            ||
| 158 | |||
| 159 | $stmt = $this->conn->create( $this->config['move-right'] );  | 
            ||
| 160 | $stmt->bind( 1, -$diff, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 161 | $stmt->bind( 2, $node->right + 1, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 162 | $stmt->bind( 3, 0x7FFFFFFF, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 163 | $stmt->execute()->finish();  | 
            ||
| 164 | |||
| 165 | return $this;  | 
            ||
| 166 | }  | 
            ||
| 167 | |||
| 168 | |||
| 169 | /**  | 
            ||
| 170 | * Returns a node and its descendants depending on the given resource.  | 
            ||
| 171 | *  | 
            ||
| 172 | * @param string|null $id Retrieve nodes starting from the given ID  | 
            ||
| 173 | * @param int $level One of the level constants from \Aimeos\MW\Tree\Manager\Base  | 
            ||
| 174 | * @param \Aimeos\Base\Criteria\Iface|null $condition Optional criteria object with conditions  | 
            ||
| 175 | * @return \Aimeos\MW\Tree\Node\Iface Node, maybe with subnodes  | 
            ||
| 176 | */  | 
            ||
| 177 | public function getNode( string $id = null, int $level = Base::LEVEL_TREE, \Aimeos\Base\Criteria\Iface $condition = null ) : \Aimeos\MW\Tree\Node\Iface  | 
            ||
| 178 | 	{ | 
            ||
| 179 | if( $id === null )  | 
            ||
| 180 | 		{ | 
            ||
| 181 | 			if( ( $node = $this->getRootNode() ) === null ) { | 
            ||
| 182 | throw new \Aimeos\MW\Tree\Exception( 'No root node available' );  | 
            ||
| 183 | }  | 
            ||
| 184 | |||
| 185 | 			if( $level === \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE ) { | 
            ||
| 186 | return $node;  | 
            ||
| 187 | }  | 
            ||
| 188 | }  | 
            ||
| 189 | else  | 
            ||
| 190 | 		{ | 
            ||
| 191 | $node = $this->getNodeById( $id );  | 
            ||
| 192 | |||
| 193 | 			if( $level === \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE ) { | 
            ||
| 194 | return $node;  | 
            ||
| 195 | }  | 
            ||
| 196 | }  | 
            ||
| 197 | |||
| 198 | |||
| 199 | $id = $node->getId();  | 
            ||
| 200 | |||
| 201 | $numlevel = $this->getLevelFromConstant( $level );  | 
            ||
| 202 | $search = $condition ?: $this->createSearch();  | 
            ||
| 203 | |||
| 204 | $types = $this->getSearchTypes( $this->searchConfig );  | 
            ||
| 205 | $funcs = $this->getSearchFunctions( $this->searchConfig );  | 
            ||
| 206 | $translations = $this->getSearchTranslations( $this->searchConfig );  | 
            ||
| 207 | $conditions = $search->getConditionSource( $types, $translations, [], $funcs );  | 
            ||
| 208 | |||
| 209 | |||
| 210 | $stmt = $this->conn->create( str_replace( ':cond', $conditions, $this->config['get'] ) );  | 
            ||
| 211 | $stmt->bind( 1, $id, $this->searchConfig['parentid']['internaltype'] );  | 
            ||
| 212 | $stmt->bind( 2, $numlevel, $this->searchConfig['level']['internaltype'] );  | 
            ||
| 213 | $result = $stmt->execute();  | 
            ||
| 214 | |||
| 215 | 		if( ( $row = $result->fetch() ) === null ) { | 
            ||
| 216 | throw new \Aimeos\MW\Tree\Exception( sprintf( 'No node with ID "%1$d" found', $id ) );  | 
            ||
| 217 | }  | 
            ||
| 218 | |||
| 219 | $node = $this->createNodeBase( $row );  | 
            ||
| 220 | $this->createTree( $result, $node );  | 
            ||
| 221 | |||
| 222 | return $node;  | 
            ||
| 223 | }  | 
            ||
| 224 | |||
| 225 | |||
| 226 | /**  | 
            ||
| 227 | * Inserts a new node before the given reference node to the parent in the storage.  | 
            ||
| 228 | *  | 
            ||
| 229 | * @param \Aimeos\MW\Tree\Node\Iface $node New node that should be inserted  | 
            ||
| 230 | * @param string|null $parentId ID of the parent node where the new node should be inserted below (null for root node)  | 
            ||
| 231 | * @param string|null $refId ID of the node where the node should be inserted before (null to append)  | 
            ||
| 232 | * @return \Aimeos\MW\Tree\Node\Iface Updated node item  | 
            ||
| 233 | */  | 
            ||
| 234 | public function insertNode( \Aimeos\MW\Tree\Node\Iface $node, string $parentId = null, string $refId = null ) : \Aimeos\MW\Tree\Node\Iface  | 
            ||
| 235 | 	{ | 
            ||
| 236 | $node->parentid = $parentId;  | 
            ||
| 237 | |||
| 238 | if( $refId !== null )  | 
            ||
| 239 | 		{ | 
            ||
| 240 | $refNode = $this->getNode( $refId, \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );  | 
            ||
| 241 | $node->left = $refNode->left;  | 
            ||
| 242 | $node->right = $refNode->left + 1;  | 
            ||
| 243 | $node->level = $refNode->level;  | 
            ||
| 244 | }  | 
            ||
| 245 | else if( $parentId !== null )  | 
            ||
| 246 | 		{ | 
            ||
| 247 | $parentNode = $this->getNode( $parentId, \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );  | 
            ||
| 248 | $node->left = $parentNode->right;  | 
            ||
| 249 | $node->right = $parentNode->right + 1;  | 
            ||
| 250 | $node->level = $parentNode->level + 1;  | 
            ||
| 251 | }  | 
            ||
| 252 | else  | 
            ||
| 253 | 		{ | 
            ||
| 254 | $node->left = 1;  | 
            ||
| 255 | $node->right = 2;  | 
            ||
| 256 | $node->level = 0;  | 
            ||
| 257 | $node->parentid = 0;  | 
            ||
| 258 | |||
| 259 | if( ( $root = $this->getRootNode( '-' ) ) !== null )  | 
            ||
| 260 | 			{ | 
            ||
| 261 | $node->left = $root->right + 1;  | 
            ||
| 262 | $node->right = $root->right + 2;  | 
            ||
| 263 | }  | 
            ||
| 264 | }  | 
            ||
| 265 | |||
| 266 | |||
| 267 | $stmt = $this->conn->create( $this->config['move-left'] );  | 
            ||
| 268 | $stmt->bind( 1, 2, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 269 | $stmt->bind( 2, 0, $this->searchConfig['level']['internaltype'] );  | 
            ||
| 270 | $stmt->bind( 3, $node->left, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 271 | $stmt->bind( 4, 0x7FFFFFFF, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 272 | $stmt->execute()->finish();  | 
            ||
| 273 | |||
| 274 | $stmt = $this->conn->create( $this->config['move-right'] );  | 
            ||
| 275 | $stmt->bind( 1, 2, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 276 | $stmt->bind( 2, $node->left, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 277 | $stmt->bind( 3, 0x7FFFFFFF, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 278 | $stmt->execute()->finish();  | 
            ||
| 279 | |||
| 280 | $stmt = $this->conn->create( $this->config['insert'] );  | 
            ||
| 281 | $stmt->bind( 1, $node->getLabel(), $this->searchConfig['label']['internaltype'] );  | 
            ||
| 282 | $stmt->bind( 2, $node->getCode(), $this->searchConfig['code']['internaltype'] );  | 
            ||
| 283 | $stmt->bind( 3, $node->getStatus(), $this->searchConfig['status']['internaltype'] );  | 
            ||
| 284 | $stmt->bind( 4, (int) $node->parentid, $this->searchConfig['parentid']['internaltype'] );  | 
            ||
| 285 | $stmt->bind( 5, $node->level, $this->searchConfig['level']['internaltype'] );  | 
            ||
| 286 | $stmt->bind( 6, $node->left, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 287 | $stmt->bind( 7, $node->right, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 288 | $stmt->execute()->finish();  | 
            ||
| 289 | |||
| 290 | |||
| 291 | $result = $this->conn->create( $this->config['newid'] )->execute();  | 
            ||
| 292 | |||
| 293 | 		if( ( $row = $result->fetch( \Aimeos\Base\DB\Result\Base::FETCH_NUM ) ) === false ) { | 
            ||
| 294 | throw new \Aimeos\MW\Tree\Exception( sprintf( 'No new record ID available' ) );  | 
            ||
| 295 | }  | 
            ||
| 296 | $result->finish();  | 
            ||
| 297 | |||
| 298 | $node->setId( $row[0] );  | 
            ||
| 299 | |||
| 300 | return $node;  | 
            ||
| 301 | }  | 
            ||
| 302 | |||
| 303 | |||
| 304 | /**  | 
            ||
| 305 | * Moves an existing node to the new parent in the storage.  | 
            ||
| 306 | *  | 
            ||
| 307 | * @param string $id ID of the node that should be moved  | 
            ||
| 308 | * @param string|null $oldParentId ID of the old parent node which currently contains the node that should be removed  | 
            ||
| 309 | * @param string|null $newParentId ID of the new parent node where the node should be moved to  | 
            ||
| 310 | * @param string|null $newRefId ID of the node where the node should be inserted before (null to append)  | 
            ||
| 311 | * @return \Aimeos\MW\Tree\Manager\Iface Manager object for method chaining  | 
            ||
| 312 | */  | 
            ||
| 313 | public function moveNode( string $id, string $oldParentId = null, string $newParentId = null, string $newRefId = null ) : Iface  | 
            ||
| 314 | 	{ | 
            ||
| 315 | $node = $this->getNode( $id, \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );  | 
            ||
| 316 | $diff = $node->right - $node->left + 1;  | 
            ||
| 317 | |||
| 318 | if( $newRefId !== null )  | 
            ||
| 319 | 		{ | 
            ||
| 320 | $refNode = $this->getNode( $newRefId, \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );  | 
            ||
| 321 | |||
| 322 | $leveldiff = $refNode->level - $node->level;  | 
            ||
| 323 | |||
| 324 | $openNodeLeftBegin = $refNode->left;  | 
            ||
| 325 | $openNodeRightBegin = $refNode->left + 1;  | 
            ||
| 326 | |||
| 327 | if( $refNode->left < $node->left )  | 
            ||
| 328 | 			{ | 
            ||
| 329 | $moveNodeLeftBegin = $node->left + $diff;  | 
            ||
| 330 | $moveNodeLeftEnd = $node->right + $diff - 1;  | 
            ||
| 331 | $moveNodeRightBegin = $node->left + $diff + 1;  | 
            ||
| 332 | $moveNodeRightEnd = $node->right + $diff;  | 
            ||
| 333 | $movesize = $refNode->left - $node->left - $diff;  | 
            ||
| 334 | }  | 
            ||
| 335 | else  | 
            ||
| 336 | 			{ | 
            ||
| 337 | $moveNodeLeftBegin = $node->left;  | 
            ||
| 338 | $moveNodeLeftEnd = $node->right - 1;  | 
            ||
| 339 | $moveNodeRightBegin = $node->left + 1;  | 
            ||
| 340 | $moveNodeRightEnd = $node->right;  | 
            ||
| 341 | $movesize = $refNode->left - $node->left;  | 
            ||
| 342 | }  | 
            ||
| 343 | |||
| 344 | $closeNodeLeftBegin = $node->left + $diff;  | 
            ||
| 345 | $closeNodeRightBegin = $node->left + $diff;  | 
            ||
| 346 | }  | 
            ||
| 347 | else  | 
            ||
| 348 | 		{ | 
            ||
| 349 | $refNode = $this->getNode( $newParentId, \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );  | 
            ||
| 350 | |||
| 351 | if( $newParentId === null )  | 
            ||
| 352 | 			{ | 
            ||
| 353 | //make virtual root  | 
            ||
| 354 | if( ( $root = $this->getRootNode( '-' ) ) !== null )  | 
            ||
| 355 | 				{ | 
            ||
| 356 | $refNode->left = $root->right;  | 
            ||
| 357 | $refNode->right = $root->right + 1;  | 
            ||
| 358 | $refNode->level = -1;  | 
            ||
| 359 | }  | 
            ||
| 360 | }  | 
            ||
| 361 | |||
| 362 | $leveldiff = $refNode->level - $node->level + 1;  | 
            ||
| 363 | $openNodeLeftBegin = $refNode->right + 1;  | 
            ||
| 364 | $openNodeRightBegin = $refNode->right;  | 
            ||
| 365 | |||
| 366 | if( $refNode->right < $node->right )  | 
            ||
| 367 | 			{ | 
            ||
| 368 | $moveNodeLeftBegin = $node->left + $diff;  | 
            ||
| 369 | $moveNodeLeftEnd = $node->right + $diff - 1;  | 
            ||
| 370 | $moveNodeRightBegin = $node->left + $diff + 1;  | 
            ||
| 371 | $moveNodeRightEnd = $node->right + $diff;  | 
            ||
| 372 | $movesize = $refNode->right - $node->left - $diff;  | 
            ||
| 373 | }  | 
            ||
| 374 | else  | 
            ||
| 375 | 			{ | 
            ||
| 376 | $moveNodeLeftBegin = $node->left;  | 
            ||
| 377 | $moveNodeLeftEnd = $node->right - 1;  | 
            ||
| 378 | $moveNodeRightBegin = $node->left + 1;  | 
            ||
| 379 | $moveNodeRightEnd = $node->right;  | 
            ||
| 380 | $movesize = $refNode->right - $node->left;  | 
            ||
| 381 | }  | 
            ||
| 382 | |||
| 383 | $closeNodeLeftBegin = $node->left + $diff;  | 
            ||
| 384 | $closeNodeRightBegin = $node->left + $diff;  | 
            ||
| 385 | }  | 
            ||
| 386 | |||
| 387 | |||
| 388 | $stmtLeft = $this->conn->create( $this->config['move-left'] );  | 
            ||
| 389 | $stmtRight = $this->conn->create( $this->config['move-right'] );  | 
            ||
| 390 | $updateParentId = $this->conn->create( $this->config['update-parentid'] );  | 
            ||
| 391 | // open gap for inserting node or subtree  | 
            ||
| 392 | |||
| 393 | $stmtLeft->bind( 1, $diff, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 394 | $stmtLeft->bind( 2, 0, $this->searchConfig['level']['internaltype'] );  | 
            ||
| 395 | $stmtLeft->bind( 3, $openNodeLeftBegin, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 396 | $stmtLeft->bind( 4, 0x7FFFFFFF, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 397 | $stmtLeft->execute()->finish();  | 
            ||
| 398 | |||
| 399 | $stmtRight->bind( 1, $diff, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 400 | $stmtRight->bind( 2, $openNodeRightBegin, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 401 | $stmtRight->bind( 3, 0x7FFFFFFF, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 402 | $stmtRight->execute()->finish();  | 
            ||
| 403 | |||
| 404 | // move node or subtree to the new gap  | 
            ||
| 405 | |||
| 406 | $stmtLeft->bind( 1, $movesize, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 407 | $stmtLeft->bind( 2, $leveldiff, $this->searchConfig['level']['internaltype'] );  | 
            ||
| 408 | $stmtLeft->bind( 3, $moveNodeLeftBegin, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 409 | $stmtLeft->bind( 4, $moveNodeLeftEnd, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 410 | $stmtLeft->execute()->finish();  | 
            ||
| 411 | |||
| 412 | $stmtRight->bind( 1, $movesize, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 413 | $stmtRight->bind( 2, $moveNodeRightBegin, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 414 | $stmtRight->bind( 3, $moveNodeRightEnd, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 415 | $stmtRight->execute()->finish();  | 
            ||
| 416 | |||
| 417 | // close gap opened by moving the node or subtree to the new location  | 
            ||
| 418 | |||
| 419 | $stmtLeft->bind( 1, -$diff, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 420 | $stmtLeft->bind( 2, 0, $this->searchConfig['level']['internaltype'] );  | 
            ||
| 421 | $stmtLeft->bind( 3, $closeNodeLeftBegin, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 422 | $stmtLeft->bind( 4, 0x7FFFFFFF, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 423 | $stmtLeft->execute()->finish();  | 
            ||
| 424 | |||
| 425 | $stmtRight->bind( 1, -$diff, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 426 | $stmtRight->bind( 2, $closeNodeRightBegin, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 427 | $stmtRight->bind( 3, 0x7FFFFFFF, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 428 | $stmtRight->execute()->finish();  | 
            ||
| 429 | |||
| 430 | |||
| 431 | $updateParentId->bind( 1, $newParentId, $this->searchConfig['parentid']['internaltype'] );  | 
            ||
| 432 | $updateParentId->bind( 2, $id, $this->searchConfig['id']['internaltype'] );  | 
            ||
| 433 | $updateParentId->execute()->finish();  | 
            ||
| 434 | |||
| 435 | return $this;  | 
            ||
| 436 | }  | 
            ||
| 437 | |||
| 438 | |||
| 439 | /**  | 
            ||
| 440 | * Stores the values of the given node to the storage.  | 
            ||
| 441 | *  | 
            ||
| 442 | * This method does only store values like the node label but doesn't change  | 
            ||
| 443 | * the tree layout by adding, moving or deleting nodes.  | 
            ||
| 444 | *  | 
            ||
| 445 | * @param \Aimeos\MW\Tree\Node\Iface $node Tree node object  | 
            ||
| 446 | * @return \Aimeos\MW\Tree\Node\Iface Updated node item  | 
            ||
| 447 | */  | 
            ||
| 448 | public function saveNode( \Aimeos\MW\Tree\Node\Iface $node ) : \Aimeos\MW\Tree\Node\Iface  | 
            ||
| 449 | 	{ | 
            ||
| 450 | 		if( $node->getId() === null ) { | 
            ||
| 451 | throw new \Aimeos\MW\Tree\Exception( sprintf( 'Unable to save newly created nodes, use insert method instead' ) );  | 
            ||
| 452 | }  | 
            ||
| 453 | |||
| 454 | 		if( $node->isModified() === false ) { | 
            ||
| 455 | return $node;  | 
            ||
| 456 | }  | 
            ||
| 457 | |||
| 458 | $stmt = $this->conn->create( $this->config['update'] );  | 
            ||
| 459 | $stmt->bind( 1, $node->getLabel(), $this->searchConfig['label']['internaltype'] );  | 
            ||
| 460 | $stmt->bind( 2, $node->getCode(), $this->searchConfig['code']['internaltype'] );  | 
            ||
| 461 | $stmt->bind( 3, $node->getStatus(), $this->searchConfig['status']['internaltype'] );  | 
            ||
| 462 | $stmt->bind( 4, $node->getId(), $this->searchConfig['id']['internaltype'] );  | 
            ||
| 463 | $stmt->execute()->finish();  | 
            ||
| 464 | |||
| 465 | return $node;  | 
            ||
| 466 | }  | 
            ||
| 467 | |||
| 468 | |||
| 469 | /**  | 
            ||
| 470 | * Retrieves a list of nodes from the storage matching the given search criteria.  | 
            ||
| 471 | *  | 
            ||
| 472 | * @param \Aimeos\Base\Criteria\Iface $search Search criteria object  | 
            ||
| 473 | * @param string|null $id Search nodes starting at the node with the given ID  | 
            ||
| 474 | * @return \Aimeos\MW\Tree\Node\Iface[] List of tree nodes  | 
            ||
| 475 | */  | 
            ||
| 476 | public function searchNodes( \Aimeos\Base\Criteria\Iface $search, string $id = null ) : array  | 
            ||
| 477 | 	{ | 
            ||
| 478 | $left = 1;  | 
            ||
| 479 | $right = 0x7FFFFFFF;  | 
            ||
| 480 | |||
| 481 | if( $id !== null )  | 
            ||
| 482 | 		{ | 
            ||
| 483 | $node = $this->getNodeById( $id );  | 
            ||
| 484 | |||
| 485 | $left = $node->left;  | 
            ||
| 486 | $right = $node->right;  | 
            ||
| 487 | }  | 
            ||
| 488 | |||
| 489 | 		if( $search->getSortations() === [] ) { | 
            ||
| 490 | $search->setSortations( [$search->sort( '+', $this->searchConfig['left']['code'] )] );  | 
            ||
| 491 | }  | 
            ||
| 492 | |||
| 493 | $types = $this->getSearchTypes( $this->searchConfig );  | 
            ||
| 494 | $funcs = $this->getSearchFunctions( $this->searchConfig );  | 
            ||
| 495 | $translations = $this->getSearchTranslations( $this->searchConfig );  | 
            ||
| 496 | $conditions = $search->getConditionSource( $types, $translations, [], $funcs );  | 
            ||
| 497 | $sortations = $search->getSortationSource( $types, $translations, $funcs );  | 
            ||
| 498 | |||
| 499 | $sql = str_replace(  | 
            ||
| 500 | [':cond', ':order', ':size', ':start'],  | 
            ||
| 501 | [$conditions, $sortations, $search->getLimit(), $search->getOffset()],  | 
            ||
| 502 | $this->config['search']  | 
            ||
| 503 | );  | 
            ||
| 504 | |||
| 505 | $stmt = $this->conn->create( $sql );  | 
            ||
| 506 | $stmt->bind( 1, $left, $this->searchConfig['left']['internaltype'] );  | 
            ||
| 507 | $stmt->bind( 2, $right, $this->searchConfig['right']['internaltype'] );  | 
            ||
| 508 | $result = $stmt->execute();  | 
            ||
| 509 | |||
| 510 | try  | 
            ||
| 511 | 		{ | 
            ||
| 512 | $nodes = [];  | 
            ||
| 513 | 			while( ( $row = $result->fetch() ) !== null ) { | 
            ||
| 514 | $nodes[$row['id']] = $this->createNodeBase( $row );  | 
            ||
| 515 | }  | 
            ||
| 516 | }  | 
            ||
| 517 | catch( \Exception $e )  | 
            ||
| 518 | 		{ | 
            ||
| 519 | $result->finish();  | 
            ||
| 520 | throw $e;  | 
            ||
| 521 | }  | 
            ||
| 522 | |||
| 523 | return $nodes;  | 
            ||
| 524 | }  | 
            ||
| 525 | |||
| 526 | |||
| 527 | /**  | 
            ||
| 528 | * Returns a list if node IDs, that are in the path of given node ID.  | 
            ||
| 529 | *  | 
            ||
| 530 | * @param string $id ID of node to get the path for  | 
            ||
| 531 | * @return \Aimeos\MW\Tree\Node\Iface[] List of tree nodes  | 
            ||
| 532 | */  | 
            ||
| 533 | public function getPath( string $id ) : array  | 
            ||
| 534 | 	{ | 
            ||
| 535 | $result = [];  | 
            ||
| 536 | $node = $this->getNode( $id, \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );  | 
            ||
| 537 | |||
| 538 | $search = $this->createSearch();  | 
            ||
| 539 | |||
| 540 | $expr = array(  | 
            ||
| 541 | $search->compare( '<=', $this->searchConfig['left']['code'], $node->left ),  | 
            ||
| 542 | $search->compare( '>=', $this->searchConfig['right']['code'], $node->right ),  | 
            ||
| 543 | );  | 
            ||
| 544 | |||
| 545 | $search->setConditions( $search->and( $expr ) );  | 
            ||
| 546 | $search->setSortations( array( $search->sort( '+', $this->searchConfig['left']['code'] ) ) );  | 
            ||
| 547 | |||
| 548 | $results = $this->searchNodes( $search );  | 
            ||
| 549 | |||
| 550 | 		foreach( $results as $item ) { | 
            ||
| 551 | $result[$item->getId()] = $item;  | 
            ||
| 552 | }  | 
            ||
| 553 | |||
| 554 | return $result;  | 
            ||
| 555 | }  | 
            ||
| 556 | |||
| 557 | |||
| 558 | /**  | 
            ||
| 559 | * Checks if all required search configurations are available.  | 
            ||
| 560 | *  | 
            ||
| 561 | * @param array $config Associative list of search configurations  | 
            ||
| 562 | * @throws \Aimeos\MW\Tree\Exception If one ore more search configurations are missing  | 
            ||
| 563 | */  | 
            ||
| 564 | protected function checkSearchConfig( array $config )  | 
            ||
| 565 | 	{ | 
            ||
| 566 | $required = array( 'id', 'label', 'status', 'level', 'left', 'right' );  | 
            ||
| 567 | |||
| 568 | foreach( $required as $key => $entry )  | 
            ||
| 569 | 		{ | 
            ||
| 570 | 			if( isset( $config[$entry] ) ) { | 
            ||
| 571 | unset( $required[$key] );  | 
            ||
| 572 | }  | 
            ||
| 573 | }  | 
            ||
| 574 | |||
| 575 | if( count( $required ) > 0 )  | 
            ||
| 576 | 		{ | 
            ||
| 577 | $msg = 'Search config in given configuration are missing: "%1$s"';  | 
            ||
| 578 | throw new \Aimeos\MW\Tree\Exception( sprintf( $msg, implode( ', ', $required ) ) );  | 
            ||
| 579 | }  | 
            ||
| 580 | }  | 
            ||
| 581 | |||
| 582 | |||
| 583 | /**  | 
            ||
| 584 | * Checks if all required SQL statements are available.  | 
            ||
| 585 | *  | 
            ||
| 586 | * @param array $config Associative list of SQL statements  | 
            ||
| 587 | * @throws \Aimeos\MW\Tree\Exception If one ore more SQL statements are missing  | 
            ||
| 588 | */  | 
            ||
| 589 | protected function checkSqlConfig( array $config )  | 
            ||
| 590 | 	{ | 
            ||
| 591 | $required = array(  | 
            ||
| 592 | 'delete', 'get', 'insert', 'move-left',  | 
            ||
| 593 | 'move-right', 'search', 'update', 'newid'  | 
            ||
| 594 | );  | 
            ||
| 595 | |||
| 596 | foreach( $required as $key => $entry )  | 
            ||
| 597 | 		{ | 
            ||
| 598 | 			if( isset( $config[$entry] ) ) { | 
            ||
| 599 | unset( $required[$key] );  | 
            ||
| 600 | }  | 
            ||
| 601 | }  | 
            ||
| 602 | |||
| 603 | if( count( $required ) > 0 )  | 
            ||
| 604 | 		{ | 
            ||
| 605 | $msg = 'SQL statements in given configuration are missing: "%1$s"';  | 
            ||
| 606 | throw new \Aimeos\MW\Tree\Exception( sprintf( $msg, implode( ', ', $required ) ) );  | 
            ||
| 607 | }  | 
            ||
| 608 | }  | 
            ||
| 609 | |||
| 610 | |||
| 611 | /**  | 
            ||
| 612 | * Creates a new node object.  | 
            ||
| 613 | *  | 
            ||
| 614 | * @param array $values List of attributes that should be stored in the new node  | 
            ||
| 615 | * @param \Aimeos\MW\Tree\Node\Iface[] $children List of child nodes  | 
            ||
| 616 | * @return \Aimeos\MW\Tree\Node\Iface Empty node object  | 
            ||
| 617 | */  | 
            ||
| 618 | protected function createNodeBase( array $values = [], array $children = [] ) : \Aimeos\MW\Tree\Node\Iface  | 
            ||
| 619 | 	{ | 
            ||
| 620 | return new \Aimeos\MW\Tree\Node\DBNestedSet( $values, $children );  | 
            ||
| 621 | }  | 
            ||
| 622 | |||
| 623 | |||
| 624 | /**  | 
            ||
| 625 | * Creates a tree from the result set returned by the database.  | 
            ||
| 626 | *  | 
            ||
| 627 | * @param \Aimeos\Base\DB\Result\Iface $result Database result  | 
            ||
| 628 | * @param \Aimeos\MW\Tree\Node\Iface $node Current node to add children to  | 
            ||
| 629 | */  | 
            ||
| 630 | protected function createTree( \Aimeos\Base\DB\Result\Iface $result, \Aimeos\MW\Tree\Node\Iface $node ) : ?\Aimeos\MW\Tree\Node\Iface  | 
            ||
| 649 | }  | 
            ||
| 650 | |||
| 651 | |||
| 652 | /**  | 
            ||
| 653 | * Tests if the first node is a child of the second node.  | 
            ||
| 654 | *  | 
            ||
| 655 | * @param \Aimeos\MW\Tree\Node\Iface $node Node to test  | 
            ||
| 656 | * @param \Aimeos\MW\Tree\Node\Iface $parent Parent node  | 
            ||
| 657 | * @return bool True if not is a child of the second node, false if not  | 
            ||
| 658 | */  | 
            ||
| 659 | protected function isChild( \Aimeos\MW\Tree\Node\Iface $node, \Aimeos\MW\Tree\Node\Iface $parent ) : bool  | 
            ||
| 660 | 	{ | 
            ||
| 661 | return $node->__get( 'left' ) > $parent->__get( 'left' ) && $node->__get( 'right' ) < $parent->__get( 'right' );  | 
            ||
| 662 | }  | 
            ||
| 663 | |||
| 664 | |||
| 665 | /**  | 
            ||
| 666 | * Converts the level constant to the depth of the tree.  | 
            ||
| 667 | *  | 
            ||
| 668 | * @param int $level Level constant from \Aimeos\MW\Tree\Manager\Base  | 
            ||
| 669 | * @return int Number of tree levels  | 
            ||
| 670 | * @throws \Aimeos\MW\Tree\Exception if level constant is invalid  | 
            ||
| 671 | */  | 
            ||
| 672 | protected function getLevelFromConstant( int $level ) : int  | 
            ||
| 673 | 	{ | 
            ||
| 674 | switch( $level )  | 
            ||
| 675 | 		{ | 
            ||
| 676 | case \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE:  | 
            ||
| 677 | return 0;  | 
            ||
| 678 | case \Aimeos\MW\Tree\Manager\Base::LEVEL_LIST:  | 
            ||
| 679 | return 1;  | 
            ||
| 680 | case \Aimeos\MW\Tree\Manager\Base::LEVEL_TREE:  | 
            ||
| 681 | return 0x3FFF; // max. possible level / 2 to prevent smallint overflow  | 
            ||
| 682 | default:  | 
            ||
| 683 | throw new \Aimeos\MW\Tree\Exception( sprintf( 'Invalid level constant "%1$d"', $level ) );  | 
            ||
| 684 | }  | 
            ||
| 685 | }  | 
            ||
| 686 | |||
| 687 | |||
| 688 | /**  | 
            ||
| 689 | * Returns a single node identified by its ID.  | 
            ||
| 690 | *  | 
            ||
| 691 | * @param string $id Unique ID  | 
            ||
| 692 | * @return \Aimeos\MW\Tree\Node\Iface Tree node  | 
            ||
| 693 | * @throws \Aimeos\MW\Tree\Exception If node is not found  | 
            ||
| 694 | * @throws \Exception If anything unexcepted occurs  | 
            ||
| 695 | */  | 
            ||
| 696 | protected function getNodeById( string $id ) : \Aimeos\MW\Tree\Node\Iface  | 
            ||
| 697 | 	{ | 
            ||
| 698 | $stmt = $this->conn->create( str_replace( ':cond', '1=1', $this->config['get'] ) );  | 
            ||
| 699 | $stmt->bind( 1, $id, $this->searchConfig['parentid']['internaltype'] );  | 
            ||
| 700 | $stmt->bind( 2, 0, $this->searchConfig['level']['internaltype'] );  | 
            ||
| 701 | $result = $stmt->execute();  | 
            ||
| 702 | |||
| 703 | 		if( ( $row = $result->fetch() ) === null ) { | 
            ||
| 704 | throw new \Aimeos\MW\Tree\Exception( sprintf( 'No node with ID "%1$d" found', $id ) );  | 
            ||
| 705 | }  | 
            ||
| 706 | |||
| 707 | return $this->createNodeBase( $row );  | 
            ||
| 708 | }  | 
            ||
| 709 | |||
| 710 | |||
| 711 | /**  | 
            ||
| 712 | * Returns the first tree root node depending on the sorting direction.  | 
            ||
| 713 | *  | 
            ||
| 714 | * @param string $sort Sort direction, '+' is ascending, '-' is descending  | 
            ||
| 715 | * @return \Aimeos\MW\Tree\Node\Iface|null Tree root node  | 
            ||
| 716 | */  | 
            ||
| 717 | protected function getRootNode( string $sort = '+' ) : ?\Aimeos\MW\Tree\Node\Iface  | 
            ||
| 729 | }  | 
            ||
| 730 | }  | 
            ||
| 731 |