Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

nestedset.php

Zuletzt modifiziert: 09.10.2024, 12:52 - Dateigröße: 27.17 KiB


001  <?php
002  /**
003  *
004  * This file is part of the phpBB Forum Software package.
005  *
006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
007  * @license GNU General Public License, version 2 (GPL-2.0)
008  *
009  * For full copyright and license information, please see
010  * the docs/CREDITS.txt file.
011  *
012  */
013   
014  namespace phpbb\tree;
015   
016  abstract class nestedset implements \phpbb\tree\tree_interface
017  {
018      /** @var \phpbb\db\driver\driver_interface */
019      protected $db;
020   
021      /** @var \phpbb\lock\db */
022      protected $lock;
023   
024      /** @var string */
025      protected $table_name;
026   
027      /**
028      * Prefix for the language keys returned by exceptions
029      * @var string
030      */
031      protected $message_prefix = '';
032   
033      /**
034      * Column names in the table
035      * @var string
036      */
037      protected $column_item_id = 'item_id';
038      protected $column_left_id = 'left_id';
039      protected $column_right_id = 'right_id';
040      protected $column_parent_id = 'parent_id';
041      protected $column_item_parents = 'item_parents';
042   
043      /**
044      * Additional SQL restrictions
045      * Allows to have multiple nested sets in one table
046      * @var string
047      */
048      protected $sql_where = '';
049   
050      /**
051      * List of item properties to be cached in the item_parents column
052      * @var array
053      */
054      protected $item_basic_data = array('*');
055   
056      /**
057      * Construct
058      *
059      * @param \phpbb\db\driver\driver_interface    $db        Database connection
060      * @param \phpbb\lock\db        $lock    Lock class used to lock the table when moving forums around
061      * @param string            $table_name            Table name
062      * @param string            $message_prefix        Prefix for the messages thrown by exceptions
063      * @param string            $sql_where            Additional SQL restrictions for the queries
064      * @param array            $item_basic_data    Array with basic item data that is stored in item_parents
065      * @param array            $columns            Array with column names to overwrite
066      */
067      public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\lock\db $lock, $table_name, $message_prefix = '', $sql_where = '', $item_basic_data = array(), $columns = array())
068      {
069          $this->db = $db;
070          $this->lock = $lock;
071   
072          $this->table_name = $table_name;
073          $this->message_prefix = $message_prefix;
074          $this->sql_where = $sql_where;
075          $this->item_basic_data = (!empty($item_basic_data)) ? $item_basic_data : array('*');
076   
077          if (!empty($columns))
078          {
079              foreach ($columns as $column => $name)
080              {
081                  $column_name = 'column_' . $column;
082                  $this->$column_name = $name;
083              }
084          }
085      }
086   
087      /**
088      * Returns additional sql where restrictions
089      *
090      * @param string        $operator        SQL operator that needs to be prepended to sql_where,
091      *                                    if it is not empty.
092      * @param string        $column_prefix    Prefix that needs to be prepended to column names
093      * @return string        Returns additional where statements to narrow down the tree,
094      *                        prefixed with operator and prepended column_prefix to column names
095      */
096      public function get_sql_where($operator = 'AND', $column_prefix = '')
097      {
098          return (!$this->sql_where) ? '' : $operator . ' ' . sprintf($this->sql_where, $column_prefix);
099      }
100   
101      /**
102      * Acquires a lock on the item table
103      *
104      * @return bool    True if the lock was acquired, false if it has been acquired previously
105      *
106      * @throws \RuntimeException If the lock could not be acquired
107      */
108      protected function acquire_lock()
109      {
110          if ($this->lock->owns_lock())
111          {
112              return false;
113          }
114   
115          if (!$this->lock->acquire())
116          {
117              throw new \RuntimeException($this->message_prefix . 'LOCK_FAILED_ACQUIRE');
118          }
119   
120          return true;
121      }
122   
123      /**
124      * {@inheritdoc}
125      */
126      public function insert(array $additional_data)
127      {
128          $item_data = $this->reset_nestedset_values($additional_data);
129   
130          $sql = 'INSERT INTO ' . $this->table_name . ' ' . $this->db->sql_build_array('INSERT', $item_data);
131          $this->db->sql_query($sql);
132   
133          $item_data[$this->column_item_id] = (int) $this->db->sql_nextid();
134   
135          return array_merge($item_data, $this->add_item_to_nestedset($item_data[$this->column_item_id]));
136      }
137   
138      /**
139      * Add an item which already has a database row at the end of the tree
140      *
141      * @param int    $item_id    The item to be added
142      * @return array        Array with updated data, if the item was added successfully
143      *                    Empty array otherwise
144      */
145      protected function add_item_to_nestedset($item_id)
146      {
147          $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . '
148              FROM ' . $this->table_name . '
149              ' . $this->get_sql_where('WHERE');
150          $result = $this->db->sql_query($sql);
151          $current_max_right_id = (int) $this->db->sql_fetchfield($this->column_right_id);
152          $this->db->sql_freeresult($result);
153   
154          $update_item_data = array(
155              $this->column_parent_id        => 0,
156              $this->column_left_id        => $current_max_right_id + 1,
157              $this->column_right_id        => $current_max_right_id + 2,
158              $this->column_item_parents    => '',
159          );
160   
161          $sql = 'UPDATE ' . $this->table_name . '
162              SET ' . $this->db->sql_build_array('UPDATE', $update_item_data) . '
163              WHERE ' . $this->column_item_id . ' = ' . (int) $item_id . '
164                  AND ' . $this->column_parent_id . ' = 0
165                  AND ' . $this->column_left_id . ' = 0
166                  AND ' . $this->column_right_id . ' = 0';
167          $this->db->sql_query($sql);
168   
169          return ($this->db->sql_affectedrows() == 1) ? $update_item_data : array();
170      }
171   
172      /**
173      * Remove an item from the tree without deleting it from the database
174      *
175      * Also removes all subitems from the tree without deleting them from the database either
176      *
177      * @param int    $item_id    The item to be deleted
178      * @return array        Item ids that have been removed
179      * @throws \OutOfBoundsException
180      */
181      protected function remove_item_from_nestedset($item_id)
182      {
183          $item_id = (int) $item_id;
184          if (!$item_id)
185          {
186              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_ITEM');
187          }
188   
189          $items = $this->get_subtree_data($item_id);
190          $item_ids = array_keys($items);
191   
192          if (empty($items) || !isset($items[$item_id]))
193          {
194              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_ITEM');
195          }
196   
197          $this->remove_subset($item_ids, $items[$item_id]);
198   
199          return $item_ids;
200      }
201   
202      /**
203      * {@inheritdoc}
204      */
205      public function delete($item_id)
206      {
207          $removed_items = $this->remove_item_from_nestedset($item_id);
208   
209          $sql = 'DELETE FROM ' . $this->table_name . '
210              WHERE ' . $this->db->sql_in_set($this->column_item_id, $removed_items) . '
211              ' . $this->get_sql_where('AND');
212          $this->db->sql_query($sql);
213   
214          return $removed_items;
215      }
216   
217      /**
218      * {@inheritdoc}
219      */
220      public function move($item_id, $delta)
221      {
222          if ($delta == 0)
223          {
224              return false;
225          }
226   
227          $this->acquire_lock();
228   
229          $action = ($delta > 0) ? 'move_up' : 'move_down';
230          $delta = abs($delta);
231   
232          // Keep $this->get_sql_where() here, to ensure we are in the right tree.
233          $sql = 'SELECT *
234              FROM ' . $this->table_name . '
235              WHERE ' . $this->column_item_id . ' = ' . (int) $item_id . '
236                  ' . $this->get_sql_where();
237          $result = $this->db->sql_query_limit($sql, $delta);
238          $item = $this->db->sql_fetchrow($result);
239          $this->db->sql_freeresult($result);
240   
241          if (!$item)
242          {
243              $this->lock->release();
244              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_ITEM');
245          }
246   
247          /**
248          * Fetch all the siblings between the item's current spot
249          * and where we want to move it to. If there are less than $delta
250          * siblings between the current spot and the target then the
251          * item will move as far as possible
252          */
253          $sql = "SELECT {$this->column_item_id}{$this->column_parent_id}{$this->column_left_id}{$this->column_right_id}{$this->column_item_parents}
254              FROM " . $this->table_name . '
255              WHERE ' . $this->column_parent_id . ' = ' . (int) $item[$this->column_parent_id] . '
256                  ' . $this->get_sql_where() . '
257                  AND ';
258   
259          if ($action == 'move_up')
260          {
261              $sql .= $this->column_right_id . ' < ' . (int) $item[$this->column_right_id] . ' ORDER BY ' . $this->column_right_id . ' DESC';
262          }
263          else
264          {
265              $sql .= $this->column_left_id . ' > ' . (int) $item[$this->column_left_id] . ' ORDER BY ' . $this->column_left_id . ' ASC';
266          }
267   
268          $result = $this->db->sql_query_limit($sql, $delta);
269   
270          $target = false;
271          while ($row = $this->db->sql_fetchrow($result))
272          {
273              $target = $row;
274          }
275          $this->db->sql_freeresult($result);
276   
277          if (!$target)
278          {
279              $this->lock->release();
280              // The item is already on top or bottom
281              return false;
282          }
283   
284          /**
285          * $left_id and $right_id define the scope of the items that are affected by the move.
286          * $diff_up and $diff_down are the values to substract or add to each item's left_id
287          * and right_id in order to move them up or down.
288          * $move_up_left and $move_up_right define the scope of the items that are moving
289          * up. Other items in the scope of ($left_id, $right_id) are considered to move down.
290          */
291          if ($action == 'move_up')
292          {
293              $left_id = (int) $target[$this->column_left_id];
294              $right_id = (int) $item[$this->column_right_id];
295   
296              $diff_up = (int) $item[$this->column_left_id] - (int) $target[$this->column_left_id];
297              $diff_down = (int) $item[$this->column_right_id] + 1 - (int) $item[$this->column_left_id];
298   
299              $move_up_left = (int) $item[$this->column_left_id];
300              $move_up_right = (int) $item[$this->column_right_id];
301          }
302          else
303          {
304              $left_id = (int) $item[$this->column_left_id];
305              $right_id = (int) $target[$this->column_right_id];
306   
307              $diff_up = (int) $item[$this->column_right_id] + 1 - (int) $item[$this->column_left_id];
308              $diff_down = (int) $target[$this->column_right_id] - (int) $item[$this->column_right_id];
309   
310              $move_up_left = (int) $item[$this->column_right_id] + 1;
311              $move_up_right = (int) $target[$this->column_right_id];
312          }
313   
314          // Now do the dirty job
315          $sql = 'UPDATE ' . $this->table_name . '
316              SET ' . $this->column_left_id . ' = ' . $this->column_left_id . ' + CASE
317                  WHEN ' . $this->column_left_id . " BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up}
318                  ELSE {$diff_down}
319              END,
320              " . $this->column_right_id . ' = ' . $this->column_right_id . ' + CASE
321                  WHEN ' . $this->column_right_id . " BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up}
322                  ELSE {$diff_down}
323              END
324              WHERE
325                  " . $this->column_left_id . " BETWEEN {$left_id} AND {$right_id}
326                  AND " . $this->column_right_id . " BETWEEN {$left_id} AND {$right_id}
327                  " . $this->get_sql_where();
328          $this->db->sql_query($sql);
329   
330          $this->lock->release();
331   
332          return true;
333      }
334   
335      /**
336      * {@inheritdoc}
337      */
338      public function move_down($item_id)
339      {
340          return $this->move($item_id, -1);
341      }
342   
343      /**
344      * {@inheritdoc}
345      */
346      public function move_up($item_id)
347      {
348          return $this->move($item_id, 1);
349      }
350   
351      /**
352      * {@inheritdoc}
353      */
354      public function move_children($current_parent_id, $new_parent_id)
355      {
356          $current_parent_id = (int) $current_parent_id;
357          $new_parent_id = (int) $new_parent_id;
358   
359          if ($current_parent_id == $new_parent_id)
360          {
361              return false;
362          }
363   
364          if (!$current_parent_id)
365          {
366              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_ITEM');
367          }
368   
369          $this->acquire_lock();
370   
371          $item_data = $this->get_subtree_data($current_parent_id);
372          if (!isset($item_data[$current_parent_id]))
373          {
374              $this->lock->release();
375              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_ITEM');
376          }
377   
378          $current_parent = $item_data[$current_parent_id];
379          unset($item_data[$current_parent_id]);
380          $move_items = array_keys($item_data);
381   
382          if (($current_parent[$this->column_right_id] - $current_parent[$this->column_left_id]) <= 1)
383          {
384              $this->lock->release();
385              return false;
386          }
387   
388          if (in_array($new_parent_id, $move_items))
389          {
390              $this->lock->release();
391              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_PARENT');
392          }
393   
394          $sql_exclude_moved_items = $this->db->sql_in_set($this->column_item_id, $move_items, true);
395   
396          $this->db->sql_transaction('begin');
397   
398          $this->remove_subset($move_items, $current_parent, false, true);
399   
400          if ($new_parent_id)
401          {
402              // Retrieve new-parent again, it may have been changed...
403              $sql = 'SELECT *
404                  FROM ' . $this->table_name . '
405                  WHERE ' . $this->column_item_id . ' = ' . $new_parent_id;
406              $result = $this->db->sql_query($sql);
407              $new_parent = $this->db->sql_fetchrow($result);
408              $this->db->sql_freeresult($result);
409   
410              if (!$new_parent)
411              {
412                  $this->db->sql_transaction('rollback');
413                  $this->lock->release();
414                  throw new \OutOfBoundsException($this->message_prefix . 'INVALID_PARENT');
415              }
416   
417              $new_right_id = $this->prepare_adding_subset($move_items, $new_parent, true);
418   
419              if ($new_right_id > $current_parent[$this->column_right_id])
420              {
421                  $diff = ' + ' . ($new_right_id - $current_parent[$this->column_right_id]);
422              }
423              else
424              {
425                  $diff = ' - ' . abs($new_right_id - $current_parent[$this->column_right_id]);
426              }
427          }
428          else
429          {
430              $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . '
431                  FROM ' . $this->table_name . '
432                  WHERE ' . $sql_exclude_moved_items . '
433                      ' . $this->get_sql_where('AND');
434              $result = $this->db->sql_query($sql);
435              $row = $this->db->sql_fetchrow($result);
436              $this->db->sql_freeresult($result);
437   
438              $diff = ' + ' . ($row[$this->column_right_id] - $current_parent[$this->column_left_id]);
439          }
440   
441          $sql = 'UPDATE ' . $this->table_name . '
442              SET ' . $this->column_left_id . ' = ' . $this->column_left_id . $diff . ',
443                  ' . $this->column_right_id . ' = ' . $this->column_right_id . $diff . ',
444                  ' . $this->column_parent_id . ' = ' . $this->db->sql_case($this->column_parent_id . ' = ' . $current_parent_id, $new_parent_id, $this->column_parent_id) . ',
445                  ' . $this->column_item_parents . " = ''
446              WHERE " . $this->db->sql_in_set($this->column_item_id, $move_items) . '
447                  ' . $this->get_sql_where('AND');
448          $this->db->sql_query($sql);
449   
450          $this->db->sql_transaction('commit');
451          $this->lock->release();
452   
453          return true;
454      }
455   
456      /**
457      * {@inheritdoc}
458      */
459      public function change_parent($item_id, $new_parent_id)
460      {
461          $item_id = (int) $item_id;
462          $new_parent_id = (int) $new_parent_id;
463   
464          if ($item_id == $new_parent_id)
465          {
466              return false;
467          }
468   
469          if (!$item_id)
470          {
471              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_ITEM');
472          }
473   
474          $this->acquire_lock();
475   
476          $item_data = $this->get_subtree_data($item_id);
477          if (!isset($item_data[$item_id]))
478          {
479              $this->lock->release();
480              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_ITEM');
481          }
482   
483          $item = $item_data[$item_id];
484          $move_items = array_keys($item_data);
485   
486          if (in_array($new_parent_id, $move_items))
487          {
488              $this->lock->release();
489              throw new \OutOfBoundsException($this->message_prefix . 'INVALID_PARENT');
490          }
491   
492          $sql_exclude_moved_items = $this->db->sql_in_set($this->column_item_id, $move_items, true);
493   
494          $this->db->sql_transaction('begin');
495   
496          $this->remove_subset($move_items, $item, false, true);
497   
498          if ($new_parent_id)
499          {
500              // Retrieve new-parent again, it may have been changed...
501              $sql = 'SELECT *
502                  FROM ' . $this->table_name . '
503                  WHERE ' . $this->column_item_id . ' = ' . $new_parent_id;
504              $result = $this->db->sql_query($sql);
505              $new_parent = $this->db->sql_fetchrow($result);
506              $this->db->sql_freeresult($result);
507   
508              if (!$new_parent)
509              {
510                  $this->db->sql_transaction('rollback');
511                  $this->lock->release();
512                  throw new \OutOfBoundsException($this->message_prefix . 'INVALID_PARENT');
513              }
514   
515              $new_right_id = $this->prepare_adding_subset($move_items, $new_parent, true);
516   
517              if ($new_right_id > (int) $item[$this->column_right_id])
518              {
519                  $diff = ' + ' . ($new_right_id - (int) $item[$this->column_right_id] - 1);
520              }
521              else
522              {
523                  $diff = ' - ' . abs($new_right_id - (int) $item[$this->column_right_id] - 1);
524              }
525          }
526          else
527          {
528              $sql = 'SELECT MAX(' . $this->column_right_id . ') AS ' . $this->column_right_id . '
529                  FROM ' . $this->table_name . '
530                  WHERE ' . $sql_exclude_moved_items . '
531                      ' . $this->get_sql_where('AND');
532              $result = $this->db->sql_query($sql);
533              $row = $this->db->sql_fetchrow($result);
534              $this->db->sql_freeresult($result);
535   
536              $diff = ' + ' . ($row[$this->column_right_id] - (int) $item[$this->column_left_id] + 1);
537          }
538   
539          $sql = 'UPDATE ' . $this->table_name . '
540              SET ' . $this->column_left_id . ' = ' . $this->column_left_id . $diff . ',
541                  ' . $this->column_right_id . ' = ' . $this->column_right_id . $diff . ',
542                  ' . $this->column_parent_id . ' = ' . $this->db->sql_case($this->column_item_id . ' = ' . $item_id, $new_parent_id, $this->column_parent_id) . ',
543                  ' . $this->column_item_parents . " = ''
544              WHERE " . $this->db->sql_in_set($this->column_item_id, $move_items) . '
545                  ' . $this->get_sql_where('AND');
546          $this->db->sql_query($sql);
547   
548          $this->db->sql_transaction('commit');
549          $this->lock->release();
550   
551          return true;
552      }
553   
554      /**
555      * {@inheritdoc}
556      */
557      public function get_path_and_subtree_data($item_id, $order_asc = true, $include_item = true)
558      {
559          $condition = 'i2.' . $this->column_left_id . ' BETWEEN i1.' . $this->column_left_id . ' AND i1.' . $this->column_right_id . '
560              OR i1.' . $this->column_left_id . ' BETWEEN i2.' . $this->column_left_id . ' AND i2.' . $this->column_right_id;
561   
562          return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item);
563      }
564   
565      /**
566      * {@inheritdoc}
567      */
568      public function get_path_data($item_id, $order_asc = true, $include_item = true)
569      {
570          $condition = 'i1.' . $this->column_left_id . ' BETWEEN i2.' . $this->column_left_id . ' AND i2.' . $this->column_right_id . '';
571   
572          return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item);
573      }
574   
575      /**
576      * {@inheritdoc}
577      */
578      public function get_subtree_data($item_id, $order_asc = true, $include_item = true)
579      {
580          $condition = 'i2.' . $this->column_left_id . ' BETWEEN i1.' . $this->column_left_id . ' AND i1.' . $this->column_right_id . '';
581   
582          return $this->get_set_of_nodes_data($item_id, $condition, $order_asc, $include_item);
583      }
584   
585      /**
586      * Get items that are related to the given item by the condition
587      *
588      * @param int        $item_id        Id of the item to retrieve the node set from
589      * @param string        $condition        Query string restricting the item list
590      * @param bool        $order_asc        Order the items ascending by their left_id
591      * @param bool        $include_item    Should the item matching the given item id be included in the list as well
592      * @return array            Array of items (containing all columns from the item table)
593      *                            ID => Item data
594      */
595      protected function get_set_of_nodes_data($item_id, $condition, $order_asc = true, $include_item = true)
596      {
597          $rows = array();
598   
599          $sql = 'SELECT i2.*
600              FROM ' . $this->table_name . ' i1
601              LEFT JOIN ' . $this->table_name . " i2
602                  ON (($condition" . $this->get_sql_where('AND', 'i2.') . ')
603              WHERE i1.' . $this->column_item_id . ' = ' . (int) $item_id . '
604                  ' . $this->get_sql_where('AND', 'i1.') . '
605              ORDER BY i2.' . $this->column_left_id . ' ' . ($order_asc ? 'ASC' : 'DESC');
606          $result = $this->db->sql_query($sql);
607   
608          while ($row = $this->db->sql_fetchrow($result))
609          {
610              if (!$include_item && $item_id == $row[$this->column_item_id])
611              {
612                  continue;
613              }
614   
615              $rows[(int) $row[$this->column_item_id]] = $row;
616          }
617          $this->db->sql_freeresult($result);
618   
619          return $rows;
620      }
621   
622      /**
623      * Get basic data of all parent items
624      *
625      * Basic data is defined in the $item_basic_data property.
626      * Data is cached in the item_parents column in the item table
627      *
628      * @param array    $item        The item to get the path from
629      * @return array            Array of items (containing basic columns from the item table)
630      *                            ID => Item data
631      */
632      public function get_path_basic_data(array $item)
633      {
634          $parents = array();
635          if ($item[$this->column_parent_id])
636          {
637              if (!$item[$this->column_item_parents])
638              {
639                  $sql = 'SELECT ' . implode(', ', $this->item_basic_data) . '
640                      FROM ' . $this->table_name . '
641                      WHERE ' . $this->column_left_id . ' < ' . (int) $item[$this->column_left_id] . '
642                          AND ' . $this->column_right_id . ' > ' . (int) $item[$this->column_right_id] . '
643                          ' . $this->get_sql_where('AND') . '
644                      ORDER BY ' . $this->column_left_id . ' ASC';
645                  $result = $this->db->sql_query($sql);
646   
647                  while ($row = $this->db->sql_fetchrow($result))
648                  {
649                      $parents[$row[$this->column_item_id]] = $row;
650                  }
651                  $this->db->sql_freeresult($result);
652   
653                  $item_parents = serialize($parents);
654   
655                  $sql = 'UPDATE ' . $this->table_name . '
656                      SET ' . $this->column_item_parents . " = '" . $this->db->sql_escape($item_parents) . "'
657                      WHERE " . $this->column_parent_id . ' = ' . (int) $item[$this->column_parent_id];
658                  $this->db->sql_query($sql);
659              }
660              else
661              {
662                  $parents = unserialize($item[$this->column_item_parents]);
663              }
664          }
665   
666          return $parents;
667      }
668   
669      /**
670      * Get all items from the tree
671      *
672      * @param bool        $order_asc        Order the items ascending by their left_id
673      * @return array            Array of items (containing all columns from the item table)
674      *                            ID => Item data
675      */
676      public function get_all_tree_data($order_asc = true)
677      {
678          $rows = array();
679   
680          $sql = 'SELECT *
681              FROM ' . $this->table_name . ' ' .
682              $this->get_sql_where('WHERE') . '
683              ORDER BY ' . $this->column_left_id . ' ' . ($order_asc ? 'ASC' : 'DESC');
684          $result = $this->db->sql_query($sql);
685   
686          while ($row = $this->db->sql_fetchrow($result))
687          {
688              $rows[(int) $row[$this->column_item_id]] = $row;
689          }
690          $this->db->sql_freeresult($result);
691   
692          return $rows;
693      }
694   
695      /**
696      * Remove a subset from the nested set
697      *
698      * @param array    $subset_items        Subset of items to remove
699      * @param array    $bounding_item        Item containing the right bound of the subset
700      * @param bool    $set_subset_zero    Should the parent, left and right id of the items be set to 0, or kept unchanged?
701      *                                    In case of removing an item from the tree, we should the values to 0
702      *                                    In case of moving an item, we shouldkeep the original values, in order to allow "+ diff" later
703      * @return    null
704      */
705      protected function remove_subset(array $subset_items, array $bounding_item, $set_subset_zero = true)
706      {
707          $acquired_new_lock = $this->acquire_lock();
708   
709          $diff = sizeof($subset_items) * 2;
710          $sql_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items);
711          $sql_not_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items, true);
712   
713          $sql_is_parent = $this->column_left_id . ' <= ' . (int) $bounding_item[$this->column_right_id] . '
714              AND ' . $this->column_right_id . ' >= ' . (int) $bounding_item[$this->column_right_id];
715   
716          $sql_is_right = $this->column_left_id . ' > ' . (int) $bounding_item[$this->column_right_id];
717   
718          $set_left_id = $this->db->sql_case($sql_is_right, $this->column_left_id . ' - ' . $diff, $this->column_left_id);
719          $set_right_id = $this->db->sql_case($sql_is_parent . ' OR ' . $sql_is_right, $this->column_right_id . ' - ' . $diff, $this->column_right_id);
720   
721          if ($set_subset_zero)
722          {
723              $set_left_id = $this->db->sql_case($sql_subset_items, 0, $set_left_id);
724              $set_right_id = $this->db->sql_case($sql_subset_items, 0, $set_right_id);
725          }
726   
727          $sql = 'UPDATE ' . $this->table_name . '
728              SET ' . (($set_subset_zero) ? $this->column_parent_id . ' = ' . $this->db->sql_case($sql_subset_items, 0, $this->column_parent_id) . ',' : '') . '
729                  ' . $this->column_left_id . ' = ' . $set_left_id . ',
730                  ' . $this->column_right_id . ' = ' . $set_right_id . '
731              ' . ((!$set_subset_zero) ? ' WHERE ' . $sql_not_subset_items . ' ' . $this->get_sql_where('AND') : $this->get_sql_where('WHERE'));
732          $this->db->sql_query($sql);
733   
734          if ($acquired_new_lock)
735          {
736              $this->lock->release();
737          }
738      }
739   
740      /**
741      * Prepare adding a subset to the nested set
742      *
743      * @param array    $subset_items        Subset of items to add
744      * @param array    $new_parent    Item containing the right bound of the new parent
745      * @return    int        New right id of the parent item
746      */
747      protected function prepare_adding_subset(array $subset_items, array $new_parent)
748      {
749          $diff = sizeof($subset_items) * 2;
750          $sql_not_subset_items = $this->db->sql_in_set($this->column_item_id, $subset_items, true);
751   
752          $set_left_id = $this->db->sql_case($this->column_left_id . ' > ' . (int) $new_parent[$this->column_right_id], $this->column_left_id . ' + ' . $diff, $this->column_left_id);
753          $set_right_id = $this->db->sql_case($this->column_right_id . ' >= ' . (int) $new_parent[$this->column_right_id], $this->column_right_id . ' + ' . $diff, $this->column_right_id);
754   
755          $sql = 'UPDATE ' . $this->table_name . '
756              SET ' . $this->column_left_id . ' = ' . $set_left_id . ',
757                  ' . $this->column_right_id . ' = ' . $set_right_id . '
758              WHERE ' . $sql_not_subset_items . '
759                  ' . $this->get_sql_where('AND');
760          $this->db->sql_query($sql);
761   
762          return $new_parent[$this->column_right_id] + $diff;
763      }
764   
765      /**
766      * Resets values required for the nested set system
767      *
768      * @param array    $item        Original item data
769      * @return    array        Original item data + nested set defaults
770      */
771      protected function reset_nestedset_values(array $item)
772      {
773          $item_data = array_merge($item, array(
774              $this->column_parent_id        => 0,
775              $this->column_left_id        => 0,
776              $this->column_right_id        => 0,
777              $this->column_item_parents    => '',
778          ));
779   
780          unset($item_data[$this->column_item_id]);
781   
782          return $item_data;
783      }
784   
785      /**
786      * Regenerate left/right ids from parent/child relationship
787      *
788      * This method regenerates the left/right ids for the tree based on
789      * the parent/child relations. This function executes three queries per
790      * item, so it should only be called, when the set has one of the following
791      * problems:
792      *    - The set has a duplicated value inside the left/right id chain
793      *    - The set has a missing value inside the left/right id chain
794      *    - The set has items that do not have a left/right id set
795      *
796      * When regenerating the items, the items are sorted by parent id and their
797      * current left id, so the current child/parent relationships are kept
798      * and running the function on a working set will not change the order.
799      *
800      * @param int    $new_id        First left_id to be used (should start with 1)
801      * @param int    $parent_id    parent_id of the current set (default = 0)
802      * @param bool    $reset_ids    Should we reset all left_id/right_id on the first call?
803      * @return    int        $new_id        The next left_id/right_id that should be used
804      */
805      public function regenerate_left_right_ids($new_id, $parent_id = 0, $reset_ids = false)
806      {
807          if ($acquired_new_lock = $this->acquire_lock())
808          {
809              $this->db->sql_transaction('begin');
810   
811              if (!$reset_ids)
812              {
813                  $sql = 'UPDATE ' . $this->table_name . '
814                      SET ' . $this->column_item_parents . " = ''
815                      " . $this->get_sql_where('WHERE');
816                  $this->db->sql_query($sql);
817              }
818          }
819   
820          if ($reset_ids)
821          {
822              $sql = 'UPDATE ' . $this->table_name . '
823                  SET ' . $this->db->sql_build_array('UPDATE', array(
824                      $this->column_left_id        => 0,
825                      $this->column_right_id        => 0,
826                      $this->column_item_parents    => '',
827                  )) . '
828                  ' . $this->get_sql_where('WHERE');
829              $this->db->sql_query($sql);
830          }
831   
832          $sql = 'SELECT *
833              FROM ' . $this->table_name . '
834              WHERE ' . $this->column_parent_id . ' = ' . (int) $parent_id . '
835                  ' . $this->get_sql_where('AND') . '
836              ORDER BY ' . $this->column_left_id . ', ' . $this->column_item_id . ' ASC';
837          $result = $this->db->sql_query($sql);
838          $rows = $this->db->sql_fetchrowset($result);
839          $this->db->sql_freeresult($result);
840   
841          foreach ($rows as $row)
842          {
843              // First we update the left_id for this module
844              if ($row[$this->column_left_id] != $new_id)
845              {
846                  $sql = 'UPDATE ' . $this->table_name . '
847                      SET ' . $this->db->sql_build_array('UPDATE', array($this->column_left_id => $new_id)) . '
848                      WHERE ' . $this->column_item_id . ' = ' . (int) $row[$this->column_item_id];
849                  $this->db->sql_query($sql);
850              }
851              $new_id++;
852   
853              // Then we go through any children and update their left/right id's
854              $new_id = $this->regenerate_left_right_ids($new_id, $row[$this->column_item_id]);
855   
856              // Then we come back and update the right_id for this module
857              if ($row[$this->column_right_id] != $new_id)
858              {
859                  $sql = 'UPDATE ' . $this->table_name . '
860                      SET ' . $this->db->sql_build_array('UPDATE', array($this->column_right_id => $new_id)) . '
861                      WHERE ' . $this->column_item_id . ' = ' . (int) $row[$this->column_item_id];
862                  $this->db->sql_query($sql);
863              }
864              $new_id++;
865          }
866   
867          if ($acquired_new_lock)
868          {
869              $this->db->sql_transaction('commit');
870              $this->lock->release();
871          }
872   
873          return $new_id;
874      }
875  }
876