File Coverage

blib/lib/WE/DB/Obj.pm
Criterion Covered Total %
statement 36 753 4.7
branch 0 334 0.0
condition 0 81 0.0
subroutine 12 85 14.1
pod 36 42 85.7
total 84 1295 6.4


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # $Id: Obj.pm,v 1.37 2005/01/28 08:44:07 eserte Exp $
5             # Author: Slaven Rezic
6             #
7             # Copyright (C) 2001 Online Office Berlin. All rights reserved.
8             # Copyright (C) 2002 Slaven Rezic.
9             # This is free software; you can redistribute it and/or modify it under the
10             # terms of the GNU General Public License, see the file COPYING.
11              
12             #
13             # Mail: slaven@rezic.de
14             # WWW: http://we-framework.sourceforge.net
15             #
16              
17             =head1 NAME
18              
19             WE::DB::Obj - object database for the WE_Framework
20              
21             =head1 SYNOPSIS
22              
23             $objdb = WE::DB::Obj->new($root, $db_file);
24             $objdb = $root->ObjDB;
25              
26             =head1 DESCRIPTION
27              
28             =cut
29              
30             package WE::DB::Obj;
31 15     15   82 use base qw(WE::DB::ObjBase);
  15         30  
  15         11774  
32              
33 15     15   118 use strict;
  15         24  
  15         591  
34 15     15   74 use vars qw($VERSION);
  15         33  
  15         1445  
35             $VERSION = sprintf("%d.%02d", q$Revision: 1.37 $ =~ /(\d+)\.(\d+)/);
36              
37             __PACKAGE__->mk_accessors(qw(DBFile DBTieArgs
38             MLDBM_Serializer MLDBM_UseDB MLDBM_DumpMeth
39             IsCachedDatabase));
40              
41 15     15   14320 use MLDBM;
  15         90654  
  15         110  
42 15     15   633 use Fcntl;
  15         31  
  15         5325  
43              
44 15     15   97 use WE::Util::Date;
  15         31  
  15         858  
45 15     15   10164 use WE::Util::LangString qw(new_langstring langstring);
  15         45  
  15         1239  
46              
47 15     15   97 use constant OBJECT => 0;
  15         29  
  15         892  
48 15     15   75 use constant CHILDREN => 1;
  15         27  
  15         730  
49 15     15   76 use constant PARENTS => 2;
  15         52  
  15         560  
50 15     15   77 use constant VERSIONS => 3;
  15         33  
  15         157216  
51              
52 0     0 0   sub DBClass { "DB_File" }
53 0     0 0   sub SerializerClass { "Data::Dumper" }
54              
55             =head2 CONSTRUCTOR new($class, $root, $file [, %args])
56              
57             C creates a new database reference object (and, if the database
58             does not exist, also the physical database). Usually called only from
59             L. Parameters are: the C<$root> object (a
60             C object) and the filename for the underlying database (here,
61             it is C).
62              
63             In the optional arguments, further options can be specified:
64              
65             =over 4
66              
67             =item -serializer => $serializer
68              
69             The type of the serializer, e.g. C (the default) or
70             C.
71              
72             =item -db => $db
73              
74             The type of the database (dbm) implementation, e.g. C (the
75             default) or C. Note that other databases than C or
76             C have length restrictions, making them unsuitable for
77             using with C. However, the CPAN module
78             C workaround the deficiency of the 1K size
79             limit in the standard C database.
80              
81             =item -locking => $bool
82              
83             True, if locking should be used. XXX For now, only 0 and 1 can be
84             used, but this should probably be changed to use shared and exclusive
85             locks.
86              
87             By default, there is no locking. If locking is enabled and the
88             database type is C, then C will be used. For
89             other database types, no locking is implemented.
90              
91             =item -readonly => $bool
92              
93             Open the database read-only. This is the same as specifying O_RDONLY.
94             By default it is opened read-write and the database is created if
95             necessary (O_RDWR|O_CREAT).
96              
97             =item -writeonly => $bool
98              
99             If true, then a database will not be created if necessary. This is the
100             same as specifying O_RDWR.
101              
102             =item -connect => $bool
103              
104             If true, connects to the database while constructing the object.
105             Otherwise the connection will be made automatically before each
106             operation. Also, the methods B and B can be used
107             for connecting and disconnecting from the database.
108              
109             Normally, long running processes (servers or mod_perl processes)
110             should specify -connect => 0 and use the auto-connection feature or
111             manually connect()/disconnect(). So database changes are propagated
112             immediately.
113              
114             The default of the -connect option is true.
115              
116             =back
117              
118             =cut
119              
120             sub new {
121 0     0 1   my($proto, $root, $file, %args) = @_;
122 0   0       my $class = ref $proto || $proto;
123 0           my $self = {};
124 0           bless $self, $class;
125              
126 0 0         $args{-db} = $self->DBClass
127             unless defined $args{-db};
128 0 0         $args{-serializer} = $self->SerializerClass
129             unless defined $args{-serializer};
130 0 0         $args{-locking} = 0 unless defined $args{-locking};
131 0 0         $args{-readonly} = 0 unless defined $args{-readonly};
132 0 0         $args{-writeonly} = 0 unless defined $args{-writeonly};
133 0 0         $args{-connect} = 1 unless defined $args{-connect};
134 0 0 0       if (!$args{-readonly} && $args{-cache}) {
135 0           die "-cache => 1 is only allowed with -readonly";
136             }
137 0 0         $args{-cache} = 0 unless defined $args{-cache};
138              
139 0           my @tie_args;
140 0 0         if ($args{-readonly}) {
    0          
141 0           push @tie_args, O_RDONLY;
142             } elsif ($args{-writeonly}) {
143 0           push @tie_args, O_RDWR;
144             } else {
145 0           push @tie_args, O_RDWR|O_CREAT;
146             }
147              
148 0 0         push @tie_args, $args{-db} eq 'Tie::TextDir' ? 0770 : 0660;
149              
150 0 0         if ($args{-db} eq 'DB_File') {
151 0           require DB_File;
152 0           push @tie_args, $DB_File::DB_HASH;
153 0 0         if ($args{-locking}) {
154 0           $self->MLDBM_UseDB('DB_File::Lock');
155 0 0         push @tie_args, $args{-readonly} ? "read" : "write";
156             } else {
157 0           $self->MLDBM_UseDB('DB_File');
158             }
159             } else {
160 0           $self->MLDBM_UseDB($args{-db});
161             }
162              
163 0           $self->MLDBM_Serializer($args{-serializer});
164 0 0         if ($self->MLDBM_Serializer eq 'Storable') {
165 0           $self->MLDBM_DumpMeth('portable');
166             }
167              
168 0           $self->DBFile($file);
169 0           $self->DBTieArgs(\@tie_args);
170              
171 0           $self->Root($root);
172 0           $self->Connected(0);
173              
174 0 0         if ($args{-cache}) {
175 0           my $cached_db = {};
176 0           $self->connect;
177 0           while(my($k,$v) = each %{ $self->{DB} }) {
  0            
178 0           $cached_db->{$k} = $v;
179             }
180 0           $self->disconnect;
181 0           $self->{DB} = $cached_db;
182 0           $self->Connected(1);
183 0           $self->IsCachedDatabase(1);
184 0           return $self;
185             }
186              
187 0 0 0       if ($args{-connect} && $args{-connect} ne 'never') {
188 0           $self->connect;
189             }
190              
191 0           $self;
192             }
193              
194             sub cached_db {
195 0     0 0   my($self) = @_;
196 0 0         my $db = ($self->MLDBM_UseDB eq 'DB_File::Lock'
197             ? 'DB_File'
198             : $self->MLDBM_UseDB
199             );
200 0           $self->new($self->Root,
201             $self->DBFile,
202             -readonly => 1,
203             -cache => 1,
204             -db => $db,
205             -serializer => $self->MLDBM_Serializer,
206             );
207             }
208              
209             =head2 DESTRUCTOR DESTROY
210              
211             Called automatically. Destroys the tied database handle.
212              
213             =cut
214              
215             ### XXX DESTROY seems to throw segfaults now (because of disconnect??? the
216             ### XXX eval in disconnect???)
217             # sub DESTROY {
218             # my $self = shift;
219             # $self->Root(undef);
220              
221             # #XXXlocal $^W = undef; # XXX
222             # $self->disconnect;
223             # # if ($self->{DB} && ref $self->{DB} eq 'HASH' && tied %{$self->{DB}}) {
224             # # untie %{ $self->{DB} };
225             # # }
226             # }
227              
228             =head2 METHODS
229              
230             Please see also L for inherited methods.
231              
232             =over 4
233              
234             =item connect
235              
236             =cut
237              
238             sub connect {
239 0     0 1   my $self = shift;
240 0           local $MLDBM::UseDB = $self->MLDBM_UseDB;
241 0           local $MLDBM::Serializer = $self->MLDBM_Serializer;
242 0           local $MLDBM::DumpMeth = $self->MLDBM_DumpMeth;
243              
244 0           my @args = @{$self->DBTieArgs};
  0            
245 0 0         tie %{ $self->{DB} }, 'MLDBM', $self->DBFile, @args
  0            
246 0           or die "Can't tie MLDBM database @{[$self->DBFile]} with args <@args>, db <$MLDBM::UseDB> and serializer <$MLDBM::Serializer>: $!";
247 0           $self->Connected(1);
248             }
249              
250             =item disconnect
251              
252             =cut
253              
254             sub disconnect {
255 0     0 1   my $self = shift;
256 0 0         if ($self->Connected) {
257 0           eval {
258 0           untie %{ $self->{DB} };
  0            
259 0 0         };warn $@ if $@;
260 0           $self->Connected(0);
261             }
262             }
263              
264             =item init
265              
266             Initialize the database to hold meta data like _root_object or
267             _next_id. Usually called only from C.
268              
269             =cut
270              
271             # XXX hardcoded to create a site...
272             sub init {
273 0     0 1   my($self, %args) = @_;
274              
275 0 0         if (!$self->root_object) {
276             $self->connect_if_necessary
277             (sub {
278 0     0     my $site = WE::Obj::Site->new();
279              
280             # XXX hmmmm... should not be doubled...
281 0           my $now = epoch2isodate();
282 0           $site->TimeCreated($now);
283 0           $site->TimeModified($now);
284 0           $site->Owner($self->Root->CurrentUser);
285 0   0       my $title = $args{-title} ||
286             new_langstring(en => "Root of the site",
287             de => "Wurzel der Website",
288             );
289 0           $site->Title($title);
290 0           my $obj = $self->_store_obj($site);
291 0           $self->{DB}{'_root_object'} = $obj->[OBJECT]->Id;
292 0           });
293             }
294             }
295              
296             =item delete_db_contents
297              
298             Delete all database contents
299              
300             =cut
301              
302             sub delete_db_contents {
303 0     0 1   my $self = shift;
304             $self->connect_if_necessary
305             (sub {
306 0     0     my @obj = keys %{ $self->{DB} };
  0            
307 0           foreach (@obj) {
308 0           delete $self->{DB}{$_};
309             }
310 0           $self->init;
311 0           });
312              
313             # update names, links ...
314 0 0         if ($self->Root->NameDB) {
315 0           $self->Root->NameDB->delete_db_contents;
316             }
317             }
318              
319             =item root_object
320              
321             Return the root object.
322              
323             =cut
324              
325             sub root_object {
326 0     0 1   my($self) = @_;
327             # XXX permission manager
328             $self->connect_if_necessary
329             (sub {
330 0 0   0     if (exists $self->{DB}{'_root_object'}) {
331 0           $self->get_object($self->{DB}{'_root_object'});
332             } else {
333 0           undef;
334             }
335 0           });
336             }
337              
338             =item is_root_object($objid)
339              
340             Return true if the object with id C<$objid> is the root object.
341              
342             =cut
343              
344             sub is_root_object {
345 0     0 1   my($self, $objid) = @_;
346 0           $self->idify_params($objid);
347             $self->connect_if_necessary
348             (sub {
349 0     0     $self->{DB}{'_root_object'} eq $objid;
350 0           });
351             }
352              
353             =item _next_id
354              
355             Increment and get the next free id. The internal id counter is always
356             incremented, regardless whether the new id will be used or not.
357              
358             =cut
359              
360             sub _next_id {
361 0     0     my($self) = @_;
362             $self->connect_if_necessary
363             (sub {
364 0   0 0     my $id = $self->{DB}->{'_next_id'} || 0;
365 0           $self->{DB}->{'_next_id'}++;
366 0           $id;
367 0           });
368             }
369              
370             =item _get_next_id
371              
372             Only get the next free id, without incrementing it.
373              
374             =cut
375              
376             sub _get_next_id {
377 0     0     my($self) = @_;
378             $self->connect_if_necessary
379             (sub {
380 0     0     $self->{DB}->{'_next_id'};
381 0           });
382             }
383              
384             =item _create_stored_obj
385              
386             Create a new internal stored object.
387              
388             =cut
389              
390             sub _create_stored_obj {
391 0     0     my($self) = @_;
392 0           [undef, [], [], []];
393             }
394              
395             =item _store_stored_obj($stored_object)
396              
397             Store the internal stored object.
398              
399             =cut
400              
401             sub _store_stored_obj {
402 0     0     my($self, $stored_obj) = @_;
403 0           my $id = $stored_obj->[OBJECT]->Id;
404 0 0         if (!defined $id) {
405 0           die "Fatal error: there is no Id in the stored object";
406             }
407             $self->connect_if_necessary
408             (sub {
409 0     0     $self->{DB}{$id} = $stored_obj;
410 0           });
411             }
412              
413             =item _store_obj($object)
414              
415             Store the object. Please note that there is a difference between a
416             stored object (holding additional data like children, parents etc.)
417             and the mere object.
418              
419             =cut
420              
421             sub _store_obj {
422 0     0     my($self, $obj) = @_;
423             $self->connect_if_necessary
424             (sub {
425 0     0     my $id = $obj->Id;
426 0 0         if (!defined $id) {
427 0           $id = $self->_next_id;
428 0           $obj->Id($id);
429             }
430 0           my $o = $self->{DB}{$id};
431 0 0         if (!$o) {
432 0           $o = [];
433 0           $o->[PARENTS] = [];
434 0           $o->[CHILDREN] = [];
435 0           $o->[VERSIONS] = [];
436             }
437 0           $o->[OBJECT] = $obj;
438              
439 0           $self->{DB}{$id} = $o;
440              
441             # return stored object
442 0           $o;
443 0           });
444             }
445              
446             =item _get_stored_obj($object_id)
447              
448             Get a stored object.
449              
450             =cut
451              
452             sub _get_stored_obj {
453 0     0     my($self, $id) = @_;
454             $self->connect_if_necessary
455             (sub {
456 0     0     $self->{DB}{$id};
457 0           });
458             }
459              
460             =item get_object($object_id)
461              
462             Get an object by id.
463              
464             =cut
465              
466             sub get_object {
467 0     0 1   my($self, $obj_id) = @_;
468 0           my $o = $self->_get_stored_obj($obj_id);
469 0 0         $o ? $o->[OBJECT] : undef;
470             }
471              
472             =item exists($object_id)
473              
474             Return true if the object exists. Parameter is the object id.
475              
476             =cut
477              
478             sub exists {
479 0     0 1   my($self, $obj_id) = @_;
480 0           defined $self->_get_stored_obj($obj_id);
481             }
482              
483             =item children_ids($object_id)
484              
485             Return a list of the children ids of this object. If the object does
486             not exist or the object has not children, return an empty list.
487              
488             =cut
489              
490             sub children_ids {
491 0     0 1   my($self, $obj_id) = @_;
492 0           $self->idify_params($obj_id);
493 0           my $o = $self->_get_stored_obj($obj_id);
494 0 0         $o ? @{ $o->[CHILDREN] } : ();
  0            
495             }
496              
497             =item parent_ids($object_id)
498              
499             Like children_ids, but return parent ids instead.
500              
501             =cut
502              
503             sub parent_ids {
504 0     0 1   my($self, $obj_id) = @_;
505 0           $self->idify_params($obj_id);
506 0           my $o = $self->_get_stored_obj($obj_id);
507 0 0         $o ? @{ $o->[PARENTS] } : ();
  0            
508             }
509              
510             =item version_ids($object_id)
511              
512             Like children_ids, but return version ids instead.
513              
514             =cut
515              
516             sub version_ids {
517 0     0 1   my($self, $obj_id) = @_;
518 0           $self->idify_params($obj_id);
519 0           my $o = $self->_get_stored_obj($obj_id);
520 0 0         $o ? @{ $o->[VERSIONS] } : ();
  0            
521             }
522              
523             =item find_links($target_id)
524              
525             Find links with the $target_id as target.
526              
527             =cut
528              
529             sub find_links {
530 0     0 1   my($self, $target_id) = @_;
531 0           $self->idify_params($target_id);
532 0           my @obj_ids;
533 0 0         if ($self->Root->LinkDB) {
534 0           @obj_ids = $self->Root->LinkDB->find_links($target_id);
535             } else {
536             $self->connect_if_necessary
537             (sub {
538 0     0     while(my($id, $stored_obj) = each %{ $self->{DB} }) {
  0            
539 0 0         next if $id =~ /^_/;
540 0           foreach my $idx (PARENTS, CHILDREN, VERSIONS) {
541 0           foreach (@{ $stored_obj->[$idx] }) {
  0            
542 0 0         if ($_ eq $target_id) {
543 0           push @obj_ids, $stored_obj->[OBJECT]->Id;
544 0           next;
545             }
546             }
547             }
548             }
549 0           });
550             }
551 0           @obj_ids;
552             }
553              
554             sub _remove_from_link_array {
555 0     0     my($self, $id, $stored_obj) = @_;
556 0           foreach my $idx (PARENTS, CHILDREN, VERSIONS) {
557 0           my $i = 0;
558 0           foreach (@{ $stored_obj->[$idx] }) {
  0            
559 0 0         if ($_ eq $id) {
560 0           splice @{ $stored_obj->[$idx] }, $i, 1;
  0            
561             }
562 0           $i++;
563             }
564             }
565             }
566              
567             =item unlink($object_id, $parent_id, %args)
568              
569             Remove the given parent link from the object. If there is no parent
570             link anymore, remove the whole object.
571              
572             Remaining arguments are passed to the B method (see there).
573              
574             =cut
575              
576             sub unlink {
577 0     0 1   my($self, $obj_id, $parent_id, %args) = @_;
578 0           $self->idify_params($obj_id, $parent_id);
579 0           my $parent_stored_obj = $self->_get_stored_obj($parent_id);
580 0 0         die "Can't get parent object with id $parent_id" unless $parent_stored_obj;
581 0           my $stored_obj = $self->_get_stored_obj($obj_id);
582 0 0         die "Can't get object with id $obj_id" unless $stored_obj;
583              
584 0           my $i = 0;
585 0           foreach (@{ $parent_stored_obj->[CHILDREN] }) {
  0            
586 0 0         if ($_ eq $obj_id) {
587 0           splice @{ $parent_stored_obj->[CHILDREN] }, $i, 1;
  0            
588             }
589 0           $i++;
590             }
591 0           $self->_store_stored_obj($parent_stored_obj);
592              
593 0           $i = 0;
594 0           foreach (@{ $stored_obj->[PARENTS] }) {
  0            
595 0 0         if ($_ eq $parent_id) {
596 0           splice @{ $stored_obj->[PARENTS] }, $i, 1;
  0            
597             }
598 0           $i++;
599             }
600              
601 0 0         if (!@{ $stored_obj->[PARENTS] }) {
  0            
602 0           $self->remove($obj_id, %args);
603             } else {
604 0           $self->_store_stored_obj($stored_obj);
605             }
606             }
607              
608             =item link($object_id, $folder_id)
609              
610             Link an object to a folder. This can be used to create multiple links.
611             It is possible to create multiple links from one object to another ---
612             this behaviour may change XXX. See also L.
613              
614             =cut
615              
616             # XXX cycle detection is missing
617             sub link {
618 0     0 1   my($self, $obj_id, $folder_id) = @_;
619 0           $self->idify_params($obj_id, $folder_id);
620 0           my $stored_obj = $self->_get_stored_obj($obj_id);
621 0 0         die "Can't get object with id $obj_id" unless $stored_obj;
622             # XXX use insertable types?
623             # XXX permission manager
624 0           my $folder_stored_obj = $self->_get_stored_obj($folder_id);
625 0 0         die "Can't get folder object with id $folder_id" unless $folder_stored_obj;
626 0           push @{ $stored_obj->[PARENTS] }, $folder_id;
  0            
627 0           push @{ $folder_stored_obj->[CHILDREN] }, $obj_id;
  0            
628 0           $self->_store_stored_obj($stored_obj);
629 0           $self->_store_stored_obj($folder_stored_obj);
630             }
631              
632             =item remove($object_id, %args)
633              
634             Remove the object $obj_id and all links to this object uncoditionally.
635              
636             If -links => "unhandled" is specified, then links to this object won't
637             get removed. This is dangerous, and needs an additional L run
638             afterwards. This option is useful if a mass-delete should be done.
639              
640             =cut
641              
642             sub remove {
643 0     0 1   my($self, $obj_id, %args) = @_;
644 0           $self->idify_params($obj_id);
645             $self->connect_if_necessary
646             (sub {
647 0     0     my $stored_obj = $self->_get_stored_obj($obj_id);
648              
649             # XXX Debugging!
650 0 0         if (!$stored_obj->[OBJECT]) {
651 0           require Data::Dumper;
652 0           warn "SHOULD NOT HAPPEN: object $obj_id has no stored object";
653 0           warn Data::Dumper::Dumper($stored_obj);
654             }
655              
656             # remove content
657 0 0 0       if (UNIVERSAL::isa($stored_obj->[OBJECT], ('WE::Obj::DocObj'))
658             && $self->Root->ContentDB) {
659 0           $self->Root->ContentDB->remove($stored_obj->[OBJECT]);
660             }
661              
662             # unlink children
663 0           foreach my $child_id (@{ $stored_obj->[CHILDREN] }) {
  0            
664 0           $self->unlink($child_id, $obj_id);
665             }
666              
667             # delete everything in name database
668 0 0         if ($self->Root->NameDB) {
669 0           my $o = $self->get_object($obj_id);
670 0           $self->Root->NameDB->update([], [$o]);
671             }
672              
673             # delete physical object
674 0           delete $self->{DB}{$obj_id};
675              
676             # delete remaining links
677 0 0 0       if (!$args{'-links'} || $args{'-links'} ne "unhandled") {
678 0           my @obj_ids = $self->find_links($obj_id);
679 0           foreach my $id (@obj_ids) {
680 0           my $stored_obj = $self->_get_stored_obj($id);
681 0           $self->_remove_from_link_array($obj_id, $stored_obj);
682 0           $self->_store_stored_obj($stored_obj);
683             }
684             }
685              
686 0           });
687             }
688              
689             =item insert_doc(%args)
690              
691             Insert a document.
692             The following arguments should be given:
693              
694             -content: a string to the content or
695             -file: the filename for the content
696             -parent: the id of the parent
697              
698             Other arguments will be used as attributes for the object, e.g.
699             -ContentType will be used as the ContentType attribute and -Title as
700             the title attribute. Note that these attributes are typically starting
701             with an uppercase letter.
702              
703             Return the generated object.
704              
705             =cut
706              
707             sub insert_doc {
708 0     0 1   my($self, %args) = @_;
709 0           my $doc = WE::Obj::Doc->new;
710 0           $self->insert_doc_obj($doc, %args);
711             }
712              
713             sub insert_doc_obj {
714 0     0 0   my($self, $doc, %args) = @_;
715              
716             # XXX permission manager
717 0           my $content = delete $args{-content};
718 0           my $file = delete $args{-file};
719 0           my $parent = delete $args{-parent};
720 0           while(my($k,$v) = each %args) {
721 0 0         die "Option does not start with a dash: $k" if $k !~ /^-/;
722 0           $doc->{ucfirst(substr($k,1))} = $v;
723             }
724 0 0         if (defined $file) {
725 0 0         $doc->{ContentType} = $self->Root->ContentDB->get_mime_type_by_filename($file) if !$doc->{ContentType};
726 0 0         open(F, $file) or die "Can't open file $file: $!";
727 0           local $/ = undef;
728 0           $content = ;
729 0           close F;
730              
731 0           require File::Basename;
732 0           my $base = File::Basename::basename($file);
733              
734             # auto set title
735 0 0         if (!defined $doc->{Title}) {
736 0 0         if ($base =~ /^(.+)(\.[^.]+)$/) {
737 0           $doc->{Title} = $1; # stripped extension
738             } else {
739 0           $doc->{Title} = $base; # there is no extension
740             }
741             }
742              
743 0 0         if (!defined $doc->{Basename}) {
744 0           $doc->{Basename} = $base;
745             }
746             }
747              
748 0 0         $doc->ContentType("text/html") if !$doc->{ContentType}; # i.e. content given
749 0           $self->insert($doc, -parent => $parent);
750 0           $self->Root->ContentDB->store($doc, $content);
751 0           $doc;
752             }
753              
754             =item insert_folder(%args)
755              
756             Insert a folder.
757             The following arguments should be given:
758              
759             -parent: the id of the parent
760              
761             Return the generated object.
762              
763             =cut
764              
765             sub insert_folder {
766 0     0 1   my($self, %args) = @_;
767 0           my $folder = WE::Obj::Folder->new;
768             # XXX permission manager
769 0           my $parent = delete $args{-parent};
770 0           while(my($k,$v) = each %args) {
771 0 0         die "Option does not start with a dash: $k" if $k !~ /^-/;
772 0           my $member = ucfirst(substr($k,1));
773 0 0         if ($folder->can($member)) {
774 0           $folder->$member($v);
775             } else {
776 0           $folder->{$member} = $v;
777             }
778             }
779              
780             ### XXX autogenerate basename here?
781             # if (!defined $folder->{Basename}) {
782             # $folder->{Basename} = langstring($folder->{Title}, $self->Root->CurrentLang);
783             # }
784              
785 0           $self->insert($folder, -parent => $parent);
786             }
787              
788             =item insert($object, %args)
789              
790             General method for inserting objects. You will mostly use either
791             insert_doc or insert_folder.
792              
793             Arguments: C<-parent> for parent object.
794              
795             Return the generated object.
796              
797             =cut
798              
799             sub insert {
800 0     0 1   my($self, $obj, %args) = @_;
801              
802             $self->connect_if_necessary(sub {
803 0     0     my $parent = delete $args{-parent};
804 0 0         if (!defined $parent) {
805 0           die "The -parent option is missing";
806             }
807 0           $self->idify_params($parent);
808 0           my $parent_stored_obj = $self->_get_stored_obj($parent);
809 0 0         if (!$parent_stored_obj) {
810 0           die "There is no parent with id $parent";
811             }
812 0           my $parent_obj = $parent_stored_obj->[OBJECT];
813 0 0         if (!$parent_obj->isa("WE::Obj::FolderObj")) {
814 0           die "The object with the id $parent is not a FolderObj, but a " . ref $parent_obj . ". Objects can only be inserted in folders.";
815             }
816 0 0         if (!$parent_obj->object_is_insertable($obj)) {
817 0           die "The object type " . ref($obj) . " is not allowed in " . ref($parent_obj) . ". The only allowed object types are: " . join(", ", @{ $parent_obj->insertable_types });
  0            
818             }
819 0           my $id = $self->_next_id;
820 0           push @{$parent_stored_obj->[CHILDREN]}, $id;
  0            
821 0           $self->_store_stored_obj($parent_stored_obj);
822              
823 0           $obj->Id($id);
824 0           my $owner = $self->Root->CurrentUser;
825 0 0         if (defined $owner) {
826 0           $obj->Owner($owner);
827             } else {
828 0           $obj->Owner(undef); # no owner
829             }
830 0           my $now = epoch2isodate();
831 0           $obj->TimeCreated($now);
832 0           $obj->TimeModified($now);
833 0           my $obj_stored_obj = $self->_create_stored_obj;
834 0           $obj_stored_obj->[OBJECT] = $obj;
835 0           $obj_stored_obj->[PARENTS] = [$parent];
836 0           $self->_store_stored_obj($obj_stored_obj);
837              
838             # update names, links ...
839 0 0         if ($self->Root->NameDB) {
840 0           $self->Root->NameDB->update([$obj],[]);
841             }
842 0           });
843              
844 0           $obj;
845             }
846              
847             sub _insert_version {
848 0     0     my($self, $obj, %args) = @_;
849              
850 0           my $version_parent = delete $args{-versionparent};
851 0           $self->idify_params($version_parent);
852 0           my $parent_stored_obj = $self->_get_stored_obj($version_parent);
853 0           my $id = $self->_next_id;
854 0           push @{$parent_stored_obj->[VERSIONS]}, $id;
  0            
855 0           $self->_store_stored_obj($parent_stored_obj);
856              
857 0           $obj->Id($id);
858 0           $obj->Version_Parent($version_parent);
859 0           my $owner = $self->Root->CurrentUser;
860 0 0         if (defined $owner) {
861 0           $obj->Version_Owner($owner);
862             } else {
863 0           $obj->Version_Owner(undef); # no owner
864             }
865 0           my $now = epoch2isodate();
866 0           $obj->Version_Time($now);
867 0 0         if (defined $args{-log}) {
868 0           $obj->Version_Comment($args{-log});
869             }
870 0 0         if (defined $args{-number}) {
871 0           $obj->Version_Number($args{-number});
872             }
873              
874 0           my $obj_stored_obj = $self->_create_stored_obj;
875 0           $obj_stored_obj->[OBJECT] = $obj;
876 0           $self->_store_stored_obj($obj_stored_obj);
877              
878 0           $obj;
879             }
880              
881             =item content($object_id)
882              
883             Get the content for the given object.
884              
885             =cut
886              
887             sub content {
888 0     0 1   my($self, $objid) = @_;
889 0           $self->idify_params($objid);
890             # XXX permission manager
891 0           my $obj = $self->get_object($objid);
892 0           $self->Root->ContentDB->get_content($obj);
893             }
894              
895             =item replace_content($object_id, $content)
896              
897             Replace the content of an existing object. Return the object itself.
898              
899             =cut
900              
901             sub replace_content {
902 0     0 1   my($self, $objid, $new_content) = @_;
903 0           $self->idify_params($objid);
904 0   0       my $obj = $self->get_object($objid) || die "Can't get object for id $objid";
905 0           $obj->TimeModified(epoch2isodate());
906 0           $obj->Dirty(1);
907 0           $obj->DirtyContent(1);
908 0           $self->_store_obj($obj);
909 0           $self->Root->ContentDB->store($obj, $new_content);
910 0           $obj;
911             }
912              
913             =item flush
914              
915             Flushes all changes, so they are visible to other processes. This is
916             done automatically on end of the program or if the object is
917             destroyed.
918              
919             =cut
920              
921             sub flush {
922 0     0 1   my $self = shift;
923 0 0         return if !$self->Connected;
924 0           (tied %{$self->{DB}})->sync;
  0            
925             }
926              
927             =item replace_object($object)
928              
929             Replace the given object. Argument is an object. This object should
930             contain the valid id. Return the object itself.
931              
932             =cut
933              
934             sub replace_object {
935 0     0 1   my($self, $obj) = @_;
936             # XXX permission manager
937 0           my $stored_obj = $self->_get_stored_obj($obj->Id);
938 0 0         die "Can't get stored object from id " . $obj->Id if !$stored_obj;
939 0           my $namedb = $self->Root->NameDB;
940 0           my $clone;
941 0 0         if ($namedb) {
942 0           $clone = $stored_obj->[OBJECT]->clone;
943             }
944 0           $obj->TimeModified(epoch2isodate());
945 0           $obj->Dirty(1);
946 0           $obj->DirtyAttributes(1);
947 0           $stored_obj->[OBJECT] = $obj;
948 0           $self->_store_stored_obj($stored_obj);
949              
950             # update names, links ...
951 0 0         if ($namedb) {
952 0           $namedb->update([$obj],[$clone]);
953             }
954              
955 0           $obj;
956             }
957              
958             =item is_ancestor($object_id, $ancestor_id)
959              
960             Return true if $ancestor_id is an ancestor of $object_id.
961              
962             =cut
963              
964             sub is_ancestor {
965 0     0 1   my($self, $object_id, $ancestor_id) = @_;
966 0           $self->idify_params($object_id, $ancestor_id);
967 0           my @pathobjects = $self->pathobjects($object_id);
968 0           pop @pathobjects; # remove itself
969 0           for my $o (@pathobjects) {
970 0 0         return 1 if ($o->Id eq $ancestor_id);
971             }
972 0           0;
973             }
974              
975             =item copy($object_id, $folder_id, %args)
976              
977             Copies the object identified by $object_id to the folder identified by
978             $folder_id. Both the object metadata and the content are copied.
979             Folders are copied by default recursively. To only copy the folder
980             object, use C<-recursive =E 0> in the %args parameter hash.
981              
982             Return the copied object. If there is a recursive copy, then return a
983             list of copied objects. In this list, the first object is the copied
984             top folder. In scalar context, always return only the first (or only)
985             copied object.
986              
987             Version information is never copied (yet).
988              
989             =cut
990              
991             sub copy {
992 0     0 1   my($self, $object_id, $target_id, %args) = @_;
993 0 0         die "Cannot copy object $object_id into itself"
994             if $target_id eq $object_id;
995 0 0         die "Cannot copy $object_id into descendent object $target_id"
996             if $self->is_ancestor($target_id, $object_id);
997 0 0         $args{-mapping} = {} if !$args{-mapping};
998 0           my @copied = $self->_copy($object_id, -parent => $target_id, %args);
999 0           $self->remap_attribute_links([ values %{ $args{-mapping} } ],
  0            
1000             $args{-mapping});
1001             # We have to remap the objects, because they might be changed
1002             # in remap_attribute_links.
1003 0           @copied = map { $self->get_object($_->Id) } @copied;
  0            
1004 0 0         wantarray ? @copied : $copied[0];
1005             }
1006              
1007             sub remap_attribute_links {
1008 0     0 0   my($self, $object_ids, $mapping) = @_;
1009             $self->connect_if_necessary
1010             (sub {
1011 0     0     for my $objid (@$object_ids) {
1012 0           my $o = $self->get_object($objid);
1013 0           my $changed;
1014 0 0 0       if ($o->can("IndexDoc") &&
      0        
1015             defined $o->IndexDoc &&
1016             exists $mapping->{$o->IndexDoc}) {
1017 0           my $new = $mapping->{$o->IndexDoc};
1018 0           $o->IndexDoc($new);
1019 0           $changed++;
1020             }
1021 0 0         if ($changed) {
1022 0           $self->replace_object($o);
1023             }
1024             }
1025 0           });
1026             }
1027              
1028             =item ci($object_id, %args)
1029              
1030             Check in the current version of the object with id C<$object_id>. You
1031             can use additional parameters:
1032              
1033             =over
1034              
1035             =item -log => $log_message
1036              
1037             Specify a log message for this version (recommended). C<-comment> is
1038             an alias for C<-log>.
1039              
1040             =item -number => $version_number
1041              
1042             Normally, the version number is just incremented (e.g. from 1.0 to
1043             1.1). If you like, you can specify another version number. There are
1044             no checks for valid version numbers (that is, you can specify more
1045             than one number, invalid formatted version numbers etc). C<-version> is an alias for C<-number>.
1046              
1047             =item -trimold => $number_of_versions
1048              
1049             If set to a value greater 0, then delete old versions. Set
1050             $number_of_versions specify the number of versions you want to keep.
1051             With -trimold => 1, all but the newest version will be wiped out.
1052              
1053             =back
1054              
1055             Return the checked-in objects. The original object is set to not dirty.
1056              
1057             =cut
1058              
1059             sub ci {
1060 0     0 1   my($self, $object_id, %args) = @_;
1061 0 0         if (defined $args{-version}) {
1062 0           $args{-number} = delete $args{-version};
1063             }
1064 0 0         if (!defined $args{-number}) {
1065 0           $args{-number} = $self->_get_next_version($object_id);
1066             }
1067 0 0         if (defined $args{-comment}) {
1068 0           $args{-log} = delete $args{-comment};
1069             }
1070 0           my $trimold = delete $args{-trimold};
1071 0           my(@ret) = $self->_copy($object_id,
1072             -versionparent => $object_id, %args);
1073              
1074 0 0         if ($trimold) {
1075 0           $self->trim_old_versions($object_id, -trimold => $trimold);
1076             }
1077              
1078 0           $self->_undirty($object_id);
1079              
1080 0 0         wantarray ? @ret : $ret[0];
1081             }
1082              
1083             =item trim_old_versions($object_id, [ -trimold => $number | -all => 1 ])
1084              
1085             Trim the last C<$number> versions of object C<$object_id>. If C<-all>
1086             is used instead, then trim all old versions. C<-all> and C<-trimold>
1087             are mutually exclusive.
1088              
1089             =cut
1090              
1091             # XXX -all is not tested yet!
1092             sub trim_old_versions {
1093 0     0 1   my($self, $object, %args) = @_;
1094 0           $self->objectify_params($object);
1095 0           my $object_id = $object->Id;
1096 0           my $trimold = delete $args{-trimold};
1097 0           my $all = delete $args{-all};
1098 0 0         if (keys %args) { die "Unknown argument: " . join ", ", keys %args }
  0            
1099 0 0 0       return if !$trimold && !$all;
1100 0           my(@versions) = $self->version_ids($object_id);
1101 0 0         if (@versions > 0) { # XXX this used to be @versions>1, but that was probably wrong
1102 0           my @newest_ids;
1103 0 0         if ($all) {
1104 0           @newest_ids = ();
1105             } else {
1106 0           @newest_ids = splice @versions, -$trimold; # don't trim the $trimold newest versions
1107             }
1108 0           foreach my $id (@versions) {
1109 0           $self->remove($id);
1110             }
1111 0           eval{
1112 0           my $stored_obj = $self->_store_obj($object);
1113 0           $stored_obj->[VERSIONS] = [@newest_ids];
1114 0           $self->_store_stored_obj($stored_obj);
1115 0 0         };die "$@ $object $object_id @versions" if $@;
1116             }
1117             }
1118              
1119             =item co($object_id [, -version => $version_number])
1120              
1121             NYI.
1122              
1123             Check out the object with the version number C<$version_number>. If
1124             version number is not given, then check out the latest version. If the
1125             version number is not given and there are no versions at all, then an
1126             exception will be thrown. Please note that a check out will override
1127             the current object, so you probably should do a C first. No
1128             locking is done (yet).
1129              
1130             =cut
1131              
1132             sub co {
1133 0     0 1   my($self, $object_id, %args) = @_;
1134 0           $self->idify_params($object_id);
1135 0 0         if (defined $args{-version}) {
1136 0           $args{-number} = delete $args{-version};
1137             }
1138 0           my $v_obj;
1139 0 0         if (!defined $args{-number}) {
1140 0           my @v_id = $self->version_ids($object_id);
1141 0 0         if (!@v_id) {
1142 0           die "There are no versions available for object $object_id";
1143             }
1144 0           $v_obj = $self->get_object($v_id[-1]);
1145             }
1146 0 0         if (!$v_obj) {
1147 0           foreach my $v ($self->versions($object_id)) {
1148 0 0         if ($v->Version_Number eq $args{-number}) {
1149 0           $v_obj = $v;
1150 0           last;
1151             }
1152             }
1153             }
1154 0 0         if (!$v_obj) {
1155 0           die "Can't find version $args{-number} for object $object_id";
1156             }
1157              
1158 0           my $stored_obj = $self->_get_stored_obj($object_id);
1159 0           my $old_o = $stored_obj->[OBJECT];
1160 0           $stored_obj->[OBJECT] = $v_obj;
1161 0           $self->Root->ContentDB->copy($v_obj, $old_o);
1162 0           $v_obj->Id($old_o->Id);
1163 0           $self->_store_stored_obj($stored_obj);
1164 0           $stored_obj->[OBJECT];
1165             }
1166              
1167             sub _copy {
1168 0     0     my($self, $object_id, %args) = @_;
1169 0           $self->idify_params($object_id);
1170 0           my $obj = $self->get_object($object_id);
1171 0 0         die "Can't find object with id $object_id" if !$obj;
1172              
1173 0           my $mapping = delete $args{-mapping};
1174              
1175 0           my %insert_args;
1176             my $insert_meth;
1177 0 0         if (defined $args{-parent}) {
1178 0           my $target_id = delete $args{-parent};
1179 0           $self->idify_params($target_id);
1180 0           my $target_obj = $self->get_object($target_id);
1181 0 0         die "Target must be a folder" if !$target_obj->is_folder;
1182 0           %insert_args = (-parent => $target_id, %args);
1183 0           $insert_meth = "insert";
1184             } else { # new version
1185 0           my $version_parent_id = delete $args{-versionparent};
1186 0           $self->idify_params($version_parent_id);
1187 0           my $target_obj = $self->get_object($version_parent_id);
1188 0 0         die "Target $version_parent_id does not exist" if !$target_obj;
1189 0           %insert_args = (-versionparent => $version_parent_id, %args);
1190 0           $insert_meth = "_insert_version";
1191             }
1192              
1193 0 0         if ($obj->is_doc) {
1194 0           my $content = $self->content($object_id);
1195 0           my $clone_obj = $obj->clone;
1196             #XXX if (grep($_ eq $target_id, $self->parent_ids($object_id))) {
1197             # # XXX NYI: change title to "Copy of ..." (lang-dependent)
1198             # # XXX no: this is also called from ci()!
1199             # }
1200 0           $self->$insert_meth($clone_obj, %insert_args);
1201 0 0         if ($mapping) {
1202 0           $mapping->{$obj->Id} = $clone_obj->Id;
1203             }
1204 0           $self->replace_content($clone_obj, $content);
1205 0           $clone_obj;
1206             } else { # copy folder
1207 0           my $clone_obj = $obj->clone;
1208             #XXX if (grep($_ eq $target_id, $self->parent_ids($object_id))) {
1209             # # XXX NYI: change title to "Copy of ..." (lang-dependent)
1210             # # XXX no: this is also called from ci()!
1211             # }
1212 0           my @ret;
1213 0           $self->$insert_meth($clone_obj, %insert_args);
1214 0 0         if ($mapping) {
1215 0           $mapping->{$obj->Id} = $clone_obj->Id;
1216             }
1217 0           push @ret, $clone_obj;
1218 0 0 0       if (!exists $args{-recursive} || $args{-recursive}) {
1219 0           foreach my $child_id ($self->children_ids($object_id)) {
1220 0 0         if (exists $insert_args{-parent}) {
1221 0           $insert_args{-parent} = $clone_obj->Id;
1222             } else {
1223 0           $insert_args{-versionparent} = $clone_obj->Id;
1224             }
1225 0           push @ret, $self->_copy($child_id, %insert_args, -mapping => $mapping);
1226             }
1227             }
1228 0           @ret;
1229             }
1230             }
1231              
1232             =item move($object_id, $parent_id, %args)
1233              
1234             Move the object with C<$object_id> and linked to the parent
1235             C<$parent_id> to another position or destination. If C<$parent_id> is
1236             C, then the first found parent is used. If there are multiple
1237             parents, then it is better to specify the right one. The C<%args>
1238             portion may look like this:
1239              
1240             =over 4
1241              
1242             =item -destination => $folder_id
1243              
1244             Move the object to another folder. You can also use C<-target> as an
1245             alias for C<-destination>.
1246              
1247             =item -after => $after_object_id
1248              
1249             Leave the object in the same folder, but move it after the object with
1250             the id C<$after_object_id>. If there is no such object in the folder,
1251             then an exception is raised.
1252              
1253             =item -before => $before_object_id
1254              
1255             Same as C<-after>, but move the object before the specified object.
1256              
1257             =item -to => "begin" | "end"
1258              
1259             Move the object to the beginning or end of the folder. For "begin",
1260             you can also use "first" and for "end", you can use "last".
1261              
1262             =back
1263              
1264             Return nothing. On error an exception will be raised.
1265              
1266             =cut
1267              
1268             sub move {
1269 0     0 1   my($self, $objid, $parentid, %args) = @_;
1270 0           $self->idify_params($objid);
1271 0 0         if (!defined $parentid) {
1272 0           $parentid = ($self->parent_ids($objid))[0];
1273             }
1274 0           $self->idify_params($parentid);
1275              
1276 0           my $destination = delete $args{-destination};
1277 0 0         if (!defined $destination) {
1278 0           $destination = delete $args{-target}; # Alias for -destination
1279             }
1280 0           my $after = delete $args{-after};
1281 0           my $before = delete $args{-before};
1282 0           my $to = delete $args{-to};
1283              
1284             my $check_move = sub {
1285 0     0     my($target_id) = @_;
1286 0 0         die "Cannot move object $objid into itself"
1287             if $target_id eq $objid;
1288 0 0         die "Cannot move $objid into descendent object $target_id"
1289             if $self->is_ancestor($target_id, $objid);
1290 0           };
1291              
1292             # XXX permission manager
1293 0 0 0       if (defined $destination) {
    0 0        
1294 0           $self->idify_params($destination);
1295 0           $check_move->($destination);
1296             # first link, then unlink (in this order!)
1297 0           $self->link($objid, $destination);
1298 0           $self->unlink($objid, $parentid);
1299             } elsif (defined $before || defined $after || defined $to) {
1300 0           my $parent_stored_obj = $self->_get_stored_obj($parentid);
1301 0           my $moved;
1302 0 0         if (defined $after) {
    0          
    0          
1303 0           $self->idify_params($after);
1304 0 0         return if $after eq $objid;
1305 0           for(my $i=0; $i<=$#{ $parent_stored_obj->[CHILDREN] }; $i++) {
  0            
1306 0           my $id = $parent_stored_obj->[CHILDREN][$i];
1307 0 0         if ($id eq $after) {
    0          
1308 0           splice @{ $parent_stored_obj->[CHILDREN] }, $i+1, 0, $objid;
  0            
1309 0           $moved = 1;
1310 0           $i++;
1311             } elsif ($id eq $objid) {
1312 0           splice @{ $parent_stored_obj->[CHILDREN] }, $i, 1;
  0            
1313 0           $i--;
1314             }
1315             }
1316             } elsif (defined $before) {
1317 0           $self->idify_params($before);
1318 0 0         return if $before eq $objid;
1319 0           for(my $i=0; $i<=$#{ $parent_stored_obj->[CHILDREN] }; $i++) {
  0            
1320 0           my $id = $parent_stored_obj->[CHILDREN][$i];
1321 0 0         if ($id eq $before) {
    0          
1322 0           splice @{ $parent_stored_obj->[CHILDREN] }, $i, 0, $objid;
  0            
1323 0           $moved = 1;
1324 0           $i++;
1325             } elsif ($id eq $objid) {
1326 0           splice @{ $parent_stored_obj->[CHILDREN] }, $i, 1;
  0            
1327 0           $i--;
1328             }
1329             }
1330             } elsif (defined $to) {
1331 0           for(my $i=0; $i<=$#{ $parent_stored_obj->[CHILDREN] }; $i++) {
  0            
1332 0           my $id = $parent_stored_obj->[CHILDREN][$i];
1333 0 0         if ($id eq $objid) {
1334 0           splice @{ $parent_stored_obj->[CHILDREN] }, $i, 1;
  0            
1335 0 0         if ($to =~ /^(begin|first)$/) {
    0          
1336 0           unshift @{ $parent_stored_obj->[CHILDREN] }, $objid;
  0            
1337 0           $moved = 1;
1338 0           last;
1339             } elsif ($to =~ /^(end|last)$/) {
1340 0           push @{ $parent_stored_obj->[CHILDREN] }, $objid;
  0            
1341 0           $moved = 1;
1342 0           last;
1343             } else {
1344 0           die "Invalid -to specification. Must be -first, -last, -begin or -end";
1345             }
1346             }
1347             }
1348             }
1349 0 0         if (!$moved) {
1350 0           die "The object $objid could not be moved in parent $parentid";
1351             }
1352 0           $self->_store_stored_obj($parent_stored_obj);
1353             } else {
1354 0           die "Nowhere to move. Please specify either -destination, -before or -after";
1355             }
1356             }
1357              
1358             =item dump(%args)
1359              
1360             Dump object structure as a string. Possible options:
1361              
1362             =over 4
1363              
1364             =item -root => $object_id
1365              
1366             Specify another object to start dumping from. If not specified, start
1367             dumping from root object.
1368              
1369             =item -versions => $bool
1370              
1371             If true, then version information is also dumped.
1372              
1373             =item -attributes => $bool
1374              
1375             If true, then attribute information is also dumped.
1376              
1377             =item -children => $bool
1378              
1379             Recurse into children. This is by default true.
1380              
1381             =item -callback => $sub
1382              
1383             A reference to a callback which can dump additional code. The
1384             subroutine will get the following key-value pairs as arguments:
1385              
1386             =over 4
1387              
1388             =item -obj
1389              
1390             The current object
1391              
1392             =item -level
1393              
1394             The current level
1395              
1396             =item -indentstring
1397              
1398             An indentation string
1399              
1400             =back
1401              
1402             The subroutine should return a string. See C in the
1403             C script for an example.
1404              
1405             =back
1406              
1407             =cut
1408              
1409             sub dump {
1410 0     0 1   my $self = shift;
1411 0           my %args = @_;
1412 0 0         my $root_object = (defined $args{-root}
1413             ? $self->get_object(delete $args{-root})
1414             : $self->root_object
1415             );
1416 0           $self->_dump($root_object, 0, {}, %args);
1417             }
1418              
1419             sub _dump {
1420 0     0     my($self, $obj, $level, $seen, %args) = @_;
1421              
1422 0           my $s = " " x $level;
1423              
1424 0 0         if (!defined $obj) {
1425 0           warn "Undefined object detected in level=$level. Probably children/parent structure or the database is damaged.\n";
1426 0           return $s . "\n";
1427             }
1428              
1429 0 0         if ($seen->{$obj->Id}) {
1430 0           warn "Object with id already seen, no dumping from this point on...\n";
1431 0           return $s . "Id . ">\n";
1432             }
1433 0           $seen->{$obj->Id}++;
1434              
1435             my $shorten = sub {
1436 0 0   0     if (length $_[0] > $_[1]) {
1437 0           substr($_[0], 0, $_[1])
1438             } else {
1439 0           $_[0];
1440             }
1441 0           };
1442             my $langstr = sub {
1443 0     0     langstring($_[0], $self->Root->CurrentLang);
1444 0           };
1445              
1446 0 0         my $title = (defined $obj->Title
    0          
    0          
1447             ? $shorten->($langstr->($obj->Title), defined $obj->Version_Number ? 35-length($obj->Version_Number)-3 : 35)
1448             : "(no title)"
1449             ) . (defined $obj->Version_Number ? " (".$obj->Version_Number.")" : "");
1450              
1451 0 0 0       $s .= sprintf "%s %-35s " . (" "x(13-$level)) . "%-8s %-8s %4d\n",
    0          
    0          
    0          
1452             ($obj->is_sequence
1453             ? "s"
1454             : $obj->is_folder
1455             ? "d"
1456             : defined $obj->Version_Number
1457             ? "v"
1458             : "-"),
1459             $title,
1460             $shorten->($obj->Owner || "(none)", 8),
1461             defined $obj->TimeModified ? WE::Util::Date::short_readable_time(isodate2epoch($obj->TimeModified)) : "(none)",
1462             $obj->Id;
1463 0 0         if ($args{-versions}) {
1464 0           foreach my $sub_obj ($self->versions($obj)) {
1465 0           $s .= $self->_dump($sub_obj, $level+1, $seen, %args);
1466             }
1467             }
1468 0 0         if ($args{-attributes}) {
1469 0           foreach my $key (sort keys %$obj) {
1470 0           my $val = $obj->{$key};
1471 0 0         if (UNIVERSAL::can($val, "dump")) {
1472 0           $val = $val->dump;
1473             }
1474 0 0         if (!defined $val) { $val = "(undef)" }
  0            
1475 0           $s .= " "x($level+1) . "|$key => $val" . "\n";
1476             }
1477 0           my @parent_ids = $self->parent_ids($obj);
1478 0 0         if (@parent_ids > 1) {
1479 0           $s .= " "x($level+1) . "|Multiple parents => @parent_ids\n";
1480             }
1481             }
1482 0 0         if ($args{-callback}) {
1483 0           my $callback_s = $args{-callback}->(-obj => $obj, -level => $level,
1484             -indentstring => " "x($level+1),
1485             );
1486 0 0         $s .= $callback_s if defined $callback_s;
1487             }
1488 0 0 0       if ($obj->is_folder && (!exists $args{-children} || $args{-children})) {
      0        
1489 0           foreach my $sub_obj ($self->children($obj)) {
1490 0           $s .= $self->_dump($sub_obj, $level+1, $seen, %args);
1491             }
1492             }
1493 0           $s;
1494             }
1495              
1496             =item depth($obj_id)
1497              
1498             Get the minimum and maximum depth of the object. There are multiple
1499             depths, because the object can be in multiple parents with different
1500             depths.
1501              
1502             ($min_depth, $max_depth) = $objdb->depth($obj_id);
1503              
1504             =cut
1505              
1506             sub depth {
1507 0     0 1   my($self, $objid) = @_;
1508 0           $self->idify_params($objid);
1509 0           $self->_depth($objid, 0, 0);
1510             }
1511              
1512             # XXX cycle detection? (see link)
1513             sub _depth {
1514 0     0     my($self, $objid, $min_depth, $max_depth) = @_;
1515 0           my($add_min_depth, $add_max_depth);
1516 0           foreach my $p_id ($self->parent_ids($objid)) {
1517 0           my($p_min, $p_max) = $self->depth($p_id);
1518 0 0 0       if (!defined $add_min_depth || $p_min < $add_min_depth) {
1519 0           $add_min_depth = $p_min;
1520             }
1521 0 0 0       if (!defined $add_max_depth || $p_max > $add_max_depth) {
1522 0           $add_max_depth = $p_max;
1523             }
1524             }
1525 0 0         $add_min_depth = 0 if !defined $add_min_depth;
1526 0 0         $add_max_depth = 0 if !defined $add_max_depth;
1527 0           ($min_depth + $add_min_depth + 1, $max_depth + $add_max_depth + 1);
1528             }
1529              
1530             sub _get_next_version {
1531 0     0     my($self, $objid) = @_;
1532 0           $self->idify_params($objid);
1533 0           my @versions = $self->versions($objid);
1534 0           my $max_major;
1535             my $max_minor;
1536 0           foreach my $v (@versions) {
1537 0           my($major, $minor);
1538 0 0         if (defined $v->Version_Number) {
1539 0           ($major, $minor) = split /\./, $v->Version_Number;
1540             }
1541 0 0 0       if (!defined $max_major ||
      0        
      0        
1542             (defined $major && ($major > $max_major ||
1543             ($major == $max_major && $minor > $max_minor))
1544             )
1545             ) {
1546 0           $max_major = $major;
1547 0           $max_minor = $minor;
1548             }
1549             }
1550 0 0         if (!defined $max_major) {
1551 0           "1.0";
1552             } else {
1553 0           $max_minor++;
1554 0           $max_major . "." . $max_minor;
1555             }
1556             }
1557              
1558             =item PATH_SEP
1559              
1560             The default path separator is "/".
1561              
1562             =cut
1563              
1564 15     15   205 use constant PATH_SEP => "/";
  15         48  
  15         23700  
1565              
1566             =item pathname2id($pathname [, $parent_obj])
1567              
1568             Return the object id for the matching "pathname". There are no real
1569             pathnames in the WE_Framework, so a dummy pathname is constructed by
1570             the titles (english, if there are multiple). C is used
1571             as the path separator.
1572              
1573             If C<$parent_obj> is given as a object, then the given pathname should
1574             be only a partial path starting from this parent object.
1575              
1576             Return C if no object could be found.
1577              
1578             =cut
1579              
1580             # XXX cycle test?
1581             sub pathname2id {
1582 0     0 1   my($self, $name, $obj) = @_;
1583 0   0       $obj ||= $self->root_object;
1584 0           my(@c) = split PATH_SEP, $name;
1585 0 0 0       shift @c if (!defined $c[0] || $c[0] eq ''); # for "/"
1586             COMP_LOOP:
1587 0           while (my $component = shift @c) {
1588             # my $component_stripped = $component;
1589             # XXX is this ok? should I check whether the last component is a folder or not?
1590             # if (@c == 0) { # last component
1591 0           (my $component_stripped = $component) =~ s/\.[^.]+$//; # strip extension # XXX for last component (files) ?
1592             # }
1593 0           foreach my $c ($self->children($obj)) {
1594 0           my $base = $c->Basename;
1595 0 0         if (defined $base) {
1596 0           $base = _make_path_component($base);
1597 0 0         if ($component eq $base) {
1598 0           $obj = $c;
1599 0           next COMP_LOOP;
1600             }
1601             } else {
1602 0           $base = langstring($c->Title);
1603 0           $base = _make_path_component($base);
1604 0 0         if ($component_stripped eq $base) {
1605 0           $obj = $c;
1606 0           next COMP_LOOP;
1607             }
1608             }
1609             }
1610 0           return undef;
1611             }
1612 0           $obj->Id;
1613             }
1614              
1615             =item pathname($object_id [, $parent_obj, %args])
1616              
1617             For the object C<$object_id>, the virtual pathname (as described in
1618             pathname2id) is returned.
1619              
1620             If C<$parent_obj> is given as a object, then the returned pathname is
1621             only a partial path starting from this parent object.
1622              
1623             Possible key-values for %args:
1624              
1625             =over
1626              
1627             =item -lang => $lang
1628              
1629             Use the specified language C<$lang> rather than the default language
1630             (en) for title composition.
1631              
1632             =back
1633              
1634             =cut
1635              
1636             # XXX cycle test
1637             # XXX should be more thought on (what about WE::Obj::Sites etc.)
1638             sub pathname {
1639 0     0 1   my($self, $obj, $parent_obj, %args) = @_;
1640 0           $self->objectify_params($obj);
1641 0           my @parents = $self->parent_ids($obj->Id);
1642 0           my $ext = "";
1643 0 0         if ($obj->is_doc) {
1644 0           $ext = "." . $self->Root->ContentDB->extension($obj);
1645             }
1646 0           my $base = $obj->Basename;
1647 0 0         if (!defined $base) {
1648 0 0         my $langstring = (exists $args{-lang}
1649             ? langstring($obj->Title, $args{-lang})
1650             : langstring($obj->Title)
1651             );
1652 0           $base = _make_path_component($langstring) . $ext;
1653             }
1654 0 0 0       if (defined $parent_obj && $obj->Id eq $parent_obj->Id) {
    0          
    0          
1655 0           "";
1656             } elsif ($obj->isa("WE::Obj::Site")) {
1657 0           "/"
1658             } elsif (@parents) {
1659 0           my $parent_path = $self->pathname($parents[0], $parent_obj, %args);
1660 0 0         $parent_path .= PATH_SEP if $parent_path !~ m|^/?$|;
1661 0           $parent_path . $base;
1662             } else {
1663 0           "/$base";
1664             }
1665             }
1666              
1667             sub _make_path_component {
1668 0     0     my $name = shift;
1669 0           $name =~ s/@{[PATH_SEP]}/_/g;
  0            
1670 0           $name;
1671             }
1672              
1673             =item get_released_children($folder_id)
1674              
1675             Return recursive all folders and released children of the given folder
1676             C<$folder_id> as an array of objects.
1677              
1678             =cut
1679              
1680             sub get_released_children {
1681 0     0 1   my($objdb, $folder_id, %args) = @_;
1682 0           my @children = $objdb->children($folder_id);
1683 0           my @res;
1684 0           for my $o (@children) {
1685 0 0         if ($o->is_folder) {
1686 0           push @res, $o;
1687             } else {
1688 0           my $r = $objdb->get_released_object($o->Id, %args);
1689 0 0         push @res, $r if defined $r;
1690             }
1691             }
1692 0           @res;
1693             }
1694              
1695             =item get_released_object($object_id)
1696              
1697             Return the last released version for C<$object_id>. If there is no
1698             released version yet, return C.
1699              
1700             =cut
1701              
1702             sub get_released_object {
1703 0     0 1   my($objdb, $obj_id, %args) = @_;
1704 0           my $obj = $objdb->get_object($obj_id);
1705 0 0         die "Can't get object with id $obj_id" if !$obj;
1706 0           my $releasable = $objdb->is_active_page($obj, %args);
1707 0 0         return undef if (!$releasable);
1708 0 0 0       if (defined $obj->Release_State && $obj->Release_State eq 'released') {
1709 0           return $obj;
1710             }
1711 0           foreach my $v_id (reverse $objdb->version_ids($obj_id)) {
1712 0           my $v = $objdb->get_object($v_id);
1713 0 0 0       if (defined $v->Release_State && $v->Release_State eq 'released') {
1714 0           return $v;
1715             }
1716             }
1717 0           undef;
1718             }
1719              
1720             =item is_active_page($obj)
1721              
1722             Return true if the object $obj is active, that is, the release state
1723             is not I and I/I does not apply.
1724              
1725             =cut
1726              
1727             sub is_active_page {
1728 0     0 1   my($objdb, $o, %args) = @_;
1729 0           $objdb->objectify_params($o);
1730 0           my $now = $args{-now};
1731 0 0         $now = epoch2isodate if !defined $now;
1732             my $active = $objdb->walk_up_preorder
1733             ($o, sub {
1734 0     0     my($obj_id) = @_;
1735 0           my $o = $objdb->get_object($obj_id);
1736 0 0         if (!$o) {
1737 0           warn "Should never happen --- No object for id $obj_id found...";
1738 0           return 1;
1739             }
1740 0 0 0       if (defined $o->Release_State && $o->Release_State eq 'inactive') {
1741 0           $WE::DB::Obj::prune = 1; # cut off subtree
1742             #warn "Inactive object found ($obj_id)\n";
1743 0           return 0;
1744             }
1745              
1746 0 0         if ($o->is_time_restricted) {
1747 0           $WE::DB::Obj::prune = 1; # cut off subtree
1748             #warn "Time restricted object found ($obj_id)\n";
1749 0           return 0;
1750             }
1751 0           1;
1752 0           });
1753 0           $active;
1754             }
1755              
1756             sub count {
1757 0     0 0   my $self = shift;
1758             $self->connect_if_necessary
1759             (sub {
1760 0     0     scalar keys(%{$self->{DB}}) - 2;
  0            
1761 0           });
1762             }
1763              
1764             1;
1765              
1766             __END__