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 2131-2136 Or setter FIXME Link Here
2131
sub extended_attributes {
2132
sub extended_attributes {
2132
    my ( $self, $attributes ) = @_;
2133
    my ( $self, $attributes ) = @_;
2133
    if ($attributes) {    # setter
2134
    if ($attributes) {    # setter
2135
        my %attribute_changes;
2136
        my $attribute_key = sub {
2137
            my ($attribute) = @_;
2138
            return join(':', $attribute->code, $self->borrowernumber);
2139
        };
2140
        if (C4::Context->preference("BorrowersLog")) {
2141
            for my $attribute ($self->extended_attributes->as_list) {
2142
                my $repeatable = $attribute->type->repeatable ? 1 : 0;
2143
                $attribute_changes{$repeatable}->{$attribute_key->($attribute)}->{before} = [];
2144
                push (
2145
                    @{$attribute_changes{$repeatable}->{$attribute_key->($attribute)}->{before}},
2146
                    $attribute->attribute
2147
                );
2148
            }
2149
        }
2134
        my $schema = $self->_result->result_source->schema;
2150
        my $schema = $self->_result->result_source->schema;
2135
        $schema->txn_do(
2151
        $schema->txn_do(
2136
            sub {
2152
            sub {
Lines 2162-2167 sub extended_attributes { Link Here
2162
                }
2178
                }
2163
            }
2179
            }
2164
        );
2180
        );
2181
        if (C4::Context->preference("BorrowersLog")) {
2182
            for my $attribute ($self->extended_attributes->as_list) {
2183
                my $repeatable = $attribute->type->repeatable ? 1 : 0;
2184
                $attribute_changes{$repeatable}->{$attribute_key->($attribute)}->{after} = [];
2185
                push (
2186
                    @{$attribute_changes{$repeatable}->{$attribute_key->($attribute)}->{after}},
2187
                    $attribute->attribute
2188
                );
2189
            }
2190
            my $is_different = sub {
2191
                my ($a, $b) = map { [ sort @{$_} ] } @_;
2192
                return @{$a} != @{$b} || notall { $_->[0] eq $_->[1] } zip6 @{$a}, @{$b};
2193
            };
2194
            while (my ($repeatable, $changes) = each %attribute_changes) {
2195
                while (my ($attribute_key, $change) = each %{$changes}) {
2196
                    my ($code) = split(':', $attribute_key);
2197
                    $change->{before} //= [];
2198
                    $change->{after} //= [];
2199
2200
                    if ($is_different->($change->{before}, $change->{after})) {
2201
                        unless ($repeatable) {
2202
                            $change->{before} = @{$change->{before}} ? $change->{before}->[0] : '';
2203
                            $change->{after} = @{$change->{after}} ? $change->{after}->[0] : '';
2204
                        }
2205
                        logaction(
2206
                            "MEMBERS",
2207
                            "MODIFY",
2208
                            $self->borrowernumber,
2209
                            "Patron attribute " . $code . ": " . to_json($change, { pretty => 1, canonical => 1 })
2210
                        );
2211
                    }
2212
                }
2213
            }
2214
        }
2165
    }
2215
    }
2166
2216
2167
    my $rs = $self->_result->borrower_attributes;
2217
    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