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 |
|
|
#доработка метода store |
68 |
|
|
sub store { |
69 |
|
|
my $self=shift; |
70 |
|
|
|
71 |
|
|
#для новосозданных секций ставим новый sorder |
72 |
|
|
unless ($self->{id}) { |
73 |
|
|
my ($sorder) = $self->keeper->SQL->selectrow_array("select max(sorder) from ".$self->class_table->db_table(), {}); |
74 |
|
|
$self->{sorder} = $sorder + 1; |
75 |
|
|
} |
76 |
|
|
|
77 |
|
|
return $self->SUPER::store(); |
78 |
|
|
} |
79 |
|
|
|
80 |
|
|
|
81 |
|
|
## Специальные свойства для секций с встроенной сортировкой документов |
82 |
|
|
sub add_properties { |
83 |
|
|
return ( |
84 |
|
|
{ # Признак "секция с сортировкой" |
85 |
|
|
'attr' => '_sorted', |
86 |
|
|
'type' => 'checkbox', |
87 |
|
|
'rusname' => 'Ручная сортировка документов', |
88 |
|
|
}, |
89 |
|
|
{ # Порядок документов (список id) |
90 |
|
|
'attr' => '_sorted_order', |
91 |
|
|
'type' => 'string', |
92 |
|
|
'rusname' => 'Порядок документов в секции', |
93 |
|
|
'hidden' => 1 |
94 |
|
|
}, |
95 |
|
|
{ |
96 |
|
|
'attr' => 'default_document_class', |
97 |
|
|
'type' => 'string', |
98 |
|
|
'rusname' => 'Класс документов в секции, показываемый по умолчанию', |
99 |
|
|
}, |
100 |
|
|
{ |
101 |
|
|
'attr' => 'default_table_class', |
102 |
|
|
'type' => 'string', |
103 |
|
|
'rusname' => 'Класс таблицы, документы которой будут показаны по умолчанию', |
104 |
|
|
}, |
105 |
|
|
{ |
106 |
|
|
'attr' => 'order_by', |
107 |
|
|
'type' => 'string', |
108 |
|
|
'rusname' => 'Сортировка документов', |
109 |
|
|
}, |
110 |
|
|
{ |
111 |
|
|
'attr' => 'filters', |
112 |
|
|
'type' => 'struct', |
113 |
|
|
'rusname' => 'Дополнительные фильтры выборки', |
114 |
|
|
}, |
115 |
|
|
); |
116 |
|
|
} |
117 |
|
|
|
118 |
|
|
# Конструктор для создания корневной секции |
119 |
|
|
sub root { |
120 |
|
|
return new(shift, shift, $ROOT); |
121 |
|
|
} |
122 |
|
|
|
123 |
|
|
|
124 |
|
|
# Заглушка для метода parent |
125 |
|
|
sub parent { |
126 |
|
|
return shift->pid(@_); |
127 |
|
|
} |
128 |
|
|
|
129 |
|
|
# ---------------------------------------------------------------------------- |
130 |
|
|
# Удаление секции. Перед удалением происходит проверка - а имеем |
131 |
|
|
# ли мы право удалять (может есть детишки). Если есть детишки, то секция |
132 |
|
|
# не удаляется. Проверка на наличие объектов в этой секции не производится, |
133 |
|
|
# но секция вычитается из всех сообщений. |
134 |
|
|
# ---------------------------------------------------------------------------- |
135 |
|
|
sub delete |
136 |
|
|
{ |
137 |
|
|
my $self = shift; |
138 |
|
|
do { $log->error("Метод ->delete() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
139 |
|
|
do { $log->warning("Вызов метода ->delete() без указания идентификатора для удаления"); return undef } unless ($self->{id}); |
140 |
|
|
|
141 |
|
|
# Проверка наличия детей... |
142 |
|
|
my ($one_id) = $self->keeper->SQL->selectrow_array('select id from '.$self->class_table->db_table.' where pid = ?', {}, $self->id); |
143 |
|
|
if (defined($one_id) && ($one_id > 0)) { return "Нельзя удалить секцию, у которой есть вложенные секции\n"; }; |
144 |
|
|
|
145 |
|
|
$self->SUPER::delete(); |
146 |
|
|
|
147 |
|
|
return 1; |
148 |
|
|
} |
149 |
|
|
|
150 |
|
|
|
151 |
|
|
|
152 |
|
|
# ---------------------------------------------------------------------------- |
153 |
|
|
# Метод, возвращающий массив детишек (в порядке sorder для |
154 |
|
|
# каждого уровня). В параметре передается глубина. |
155 |
|
|
# |
156 |
|
|
# Формат вызова: |
157 |
|
|
# $section->childs([глубина]); |
158 |
|
|
# ---------------------------------------------------------------------------- |
159 |
|
|
sub childs { |
160 |
|
|
my ($self, $depth) = @_; |
161 |
|
|
do { $log->error("Метод ->childs() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
162 |
|
|
|
163 |
|
|
# Глубина по умолчанию - 1 |
164 |
|
|
$depth ||= 1; |
165 |
|
|
|
166 |
|
|
my $SIDS = []; |
167 |
|
|
my $NEW_SIDS = [$self->id]; |
168 |
|
|
|
169 |
|
|
#пока не достигнута нужная глубина и пока нашлись новые дети |
170 |
|
|
while ($depth>0) { |
171 |
|
|
$NEW_SIDS = $self->keeper->get_sections(s=>$NEW_SIDS, ids=>1, return_mode=>'array_ref', order_by=>'pid, sorder'); |
172 |
|
|
if (ref($NEW_SIDS) and @$NEW_SIDS) { |
173 |
|
|
push (@$SIDS, @$NEW_SIDS); |
174 |
|
|
} else { |
175 |
|
|
last; |
176 |
|
|
} |
177 |
|
|
$depth--; |
178 |
|
|
} |
179 |
|
|
return @$SIDS; |
180 |
|
|
} |
181 |
|
|
|
182 |
|
|
|
183 |
|
|
|
184 |
|
|
# ---------------------------------------------------------------------------- |
185 |
|
|
# Метод для перемещение секции вверх/вниз по рубрикатору (изменение |
186 |
|
|
# sorder)... |
187 |
|
|
# |
188 |
|
|
# Формат вызова: |
189 |
|
|
# $section->move($direction); Направление задается строкой 'up'/'down' |
190 |
|
|
# ---------------------------------------------------------------------------- |
191 |
|
|
sub move { |
192 |
|
|
my ($self, $direction) = @_; |
193 |
|
|
do { $log->error("Метод ->move() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
194 |
|
|
|
195 |
|
|
return undef if ($self->keeper->state->readonly()); |
196 |
|
|
|
197 |
|
|
my $keeper = $self->keeper; |
198 |
|
|
do { $log->error("В объекте секции не определена ссылка на базу данных"); die } unless ref($keeper); |
199 |
|
|
do { $log->warning("Вызов метода ->move() без указания идентификатора секции"); return undef } |
200 |
|
|
unless (exists($self->{id}) && ($self->{id} > 0)); |
201 |
|
|
do { $log->warning("Вызов метода ->move() без указания порядка сортировки (sorder)"); return undef } |
202 |
|
|
unless (exists($self->{sorder}) && ($self->{sorder} >= 0)); |
203 |
|
|
do { $log->warning("Вызов метода ->childs() без указания родителя"); return undef } unless (exists($self->{pid}) && ($self->{pid} >= 0)); |
204 |
|
|
|
205 |
|
|
$direction = lc($direction); |
206 |
|
|
if ( ($direction ne 'up') && ($direction ne 'down') ) { $log->warning("Направление перемещения секции задано неверено"); return undef }; |
207 |
|
|
|
208 |
|
|
|
209 |
|
|
$keeper->t_connect() || do { $keeper->error(); return undef; }; |
210 |
|
|
$keeper->TSQL->begin_work(); |
211 |
|
|
|
212 |
|
|
|
213 |
|
|
# Получение соседней секции для обмена... |
214 |
|
|
my ($id_, $sorder_); |
215 |
|
|
if ($direction eq 'up') |
216 |
|
|
{ |
217 |
|
|
($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}); |
218 |
|
|
} else { |
219 |
|
|
($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}); |
220 |
|
|
} |
221 |
|
|
|
222 |
|
|
|
223 |
|
|
# Собственно обмен... |
224 |
|
|
if ( defined($id_) && ($id_ > 0) && defined($sorder_) && ($sorder_ > 0) ) |
225 |
|
|
{ |
226 |
|
|
$keeper->TSQL->do("update ".$self->class_table->db_table." set sorder = ? where id = ?", {}, $sorder_, $self->{id}) |
227 |
|
|
|| return $keeper->t_abort(); |
228 |
|
|
$keeper->TSQL->do("update ".$self->class_table->db_table." set sorder = ? where id = ?", {}, $self->{sorder}, $id_) |
229 |
|
|
|| return $keeper->t_abort(); |
230 |
|
|
} else { |
231 |
|
|
$log->warning("Не могу поменяться с элементом (он неверно оформлен или его нет)"); return 2; |
232 |
|
|
} |
233 |
|
|
|
234 |
|
|
$keeper->t_finish(); |
235 |
|
|
$self->{sorder} = $sorder_; |
236 |
|
|
return 1; |
237 |
|
|
} |
238 |
|
|
|
239 |
|
|
|
240 |
|
|
|
241 |
|
|
# ---------------------------------------------------------------------------- |
242 |
|
|
# Метод для перемещения документа с id = $doc_id вверх/вниз |
243 |
|
|
# по порядку сортировки (в пределах текущей секции)... |
244 |
|
|
# |
245 |
|
|
# Формат вызова: |
246 |
|
|
# $doc->dmove($doc_id, $direction); Направление задается строкой 'up'/'down' |
247 |
|
|
# ---------------------------------------------------------------------------- |
248 |
|
|
sub dmove { |
249 |
|
|
my ($self, $doc_id, $direction) = @_; |
250 |
|
|
do { $log->error("Метод ->dmove() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
251 |
|
|
|
252 |
|
|
return undef if ($self->keeper->state->readonly()); |
253 |
|
|
|
254 |
|
|
my $keeper = $self->keeper; |
255 |
|
|
do { $log->error("В объекте не определена ссылка на базу данных"); die } unless ref($keeper); |
256 |
|
|
do { $log->warning("Вызов метода ->dmove() без указания идентификатора секции"); return undef } |
257 |
|
|
unless (exists($self->{id}) && ($self->{id} > 0)); |
258 |
|
|
|
259 |
|
|
$direction = lc($direction); |
260 |
|
|
if ( ($direction ne 'up') && ($direction ne 'down') ) { $log->warning("Направление перемещения документа задано неверно"); return undef }; |
261 |
|
|
|
262 |
|
|
my $sorder_; |
263 |
|
|
if ($self->_sorted()) { |
264 |
|
|
my @ids = $keeper->get_documents( ids =>1, s => $self->id(), ($self->default_document_class ? (class => $self->default_document_class) : ()), order => ['date', undef], light => 1); |
265 |
|
|
my %ids = map { $_ => 1 } @ids; |
266 |
|
|
unless ($self->{_sorted_order}) { |
267 |
|
|
$self->{_sorted_order} = join ',', @ids; |
268 |
|
|
} |
269 |
|
|
|
270 |
|
|
my @order = split(/,/, $self->{_sorted_order}); |
271 |
|
|
@order = grep { |
272 |
|
|
my $res; |
273 |
|
|
if (exists $ids{$_}) { |
274 |
|
|
$res = 1; |
275 |
|
|
delete $ids{$_}; |
276 |
|
|
} |
277 |
|
|
$res |
278 |
|
|
} @order; |
279 |
|
|
|
280 |
|
|
push @order, keys %ids; |
281 |
|
|
|
282 |
|
|
foreach my $i (0 .. $#order) { |
283 |
|
|
if ($order[$i] == $doc_id) { |
284 |
|
|
my $t; |
285 |
|
|
if ($direction eq 'up') { |
286 |
|
|
last if $i == 0; |
287 |
|
|
$t = $order[$i-1]; |
288 |
|
|
$order[$i-1] = $order[$i]; |
289 |
|
|
$order[$i] = $t; |
290 |
|
|
$sorder_ = $i - 1; |
291 |
|
|
last; |
292 |
|
|
} elsif ($direction eq 'down') { |
293 |
|
|
last if $i == $#order; |
294 |
|
|
$t = $order[$i+1]; |
295 |
|
|
$order[$i+1] = $order[$i]; |
296 |
|
|
$order[$i] = $t; |
297 |
|
|
$sorder_ = $i + 1; |
298 |
|
|
last; |
299 |
|
|
} |
300 |
|
|
} |
301 |
|
|
} |
302 |
|
|
|
303 |
|
|
$self->{_sorted_order} = join ',', @order; |
304 |
|
|
$self->store(); |
305 |
|
|
} else { |
306 |
|
|
$log->warning("dmove called for section without enabled sorted feature... $self->{id}/$self->{class}"); |
307 |
|
|
} |
308 |
|
|
|
309 |
|
|
$self->{sorder} = $sorder_; |
310 |
|
|
return 1; |
311 |
|
|
} |
312 |
|
|
|
313 |
|
|
|
314 |
|
|
|
315 |
|
|
|
316 |
|
|
# ---------------------------------------------------------------------------- |
317 |
|
|
# Метод для построения пути между двумя рубриками... Возвращает |
318 |
|
|
# массив идентификаторов секций от рубрики секции до $root_id снизу |
319 |
|
|
# вверх. $root_id обязательно должен быть выше по рубрикатору. В результат |
320 |
|
|
# включаются обе стороны. Если рубрики равны, то возвращается массив из |
321 |
|
|
# одного элемента, если пути нет - то пустой массив. |
322 |
|
|
# |
323 |
|
|
# Формат вызова: |
324 |
|
|
# $section->trace($root_id) |
325 |
|
|
# ---------------------------------------------------------------------------- |
326 |
|
|
sub trace { |
327 |
|
|
my ($self, $root_id) = @_; |
328 |
|
|
do { $log->error("Метод ->trace() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
329 |
|
|
|
330 |
|
|
do { $log->warning("Вызов метода ->trace() без указания идентификатора секции"); return () } |
331 |
|
|
unless (exists($self->{id}) && ($self->{id} > 0)); |
332 |
|
|
$root_id ||= $ROOT; |
333 |
|
|
|
334 |
|
|
my $id_ = $self->{id}; |
335 |
|
|
my @SIDS = ($id_); |
336 |
|
|
my $sth = $self->keeper->SQL->prepare_cached("select pid from ".$self->class_table->db_table." where id = ?"); |
337 |
|
|
|
338 |
|
|
while ($id_ != $root_id) |
339 |
|
|
{ |
340 |
|
|
$sth->execute($id_); |
341 |
|
|
($id_) = $sth->fetchrow_array(); |
342 |
|
|
if (defined($id_) && ($id_ > 0)) |
343 |
|
|
{ |
344 |
|
|
unshift (@SIDS, $id_); |
345 |
|
|
} else { |
346 |
|
|
# Мы закочили путешествие вверх по рубрикам, а до корня не дошли... |
347 |
|
|
$sth->finish; |
348 |
|
|
return (); |
349 |
|
|
} |
350 |
|
|
} |
351 |
|
|
$sth->finish; |
352 |
|
|
return @SIDS; |
353 |
|
|
} |
354 |
|
|
|
355 |
|
|
|
356 |
|
|
# ---------------------------------------------------------------------------- |
357 |
|
|
# Предки |
358 |
|
|
# Возвращает массив идентификаторов всех предков (родителей на всех уровнях) данной секции |
359 |
|
|
# ---------------------------------------------------------------------------- |
360 |
|
|
sub ancestors |
361 |
|
|
{ |
362 |
|
|
my $self = shift; |
363 |
|
|
do { $log->error("Метод ->ancestors() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
364 |
|
|
|
365 |
|
|
my $keeper = $self->keeper; |
366 |
|
|
do { $log->error("В объекте секции не определена ссылка на базу данных"); die } unless ref($keeper); |
367 |
|
|
|
368 |
|
|
do { $log->warning("Вызов метода ->ancestors() без указания идентификатора секции"); return () } unless (exists($self->{id}) && ($self->{id} > 0)); |
369 |
|
|
|
370 |
|
|
my @ancestors = (); |
371 |
|
|
my $sectionid = $self->{id}; |
372 |
|
|
while ($sectionid) |
373 |
|
|
{ |
374 |
|
|
$sectionid = $keeper->SQL->selectrow_array("select pid from ".$self->class_table->db_table." where id = ?", {}, $sectionid); |
375 |
|
|
push @ancestors, $sectionid if defined $sectionid && $sectionid; |
376 |
|
|
} |
377 |
|
|
return @ancestors; |
378 |
|
|
} |
379 |
|
|
|
380 |
|
|
# ---------------------------------------------------------------------------- |
381 |
|
|
# Потомки |
382 |
|
|
# Возвращает массив идентификаторов всех потомков (детей на всех уровнях) данной секции |
383 |
|
|
# ---------------------------------------------------------------------------- |
384 |
|
|
sub descendants |
385 |
|
|
{ |
386 |
|
|
my $self = shift; |
387 |
|
|
do { $log->error("Метод ->descendants() можно вызывать только у объектов, но не классов"); die } unless ref($self); |
388 |
|
|
|
389 |
|
|
my $keeper = $self->keeper; |
390 |
|
|
do { $log->error("В объекте секции не определена ссылка на базу данных"); die } unless ref($keeper); |
391 |
|
|
|
392 |
|
|
do { $log->warning("Вызов метода ->descendants() без указания идентификатора секции"); return () } unless (exists($self->{id}) && ($self->{id} > 0)); |
393 |
|
|
|
394 |
|
|
my @descendants = (); |
395 |
|
|
my @ids = ($self->{id}); |
396 |
|
|
while (scalar @ids) |
397 |
|
|
{ |
398 |
|
|
my $sth = $keeper->SQL->prepare("select id from ".$self->class_table->db_table." where pid in (" . (join ", ", @ids) . ")"); |
399 |
|
|
$sth->execute; |
400 |
|
|
@ids = (); |
401 |
|
|
while (my ($id) = $sth->fetchrow_array) |
402 |
|
|
{ |
403 |
|
|
push @ids, $id; |
404 |
|
|
} |
405 |
|
|
$sth->finish(); |
406 |
|
|
push @descendants, @ids; |
407 |
|
|
last if !$sth->rows; |
408 |
|
|
} |
409 |
|
|
return @descendants; |
410 |
|
|
} |
411 |
|
|
|
412 |
|
|
# ------------------------------------------------------------------------------------------------- |
413 |
|
|
# Получение деревца... |
414 |
|
|
# Параметры: |
415 |
|
|
# light => облегченная версия |
416 |
|
|
# root => корень дерева (по умолчанию - 1) |
417 |
|
|
# ------------------------------------------------------------------------------------------------- |
418 |
|
|
sub get_tree { |
419 |
|
|
my ($self, %opts) = @_; |
420 |
|
|
do { $log->warning("Метод ->get_tree() можно вызывать только у объектов, но не классов"); return undef } unless ref($self); |
421 |
|
|
|
422 |
|
|
my $root = $opts{root} || $ROOT; |
423 |
|
|
|
424 |
|
|
|
425 |
|
|
# ---------------------------------------------------------------------------------------- |
426 |
|
|
# Выбираем все секции |
427 |
|
|
$opts{no_limit} = 1; |
428 |
|
|
delete $opts{root}; |
429 |
|
|
my @sections = $self->keeper->get_sections(%opts); |
430 |
|
|
|
431 |
|
|
my $CACHE = {}; |
432 |
|
|
foreach my $section (@sections) { |
433 |
|
|
if (ref($section)) { |
434 |
|
|
$CACHE->{$section->id()} = $section; |
435 |
|
|
} |
436 |
|
|
} |
437 |
|
|
|
438 |
|
|
for my $id (sort { $CACHE->{$a}->sorder() <=> $CACHE->{$b}->sorder() } (keys(%{ $CACHE }))) { |
439 |
|
|
my $pid = $CACHE->{$id}->pid() || ''; |
440 |
|
|
$CACHE->{$pid}->{childs} = [] if (! exists($CACHE->{$pid}->{childs})); |
441 |
|
|
$CACHE->{$id}->{parent} = $CACHE->{$pid}; |
442 |
|
|
push (@{ $CACHE->{$pid}->{childs} }, $CACHE->{$id} ); |
443 |
|
|
} |
444 |
|
|
|
445 |
|
|
return $CACHE->{$root}; |
446 |
|
|
} |
447 |
|
|
|
448 |
|
|
1; |
449 |
|
|
|