source: trunk/LATMOS-Accounts/lib/LATMOS/Accounts/Bases/Sql/User.pm @ 2138

Last change on this file since 2138 was 2138, checked in by nanardon, 6 years ago

Compute departure always for next status

  • Property svn:keywords set to Id Rev
File size: 75.6 KB
RevLine 
[29]1package LATMOS::Accounts::Bases::Sql::User;
[19]2
3use 5.010000;
4use strict;
5use warnings;
[1329]6use overload '""' => 'stringify';
[19]7
[175]8use LATMOS::Accounts::Utils;
[390]9use LATMOS::Accounts::Log;
[1079]10use POSIX qw(strftime);
[1500]11use Date::Parse qw(str2time);
12use DateTime;
13use DateTime::TimeZone;
[29]14use base qw(LATMOS::Accounts::Bases::Sql::objects);
[1549]15use LATMOS::Accounts::I18N;
[1623]16use Date::Calc;
[1640]17use LATMOS::Accounts::Mail;
[19]18
19our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];
20
21=head1 NAME
22
23LATMOS::Ad - Perl extension for blah blah blah
24
25=head1 DESCRIPTION
26
27Account base access over standard unix file format.
28
29=head1 FUNCTIONS
30
31=cut
32
[1071]33=head2 new(%config)
[19]34
35Create a new LATMOS::Ad object for windows AD $domain.
36
37domain / server: either the Ad domain or directly the server
38
39ldap_args is an optionnal list of arguments to pass to L<Net::LDAP>.
40
41=cut
42
[1014]43sub _object_table { 'user' }
[19]44
[1014]45sub _key_field { 'name' }
[29]46
[1014]47sub _has_extended_attributes { 1 }
[74]48
[1329]49sub stringify {
50    my ($self) = @_;
51
52    return join(' ', grep { $_ }
53        (
54            $self->get_field('givenName'),
55            $self->get_field('sn')
56        )
57    )
58    || $self->get_field('description')
59    || $self->id;
60}
61
[1992]62sub GetOtypeDef {
63    my ($class) = @_;
64
65    {
66        address => 'user',
67        employment => 'user',
68    },
69}
70
[861]71sub _get_attr_schema {
72    my ($class, $base) = @_;
[92]73
[1315]74    my $subsetaddress = sub {
75        my ($self, $data) = @_;
76        my $fmainaddress = $self->object->_get_c_field('mainaddress');
77
78        # set address attribute => create address object on the fly
79        # except if attr is empty !
80        if (!$fmainaddress && $data) {
81            $fmainaddress = $self->object->id . '-' . join('', map { ('a'..'z')[rand(26)] }
82                (0..4));
83            $self->base->_create_c_object(
84                'address', $fmainaddress,
85                user => $self->object->id,
86                isMainAddress => 1, ) or do {
87                $self->base->log(LA_ERR,
88                    "Cannot create main address for user %s", $self->object->id);
89                return;
90            };
91        }
92        if ($fmainaddress && 
93            (my $address = $self->base->get_object('address', $fmainaddress))) {
94            if ($address->attribute($self->name) &&
95                !$address->attribute($self->name)->ro) {
96                return $address->set_c_fields($self->name => $data) ||0;
97            }
98        }
99    };
100
[1524]101    my $attrs = {
[1315]102            exported => {
103                post => sub {
104                    my ($self, $value) = @_;
105                    my $attr = $self->name;
106
107                    if (my $obj = $self->base->
108                            get_object('revaliases', $self->object->id)) {
109                        my $ares = $obj->set_c_fields(
110                            ($attr eq 'exported' ? 'exported' : 'unexported') => $value
111                        );
112                        if (!defined($ares)) {
113                            $self->base->log(LA_ERR,
114                                'Cannot set revaliases exported attribute for user %s',
115                                $self->object->id);
116                        }
117                    }
118                    my $must_expire = $self->name eq 'exported'
119                        ? ($value ? 0 : 1 )
120                        : ($value ? 1 : 0 );
121
[1500]122                    foreach my $al (grep { $_ } $self->object->get_attributes('aliases'), $self->object->id) {
[1315]123                        my $obj = $self->base->get_object('aliases', $al) or next;
124                        $obj->_set_c_fields(
[1470]125                            exported => $value,
[1315]126                        );
127                    }
[1498]128                    foreach my $al ($self->object->get_attributes('otheraddress')) {
129                        my $obj = $self->base->get_object('address', $al) or next;
130                        $obj->_set_c_fields(
131                            exported => $value,
132                        );
133                    }
[1315]134                },
135            },
[910]136            uidNumber => {
137                inline => 1,
138                iname => 'uidnumber',
139                uniq => 1,
140                mandatory => 1,
141                formopts => { length => 7 },
[1550]142                label => l('UID'),
[910]143            },
[1297]144            uidnumber => { inline => 1, hide => 1, monitored => 1 },
[1280]145            gidNumber => {
[861]146                inline => 1,
147                iname => 'gidnumber',
148                mandatory => 1,
149                can_values => sub {
[1347]150                    map { $_->id, $_->get_attributes('gidNumber') }
151                    map { $base->get_object('group', $_) }
152                    $base->list_objects('group')
153                },
154                can_values => sub {
[1315]155                    my $sth = $base->db->prepare_cached(
156                        q{
[1347]157                        select name, gidnumber from "group" where exported = true
[1315]158                        }
159                    );
160                    $sth->execute();
161                    my @res;
162                    while (my $res = $sth->fetchrow_hashref) {
[1347]163                        push(@res, $res->{name});
[1315]164                        push(@res, $res->{gidnumber});
165                    }
166                    return @res;
[861]167                },
168                display => sub {
169                    my ($self, $val) = @_;
[1918]170                    if ($val =~ /^\d+$/) {
171                        my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
172                            or return;
173                        return $gr;
174                    } else {
175                        my ($gr) = $self->base->search_objects('group', "name=$val")
176                            or return;
177                        return $gr;
178                    }
[861]179                },
[1347]180                input => sub {
181                    my ($val) = @_;
182                    $val =~ /^\d+$/ and return $val;
183                    my ($gr) = $base->search_objects('group', "name=$val") or return;
184                    return $base->get_object('group', $gr)->get_attributes('gidNumber');
185                },
[1917]186                #reference => 'group',
[1550]187                label => l('GID'),
[861]188            },
[1550]189            loginShell => {
[1551]190                mandatory => 1,
[1550]191                label => l('Shell'),
192            },
[861]193            gidnumber => { inline => 1, hide => 1,
194                can_values => sub {
[1347]195                    map { $_->id, $_->get_attributes('gidNumber') }
[861]196                    map { $base->get_object('group', $_) }
197                    $base->list_objects('group')
198                },
[1347]199                display => sub {
200                    my ($self, $val) = @_;
201                    my ($gr) = $self->base->search_objects('group', "gidNumber=$val")
202                        or return;
203                    return $gr;
204                },
205                input => sub {
206                    my ($val) = @_;
207                    $val =~ /^\d+$/ and return $val;
208                    my ($gr) = $base->search_objects('group', "name=$val") or return;
209                    return $base->get_object('group', $gr)->get_attributes('gidNumber');
210                },
[861]211                mandatory => 1,
212                reference => 'group',
[1297]213                monitored => 1,
[861]214            },
215            locked    => {
216                formtype => 'CHECKBOX',
217                formopts => { rawvalue => 1, },
[1297]218                monitored => 1,
[1550]219                label => l('Locked'),
[861]220            },
[1506]221            expire        => {
222                inline => 1,
223                formtype => 'DATETIME',
224                monitored => 1,
[1620]225                label => l('Expire on'),
[1506]226            },
227            endcircuit    => {
228                inline => 1,
229                formtype => 'DATE',
230                monitored => 1,
[1550]231                label => l('End of entrance'),
[1506]232            },
[1566]233            _endEmployment => {
[1500]234                formtype => 'DATETIME',
235                managed => 1,
236                ro => 1,
[1566]237                hide => 1,
[1500]238                get => sub {
239                    my ($attr) = @_;
240                    my $self = $attr->object;
[1592]241                    $self->_computeEndEmployment($self->base->config('employment_delay') || 0);
[1500]242                },
[1550]243                label => l('End of employment'),
[1500]244            },
[1566]245            endEmployment => {
[1514]246                formtype => 'DATETIME',
[1566]247                ro => 1,
248                label => l('End of employment'),
249            },
[1748]250            _departure => {
251                formtype => 'DATETIME',
252                managed => 1,
253                ro => 1,
254                hide => 1,
255                get => sub {
256                    my ($attr) = @_;
257                    my $self = $attr->object;
[2138]258                    $self->_computeEndEmployment($self->base->config('employment_delay') || 0, 1);
[1748]259                },
260                label => l('Start of employment'),
261            },
262            departure => {
263                formtype => 'DATETIME',
264                ro => 1,
[1863]265                label => l('Leaving'),
[1748]266            },
[1566]267            _endStrictEmployment => {
268                formtype => 'DATETIME',
[1514]269                managed => 1,
270                ro => 1,
[1566]271                hide => 1,
[1514]272                get => sub {
273                    my ($attr) = @_;
274                    my $self = $attr->object;
[1526]275                    $self->_computeEndEmployment();
[1522]276                },
277            },
[1566]278            endStrictEmployment => {
[1522]279                formtype => 'DATETIME',
[1566]280                ro => 1,
281            },
282            _endCurrentEmployment => {
283                formtype => 'DATETIME',
[1522]284                managed => 1,
285                ro => 1,
[1566]286                hide => 1,
[1522]287                get => sub {
288                    my ($attr) = @_;
289                    my $self = $attr->object;
[1514]290
291                    my $list_empl = $self->base->db->prepare_cached(q{
292                        SELECT * FROM employment WHERE "user" = ? and
[1522]293                            firstday <= now() and
[1514]294                            (lastday is null or lastday >= now() - '1 days'::interval)
295                            order by firstday asc
296                    });
297                    $list_empl->execute($self->id);
298                    my $end;
299                    while (my $res = $list_empl->fetchrow_hashref) {
300                        if (!$res->{lastday}) {
301                            # Ultimate employment.
302                            $list_empl->finish;
303                            return undef;
[1522]304                        } else {
[1514]305                            $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
306                            $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
307                            $end->add(hours => 23, minutes => 59, seconds => 59);
308                        }
[1522]309                        last;
[1514]310                    }
[1522]311                    $list_empl->finish;
312
[1514]313                    return $end ? $end->iso8601 : undef
314                },
315            },
[1566]316            endCurrentEmployment => {
[1511]317                formtype => 'DATETIME',
[1566]318                ro => 1,
319            },
320            _endLastEmployment => {
321                formtype => 'DATETIME',
[1511]322                managed => 1,
323                ro => 1,
[1566]324                hide => 1,
[1511]325                get => sub {
326                    my ($attr) = @_;
327                    my $self = $attr->object;
328
329                    my $list_empl = $self->base->db->prepare_cached(q{
330                        SELECT * FROM employment WHERE "user" = ?
331                        order by lastday desc nulls first
332                    });
333                    $list_empl->execute($self->id);
334                    my $res = $list_empl->fetchrow_hashref;
335                    $list_empl->finish;
336                    my $end;
337                    if ($res && $res->{lastday}) {
338                        $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
339                        $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
340                        $end->add(hours => 23, minutes => 59, seconds => 59);
341                    }
342                    return $end ? $end->iso8601 : undef
343                },
[1550]344                label => l('End of any employment'),
[1511]345            },
[1566]346            endLastEmployment => {
347                formtype => 'DATETIME',
348                ro => 1,
349                label => l('End of any employment'),
350            },
[1590]351            _startEmployment => {
352                formtype => 'DATETIME',
353                managed => 1,
354                ro => 1,
355                hide => 1,
356                get => sub {
357                    my ($attr) = @_;
358                    my $self = $attr->object;
[1592]359                    $self->_computeStartEmployment($self->base->config('employment_delay') || 0);
[1590]360                },
361                label => l('Start of employment'),
362            },
363            startEmployment => {
364                formtype => 'DATETIME',
365                ro => 1,
366                label => l('Start of employment'),
367            },
[1748]368            _arrival => {
369                formtype => 'DATETIME',
370                managed => 1,
371                ro => 1,
372                hide => 1,
373                get => sub {
374                    my ($attr) = @_;
375                    my $self = $attr->object;
[1757]376                    $self->_computeStartEmployment($self->base->config('employment_delay') || 0, 1, 1);
[1748]377                },
378                label => l('Start of employment'),
379            },
380            arrival => {
381                formtype => 'DATETIME',
382                ro => 1,
383                label => l('Arrival'),
384            },           
[1590]385            _startStrictEmployment => {
386                formtype => 'DATETIME',
387                managed => 1,
388                ro => 1,
389                hide => 1,
390                get => sub {
391                    my ($attr) = @_;
392                    my $self = $attr->object;
393                    $self->_computeStartEmployment();
394                },
395            },
396            startStrictEmployment => {
397                formtype => 'DATETIME',
398                ro => 1,
399            },
400            _startCurrentEmployment => {
401                formtype => 'DATETIME',
402                managed => 1,
403                ro => 1,
404                hide => 1,
405                get => sub {
406                    my ($attr) = @_;
407                    my $self = $attr->object;
408
409                    my $list_empl = $self->base->db->prepare_cached(q{
410                        SELECT * FROM employment WHERE "user" = ? and
411                            firstday <= now() and
412                            (lastday is null or lastday >= now() - '1 days'::interval)
413                            order by firstday asc
414                    });
415                    $list_empl->execute($self->id);
416                    my $start;
417                    while (my $res = $list_empl->fetchrow_hashref) {
418                        $start = DateTime->from_epoch(epoch => str2time($res->{firstday}));
419                        $start->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
420                        last;
421                    }
422                    $list_empl->finish;
423
424                    return $start ? $start->iso8601 : undef
425                },
426            },
427            startCurrentEmployment => {
428                formtype => 'DATETIME',
429                ro => 1,
430            },
431            _startFirstEmployment => {
432                formtype => 'DATETIME',
433                managed => 1,
434                ro => 1,
435                hide => 1,
436                get => sub {
437                    my ($attr) = @_;
438                    my $self = $attr->object;
439
440                    my $list_empl = $self->base->db->prepare_cached(q{
441                        SELECT * FROM employment WHERE "user" = ?
442                        order by firstday asc
443                    });
444                    $list_empl->execute($self->id);
445                    my $res = $list_empl->fetchrow_hashref;
446                    $list_empl->finish;
447                    my $start;
448                    if ($res && $res->{firstday}) {
449                        $start = DateTime->from_epoch(epoch => str2time($res->{firstday}));
450                        $start->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
451                    }
452                    return $start ? $start->iso8601 : undef
453                },
454                label => l('Start of any employment'),
455            },
456            startFirstEmployment => {
457                formtype => 'DATETIME',
458                ro => 1,
459                label => l('Start of any employment'),
460            },
[1623]461            employmentLength => {
462                ro => 1,
463                managed => 1,
464                get => sub {
465                    my ($self) = @_;
466                    my $lastday = $self->object->get_attributes('endEmployment') || DateTime->now->ymd('-');
467                    my $firstday = $self->object->get_attributes('startEmployment') or return;
468
469                    my @fd = split('-', DateTime->from_epoch(epoch => str2time($firstday))->ymd('-'));
470                    my @ld = split('-', DateTime->from_epoch(epoch => str2time($lastday))->ymd('-'));
471
472                    return Date::Calc::Delta_Days(@fd, @ld) +1;
473                },
474                label => l('Work duration'),
475            },
476            employmentLengthText => {
477                ro => 1,
478                managed => 1,
479                get => sub {
480                    my ($self) = @_;
481                    my $firstday = $self->object->get_attributes('startEmployment') or return;
482                    my $lastday = $self->object->get_attributes('endEmployment')|| DateTime->now->ymd('-');
483                    {
484                        my $dtlast = DateTime->from_epoch(epoch => str2time($lastday));
485                        $dtlast->add(days => 1);
486                        $lastday = $dtlast->ymd('-');
487                    }
488
489                    my @fd = split('-', DateTime->from_epoch(epoch => str2time($firstday))->ymd('-'));
490                    my @ld = split('-', DateTime->from_epoch(epoch => str2time($lastday))->ymd('-'));
491
492                    my ($Dy,$Dm,$Dd) = Date::Calc::N_Delta_YMD(@fd, @ld);
493                    return join(', ',
494                        ($Dy ? l('%d years', $Dy)  : ()),
495                        ($Dm ? l('%d months', $Dm) : ()),
496                        ($Dd ? l('%d days', $Dd)   : ()),
497                    );
498                },
499                label => l('Work duration'),
500            },
[1117]501            cn        => {
502                inline => 1, ro => 1,
503                get => sub {
504                    my ($self) = @_;
505                    return join(' ', grep { $_ } 
506                        (
507                            $self->object->_get_c_field('givenName'),
508                            $self->object->_get_c_field('sn')
509                        )
510                    )
511                    || $self->object->_get_c_field('description')
512                    || $self->object->id;
513                },
514            },
[938]515            memberOf  => {
[1329]516                reference => 'group',
[938]517                multiple => 1, delayed => 1,
518                get => sub {
519                    my ($self) = @_;
520                    my $obj = $self->object;
521                    my $sth = $obj->db->prepare_cached(
522                        q{
523                        select name from "group" join
524                        group_attributes on group_attributes.okey = "group".ikey
[1865]525                        where value = ? and attr = ? and internobject = false
[1353]526                        } . ($self->base->{wexported} ? '' : ' and "group".exported = true')
[938]527                    );
528                    $sth->execute($obj->id, 'memberUID');
529                    my @res;
530                    while (my $res = $sth->fetchrow_hashref) {
531                        push(@res, $res->{name});
532                    }
533                    return \@res;
[1315]534                },
535                set => sub {
536                    my ($self, $values) = @_;
537                    my %old = map { $_ => 'o' } @{ $self->get };
538                    foreach my $group (grep { $_ } ref $values ? @{ $values } : $values) {
539                        if ($old{$group}) {
540                            $old{$group} = undef; # no change
541                        } else {
542                            $old{$group} = 'n';
543                        }
544                    }
545
546                    my $res = 0;
547                    foreach my $group (keys %old) {
548                        $old{$group} or next; # no change
549
550                        my $ogroup = $self->base->get_object('group', $group) or next;
551                        ($ogroup->_get_c_field('sutype') || '') =~ /^(jobtype|contrattype)$/ and next;
552
553                        if ($old{$group} eq 'n') {
554                            $res += $ogroup->_addAttributeValue('memberUID', $self->object->id);
555                        } else {
556                            if (($self->object->_get_c_field('department') || '') eq $ogroup->id) {
557                                $self->base->log(LA_WARN,
558                                    "Don't removing user %s from group %s: is its department",
559                                    $self->object->id, $ogroup->id);
560                                next;
561                            }
562                            $res += $ogroup->_delAttributeValue('memberUID', $self->object->id);
563                        }
564                    }
565                    return $res;
566                },
[1550]567                label => l('Member of'),
[938]568            },
[1315]569            forward => {
570                managed => 1,
[1857]571                multiple => 1,
[1315]572                get => sub {
573                    my ($self) = @_;
574                    my $sth = $self->base->db->prepare(q{
[1865]575                        select forward from aliases where name = ? and internobject = false
[1315]576                        } . ($self->base->{wexported} ? '' : ' and exported = true'));
577                    $sth->execute($self->object->id);
578                    my $res = $sth->fetchrow_hashref;
579                    $sth->finish;
580                    return $res->{forward}
581                },
582                set => sub {
583                    my ($self, $data) = @_;
584                    if ($data) {
[1845]585                        my @datas = ref $data ? @$data : split(/\s*,\s*/, $data);
[1315]586                        if (my $f = $self->base->get_object('aliases', $self->object->id)) {
[1844]587                            return $f->_set_c_fields(
[1845]588                                forward => \@datas,
[1844]589                                comment => undef,
590                                description => 'Forward for user ' . $self->object->id,
591                            );
[1315]592                        } else {
593                            if ($self->base->_create_c_object(
594                                    'aliases', $self->object->id,
[1845]595                                    forward => \@datas,
[1315]596                                    description => 'automatically created for ' . $self->object->id,
597                                )) {
598                                return 1;
599                            } else {
600                                $self->base->log(LA_ERR, "Cannot add forward for %s",
[1498]601                                    $self->object->id);
[1315]602                            }
603                        }
[1666]604                    } elsif ($self->base->get_object('aliases', $self->object->id)) {
[1372]605                        if (my $res = $self->base->_delete_object('aliases', $self->object->id)) {
[1315]606                            return $res;
607                        } else {
608                            $self->base->log(LA_ERR, "Cannot remove forward for %s",
[1395]609                                $self->object->id);
[1315]610                        }
[1666]611                    } else {
612                        return 1;
[1315]613                    }
614                    return;
615                },
[1550]616                label => l('Forward'),
[1315]617            },
[861]618            aliases   => {
[1385]619                #reference => 'aliases',
[1418]620                delayed => 1,
[861]621                formtype => 'TEXT',
622                multiple => 1,
[1315]623                get => sub {
624                    my ($self) = @_;
625                    my $sth = $self->base->db->prepare(q{
[1865]626                        select name from aliases where internobject = false and lower($1) =
[1460]627                        lower(array_to_string("forward", ','))
[1315]628                        } . ($self->base->{wexported} ? '' : 'and exported = true'));
629                    $sth->execute($self->object->id);
630                    my @values;
631                    while (my $res = $sth->fetchrow_hashref) {
632                        push(@values, $res->{name});
633                    }
634                    return \@values;
635                },
636                set => sub {
637                    my ($self, $data) = @_;
[1418]638
[1315]639                    my $res = 0;
640                    my %aliases = map { $_ => 1 } grep { $_ } (ref $data ? @{$data} : $data);
641                    foreach ($self->object->_get_attributes('aliases')) {
642                        $aliases{$_} ||= 0;
643                        $aliases{$_} +=2;
644                    }
645                    foreach (keys %aliases) {
646                        if ($aliases{$_} == 2) {
647                            if ($self->base->_delete_object('aliases', $_)) {
648                                $res++
649                            } else {
650                                $self->base->log(LA_ERR,
651                                    "Cannot remove aliases %s from user %s", $_,
652                                    $self->object->id);
653                            }
654                        } elsif ($aliases{$_} == 1) {
655                            if ($self->base->_create_c_object(
656                                    'aliases', $_,
657                                    forward => [ $self->object->id ],
658                                    description => 'automatically created for ' . $self->object->id,
659                                )) {
660                                $res++
661                            } else {
662                                $self->base->log(LA_ERR, 'Cannot set forward %s to user %s',
663                                    $_, $self->object->id);
664                                return;
665                            }
666                        } # 3 no change
667                    }
668                    $res
669                },
[1550]670                label => l('Aliases'),
[861]671            },
672            revaliases => {
673                formtype => 'TEXT',
[1418]674                get => sub {
675                    my ($self) = @_;
676
677                    if (my $obj = $self->base->
678                        get_object('revaliases', $self->object->id)) {
679                        return $obj->get_attributes('as');
680                    } else {
681                        return;
682                    }
683                },
[1315]684                set => sub {
685                    my ($self, $data) = @_;
686               
687                    my $res = 0;
688                    if ($data) {
689                        if (my $obj = $self->base->
690                            get_object('revaliases', $self->object->id)) {
691                            my $ares = $obj->set_c_fields(
692                                'as' => $data,
[1404]693                                'exported' => ($self->object->get_attributes('exported') || 0),
[1315]694                            );
695                            if (defined($ares)) {
696                                $res+=$ares;
697                            } else {
698                                $self->base->log(LA_ERR, 'Cannot set revaliases for user %s',
699                                    $self->object->id);
700                            }
701                        } else {
702                            if ($self->base->_create_c_object(
703                                    'revaliases',
704                                    $self->object->id, as => $data,
[1392]705                                    'exported' => ($self->object->get_attributes('exported') || 0),
[1315]706                                    description => 'automatically created for ' . $self->object->id,
707                                )) {
708                                $res++;
709                            } else {
710                                $self->base->log(LA_ERR, 'Cannot set revaliases for user %s',
711                                    $self->object->id);
712                            }
713                        }
714                    } else {
715                        $self->base->_delete_object('revaliases', $self->object->id);
716                        $res++;
717                    }
718
719                    $res
720                },
[861]721            },
722            manager => {
[1365]723                reference => 'user',
724                ro => 1,
725                get => sub {
726                    my ($self) = @_;
727                    if (my $manager = $self->object->_get_c_field('managerContact')) {
728                        return $manager;
729                    } elsif (my $department = $self->object->_get_c_field('department')) {
730                        my $obj = $self->base->get_object('group', $department);
731                        return $obj->_get_c_field('managedBy');
732                    } else {
733                        return;
734                    }
[861]735                },
[1550]736                label => l('Responsible'),
[861]737            },
738            department => {
739                reference => 'group',
740                can_values => sub {
741                    $base->search_objects('group', 'sutype=dpmt')
[1280]742                },
[1297]743                monitored => 1,
[1550]744                label => l('Department'),
[861]745            },
746            contratType => {
747                reference => 'group',
748                can_values => sub {
749                    $base->search_objects('group', 'sutype=contrattype')
[1280]750                },
[1297]751                monitored => 1,
[1550]752                label => l('Type of contract'),
[861]753            },
754            site => {
755                reference => 'site',
756                can_values => sub {
757                    $base->search_objects('site')
[1315]758                },
759                get => sub {
760                    my ($self) = @_;
761                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
762                        $self->base->get_object('address', $fmainaddress)
763                            ->_get_c_field($self->name);
764                    } else {
765                        return;
766                    }
767                },
768                set => $subsetaddress,
[1550]769                label => l('Site'),
[861]770            },
[1315]771            co => {
772                get => sub {
773                    my ($self) = @_;
774                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
775                        $self->base->get_object('address', $fmainaddress)
776                            ->_get_c_field($self->name);
777                    } else {
778                        return;
779                    }
780                },
781                set => $subsetaddress,
782            },
783            l => {
784                get => sub {
785                    my ($self) = @_;
786                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
787                        $self->base->get_object('address', $fmainaddress)
788                            ->_get_c_field($self->name);
789                    } else {
790                        return;
791                    }
792                },
793                set => $subsetaddress,
[1550]794                label => l('City'),
[1315]795            },
796            postalCode => {
797                get => sub {
798                    my ($self) = @_;
799                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
800                        $self->base->get_object('address', $fmainaddress)
801                            ->_get_c_field($self->name);
802                    } else {
803                        return;
804                    }
805                },
806                set => $subsetaddress,
[1550]807                label => l('Postal code'),
[1315]808            },
809            streetAddress => {
810                formtype => 'TEXTAREA',
811                get => sub {
812                    my ($self) = @_;
813                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
814                        $self->base->get_object('address', $fmainaddress)
815                            ->_get_c_field($self->name);
816                    } else {
817                        return;
818                    }
819                },
820                set => $subsetaddress,
[1550]821                label => l('Street'),
[1315]822            },
823            postOfficeBox => {
824                get => sub {
825                    my ($self) = @_;
826                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
827                        $self->base->get_object('address', $fmainaddress)
828                            ->_get_c_field($self->name);
829                    } else {
830                        return;
831                    }
832                },
833                set => $subsetaddress,
[1550]834                label => l('Post office box'),
[1315]835            },
836            st => {
837                get => sub {
838                    my ($self) = @_;
839                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
840                        $self->base->get_object('address', $fmainaddress)
841                            ->_get_c_field($self->name);
842                    } else {
843                        return;
844                    }
845                },
846                set => $subsetaddress,
847            },
848            facsimileTelephoneNumber => {
849                get => sub {
850                    my ($self) = @_;
851                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
852                        $self->base->get_object('address', $fmainaddress)
853                            ->_get_c_field($self->name);
854                    } else {
855                        return;
856                    }
857                },
858                set => $subsetaddress,
[1550]859                label => l('Fax number'),
[1315]860            },
861            o => {
862                ro => 1,
863                iname => 'company',
[1550]864                label => l('Company'),
[1315]865            },
[1550]866            ou => {
867                iname => 'department',
868                ro => 1,
869                label => l('Department'),
870            },
[1315]871            telephoneNumber => {
872                get => sub {
873                    my ($self) = @_;
874                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
875                        $self->base->get_object('address', $fmainaddress)
876                            ->_get_c_field($self->name);
877                    } else {
878                        return;
879                    }
880                },
881                set => $subsetaddress,
[1550]882                label => l('Phone number'),
[1315]883            },
884            physicalDeliveryOfficeName => {
885                get => sub {
886                    my ($self) = @_;
887                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
888                        $self->base->get_object('address', $fmainaddress)
889                            ->_get_c_field($self->name);
890                    } else {
891                        return;
892                    }
893                },
894                set => $subsetaddress,
[1550]895                label => l('Office'),
[1315]896            },
[1557]897            uid => {
898                iname => 'name',
[1559]899                ro => 1,
[1557]900                label => l('Login'),
901            },
[861]902            cn =>  { iname => 'name', ro => 1 },
[938]903            gecos => {
[1570]904                managed => 1,
[938]905                ro => 1,
906                get => sub {
907                    my ($self) = @_;
908                    my $obj = $self->object;
[1887]909
[1889]910                    my $ec = $obj->_get_c_field('endcircuit') || '';
[1887]911
912                    my $date = $ec
[1889]913                        ? sprintf('%s (%s)', $obj->_get_c_field('expireTextEC'), $obj->_get_c_field('expireText'))
[1887]914                        : $obj->_get_c_field('expireText');
915
[1898]916                    $date ||= '';
917
[938]918                    my $gecos = sprintf("%s,%s,%s,%s",
919                        join(' ', grep { $_ }
920                                ($obj->_get_c_field('givenName'),
921                                ($obj->_get_c_field('sn'))))
922                            || $obj->_get_c_field('description') || '',
923                        join(' - ', grep { $_ } (($obj->_get_c_field('site') ||
924                                    $obj->_get_c_field('l')),
925                            $obj->_get_c_field('physicalDeliveryOfficeName'))) || '',
926                        $obj->_get_c_field('telephoneNumber') || '',
[1887]927                        $date,
[938]928                    );
929                    $gecos =~ s/:/ /g;
930                    return to_ascii($gecos);
931                },
[1550]932                label => l('GECOS'),
[938]933            },
934            displayName  => {
935                ro => 1, managed => 1,
936                get => sub {
937                    my ($self) = @_;
938                    return join(' ', grep { $_ } 
939                        (
940                            $self->object->_get_c_field('givenName'),
941                            $self->object->_get_c_field('sn')
942                        )
943                    )
944                    || $self->object->_get_c_field('description')
[1075]945                    || $self->object->id;
[938]946                },
[1550]947                label => l('Name'),
[938]948            },
[2045]949            sAMAccountName  => {
950                ro => 1,
951                managed => 1,
952                iname => 'name',
[2047]953                get => sub {
954                    my ($self) = @_;
955                    substr($self->object->id, 0, 19)
956                },
[2045]957            },
[938]958            accountExpires => {
959                ro => 1,
960                managed => 1,
961                get => sub {
962                    my ($self) = @_;
963                    my $obj = $self->object;
964                    my $sth = $obj->db->prepare_cached(
965                        sprintf(
[1348]966                            q{select extract(epoch from COALESCE(endcircuit,  expire)) + 11644474161 as expire
[938]967                            from %s where %s = ?},
[1014]968                            $obj->db->quote_identifier($obj->_object_table),
969                            $obj->db->quote_identifier($obj->_key_field),
[938]970                        )
971                    );
972                    $sth->execute($obj->id);
973                    my $res = $sth->fetchrow_hashref;
974                    $sth->finish;
975                    return $res->{expire} ? sprintf("%.f", $res->{expire} * 1E7) : '9223372036854775807';
976                }
977            },
978            shadowExpire => {
979                ro => 1,
980                managed => 1,
981                get => sub {
982                    my ($self) = @_;
983                    my $obj = $self->object;
984                    my $sth = $obj->db->prepare_cached(
985                        sprintf(
[1348]986                            q{select justify_hours(COALESCE(endcircuit,  expire) - '1/1/1970'::timestamp) as expire
[938]987                            from %s where %s = ?},
[1014]988                            $obj->db->quote_identifier($obj->_object_table),
989                            $obj->db->quote_identifier($obj->_key_field),
[938]990                        )
991                    );
992                    $sth->execute($obj->id);
993                    my $res = $sth->fetchrow_hashref;
994                    $sth->finish;
995                    return -1 unless($res->{expire});
996                    $res->{expire} =~ /(\d+) days\s*(\w)?/;
[1625]997                    # Add one day is time is not 00H00
[938]998                    return $1 + ($2 ? 1 : 0);
999                }
1000            },
[861]1001            directReports => {
1002                reference => 'user',
1003                ro => 1,
1004                delayed => 1,
[938]1005                get => sub {
1006                    my ($self) = @_;
1007                    my $obj = $self->object;
1008                    my $sth = $obj->db->prepare_cached(
1009                        q{
[1365]1010                        SELECT
1011                        "user".name FROM
1012                        public."user",
1013                        public.user_attributes_groups,
1014                        public.group_attributes_users,
1015                        public.group_attributes_base gb,
1016                        public."group"
1017                        WHERE
1018                        "user".ikey = user_attributes_groups.okey AND
1019                        user_attributes_groups.value = "group".name AND
1020                        group_attributes_users.okey = gb.okey AND
1021                        "group".ikey = group_attributes_users.okey AND
1022                        gb.attr = 'sutype' AND
1023                        gb.value = 'dpmt' AND
1024                        group_attributes_users.attr = 'managedBy' AND
1025                        group_attributes_users.value = ?
1026                        } . ($self->base->{wexported} ? '' : ' and "user".exported = true') . q{
1027                            and "user".ikey not in
1028                        (select okey from user_attributes_users
1029                        where attr = 'manager' and value != ? )
1030                        union
1031                        select "user".name FROM public."user",
1032                        user_attributes_users where
1033                        user_attributes_users.attr = 'manager' and user_attributes_users.value = ?
1034                            and "user".ikey = user_attributes_users.okey
1035                        } . ($self->base->{wexported} ? '' : ' and "user".exported = true')
[938]1036                    );
[1365]1037                    $sth->execute($obj->id, $obj->id, $obj->id);
[938]1038                    my @res;
1039                    while (my $res = $sth->fetchrow_hashref) {
1040                        push(@res, $res->{name});
1041                    }
1042                    return \@res;
1043                },
[861]1044            },
1045            managedObjects => { ro => 1, reference => 'group', },
[1315]1046            otheraddress => {
1047                ro => 1,
1048                reference => 'address',
1049                get => sub {
1050                    my ($self) = @_;
1051                    my $sth = $self->base->db->prepare_cached(q{
1052                        select name from address left join address_attributes
1053                        on address.ikey = address_attributes.okey and
1054                        address_attributes.attr = 'isMainAddress'
1055                        where "user" = ?
1056                        order by address_attributes.attr
[2136]1057                        } . ($self->base->{wexported} ? '' : ' and "address".exported = true'));
[1315]1058                    $sth->execute($self->object->id);
1059                    my @values;
1060                    while (my $res = $sth->fetchrow_hashref) {
1061                        push(@values, $res->{name});
1062                    }
1063                    return \@values;
1064                },
1065            },
1066            mainaddress => {
1067                ro => 1,
1068                reference => 'address',
1069                get => sub {
1070                    my ($self) = @_;
1071                    my $sth = $self->base->db->prepare_cached(q{
1072                        select name from address join address_attributes on ikey = okey
1073                        where "user" = ? and attr = 'isMainAddress'
[2136]1074                        } . ($self->base->{wexported} ? '' : ' and "address".exported = true'));
[1315]1075                    $sth->execute($self->object->id);
1076                    my $res = $sth->fetchrow_hashref;
1077                    $sth->finish;
1078                    return $res->{name};
1079                },
1080            },
1081            postalAddress => {
1082                ro => 1,
1083                get => sub {
1084                    my ($self) = @_;
1085                    if (my $fmainaddress = $self->object->_get_c_field('mainaddress')) {
1086                        $self->base->get_object('address', $fmainaddress)
1087                            ->_get_c_field($self->name);
1088                    } else {
1089                        return;
1090                    }
1091                },
[1550]1092                label => l('Postal Address'),
[1315]1093            },
[1550]1094            facsimileTelephoneNumber => {
1095                ro => 1,
1096                label => l('Fax number'),
1097            },
[861]1098            allsite   => {
1099                ro => 1,
1100                reference => 'site',
1101            },
1102            managerContact => {
[1365]1103                delayed => 1,
1104                can_values => sub {
1105                    my %uniq = map { $_ => 1 } grep { $_ }
[1912]1106                    (($_[1] ? $_[1]->get_attributes('managerContact') : ()),
1107                    $base->search_objects('user', 'active=1'));
[1365]1108                    sort keys %uniq;
1109                },
[861]1110                reference => 'user',
[1365]1111                monitored => 1,
1112                iname => 'manager',
[1550]1113                label => l('Manager'),
[861]1114            },
[1887]1115            expireTextEC => {
[938]1116                ro => 1,
[1887]1117                managed => 1,
[938]1118                get => sub {
1119                    my ($self) = @_;
1120                    my $obj = $self->object;
1121                    my $sth = $obj->db->prepare_cached(
1122                        sprintf(
[1348]1123                            q{select to_char(COALESCE(endcircuit,  expire), 'YYYY/MM/DD') as expire
[938]1124                            from %s where %s = ?},
[1014]1125                            $obj->db->quote_identifier($obj->_object_table),
1126                            $obj->db->quote_identifier($obj->_key_field),
[938]1127                        )
1128                    );
[1401]1129                    $sth->execute($obj->id) or $obj->db->rollback;
[938]1130                    my $res = $sth->fetchrow_hashref;
1131                    $sth->finish;
1132                    return $res->{expire}
1133                },
1134            },
[1887]1135            expireText => {
1136                ro => 1,
1137                managed => 1,
1138                get => sub {
1139                    my ($self) = @_;
1140                    my $obj = $self->object;
1141                    my $sth = $obj->db->prepare_cached(
1142                        sprintf(
1143                            q{select to_char(expire, 'YYYY/MM/DD') as expire
1144                            from %s where %s = ?},
1145                            $obj->db->quote_identifier($obj->_object_table),
1146                            $obj->db->quote_identifier($obj->_key_field),
1147                        )
1148                    );
1149                    $sth->execute($obj->id) or $obj->db->rollback;
1150                    my $res = $sth->fetchrow_hashref;
1151                    $sth->finish;
1152                    return $res->{expire}
1153                },
1154            },
[1315]1155            krb5ValidEnd => {
1156                ro => 1,
1157                managed => 1,
1158                get => sub {
1159                    my ($self) = @_;
1160                    my $sth = $self->object->db->prepare_cached(
1161                        sprintf(
[1348]1162                            q{select date_part('epoch', COALESCE(endcircuit,  expire))::int as expire
[1315]1163                            from %s where %s = ?},
1164                            $self->object->db->quote_identifier($self->object->_object_table),
1165                            $self->object->db->quote_identifier($self->object->_key_field),
1166                        )
1167                    );
[1401]1168                    $sth->execute($self->object->id) or $self->object->db->rollback;
[1315]1169                    my $res = $sth->fetchrow_hashref;
1170                    $sth->finish;
1171                    return $res->{expire}
1172                },
1173            },
[861]1174            cells  => {
1175                ro => 1,
1176                reference => 'group',
1177            },
1178            departments => {
1179                reference => 'group',
1180                delayed => 1,
1181                ro => 1,
[1550]1182                label => l('Departments'),
[861]1183            },
1184            arrivalDate => { },
[1550]1185            expired => {
[1551]1186                ro => 1,
[1550]1187                label => l('Expired'),
1188            },
1189            active => {
[1551]1190                ro => 1,
[1550]1191                label => l('Active'),
1192            },
[1796]1193            status => {
1194                ro => 1,
1195                label => l('Statut du compte'),
1196            },
[938]1197            pwdAccountLockedTime => {
1198                managed => 1,
1199                ro => 1,
1200                get => sub {
1201                    my ($self) = @_;
1202                    my $obj = $self->object;
1203                    if ($obj->_get_c_field('locked')) {
1204                        return '000001010000Z';
1205                    } else {
1206                        my $sth = $obj->db->prepare_cached(
1207                            sprintf(
[1348]1208                                q{select to_char(COALESCE(endcircuit,  expire) AT TIME ZONE 'Z', 'YYYYMMDDHH24MISSZ') as expire
[938]1209                                from %s where %s = ? and expire < now()},
[1014]1210                                $obj->db->quote_identifier($obj->_object_table),
1211                                $obj->db->quote_identifier($obj->_key_field),
[938]1212                            )
1213                        );
1214                        $sth->execute($obj->id);
1215                        my $res = $sth->fetchrow_hashref;
1216                        $sth->finish;
1217                        return $res->{expire}
1218                    }
1219                },
1220            },
[933]1221            userPassword => { readable => 0, },
[1550]1222            wWWHomePage => {
1223                label => l('Web Page'),
[1953]1224                formopts => { length => 35 },
[1550]1225            },
[1315]1226            title => { },
[1550]1227            snNative => {
1228                label => l('Native name'),
1229            },
1230            givenNameNative => {
1231                label => l('Native first name'),
1232            },
1233            sn => {
1234                label => l('Name'),
1235            },
[1315]1236            shadowWarning => { },
1237            shadowMin => { },
1238            shadowMax => { },
1239            shadowLastChange => { },
1240            shadowInactive => { },
1241            shadowFlag => { },
1242            otherTelephone => { },
[1557]1243            nickname => {
1244                label => l('Nickname'),
1245            },
[1315]1246            mobile => { },
[1550]1247            mail => {
1248                label => l('Email'),
1249            },
[1847]1250            otherEmail => {
1251                label => l('External mail'),
[1842]1252            },
[1315]1253            labeledURI => { },
1254            jobType => { },
1255            ipPhone => { },
[1550]1256            initials => {
1257                label => l('Initials'),
[2049]1258                checkinput => sub {
1259                    $_[0] or return;
1260                    return(length($_[0]) <= 6)
1261                }
[1550]1262            },
[1315]1263            homePhone => { },
[1550]1264            homeDirectory => {
1265                label => l('Home directory'),
1266            },
[1557]1267            halReference => {
1268                label => l('HAL id'),
1269            },
[1315]1270            grade => { },
[1550]1271            givenName => {
[1551]1272                label => l('First name'),
[1550]1273            },
[1315]1274            encryptedPassword => { },
[1550]1275            description => {
1276                label => l('Description'),
1277            },
1278            company => {
1279                label => l('Company'),
1280            },
[1860]1281            employer => {
1282                label => l('Employer'),
1283            },
[1550]1284            comment => {
1285                label => l('Comment'),
1286            },
[1315]1287            college => { },
[1366]1288            passwordLastSet => {
1289                ro => 1,
[1550]1290                label => l('Password set'),
[1366]1291            },
[1752]1292            oldPassword => {
1293                multiple => 1,
1294            },
1295            bannedPassword => {
1296                multiple => 1,
1297            },
[1953]1298            sshPublicKey => {
1299                multiple => 1,
1300                formopts => { length => 45 },
1301            },
[1489]1302            currentEmployment => {
1303                managed => 1,
1304                ro => 1,
1305                reference => 'employment',
1306                get => sub {
1307                    my ($attr) = @_;
1308                    my $self = $attr->object;
1309
[2030]1310                    my $now = DateTime->now()->iso8601 . 'Z';
[2028]1311
[1489]1312                    my $sth = $self->base->db->prepare_cached(
1313                        q{
[2028]1314                        select name from employment where firstday <= ?::timestamp and
1315                        (lastday is null or lastday >= ?::timestamp - '1 days'::interval) and "user" = ?
[1489]1316                        limit 1
1317                        }
1318                    );
[2028]1319                    $sth->execute($now, $now, $self->id);
[1498]1320                    my $res = $sth->fetchrow_hashref;
1321                    $sth->finish;
1322                    if ($res) {
[1489]1323                        return $res->{name}
1324                    } else {
1325                        return;
1326                    }
[1551]1327                },
[1489]1328            },
[1524]1329            nextEmployment => {
1330                managed => 1,
1331                ro => 1,
1332                reference => 'employment',
1333                get => sub {
1334                    my ($attr) = @_;
1335                    my $self = $attr->object;
1336
[2030]1337                    my $now = DateTime->now()->iso8601 . 'Z';
[2028]1338
[1524]1339                    my $sth = $self->base->db->prepare_cached(
1340                        q{
[2031]1341                        select name from employment where firstday > ?::timestamp and "user" = ?
[1645]1342                        order by firstday asc
[1524]1343                        limit 1
1344                        }
1345                    );
[2028]1346                    $sth->execute($now, $self->id);
[1524]1347                    my $res = $sth->fetchrow_hashref;
1348                    $sth->finish;
1349                    if ($res) {
1350                        return $res->{name}
1351                    } else {
1352                        return;
1353                    }
1354                }
1355            },
[1645]1356            prevEmployment => {
1357                managed => 1,
1358                ro => 1,
1359                reference => 'employment',
1360                get => sub {
1361                    my ($attr) = @_;
1362                    my $self = $attr->object;
1363
[2030]1364                    my $now = DateTime->now()->iso8601 . 'Z';
[2028]1365
[1645]1366                    my $sth = $self->base->db->prepare_cached(
1367                        q{
1368                        select name from employment where
[2031]1369                        (lastday is not null and lastday <= ?::timestamp - '1 days'::interval) and "user" = ?
[1645]1370                        order by firstday desc
1371                        limit 1
1372                        }
1373                    );
[2028]1374                    $sth->execute($now, $self->id);
[1645]1375                    my $res = $sth->fetchrow_hashref;
1376                    $sth->finish;
1377                    if ($res) {
1378                        return $res->{name}
1379                    } else {
1380                        return;
1381                    }
1382                }
1383            },
[1500]1384            appliedEmployement => {
[1566]1385                hide => 1,
[1500]1386                reference => 'employment',
1387            },
[1854]1388            contratTypeHistory => {
1389                reference => 'group',
1390                can_values => sub {
1391                    $base->search_objects('group', 'sutype=contrattype')
1392                },
1393                multiple => 1,
1394            },
[1856]1395            employmentHistory => {
1396                reference => 'employment',
1397                multiple => 1,
1398                ro => 1,
1399            },
[1589]1400            hosted => {
1401                formtype => 'CHECKBOX',
1402                label => l('Hosted'),
1403            },
[2114]1404            createRequestId => {
1405                label => l('Account Request id')
[2116]1406            },
[2114]1407            requestId => {
1408                label => l('Request id')
1409            }
[1589]1410
[1524]1411    };
1412
[1577]1413    my $employmentro = sub {
1414        my $setting = $base->config('employment_lock_user') || 'any';
[1524]1415
[1577]1416        for ($setting) {
1417            /^always$/ and return 1;
1418            /^never$/ and return 0;
[1524]1419
[1865]1420            $_[0] or return 0;
[1524]1421
[1577]1422            /^any$/i and return $_[0]->listEmployment ? 1 : 0;
1423            /^active/i and do {
1424                return $_[0]->_get_c_field('currentEmployment')
1425                ? 1
1426                : $_[0]->_get_c_field('nextEmployment') ? 1 : 0;
1427            };
1428            /(\S+)=(\S+)/ and do {
1429                my $attr = $_[0]->_get_c_field($1);
1430                if (defined($attr)) {
1431                    if ($2 eq '*') {
1432                        return 1;
1433                    } elsif($2 eq $attr) {
1434                        return 1;
[1524]1435                    } else {
1436                        return 0;
1437                    }
[1577]1438                } else {
1439                    return 0;
1440                }
1441            };
1442        }
1443        return $_[0]->listEmployment ? 1 : 0; # default is any!
1444    };
1445
[2114]1446    foreach (qw(contratType managerContact company endcircuit department hosted requestId
[1860]1447                contratTypeHistory employer)) {
[1577]1448        $attrs->{$_}{ro} = $employmentro;
[1524]1449    }
1450
[1577]1451    $attrs->{expire}{ro} = sub {
1452        my $expireOn = $base->config('expireOn') || '';
1453        if ($expireOn eq 'never') {
1454            return 0;
1455        }
1456
1457        $employmentro->($_[0]);
1458    };
1459
[1524]1460    $class->SUPER::_get_attr_schema($base, $attrs)
[95]1461}
1462
[1865]1463sub CreateAlias {
1464    my ($class, $base, $name, $for) = @_;
1465
1466    my $stAddAlias = $base->db->prepare_cached(
[1907]1467        q{INSERT INTO "user" (name, uidnumber,            gidnumber, oalias, oaliascache) values
1468                             (?,    -nextval('ikey_seq'), ?,         ?,      ?)}
[1865]1469    );
1470
[1910]1471    my $ref = $base->_derefObject($class->type, $for);
1472    my $res = $stAddAlias->execute($name, -1, $for, $ref ? $ref->id : undef);
[1865]1473    return $res ? 1 : 0;
1474}
1475
[861]1476sub _get_state {
1477    my ($self, $state) = @_;
1478    for ($state) {
1479        /^expired$/ and do {
1480            my $attribute = $self->attribute('expire');
1481            $attribute->check_acl('r') or return;
1482            my $sth = $self->db->prepare_cached(
1483                q{ select coalesce(expire < now(), false) as exp from "user"
1484                where "user".name = ?}
1485            );
1486            $sth->execute($self->id);
1487            my $res = $sth->fetchrow_hashref;
1488            $sth->finish;
1489            return $res->{exp} ? 1 : 0;
1490        };
1491    }
1492}
1493
[1628]1494sub set_fields {
1495    my ($self, %data) = @_;
1496
1497    my $old;
1498    if (exists($data{department})) {
1499        $old = $self->_get_attributes('department');
1500    }
[1941]1501
[1628]1502    if (($data{department} || '') eq ($old || '')) {
1503        # We do not remove the group, there is no change
1504        $old = undef;
1505    }
1506
[1941]1507    if ($old) {
1508        # If the department is no longer a department
1509        # we do nothing
1510        my @names = $self->base->search_objects('group', 'name=' . $old, 'sutype=dpmt');
1511        if (! @names) {
1512            $old = undef;
1513        }
1514    }
1515
[1628]1516    my $res = $self->SUPER::set_fields(%data) or return;
1517
1518    if ($self->base->config('remove_old_dpmt') && $old) {
1519        $self->base->log(LA_DEBUG,
1520            "Removing %s from group %s (department change to %s)",
1521            $self->id,
1522            $old,
1523            $data{department} || '');
1524        $self->_delAttributeValue('memberOf', $old) or return;
1525        $res++;
1526    }
1527
1528    $res
1529}
1530
[1498]1531=head2 listEmployment
1532
1533Return the ordered list of contract
1534
1535=cut
1536
1537sub listEmployment {
1538    my ($self) = @_;
1539
1540    my $sth = $self->base->db->prepare_cached(
1541        q{
1542        select name from employment where "user" = ?
1543        order by lastday desc NULLS first
1544        }
1545    );
1546    $sth->execute($self->id);
1547    my @list = ();
1548    while (my $res = $sth->fetchrow_hashref) {
1549        push(@list, $res->{name});
1550    }
1551
1552    @list
1553}
1554
[2114]1555sub _reported_atributes { qw(contratType endcircuit hosted requestId company employer) }
[1945]1556
1557=head2 applyCurrentEmployment
1558
1559Search the current employment is any and apply paramter to user
1560
1561=cut
1562
1563sub applyCurrentEmployment {
1564    my ($self) = @_;
1565
[1950]1566    # Get current employment name
[1947]1567    my $currentempl = $self->get_attributes('currentEmployment') || '';
[1945]1568
1569    $self->base->log(
1570        LA_DEBUG,
1571        "Applying Employement %s to user %s",
[1947]1572        $currentempl || '(none)',
[1945]1573        $self->id
1574    );
1575
[1947]1576    if (my $currentemployment = $self->base->get_object('employment', $currentempl)) {
[1950]1577
1578        # If an employement apply we set the value to the user object
1579
[1947]1580        $self->computeEmploymentDate;
[1945]1581
[1947]1582        my %attrsets = (
1583            appliedEmployement => $currentemployment->id,
1584        );
1585        foreach my $attr (_reported_atributes(), qw(department managerContact)) {
1586            my $uval = $self->get_attributes($attr) || '';
1587            my $cval = $currentemployment->get_attributes($attr) || '';
[1945]1588
[2074]1589            if ($attr eq 'managerContact') {
1590                if (!$cval) {
1591                    my $dpmt  = $currentemployment->get_attributes('department') or last;
1592                    my $odmpt = $currentemployment->base->get_object('group', $dpmt) or last;
1593                    $cval = $odmpt->get_attributes('managedBy');
[1945]1594                }
1595            }
1596
[1947]1597            if ($uval ne $cval) {
[1948]1598                my $oattr = $currentemployment->base->attribute('user', $attr);
[1947]1599                $attrsets{$oattr->iname} = $cval;
1600            }
[1945]1601        }
1602
[1947]1603        if (keys %attrsets) {
1604            if (my $res = $self->set_fields(%attrsets)) {
1605                $self->ReportChange('Update', 'Attr %s updated to match Employment %s', join(', ', sort keys %attrsets), $currentemployment->id);
1606                return $res;
1607            }
1608        } else {
1609            return 1;
[1945]1610        }
1611    } else {
[1950]1612        # No current employment, resetting values:
1613
[1947]1614        return $self->_resetEmployment;
[1945]1615    }
[1947]1616
[1945]1617}
1618
[1950]1619# Reset attribute value set by employment
[1964]1620# except managerContact and expire
[1950]1621
[1945]1622sub _resetEmployment {
1623    my ($self) = @_;
1624
1625    $self->computeEmploymentDate;
1626
1627    my %changes = (
1628        appliedEmployement => undef,
1629    );
1630
[2074]1631    my @attributesToReset = (_reported_atributes, qw(department));
[1945]1632
1633    foreach my $attr (@attributesToReset) {
1634        my $default = $self->base->config("unemployment.$attr") || '';
1635        my $old = $self->_get_attributes($attr) || '';
[2075]1636
[1945]1637        if ($old ne $default) {
1638            $changes{$attr} = $default || undef;
1639        }
1640    }
[2070]1641
1642    if(!$self->get_attributes('managerContact')) {
1643        if (my $next = $self->_get_attributes('nextEmployment')) {
1644            my $onext = $self->base->get_object('employment', $next);
1645            $changes{'manager'} = $onext->_get_attributes('managerContact');
1646        }
1647    }
1648
[1949]1649    if (%changes) {
1650        if ($self->set_fields(%changes)) {
1651            $self->base->log(LA_NOTICE, "Updating user %s to match unemployment", $self->id);
1652            $self->ReportChange('Update', 'Update %s to match unemployment', join(', ', sort keys %changes));
1653            return 1;
1654        } else {
1655            return 0;
1656        }
1657    } else {
[1945]1658        return 1;
1659    }
1660
1661}
1662
1663
[1636]1664=head2 computeEmploymentDate
1665
1666Compute and copy to user start and end employment date
1667
1668=cut
1669
1670sub computeEmploymentDate {
1671    my ($self) = @_;
1672
1673    my $currentemployment = $self->get_attributes('currentEmployment') || '';
1674
[1649]1675    my $expire = str2time($self->_get_attributes('expire') || '1970-01-01T00:00:00');
[1636]1676
1677    my %changes;
1678    my @employmentDate = qw(
1679        endEmployment   endStrictEmployment   endCurrentEmployment   endLastEmployment
1680        startEmployment startStrictEmployment startCurrentEmployment startFirstEmployment
[1748]1681        arrival departure
[1636]1682    );
1683    foreach (@employmentDate) {
1684        my $old = $self->_get_attributes($_) || '';
1685        my $new = $self->_get_attributes("_$_") || '';
1686        if ($old ne $new) {
1687            $changes{$_} = $new || undef;
1688        }
1689    }
1690
1691    # If there is no current employment we try to find any to not let expire
1692    # unset
1693
1694    my $expireOn = $self->base->config('expireOn') || '';
1695    if (!grep { $_ eq $expireOn } (@employmentDate, '', 'never')) {
1696        $self->base->log(LA_ERR, "expireOn set to invalid parameter %s, using endEmployment instead", $expireOn);
1697        $expireOn = undef;
1698    }
1699    $expireOn ||= 'endEmployment';
1700
[1647]1701    # We check if matching start exists to know if using end* for expiration is
1702    # safe, even undef
1703    my %end2start = (
1704        endEmployment        => 'startEmployment',
1705        endStrictEmployment  => 'startStrictEmployment',
1706        endCurrentEmployment => 'startCurrentEmployment',
1707        endLastEmployment    => 'startFirstEmployment',
1708    );
1709
[1921]1710    # TODO rework this, working code but bloat
[1636]1711    if ($expireOn ne 'never') {
[1748]1712        my $endemploy = '';
[1649]1713        if ($self->_get_attributes("_$end2start{$expireOn}")) {
[1836]1714            $endemploy = $self->_get_attributes("_$expireOn") || '';
1715            $endemploy ||= 'UNCHANGED' unless($currentemployment);
[1649]1716        } elsif (($self->base->config('unemployed_expire') ||'') eq 'no') {
[1748]1717            $endemploy = '';
[1649]1718        } else {
[1921]1719            # No expiration date apply, don't touch
1720            $endemploy = 'UNCHANGED';
[1649]1721        }
[1636]1722
[1649]1723        if ($endemploy ne 'UNCHANGED') {
1724            my $nextexpire = str2time( $endemploy || '1970-01-01T00:00:00' );
[1636]1725
[1649]1726            if ($expire != $nextexpire) {
1727                $changes{expire} = $endemploy;
1728            }
[1636]1729        }
1730    }
1731
1732    if (keys %changes) {
[1922]1733        $self->base->log(LA_DEBUG, 'Applying employment state to user %s for field %s', $self->id, join(', ', keys %changes));
[1636]1734        $self->ReportChange('Update', 'Update %s to match employment', join(', ', sort keys %changes));
[1922]1735        if (exists($changes{expire})) {
1736            $self->ReportChange('Update', 'Expire update to %s to match employment', ($changes{expire} || '(none)'));
1737            $self->base->log(LA_DEBUG, 'New expiration is %s for user %s', ($changes{expire} || '(none)'), $self->id);
1738        }
[1636]1739        $self->set_fields(%changes);
[1922]1740    } else {
1741        $self->base->log(LA_DEBUG, 'No employment change for user %s', $self->id);
[1636]1742    }
1743
[1854]1744    $self->_computeEmploymentHistory();
1745
[1636]1746    return 1;
1747}
1748
[1590]1749sub _computeStartEmployment {
[1757]1750    my ($self, $delay, $any, $workday) = @_;
[1590]1751
[1623]1752    $delay ||= 0;
[1750]1753    my $start;
1754    my $nstart;
[1623]1755
[1750]1756    if (my $next = $self->_get_attributes('nextEmployment')) {
1757        my $onext = $self->base->get_object('employment', $next);
1758        $nstart = DateTime->from_epoch(epoch => str2time($onext->_get_attributes('firstday')));
1759        $nstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1760    }
1761
[1590]1762    my $list_empl = $self->base->db->prepare_cached(q{
[1734]1763        SELECT *, (lastday is null or lastday >= now() - '1days'::interval) as "current" FROM employment
[1645]1764        WHERE "user" = ? and firstday < now()
[1590]1765        order by firstday desc
1766        });
1767    $list_empl->execute($self->id);
[1750]1768
1769    while (my $res = $list_empl->fetchrow_hashref) {
1770        if ($res->{current}) {
1771        } elsif ($nstart) {
[1590]1772            my $prevend = DateTime->from_epoch(epoch => str2time($res->{lastday}));
[1623]1773            $prevend->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
[1750]1774            my $tstart = $nstart->clone;
[1623]1775            $tstart->subtract(days => $delay + 1);
[1590]1776            if ($tstart->ymd gt $prevend->ymd) {
1777                last;
1778            }
[1751]1779        } elsif ((!$res->{current}) && (!$any)) {
[1748]1780            last;
[1590]1781        }
[1750]1782        $nstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
1783        $nstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1784        $start = $nstart->clone;
[1590]1785    }
1786    $list_empl->finish;
1787
[1750]1788    $start ||= $nstart if ($any);
[1748]1789
[1757]1790    if ($start) {
1791        if ($workday) {
1792            my $day_of_week = $start->day_of_week;
1793            $start->add(days =>
1794                $day_of_week == 6 ? 2 :
1795                $day_of_week == 7 ? 1 : 0
1796            );
1797        }
1798    }
1799
[1590]1800    return $start ? $start->iso8601 : undef
1801}
1802
[1526]1803sub _computeEndEmployment {
[2138]1804    my ($self, $delay, $workday) = @_;
[1522]1805
[1623]1806    $delay ||= 0;
[1750]1807    my $end;
1808    my $pend;
[1623]1809
[1750]1810    if (my $prev = $self->_get_attributes('prevEmployment')) {
1811        my $oprev = $self->base->get_object('employment', $prev);
1812        $pend = DateTime->from_epoch(epoch => str2time($oprev->_get_attributes('lastday')));
1813        $pend->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
[1751]1814        $pend->add(hours => 23, minutes => 59, seconds => 59);
[1750]1815    }
1816
[1522]1817    my $list_empl = $self->base->db->prepare_cached(q{
[1590]1818        SELECT *, firstday <= now() as "current" FROM employment WHERE "user" = ? and
1819        (lastday is null or lastday >= now() - '1 days'::interval)
[1522]1820        order by firstday asc
1821        });
1822    $list_empl->execute($self->id);
[1827]1823    while (my $res = $list_empl->fetchrow_hashref) {
[1522]1824        if (!$res->{lastday}) {
1825            # Ultimate employment.
1826            $list_empl->finish;
1827            return undef;
1828        }
[1750]1829        if ($res->{current}) {
[1751]1830        } elsif ($end) {
[1522]1831            my $nextstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
[1623]1832            $nextstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
[1751]1833            my $tend = $end->clone;
[1623]1834            $tend->add(days => $delay + 1);
[1522]1835            if ($tend->ymd lt $nextstart->ymd) {
1836                last;
1837            }
[1827]1838        } elsif ($pend) {
1839            my $nextstart = DateTime->from_epoch(epoch => str2time($res->{firstday}));
1840            $nextstart->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1841            my $tend = $pend->clone;
1842            $tend->add(days => $delay + 1);
1843            if ($tend->ymd lt $nextstart->ymd) {
1844                last;
1845            }
1846            $end = $tend
[1522]1847        }
[1751]1848        $end = DateTime->from_epoch(epoch => str2time($res->{lastday}));
1849        $end->set_time_zone( DateTime::TimeZone->new( name => 'local' ) );
1850        $end->add(hours => 23, minutes => 59, seconds => 59);
[1522]1851    }
1852    $list_empl->finish;
1853
[1757]1854    if ($end) {
1855        if ($workday) {
1856            my $day_of_week = $end->day_of_week;
1857            $end->subtract(days =>
1858                $day_of_week == 7 ? 2 :
1859                $day_of_week == 6 ? 1 : 0
1860            );
1861        }
1862    }
1863
[1522]1864    return $end ? $end->iso8601 : undef
1865}
1866
[1855]1867# Compute a summary of employement and store the value
1868# into an attribute
1869
[1854]1870sub _computeEmploymentHistory {
1871    my ($self) = @_;
1872
1873    my %changes;
1874
[1856]1875    {
1876        my $sth = $self->db->prepare_cached(q{
1877            select employment.name from employment
1878            where "user" = ? and employment.firstday < now()
1879        });
1880        $sth->execute($self->id);
1881        my @values;
1882        while (my $res = $sth->fetchrow_hashref) {
1883            push(@values, $res->{name});
1884        }
1885        $changes{"employmentHistory"} = \@values;
1886    }
1887
[1854]1888    foreach my $attribute (qw(contratType)) {
1889        my $sth = $self->db->prepare_cached(q{
1890            select employment_attributes.value from employment
1891            join employment_attributes
1892            on employment.ikey = employment_attributes.okey
1893            where "user" = ? and employment_attributes.attr = ?
1894            and employment.firstday < now()
1895            group by employment_attributes.value
1896            });
1897        $sth->execute($self->id, $attribute);
1898        my @values;
1899        while (my $res = $sth->fetchrow_hashref) {
1900            push(@values, $res->{value});
1901        }
1902        $changes{$attribute . "History"} = \@values;
1903    }
1904
1905    $self->set_fields(%changes);
1906}
1907
[1752]1908=head2 storeBannedPassword($epassword)
1909
1910Add an encrypted password to untrust list
1911
1912=cut
1913
1914sub storeBannedPassword {
1915    my ($self, $EncPass) = @_;
1916
1917    my @banned = sort { $b cmp $a } $self->_get_attributes('bannedPassword');
1918    my $now = DateTime->now; 
1919    unshift(@banned, $now->iso8601 . ';' . $EncPass);
1920    $self->set_fields('bannedPassword', [ grep { $_ } @banned ]);
1921
1922}
1923
1924=head2 banCurrentPassword
1925
1926Store the current password as banned
1927
1928=cut
1929
1930sub banCurrentPassword {
1931    my ($self) = @_;
1932   
1933    my $old = $self->get_field('userPassword') or return;
1934    $self->storeBannedPassword($old);
1935}
1936
1937sub check_password {
1938    my ( $self, $password ) = @_;
1939
[1754]1940    my $res = $self->SUPER::check_password($password);
1941    if ($res !~ /^ok$/) {
[1752]1942        return $res;
1943    }
1944
1945    foreach my $banned ($self->_get_attributes('bannedPassword')) {
1946        my ($date, $oldPassword) = $banned =~ /^([^;]*);(.*)/;
[1754]1947        if (crypt($password, $oldPassword) eq $oldPassword) {
[1752]1948            return "Banned password, cannot be used anymore";
1949        }
1950    }
1951
[1754]1952    return 'ok';
[1752]1953}
1954
1955sub _set_password {
1956    my ($self, $clear_pass) = @_;
1957    if (my $attr = $self->base->attribute($self->type, 'userPassword')) {
1958        my $field = $attr->iname;
1959
1960        # Storing as old password
1961        my @olds = sort { $b cmp $a } $self->_get_attributes('oldPassword');
[1767]1962        if (my $old = $self->get_field('userPassword')) {
1963            my $now = DateTime->now; 
1964            unshift(@olds, $now->iso8601 . ';' . $old);
1965            $self->set_fields('oldPassword', [ grep { $_ } @olds[0 .. 14] ]);
1966        }
[1752]1967
[2041]1968        my $res = $self->set_fields($field, $self->base->passCrypt($clear_pass));
[1752]1969        if ($res) {
1970            if ($self->base->get_global_value('rsa_public_key')) {
1971                $self->setCryptPassword($clear_pass) or return;
1972            }
1973        }
1974
1975        $self->set_fields('passwordLastSet', DateTime->now->datetime);
1976        $self->base->log(LA_NOTICE,
1977            'Mot de passe changé pour %s',
1978            $self->id
1979        );
1980
1981
1982        return $res;
1983    } else {
1984        $self->log(LA_WARN,
1985            "Cannot set password: userPassword attributes is unsupported");
1986    }
1987}
1988
1989=head2 setCryptPassword($clear_pass)
1990
1991Store password encrypted using RSA encryption.
1992
1993=cut
1994
1995sub setCryptPassword {
1996    my ($self, $clear_pass) = @_;
1997    if (my $serialize = $self->base->get_global_value('rsa_public_key')) {
1998        my $public = Crypt::RSA::Key::Public->new;
1999        $public = $public->deserialize(String => [ $serialize ]);
2000        my $rsa = new Crypt::RSA ES => 'PKCS1v15';
2001        my $rsa_password = $rsa->encrypt (
2002            Message    => $clear_pass,
2003            Key        => $public,
2004            Armour     => 1,
2005        ) || die $rsa->errstr();
2006        if (!$self->_set_c_fields('encryptedPassword', $rsa_password)) {
2007            $self->log(LA_ERR,
2008                "Cannot set 'encryptedPassword' attribute for object %s/%s",
2009                $self->type, $self->id,
2010            );
2011            return;
2012        }
2013    }
2014    $self->ReportChange('Password', 'Password stored using internal key');
2015    return 1;
2016}
2017
[1935]2018=head2 _InjectCryptPasswd($cryptpasswd)
[1752]2019
[1935]2020Inject a password encrypted using standard UNIX method.
2021
2022The passwrod will be used to authenticate user inside the application but it
2023will not be transmit to any other database.
2024
2025=cut
2026
2027sub _InjectCryptPasswd {
2028    my ($self, $cryptpasswd) = @_;
2029
2030    if (my $current = $self->get_field('userPassword')) {
2031        if ($cryptpasswd eq $current) {
2032            return 1;
2033        }
2034    }
2035    my $res = $self->set_fields('userPassword', $cryptpasswd);
2036
2037    if ($res) {
2038        $self->base->log(LA_NOTICE, 'Crypted password injected for %s', $self->id);
2039        return 1;
2040    } else {
2041        $self->base->log(LA_ERR, 'Cannot inject crypted password for %s', $self->id);
2042        return 0;
2043    }
2044}
2045
[1640]2046=head2 GenPasswordResetId
2047
2048Return a new id allowing passowrd reset
2049
2050=cut
2051
2052sub GenPasswordResetId {
2053    my ($self) = @_;
2054
2055    my $id = LATMOS::Accounts::Utils::genpassword(length => 32);
2056
2057    my $sth = $self->base->db->prepare_cached(q{
2058        INSERT INTO passwordreset (id, "user") values (?,?)
2059    });
2060
2061    if ($sth->execute($id, $self->id)) {
2062        return $id;
2063    } else {
2064        return;
2065    }
2066}
2067
2068=head2 SendPasswordReset($url)
2069
2070Generate a password reset Id and the to the user.
2071
2072C<$url> is the URL where the password can changed (printf forward, the %s is
2073replaced by the request id)
2074
2075=cut
2076
2077sub SendPasswordReset {
2078    my ($self, $url) = @_;
2079
2080    my $id = $self->GenPasswordResetId;
2081
[1642]2082    my $mail = $self->_get_attributes('mail') or do {
[1640]2083        $self->base->log(LA_ERR, "Cannot sent reset password mail: no mail found");
2084        return;
2085    };
2086
[2055]2087    my $MailSubject = $self->base->la->val('_default_', 'mailSubject', 'LATMOS::Accounts');
[1958]2088
[1640]2089    my %mail = (
[1958]2090        Subject => "$MailSubject: pasword reset",
2091        'X-LATMOS-Reason' => 'Password Reset',
[1640]2092        to => $mail,
2093    );
2094
[1847]2095    if (my $otherEmail = $self->_get_attributes('otherEmail')) {
2096        $mail{cc} = $otherEmail;
[1846]2097    }
2098
[1640]2099    my $vars = {
2100        url => sprintf($url, $id),
2101    };
2102    $vars->{id} =  $id;
2103    $vars->{obj} = $self;
2104
2105    my $lamail = LATMOS::Accounts::Mail->new(
2106        $self->base->la,
2107        'passwordreset.mail',
2108    );
2109
2110    if ($lamail->process(\%mail, $vars)) {
2111        $self->base->log(LA_NOTICE,
2112            "Reset password sent to %s for user %s",
2113            $mail{to},
2114            $self->id,
2115        );
[1641]2116        return 1;
2117    } else {
2118        return;
[1640]2119    }
[1641]2120
[1640]2121}
2122
2123=head2 CheckPasswordResetId($id)
2124
2125Return True if the reset password ID can be found and is less than one day old
2126
2127=cut
2128
2129sub CheckPasswordResetId {
2130    my ($self, $id) = @_;
2131
2132    my $sth = $self->base->db->prepare_cached(q{
2133        SELECT * FROM passwordreset WHERE
2134            "user" = ? and
2135            id = ? and
2136            "create" >= now() - '1 days'::interval
2137    });
2138    $sth->execute($self->id, $id);
2139
2140    my $res = $sth->fetchrow_hashref;
2141    $sth->finish;
2142
2143    return $res ? 1 : 0;
2144}
2145
[1641]2146=head2 DeletePasswordId($id)
2147
2148Delete password reset C<$id> and all expired request
2149
2150=cut
2151
2152sub DeletePasswordId {
2153    my ($self, $id) = @_;
2154
2155    my $sth = $self->base->db->prepare_cached(q{
2156        DELETE FROM passwordreset WHERE
2157        "user" = ? AND (id = ? or "create" < now() - '1 days'::interval)
2158    });
2159
2160    $sth->execute($self->id, $id);
2161}
2162
[19]21631;
2164
2165__END__
2166
2167=head1 SEE ALSO
2168
2169=head1 AUTHOR
2170
2171Olivier Thauvin, E<lt>olivier.thauvin@latmos.ipsl.frE<gt>
2172
2173=head1 COPYRIGHT AND LICENSE
2174
2175Copyright (C) 2008, 2009 CNRS SA/CETP/LATMOS
2176
2177This library is free software; you can redistribute it and/or modify
2178it under the same terms as Perl itself, either Perl version 5.10.0 or,
2179at your option, any later version of Perl 5 you may have available.
2180
2181=cut
Note: See TracBrowser for help on using the repository browser.