| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # -*- perl -*- | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # $Id: Support.pm,v 1.10 2006/04/18 21:39:44 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 |  |  |  |  |  |  | package WE::Util::Support; | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 2 |  |  | 2 |  | 3567 | use strict; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 53 |  | 
| 20 | 2 |  |  | 2 |  | 9 | use vars qw($VERSION); | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 143 |  | 
| 21 |  |  |  |  |  |  | $VERSION = sprintf("%d.%02d", q$Revision: 1.10 $ =~ /(\d+)\.(\d+)/); | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | =head1 NAME | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | WE::Util::Support - support functions for the WE::DB framework | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | use WE::Util::Support; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | =cut | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | package WE::DB::Obj; | 
| 36 | 2 |  |  | 2 |  | 700 | use WE::DB::Obj; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 17 |  | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | # Backward compatibility... | 
| 39 |  |  |  |  |  |  | if (!WE::DB::Obj->can('idify_params')) { | 
| 40 |  |  |  |  |  |  | #warn "Define alias idify_params"; | 
| 41 |  |  |  |  |  |  | *idify_params = sub { shift->_idify_params(@_) }; | 
| 42 |  |  |  |  |  |  | } | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | =head2 METHODS in the WE::DB::Obj package | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | =over 4 | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | =item create_folder_tree(...) | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | Create a folder tree from file or string. | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | Options are: | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | =over 4 | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | =item -string => $string | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | The string with the folder information. | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | =item -file => $file | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | A file with the folder information. Either -string or -file have to exist. | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | =item -rootid => $rootid | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | The root object id to put the top-most folder in. If missing, then the | 
| 67 |  |  |  |  |  |  | root obejct of the database will be used. | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | =item -standardargs => { ... } | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | Add standard arguments for each created object. The standard arguments | 
| 72 |  |  |  |  |  |  | will be overwritten by the arguments in the folder information | 
| 73 |  |  |  |  |  |  | string/file. | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | =back | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | The file or string should contain a list of attributes, prefixed with | 
| 78 |  |  |  |  |  |  | a "-" and their values. All keys and values should be separated by | 
| 79 |  |  |  |  |  |  | spaces. To mask spaces in attribute values, just use double quotes. | 
| 80 |  |  |  |  |  |  | The folder structure is controlled by indentation, that is, top-level | 
| 81 |  |  |  |  |  |  | folder are not indented, the second-level folders have one space | 
| 82 |  |  |  |  |  |  | indentation and so on. | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | For the B attribute, there is an exception to handle language | 
| 85 |  |  |  |  |  |  | dependent strings: the first two letters should form the ISO 638 code | 
| 86 |  |  |  |  |  |  | for the language, followed by a colon, followed by the language | 
| 87 |  |  |  |  |  |  | dependent string. You can specify as many language strings as you | 
| 88 |  |  |  |  |  |  | like. If you do not need language dependent Title strings, then just | 
| 89 |  |  |  |  |  |  | use this attribute as the others. | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | Examples for B: | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | -Title "language independent string" -Name a_name | 
| 94 |  |  |  |  |  |  | -Title "en:english" "de:german" "it:italian" -Name b_name | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | =cut | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | sub create_folder_tree { | 
| 99 | 0 |  |  | 0 | 0 |  | my($self, %args) = @_; | 
| 100 | 0 |  |  |  |  |  | my $string = $args{'-string'}; | 
| 101 | 0 | 0 |  |  |  |  | if (!defined $string) { | 
| 102 | 0 | 0 |  |  |  |  | if (exists $args{-file}) { | 
| 103 | 0 | 0 |  |  |  |  | open(F, $args{-file}) or die "Can't open $args{-file}: $!"; | 
| 104 | 0 |  |  |  |  |  | local $/ = undef; | 
| 105 | 0 |  |  |  |  |  | $string = ; | 
| 106 | 0 |  |  |  |  |  | close F; | 
| 107 |  |  |  |  |  |  | } else { | 
| 108 | 0 |  |  |  |  |  | die "Either -file or -string must be given"; | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  | } | 
| 111 | 0 |  |  |  |  |  | my $rootid = $args{'-rootid'}; | 
| 112 | 0 | 0 |  |  |  |  | if (!defined $rootid) { | 
| 113 | 0 |  |  |  |  |  | $rootid = $self->root_object->Id; | 
| 114 |  |  |  |  |  |  | } | 
| 115 | 0 | 0 |  |  |  |  | my %std_args = ($args{'-standardargs'} ? %{$args{'-standardargs'}} : ()); | 
|  | 0 |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | # first pass: just check the indentation | 
| 118 | 0 |  |  |  |  |  | my $last_indent = 0; | 
| 119 | 0 |  |  |  |  |  | foreach my $line (split /\n/, $string) { | 
| 120 | 0 |  |  |  |  |  | $line =~ /^(\s*)/; | 
| 121 | 0 |  |  |  |  |  | my $indent = length $1; | 
| 122 | 0 | 0 |  |  |  |  | if ($indent > $last_indent+1) { | 
| 123 | 0 |  |  |  |  |  | die "Indentation error in line <$line>\n"; | 
| 124 |  |  |  |  |  |  | } | 
| 125 | 0 |  |  |  |  |  | $last_indent = $indent; | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | # second pass: do the right thing | 
| 129 | 0 |  |  |  |  |  | require Text::ParseWords; | 
| 130 | 0 |  |  |  |  |  | my @indent2objid; | 
| 131 | 0 |  |  |  |  |  | $indent2objid[0] = $rootid; | 
| 132 | 0 |  |  |  |  |  | foreach my $line (split /\n/, $string) { | 
| 133 | 0 |  |  |  |  |  | $line =~ s/^(\s*)//; | 
| 134 | 0 |  |  |  |  |  | my $indent = length $1; | 
| 135 | 0 |  |  |  |  |  | my $parentid = $indent2objid[$indent]; | 
| 136 | 0 | 0 |  |  |  |  | if (!defined $parentid) { | 
| 137 | 0 |  |  |  |  |  | die "Parse error in line $line? Check the indentation!\n"; | 
| 138 |  |  |  |  |  |  | } | 
| 139 | 0 |  |  |  |  |  | my %token; | 
| 140 | 0 |  |  |  |  |  | my(@token) = Text::ParseWords::shellwords($line); | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | # special handling for languagestrings in Title: | 
| 143 | 0 |  |  |  |  |  | for(my $i=0; $i<=$#token; ) { | 
| 144 | 0 |  |  |  |  |  | my($key, $val) = @token[$i, $i+1]; | 
| 145 | 0 | 0 |  |  |  |  | if ($key eq '-Title') { | 
| 146 | 0 | 0 |  |  |  |  | if ($val =~ /^(..):(.*)/) { | 
| 147 | 0 |  |  |  |  |  | my($lang, $string) = ($1,$2); | 
| 148 | 0 |  |  |  |  |  | require WE::Util::LangString; | 
| 149 | 0 |  |  |  |  |  | my $ls = WE::Util::LangString->new; | 
| 150 | 0 |  |  |  |  |  | $ls->{$lang} = $string; | 
| 151 | 0 |  |  |  |  |  | $i+=2; | 
| 152 | 0 |  |  |  |  |  | while (1) { | 
| 153 | 0 |  |  |  |  |  | $val = $token[$i]; | 
| 154 | 0 | 0 | 0 |  |  |  | if (!defined $val || $val =~ /^-/) { | 
|  |  | 0 |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | # next option/no option => write langstring | 
| 156 | 0 |  |  |  |  |  | $token{$key} = $ls; | 
| 157 | 0 |  |  |  |  |  | last; | 
| 158 |  |  |  |  |  |  | } elsif ($val =~ /^(..):(.*)/) { | 
| 159 | 0 |  |  |  |  |  | my($lang, $string) = ($1,$2); | 
| 160 | 0 |  |  |  |  |  | $ls->{$lang} = $string; | 
| 161 | 0 |  |  |  |  |  | $i++; | 
| 162 |  |  |  |  |  |  | } else { | 
| 163 | 0 |  |  |  |  |  | die "Parse error in line $line: either language string or new option expected, but got $val"; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  | } else { | 
| 167 | 0 |  |  |  |  |  | $token{$key} = $val; | 
| 168 | 0 |  |  |  |  |  | $i+=2; | 
| 169 |  |  |  |  |  |  | } | 
| 170 |  |  |  |  |  |  | } else { | 
| 171 | 0 |  |  |  |  |  | $token{$key} = $val; | 
| 172 | 0 |  |  |  |  |  | $i+=2; | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  |  | 
| 176 | 0 |  |  |  |  |  | my %args = (%std_args, %token); | 
| 177 | 0 |  |  |  |  |  | $args{'-parent'} = $parentid; | 
| 178 | 0 |  |  |  |  |  | my $fldr = $self->insert_folder(%args); # XXX handle langstrings? | 
| 179 | 0 |  |  |  |  |  | my $newobjid = $fldr->Id; | 
| 180 | 0 |  |  |  |  |  | $indent2objid[$indent+1] = $newobjid; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  | } | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | =item change_order($p_id, \@child_ids) | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | For the parent collection C<$p_id> the children will be sorted | 
| 187 |  |  |  |  |  |  | according to the array reference C<@child_ids>. Children of C<$p_id> | 
| 188 |  |  |  |  |  |  | which are not in C<@child_ids> will be put to the back. Throws an | 
| 189 |  |  |  |  |  |  | error if one of C<@child_ids> is not a child of C<$p_id>. | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | =cut | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | sub change_order { | 
| 194 | 0 |  |  | 0 | 0 |  | my($self, $p_id, $child_ids_ref) = @_; | 
| 195 | 0 |  |  |  |  |  | my(@child_ids) = @$child_ids_ref; | 
| 196 | 0 |  |  |  |  |  | $self->idify_params($p_id, @child_ids); | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | # The connect is here used as a locking mechanism. | 
| 199 |  |  |  |  |  |  | $self->connect_if_necessary | 
| 200 |  |  |  |  |  |  | (sub { | 
| 201 | 0 |  |  | 0 |  |  | my $p_stored_obj = $self->_get_stored_obj($p_id); | 
| 202 | 0 | 0 |  |  |  |  | die "Can't get stored object for <$p_id>" if !$p_stored_obj; | 
| 203 |  |  |  |  |  |  | # first check whether the children are really the children: | 
| 204 | 0 |  |  |  |  |  | my @real_children = $self->children_ids($p_id); | 
| 205 | 0 |  |  |  |  |  | my(%real_children) = map { ($_=>1) } @real_children; | 
|  | 0 |  |  |  |  |  |  | 
| 206 | 0 |  |  |  |  |  | foreach my $c_id (@child_ids) { | 
| 207 | 0 | 0 |  |  |  |  | if (!exists $real_children{$c_id}) { | 
| 208 | 0 |  |  |  |  |  | die "The object <$c_id> is not child of <$p_id>!"; | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  | } | 
| 211 | 0 |  |  |  |  |  | my @new_child_list = @child_ids; | 
| 212 | 0 |  |  |  |  |  | foreach (@child_ids) { delete $real_children{$_} } | 
|  | 0 |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | # put missing ids to the back | 
| 214 | 0 | 0 |  |  |  |  | if (keys %real_children) { | 
| 215 | 0 |  |  |  |  |  | warn "The following ids are unhandled by change_order: @{[ keys %real_children ]}"; | 
|  | 0 |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | # iterate over array to preserver old order | 
| 217 | 0 |  |  |  |  |  | foreach (@real_children) { | 
| 218 | 0 | 0 |  |  |  |  | if (exists $real_children{$_}) { | 
| 219 | 0 |  |  |  |  |  | push @new_child_list, $_; | 
| 220 | 0 |  |  |  |  |  | delete $real_children{$_}; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  | } | 
| 223 | 0 | 0 |  |  |  |  | if (keys %real_children) { | 
| 224 | 0 |  |  |  |  |  | die "Strange! There are still some keys unhandled: @{[ keys %real_children ]}"; | 
|  | 0 |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 0 |  |  |  |  |  | $p_stored_obj->[WE::DB::Obj::CHILDREN] = \@new_child_list; | 
| 229 | 0 |  |  |  |  |  | $self->_store_stored_obj($p_stored_obj); | 
| 230 | 0 |  |  |  |  |  | }); | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | =item get_position_array($obj_id, [%args]) | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | For a given object C<$obj_id>, return an array with the positions of | 
| 236 |  |  |  |  |  |  | this object in its parent, and the positions of all predecessors. | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | The following options are recognized for C<%args>: | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | =over 4 | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | =item -base => C<$base> | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | The positions are by default 0-based, but can be changed with the | 
| 245 |  |  |  |  |  |  | optional argument C<$base>. | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | =item -filter => C<$sub> | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | Specify an optional filter for the recursion process. The filter | 
| 250 |  |  |  |  |  |  | options should be a reference to a subroutine. This subroutine is | 
| 251 |  |  |  |  |  |  | called with two arguments: the object database reference and the id of | 
| 252 |  |  |  |  |  |  | an object to test. If the object should be included into the position | 
| 253 |  |  |  |  |  |  | counting, then 1 should be returned, otherwise 0. | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | =item -indexdoc => C<$bool> | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | If true, then the IndexDoc attribute is used for determining the | 
| 258 |  |  |  |  |  |  | document position. | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | =back | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | Note that only the first parent is used, if there are objects with | 
| 263 |  |  |  |  |  |  | multiple parents. | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | =cut | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | sub get_position_array { | 
| 268 | 0 |  |  | 0 | 0 |  | my($self, $obj_id, %args) = @_; | 
| 269 | 0 | 0 |  |  |  |  | my $base = defined $args{-base} ? $args{-base} : 0; | 
| 270 | 0 |  |  |  |  |  | my $filter = $args{-filter}; | 
| 271 | 0 |  |  |  |  |  | $self->idify_params($obj_id); | 
| 272 | 0 |  |  |  |  |  | my($p_id) = ($self->parent_ids($obj_id))[0]; | 
| 273 | 0 | 0 |  |  |  |  | return () if !defined $p_id; | 
| 274 | 0 |  |  |  |  |  | my(@children_ids) = $self->children_ids($p_id); | 
| 275 |  |  |  |  |  |  |  | 
| 276 | 0 | 0 |  |  |  |  | if ($args{'-indexdoc'}) { | 
| 277 | 0 |  |  |  |  |  | my $p_obj = $self->get_object($p_id); | 
| 278 | 0 | 0 |  |  |  |  | if (!$p_obj) { | 
| 279 | 0 |  |  |  |  |  | die "Can't get object for id $p_id"; | 
| 280 |  |  |  |  |  |  | } | 
| 281 | 0 | 0 | 0 |  |  |  | if ($p_obj->{IndexDoc} && $p_obj->{IndexDoc} == $obj_id) { | 
| 282 | 0 |  |  |  |  |  | return $self->get_position_array($p_id, %args); | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  | } | 
| 285 |  |  |  |  |  |  |  | 
| 286 | 0 |  |  |  |  |  | my $this_pos; | 
| 287 | 0 |  |  |  |  |  | my $pos = 0; | 
| 288 | 0 |  |  |  |  |  | for my $c_id (@children_ids) { | 
| 289 | 0 | 0 | 0 |  |  |  | if ($filter && !$filter->($self, $c_id)) { | 
| 290 | 0 |  |  |  |  |  | next; | 
| 291 |  |  |  |  |  |  | } | 
| 292 | 0 | 0 |  |  |  |  | if ($c_id eq $obj_id) { | 
| 293 | 0 |  |  |  |  |  | $this_pos = $pos + $base; | 
| 294 | 0 |  |  |  |  |  | last; | 
| 295 |  |  |  |  |  |  | } | 
| 296 | 0 |  |  |  |  |  | $pos++; | 
| 297 |  |  |  |  |  |  | } | 
| 298 | 0 | 0 |  |  |  |  | if (!defined $this_pos) { | 
| 299 | 0 |  |  |  |  |  | my $err = "Strange: can't find object <$obj_id> in the children collection of its parent <$p_id>."; | 
| 300 | 0 | 0 |  |  |  |  | if ($filter) { | 
| 301 | 0 |  |  |  |  |  | $err .= "\nMaybe the filter was to strict?"; | 
| 302 |  |  |  |  |  |  | } | 
| 303 | 0 |  |  |  |  |  | die $err; | 
| 304 |  |  |  |  |  |  | } | 
| 305 | 0 |  |  |  |  |  | ($self->get_position_array($p_id, -filter => $filter, -base => $base), $this_pos); | 
| 306 |  |  |  |  |  |  | } | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | =item check_integrity | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | Return a C object with lists of inconsistencies in | 
| 311 |  |  |  |  |  |  | the C database. | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | =cut | 
| 314 |  |  |  |  |  |  |  | 
| 315 |  |  |  |  |  |  | sub check_integrity { | 
| 316 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 317 | 0 |  |  |  |  |  | my $contentdb = shift; | 
| 318 |  |  |  |  |  |  |  | 
| 319 | 0 |  |  |  |  |  | my @undef_values; | 
| 320 |  |  |  |  |  |  | my @broken_values; | 
| 321 | 0 |  |  |  |  |  | my @not_existing_children; | 
| 322 | 0 |  |  |  |  |  | my @not_existing_parents; | 
| 323 | 0 |  |  |  |  |  | my @not_existing_versions; | 
| 324 | 0 |  |  |  |  |  | my @not_referenced; | 
| 325 | 0 |  |  |  |  |  | my @wrong_ids; | 
| 326 | 0 |  |  |  |  |  | my @child_parent_mismatches; | 
| 327 | 0 |  |  |  |  |  | my @doc_object_without_content; | 
| 328 |  |  |  |  |  |  |  | 
| 329 | 0 |  |  |  |  |  | my $root_object_missing = 0; | 
| 330 |  |  |  |  |  |  |  | 
| 331 | 0 |  |  |  |  |  | my %referenced; | 
| 332 |  |  |  |  |  |  |  | 
| 333 | 0 |  |  |  |  |  | my $root_obj = $self->root_object; | 
| 334 | 0 | 0 |  |  |  |  | if (!$root_obj) { | 
| 335 |  |  |  |  |  |  | # This is fatal, don't do any other checks | 
| 336 | 0 |  |  |  |  |  | $root_object_missing = 1; | 
| 337 | 0 |  |  |  |  |  | goto RETURN; | 
| 338 |  |  |  |  |  |  | } else { | 
| 339 | 0 |  |  |  |  |  | $referenced{$root_obj->Id} = []; | 
| 340 |  |  |  |  |  |  | } | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | $self->connect_if_necessary(sub { | 
| 343 | 0 |  |  | 0 |  |  | my @keys = grep { !/^_/ } keys %{ $self->{DB} }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | # XXX Can't use while...each --- segfault with 5.8.0 | 
| 345 | 0 |  |  |  |  |  | for my $k (@keys) { | 
| 346 | 0 |  |  |  |  |  | my $v = $self->{DB}{$k}; | 
| 347 | 0 | 0 | 0 |  |  |  | if (!defined $v) { | 
|  |  | 0 |  |  |  |  |  | 
| 348 | 0 |  |  |  |  |  | push @undef_values, $k; | 
| 349 |  |  |  |  |  |  | } elsif (ref $v ne 'ARRAY' || @$v != 4) { | 
| 350 | 0 |  |  |  |  |  | push @broken_values, $k; | 
| 351 |  |  |  |  |  |  | } else { | 
| 352 | 0 | 0 |  |  |  |  | if ($v->[OBJECT]->{Id} ne $k) { | 
| 353 | 0 |  |  |  |  |  | push @wrong_ids, $k; | 
| 354 |  |  |  |  |  |  | } | 
| 355 | 0 |  |  |  |  |  | my(@children_ids) = @{$v->[CHILDREN]}; | 
|  | 0 |  |  |  |  |  |  | 
| 356 | 0 |  |  |  |  |  | my(@parent_ids)   = @{$v->[PARENTS]}; | 
|  | 0 |  |  |  |  |  |  | 
| 357 | 0 |  |  |  |  |  | my(@version_ids)  = @{$v->[VERSIONS]}; | 
|  | 0 |  |  |  |  |  |  | 
| 358 | 0 |  |  |  |  |  | for my $def (["c", \@children_ids, \@not_existing_children], | 
| 359 |  |  |  |  |  |  | ["p", \@parent_ids,   \@not_existing_parents], | 
| 360 |  |  |  |  |  |  | ["v", \@version_ids,  \@not_existing_versions], | 
| 361 |  |  |  |  |  |  | ) { | 
| 362 | 0 |  |  |  |  |  | my($type, $ids, $res) = @$def; | 
| 363 | 0 |  |  |  |  |  | for my $id (@$ids) { | 
| 364 | 0 | 0 |  |  |  |  | if (!exists $self->{DB}{$id}) { | 
| 365 | 0 |  |  |  |  |  | push @$res, [$k, $id]; | 
| 366 |  |  |  |  |  |  | } else { | 
| 367 | 0 | 0 |  |  |  |  | if ($type ne "p") { | 
| 368 | 0 |  |  |  |  |  | push @{$referenced{$id}}, $k; | 
|  | 0 |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | } | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  | } | 
| 372 |  |  |  |  |  |  | } | 
| 373 | 0 |  |  |  |  |  | for my $id (@children_ids) { | 
| 374 | 0 | 0 |  |  |  |  | if (exists $self->{DB}{$id}) { | 
| 375 | 0 |  |  |  |  |  | my $c = $self->{DB}{$id}; | 
| 376 | 0 |  |  |  |  |  | CHECK_PARENT: { | 
| 377 | 0 |  |  |  |  |  | for my $p_id (@{$c->[PARENTS]}) { | 
|  | 0 |  |  |  |  |  |  | 
| 378 | 0 | 0 |  |  |  |  | if ($p_id eq $k) { | 
| 379 | 0 |  |  |  |  |  | last CHECK_PARENT; | 
| 380 |  |  |  |  |  |  | } | 
| 381 |  |  |  |  |  |  | } | 
| 382 | 0 |  |  |  |  |  | push @child_parent_mismatches, [$k, $id]; | 
| 383 |  |  |  |  |  |  | } | 
| 384 |  |  |  |  |  |  | } | 
| 385 |  |  |  |  |  |  | } | 
| 386 | 0 | 0 |  |  |  |  | if ($contentdb) { | 
| 387 | 0 |  |  |  |  |  | my $o = $v->[WE::DB::Obj::OBJECT]; | 
| 388 | 0 | 0 |  |  |  |  | if ($o->is_doc) { | 
| 389 | 0 |  |  |  |  |  | my $f = $contentdb->filename($o); | 
| 390 | 0 | 0 |  |  |  |  | if (!-e $f) { | 
| 391 | 0 |  |  |  |  |  | push @doc_object_without_content, $o->Id; | 
| 392 |  |  |  |  |  |  | } | 
| 393 |  |  |  |  |  |  | } | 
| 394 |  |  |  |  |  |  | } | 
| 395 |  |  |  |  |  |  | } | 
| 396 |  |  |  |  |  |  | } | 
| 397 |  |  |  |  |  |  |  | 
| 398 | 0 |  |  |  |  |  | for my $k (@keys) { | 
| 399 | 0 | 0 |  |  |  |  | if (!exists $referenced{$k}) { | 
| 400 | 0 |  |  |  |  |  | push @not_referenced, $k; | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  | } | 
| 403 | 0 |  |  |  |  |  | }); | 
| 404 |  |  |  |  |  |  |  | 
| 405 | 0 |  |  |  |  |  | RETURN: | 
| 406 | 0 |  |  |  |  |  | @not_existing_children = sort {$a->[0]<=>$b->[0]} @not_existing_children; | 
| 407 | 0 |  |  |  |  |  | @not_existing_parents  = sort {$a->[0]<=>$b->[0]} @not_existing_parents; | 
|  | 0 |  |  |  |  |  |  | 
| 408 | 0 |  |  |  |  |  | @not_existing_versions = sort {$a->[0]<=>$b->[0]} @not_existing_versions; | 
|  | 0 |  |  |  |  |  |  | 
| 409 | 0 |  |  |  |  |  | @not_referenced        = sort {$a<=>$b} @not_referenced; | 
|  | 0 |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  |  | 
| 411 | 0 |  |  |  |  |  | bless { "undef_values"               => \@undef_values, | 
| 412 |  |  |  |  |  |  | "broken_values"              => \@broken_values, | 
| 413 |  |  |  |  |  |  | "wrong_ids"	                 => \@wrong_ids, | 
| 414 |  |  |  |  |  |  | "not_existing_children"      => \@not_existing_children, | 
| 415 |  |  |  |  |  |  | "not_existing_parents"       => \@not_existing_parents, | 
| 416 |  |  |  |  |  |  | "not_existing_versions"      => \@not_existing_versions, | 
| 417 |  |  |  |  |  |  | "not_referenced"             => \@not_referenced, | 
| 418 |  |  |  |  |  |  | "child_parent_mismatches"    => \@child_parent_mismatches, | 
| 419 |  |  |  |  |  |  | "doc_object_without_content" => \@doc_object_without_content, | 
| 420 |  |  |  |  |  |  | "root_object_missing"	 => $root_object_missing, | 
| 421 |  |  |  |  |  |  | }, 'WE::DB::Obj::Fsck'; | 
| 422 |  |  |  |  |  |  | } | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | =item repair_database($errors, %args) | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | Take a C object with lists of inconsistencies and | 
| 427 |  |  |  |  |  |  | tries to repair the C database. C<%args> may be: | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | =over | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | =item -verbose | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | Be verbose if set to a true value. | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | =back | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | =cut | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | # errors is the return value of check_integrity() (a WE::DB::Obj::Fsck object) | 
| 440 |  |  |  |  |  |  | sub repair_database { | 
| 441 | 0 |  |  | 0 | 0 |  | my($self, $errors, %args) = @_; | 
| 442 | 0 | 0 |  |  |  |  | my $v = 1 if $args{-verbose}; | 
| 443 | 0 |  |  |  |  |  | my $root_object_id = $args{-rootobjectid}; | 
| 444 |  |  |  |  |  |  | $self->connect_if_necessary(sub { | 
| 445 | 0 | 0 |  | 0 |  |  | if ($errors->{root_object_missing}) { | 
| 446 | 0 | 0 |  |  |  |  | if (!defined $root_object_id) { | 
| 447 | 0 |  |  |  |  |  | die "rootobjectid not specified"; | 
| 448 |  |  |  |  |  |  | } | 
| 449 | 0 |  |  |  |  |  | $self->{DB}{'_root_object'} = $root_object_id; | 
| 450 |  |  |  |  |  |  | } | 
| 451 | 0 |  |  |  |  |  | foreach my $id (@{ $errors->{"not_referenced"} }) { | 
|  | 0 |  |  |  |  |  |  | 
| 452 | 0 | 0 |  |  |  |  | warn "Remove object $id from database\n" if $v; | 
| 453 | 0 |  |  |  |  |  | delete $self->{DB}{$id}; | 
| 454 |  |  |  |  |  |  | } | 
| 455 | 0 |  |  |  |  |  | for my $def ([CHILDREN, "not_existing_children", "child"], | 
| 456 |  |  |  |  |  |  | [PARENTS,  "not_existing_parents",  "parent"], | 
| 457 |  |  |  |  |  |  | [VERSIONS, "not_existing_versions", "version"], | 
| 458 |  |  |  |  |  |  | ) { | 
| 459 | 0 |  |  |  |  |  | my($index, $key, $label) = @$def; | 
| 460 | 0 |  |  |  |  |  | for my $def2 (@{ $errors->{$key} }) { | 
|  | 0 |  |  |  |  |  |  | 
| 461 | 0 |  |  |  |  |  | my($id, $refid) = @$def2; | 
| 462 | 0 |  |  |  |  |  | my $o = $self->{DB}{$id}; | 
| 463 | 0 | 0 |  |  |  |  | if (!$o) { | 
| 464 | 0 |  |  |  |  |  | warn "Object $id does not exist anymore, skipping...\n"; | 
| 465 |  |  |  |  |  |  | } else { | 
| 466 | 0 | 0 |  |  |  |  | warn "Remove $label $refid from $id\n" if $v; | 
| 467 | 0 |  |  |  |  |  | @{$o->[$index]} = grep { $_ ne $refid } @{$o->[$index]}; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 468 | 0 |  |  |  |  |  | $self->{DB}{$id} = $o; | 
| 469 |  |  |  |  |  |  | } | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  | } | 
| 472 | 0 | 0 |  |  |  |  | if ($errors->{doc_object_without_content}) { | 
| 473 | 0 |  |  |  |  |  | for my $id (@{ $errors->{doc_object_without_content} }) { | 
|  | 0 |  |  |  |  |  |  | 
| 474 | 0 | 0 |  |  |  |  | warn "Remove doc object $id from database (no content file)\n" | 
| 475 |  |  |  |  |  |  | if $v; | 
| 476 | 0 |  |  |  |  |  | delete $self->{DB}{$id}; | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  | } | 
| 479 | 0 | 0 |  |  |  |  | warn "Cannot repair undef_values\n" if @{ $errors->{"undef_values"} }; | 
|  | 0 |  |  |  |  |  |  | 
| 480 | 0 | 0 |  |  |  |  | warn "Cannot repair broken_values\n" if @{ $errors->{"broken_values"} }; | 
|  | 0 |  |  |  |  |  |  | 
| 481 | 0 |  |  |  |  |  | }); | 
| 482 |  |  |  |  |  |  | } | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | =back | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | =cut | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | package WE::DB::Content; | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | =head2 METHODS in the WE::DB::Content package | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | =over | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | =item check_integrity($objdb) | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | Return a C object with lists of inconsistencies | 
| 497 |  |  |  |  |  |  | in the C database. The check is done against the | 
| 498 |  |  |  |  |  |  | C database C<$objdb>. | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | =cut | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | sub check_integrity { | 
| 503 | 0 |  |  | 0 | 0 |  | my($self, $objdb) = @_; | 
| 504 |  |  |  |  |  |  |  | 
| 505 | 0 |  |  |  |  |  | my @extra_files; | 
| 506 |  |  |  |  |  |  | my @unreferenced_files; | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | $objdb->connect_if_necessary(sub { | 
| 509 | 0 | 0 |  | 0 |  |  | opendir(D, $self->Directory) or die "Can't open " . $self->Directory . ": $!"; | 
| 510 | 0 |  |  |  |  |  | while(my $f = readdir D) { | 
| 511 | 0 | 0 |  |  |  |  | next if $f =~ /^\.\.?$/; | 
| 512 | 0 |  |  |  |  |  | my($id) = $f =~ /^(\d+)\./; | 
| 513 | 0 | 0 |  |  |  |  | if (defined $id) { | 
| 514 | 0 | 0 |  |  |  |  | if (!exists $objdb->{DB}{$id}) { | 
| 515 | 0 |  |  |  |  |  | push @unreferenced_files, $f; | 
| 516 |  |  |  |  |  |  | } | 
| 517 |  |  |  |  |  |  | } else { | 
| 518 | 0 | 0 |  |  |  |  | if ($f !~ /^(\.svn|CVS|\.cvsignore|\.keep_me|RCS)$/) { | 
| 519 | 0 |  |  |  |  |  | push @extra_files, $f; | 
| 520 |  |  |  |  |  |  | } | 
| 521 |  |  |  |  |  |  | } | 
| 522 |  |  |  |  |  |  | } | 
| 523 | 0 |  |  |  |  |  | closedir D; | 
| 524 | 0 |  |  |  |  |  | }); | 
| 525 |  |  |  |  |  |  |  | 
| 526 | 0 |  |  |  |  |  | bless {"unreferenced_files"         => \@unreferenced_files, | 
| 527 |  |  |  |  |  |  | "extra_files"                => \@extra_files, | 
| 528 |  |  |  |  |  |  | }, 'WE::DB::Content::Fsck'; | 
| 529 |  |  |  |  |  |  | } | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | =item repair_database($errors, %args) | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | Take a C object with lists of inconsistencies | 
| 534 |  |  |  |  |  |  | and tries to repair the C database. C<%args> may be: | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | =over | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | =item -verbose | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | Be verbose if set to a true value. | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | =back | 
| 543 |  |  |  |  |  |  |  | 
| 544 |  |  |  |  |  |  | =cut | 
| 545 |  |  |  |  |  |  |  | 
| 546 |  |  |  |  |  |  | # errors is the return value of check_integrity() (a | 
| 547 |  |  |  |  |  |  | # WE::DB::Content::Fsck object) | 
| 548 |  |  |  |  |  |  | sub repair_database { | 
| 549 | 0 |  |  | 0 | 0 |  | my($self, $errors, %args) = @_; | 
| 550 | 0 | 0 |  |  |  |  | my $v = 1 if $args{-verbose}; | 
| 551 | 0 |  |  |  |  |  | require File::Spec; | 
| 552 | 0 |  |  |  |  |  | for my $file (@{ $errors->{unreferenced_files} }) { | 
|  | 0 |  |  |  |  |  |  | 
| 553 | 0 |  |  |  |  |  | my $path = File::Spec->catfile($self->Directory, $file); | 
| 554 | 0 |  |  |  |  |  | warn "unlink $path\n"; | 
| 555 | 0 | 0 |  |  |  |  | unlink $path or warn "Can't unlink $path: $!"; | 
| 556 |  |  |  |  |  |  | } | 
| 557 | 0 | 0 |  |  |  |  | if (@{ $errors->{extra_files} }) { | 
|  | 0 |  |  |  |  |  |  | 
| 558 | 0 |  |  |  |  |  | warn "Please remove extra files manually, e.g. with | 
| 559 | 0 |  |  |  |  |  | cd @{[ $self->Directory ]} && rm -i @{ $errors->{extra_files} } | 
|  | 0 |  |  |  |  |  |  | 
| 560 |  |  |  |  |  |  | "; | 
| 561 |  |  |  |  |  |  | } | 
| 562 |  |  |  |  |  |  | } | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  |  | 
| 565 |  |  |  |  |  |  | package WE::DB::Fsck; | 
| 566 |  |  |  |  |  |  |  | 
| 567 |  |  |  |  |  |  | sub has_errors { | 
| 568 | 0 |  |  | 0 |  |  | my $errors = shift; | 
| 569 | 0 |  |  |  |  |  | foreach (values %$errors) { | 
| 570 | 0 | 0 | 0 |  |  |  | if (ref $_ eq 'ARRAY' && @$_) { | 
| 571 | 0 |  |  |  |  |  | return 1; | 
| 572 |  |  |  |  |  |  | } | 
| 573 |  |  |  |  |  |  | } | 
| 574 | 0 | 0 |  |  |  |  | if ($errors->has_fatal_errors) { | 
| 575 | 0 |  |  |  |  |  | return 1; | 
| 576 |  |  |  |  |  |  | } | 
| 577 | 0 |  |  |  |  |  | 0; | 
| 578 |  |  |  |  |  |  | } | 
| 579 |  |  |  |  |  |  |  | 
| 580 |  |  |  |  |  |  | sub has_fatal_errors { | 
| 581 | 0 |  |  | 0 |  |  | my $errors = shift; | 
| 582 | 0 | 0 |  |  |  |  | return $errors->{root_object_missing} ? 1 : 0; | 
| 583 |  |  |  |  |  |  | } | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | package WE::DB::Obj::Fsck; | 
| 586 |  |  |  |  |  |  | @WE::DB::Obj::Fsck::ISA = 'WE::DB::Fsck'; | 
| 587 |  |  |  |  |  |  |  | 
| 588 |  |  |  |  |  |  | package WE::DB::Content::Fsck; | 
| 589 |  |  |  |  |  |  | @WE::DB::Content::Fsck::ISA = 'WE::DB::Fsck'; | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  | 1; | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | __END__ |