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

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

allow %%{} to not evaluate attribute

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