| Lines 29-42
          use C4::Output;
      
      
        Link Here | 
        
          | 29 | use C4::Members; | 29 | use C4::Members; | 
        
          | 30 | use C4::Members::Attributes qw( GetBorrowerAttributes ); | 30 | use C4::Members::Attributes qw( GetBorrowerAttributes ); | 
        
          | 31 | use C4::Form::MessagingPreferences; | 31 | use C4::Form::MessagingPreferences; | 
            
              | 32 | use Koha::Patrons; |  |  | 
            
              | 33 | use Koha::Patron::Modification; | 
            
              | 34 | use Koha::Patron::Modifications; | 
        
          | 35 | use C4::Scrubber; | 32 | use C4::Scrubber; | 
        
          | 36 | use Email::Valid; | 33 | use Email::Valid; | 
        
          | 37 | use Koha::DateUtils; | 34 | use Koha::DateUtils; | 
        
          | 38 | use Koha::Libraries; | 35 | use Koha::Libraries; | 
            
              |  |  | 36 | use Koha::Patron::Attribute::Types; | 
            
              | 37 | use Koha::Patron::Attributes; | 
        
          | 39 | use Koha::Patron::Images; | 38 | use Koha::Patron::Images; | 
            
              |  |  | 39 | use Koha::Patron::Modification; | 
            
              | 40 | use Koha::Patron::Modifications; | 
            
              | 41 | use Koha::Patrons; | 
        
          | 40 | use Koha::Token; | 42 | use Koha::Token; | 
        
          | 41 |  | 43 |  | 
        
          | 42 | my $cgi = new CGI; | 44 | my $cgi = new CGI; | 
  
    | Lines 246-260
          elsif ( $action eq 'update' ) {
      
      
        Link Here | 
        
          | 246 |                 secret => md5_base64( Encode::encode( 'UTF-8', C4::Context->config('pass') ) ), | 248 |                 secret => md5_base64( Encode::encode( 'UTF-8', C4::Context->config('pass') ) ), | 
        
          | 247 |             }), | 249 |             }), | 
        
          | 248 |         ); | 250 |         ); | 
          
            
              | 249 |         $template->param( patron_attribute_classes => GeneratePatronAttributesForm( undef, $attributes ) ); | 251 |         $template->param( patron_attribute_classes => GeneratePatronAttributesForm( $borrowernumber, $attributes ) ); | 
        
          | 250 |  | 252 |  | 
        
          | 251 |         $template->param( action => 'edit' ); | 253 |         $template->param( action => 'edit' ); | 
        
          | 252 |     } | 254 |     } | 
        
          | 253 |     else { | 255 |     else { | 
        
          | 254 |         my %borrower_changes = DelUnchangedFields( $borrowernumber, %borrower ); | 256 |         my %borrower_changes = DelUnchangedFields( $borrowernumber, %borrower ); | 
          
            
              | 255 |         my $extended_attributes_changes = ExtendedAttributesMatch( $borrowernumber, $attributes ); | 257 |         my $extended_attributes_changes = FilterUnchagedAttributes( $borrowernumber, $attributes ); | 
        
          | 256 |  | 258 |  | 
          
            
              | 257 |         if ( %borrower_changes || $extended_attributes_changes ) { | 259 |         if ( %borrower_changes || scalar @{$extended_attributes_changes} > 0 ) { | 
        
          | 258 |             ( $template, $borrowernumber, $cookie ) = get_template_and_user( | 260 |             ( $template, $borrowernumber, $cookie ) = get_template_and_user( | 
        
          | 259 |                 { | 261 |                 { | 
        
          | 260 |                     template_name   => "opac-memberentry-update-submitted.tt", | 262 |                     template_name   => "opac-memberentry-update-submitted.tt", | 
  
    | Lines 265-271
          elsif ( $action eq 'update' ) {
      
      
        Link Here | 
        
          | 265 |             ); | 267 |             ); | 
        
          | 266 |  | 268 |  | 
        
          | 267 |             $borrower_changes{borrowernumber} = $borrowernumber; | 269 |             $borrower_changes{borrowernumber} = $borrowernumber; | 
          
            
              | 268 |             $borrower_changes{extended_attributes} = to_json($attributes); | 270 |             $borrower_changes{extended_attributes} = to_json($extended_attributes_changes); | 
        
          | 269 |  | 271 |  | 
        
          | 270 |             # FIXME update the following with | 272 |             # FIXME update the following with | 
        
          | 271 |             # Koha::Patron::Modifications->search({ borrowernumber => $borrowernumber })->delete; | 273 |             # Koha::Patron::Modifications->search({ borrowernumber => $borrowernumber })->delete; | 
  
    | Lines 286-292
          elsif ( $action eq 'update' ) {
      
      
        Link Here | 
        
          | 286 |                 action => 'edit', | 288 |                 action => 'edit', | 
        
          | 287 |                 nochanges => 1, | 289 |                 nochanges => 1, | 
        
          | 288 |                 borrower => GetMember( borrowernumber => $borrowernumber ), | 290 |                 borrower => GetMember( borrowernumber => $borrowernumber ), | 
          
            
              | 289 |                 patron_attribute_classes => GeneratePatronAttributesForm( undef, $attributes ), | 291 |                 patron_attribute_classes => GeneratePatronAttributesForm( $borrowernumber, $attributes ), | 
        
          | 290 |                 csrf_token => Koha::Token->new->generate_csrf({ | 292 |                 csrf_token => Koha::Token->new->generate_csrf({ | 
        
          | 291 |                     id     => Encode::encode( 'UTF-8', $borrower->{userid} ), | 293 |                     id     => Encode::encode( 'UTF-8', $borrower->{userid} ), | 
        
          | 292 |                     secret => md5_base64( Encode::encode( 'UTF-8', C4::Context->config('pass') ) ), | 294 |                     secret => md5_base64( Encode::encode( 'UTF-8', C4::Context->config('pass') ) ), | 
  
    | Lines 472-543
          sub DelEmptyFields {
      
      
        Link Here | 
        
          | 472 |     return %borrower; | 474 |     return %borrower; | 
        
          | 473 | } | 475 | } | 
        
          | 474 |  | 476 |  | 
          
            
              | 475 | sub ExtendedAttributesMatch { | 477 | sub FilterUnchagedAttributes { | 
        
          | 476 |     my ( $borrowernumber, $entered_attributes ) = @_; | 478 |     my ( $borrowernumber, $entered_attributes ) = @_; | 
        
          | 477 |  | 479 |  | 
          
            
              | 478 |     my @patron_attributes_arr = GetBorrowerAttributes( $borrowernumber, 1 ); | 480 |     my @patron_attributes = grep {$_->opac_editable} Koha::Patron::Attributes->search({ borrowernumber => $borrowernumber })->as_list; | 
            
              | 479 |     my $patron_attributes = $patron_attributes_arr[0]; | 481 |  | 
            
              |  |  | 482 |     my $patron_attribute_types; | 
            
              | 483 |     foreach my $attr (@patron_attributes) { | 
            
              | 484 |         $patron_attribute_types->{ $attr->code } += 1; | 
            
              | 485 |     } | 
            
              | 486 |  | 
            
              | 487 |     my $passed_attribute_types; | 
            
              | 488 |     foreach my $attr (@{ $entered_attributes }) { | 
            
              | 489 |         $passed_attribute_types->{ $attr->{ code } } += 1; | 
            
              | 490 |     } | 
            
              | 491 |  | 
            
              | 492 |     my @changed_attributes; | 
            
              | 493 |  | 
            
              | 494 |     # Loop through the current patron attributes | 
            
              | 495 |     foreach my $attribute_type ( keys %{ $patron_attribute_types } ) { | 
            
              | 496 |         if ( $patron_attribute_types->{ $attribute_type } !=  $passed_attribute_types->{ $attribute_type } ) { | 
            
              | 497 |             # count differs, overwrite all attributes for given type | 
            
              | 498 |             foreach my $attr (@{ $entered_attributes }) { | 
            
              | 499 |                 push @changed_attributes, $attr | 
            
              | 500 |                     if $attr->{ code } eq $attribute_type; | 
            
              | 501 |             } | 
            
              | 502 |         } else { | 
            
              | 503 |             # count matches, check values | 
            
              | 504 |             my $changes = 0; | 
            
              | 505 |             foreach my $attr (grep { $_->code eq $attribute_type } @patron_attributes) { | 
            
              | 506 |                 $changes = 1 | 
            
              | 507 |                     unless any { $_->{ value } eq $attr->attribute } @{ $entered_attributes }; | 
            
              | 508 |                 last if $changes; | 
            
              | 509 |             } | 
        
          | 480 |  | 510 |  | 
          
            
              | 481 |     if ( scalar @{$entered_attributes} != scalar @{$patron_attributes} ) { | 511 |             if ( $changes ) { | 
            
              | 482 |         return 1; | 512 |                 foreach my $attr (@{ $entered_attributes }) { | 
            
              |  |  | 513 |                     push @changed_attributes, $attr | 
            
              | 514 |                         if $attr->{ code } eq $attribute_type; | 
            
              | 515 |                 } | 
            
              | 516 |             } | 
            
              | 517 |         } | 
        
          | 483 |     } | 518 |     } | 
        
          | 484 |  | 519 |  | 
          
            
              | 485 |     foreach my $attr ( @{$patron_attributes} ) { | 520 |     # Loop through passed attributes, looking for new ones | 
            
              | 486 |         next if any { | 521 |     foreach my $attribute_type ( keys %{ $passed_attribute_types } ) { | 
            
              | 487 |             $_->{code} eq $attr->{code} and $_->{value} eq $attr->{value}; | 522 |         if ( !defined $patron_attribute_types->{ $attribute_type } ) { | 
            
              |  |  | 523 |             # YAY, new stuff | 
            
              | 524 |             foreach my $attr (grep { $_->{code} eq $attribute_type } @{ $entered_attributes }) { | 
            
              | 525 |                 push @changed_attributes, $attr; | 
            
              | 526 |             } | 
        
          | 488 |         } | 527 |         } | 
            
              | 489 |         @{$entered_attributes}; |  |  | 
            
              | 490 |         return 1; | 
        
          | 491 |     } | 528 |     } | 
        
          | 492 |  | 529 |  | 
          
            
              | 493 |     return 0; | 530 |     return \@changed_attributes; | 
        
          | 494 | } | 531 | } | 
        
          | 495 |  | 532 |  | 
            
              | 496 |  |  |  | 
        
          | 497 | sub GeneratePatronAttributesForm { | 533 | sub GeneratePatronAttributesForm { | 
        
          | 498 |     my ( $borrowernumber, $entered_attributes ) = @_; | 534 |     my ( $borrowernumber, $entered_attributes ) = @_; | 
        
          | 499 |  | 535 |  | 
        
          | 500 |     # Get all attribute types and the values for this patron (if applicable) | 536 |     # Get all attribute types and the values for this patron (if applicable) | 
          
            
              | 501 |     my @types = C4::Members::AttributeTypes::GetAttributeTypes(); | 537 |     my @types = grep { $_->opac_editable() or $_->opac_display } | 
            
              | 502 |  | 538 |         Koha::Patron::Attribute::Types->search()->as_list(); | 
            
              | 503 |     if (scalar(@types) == 0) { | 539 |     if ( scalar(@types) == 0 ) { | 
        
          | 504 |         return []; | 540 |         return []; | 
        
          | 505 |     } | 541 |     } | 
        
          | 506 |  | 542 |  | 
          
            
              | 507 |     my %attr_values = (); | 543 |     my @displayable_attributes = grep { $_->opac_display } | 
            
              |  |  | 544 |         Koha::Patron::Attributes->search({ borrowernumber => $borrowernumber })->as_list; | 
        
          | 508 |  | 545 |  | 
          
            
              | 509 |     if ( $borrowernumber ) { | 546 |     my %attr_values = (); | 
            
              | 510 |         my $attributes = C4::Members::Attributes::GetBorrowerAttributes($borrowernumber); |  |  | 
        
          | 511 |  | 547 |  | 
          
            
              | 512 |         # Remap the patron's attributes into a hash of arrayrefs per attribute (depends on | 548 |     # Build the attribute values list either from the passed values | 
            
              | 513 |         # autovivification) | 549 |     # or taken from the patron itself | 
            
              | 514 |         foreach my $attr (@$attributes) { | 550 |     if ( defined $entered_attributes ) { | 
            
              | 515 |             push @{ $attr_values{ $attr->{code} } }, $attr; | 551 |         foreach my $attr (@$entered_attributes) { | 
            
              |  |  | 552 |             push @{ $attr_values{ $attr->{code} } }, $attr->{value}; | 
        
          | 516 |         } | 553 |         } | 
        
          | 517 |     } | 554 |     } | 
          
            
              | 518 |  | 555 |     elsif ( defined $borrowernumber ) { | 
            
              | 519 |     if ( $entered_attributes ) { | 556 |         my @editable_attributes = grep { $_->opac_editable } @displayable_attributes; | 
            
              | 520 |         foreach my $attr (@$entered_attributes) { | 557 |         foreach my $attr (@editable_attributes) { | 
            
              | 521 |             push @{ $attr_values{ $attr->{code} } }, $attr; | 558 |             push @{ $attr_values{ $attr->code } }, $attr->attribute; | 
        
          | 522 |         } | 559 |         } | 
        
          | 523 |     } | 560 |     } | 
        
          | 524 |  | 561 |  | 
            
              |  |  | 562 |     # Add the non-editable attributes (that don't come from the form) | 
            
              | 563 |     foreach my $attr ( grep { !$_->opac_editable } @displayable_attributes ) { | 
            
              | 564 |         push @{ $attr_values{ $attr->code } }, $attr->attribute; | 
            
              | 565 |     } | 
            
              | 566 |  | 
        
          | 525 |     # Find all existing classes | 567 |     # Find all existing classes | 
          
            
              | 526 |     my @classes = uniq( map { $_->{class} } @types ); | 568 |     my @classes = sort( uniq( map { $_->class } @types ) ); | 
            
              | 527 |     @classes = sort @classes; |  |  | 
        
          | 528 |     my %items_by_class; | 569 |     my %items_by_class; | 
        
          | 529 |  | 570 |  | 
          
            
              | 530 |     foreach my $attr_type_desc (@types) { | 571 |     foreach my $attr_type (@types) { | 
            
              | 531 |         my $attr_type = C4::Members::AttributeTypes->fetch( $attr_type_desc->{code} ); |  |  | 
            
              | 532 |         # Make sure this attribute should be displayed in the OPAC | 
            
              | 533 |         next unless ( $attr_type->opac_display() ); | 
            
              | 534 |         # Then, make sure it either has values or is editable | 
            
              | 535 |         next unless ( $attr_values{ $attr_type->code() } || $attr_type->opac_editable() ); | 
            
              | 536 |  | 
        
          | 537 |         push @{ $items_by_class{ $attr_type->class() } }, { | 572 |         push @{ $items_by_class{ $attr_type->class() } }, { | 
        
          | 538 |             type => $attr_type, | 573 |             type => $attr_type, | 
          
            
              | 539 |             # If editable, make sure there's at least one empty entry, to make the template's job easier | 574 |             # If editable, make sure there's at least one empty entry, | 
            
              | 540 |             values => $attr_values{ $attr_type->code() } || [{}] | 575 |             # to make the template's job easier | 
            
              |  |  | 576 |             values => $attr_values{ $attr_type->code() } || [''] | 
        
          | 541 |         }; | 577 |         }; | 
        
          | 542 |     } | 578 |     } | 
        
          | 543 |  | 579 |  | 
  
    | Lines 546-588
          sub GeneratePatronAttributesForm {
      
      
        Link Here | 
        
          | 546 |     foreach my $class (@classes) { | 582 |     foreach my $class (@classes) { | 
        
          | 547 |         next unless ( $items_by_class{$class} ); | 583 |         next unless ( $items_by_class{$class} ); | 
        
          | 548 |  | 584 |  | 
          
            
              | 549 |         my $av = Koha::AuthorisedValues->search({ category => 'PA_CLASS', authorised_value => $class }); | 585 |         my $av = Koha::AuthorisedValues->search( | 
            
              |  |  | 586 |             { category => 'PA_CLASS', authorised_value => $class } ); | 
            
              | 587 |  | 
        
          | 550 |         my $lib = $av->count ? $av->next->opac_description : $class; | 588 |         my $lib = $av->count ? $av->next->opac_description : $class; | 
        
          | 551 |  | 589 |  | 
          
            
              | 552 |         push @class_loop, { | 590 |         push @class_loop, | 
            
              |  |  | 591 |             { | 
        
          | 553 |             class => $class, | 592 |             class => $class, | 
        
          | 554 |             items => $items_by_class{$class}, | 593 |             items => $items_by_class{$class}, | 
        
          | 555 |             lib   => $lib, | 594 |             lib   => $lib, | 
          
            
              | 556 |         }; | 595 |             }; | 
        
          | 557 |     } | 596 |     } | 
        
          | 558 |  | 597 |  | 
        
          | 559 |     return \@class_loop; | 598 |     return \@class_loop; | 
        
          | 560 | } | 599 | } | 
        
          | 561 |  | 600 |  | 
        
          | 562 | sub ParsePatronAttributes { | 601 | sub ParsePatronAttributes { | 
          
            
              | 563 |     my ($borrowernumber,$cgi) = @_; | 602 |     my ( $borrowernumber, $cgi ) = @_; | 
        
          | 564 |  | 603 |  | 
        
          | 565 |     my @codes  = $cgi->multi_param('patron_attribute_code'); | 604 |     my @codes  = $cgi->multi_param('patron_attribute_code'); | 
        
          | 566 |     my @values = $cgi->multi_param('patron_attribute_value'); | 605 |     my @values = $cgi->multi_param('patron_attribute_value'); | 
        
          | 567 |  | 606 |  | 
        
          | 568 |     my $ea = each_array( @codes, @values ); | 607 |     my $ea = each_array( @codes, @values ); | 
        
          | 569 |     my @attributes; | 608 |     my @attributes; | 
          
            
              | 570 |     my %dups = (); | 609 |  | 
            
              |  |  | 610 |     my $delete_candidates = {}; | 
        
          | 571 |  | 611 |  | 
        
          | 572 |     while ( my ( $code, $value ) = $ea->() ) { | 612 |     while ( my ( $code, $value ) = $ea->() ) { | 
          
            
              | 573 |         # Don't skip if the patron already has attributes with $code, because | 613 |         if ( !defined($value) or $value eq '' ) { | 
            
              | 574 |         # it means we are being requested to remove the attributes. | 614 |             $delete_candidates->{$code} = 1 | 
            
              | 575 |         next | 615 |                 unless $delete_candidates->{$code}; | 
            
              | 576 |             unless defined($value) and $value ne '' | 616 |         } | 
            
              | 577 |             or Koha::Patron::Attributes->search( | 617 |         else { | 
            
              | 578 |             { borrowernumber => $borrowernumber, code => $code } )->count > 0; | 618 |             # we've got a value | 
            
              | 579 |         next if exists $dups{$code}->{$value}; | 619 |             push @attributes, { code => $code, value => $value }; | 
            
              | 580 |         $dups{$code}->{$value} = 1; | 620 |  | 
            
              |  |  | 621 |             # 'code' is no longer a delete candidate | 
            
              | 622 |             delete $delete_candidates->{$code}; | 
            
              | 623 |         } | 
            
              | 624 |     } | 
        
          | 581 |  | 625 |  | 
          
            
              | 582 |         push @attributes, { code => $code, value => $value }; | 626 |     foreach my $code ( keys %{$delete_candidates} ) { | 
            
              |  |  | 627 |         if (Koha::Patron::Attributes->search( | 
            
              | 628 |                 { borrowernumber => $borrowernumber, code => $code } | 
            
              | 629 |             )->count > 0 | 
            
              | 630 |             ) | 
            
              | 631 |         { | 
            
              | 632 |             push @attributes, { code => $code, value => '' } | 
            
              | 633 |                 unless any { $_->{code} eq $code } @attributes; | 
            
              | 634 |         } | 
        
          | 583 |     } | 635 |     } | 
        
          | 584 |  | 636 |  | 
        
          | 585 |     return \@attributes; | 637 |     return \@attributes; | 
        
          | 586 | } | 638 | } | 
        
          | 587 |  | 639 |  | 
            
              |  |  | 640 |  | 
        
          | 588 | 1; | 641 | 1; | 
            
              | 589 | -  |  |  |