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

Last change on this file since 2359 was 2354, checked in by nanardon, 4 years ago

Avoid undef warning

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