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