Dotclear

source: inc/core/class.dc.categories.php @ 3874:ab8368569446

Revision 3874:ab8368569446, 20.6 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

short notation for array (array() → [])

Line 
1<?php
2/**
3 * nestedTree class is based on excellent work of Kuzma Feskov
4 * (http://php.russofile.ru/ru/authors/sql/nestedsets01/)
5 *
6 * One day we'll move nestedTree to Clearbricks.
7 *
8 * @package Dotclear
9 * @subpackage Core
10 *
11 * @copyright Olivier Meunier & Association Dotclear
12 * @copyright GPL-2.0-only
13 *
14 */
15
16if (!defined('DC_RC_PATH')) {return;}
17
18class dcCategories extends nestedTree
19{
20    protected $f_left  = 'cat_lft';
21    protected $f_right = 'cat_rgt';
22    protected $f_id    = 'cat_id';
23
24    protected $core;
25    protected $blog_id;
26
27    public function __construct($core)
28    {
29        $this->core          = &$core;
30        $this->con           = &$core->con;
31        $this->blog_id       = $core->blog->id;
32        $this->table         = $core->prefix . 'category';
33        $this->add_condition = ['blog_id' => "'" . $this->con->escape($this->blog_id) . "'"];
34    }
35
36    public function getChildren($start = 0, $id = null, $sort = 'asc', $fields = [])
37    {
38        $fields = array_merge(['cat_title', 'cat_url', 'cat_desc'], $fields);
39        return parent::getChildren($start, $id, $sort, $fields);
40    }
41
42    public function getParents($id, $fields = [])
43    {
44        $fields = array_merge(['cat_title', 'cat_url', 'cat_desc'], $fields);
45        return parent::getParents($id, $fields);
46    }
47
48    public function getParent($id, $fields = [])
49    {
50        $fields = array_merge(['cat_title', 'cat_url', 'cat_desc'], $fields);
51        return parent::getParent($id, $fields);
52    }
53}
54
55abstract class nestedTree
56{
57    protected $con;
58
59    protected $table;
60    protected $f_left;
61    protected $f_right;
62    protected $f_id;
63
64    protected $add_condition = [];
65
66    protected $parents;
67
68    public function __construct($con)
69    {
70        $this->con = &$con;
71    }
72
73    public function getChildren($start = 0, $id = null, $sort = 'asc', $fields = [])
74    {
75        $fields = count($fields) > 0 ? ', C2.' . implode(', C2.', $fields) : '';
76
77        $sql = 'SELECT C2.' . $this->f_id . ', C2.' . $this->f_left . ', C2.' . $this->f_right . ', COUNT(C1.' . $this->f_id . ') AS level '
78        . $fields . ' '
79        . 'FROM ' . $this->table . ' AS C1, ' . $this->table . ' AS C2 %s '
80        . 'WHERE C2.' . $this->f_left . ' BETWEEN C1.' . $this->f_left . ' AND C1.' . $this->f_right . ' '
81        . ' %s '
82        . $this->getCondition('AND', 'C2.')
83        . $this->getCondition('AND', 'C1.')
84        . 'GROUP BY C2.' . $this->f_id . ', C2.' . $this->f_left . ', C2.' . $this->f_right . ' ' . $fields . ' '
85        . ' %s '
86        . 'ORDER BY C2.' . $this->f_left . ' ' . ($sort == 'asc' ? 'ASC' : 'DESC') . ' ';
87
88        $from = $where = '';
89        if ($start > 0) {
90            $from  = ', ' . $this->table . ' AS C3';
91            $where = 'AND C3.' . $this->f_id . ' = ' . (integer) $start . ' AND C1.' . $this->f_left . ' >= C3.' . $this->f_left . ' AND C1.' . $this->f_right . ' <= C3.' . $this->f_right;
92            $where .= $this->getCondition('AND', 'C3.');
93        }
94
95        $having = '';
96        if ($id !== null) {
97            $having = ' HAVING C2.' . $this->f_id . ' = ' . (integer) $id;
98        }
99
100        $sql = sprintf($sql, $from, $where, $having);
101
102        return $this->con->select($sql);
103    }
104
105    public function getParents($id, $fields = [])
106    {
107        $fields = count($fields) > 0 ? ', C1.' . implode(', C1.', $fields) : '';
108
109        return $this->con->select(
110            'SELECT C1.' . $this->f_id . ' ' . $fields . ' '
111            . 'FROM ' . $this->table . ' C1, ' . $this->table . ' C2 '
112            . 'WHERE C2.' . $this->f_id . ' = ' . (integer) $id . ' '
113            . 'AND C1.' . $this->f_left . ' < C2.' . $this->f_left . ' '
114            . 'AND C1.' . $this->f_right . ' > C2.' . $this->f_right . ' '
115            . $this->getCondition('AND', 'C2.')
116            . $this->getCondition('AND', 'C1.')
117            . 'ORDER BY C1.' . $this->f_left . ' ASC '
118        );
119    }
120
121    public function getParent($id, $fields = [])
122    {
123        $fields = count($fields) > 0 ? ', C1.' . implode(', C1.', $fields) : '';
124
125        return $this->con->select(
126            'SELECT C1.' . $this->f_id . ' ' . $fields . ' '
127            . 'FROM ' . $this->table . ' C1, ' . $this->table . ' C2 '
128            . 'WHERE C2.' . $this->f_id . ' = ' . (integer) $id . ' '
129            . 'AND C1.' . $this->f_left . ' < C2.' . $this->f_left . ' '
130            . 'AND C1.' . $this->f_right . ' > C2.' . $this->f_right . ' '
131            . $this->getCondition('AND', 'C2.')
132            . $this->getCondition('AND', 'C1.')
133            . 'ORDER BY C1.' . $this->f_left . ' DESC '
134            . $this->con->limit(1)
135        );
136    }
137
138    /* ------------------------------------------------
139     * Tree manipulations
140     * ---------------------------------------------- */
141    public function addNode($data, $target = 0)
142    {
143        if (!is_array($data) && !($data instanceof cursor)) {
144            throw new Exception('Invalid data block');
145        }
146
147        if (is_array($data)) {
148            $D    = $data;
149            $data = $this->con->openCursor($this->table);
150            foreach ($D as $k => $v) {
151                $data->{$k} = $v;
152            }
153            unset($D);
154        }
155
156        # We want to put it at the end
157        $this->con->writeLock($this->table);
158        try
159        {
160            $rs = $this->con->select('SELECT MAX(' . $this->f_id . ') as n_id FROM ' . $this->table);
161            $id = $rs->n_id;
162
163            $rs = $this->con->select(
164                'SELECT MAX(' . $this->f_right . ') as n_r ' .
165                'FROM ' . $this->table .
166                $this->getCondition('WHERE')
167            );
168            $last = $rs->n_r == 0 ? 1 : $rs->n_r;
169
170            $data->{$this->f_id}    = $id + 1;
171            $data->{$this->f_left}  = $last + 1;
172            $data->{$this->f_right} = $last + 2;
173
174            $data->insert();
175            $this->con->unlock();
176            try {
177                $this->setNodeParent($id + 1, $target);
178                return $data->{$this->f_id};
179            } catch (Exception $e) {} # We don't mind error in this case
180        } catch (Exception $e) {
181            $this->con->unlock();
182            throw $e;
183        }
184    }
185
186    public function updatePosition($id, $left, $right)
187    {
188        $node_left  = (integer) $left;
189        $node_right = (integer) $right;
190        $node_id    = (integer) $id;
191        $sql        = 'UPDATE ' . $this->table . ' SET '
192        . $this->f_left . ' = ' . $node_left . ', '
193        . $this->f_right . ' = ' . $node_right
194        . ' WHERE ' . $this->f_id . ' = ' . $node_id
195        . $this->getCondition();
196
197        $this->con->begin();
198        try {
199            $this->con->execute($sql);
200            $this->con->commit();
201        } catch (Exception $e) {
202            $this->con->rollback();
203            throw $e;
204        }
205    }
206
207    public function deleteNode($node, $keep_children = true)
208    {
209        $node = (integer) $node;
210
211        $rs = $this->getChildren(0, $node);
212        if ($rs->isEmpty()) {
213            throw new Exception('Node does not exist.');
214        }
215        $node_left  = (integer) $rs->{$this->f_left};
216        $node_right = (integer) $rs->{$this->f_right};
217
218        try
219        {
220            $this->con->begin();
221
222            if ($keep_children) {
223                $this->con->execute('DELETE FROM ' . $this->table . ' WHERE ' . $this->f_id . ' = ' . $node);
224
225                $sql = 'UPDATE ' . $this->table . ' SET '
226                . $this->f_right . ' = CASE '
227                . 'WHEN ' . $this->f_right . ' BETWEEN ' . $node_left . ' AND ' . $node_right . ' '
228                . 'THEN ' . $this->f_right . ' - 1 '
229                . 'WHEN ' . $this->f_right . ' > ' . $node_right . ' '
230                . 'THEN ' . $this->f_right . ' - 2 '
231                . 'ELSE ' . $this->f_right . ' '
232                . 'END, '
233                . $this->f_left . ' = CASE '
234                . 'WHEN ' . $this->f_left . ' BETWEEN ' . $node_left . ' AND ' . $node_right . ' '
235                . 'THEN ' . $this->f_left . ' - 1 '
236                . 'WHEN ' . $this->f_left . ' > ' . $node_right . ' '
237                . 'THEN ' . $this->f_left . ' - 2 '
238                . 'ELSE ' . $this->f_left . ' '
239                . 'END '
240                . 'WHERE ' . $this->f_right . ' > ' . $node_left
241                . $this->getCondition();
242
243                $this->con->execute($sql);
244            } else {
245                $this->con->execute('DELETE FROM ' . $this->table . ' WHERE ' . $this->f_left . ' BETWEEN ' . $node_left . ' AND ' . $node_right);
246
247                $node_delta = $node_right - $node_left + 1;
248                $sql        = 'UPDATE ' . $this->table . ' SET '
249                . $this->f_left . ' = CASE '
250                . 'WHEN ' . $this->f_left . ' > ' . $node_left . ' '
251                . 'THEN ' . $this->f_left . ' - (' . $node_delta . ') '
252                . 'ELSE ' . $this->f_left . ' '
253                . 'END, '
254                . $this->f_right . ' = CASE '
255                . 'WHEN ' . $this->f_right . ' > ' . $node_left . ' '
256                . 'THEN ' . $this->f_right . ' - (' . $node_delta . ') '
257                . 'ELSE ' . $this->f_right . ' '
258                . 'END '
259                . 'WHERE ' . $this->f_right . ' > ' . $node_right
260                . $this->getCondition();
261            }
262
263            $this->con->commit();
264        } catch (Exception $e) {
265            $this->con->rollback();
266            throw $e;
267        }
268    }
269
270    public function resetOrder()
271    {
272        $rs = $this->con->select(
273            'SELECT ' . $this->f_id . ' '
274            . 'FROM ' . $this->table . ' '
275            . $this->getCondition('WHERE')
276            . 'ORDER BY ' . $this->f_left . ' ASC '
277        );
278
279        $lft = 2;
280        $this->con->begin();
281        try
282        {
283            while ($rs->fetch()) {
284                $this->con->execute(
285                    'UPDATE ' . $this->table . ' SET '
286                    . $this->f_left . ' = ' . ($lft++) . ', '
287                    . $this->f_right . ' = ' . ($lft++) . ' '
288                    . 'WHERE ' . $this->f_id . ' = ' . (integer) $rs->{$this->f_id} . ' '
289                    . $this->getCondition()
290                );
291            }
292            $this->con->commit();
293        } catch (Exception $e) {
294            $this->con->rollback();
295            throw $e;
296        }
297    }
298
299    public function setNodeParent($node, $target = 0)
300    {
301        if ($node == $target) {
302            return;
303        }
304        $node   = (integer) $node;
305        $target = (integer) $target;
306
307        $rs = $this->getChildren(0, $node);
308        if ($rs->isEmpty()) {
309            throw new Exception('Node does not exist.');
310        }
311        $node_left  = (integer) $rs->{$this->f_left};
312        $node_right = (integer) $rs->{$this->f_right};
313        $node_level = (integer) $rs->level;
314
315        if ($target > 0) {
316            $rs = $this->getChildren(0, $target);
317        } else {
318            $rs = $this->con->select(
319                'SELECT MIN(' . $this->f_left . ')-1 AS ' . $this->f_left . ', MAX(' . $this->f_right . ')+1 AS ' . $this->f_right . ', 0 AS level ' .
320                'FROM ' . $this->table . ' ' .
321                $this->getCondition('WHERE')
322            );
323        }
324        $target_left  = (integer) $rs->{$this->f_left};
325        $target_right = (integer) $rs->{$this->f_right};
326        $target_level = (integer) $rs->level;
327
328        if ($node_left == $target_left
329            || ($target_left >= $node_left && $target_left <= $node_right)
330            || ($node_level == $target_level + 1 && $node_left > $target_left && $node_right < $target_right)
331        ) {
332            throw new Exception('Cannot move tree');
333        }
334
335        if ($target_left < $node_left && $target_right > $node_right && $target_level < $node_level - 1) {
336            $sql = 'UPDATE ' . $this->table . ' SET '
337            . $this->f_right . ' = CASE '
338            . 'WHEN ' . $this->f_right . ' BETWEEN ' . ($node_right + 1) . ' AND ' . ($target_right - 1) . ' '
339            . 'THEN ' . $this->f_right . '-(' . ($node_right - $node_left + 1) . ') '
340            . 'WHEN ' . $this->f_left . ' BETWEEN ' . $node_left . ' AND ' . $node_right . ' '
341            . 'THEN ' . $this->f_right . '+' . ((($target_right - $node_right - $node_level + $target_level) / 2) * 2 + $node_level - $target_level - 1) . ' '
342            . 'ELSE '
343            . $this->f_right . ' '
344            . 'END, '
345            . $this->f_left . ' = CASE '
346            . 'WHEN ' . $this->f_left . ' BETWEEN ' . ($node_right + 1) . ' AND ' . ($target_right - 1) . ' '
347            . 'THEN ' . $this->f_left . '-(' . ($node_right - $node_left + 1) . ') '
348            . 'WHEN ' . $this->f_left . ' BETWEEN ' . $node_left . ' AND ' . $node_right . ' '
349            . 'THEN ' . $this->f_left . '+' . ((($target_right - $node_right - $node_level + $target_level) / 2) * 2 + $node_level - $target_level - 1) . ' '
350            . 'ELSE ' . $this->f_left . ' '
351            . 'END '
352            . 'WHERE ' . $this->f_left . ' BETWEEN ' . ($target_left + 1) . ' AND ' . ($target_right - 1) . '';
353        } elseif ($target_left < $node_left) {
354            $sql = 'UPDATE ' . $this->table . ' SET '
355            . $this->f_left . ' = CASE '
356            . 'WHEN ' . $this->f_left . ' BETWEEN ' . $target_right . ' AND ' . ($node_left - 1) . ' '
357            . 'THEN ' . $this->f_left . '+' . ($node_right - $node_left + 1) . ' '
358            . 'WHEN ' . $this->f_left . ' BETWEEN ' . $node_left . ' AND ' . $node_right . ' '
359            . 'THEN ' . $this->f_left . '-(' . ($node_left - $target_right) . ') '
360            . 'ELSE ' . $this->f_left . ' '
361            . 'END, '
362            . $this->f_right . ' = CASE '
363            . 'WHEN ' . $this->f_right . ' BETWEEN ' . $target_right . ' AND ' . $node_left . ' '
364            . 'THEN ' . $this->f_right . '+' . ($node_right - $node_left + 1) . ' '
365            . 'WHEN ' . $this->f_right . ' BETWEEN ' . $node_left . ' AND ' . $node_right . ' '
366            . 'THEN ' . $this->f_right . '-(' . ($node_left - $target_right) . ') '
367            . 'ELSE ' . $this->f_right . ' '
368            . 'END '
369            . 'WHERE (' . $this->f_left . ' BETWEEN ' . $target_left . ' AND ' . $node_right . ' '
370            . 'OR ' . $this->f_right . ' BETWEEN ' . $target_left . ' AND ' . $node_right . ')';
371        } else {
372            $sql = 'UPDATE ' . $this->table . ' SET '
373            . $this->f_left . ' = CASE '
374            . 'WHEN ' . $this->f_left . ' BETWEEN ' . $node_right . ' AND ' . $target_right . ' '
375            . 'THEN ' . $this->f_left . '-' . ($node_right - $node_left + 1) . ' '
376            . 'WHEN ' . $this->f_left . ' BETWEEN ' . $node_left . ' AND ' . $node_right . ' '
377            . 'THEN ' . $this->f_left . '+' . ($target_right - 1 - $node_right) . ' '
378            . 'ELSE ' . $this->f_left . ' '
379            . 'END, '
380            . $this->f_right . ' = CASE '
381            . 'WHEN ' . $this->f_right . ' BETWEEN ' . ($node_right + 1) . ' AND ' . ($target_right - 1) . ' '
382            . 'THEN ' . $this->f_right . '-' . ($node_right - $node_left + 1) . ' '
383            . 'WHEN ' . $this->f_right . ' BETWEEN ' . $node_left . ' AND ' . $node_right . ' '
384            . 'THEN ' . $this->f_right . '+' . ($target_right - 1 - $node_right) . ' '
385            . 'ELSE ' . $this->f_right . ' '
386            . 'END '
387            . 'WHERE (' . $this->f_left . ' BETWEEN ' . $node_left . ' AND ' . $target_right . ' '
388            . 'OR ' . $this->f_right . ' BETWEEN ' . $node_left . ' AND ' . $target_right . ')';
389        }
390
391        $sql .= ' ' . $this->getCondition();
392
393        $this->con->execute($sql);
394    }
395
396    public function setNodePosition($nodeA, $nodeB, $position = 'after')
397    {
398        $nodeA = (integer) $nodeA;
399        $nodeB = (integer) $nodeB;
400
401        $rs = $this->getChildren(0, $nodeA);
402        if ($rs->isEmpty()) {
403            throw new Exception('Node does not exist.');
404        }
405        $A_left  = $rs->{$this->f_left};
406        $A_right = $rs->{$this->f_right};
407        $A_level = $rs->level;
408
409        $rs = $this->getChildren(0, $nodeB);
410        if ($rs->isEmpty()) {
411            throw new Exception('Node does not exist.');
412        }
413        $B_left  = $rs->{$this->f_left};
414        $B_right = $rs->{$this->f_right};
415        $B_level = $rs->level;
416
417        if ($A_level != $B_level) {
418            throw new Exception('Cannot change position');
419        }
420
421        $rs      = $this->getParents($nodeA);
422        $parentA = $rs->isEmpty() ? 0 : $rs->{$this->f_id};
423        $rs      = $this->getParents($nodeB);
424        $parentB = $rs->isEmpty() ? 0 : $rs->{$this->f_id};
425
426        if ($parentA != $parentB) {
427            throw new Exception('Cannot change position');
428        }
429
430        if ($position == 'before') {
431            if ($A_left > $B_left) {
432                $sql = 'UPDATE ' . $this->table . ' SET '
433                . $this->f_right . ' = CASE WHEN ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $A_right . ' THEN ' . $this->f_right . ' - (' . ($A_left - $B_left) . ') '
434                . 'WHEN ' . $this->f_left . ' BETWEEN ' . $B_left . ' AND ' . ($A_left - 1) . ' THEN ' . $this->f_right . ' +  ' . ($A_right - $A_left + 1) . ' ELSE ' . $this->f_right . ' END, '
435                . $this->f_left . ' = CASE WHEN ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $A_right . ' THEN ' . $this->f_left . ' - (' . ($A_left - $B_left) . ') '
436                . 'WHEN ' . $this->f_left . ' BETWEEN ' . $B_left . ' AND ' . ($A_left - 1) . ' THEN ' . $this->f_left . ' + ' . ($A_right - $A_left + 1) . ' ELSE ' . $this->f_left . ' END '
437                . 'WHERE ' . $this->f_left . ' BETWEEN ' . $B_left . ' AND ' . $A_right;
438            } else {
439                $sql = 'UPDATE ' . $this->table . ' SET '
440                . $this->f_right . ' = CASE WHEN ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $A_right . ' THEN ' . $this->f_right . ' + ' . (($B_left - $A_left) - ($A_right - $A_left + 1)) . ' '
441                . 'WHEN ' . $this->f_left . ' BETWEEN ' . ($A_right + 1) . ' AND ' . ($B_left - 1) . ' THEN ' . $this->f_right . ' - (' . (($A_right - $A_left + 1)) . ') ELSE ' . $this->f_right . ' END, '
442                . $this->f_left . ' = CASE WHEN ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $A_right . ' THEN ' . $this->f_left . ' + ' . (($B_left - $A_left) - ($A_right - $A_left + 1)) . ' '
443                . 'WHEN ' . $this->f_left . ' BETWEEN ' . ($A_right + 1) . ' AND ' . ($B_left - 1) . ' THEN ' . $this->f_left . ' - (' . ($A_right - $A_left + 1) . ') ELSE ' . $this->f_left . ' END '
444                . 'WHERE ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . ($B_left - 1);
445            }
446        } else {
447            if ($A_left > $B_left) {
448                $sql = 'UPDATE ' . $this->table . ' SET '
449                . $this->f_right . ' = CASE WHEN ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $A_right . ' THEN ' . $this->f_right . ' - (' . ($A_left - $B_left - ($B_right - $B_left + 1)) . ') '
450                . 'WHEN ' . $this->f_left . ' BETWEEN ' . ($B_right + 1) . ' AND ' . ($A_left - 1) . ' THEN ' . $this->f_right . ' +  ' . ($A_right - $A_left + 1) . ' ELSE ' . $this->f_right . ' END, '
451                . $this->f_left . ' = CASE WHEN ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $A_right . ' THEN ' . $this->f_left . ' - (' . ($A_left - $B_left - ($B_right - $B_left + 1)) . ') '
452                . 'WHEN ' . $this->f_left . ' BETWEEN ' . ($B_right + 1) . ' AND ' . ($A_left - 1) . ' THEN ' . $this->f_left . ' + ' . ($A_right - $A_left + 1) . ' ELSE ' . $this->f_left . ' END '
453                . 'WHERE ' . $this->f_left . ' BETWEEN ' . ($B_right + 1) . ' AND ' . $A_right;
454            } else {
455                $sql = 'UPDATE ' . $this->table . ' SET '
456                . $this->f_right . ' = CASE WHEN ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $A_right . ' THEN ' . $this->f_right . ' + ' . ($B_right - $A_right) . ' '
457                . 'WHEN ' . $this->f_left . ' BETWEEN ' . ($A_right + 1) . ' AND ' . $B_right . ' THEN ' . $this->f_right . ' - (' . (($A_right - $A_left + 1)) . ') ELSE ' . $this->f_right . ' END, '
458                . $this->f_left . ' = CASE WHEN ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $A_right . ' THEN ' . $this->f_left . ' + ' . ($B_right - $A_right) . ' '
459                . 'WHEN ' . $this->f_left . ' BETWEEN ' . ($A_right + 1) . ' AND ' . $B_right . ' THEN ' . $this->f_left . ' - (' . ($A_right - $A_left + 1) . ') ELSE ' . $this->f_left . ' END '
460                . 'WHERE ' . $this->f_left . ' BETWEEN ' . $A_left . ' AND ' . $B_right;
461            }
462        }
463
464        $sql .= $this->getCondition();
465        $this->con->execute($sql);
466    }
467
468    protected function getCondition($start = 'AND', $prefix = '')
469    {
470        if (empty($this->add_condition)) {
471            return '';
472        }
473
474        $w = [];
475        foreach ($this->add_condition as $c => $n) {
476            $w[] = $prefix . $c . ' = ' . $n;
477        }
478        return ' ' . $start . ' ' . implode(' AND ', $w) . ' ';
479    }
480}
Note: See TracBrowser for help on using the repository browser.

Sites map