Line # Revision Author
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