View | Details | Raw Unified | Return to bug 26744
Collapse All | Expand All

(-)a/Koha/Patron.pm (-1 / +51 lines)
Lines 20-30 package Koha::Patron; Link Here
20
20
21
use Modern::Perl;
21
use Modern::Perl;
22
22
23
use List::MoreUtils qw( any uniq );
23
use List::MoreUtils qw( any uniq notall zip6);
24
use JSON qw( to_json );
24
use JSON qw( to_json );
25
use Unicode::Normalize qw( NFKD );
25
use Unicode::Normalize qw( NFKD );
26
use Try::Tiny;
26
use Try::Tiny;
27
use DateTime ();
27
use DateTime ();
28
use C4::Log qw( logaction );
28
29
29
use C4::Auth qw( checkpw_hash );
30
use C4::Auth qw( checkpw_hash );
30
use C4::Context;
31
use C4::Context;
Lines 2124-2129 Or setter FIXME Link Here
2124
sub extended_attributes {
2125
sub extended_attributes {
2125
    my ( $self, $attributes ) = @_;
2126
    my ( $self, $attributes ) = @_;
2126
    if ($attributes) {    # setter
2127
    if ($attributes) {    # setter
2128
        my %attribute_changes;
2129
        my $attribute_key = sub {
2130
            my ($attribute) = @_;
2131
            return join(':', $attribute->code, $self->borrowernumber);
2132
        };
2133
        if (C4::Context->preference("BorrowersLog")) {
2134
            for my $attribute ($self->extended_attributes->as_list) {
2135
                my $repeatable = $attribute->type->repeatable ? 1 : 0;
2136
                $attribute_changes{$repeatable}->{$attribute_key->($attribute)}->{before} = [];
2137
                push (
2138
                    @{$attribute_changes{$repeatable}->{$attribute_key->($attribute)}->{before}},
2139
                    $attribute->attribute
2140
                );
2141
            }
2142
        }
2127
        my $schema = $self->_result->result_source->schema;
2143
        my $schema = $self->_result->result_source->schema;
2128
        $schema->txn_do(
2144
        $schema->txn_do(
2129
            sub {
2145
            sub {
Lines 2155-2160 sub extended_attributes { Link Here
2155
                }
2171
                }
2156
            }
2172
            }
2157
        );
2173
        );
2174
        if (C4::Context->preference("BorrowersLog")) {
2175
            for my $attribute ($self->extended_attributes->as_list) {
2176
                my $repeatable = $attribute->type->repeatable ? 1 : 0;
2177
                $attribute_changes{$repeatable}->{$attribute_key->($attribute)}->{after} = [];
2178
                push (
2179
                    @{$attribute_changes{$repeatable}->{$attribute_key->($attribute)}->{after}},
2180
                    $attribute->attribute
2181
                );
2182
            }
2183
            my $is_different = sub {
2184
                my ($a, $b) = map { [ sort @{$_} ] } @_;
2185
                return @{$a} != @{$b} || notall { $_->[0] eq $_->[1] } zip6 @{$a}, @{$b};
2186
            };
2187
            while (my ($repeatable, $changes) = each %attribute_changes) {
2188
                while (my ($attribute_key, $change) = each %{$changes}) {
2189
                    my ($code) = split(':', $attribute_key);
2190
                    $change->{before} //= [];
2191
                    $change->{after} //= [];
2192
2193
                    if ($is_different->($change->{before}, $change->{after})) {
2194
                        unless ($repeatable) {
2195
                            $change->{before} = @{$change->{before}} ? $change->{before}->[0] : '';
2196
                            $change->{after} = @{$change->{after}} ? $change->{after}->[0] : '';
2197
                        }
2198
                        logaction(
2199
                            "MEMBERS",
2200
                            "MODIFY",
2201
                            $self->borrowernumber,
2202
                            "Patron attribute " . $code . ": " . to_json($change, { pretty => 1, canonical => 1 })
2203
                        );
2204
                    }
2205
                }
2206
            }
2207
        }
2158
    }
2208
    }
2159
2209
2160
    my $rs = $self->_result->borrower_attributes;
2210
    my $rs = $self->_result->borrower_attributes;
(-)a/Koha/Patron/Attribute.pm (-55 lines)
Lines 21-34 use Koha::Database; Link Here
21
use Koha::Exceptions::Patron::Attribute;
21
use Koha::Exceptions::Patron::Attribute;
22
use Koha::Patron::Attribute::Types;
22
use Koha::Patron::Attribute::Types;
23
use Koha::AuthorisedValues;
23
use Koha::AuthorisedValues;
24
use Koha::Cache::Memory::Lite;
25
use C4::Log qw( logaction );
26
use JSON qw( to_json );
27
24
28
use base qw(Koha::Object);
25
use base qw(Koha::Object);
29
26
30
our %deleted_attributes;
31
32
=head1 NAME
27
=head1 NAME
33
28
34
Koha::Patron::Attribute - Koha Patron Attribute Object class
29
Koha::Patron::Attribute - Koha Patron Attribute Object class
Lines 60-92 sub store { Link Here
60
    Koha::Exceptions::Patron::Attribute::UniqueIDConstraint->throw( attribute => $self )
55
    Koha::Exceptions::Patron::Attribute::UniqueIDConstraint->throw( attribute => $self )
61
        unless $self->unique_ok();
56
        unless $self->unique_ok();
62
57
63
    # Only log for non-repeatable attributes
64
    if (!$self->type->repeatable && C4::Context->preference("BorrowersLog")) {
65
        my $change;
66
        if ( exists $deleted_attributes{$self->_deleted_attribute_key} ) {
67
            my $previous_attribute = $deleted_attributes{$self->_deleted_attribute_key};
68
            if ( $previous_attribute->attribute ne $self->attribute ) {
69
                $change = {
70
                    before => $previous_attribute->attribute,
71
                    after  => $self->attribute
72
                };
73
            }
74
            delete $deleted_attributes{$self->_deleted_attribute_key};
75
        } else {
76
            $change = {
77
                before => "",
78
                after  => $self->attribute
79
            };
80
        }
81
        if (defined $change) {
82
            logaction(
83
                "MEMBERS",
84
                "MODIFY",
85
                $self->borrowernumber,
86
                "Patron attribute " . $self->code . ": " . to_json($change, { pretty => 1, canonical => 1 })
87
            );
88
        }
89
    }
90
    return $self->SUPER::store();
58
    return $self->SUPER::store();
91
}
59
}
92
60
Lines 221-240 sub unique_ok { Link Here
221
    return $ok;
189
    return $ok;
222
}
190
}
223
191
224
=head3 delete
225
226
Overloaded I<store> method to temporarily stash deleted attribute for action log.
227
228
=cut
229
230
sub delete {
231
    my ($self) = @_;
232
    if (!$self->type->repeatable && C4::Context->preference("BorrowersLog")) {
233
        $deleted_attributes{$self->_deleted_attribute_key} = $self;
234
    }
235
    $self->SUPER::delete();
236
}
237
238
=head2 Internal methods
192
=head2 Internal methods
239
193
240
=head3 _type
194
=head3 _type
Lines 245-257 sub _type { Link Here
245
    return 'BorrowerAttribute';
199
    return 'BorrowerAttribute';
246
}
200
}
247
201
248
=head3 _deleted_attribute_key
249
250
=cut
251
252
sub _deleted_attribute_key {
253
    my ($self) = @_;
254
    return join(':', $self->borrowernumber, $self->code);
255
}
256
257
1;
202
1;
(-)a/t/db_dependent/Koha/Patron/Attribute.t (-61 / +111 lines)
Lines 409-418 subtest 'merge_and_replace_with' => sub { Link Here
409
};
409
};
410
410
411
subtest 'action log tests' => sub {
411
subtest 'action log tests' => sub {
412
    plan tests => 4;
412
    plan tests => 9;
413
    my $schema = Koha::Database->new->schema;
413
    my $schema = Koha::Database->new->schema;
414
    $schema->storage->txn_begin;
414
    $schema->storage->txn_begin;
415
415
416
    my $get_info = sub {
417
        my ($before, $after, $code, $repeatable) = @_;
418
        my $change = {
419
            before => $before,
420
            after  => $after
421
        };
422
        if ($repeatable) {
423
            while (my ($k, $v) = each %{$change}) {
424
                $change->{$k} = $v ? [$v] : [];
425
            }
426
        }
427
        return  "Patron attribute " . $code . ": " . to_json($change, { pretty => 1, canonical => 1 });
428
    };
429
416
    my $patron = $builder->build_object({ class=> 'Koha::Patrons' });
430
    my $patron = $builder->build_object({ class=> 'Koha::Patrons' });
417
    my $attribute_type = $builder->build_object(
431
    my $attribute_type = $builder->build_object(
418
        {
432
        {
Lines 429-439 subtest 'action log tests' => sub { Link Here
429
        }
443
        }
430
    ];
444
    ];
431
    $patron->extended_attributes($attributes);
445
    $patron->extended_attributes($attributes);
432
    my $change = {
446
433
        before => "",
447
    my $info = $get_info->('', 'Foo', $attribute_type->code);
434
        after  => 'Foo',
435
    };
436
    my $info = "Patron attribute " . $attribute_type->code . ": " . to_json($change, { pretty => 1, canonical => 1 });
437
    my $action_logs = Koha::ActionLogs->search(
448
    my $action_logs = Koha::ActionLogs->search(
438
        {
449
        {
439
            module => "MEMBERS",
450
            module => "MEMBERS",
Lines 447-510 subtest 'action log tests' => sub { Link Here
447
        0,
458
        0,
448
        'No action log entry has been created when adding patron attribute if BorrowersLog syspref disabled'
459
        'No action log entry has been created when adding patron attribute if BorrowersLog syspref disabled'
449
    );
460
    );
450
    $patron->extended_attributes([]);
451
461
452
    t::lib::Mocks::mock_preference( 'BorrowersLog', 1 );
462
    t::lib::Mocks::mock_preference( 'BorrowersLog', 1 );
453
    $patron->extended_attributes($attributes);
463
    my $current_action_logs_count;
454
    $action_logs = Koha::ActionLogs->search(
464
    my $repeatable_text;
455
        {
465
    for my $repeatable (0, 1) {
456
            module => "MEMBERS",
466
        $repeatable_text = $repeatable ? ' repeatable' : '';
457
            action => "MODIFY",
458
            object => $patron->borrowernumber,
459
            info => $info
460
        }
461
    );
462
    is(
463
        $action_logs->count,
464
        1,
465
        'An action log entry has been created when adding patron attribute'
466
    );
467
467
468
    $patron->extended_attributes($attributes);
468
        $patron->extended_attributes([]);
469
    $action_logs = Koha::ActionLogs->search(
470
        {
471
            module => "MEMBERS",
472
            action => "MODIFY",
473
            object => $patron->borrowernumber,
474
            info => $info
475
        }
476
    );
477
    is(
478
        $action_logs->count,
479
        1,
480
        'No additional action log entry has been created when updating patron attribute with same value'
481
    );
482
469
483
    $attributes = [
470
        $attribute_type = $builder->build_object(
484
        {
471
            {
485
            attribute => 'Bar',
472
                class => 'Koha::Patron::Attribute::Types',
486
            code => $attribute_type->code,
473
                value  => { repeatable => $repeatable }
487
        }
474
            }
488
    ];
475
        );
489
    $patron->extended_attributes($attributes);
476
        $attributes = [
490
    $change = {
477
            {
491
        before => 'Foo',
478
                attribute => 'Foo',
492
        after  => 'Bar'
479
                code => $attribute_type->code,
493
    };
480
            }
494
    $info = "Patron attribute " . $attribute_type->code . ": " . to_json($change, { pretty => 1, canonical => 1 });
481
        ];
495
    $action_logs = Koha::ActionLogs->search(
482
496
        {
483
        $patron->extended_attributes($attributes);
497
            module => "MEMBERS",
484
        $info = $get_info->('', 'Foo', $attribute_type->code, $repeatable);
498
            action => "MODIFY",
485
        $action_logs = Koha::ActionLogs->search(
499
            object => $patron->borrowernumber,
486
            {
500
            info => $info
487
                module => "MEMBERS",
501
        }
488
                action => "MODIFY",
502
    );
489
                object => $patron->borrowernumber,
503
    is(
490
                info => $info
504
        $action_logs->count,
491
            }
505
        1,
492
        );
506
        'New action log entry has been created when updating patron attribute with different value'
493
        is(
507
    );
494
            $action_logs->count,
495
            1,
496
            "An action log entry has been created when adding$repeatable_text patron attribute"
497
        );
498
499
        $current_action_logs_count = Koha::ActionLogs->search(
500
            {
501
                module => "MEMBERS",
502
                action => "MODIFY",
503
                object => $patron->borrowernumber
504
            }
505
        )->count;
506
507
        $patron->extended_attributes($attributes);
508
        $action_logs = Koha::ActionLogs->search(
509
            {
510
                module => "MEMBERS",
511
                action => "MODIFY",
512
                object => $patron->borrowernumber
513
            }
514
        );
515
        is(
516
            $action_logs->count,
517
            $current_action_logs_count,
518
            "No additional action log entry has been created when updating$repeatable_text patron attribute with same value"
519
        );
520
521
        $attributes = [
522
            {
523
                attribute => 'Bar',
524
                code => $attribute_type->code,
525
            }
526
        ];
527
        $patron->extended_attributes($attributes);
528
        $info = $get_info->('Foo', 'Bar', $attribute_type->code, $repeatable);
529
        $action_logs = Koha::ActionLogs->search(
530
            {
531
                module => "MEMBERS",
532
                action => "MODIFY",
533
                object => $patron->borrowernumber,
534
                info => $info
535
            }
536
        );
537
        is(
538
            $action_logs->count,
539
            1,
540
            "New action log entry has been created when updating$repeatable_text patron attribute with different value"
541
        );
542
543
        $patron->extended_attributes([]);
544
        $info = $get_info->('Bar', '', $attribute_type->code, $repeatable);
545
        $action_logs = Koha::ActionLogs->search(
546
            {
547
                module => "MEMBERS",
548
                action => "MODIFY",
549
                object => $patron->borrowernumber,
550
                info => $info
551
            }
552
        );
553
        is(
554
            $action_logs->count,
555
            1,
556
            "New action log entry has been created when deleting$repeatable_text patron attribute value"
557
        );
558
    }
508
559
509
    $schema->storage->txn_rollback;
560
    $schema->storage->txn_rollback;
510
};
561
};
511
- 

Return to bug 26744