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

Last change on this file since 2505 was 2505, checked in by nanardon, 2 years ago

Allow to create sub object when editing

  • Property svn:keywords set to Id Rev
File size: 26.9 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            $val = '' unless( defined( $val ) );
340
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 {
351                $res = $val;
352                foreach (split('\|' , $modifier)) {
353                    /upper/ and do {
354                        $res = uc($res);
355                        next;
356                    };
357                    /ucfirst/ and do {
358                        $res = ucfirst($res);
359                        next;
360                    };
361                    /lower/ and do {
362                        $res = lc($res);
363                        next;
364                    };
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                    };
373                    /substr\s+(\d+)\s+(\d+)?/ and do {
374                        $res = substr($res, $1, $2);
375                        next;
376                    };
377                    $res = sprintf('%' . ($modifier || 's'), ref $val ? join(',', @$val) : (defined($val) ? $val : ''))
378                }
379            }
380            $res
381        &egx;
382    } while($old ne $fmt);
383    $fmt;
384}
385
386=head2 set_fields(%data)
387
388Set values for this object. %data is a list or peer field => values.
389
390    sub set_fields {
391        my ($self, %data) = @_;
392    }
393
394=cut
395
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
410=head2 check_allowed_values ($attr, $values)
411
412Check if value C<$values> is allowed for attributes C<$attr>
413
414=cut
415
416sub check_allowed_values {
417    my ($self, $attr, $values) = @_;
418    $self->base->check_allowed_values($self->type, $attr, $values);
419}
420
421=head2 attr_allow_values ($attr)
422
423Return allowed for attribute C<$attr>
424
425=cut
426
427sub attr_allow_values {
428    my ($self, $attr) = @_;
429    return $self->base->obj_attr_allowed_values(
430        $self->type,
431        $attr,
432    );
433}
434
435=head2 set_c_fields(%data)
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) = @_;
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    }
451
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        };
458    }
459
460    $self->_set_c_fields(%cdata);
461}
462
463sub _set_c_fields {
464    my ($self, %cdata) = @_;
465    my %data;
466    my $sub;
467    my $res = 0;
468    foreach my $cfield (keys %cdata) {
469
470        if (my ($sotype, $key, $scfield) = $cfield =~ /^(\w+)(?:\[(\w+)\])?\.(.*)/) {
471            $key ||= '_';
472            $sub->{$sotype}{$key}{$scfield} = $cdata{$cfield};
473            next;
474        }
475
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        $attribute->ro and do {
484            $self->base->log(LA_ERR,
485                "Cannot set read-only attribute %s to %s (%s)",
486                $cfield, $self->id, $self->type
487            );
488            return;
489        };
490
491        if (!$attribute->checkinput($cdata{$cfield})) {
492            $self->base->log(LA_ERR,
493                "Value for attribute %s to %s (%s) does not match requirements",
494                $cfield, $self->id, $self->type
495            );
496            return;
497        };
498    }
499
500    if (!$self->checkValues($self->base, $self, %cdata)) {
501        my $last = LATMOS::Accounts::Log::lastmessage(LA_ERR);
502        $self->base->log(LA_ERR,
503            "Cannot update %s (%s): wrong value%s",
504            $self->id, $self->type,
505            ($last ? ": $last" : $last)
506        );
507        return;
508    }
509
510    my %updated = ();
511    foreach my $cfield (keys %cdata) {
512        my $attribute = $self->attribute($cfield) or do {
513            $self->base->log(LA_ERR,
514                "Cannot set unsupported attribute %s to %s (%s)",
515                $cfield, $self->id, $self->type
516            );
517            return;
518        };
519        if ($attribute->set($cdata{$cfield})) {
520            $updated{$cfield} = $attribute->monitored;
521        }
522    }
523
524    my $subupdate = 0;
525    if ($sub) {
526
527        # Security: if caller is create_c_object calling it to check permission ?
528        # See below
529        # my ($caller) = caller();
530        # my $subcreate = $caller eq 'create_c_object' ? 'create_c_object' : __SUB__;
531
532        # Trying to create subobject
533        foreach my $sotype (keys %$sub) {
534            my $SubKeyRef = $self->base->GetSubObjectKey($self->otype, $sotype) or do {
535                $self->base->log(LA_ERR, "Cannot create object type $sotype, subtype of " . $self->otype . " not defined");
536                return;
537            };
538            foreach my $skey (keys %{ $sub->{$sotype} || {} }) {
539                # TODO Check if object exists !
540                # Building id
541                my $info = $sub->{$sotype}{$skey} || {};
542                # For id: if key is given using it, otherwise using random
543                my $sid =
544                    $info->{$SubKeyRef} ||
545                    $self->id . '-' . join('', map { ('a' .. 'z')[rand(26)] } (0 .. 6));
546                $info->{$SubKeyRef} = $self->id;
547
548                # Here we don't check permission to create sub object:
549                $self->base->_create_c_object($sotype, $sid, %{ $info || {} }) or return;
550                $subupdate++;
551            }
552        }
553    }
554
555
556    if (keys %updated) {
557        $self->PostSetAttribute() or do {
558            $self->base->log(LA_ERR, "PostSetAttribute failed when updating %s/%s",
559                $self->type, $self->id);
560            return;
561        };
562
563        $self->ReportChange('Update', 'Attributes %s have been updated', join(', ', sort keys %updated));
564        foreach (sort keys %updated) {
565            $self->ReportChange('Attributes', '%s set to %s', $_, 
566                (ref $cdata{$_}
567                    ? join(', ', sort @{ $cdata{$_} })
568                    : $cdata{$_}) || '(none)')
569                if ($updated{$_});
570        }
571    }
572    return(scalar(keys %updated) + $subupdate);
573}
574
575=head2 PostSetAttribute
576
577This function is call to compute data when object is modify.
578
579=cut
580
581sub PostSetAttribute {
582    my ($self) = @_;
583
584    return 1;
585}
586
587=head2 addAttributeValue($attribute, $value)
588
589Add a value to a multivalue attributes
590
591=cut
592
593sub _addAttributeValue {
594    my ($self, $attribute, @values) = @_;
595
596    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
597    $self->_set_c_fields($attribute => [ @oldvalues, @values ]);
598}
599
600sub addAttributeValue {
601    my ($self, $attribute, @values) = @_;
602
603    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
604    $self->set_c_fields($attribute => [ @oldvalues, @values ]);
605}
606
607=head2 delAttributeValue($attribute, $value)
608
609Remove a value to a multivalue attributes
610
611=cut
612
613sub _delAttributeValue {
614    my ($self, $attribute, @values) = @_;
615
616    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
617
618    foreach my $value (@values) {
619        @oldvalues = grep { $_ ne $value } @oldvalues;
620    }
621
622    $self->_set_c_fields($attribute => @oldvalues ? [ @oldvalues, ] : undef );
623}
624
625sub delAttributeValue {
626    my ($self, $attribute, @values) = @_;
627
628    my @oldvalues = grep { $_ } $self->_get_attributes($attribute);
629
630    foreach my $value (@values) {
631        @oldvalues = grep { $_ ne $value } @oldvalues;
632    }
633
634    $self->set_c_fields($attribute => @oldvalues ? [ @oldvalues, ] : undef );
635}
636
637=head2 set_password($password)
638
639Set the password into the database, $password is the clear version
640of the password.
641
642This function store it into userPassword canonical field if supported
643using crypt unix and md5 algorythm (crypt md5), the salt is 8 random
644caracters.
645
646The base driver should override it if another encryption is need.
647
648=cut
649
650sub set_password {
651    my ($self, $clear_pass) = @_;
652    if ($self->base->check_acl($self, 'userPassword', 'w')) {
653        if ($self->_set_password($clear_pass)) {
654             $self->ReportChange('Password', 'user password has changed');
655             return 1;
656        } else {
657            return;
658        }
659    } else {
660        $self->base->log(LA_ERROR, "Permission denied for %s to change its password",
661            $self->id);
662        return;
663    }
664}
665
666sub _set_password {
667    my ($self, $clear_pass) = @_;
668    if (my $attribute = $self->base->attribute($self->type, 'userPassword')) {
669        my $res = $self->set_fields($attribute->iname, $self->base->passCrypt($clear_pass));
670        $self->base->log(LA_NOTICE, 'Mot de passe changé pour %s', $self->id)
671            if($res);
672        return $res;
673    } else {
674        $self->base->log(LA_WARN,
675            "Cannot set password: userPassword attributes is unsupported");
676    }
677}
678
679=head2 check_password ($password)
680
681Check given password is secure using L<Crypt::Cracklib>
682
683=cut
684
685sub check_password {
686    my ( $self, $password ) = @_;
687    my $dictionary = $self->base->config('cracklib_dictionnary');
688
689    if ($password !~ /^[[:ascii:]]*$/) {
690       return "the password must contains ascii characters only";
691    }
692
693    return fascist_check($password, $dictionary);
694}
695
696=head2 InjectCryptPasswd($cryptpasswd)
697
698Inject a password encrypted using standard UNIX method.
699
700=cut
701
702sub InjectCryptPasswd {
703    my ($self, $cryptpasswd) = @_;
704
705    if ($self->can('_InjectCryptPasswd')) {
706        return $self->_InjectCryptPasswd($cryptpasswd);
707    } else {
708        $self->base->log(LA_ERR, 'Injecting unix crypt password is not supported');
709        return;
710    }
711}
712
713=head2 search ($base, @filter)
714
715Search object matching C<@filter>
716
717=cut
718
719sub search {
720    my ($class, $base, @filter) = @_;
721    my @results;
722    my %parsed_filter;
723    while (my $item = shift(@filter)) {
724        # attr=foo => no extra white space !
725        # \W is false, it is possible to have two char
726        my ($attr, $mode, $val) = $item =~ /^(\w+)(?:(\W)(.+))?$/ or next;
727        if (!$mode) {
728            $mode = '~';
729            $val = shift(@filter);
730        }
731        push(
732            @{$parsed_filter{$attr}},
733            {
734                attr => $attr,
735                mode => $mode,
736                val  => $val,
737            }
738        );
739    }
740    foreach my $id ($base->list_objects($class->type)) {
741        my $obj = $base->get_object($class->type, $id);
742        my $match = 1;
743        foreach my $field (keys %parsed_filter) {
744            $base->attribute($class->type, $field) or
745                la_log(LA_WARN, "Unsupported attribute %s", $field);
746            my $tmatch = 0;
747            foreach (@{$parsed_filter{$field}}) {
748                my $value = $_->{val};
749                my $fval = $obj->_get_c_field($field) || '';
750                if ($value eq '*') {
751                    if ($fval ne '') {
752                        $tmatch = 1;
753                        last;
754                    }
755                } elsif ($value eq '!') {
756                    if ($fval eq '') {
757                        $match = 1;
758                        last;
759                    }
760                } elsif ($_->{mode} eq '=') {
761                    if ($fval eq $value) {
762                        $tmatch = 1;
763                        last;
764                    }
765                } elsif($_->{mode} eq '~') {
766                    if ($fval =~ m/\Q$value\E/i) {
767                        $tmatch = 1;
768                        last;
769                    }
770                }
771            }
772            $match = 0 unless($tmatch);
773        }
774        push(@results, $id) if($match);
775    }
776    @results;
777}
778
779=head2 attributes_summary ($base, $attribute)
780
781Return list of values existing in base for C<$attribute>
782
783=cut
784
785sub attributes_summary {
786    my ($class, $base, $attribute) = @_;
787    my $attr = $base->attribute($class->type, $attribute) or do {
788        $base->log(LA_WARN, "Cannot instantiate %s attribute for class %s", $attribute, $class->type);
789        return;
790    };
791    if (!$attr->readable) {
792        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
793        return;
794    }
795    if (!$base->check_acl($class->type, $attribute, 'r')) {
796        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
797        return;
798    }
799    my %values;
800    foreach my $id ($base->list_objects($class->type)) {
801        my $obj = $base->get_object($class->type, $id);
802        my $value = $obj->_get_c_field($attribute);
803        if ($value) {
804            if (ref $value) {
805                foreach (@$value) {
806                    $values{$_} = 1;
807                }
808            } else {
809                $values{$value} = 1;
810            }
811        }
812    }
813    return sort(keys %values);
814}
815
816=head2 attributes_summary_by_object ($base, $attribute)
817
818Return list of peer object <=> values
819
820=cut
821
822sub attributes_summary_by_object {
823    my ($class, $base, $attribute) = @_;
824    my $attr = $base->attribute($class->type, $attribute) or do {
825        $base->log(LA_WARN, "Cannot instantiate %s attribute for class %s", $attribute, $class->type);
826        return;
827    };
828    if (!$attr->readable) {
829        $base->log(LA_WARN, l('Attribute %s is not readable', $attribute));
830        return;
831    }
832    if (!$base->check_acl($class->type, $attribute, 'r')) {
833        $base->log(LA_WARN, l('Permission denied to read attribute %s', $attribute));
834        return;
835    }
836    my %values;
837    foreach my $id ($base->list_objects($class->type)) {
838        my $obj = $base->get_object($class->type, $id);
839        my $value = $obj->_get_c_field($attribute);
840        if ($value) {
841            if (ref $value) {
842                foreach (@$value) {
843                    push(@{ $values{ $id } }, $_);
844                }
845            } else {
846                push(@{ $values{ $id } }, $value);
847            }
848        }
849    }
850    return %values;
851}
852
853=head2 find_next_numeric_id ($base, $field, $min, $max)
854
855Find next free uniq id for attribute C<$field>
856
857=cut
858
859sub find_next_numeric_id {
860    my ($class, $base, $field, $min, $max) = @_;
861    $base->attribute($class->type, $field) or return;
862    $min ||= 
863        $field eq 'uidNumber' ? 500 :
864        $field eq 'gidNumber' ? 500 :
865        1;
866    $max ||= 65635;
867    $base->log(LA_DEBUG, "Trying to find %s in range %d - %d",
868        $field, $min, $max);
869    my %existsid;
870    $base->temp_switch_unexported(sub {
871        foreach ($base->list_objects($class->type)) {
872            my $obj = $base->get_object($class->type, $_) or next;
873            my $id = $obj->_get_c_field($field) or next;
874            $existsid{$id + 0} = 1;
875        }
876    }, 1);
877    $min += 0;
878    $max += 0;
879    for(my $i = $min; $i <= $max; $i++) {
880        $existsid{$i + 0} or do {
881            $base->log(LA_DEBUG, "Next %s found: %d", $field, $i);
882            return $i;
883        };
884    }
885    return;
886}
887
888=head2 ListChildObjects
889
890List dependant objects.
891
892=cut
893
894sub ListChildObjects {
895    my ( $self ) = @_;
896
897    return;
898}
899
900=head2 DataDump($config)
901
902Return a structure about the object
903
904=cut
905
906sub DataDump {
907    my ($self, $config, $base) = @_;
908
909    # {
910    #   base => # base latmoslocal: object address/thauvin-GY
911    #   otype => 'otype',
912    #   id => 'name',
913    #   roAttrs => []
914    #   Attrs => {
915    #       'Attr' => [],
916    #   }
917    #   subObjs => {
918    #       'otype' => [],
919    #   }
920    # }
921
922
923    $config->{level} ||= 0;
924    my $otype = $self->type;
925    $base ||= $self->base;
926    my $dump = {
927        otype => $otype,
928        id => ref $self ? $self->id : 'N/A',
929    };
930    if (ref $self) {
931        $dump->{base} = $base->label;
932    }
933
934    my %roAttrs = ();
935    foreach my $attr (sort { $a cmp $b } $base->list_canonical_fields($otype,
936        $config->{only_rw} ? 'rw' : 'r')) {
937        my $oattr = ref $self ? $self->attribute($attr) : $base->attribute($otype, $attr);
938        next if ($oattr->hidden);
939
940        if (ref $self) {
941            my $val = $self->get_c_field($attr);
942            if ($val || $config->{empty_attr}) {
943                my @vals = ref $val ? @{ $val } : $val;
944                $dump->{Attrs}{$attr} = \@vals;
945                $roAttrs{ $attr } = 1 if ($oattr->ro);
946            }
947        } else {
948            $dump->{Attrs}{$attr} = undef;
949            $roAttrs{ $attr } = 1 if ($oattr->ro);
950        }
951
952        $dump->{roAttrs} = [ sort keys %roAttrs ]
953            unless($config->{noSchema});
954    }
955
956    if ($config->{cb}) {
957        $config->{cb}->($config, $dump);
958    }
959
960    my $SubOtype = undef;
961
962    if (@{ $config->{SubOtype} || []}) {
963        $SubOtype = { map { $_ => 1 } @{ $config->{SubOtype} } };
964    }
965
966    if (ref $self && $config->{recur}) {
967          my %subobj = $self->ListChildObjects;
968          foreach my $otype (sort keys %subobj) {
969              if ($SubOtype) {
970                  $SubOtype->{$otype} or next;
971              }
972              foreach my $oname (sort @{ $subobj{$otype} }) {
973                  my $obj = $self->base->get_object($otype, $oname) or next;
974                  push(@{ $dump->{subObjs}{$otype} }, $obj->DataDump({ %{$config || {}}, recur => $config->{recur}, level => $config->{level} + 2 }));
975              }
976          }
977    }
978
979    return $dump;
980}
981
982=head2 text_dump ($handle, $config, $base)
983
984Dump object into C<$handle>
985
986=cut
987
988sub text_dump {
989    my ($self, $handle, $config, $base) = @_;
990    print $handle $self->dump($config, $base);
991    return 1;
992}
993
994=head2 dump
995
996Return dump for this object
997
998=cut
999
1000sub dump {
1001    my ($self, $InitConfig, $base) = @_;
1002
1003    $InitConfig->{level} ||= 0;
1004    $base ||= $self->base;
1005    my $dump = '';
1006
1007    $InitConfig->{cb} = sub {
1008        my ( $config, $Dump ) = @_;
1009
1010        if ($config->{level}) {
1011            $dump .= "\n";
1012        }
1013
1014        if (ref $self) {
1015            $dump .= sprintf "%s# base %s: object %s/%s\n",
1016                ' ' x $config->{level},
1017                $Dump->{base}, $Dump->{otype}, $Dump->{id};
1018        }
1019        $dump .= sprintf(
1020            "%s# %s\n",
1021            ' ' x $config->{level},
1022            scalar(localtime)
1023        );
1024
1025        my %roAttrs = map { $_ => 1 } @{ $Dump->{roAttrs} || [] };
1026
1027        foreach my $attr (sort { $a cmp $b } sort keys %{ $Dump->{Attrs} || {} }) {
1028            my $val = $Dump->{Attrs}{$attr};
1029            my $oattr = $base->attribute($Dump->{otype}, $attr);
1030            if ($val) {
1031                if (my @allowed = $base->obj_attr_allowed_values($Dump->{otype}, $attr)) {
1032                    $dump .= sprintf("%s# %s must be%s: %s\n",
1033                        ' ' x $config->{level},
1034                        $attr,
1035                        ($oattr->mandatory ? '' : ' empty or either'),
1036                        join(', ', @allowed)
1037                    );
1038                }
1039
1040                foreach (@$val) {
1041                    $_ ||= '';
1042                    s/\r?\n/\\n/g;
1043                    $dump .= sprintf("%s%s%s%s:%s\n",
1044                        ' ' x $config->{level},
1045                        $roAttrs{$attr} ? '# (ro) ' : '',
1046                        $config->{level} ? $Dump->{otype} . '[' . $Dump->{id} . '].' : '',
1047                        $attr, $_ ? " $_" : '');
1048                }
1049            } elsif ( $config->{empty_attr} || ! ref $self) {
1050                if (my @allowed = $base->obj_attr_allowed_values($Dump->{otype}, $attr)) {
1051                    $dump .= sprintf("%s# %s must be%s: %s\n",
1052                        ' ' x $config->{level},
1053                        $attr,
1054                        ($oattr->mandatory ? '' : ' empty or either'),
1055                        join(', ', @allowed)
1056                    );
1057                }
1058                $dump .= sprintf("%s%s%s%s:\n",
1059                    ' ' x $config->{level},
1060                    $roAttrs{$attr} ? '# (ro) ' : '',
1061                    $config->{level} ? $Dump->{otype} . '[' . $Dump->{id} . '].' : '',
1062                    $attr);
1063            }
1064        }
1065    };
1066
1067    $self->DataDump($InitConfig, $base);
1068
1069    return $dump;
1070}
1071
1072=head2 ReportChange($changetype, $message, @args)
1073
1074Possible per database way to log changes
1075
1076=cut
1077
1078sub ReportChange {
1079    my ($self, $changetype, $message, @args) = @_;
1080
1081    $self->base->ReportChange(
1082        $self->type,
1083        $self->id,
1084        $self->Iid,
1085        $changetype, $message, @args
1086    )
1087}
1088
10891;
1090
1091__END__
1092
1093
1094=head1 SEE ALSO
1095
1096L<LATMOS::Accounts::Bases>
1097
1098=head1 AUTHOR
1099
1100Thauvin Olivier, E<lt>olivier.thauvin.ipsl.fr@localdomainE<gt>
1101
1102=head1 COPYRIGHT AND LICENSE
1103
1104Copyright (C) 2009 by Thauvin Olivier
1105
1106This library is free software; you can redistribute it and/or modify
1107it under the same terms as Perl itself, either Perl version 5.10.0 or,
1108at your option, any later version of Perl 5 you may have available.
1109
1110=cut
Note: See TracBrowser for help on using the repository browser.