source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Objects.pm @ 2280

Last change on this file since 2280 was 2280, checked in by nanardon, 5 years ago

la-dump: add support for json and yaml, allow to write into files

  • Property svn:keywords set to Id Rev
File size: 24.5 KB
Line 
1package LATMOS::Accounts::Bases::Objects;
2
3use 5.010000;
4use strict;
5use warnings;
6
7use overload '""' => 'stringify';
8
9use LATMOS::Accounts::Log;
10use LATMOS::Accounts::Bases::Attributes;
11use Crypt::Cracklib;
12
13our $VERSION = (q$Rev: 2072 $ =~ /^Rev: (\d+) /)[0];
14
15=head1 NAME
16
17LATMOS::Accounts::Bases::Objects - Base class for account objects
18
19=head1 SYNOPSIS
20
21  use LATMOS::Accounts::Bases::Objects;
22  LATMOS::Accounts::Bases::Objects->new($base, $type, $id);
23
24=head1 DESCRIPTION
25
26=head1 FUNCTIONS
27
28=cut
29
30=head2 is_supported
31
32If exists, must return true or false if the object is supported or not
33
34=cut
35
36=head2 list($base)
37
38List object supported by this module existing in base $base
39
40Must be provide by object class
41
42    sub list {
43        my ($class, $base) = @_;
44    }
45
46=cut
47
48=head2 listReal($base)
49
50List object supported by this module existing in base $base
51
52Can be override by base driver. The result must exclude specials object such alias.
53
54=cut
55
56sub listReal {
57    my ($class, $base) = @_;
58    $class->list($base);
59}
60
61=head2 list_from_rev($base, $rev)
62
63List objects create or modified after base revision C<$rev>.
64
65=cut
66
67=head2 new($base, $id)
68
69Create a new object having $id as uid.
70
71=cut
72
73sub new {
74    my ($class, $base, $id, @args) = @_;
75    # So can be call as $class->SUPER::new()
76    bless {
77        _base => $base,
78        _type => lc(($class =~ m/::([^:]*)$/)[0]),
79        _id => $id,
80    }, $class;
81}
82
83=head2 _create($class, $base, $id, %data)
84
85Must create a new object in database.
86
87Is called if underling base does not override create_object
88
89    sub _create(
90        my ($class, $base, $id, %data)
91    }
92
93=cut
94
95=head2 type
96
97Return the type of the object
98
99=cut
100
101sub type {
102    my ($self) = @_;
103    if (ref $self) {
104        return $self->{_type}
105    } else {
106        return lc(($self =~ /::([^:]+)$/)[0]);
107    }
108}
109
110=head2 base
111
112Return the base handle for this object.
113
114=cut
115
116sub base {
117    return $_[0]->{_base}
118}
119
120=head2 id
121
122Must return the unique identifier for this object
123
124=cut
125
126sub id {
127    my ($self) = @_;
128    $self->{_id}
129}
130
131=head2 Iid
132
133Return internal id if different from Id
134
135=cut
136
137sub Iid {
138    my ($self) = @_;
139    $self->id
140}
141
142=head2 stringify
143
144Display object as a string
145
146=cut
147
148sub stringify {
149    my ($self) = @_;
150
151    return $self->id
152}
153
154=head2 list_canonical_fields($for)
155
156Object shortcut to get the list of field supported by the object.
157
158=cut
159
160sub list_canonical_fields {
161    my ($self, $for) = @_;
162    $for ||= 'rw';
163    $self->_canonical_fields($for);
164}
165
166=head2 attribute ($attribute)
167
168Return L<LATMOS::Accounts::Bases::Attributes> object for C<$attribute>
169
170=cut
171
172sub attribute {
173    my ($self, $attribute) = @_;
174
175    my $attrinfo;
176    if (! ref $attribute) {
177        $attrinfo = $self->_get_attr_schema(
178            $self->base)->{$attribute}
179        or return;
180        $attrinfo->{name} = $attribute;
181    } else {
182        $attrinfo = $attribute;
183    }
184
185    return LATMOS::Accounts::Bases::Attributes->new(
186        $attrinfo,
187        $self,
188    );
189}   
190
191sub _canonical_fields {
192    my ($class, $base, $for) = @_;
193    $for ||= 'rw';
194    my $info = $base->_get_attr_schema($class->type);
195    my @attrs = map { $base->attribute($class->type, $_) } keys %{$info || {}};
196    @attrs = grep { ! $_->ro } @attrs if($for =~ /w/);
197    @attrs = grep { $_->readable } @attrs if($for =~ /r/);
198    @attrs = grep { !$_->hidden }  @attrs unless($for =~ /a/);
199    map { $_->name } @attrs;
200}
201
202=head2 GetOtypeDef
203
204This function is called to provide sub object definition. Must be overwritten
205per object class when need.
206
207Must return a hashref listing each sub object type and their related key atribute:
208
209    return {
210        addresses => 'user',
211    }
212
213=cut
214
215sub GetOtypeDef {
216    my ($class) = @_;
217
218    return;
219}
220
221=head2 get_field($field)
222
223Return the value for $field, must be provide by data base.
224
225    sub get_field {
226        my ($self, $field)
227    }
228
229=cut
230
231=head2 get_c_field($cfield)
232
233Return the value for canonical field $cfield.
234
235Call driver specific get_field()
236
237=cut
238
239sub get_c_field {
240    my ($self, $cfield) = @_;
241    $self->base->check_acl($self, $cfield, 'r') or do {
242        $self->base->log(LA_ERR, "Permission denied to get %s/%s",
243            $self->id, $cfield
244        );
245        return;
246    };
247    return $self->_get_c_field($cfield);
248}
249
250=head2 get_attributes($attr)
251
252Like get_c_field but always return an array
253
254=cut
255
256sub get_attributes {
257    my ($self, $cfield) = @_;
258    my $res = $self->get_c_field($cfield);
259    if ($res) {
260        return(ref $res ? @{$res} : $res);
261    } else {
262        return;
263    }
264}
265
266sub _get_attributes {
267    my ($self, $cfield) = @_;
268    my $res = $self->_get_c_field($cfield);
269    if ($res) {
270        return(ref $res ? @{$res} : ($res));
271    } else {
272        return;
273    }
274}
275
276sub _get_c_field {
277    my ($self, $cfield) = @_;
278    my $attribute = $self->attribute($cfield) or do {
279        $self->base->log(LA_WARN, "Unknow attribute $cfield");
280        return;
281    };
282    $attribute->readable or do {
283        $self->base->log(LA_WARN, "Attribute $cfield is not readable");
284        return;
285    };
286    return $attribute->get; 
287}
288
289=head2 GetAttributeValue($cfield)
290
291Return the value to exposed to other base
292
293=cut
294
295sub GetAttributeValue {
296    my ($self, $cfield) = @_;
297
298    return $self->get_c_field($cfield);
299}
300
301
302=head2 queryformat ($fmt)
303
304Return formated string according C<$fmt>
305
306=cut
307
308sub queryformat {
309    my ($self, $fmt) = @_;
310    $fmt =~ s/\\n/\n/g;
311    $fmt =~ s/\\t/\t/g;
312
313    my $old;
314    do {
315        $old = $fmt;
316        $fmt =~ s&(?:%\{([\?!]+)?([^:}%]*)(?::([^}%]*))?\})&
317            my $op = $1;
318            my $attr = $2;
319            my $val = '';
320           
321            if ($attr =~ /^(\w+)\((.*)\)$/) {
322                $val = $self->base->QFunc($1, $2);
323            } else {
324                $val = $self->get_c_field($2);
325            }
326
327            my $modifier = $3 || '';
328            my $res = '';
329
330            if ($op) {
331                if ($op eq '?') {
332                    $res = $val ? $3 : '';
333                } elsif ($op eq '?!') {
334                    $res = $val ? '' : $3;
335                }
336                if ($res =~ /^(\w+)\((.*)\)$/) {
337                    $res = $self->base->QFunc($1, $2);
338                }
339            } else {
340                for ($modifier) {
341                    /upper/ and do {
342                        $res = uc($val);
343                        last;
344                    };
345                    /lower/ and do {
346                        $res = lc($val);
347                        last;
348                    };
349                    $res = sprintf('%' . ($3 || 's'), ref $val ? join(',', @$val) : (defined($val) ? $val : ''))
350                }
351            }
352            $res
353        &egx;
354    } while($old ne $fmt);
355    $fmt;
356}
357
358=head2 set_fields(%data)
359
360Set values for this object. %data is a list or peer field => values.
361
362    sub set_fields {
363        my ($self, %data) = @_;
364    }
365
366=cut
367
368=head2 checkValues ($base, $obj, %attributes)
369
370Allow to pre-check values when object are modified or created
371
372C<$obj> is either the new id at object creation or the object itself on modification.
373
374=cut
375
376sub checkValues {
377    my ($class, $base, $obj, %attributes) = @_;
378
379    return 1;
380}
381
382=head2 check_allowed_values ($attr, $values)
383
384Check if value C<$values> is allowed for attributes C<$attr>
385
386=cut
387
388sub check_allowed_values {
389    my ($self, $attr, $values) = @_;
390    $self->base->check_allowed_values($self->type, $attr, $values);
391}
392
393=head2 attr_allow_values ($attr)
394
395Return allowed for attribute C<$attr>
396
397=cut
398
399sub attr_allow_values {
400    my ($self, $attr) = @_;
401    return $self->base->obj_attr_allowed_values(
402        $self->type,
403        $attr,
404    );
405}
406
407=head2 set_c_fields(%data)
408
409Set values for this object. %data is a list or peer
410canonical field => values. Fields names are translated.
411
412=cut
413
414sub set_c_fields {
415    my ($self, %cdata) = @_;
416    foreach my $cfield (keys %cdata) {
417        $self->base->check_acl($self, $cfield, 'w') or do { 
418            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
419                $self->type, $self->id, "permission denied");
420            return;
421        };
422    }
423
424    foreach my $cfield (keys %cdata) {
425        $self->check_allowed_values($cfield, $cdata{$cfield}) or do {
426            $self->base->log(LA_ERR, "Cannot modified %s/%s: %s",
427                $self->type, $self->id, "non authorized value");
428            return;
429        };
430    }
431
432    $self->_set_c_fields(%cdata);
433}
434
435sub _set_c_fields {
436    my ($self, %cdata) = @_;
437    my %data;
438    my $res = 0;
439    foreach my $cfield (keys %cdata) {
440        my $attribute = $self->attribute($cfield) or do {
441            $self->base->log(LA_ERR,
442                "Cannot set unsupported attribute %s to %s (%s)",
443                $cfield, $self->id, $self->type
444            );
445            return;
446        };
447        $attribute->ro and do {
448            $self->base->log(LA_ERR,
449                "Cannot set read-only attribute %s to %s (%s)",
450                $cfield, $self->id, $self->type
451            );
452            return;
453        };
454
455        if (!$attribute->checkinput($cdata{$cfield})) {
456            $self->base->log(LA_ERR,
457                "Value for attribute %s to %s (%s) does not match requirements",
458                $cfield, $self->id, $self->type
459            );
460            return;
461        };
462    }
463
464    if (!$self->checkValues($self->base, $self, %cdata)) {
465        my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
466        $self->base->log(LA_ERR,
467            "Cannot update %s (%s): wrong value%s",
468            $self->id, $self->type,
469            ($last ? ": $last" : $last)
470        );
471        return;
472    }
473
474    my %updated = ();
475    foreach my $cfield (keys %cdata) {
476        my $attribute = $self->attribute($cfield) or do {
477            $self->base->log(LA_ERR,
478                "Cannot set unsupported attribute %s to %s (%s)",
479                $cfield, $self->id, $self->type
480            );
481            return;
482        };
483        if ($attribute->set($cdata{$cfield})) {
484            $updated{$cfield} = $attribute->monitored;
485        }
486    }
487
488    if (keys %updated) {
489        $self->PostSetAttribute() or do {
490            $self->base->log(LA_ERR, "PostSetAttribute failed when updating %s/%s",
491                $self->type, $self->id);
492            return;
493        };
494
495        $self->ReportChange('Update', 'Attributes %s have been updated', join(', ', sort keys %updated));
496        foreach (sort keys %updated) {
497            $self->ReportChange('Attributes', '%s set to %s', $_, 
498                (ref $cdata{$_}
499                    ? join(', ', sort @{ $cdata{$_} })
500                    : $cdata{$_}) || '(none)')
501                if ($updated{$_});
502        }
503    }
504    return scalar(keys %updated);
505}
506
507=head2 PostSetAttribute
508
509This function is call to compute data when object is modify.
510
511=cut
512
513sub PostSetAttribute {
514    my ($self) = @_;
515
516    return 1;
517}
518
519=head2 addAttributeValue($attribute, $value)
520
521Add a value to a multivalue attributes
522
523=cut
524
525sub _addAttributeValue {
526    my ($self, $attribute, @values) = @_;
527
528    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
529    $self->_set_c_fields($attribute => [ @oldvalues, @values ]);
530}
531
532sub addAttributeValue {
533    my ($self, $attribute, @values) = @_;
534
535    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
536    $self->set_c_fields($attribute => [ @oldvalues, @values ]);
537}
538
539=head2 delAttributeValue($attribute, $value)
540
541Remove a value to a multivalue attributes
542
543=cut
544
545sub _delAttributeValue {
546    my ($self, $attribute, @values) = @_;
547
548    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
549
550    foreach my $value (@values) {
551        @oldvalues = grep { $_ ne $value } @oldvalues;
552    }
553
554    $self->_set_c_fields($attribute => @oldvalues ? [ @oldvalues, ] : undef );
555}
556
557sub delAttributeValue {
558    my ($self, $attribute, @values) = @_;
559
560    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
561
562    foreach my $value (@values) {
563        @oldvalues = grep { $_ ne $value } @oldvalues;
564    }
565
566    $self->set_c_fields($attribute => @oldvalues ? [ @oldvalues, ] : undef );
567}
568
569=head2 set_password($password)
570
571Set the password into the database, $password is the clear version
572of the password.
573
574This function store it into userPassword canonical field if supported
575using crypt unix and md5 algorythm (crypt md5), the salt is 8 random
576caracters.
577
578The base driver should override it if another encryption is need.
579
580=cut
581
582sub set_password {
583    my ($self, $clear_pass) = @_;
584    if ($self->base->check_acl($self, 'userPassword', 'w')) {
585        if ($self->_set_password($clear_pass)) {
586             $self->ReportChange('Password', 'user password has changed');
587             return 1;
588        } else {
589            return;
590        }
591    } else {
592        $self->base->log(LA_ERROR, "Permission denied for %s to change its password",
593            $self->id);
594        return;
595    }
596}
597
598sub _set_password {
599    my ($self, $clear_pass) = @_;
600    if (my $attribute = $self->base->attribute($self->type, 'userPassword')) {
601        my $res = $self->set_fields($attribute->iname, $self->base->passCrypt($clear_pass));
602        $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id)
603            if($res);
604        return $res;
605    } else {
606        $self->base->log(LA_WARN,
607            "Cannot set password: userPassword attributes is unsupported");
608    }
609}
610
611=head2 check_password ($password)
612
613Check given password is secure using L<Crypt::Cracklib>
614
615=cut
616
617sub check_password {
618    my ( $self, $password ) = @_;
619    my $dictionary = $self->base->config('cracklib_dictionnary');
620
621    if ($password !~ /^[[:ascii:]]*$/) {
622       return "the password must contains ascii characters only";
623    }
624
625    return fascist_check($password, $dictionary);
626}
627
628=head2 InjectCryptPasswd($cryptpasswd)
629
630Inject a password encrypted using standard UNIX method.
631
632=cut
633
634sub InjectCryptPasswd {
635    my ($self, $cryptpasswd) = @_;
636
637    if ($self->can('_InjectCryptPasswd')) {
638        return $self->_InjectCryptPasswd($cryptpasswd);
639    } else {
640        $self->base->log(LA_ERR, 'Injecting unix crypt password is not supported');
641        return;
642    }
643}
644
645=head2 search ($base, @filter)
646
647Search object matching C<@filter>
648
649=cut
650
651sub search {
652    my ($class, $base, @filter) = @_;
653    my @results;
654    my %parsed_filter;
655    while (my $item = shift(@filter)) {
656        # attr=foo => no extra white space !
657        # \W is false, it is possible to have two char
658        my ($attr, $mode, $val) = $item =~ /^(\w+)(?:(\W)(.+))?$/ or next;
659        if (!$mode) {
660            $mode = '~';
661            $val = shift(@filter);
662        }
663        push(
664            @{$parsed_filter{$attr}},
665            {
666                attr => $attr,
667                mode => $mode,
668                val  => $val,
669            }
670        );
671    }
672    foreach my $id ($base->list_objects($class->type)) {
673        my $obj = $base->get_object($class->type, $id);
674        my $match = 1;
675        foreach my $field (keys %parsed_filter) {
676            $base->attribute($class->type, $field) or
677                la_log(LA_WARN, "Unsupported attribute %s", $field);
678            my $tmatch = 0;
679            foreach (@{$parsed_filter{$field}}) {
680                my $value = $_->{val};
681                my $fval = $obj->_get_c_field($field) || '';
682                if ($value eq '*') {
683                    if ($fval ne '') {
684                        $tmatch = 1;
685                        last;
686                    }
687                } elsif ($value eq '!') {
688                    if ($fval eq '') {
689                        $match = 1;
690                        last;
691                    }
692                } elsif ($_->{mode} eq '=') {
693                    if ($fval eq $value) {
694                        $tmatch = 1;
695                        last;
696                    }
697                } elsif($_->{mode} eq '~') {
698                    if ($fval =~ m/\Q$value\E/i) {
699                        $tmatch = 1;
700                        last;
701                    }
702                }
703            }
704            $match = 0 unless($tmatch);
705        }
706        push(@results, $id) if($match);
707    }
708    @results;
709}
710
711=head2 attributes_summary ($base, $attribute)
712
713Return list of values existing in base for C<$attribute>
714
715=cut
716
717sub attributes_summary {
718    my ($class, $base, $attribute) = @_;
719    my $attr = $base->attribute($class->type, $attribute) or do {
720        $base->log(LA_ERR, "Cannot instantiate %s attribute", $attribute);
721        return;
722    };
723    if (!$attr->readable) {
724        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
725        return;
726    }
727    if (!$base->check_acl($class->type, $attribute, 'r')) {
728        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
729        return;
730    }
731    my %values;
732    foreach my $id ($base->list_objects($class->type)) {
733        my $obj = $base->get_object($class->type, $id);
734        my $value = $obj->_get_c_field($attribute);
735        if ($value) {
736            if (ref $value) {
737                foreach (@$value) {
738                    $values{$_} = 1;
739                }
740            } else {
741                $values{$value} = 1;
742            }
743        }
744    }
745    return sort(keys %values);
746}
747
748=head2 attributes_summary_by_object ($base, $attribute)
749
750Return list of peer object <=> values
751
752=cut
753
754sub attributes_summary_by_object {
755    my ($class, $base, $attribute) = @_;
756    my $attr = $base->attribute($class->type, $attribute) or do {
757        $base->log(LA_ERR, "Cannot instantiate %s attribute", $attribute);
758        return;
759    };
760    if (!$attr->readable) {
761        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
762        return;
763    }
764    if (!$base->check_acl($class->type, $attribute, 'r')) {
765        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
766        return;
767    }
768    my %values;
769    foreach my $id ($base->list_objects($class->type)) {
770        my $obj = $base->get_object($class->type, $id);
771        my $value = $obj->_get_c_field($attribute);
772        if ($value) {
773            if (ref $value) {
774                foreach (@$value) {
775                    push(@{ $values{ $id } }, $_);
776                }
777            } else {
778                push(@{ $values{ $id } }, $value);
779            }
780        }
781    }
782    return %values;
783}
784
785=head2 find_next_numeric_id ($base, $field, $min, $max)
786
787Find next free uniq id for attribute C<$field>
788
789=cut
790
791sub find_next_numeric_id {
792    my ($class, $base, $field, $min, $max) = @_;
793    $base->attribute($class->type, $field) or return;
794    $min ||= 
795        $field eq 'uidNumber' ? 500 :
796        $field eq 'gidNumber' ? 500 :
797        1;
798    $max ||= 65635;
799    $base->log(LA_DEBUG, "Trying to find %s in range %d - %d",
800        $field, $min, $max);
801    my %existsid;
802    $base->temp_switch_unexported(sub {
803        foreach ($base->list_objects($class->type)) {
804            my $obj = $base->get_object($class->type, $_) or next;
805            my $id = $obj->_get_c_field($field) or next;
806            $existsid{$id + 0} = 1;
807        }
808    }, 1);
809    $min += 0;
810    $max += 0;
811    for(my $i = $min; $i <= $max; $i++) {
812        $existsid{$i + 0} or do {
813            $base->log(LA_DEBUG, "Next %s found: %d", $field, $i);
814            return $i;
815        };
816    }
817    return;
818}
819
820=head2 ListChildObjects
821
822List dependant objects.
823
824=cut
825
826sub ListChildObjects {
827    my ( $self ) = @_;
828
829    return;
830}
831
832=head2 DataDump($config)
833
834Return a structure about the object
835
836=cut
837
838sub DataDump {
839    my ($self, $config, $base) = @_;
840
841    # {
842    #   base => # base latmoslocal: object address/thauvin-GY
843    #   otype => 'otype',
844    #   id => 'name',
845    #   roAttrs => []
846    #   Attrs => {
847    #       'Attr' => [],
848    #   }
849    #   subObjs => {
850    #       'otype' => [],
851    #   }
852    # }
853
854
855    $config->{level} ||= 0;
856    my $otype = $self->type;
857    $base ||= $self->base;
858    my $dump = {
859        otype => $otype,
860        id => $self->id,
861    };
862    if (ref $self) {
863        $dump->{base} = $base->label;
864    }
865
866    my %roAttrs = ();
867    foreach my $attr (sort { $a cmp $b } $base->list_canonical_fields($otype,
868        $config->{only_rw} ? 'rw' : 'r')) {
869        my $oattr = ref $self ? $self->attribute($attr) : $base->attribute($otype, $attr);
870        next if ($oattr->hidden);
871
872        if (ref $self) {
873            my $val = $self->get_c_field($attr);
874            if ($val || $config->{empty_attr}) {
875                my @vals = ref $val ? @{ $val } : $val;
876                $dump->{Attrs}{$attr} = \@vals;
877                $roAttrs{ $attr } = 1 if ($oattr->ro);
878            }
879        } else {
880            $dump->{Attrs}{$attr} = undef;
881            $roAttrs{ $attr } = 1 if ($oattr->ro);
882        }
883
884        $dump->{roAttrs} = [ sort keys %roAttrs ]
885            unless($config->{noSchema});
886    }
887
888    if ($config->{cb}) {
889        $config->{cb}->($config, $dump);
890    }
891
892    my $SubOtype = undef;
893
894    if (@{ $config->{SubOtype} || []}) {
895        $SubOtype = { map { $_ => 1 } @{ $config->{SubOtype} } };
896    }
897
898    if (ref $self && $config->{recur}) {
899          my %subobj = $self->ListChildObjects;
900          foreach my $otype (sort keys %subobj) {
901              if ($SubOtype) {
902                  $SubOtype->{$otype} or next;
903              }
904              foreach my $oname (sort @{ $subobj{$otype} }) {
905                  my $obj = $self->base->get_object($otype, $oname) or next;
906                  push(@{ $dump->{subObjs}{$otype} }, $obj->DataDump({ %{$config || {}}, recur => $config->{recur}, level => $config->{level} + 2 }));
907              }
908          }
909    }
910
911    return $dump;
912}
913
914=head2 text_dump ($handle, $config, $base)
915
916Dump object into C<$handle>
917
918=cut
919
920sub text_dump {
921    my ($self, $handle, $config, $base) = @_;
922    print $handle $self->dump($config, $base);
923    return 1;
924}
925
926=head2 dump
927
928Return dump for this object
929
930=cut
931
932sub dump {
933    my ($self, $InitConfig, $base) = @_;
934
935    $InitConfig->{level} ||= 0;
936    $base ||= $self->base;
937    my $dump = '';
938
939    $InitConfig->{cb} = sub {
940        my ( $config, $Dump ) = @_;
941
942        if ($config->{level}) {
943            $dump .= "\n";
944        }
945
946        if (ref $self) {
947            $dump .= sprintf "%s# base %s: object %s/%s\n",
948                ' ' x $config->{level},
949                $Dump->{base}, $Dump->{otype}, $Dump->{id};
950        }
951        $dump .= sprintf(
952            "%s# %s\n",
953            ' ' x $config->{level},
954            scalar(localtime)
955        );
956
957        my %roAttrs = map { $_ => 1 } @{ $Dump->{roAttrs} || [] };
958
959        foreach my $attr (sort { $a cmp $b } sort keys %{ $Dump->{Attrs} || {} }) {
960            my $val = $Dump->{Attrs}{$attr};
961            my $oattr = $base->attribute($Dump->{otype}, $attr);
962            if ($val) {
963                if (my @allowed = $base->obj_attr_allowed_values($Dump->{otype}, $attr)) {
964                    $dump .= sprintf("%s# %s must be%s: %s\n",
965                        ' ' x $config->{level},
966                        $attr,
967                        ($oattr->mandatory ? '' : ' empty or either'),
968                        join(', ', @allowed)
969                    );
970                }
971
972                foreach (@$val) {
973                    $_ ||= '';
974                    s/\r?\n/\\n/g;
975                    $dump .= sprintf("%s%s%s%s:%s\n",
976                        ' ' x $config->{level},
977                        $roAttrs{$attr} ? '# (ro) ' : '',
978                        $config->{level} ? $Dump->{otype} . '[' . $Dump->{id} . '].' : '',
979                        $attr, $_ ? " $_" : '');
980                }
981            } elsif ( $config->{empty_attr} || ! ref $self) {
982                if (my @allowed = $base->obj_attr_allowed_values($Dump->{otype}, $attr)) {
983                    $dump .= sprintf("%s# %s must be%s: %s\n",
984                        ' ' x $config->{level},
985                        $attr,
986                        ($oattr->mandatory ? '' : ' empty or either'),
987                        join(', ', @allowed)
988                    );
989                }
990                $dump .= sprintf("%s%s%s%s:\n",
991                    ' ' x $config->{level},
992                    $roAttrs{$attr} ? '# (ro) ' : '',
993                    $config->{level} ? $Dump->{otype} . '[' . $Dump->{id} . '].' : '',
994                    $attr);
995            }
996        }
997    };
998
999    $self->DataDump($InitConfig, $base);
1000
1001    return $dump;
1002}
1003
1004=head2 ReportChange($changetype, $message, @args)
1005
1006Possible per database way to log changes
1007
1008=cut
1009
1010sub ReportChange {
1011    my ($self, $changetype, $message, @args) = @_;
1012
1013    $self->base->ReportChange(
1014        $self->type,
1015        $self->id,
1016        $self->Iid,
1017        $changetype, $message, @args
1018    )
1019}
1020
10211;
1022
1023__END__
1024
1025
1026=head1 SEE ALSO
1027
1028L<LATMOS::Accounts::Bases>
1029
1030=head1 AUTHOR
1031
1032Thauvin Olivier, E<lt>olivier.thauvin.ipsl.fr@localdomainE<gt>
1033
1034=head1 COPYRIGHT AND LICENSE
1035
1036Copyright (C) 2009 by Thauvin Olivier
1037
1038This library is free software; you can redistribute it and/or modify
1039it under the same terms as Perl itself, either Perl version 5.10.0 or,
1040at your option, any later version of Perl 5 you may have available.
1041
1042=cut
Note: See TracBrowser for help on using the repository browser.