1 |
8 |
ahitrov@rambler.ru |
package Contenido::Section; |
2 |
|
|
|
3 |
|
|
# ---------------------------------------------------------------------------- |
4 |
|
|
# Класс Секция. |
5 |
|
|
# Теперь это опять же - базовый класс, вводим в него дополнительную |
6 |
|
|
# функциональность. |
7 |
|
|
# ---------------------------------------------------------------------------- |
8 |
|
|
|
9 |
|
|
use strict; |
10 |
|
|
use warnings; |
11 |
|
|
use locale; |
12 |
|
|
|
13 |
|
|
use vars qw($VERSION $ROOT); |
14 |
|
|
$VERSION = '6.0'; |
15 |
|
|
|
16 |
|
|
use base 'Contenido::Object'; |
17 |
|
|
use Contenido::Globals; |
18 |
|
|
|
19 |
|
|
$ROOT = 1; # Корневая секция |
20 |
|
|
|
21 |
|
|
sub class_name { |
22 |
|
|
return 'Секция'; |
23 |
|
|
} |
24 |
|
|
|
25 |
|
|
sub class_description { |
26 |
|
|
return 'Секция по умолчанию'; |
27 |
|
|
} |
28 |
|
|
|
29 |
|
|
# DEFAULT клас реализации таблицы |
30 |
|
|
sub class_table { |
31 |
|
|
return 'SQL::SectionTable'; |
32 |
|
|
} |
33 |
|
|
|
34 |
|
|
# ---------------------------------------------------------------------------- |
35 |
|
|
# Конструктор. Создает новый объект сессии. |
36 |
|
|
# |
37 |
|
|
# Формат использования: |
38 |
|
|
# Contenido::Section->new() |
39 |
|
|
# Contenido::Section->new($keeper) |
40 |
|
|
# Contenido::Section->new($keeper,$id) |
41 |
|
|
# Contenido::Section->new($keeper,$id,$pid) |
42 |
|
|
# ---------------------------------------------------------------------------- |
43 |
|
|
sub new { |
44 |
|
|
my ($proto, $keeper, $id, $pid) = @_; |
45 |
|
|
my $class = ref($proto) || $proto; |
46 |
|
|
my $self; |
47 |
|
|
|
48 |
|
|
if (defined($id) && ($id>0) && defined($keeper)) { |
49 |
|
|
$self=$keeper->get_section_by_id($id, class=>$class); |
50 |
|
|
} else { |
51 |
|
|
$self = {}; |
52 |
|
|
bless($self, $class); |
53 |
|
|
$self->init(); |
54 |
|
|
$self->keeper($keeper) if (defined($keeper)); |
55 |
|
|
$self->{class} = $class; |
56 |
|
|
$self->id($id) if (defined($id) && ($id > 0)); |
57 |
|
|
$self->pid($pid) if (defined($pid) && ($pid > 0)); |
58 |
|
|
} |
59 |
|
|
|
60 |
|
|
return $self; |
61 |
|
|
} |
62 |
|
|
|
63 |
|
|
sub _get_table { |
64 |
|
|
class_table()->new(); |
65 |
|
|
} |
66 |
|
|
|
67 |
607 |
ahitrov |
|
68 |
8 |
ahitrov@rambler.ru |
#доработка метода store |
69 |
|
|
sub store { |
70 |
608 |
ahitrov |
my $self = shift; |
71 |
|
|
my (%opts) = @_; |
72 |
8 |
ahitrov@rambler.ru |
|
73 |
|
|
#для новосозданных секций ставим новый sorder |
74 |
608 |
ahitrov |
if ( $self->{id} ) { |
75 |
|
|
my $without_sort = delete $opts{without_sort}; |
76 |
|
|
unless ( $without_sort || $self->{__light} ) { |
77 |
|
|
### Autofill or autoflush documents order if applicable |
78 |
|
|
if ( $self->_sorted ) { |
79 |
|
|
my $ids = $self->_get_document_order; |
80 |
|
|
$self->_sorted_order( join(',', @$ids) ); |
81 |
|
|
} elsif ( !$self->_sorted && $self->_sorted_order ) { |
82 |
|
|
$self->_sorted_order( undef ); |
83 |
|
|
} |
84 |
|
|
} |
85 |
|
|
} else { |
86 |
|
|
my ($sorder) = $self->keeper->SQL->selectrow_array("select max(sorder) from ".$self->class_table->db_table(), {}); |
87 |
|
|
$self->{sorder} = $sorder + 1; |
88 |
8 |
ahitrov@rambler.ru |
} |
89 |
|
|
|
90 |
|
|
return $self->SUPER::store(); |
91 |
|
|
} |
92 |
|
|
|
93 |
|
|
|
94 |
|
|
## Специальные свойства для секций с встроенной сортировкой документов |
95 |
|
|
sub add_properties { |
96 |
|
|
return ( |
97 |
616 |
ahitrov |
{ |
98 |
|
|
'attr' => '_page_size', |
99 |
|
|
'type' => 'integer', |
100 |
|
|
'rusname' => 'Количество документов, подгружаемых на страницу раздела в админке', |
101 |
|
|
'default' => 40, |
102 |
|
|
'hidden' => 1 |
103 |
|
|
}, |
104 |
|
|
{ # Признак "секция с сортировкой" |
105 |
8 |
ahitrov@rambler.ru |
'attr' => '_sorted', |
106 |
|
|
'type' => 'checkbox', |
107 |
|
|
'rusname' => 'Ручная сортировка документов', |
108 |
|
|
}, |
109 |
616 |
ahitrov |
{ # Порядок документов (список id) |
110 |
8 |
ahitrov@rambler.ru |
'attr' => '_sorted_order', |
111 |
|
|
'type' => 'string', |
112 |
|
|
'rusname' => 'Порядок документов в секции', |
113 |
|
|
'hidden' => 1 |
114 |
|
|
}, |
115 |
|
|
{ |
116 |
|
|
'attr' => 'default_document_class', |
117 |
|
|
'type' => 'string', |
118 |
|
|
'rusname' => 'Класс документов в секции, показываемый по умолчанию', |
119 |
|
|
}, |
120 |
|
|
{ |
121 |
|
|
'attr' => 'default_table_class', |
122 |
|
|
'type' => 'string', |
123 |
|
|
'rusname' => 'Класс таблицы, документы которой будут показаны по умолчанию', |
124 |
|
|
}, |
125 |
|
|
{ |
126 |
|
|
'attr' => 'order_by', |
127 |
|
|
'type' => 'string', |
128 |
|
|
'rusname' => 'Сортировка документов', |
129 |
|
|
}, |
130 |
|
|
{ |
131 |
118 |
ahitrov |
'attr' => 'no_count', |
132 |
|
|
'type' => 'checkbox', |
133 |
|
|
'rusname' => 'Не пересчитывать документы в разделе админки', |
134 |
|
|
}, |
135 |
|
|
{ |
136 |
8 |
ahitrov@rambler.ru |
'attr' => 'filters', |
137 |
|
|
'type' => 'struct', |
138 |
|
|
'rusname' => 'Дополнительные фильтры выборки', |
139 |
|
|
}, |
140 |
|
|
); |
141 |
|
|
} |
142 |
|
|
|
143 |
|
|
# Конструктор для создания корневной секции |
144 |
|
|
sub root { |
145 |
|
|
return new(shift, shift, $ROOT); |
146 |
|
|
} |
147 |
|
|
|
148 |
|
|
|
149 |
|
|
# Заглушка для метода parent |
150 |
|
|
sub parent { |
151 |
|
|
return shift->pid(@_); |
152 |
|
|
} |
153 |
|
|
|
154 |
|
|
# ---------------------------------------------------------------------------- |
155 |
|
|
# Удаление секции. Перед удалением происходит проверка - а имеем |
156 |
|
|
# ли мы право удалять (может есть детишки). Если есть детишки, то секция |
157 |
|
|
# не удаляется. Проверка на наличие объектов в этой секции не производится, |
158 |
|
|
# но секция вычитается из всех сообщений. |
159 |
|
|
# ---------------------------------------------------------------------------- |
160 |
|
|
sub delete |
161 |
|
|
{ |
162 |
|
|
my $self = shift; |
163 |
|
|
do { $log->error("Метод ->delete() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
164 |
|
|
do { $log->warning("Вызов метода ->delete() без указания идентификатора для удаления"); return undef } unless ($self->{id}); |
165 |
|
|
|
166 |
|
|
# Проверка наличия детей... |
167 |
|
|
my ($one_id) = $self->keeper->SQL->selectrow_array('select id from '.$self->class_table->db_table.' where pid = ?', {}, $self->id); |
168 |
|
|
if (defined($one_id) && ($one_id > 0)) { return "Нельзя удалить секцию, у которой есть вложенные секции\n"; }; |
169 |
|
|
|
170 |
|
|
$self->SUPER::delete(); |
171 |
|
|
|
172 |
|
|
return 1; |
173 |
|
|
} |
174 |
|
|
|
175 |
|
|
|
176 |
|
|
|
177 |
|
|
# ---------------------------------------------------------------------------- |
178 |
|
|
# Метод, возвращающий массив детишек (в порядке sorder для |
179 |
|
|
# каждого уровня). В параметре передается глубина. |
180 |
|
|
# |
181 |
|
|
# Формат вызова: |
182 |
|
|
# $section->childs([глубина]); |
183 |
|
|
# ---------------------------------------------------------------------------- |
184 |
|
|
sub childs { |
185 |
|
|
my ($self, $depth) = @_; |
186 |
|
|
do { $log->error("Метод ->childs() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
187 |
|
|
|
188 |
|
|
# Глубина по умолчанию - 1 |
189 |
|
|
$depth ||= 1; |
190 |
|
|
|
191 |
|
|
my $SIDS = []; |
192 |
|
|
my $NEW_SIDS = [$self->id]; |
193 |
|
|
|
194 |
|
|
#пока не достигнута нужная глубина и пока нашлись новые дети |
195 |
|
|
while ($depth>0) { |
196 |
|
|
$NEW_SIDS = $self->keeper->get_sections(s=>$NEW_SIDS, ids=>1, return_mode=>'array_ref', order_by=>'pid, sorder'); |
197 |
|
|
if (ref($NEW_SIDS) and @$NEW_SIDS) { |
198 |
|
|
push (@$SIDS, @$NEW_SIDS); |
199 |
|
|
} else { |
200 |
|
|
last; |
201 |
|
|
} |
202 |
|
|
$depth--; |
203 |
|
|
} |
204 |
|
|
return @$SIDS; |
205 |
|
|
} |
206 |
|
|
|
207 |
|
|
|
208 |
608 |
ahitrov |
sub _get_document_order { |
209 |
|
|
my ($self) = shift; |
210 |
609 |
ahitrov |
return unless $self->_sorted; |
211 |
8 |
ahitrov@rambler.ru |
|
212 |
608 |
ahitrov |
my @order = $self->_sorted_order ? split( /,/, $self->_sorted_order ) : (); |
213 |
|
|
|
214 |
|
|
my %opts; |
215 |
|
|
if ( $self->default_document_class ) { |
216 |
|
|
$opts{class} = $self->default_document_class; |
217 |
|
|
} elsif ( $self->default_table_class ) { |
218 |
|
|
$opts{table} = $self->default_table_class; |
219 |
|
|
} else { |
220 |
|
|
$opts{table} = 'Contenido::SQL::DocumentTable'; |
221 |
|
|
} |
222 |
|
|
if ( $self->order_by ) { |
223 |
|
|
$opts{order_by} = $self->order_by; |
224 |
|
|
} |
225 |
|
|
if ( $self->filters ) { |
226 |
|
|
no strict 'vars'; |
227 |
|
|
my $filters = eval($self->filters); |
228 |
|
|
if ($@) { |
229 |
|
|
warn "Bad filter: " . $self->filters . " in section " . $self->id; |
230 |
|
|
} elsif (ref $filters eq 'HASH') { |
231 |
|
|
while ( my ($key, $val) = each %$filters ) { |
232 |
|
|
$opts{$key} = $val; |
233 |
|
|
} |
234 |
|
|
} |
235 |
|
|
} |
236 |
|
|
my $ids = $keeper->get_documents( s => $self->id, %opts, ids => 1, return_mode => 'array_ref' ); |
237 |
|
|
my %ids = map { $_ => 1 } @$ids; |
238 |
|
|
my @new_order; |
239 |
|
|
foreach my $iid ( @order ) { |
240 |
|
|
if ( exists $ids{$iid} ) { |
241 |
|
|
push @new_order, $iid; |
242 |
|
|
delete $ids{$iid}; |
243 |
|
|
} |
244 |
|
|
} |
245 |
|
|
foreach my $iid ( @$ids ) { |
246 |
|
|
if ( exists $ids{$iid} ) { |
247 |
|
|
push @new_order, $iid; |
248 |
|
|
delete $ids{$iid}; |
249 |
|
|
} |
250 |
|
|
} |
251 |
|
|
return \@new_order; |
252 |
|
|
} |
253 |
|
|
|
254 |
|
|
|
255 |
609 |
ahitrov |
sub _get_document_pos { |
256 |
|
|
my ($self) = shift; |
257 |
|
|
my ($doc_id) = shift; |
258 |
|
|
return unless $doc_id && $doc_id =~ /^\d+$/; |
259 |
|
|
return unless $self->_sorted; |
260 |
|
|
|
261 |
|
|
my @order = $self->_sorted_order ? split( /,/, $self->_sorted_order ) : (); |
262 |
|
|
my %pos; |
263 |
|
|
for ( my $i = 0; $i < scalar @order; $i++ ) { |
264 |
|
|
if ( $order[$i] == $doc_id ) { |
265 |
|
|
$pos{index} = $i; |
266 |
|
|
if ( $i > 0 ) { |
267 |
|
|
$pos{after} = $order[$i-1]; |
268 |
|
|
} |
269 |
|
|
if ( $i < $#order ) { |
270 |
|
|
$pos{before} = $order[$i+1]; |
271 |
|
|
} |
272 |
|
|
if ( $i == $#order ) { |
273 |
|
|
$pos{last} = 1; |
274 |
|
|
} elsif ( $i == 0 ) { |
275 |
|
|
$pos{first} = 1; |
276 |
|
|
} |
277 |
|
|
last; |
278 |
|
|
} |
279 |
|
|
} |
280 |
|
|
return (exists $pos{index} ? \%pos : undef); |
281 |
|
|
} |
282 |
|
|
|
283 |
|
|
|
284 |
8 |
ahitrov@rambler.ru |
# ---------------------------------------------------------------------------- |
285 |
|
|
# Метод для перемещение секции вверх/вниз по рубрикатору (изменение |
286 |
|
|
# sorder)... |
287 |
|
|
# |
288 |
|
|
# Формат вызова: |
289 |
|
|
# $section->move($direction); Направление задается строкой 'up'/'down' |
290 |
|
|
# ---------------------------------------------------------------------------- |
291 |
|
|
sub move { |
292 |
|
|
my ($self, $direction) = @_; |
293 |
|
|
do { $log->error("Метод ->move() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
294 |
|
|
|
295 |
|
|
return undef if ($self->keeper->state->readonly()); |
296 |
|
|
|
297 |
|
|
my $keeper = $self->keeper; |
298 |
|
|
do { $log->error("В объекте секции не определена ссылка на базу данных"); die } unless ref($keeper); |
299 |
|
|
do { $log->warning("Вызов метода ->move() без указания идентификатора секции"); return undef } |
300 |
|
|
unless (exists($self->{id}) && ($self->{id} > 0)); |
301 |
|
|
do { $log->warning("Вызов метода ->move() без указания порядка сортировки (sorder)"); return undef } |
302 |
|
|
unless (exists($self->{sorder}) && ($self->{sorder} >= 0)); |
303 |
|
|
do { $log->warning("Вызов метода ->childs() без указания родителя"); return undef } unless (exists($self->{pid}) && ($self->{pid} >= 0)); |
304 |
|
|
|
305 |
|
|
$direction = lc($direction); |
306 |
|
|
if ( ($direction ne 'up') && ($direction ne 'down') ) { $log->warning("Направление перемещения секции задано неверено"); return undef }; |
307 |
|
|
|
308 |
|
|
|
309 |
|
|
$keeper->t_connect() || do { $keeper->error(); return undef; }; |
310 |
|
|
$keeper->TSQL->begin_work(); |
311 |
|
|
|
312 |
|
|
|
313 |
|
|
# Получение соседней секции для обмена... |
314 |
|
|
my ($id_, $sorder_); |
315 |
|
|
if ($direction eq 'up') |
316 |
|
|
{ |
317 |
|
|
($id_, $sorder_) = $keeper->TSQL->selectrow_array("select id, sorder from ".$self->class_table->db_table." where sorder < ? and pid = ? order by sorder desc limit 1", {}, $self->{sorder}, $self->{pid}); |
318 |
|
|
} else { |
319 |
|
|
($id_, $sorder_) = $keeper->TSQL->selectrow_array("select id, sorder from ".$self->class_table->db_table." where sorder > ? and pid = ? order by sorder asc limit 1", {}, $self->{sorder}, $self->{pid}); |
320 |
|
|
} |
321 |
|
|
|
322 |
|
|
|
323 |
|
|
# Собственно обмен... |
324 |
|
|
if ( defined($id_) && ($id_ > 0) && defined($sorder_) && ($sorder_ > 0) ) |
325 |
|
|
{ |
326 |
|
|
$keeper->TSQL->do("update ".$self->class_table->db_table." set sorder = ? where id = ?", {}, $sorder_, $self->{id}) |
327 |
|
|
|| return $keeper->t_abort(); |
328 |
|
|
$keeper->TSQL->do("update ".$self->class_table->db_table." set sorder = ? where id = ?", {}, $self->{sorder}, $id_) |
329 |
|
|
|| return $keeper->t_abort(); |
330 |
|
|
} else { |
331 |
|
|
$log->warning("Не могу поменяться с элементом (он неверно оформлен или его нет)"); return 2; |
332 |
|
|
} |
333 |
|
|
|
334 |
|
|
$keeper->t_finish(); |
335 |
|
|
$self->{sorder} = $sorder_; |
336 |
|
|
return 1; |
337 |
|
|
} |
338 |
|
|
|
339 |
|
|
|
340 |
|
|
|
341 |
|
|
# ---------------------------------------------------------------------------- |
342 |
|
|
# Метод для перемещения документа с id = $doc_id вверх/вниз |
343 |
|
|
# по порядку сортировки (в пределах текущей секции)... |
344 |
|
|
# |
345 |
|
|
# Формат вызова: |
346 |
|
|
# $doc->dmove($doc_id, $direction); Направление задается строкой 'up'/'down' |
347 |
|
|
# ---------------------------------------------------------------------------- |
348 |
|
|
sub dmove { |
349 |
608 |
ahitrov |
my ($self, $doc_id, $direction, $anchor) = @_; |
350 |
8 |
ahitrov@rambler.ru |
do { $log->error("Метод ->dmove() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
351 |
|
|
|
352 |
|
|
return undef if ($self->keeper->state->readonly()); |
353 |
|
|
|
354 |
|
|
my $keeper = $self->keeper; |
355 |
|
|
do { $log->error("В объекте не определена ссылка на базу данных"); die } unless ref($keeper); |
356 |
|
|
do { $log->warning("Вызов метода ->dmove() без указания идентификатора секции"); return undef } |
357 |
|
|
unless (exists($self->{id}) && ($self->{id} > 0)); |
358 |
|
|
|
359 |
|
|
$direction = lc($direction); |
360 |
608 |
ahitrov |
unless ( $direction eq 'up' || $direction eq 'down' || $direction eq 'first' || $direction eq 'last' || $direction eq 'before' || $direction eq 'after' ) { |
361 |
|
|
$log->warning("Направление перемещения документа задано неверно"); return undef |
362 |
|
|
}; |
363 |
|
|
my $anchor_flag = 0; |
364 |
|
|
if ( ($direction eq 'before' || $direction eq 'after') && !$anchor ) { |
365 |
|
|
$log->warning("Неверный вызов функции dmove для направления '$direction'. Необходимо указать дополнительно id в списке документов"); return undef; |
366 |
|
|
} elsif ( $direction eq 'before' || $direction eq 'after' ) { |
367 |
|
|
$anchor_flag = 1; |
368 |
|
|
} |
369 |
8 |
ahitrov@rambler.ru |
|
370 |
|
|
if ($self->_sorted()) { |
371 |
608 |
ahitrov |
my $order = $self->_get_document_order; |
372 |
|
|
my @new_order; |
373 |
|
|
if ( $direction eq 'first' ) { |
374 |
|
|
push @new_order, $doc_id; |
375 |
|
|
} |
376 |
|
|
for ( my $i = 0; $i < scalar @$order; $i++ ) { |
377 |
|
|
if ( ($direction eq 'first' || $direction eq 'last' || $direction eq 'after' || $direction eq 'before') && $order->[$i] == $doc_id ) { |
378 |
|
|
next; |
379 |
|
|
} elsif ( $direction eq 'up' && $order->[$i] == $doc_id ) { |
380 |
|
|
if ( $i ) { |
381 |
|
|
my $id = pop @new_order; |
382 |
|
|
push @new_order, ($doc_id, $id); |
383 |
|
|
} else { |
384 |
|
|
push @new_order, $doc_id; |
385 |
|
|
} |
386 |
|
|
} elsif ( $direction eq 'down' && $order->[$i] == $doc_id ) { |
387 |
|
|
if ( $i < scalar(@$order) - 1 ) { |
388 |
|
|
push @new_order, ($order->[++$i], $doc_id); |
389 |
|
|
} else { |
390 |
|
|
push @new_order, $doc_id; |
391 |
|
|
} |
392 |
|
|
} elsif ( $direction eq 'before' && $order->[$i] == $anchor ) { |
393 |
|
|
push @new_order, ($doc_id, $order->[$i]); |
394 |
|
|
$anchor_flag = 0; |
395 |
|
|
} elsif ( $direction eq 'after' && $order->[$i] == $anchor ) { |
396 |
|
|
push @new_order, ($order->[$i], $doc_id); |
397 |
|
|
$anchor_flag = 0; |
398 |
|
|
} else { |
399 |
|
|
push @new_order, $order->[$i]; |
400 |
|
|
} |
401 |
|
|
} |
402 |
|
|
if ( $anchor_flag ) { |
403 |
|
|
$log->warning("Неверный вызов функции dmove для направления '$direction'. Не найден якорь [$anchor]"); return undef; |
404 |
|
|
} |
405 |
|
|
if ( $direction eq 'last' ) { |
406 |
|
|
push @new_order, $doc_id; |
407 |
|
|
} |
408 |
8 |
ahitrov@rambler.ru |
|
409 |
608 |
ahitrov |
$self->{_sorted_order} = join ',', @new_order; |
410 |
|
|
$self->store( without_sort => 1 ); |
411 |
8 |
ahitrov@rambler.ru |
} else { |
412 |
608 |
ahitrov |
$log->warning("dmove called for section without enabled sorted feature... ".$self->id."/".$self->class); |
413 |
8 |
ahitrov@rambler.ru |
} |
414 |
|
|
|
415 |
|
|
return 1; |
416 |
|
|
} |
417 |
|
|
|
418 |
|
|
|
419 |
|
|
|
420 |
|
|
|
421 |
|
|
# ---------------------------------------------------------------------------- |
422 |
|
|
# Метод для построения пути между двумя рубриками... Возвращает |
423 |
|
|
# массив идентификаторов секций от рубрики секции до $root_id снизу |
424 |
|
|
# вверх. $root_id обязательно должен быть выше по рубрикатору. В результат |
425 |
|
|
# включаются обе стороны. Если рубрики равны, то возвращается массив из |
426 |
|
|
# одного элемента, если пути нет - то пустой массив. |
427 |
|
|
# |
428 |
|
|
# Формат вызова: |
429 |
|
|
# $section->trace($root_id) |
430 |
|
|
# ---------------------------------------------------------------------------- |
431 |
|
|
sub trace { |
432 |
|
|
my ($self, $root_id) = @_; |
433 |
|
|
do { $log->error("Метод ->trace() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
434 |
|
|
|
435 |
|
|
do { $log->warning("Вызов метода ->trace() без указания идентификатора секции"); return () } |
436 |
|
|
unless (exists($self->{id}) && ($self->{id} > 0)); |
437 |
|
|
$root_id ||= $ROOT; |
438 |
|
|
|
439 |
|
|
my $id_ = $self->{id}; |
440 |
|
|
my @SIDS = ($id_); |
441 |
|
|
my $sth = $self->keeper->SQL->prepare_cached("select pid from ".$self->class_table->db_table." where id = ?"); |
442 |
|
|
|
443 |
|
|
while ($id_ != $root_id) |
444 |
|
|
{ |
445 |
|
|
$sth->execute($id_); |
446 |
|
|
($id_) = $sth->fetchrow_array(); |
447 |
|
|
if (defined($id_) && ($id_ > 0)) |
448 |
|
|
{ |
449 |
|
|
unshift (@SIDS, $id_); |
450 |
|
|
} else { |
451 |
|
|
# Мы закочили путешествие вверх по рубрикам, а до корня не дошли... |
452 |
|
|
$sth->finish; |
453 |
|
|
return (); |
454 |
|
|
} |
455 |
|
|
} |
456 |
|
|
$sth->finish; |
457 |
|
|
return @SIDS; |
458 |
|
|
} |
459 |
|
|
|
460 |
|
|
|
461 |
|
|
# ---------------------------------------------------------------------------- |
462 |
|
|
# Предки |
463 |
|
|
# Возвращает массив идентификаторов всех предков (родителей на всех уровнях) данной секции |
464 |
|
|
# ---------------------------------------------------------------------------- |
465 |
|
|
sub ancestors |
466 |
|
|
{ |
467 |
|
|
my $self = shift; |
468 |
|
|
do { $log->error("Метод ->ancestors() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
469 |
|
|
|
470 |
|
|
my $keeper = $self->keeper; |
471 |
|
|
do { $log->error("В объекте секции не определена ссылка на базу данных"); die } unless ref($keeper); |
472 |
|
|
|
473 |
|
|
do { $log->warning("Вызов метода ->ancestors() без указания идентификатора секции"); return () } unless (exists($self->{id}) && ($self->{id} > 0)); |
474 |
|
|
|
475 |
|
|
my @ancestors = (); |
476 |
|
|
my $sectionid = $self->{id}; |
477 |
|
|
while ($sectionid) |
478 |
|
|
{ |
479 |
|
|
$sectionid = $keeper->SQL->selectrow_array("select pid from ".$self->class_table->db_table." where id = ?", {}, $sectionid); |
480 |
|
|
push @ancestors, $sectionid if defined $sectionid && $sectionid; |
481 |
|
|
} |
482 |
|
|
return @ancestors; |
483 |
|
|
} |
484 |
|
|
|
485 |
|
|
# ---------------------------------------------------------------------------- |
486 |
|
|
# Потомки |
487 |
|
|
# Возвращает массив идентификаторов всех потомков (детей на всех уровнях) данной секции |
488 |
|
|
# ---------------------------------------------------------------------------- |
489 |
|
|
sub descendants |
490 |
|
|
{ |
491 |
|
|
my $self = shift; |
492 |
|
|
do { $log->error("Метод ->descendants() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
493 |
|
|
|
494 |
|
|
my $keeper = $self->keeper; |
495 |
|
|
do { $log->error("В объекте секции не определена ссылка на базу данных"); die } unless ref($keeper); |
496 |
|
|
|
497 |
|
|
do { $log->warning("Вызов метода ->descendants() без указания идентификатора секции"); return () } unless (exists($self->{id}) && ($self->{id} > 0)); |
498 |
|
|
|
499 |
|
|
my @descendants = (); |
500 |
|
|
my @ids = ($self->{id}); |
501 |
|
|
while (scalar @ids) |
502 |
|
|
{ |
503 |
|
|
my $sth = $keeper->SQL->prepare("select id from ".$self->class_table->db_table." where pid in (" . (join ", ", @ids) . ")"); |
504 |
|
|
$sth->execute; |
505 |
|
|
@ids = (); |
506 |
|
|
while (my ($id) = $sth->fetchrow_array) |
507 |
|
|
{ |
508 |
|
|
push @ids, $id; |
509 |
|
|
} |
510 |
|
|
$sth->finish(); |
511 |
|
|
push @descendants, @ids; |
512 |
|
|
last if !$sth->rows; |
513 |
|
|
} |
514 |
|
|
return @descendants; |
515 |
|
|
} |
516 |
|
|
|
517 |
|
|
# ------------------------------------------------------------------------------------------------- |
518 |
|
|
# Получение деревца... |
519 |
|
|
# Параметры: |
520 |
|
|
# light => облегченная версия |
521 |
|
|
# root => корень дерева (по умолчанию - 1) |
522 |
|
|
# ------------------------------------------------------------------------------------------------- |
523 |
|
|
sub get_tree { |
524 |
|
|
my ($self, %opts) = @_; |
525 |
|
|
do { $log->warning("Метод ->get_tree() можно вызывать только у объектов, но не классов"); return undef } unless ref($self); |
526 |
|
|
|
527 |
|
|
my $root = $opts{root} || $ROOT; |
528 |
|
|
|
529 |
|
|
|
530 |
|
|
# ---------------------------------------------------------------------------------------- |
531 |
|
|
# Выбираем все секции |
532 |
|
|
$opts{no_limit} = 1; |
533 |
|
|
delete $opts{root}; |
534 |
|
|
my @sections = $self->keeper->get_sections(%opts); |
535 |
|
|
|
536 |
|
|
my $CACHE = {}; |
537 |
|
|
foreach my $section (@sections) { |
538 |
|
|
if (ref($section)) { |
539 |
|
|
$CACHE->{$section->id()} = $section; |
540 |
|
|
} |
541 |
|
|
} |
542 |
|
|
|
543 |
|
|
for my $id (sort { $CACHE->{$a}->sorder() <=> $CACHE->{$b}->sorder() } (keys(%{ $CACHE }))) { |
544 |
|
|
my $pid = $CACHE->{$id}->pid() || ''; |
545 |
|
|
$CACHE->{$pid}->{childs} = [] if (! exists($CACHE->{$pid}->{childs})); |
546 |
|
|
$CACHE->{$id}->{parent} = $CACHE->{$pid}; |
547 |
|
|
push (@{ $CACHE->{$pid}->{childs} }, $CACHE->{$id} ); |
548 |
|
|
} |
549 |
|
|
|
550 |
|
|
return $CACHE->{$root}; |
551 |
|
|
} |
552 |
|
|
|
553 |
|
|
1; |
554 |
|
|
|