Dotclear

source: inc/core/class.dc.categories.php @ 2707:f4749a8fc3ce

Revision 2707:f4749a8fc3ce, 16.8 KB checked in by Dsls, 11 years ago (diff)

Strenghened categories ordering, thx Egidio Romano

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

Sites map