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

(-)a/C4/Biblio.pm (-8 / +235 lines)
Lines 29-34 use MARC::File::USMARC; Link Here
29
use MARC::File::XML;
29
use MARC::File::XML;
30
use POSIX qw(strftime);
30
use POSIX qw(strftime);
31
use Module::Load::Conditional qw(can_load);
31
use Module::Load::Conditional qw(can_load);
32
use JSON qw(encode_json decode_json);
32
33
33
use C4::Koha;
34
use C4::Koha;
34
use C4::Dates qw/format_date/;
35
use C4::Dates qw/format_date/;
Lines 78-83 BEGIN { Link Here
78
      &GetMarcISSN
79
      &GetMarcISSN
79
      &GetMarcSubjects
80
      &GetMarcSubjects
80
      &GetMarcBiblio
81
      &GetMarcBiblio
82
      &GetMarcBiblioHistory
81
      &GetMarcAuthors
83
      &GetMarcAuthors
82
      &GetMarcSeries
84
      &GetMarcSeries
83
      &GetMarcHosts
85
      &GetMarcHosts
Lines 139-144 BEGIN { Link Here
139
      &TransformHtmlToXml
141
      &TransformHtmlToXml
140
      prepare_host_field
142
      prepare_host_field
141
    );
143
    );
144
145
    # History functions
146
    push @EXPORT, qw(
147
      &HistoryUpdate
148
      &HistoryParse
149
      &HistoryMapUsers
150
      &HistoryBorrowerDetails
151
      &HistoryRecordNew
152
    );
142
}
153
}
143
154
144
=head1 NAME
155
=head1 NAME
Lines 266-273 sub AddBiblio { Link Here
266
    # update MARC subfield that stores biblioitems.cn_sort
277
    # update MARC subfield that stores biblioitems.cn_sort
267
    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
278
    _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
268
279
269
    # now add the record
280
    # now add the record (history parameter is undef: no history yet])
270
    ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
281
    ModBiblioMarc( $record, undef, $biblionumber, $frameworkcode ) unless $defer_marc_save;
271
282
272
    # update OAI-PMH sets
283
    # update OAI-PMH sets
273
    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
284
    if(C4::Context->preference("OAI-PMH:AutoUpdateSets")) {
Lines 332-340 sub ModBiblio { Link Here
332
    # update biblionumber and biblioitemnumber in MARC
343
    # update biblionumber and biblioitemnumber in MARC
333
    # FIXME - this is assuming a 1 to 1 relationship between
344
    # FIXME - this is assuming a 1 to 1 relationship between
334
    # biblios and biblioitems
345
    # biblios and biblioitems
335
    my $sth = $dbh->prepare("select biblioitemnumber from biblioitems where biblionumber=?");
346
    my $sth = $dbh->prepare("select biblioitemnumber,history from biblioitems where biblionumber=?");
336
    $sth->execute($biblionumber);
347
    $sth->execute($biblionumber);
337
    my ($biblioitemnumber) = $sth->fetchrow;
348
    my ($biblioitemnumber, $history) = $sth->fetchrow;
338
    $sth->finish();
349
    $sth->finish();
339
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
350
    _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
340
351
Lines 345-351 sub ModBiblio { Link Here
345
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
356
    _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
346
357
347
    # update the MARC record (that now contains biblio and items) with the new record data
358
    # update the MARC record (that now contains biblio and items) with the new record data
348
    &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
359
    &ModBiblioMarc( $record, $history, $biblionumber, $frameworkcode );
349
360
350
    # modify the other koha tables
361
    # modify the other koha tables
351
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
362
    _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
Lines 1291-1296 sub GetMarcBiblio { Link Here
1291
    }
1302
    }
1292
}
1303
}
1293
1304
1305
=head2 GetMarcBiblioHistory
1306
1307
  my ($record, $history) = GetMarcBiblio($biblionumber, [$embeditems]);
1308
1309
Returns MARC::Record representing bib identified by
1310
C<$biblionumber>.  If no bib exists, returns undef.
1311
C<$embeditems>.  If set to true, items data are included.
1312
The MARC record contains biblio data, and items data if $embeditems is set to true.
1313
1314
=cut
1315
1316
sub GetMarcBiblioHistory {
1317
    my $biblionumber = shift;
1318
    my $embeditems   = shift || 0;
1319
1320
    if (not defined $biblionumber) {
1321
        carp 'GetMarcBiblio called with undefined biblionumber';
1322
        return;
1323
    }
1324
1325
    my $dbh          = C4::Context->dbh;
1326
    my $sth          = $dbh->prepare("SELECT marcxml,history FROM biblioitems WHERE biblionumber=? ");
1327
    $sth->execute($biblionumber);
1328
    my $row     = $sth->fetchrow_hashref;
1329
    my $marcxml = StripNonXmlChars( $row->{'marcxml'} );
1330
    MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
1331
    my $record = MARC::Record->new();
1332
    my ($history, $borrowers) = HistoryParse( $row->{'history'} );
1333
    $borrowers = HistoryBorrowerDetails( $borrowers );
1334
    $history = HistoryMapUsers( $history, $borrowers );
1335
1336
    if ($marcxml) {
1337
        $record = eval { MARC::Record::new_from_xml( $marcxml, "utf8", C4::Context->preference('marcflavour') ) };
1338
1339
        if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
1340
        return unless $record;
1341
1342
        C4::Biblio::_koha_marc_update_bib_ids($record, '', $biblionumber, $biblionumber);
1343
        C4::Biblio::EmbedItemsInMarcBiblio($record, $biblionumber) if ($embeditems);
1344
        return ($record, $history);
1345
    } else {
1346
        return;
1347
    }
1348
}
1349
# Input format (JSON): [ { datetime, borrowernumber, marcxml } ]
1350
# Output format: { tag } { subfield } [ { borrowernumber, data, datetime } ]
1351
sub HistoryParse
1352
{
1353
    my $json = shift;
1354
1355
    if (!defined $json) {
1356
        carp('HistoryParse: Missing JSON parameter');
1357
        return undef;
1358
    }
1359
1360
    my $input = decode_json($json);
1361
1362
    # Reindex the dataset; order all values by datetime under each tag name.
1363
    my $borrowers = { };
1364
	my $output = { };
1365
    for my $item (@{$input}) {
1366
1367
        my $datetime = $item->{datetime};
1368
        $borrowers->{ $item->{borrowernumber }} = undef;
1369
        my $record = MARC::File::XML::decode($item->{marcxml});
1370
1371
        for my $field ($record->fields()) {
1372
            my $tag = $field->tag();
1373
			$output->{$tag} = {} unless defined $output->{$tag};
1374
1375
            for my $subfield ($field->subfields()) {
1376
1377
				# Build hash ref structure.
1378
                my $subfield_tag = @{$subfield}[0];
1379
                $output->{$tag}->{$subfield_tag} = []
1380
						unless defined $output->{$tag}->{$subfield_tag};
1381
1382
                push( @{ $output->{$tag}->{$subfield_tag} }, HistoryParsedRecordNew(
1383
                        @{ $subfield }[1], $item->{borrowernumber}, $datetime));
1384
            }
1385
        }
1386
    }
1387
    return ( $output, $borrowers );
1388
}
1389
1390
# Take a hashref with borrowernumbers as keys, returns new hash with the
1391
# borrowernumber as key to hash containing firstname, surname and title.
1392
sub HistoryBorrowerDetails
1393
{
1394
	my $borrowernumbers = shift;
1395
1396
	my $dbh = C4::Context->dbh;
1397
    my $output = { };
1398
    my @numbers = keys %{$borrowernumbers};
1399
    if (@numbers > 0) {
1400
	    # We now need to look up the numeric user id's and map to name and title strings.
1401
	    my $sth = $dbh->prepare(qq{SELECT
1402
	                            borrowernumber, title, firstname, surname
1403
	                            FROM borrowers
1404
	                            WHERE borrowernumber IN}
1405
	                            . '(' . join(',', ('?') x @numbers) . ')');
1406
	    $sth->execute(@numbers)
1407
				or die ('BorrowerDetails: Unable to execute SELECT statement');
1408
	    while (my $row = $sth->fetchrow_hashref()) {
1409
			$output->{ $row->{borrowernumber} } = { };
1410
	        $output->{ $row->{borrowernumber} }->{title}     = $row->{title};
1411
	        $output->{ $row->{borrowernumber} }->{firstname} = $row->{firstname};
1412
	        $output->{ $row->{borrowernumber} }->{surname}   = $row->{surname};
1413
	    }
1414
	    $sth->finish();
1415
    }
1416
1417
    return $output;
1418
}
1419
1420
sub HistoryMapUsers
1421
{
1422
	my ($history, $borrowers) = @_;
1423
1424
    my $mappedhistory = { };
1425
    for my $tag (keys %{ $history }) {
1426
		$mappedhistory->{ $tag } = { };
1427
		for my $field (keys %{ $history->{ $tag } }) {
1428
			$mappedhistory->{ $tag }->{ $field } = [ ];
1429
			for my $entry (@{ $history->{ $tag }->{ $field } }) {
1430
				# C4::Dates does not allow me to print time, only dates.
1431
				#my $newtime = C4::Dates->new( $history->{ $tag }->{ $field }->{ $time }->{timestamp}, 'iso' )->output('syspref');
1432
1433
				my $newitem = { };
1434
				my $user = $borrowers->{ $entry->{borrowernumber} };
1435
1436
				# Maybe change this to work on original instead of copy?
1437
				$newitem->{title}          = $user->{title};
1438
				$newitem->{firstname}      = $user->{firstname};
1439
				$newitem->{surname}        = $user->{surname};
1440
				$newitem->{borrowernumber} = $entry->{borrowernumber};
1441
				$newitem->{data}           = $entry->{data};
1442
				$newitem->{datetime}       = $entry->{datetime};
1443
1444
				push ( @{ $mappedhistory->{ $tag }->{ $field } } , $newitem);
1445
			}
1446
		}
1447
	}
1448
1449
    return $mappedhistory;
1450
}
1451
1452
sub HistoryParsedRecordNew
1453
{
1454
    my ($value, $borrowernumber, $datetime) = @_;
1455
    my $newrecord = {
1456
        data           => $value,
1457
        borrowernumber => $borrowernumber,
1458
        datetime       => $datetime,
1459
    };
1460
1461
    return $newrecord;
1462
}
1463
1294
=head2 GetXmlBiblio
1464
=head2 GetXmlBiblio
1295
1465
1296
  my $marcxml = GetXmlBiblio($biblionumber);
1466
  my $marcxml = GetXmlBiblio($biblionumber);
Lines 3361-3367 Function exported, but should NOT be used, unless you really know what you're do Link Here
3361
sub ModBiblioMarc {
3531
sub ModBiblioMarc {
3362
    # pass the MARC::Record to this function, and it will create the records in
3532
    # pass the MARC::Record to this function, and it will create the records in
3363
    # the marc field
3533
    # the marc field
3364
    my ( $record, $biblionumber, $frameworkcode ) = @_;
3534
    my ( $record, $history, $biblionumber, $frameworkcode ) = @_;
3365
    if ( !$record ) {
3535
    if ( !$record ) {
3366
        carp 'ModBiblioMarc passed an undefined record';
3536
        carp 'ModBiblioMarc passed an undefined record';
3367
        return;
3537
        return;
Lines 3407-3414 sub ModBiblioMarc { Link Here
3407
      $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3577
      $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3408
    }
3578
    }
3409
3579
3410
    $sth = $dbh->prepare("UPDATE biblioitems SET marc=?,marcxml=? WHERE biblionumber=?");
3580
    # Decode JSON history, create new record, update and re-encode to JSON.
3411
    $sth->execute( $record->as_usmarc(), $record->as_xml_record($encoding), $biblionumber );
3581
    my $historylength = 10;
3582
	eval {
3583
		$history = decode_json($history);
3584
		1;
3585
	} or do {
3586
		$history = undef;
3587
	};
3588
    my $newrecord = HistoryRecordNew($record);
3589
    my $newhistory = HistoryUpdate($newrecord, $historylength, $history);
3590
    $newhistory = encode_json($newhistory);
3591
3592
    $sth = $dbh->prepare("UPDATE biblioitems SET marc=?,marcxml=?,history=? WHERE biblionumber=?");
3593
    $sth->execute( $record->as_usmarc(), $record->as_xml_record($encoding), $newhistory, $biblionumber );
3412
    $sth->finish;
3594
    $sth->finish;
3413
    ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
3595
    ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
3414
    return $biblionumber;
3596
    return $biblionumber;
Lines 3765-3770 sub RemoveAllNsb { Link Here
3765
    return $record;
3947
    return $record;
3766
}
3948
}
3767
3949
3950
sub HistoryRecordNew
3951
{
3952
    my ( $record ) = @_;
3953
3954
    my $encoding = C4::Context->preference("marcflavour");
3955
    my $hash_record = {
3956
        borrowernumber => C4::Context->userenv()->{number},
3957
        datetime       => POSIX::strftime('%Y-%m-%d %T', localtime(time)),
3958
        marcxml        => $record->as_xml_record($encoding),
3959
    };
3960
3961
    return $hash_record;
3962
};
3963
3964
sub HistoryUpdate
3965
{
3966
    my ( $record, $limit, $collection ) = @_;
3967
3968
    if ( !$record ) {
3969
        carp('HistoryUpdate: Called with undefined record');
3970
        return;
3971
    }
3972
3973
    if ( ref($record) ne 'HASH' ) {
3974
        carp('HistoryUpdate: Record parameter is not a hashref');
3975
        return;
3976
    }
3977
3978
    $limit = 10 unless defined $limit;
3979
3980
    # We need something to append history to, even if there is none.
3981
    $collection = [] unless defined $collection;
3982
3983
    # Truncate history to $histsize - 1, need space for new entry.
3984
    splice(@{$collection}, $limit - 1);
3985
    push(@{$collection}, $record);
3986
3987
    # Sort the resulting array, newest entries first.
3988
    # Removed; used to be integer timestamps. Is now MySQL date strings.
3989
    #@{$collection} = sort { $a->{timestamp} < $b->{timestamp} } @{$collection};
3990
	@{$collection} = sort { $a->{datetime} lt $b->{datetime} } @{$collection};
3991
3992
    return $collection;
3993
}
3994
3768
1;
3995
1;
3769
3996
3770
3997
(-)a/cataloguing/addbiblio.pl (-13 / +29 lines)
Lines 41-46 use MARC::File::USMARC; Link Here
41
use MARC::File::XML;
41
use MARC::File::XML;
42
use URI::Escape;
42
use URI::Escape;
43
43
44
use Data::Dumper;
45
44
if ( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
46
if ( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
45
    MARC::File::XML->default_record_format('UNIMARC');
47
    MARC::File::XML->default_record_format('UNIMARC');
46
}
48
}
Lines 291-300 sub GetMandatoryFieldZ3950 { Link Here
291
=cut
293
=cut
292
294
293
sub create_input {
295
sub create_input {
294
    my ( $tag, $subfield, $value, $index_tag, $tabloop, $rec, $authorised_values_sth,$cgi ) = @_;
296
297
    my ( $tag, $subfield, $value, $index_tag, $tabloop, $rec, $authorised_values_sth,$cgi, $history ) = @_;
295
    
298
    
296
    my $index_subfield = CreateKey(); # create a specifique key for each subfield
299
    my $index_subfield = CreateKey(); # create a specifique key for each subfield
297
300
301
    my $taghistory = $history->{$tag}->{$subfield};
302
    if (defined $taghistory ) {
303
		warn "TAG HIST: $tag/$subfield", Dumper($taghistory);
304
	}
298
    $value =~ s/"/&quot;/g;
305
    $value =~ s/"/&quot;/g;
299
306
300
    # if there is no value provided but a default value in parameters, get it
307
    # if there is no value provided but a default value in parameters, get it
Lines 332-337 sub create_input { Link Here
332
        value          => $value,
339
        value          => $value,
333
        maxlength      => $tagslib->{$tag}->{$subfield}->{maxlength},
340
        maxlength      => $tagslib->{$tag}->{$subfield}->{maxlength},
334
        random         => CreateKey(),
341
        random         => CreateKey(),
342
        history        => $taghistory,
335
    );
343
    );
336
344
337
    if(exists $mandatory_z3950->{$tag.$subfield}){
345
    if(exists $mandatory_z3950->{$tag.$subfield}){
Lines 387-392 sub create_input { Link Here
387
            maxlength => $subfield_data{maxlength},
395
            maxlength => $subfield_data{maxlength},
388
            readonly  => ($is_readonly) ? 1 : 0,
396
            readonly  => ($is_readonly) ? 1 : 0,
389
            authtype  => $tagslib->{$tag}->{$subfield}->{authtypecode},
397
            authtype  => $tagslib->{$tag}->{$subfield}->{authtypecode},
398
            history   => $taghistory,
390
        };
399
        };
391
400
392
    # it's a plugin field
401
    # it's a plugin field
Lines 408-413 sub create_input { Link Here
408
                maxlength      => $subfield_data{maxlength},
417
                maxlength      => $subfield_data{maxlength},
409
                javascript     => $plugin->javascript,
418
                javascript     => $plugin->javascript,
410
                noclick        => $plugin->noclick,
419
                noclick        => $plugin->noclick,
420
                history        => $taghistory,
411
            };
421
            };
412
        } else {
422
        } else {
413
            warn $plugin->errstr;
423
            warn $plugin->errstr;
Lines 420-425 sub create_input { Link Here
420
                size      => 67,
430
                size      => 67,
421
                maxlength => $subfield_data{maxlength},
431
                maxlength => $subfield_data{maxlength},
422
                readonly  => 0,
432
                readonly  => 0,
433
                history   => $taghistory,
423
            };
434
            };
424
        }
435
        }
425
436
Lines 432-437 sub create_input { Link Here
432
            value     => $value,
443
            value     => $value,
433
            size      => 67,
444
            size      => 67,
434
            maxlength => $subfield_data{maxlength},
445
            maxlength => $subfield_data{maxlength},
446
            history   => $taghistory,
435
        };
447
        };
436
448
437
    }
449
    }
Lines 452-457 sub create_input { Link Here
452
                id        => $subfield_data{id},
464
                id        => $subfield_data{id},
453
                name      => $subfield_data{id},
465
                name      => $subfield_data{id},
454
                value     => $value,
466
                value     => $value,
467
                history   => $taghistory,
455
            };
468
            };
456
469
457
        }
470
        }
Lines 464-469 sub create_input { Link Here
464
                size      => 67,
477
                size      => 67,
465
                maxlength => $subfield_data{maxlength},
478
                maxlength => $subfield_data{maxlength},
466
                readonly  => 0,
479
                readonly  => 0,
480
                history   => $taghistory,
467
            };
481
            };
468
482
469
        }
483
        }
Lines 489-499 sub format_indicator { Link Here
489
}
503
}
490
504
491
sub build_tabs {
505
sub build_tabs {
492
    my ( $template, $record, $dbh, $encoding,$input ) = @_;
506
    my ( $template, $record, $dbh, $encoding, $input, $history ) = @_;
493
507
494
    # fill arrays
508
    # fill arrays
495
    my @loop_data = ();
509
    my @loop_data = ();
496
    my $tag;
510
    #my $tag;
497
511
498
    my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
512
    my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
499
    my $query = "SELECT authorised_value, lib
513
    my $query = "SELECT authorised_value, lib
Lines 509-515 sub build_tabs { Link Here
509
    my @BIG_LOOP;
523
    my @BIG_LOOP;
510
    my %seen;
524
    my %seen;
511
    my @tab_data; # all tags to display
525
    my @tab_data; # all tags to display
512
    
526
    #print STDERR "usedTagsLibs: ", Dumper(@$usedTagsLib);
513
    foreach my $used ( @$usedTagsLib ){
527
    foreach my $used ( @$usedTagsLib ){
514
        push @tab_data,$used->{tagfield} if not $seen{$used->{tagfield}};
528
        push @tab_data,$used->{tagfield} if not $seen{$used->{tagfield}};
515
        $seen{$used->{tagfield}}++;
529
        $seen{$used->{tagfield}}++;
Lines 546-552 sub build_tabs { Link Here
546
		}
560
		}
547
		# loop through each field
561
		# loop through each field
548
                foreach my $field (@fields) {
562
                foreach my $field (@fields) {
549
                    
550
                    my @subfields_data;
563
                    my @subfields_data;
551
                    if ( $tag < 10 ) {
564
                    if ( $tag < 10 ) {
552
                        my ( $value, $subfield );
565
                        my ( $value, $subfield );
Lines 566-572 sub build_tabs { Link Here
566
                            @subfields_data,
579
                            @subfields_data,
567
                            &create_input(
580
                            &create_input(
568
                                $tag, $subfield, $value, $index_tag, $tabloop, $record,
581
                                $tag, $subfield, $value, $index_tag, $tabloop, $record,
569
                                $authorised_values_sth,$input
582
                                $authorised_values_sth,$input, $history
570
                            )
583
                            )
571
                        );
584
                        );
572
                    }
585
                    }
Lines 581-587 sub build_tabs { Link Here
581
                                @subfields_data,
594
                                @subfields_data,
582
                                &create_input(
595
                                &create_input(
583
                                    $tag, $subfield, $value, $index_tag, $tabloop,
596
                                    $tag, $subfield, $value, $index_tag, $tabloop,
584
                                    $record, $authorised_values_sth,$input
597
                                    $record, $authorised_values_sth,$input, $history
585
                                )
598
                                )
586
                            );
599
                            );
587
                        }
600
                        }
Lines 609-615 sub build_tabs { Link Here
609
                            @subfields_data,
622
                            @subfields_data,
610
                            &create_input(
623
                            &create_input(
611
                                $tag, $subfield, '', $index_tag, $tabloop, $record,
624
                                $tag, $subfield, '', $index_tag, $tabloop, $record,
612
                                $authorised_values_sth,$input
625
                                $authorised_values_sth,$input, $history
613
                            )
626
                            )
614
                        );
627
                        );
615
                    }
628
                    }
Lines 655-665 sub build_tabs { Link Here
655
                           # always include in the form regardless of the hidden setting - bug 2206
668
                           # always include in the form regardless of the hidden setting - bug 2206
656
                    next
669
                    next
657
                      if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
670
                      if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
671
658
			push(
672
			push(
659
                        @subfields_data,
673
                        @subfields_data,
660
                        &create_input(
674
                        &create_input(
661
                            $tag, $subfield, '', $index_tag, $tabloop, $record,
675
                            $tag, $subfield, '', $index_tag, $tabloop, $record,
662
                            $authorised_values_sth,$input
676
                            $authorised_values_sth,$input, $history
663
                        )
677
                        )
664
                    );
678
                    );
665
                }
679
                }
Lines 676-682 sub build_tabs { Link Here
676
                        tagfirstsubfield => $subfields_data[0],
690
                        tagfirstsubfield => $subfields_data[0],
677
                        fixedfield       => $tag < 10?1:0,
691
                        fixedfield       => $tag < 10?1:0,
678
                    );
692
                    );
679
                    
693
680
                    push @loop_data, \%tag_data ;
694
                    push @loop_data, \%tag_data ;
681
                }
695
                }
682
            }
696
            }
Lines 772-777 $mandatory_z3950 = GetMandatoryFieldZ3950($frameworkcode); Link Here
772
# -- Global
786
# -- Global
773
787
774
my $record   = -1;
788
my $record   = -1;
789
my $history  = undef;
775
my $encoding = "";
790
my $encoding = "";
776
my (
791
my (
777
	$biblionumbertagfield,
792
	$biblionumbertagfield,
Lines 783-789 my ( Link Here
783
);
798
);
784
799
785
if (($biblionumber) && !($breedingid)){
800
if (($biblionumber) && !($breedingid)){
786
	$record = GetMarcBiblio($biblionumber);
801
	($record, $history) = GetMarcBiblioHistory($biblionumber);
787
}
802
}
788
if ($breedingid) {
803
if ($breedingid) {
789
    ( $record, $encoding ) = MARCfindbreeding( $breedingid ) ;
804
    ( $record, $encoding ) = MARCfindbreeding( $breedingid ) ;
Lines 917-923 if ( $op eq "addbiblio" ) { Link Here
917
        }
932
        }
918
    } else {
933
    } else {
919
    # it may be a duplicate, warn the user and do nothing
934
    # it may be a duplicate, warn the user and do nothing
920
        build_tabs ($template, $record, $dbh,$encoding,$input);
935
        build_tabs ($template, $record, $dbh,$encoding, $input, $history);
921
        $template->param(
936
        $template->param(
922
            biblionumber             => $biblionumber,
937
            biblionumber             => $biblionumber,
923
            biblioitemnumber         => $biblioitemnumber,
938
            biblioitemnumber         => $biblioitemnumber,
Lines 964-970 elsif ( $op eq "delete" ) { Link Here
964
            $record = $urecord;
979
            $record = $urecord;
965
        };
980
        };
966
    }
981
    }
967
    build_tabs( $template, $record, $dbh, $encoding,$input );
982
983
    build_tabs( $template, $record, $dbh, $encoding, $input, $history );
968
    $template->param(
984
    $template->param(
969
        biblionumber             => $biblionumber,
985
        biblionumber             => $biblionumber,
970
        biblionumbertagfield        => $biblionumbertagfield,
986
        biblionumbertagfield        => $biblionumbertagfield,
(-)a/installer/data/mysql/atomicupdate/add_history.sql (+1 lines)
Line 0 Link Here
1
ALTER TABLE biblioitems ADD COLUMN `history` LONGTEXT DEFAULT NULL AFTER `marcxml`;
(-)a/koha-tmpl/intranet-tmpl/prog/en/css/addbiblio.css (-1 / +21 lines)
Lines 16-21 Link Here
16
	text-decoration : none;
16
	text-decoration : none;
17
}
17
}
18
18
19
.buttonHistory {
20
	font-weight : bold;
21
	text-decoration : none;
22
}
23
19
a.expandfield {
24
a.expandfield {
20
	text-decoration : none;
25
	text-decoration : none;
21
}
26
}
Lines 127-132 a.tagnum { Link Here
127
.linktools a:first-child { border-bottom: 1px solid #DDD; }
132
.linktools a:first-child { border-bottom: 1px solid #DDD; }
128
.linktools a:hover { background-color: #FFC; }
133
.linktools a:hover { background-color: #FFC; }
129
.subfield_controls { margin : 0 .5em; }
134
.subfield_controls { margin : 0 .5em; }
135
.subfield_history { margin : 0 .5em; float: right; }
130
.readonly { border-width : 1px; border-style: inset; padding-left : 15px; background: #EEE url(../../img/locked.png) center left no-repeat; width:29em; }
136
.readonly { border-width : 1px; border-style: inset; padding-left : 15px; background: #EEE url(../../img/locked.png) center left no-repeat; width:29em; }
131
137
132
#cataloguing_additem_itemlist {
138
#cataloguing_additem_itemlist {
Lines 170-173 tr.active.highlight td { Link Here
170
    position: absolute;
176
    position: absolute;
171
    top: 50%;
177
    top: 50%;
172
    width: 15em;
178
    width: 15em;
173
}
179
}
180
181
.history_container {
182
	display: none;
183
}
184
185
.history_table {
186
	width: 100%;
187
	display: none;
188
	float: right;
189
	clear: both;
190
	font-size: 75%;
191
	width: 75%;
192
	margin: 0.3em;
193
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/js/cataloging.js (+10 lines)
Lines 55-60 function openAuth(tagsubfieldid,authtype,source) { Link Here
55
    newin=window.open("../authorities/auth_finder.pl?source="+source+"&authtypecode="+authtype+"&index="+tagsubfieldid+"&value_mainstr="+encodeURI(mainmainstring)+"&value_main="+encodeURI(mainstring), "_blank",'width=700,height=550,toolbar=false,scrollbars=yes');
55
    newin=window.open("../authorities/auth_finder.pl?source="+source+"&authtypecode="+authtype+"&index="+tagsubfieldid+"&value_mainstr="+encodeURI(mainmainstring)+"&value_main="+encodeURI(mainstring), "_blank",'width=700,height=550,toolbar=false,scrollbars=yes');
56
}
56
}
57
57
58
function HistoryToggle (tagid)
59
{
60
	$('#' + tagid).toggle();
61
}
62
63
function HistoryRollback (tagid, value)
64
{
65
	$('#' + tagid).val(value);
66
}
67
58
function ExpandField(index) {
68
function ExpandField(index) {
59
    var original = document.getElementById(index); //original <div>
69
    var original = document.getElementById(index); //original <div>
60
    var divs = original.getElementsByTagName('div');
70
    var divs = original.getElementsByTagName('div');
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/addbiblio.tt (-4 / +25 lines)
Lines 620-629 function Changefwk(FwkList) { Link Here
620
                    [% IF ( mv.readonly == 1 ) %]
620
                    [% IF ( mv.readonly == 1 ) %]
621
                    <input type="text" id="[%- mv.id -%]" name="[%- mv.name -%]" value="[%- mv.value -%]" class="input_marceditor readonly" tabindex="1" size="[%- mv.size -%]" maxlength="[%- mv.maxlength -%]" readonly="readonly" />
621
                    <input type="text" id="[%- mv.id -%]" name="[%- mv.name -%]" value="[%- mv.value -%]" class="input_marceditor readonly" tabindex="1" size="[%- mv.size -%]" maxlength="[%- mv.maxlength -%]" readonly="readonly" />
622
                    [% ELSE %]
622
                    [% ELSE %]
623
                    <input type="text" id="[%- mv.id -%]" name="[%- mv.name -%]" value="[%- mv.value -%]" class="input_marceditor" tabindex="1" size="[%- mv.size -%]" maxlength="[%- mv.maxlength -%]" />
623
                    <input type="text" id="[%- mv.id -%]" name="[%- mv.name -%]" value="[%- mv.value -%]" class="input_marceditor" tabindex="1" size="[%- mv.size -%]" maxlength="[%- mv.maxlength -%]" onclick="historyToggle('[%- mv.id -%]_history'); return false;"/>
624
                    [% END %]
624
                    [% END %]
625
625
                    [% IF ( mv.authtype ) %]
626
                    [% IF ( mv.authtype ) %]
626
                    <span class="subfield_controls"><a href="#" class="buttonDot tag_editor" onclick="openAuth(this.parentNode.parentNode.getElementsByTagName('input')[1].id,'[%- mv.authtype -%]','biblio'); return false;" tabindex="1" title="Tag editor">Tag editor</a></span>
627
628
					<a href="#" class="buttonDot tag_editor" onclick="openAuth(this.parentNode.parentNode.getElementsByTagName('input')[1].id,'[%- mv.authtype -%]','biblio'); return false;" tabindex="1" title="Tag editor">Tag editor</a>
629
                    </span>
627
                    [% END %]
630
                    [% END %]
628
                [% ELSIF ( mv.type == 'text_complex' ) %]
631
                [% ELSIF ( mv.type == 'text_complex' ) %]
629
                    <input type="text" id="[%- mv.id -%]" name="[%- mv.name -%]" value="[%- mv.value -%]" class="input_marceditor framework_plugin" tabindex="1" size="[%- mv.size -%]" maxlength="[%- mv.maxlength -%]" />
632
                    <input type="text" id="[%- mv.id -%]" name="[%- mv.name -%]" value="[%- mv.value -%]" class="input_marceditor framework_plugin" tabindex="1" size="[%- mv.size -%]" maxlength="[%- mv.maxlength -%]" />
Lines 631-637 function Changefwk(FwkList) { Link Here
631
                        [% IF mv.noclick %]
634
                        [% IF mv.noclick %]
632
                            <a href="#" class="buttonDot tag_editor disabled" tabindex="-1" title="No popup"></a>
635
                            <a href="#" class="buttonDot tag_editor disabled" tabindex="-1" title="No popup"></a>
633
                        [% ELSE %]
636
                        [% ELSE %]
634
                            <a href="#" id="buttonDot_[% mv.id %]" class="buttonDot tag_editor framework_plugin" tabindex="1" title="Tag editor">Tag editor</a>
637
638
                            <a href="#" id="buttonDot_[% mv.id %]" class="buttonDot tag_editor framework_plugin" tabindex="2" title="Tag editor">Tag editor</a>
635
                        [% END %]
639
                        [% END %]
636
                    </span>
640
                    </span>
637
                    [% mv.javascript %]
641
                    [% mv.javascript %]
Lines 661-668 function Changefwk(FwkList) { Link Here
661
                    </a>
665
                    </a>
662
                [% END %]
666
                [% END %]
663
                </span>
667
                </span>
664
                
668
669
                [% IF ( subfield_loo.history ) %]
670
                <span class="subfield_history">
671
                    <a href="#" class="buttonHistory" onclick="HistoryToggle('[% mv.id %]_history'); return false;">
672
                        <img src="[% interface %]/[% theme %]/img/icon-history.png" alt="History" title="Display history for this field" />
673
                    </a>
674
                </span>
675
676
                <table class="history_table" id="[%- mv.id -%]_history" name="[%- mv.name -%]_history">
677
                    <tr><th>Value</th><th>Date</th><th>User</th></tr>
678
                    [% FOREACH hist IN subfield_loo.history %]
679
						<tr><td><a href="#" onclick="HistoryRollback('[% mv.id %]', '[% hist.data %]'); return false;">[% hist.data %]</a></td>
680
                        <td>[% hist.datetime %]</td>
681
                        <td>[% hist.title %] [% hist.firstname %] [% hist.surname %]</td></tr>
682
                    [% END %]
683
                </table>
684
                [% END %]
665
            </div>
685
            </div>
686
666
            <!-- End of the line -->
687
            <!-- End of the line -->
667
        [% END %]
688
        [% END %]
668
689
(-)a/t/db_dependent/BiblioHistory.t (-1 / +254 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
use Modern::Perl;
4
use t::lib::TestBuilder;
5
use t::lib::Mocks;
6
use Test::More tests => 6;
7
use POSIX qw(strftime);
8
9
use MARC::Record;
10
use MARC::Field;
11
use JSON qw(encode_json);
12
13
BEGIN {
14
    use_ok('C4::Biblio');
15
}
16
17
our $dbh = C4::Context->dbh;
18
$dbh->{AutoCommit} = 0;
19
$dbh->{RaiseError} = 0;
20
21
$dbh->do(q|DELETE FROM issues|);
22
$dbh->do(q|DELETE FROM items|);
23
$dbh->do(q|DELETE FROM borrowers|);
24
$dbh->do(q|DELETE FROM branches|);
25
$dbh->do(q|DELETE FROM categories|);
26
$dbh->do(q|DELETE FROM biblioitems|);
27
28
my $builder = t::lib::TestBuilder->new();
29
30
my $branch = $builder->build(
31
    {
32
        source => 'Branch',
33
    }
34
);
35
36
my $category = $builder->build(
37
    {
38
        source => 'Category',
39
    }
40
);
41
42
my $patron1 = $builder->build(
43
    {
44
        source => 'Borrower',
45
        value  => {
46
            title        => 'Mr/Mrs',
47
            firstname    => 'First name',
48
            surname      => 'Surname',
49
            categorycode => $category->{categorycode},
50
            branchcode   => $branch->{branchcode},
51
        },
52
    }
53
);
54
55
my $patron2 = $builder->build(
56
    {
57
        source => 'Borrower',
58
        value  => {
59
            title        => 'Mr/Mrs 2',
60
            firstname    => 'First name 2',
61
            surname      => 'Surname 2',
62
            categorycode => $category->{categorycode},
63
            branchcode   => $branch->{branchcode},
64
        },
65
    }
66
);
67
68
my $biblio = $builder->build(
69
    {
70
        source => 'Biblio',
71
        value  => {
72
            branchcode => $branch->{branchcode},
73
        },
74
    }
75
);
76
77
C4::Context->_new_userenv('DUMMY_SESSION_ID');
78
C4::Context->set_userenv( $patron1->{borrowernumber},
79
    $patron1->{userid}, 'usercnum', $patron1->{firstname}, $patron1->{surname},
80
    $branch->{branchcode}, 'My library', 0 );
81
82
# Item with no pre-existing history backlog.
83
my $record1 = MARC::Record->new();
84
my $field1  = MARC::Field->new(
85
    245, '1', '0',
86
    'a' => 'First Title',
87
    'c' => 'Some dude'
88
);
89
$record1->append_fields($field1);
90
91
my $record2 = MARC::Record->new();
92
my $field2  = MARC::Field->new(
93
    245, '1', '0',
94
    'a' => 'Second Title',
95
    'c' => 'Former dude'
96
);
97
$record2->append_fields($field2);
98
99
my $biblioitem1 = $builder->build(
100
    {
101
        source => 'Biblioitem',
102
        value  => {
103
            marcxml => $record1->as_xml(),
104
            history => undef,
105
        }
106
    }
107
);
108
109
# items{tagname}{subfield}{timestamp}{borrowernumber|timestamp|marcxml}
110
my $items = [
111
    {
112
        borrowernumber => $patron1->{borrowernumber},
113
        datetime       => '2012-08-12 12:00:10',
114
        marcxml        => $record1->as_xml(),
115
    },
116
    {
117
        borrowernumber => $patron2->{borrowernumber},
118
        datetime       => '2012-08-12 12:00:10',
119
        marcxml        => $record2->as_xml(),
120
    }
121
];
122
123
# Item with previous history entries.
124
my $biblioitem2 = $builder->build(
125
    {
126
        source => 'Biblioitem',
127
        value  => {
128
            marcxml => $record2->as_xml(),
129
            history => encode_json($items),
130
        }
131
    }
132
);
133
134
my $expected_history = {
135
    245 => {
136
        a => [
137
            {
138
                borrowernumber => $patron1->{borrowernumber},
139
                data           => 'First Title',
140
                datetime       => '2012-08-12 12:00:10'
141
            },
142
            {
143
                borrowernumber => $patron2->{borrowernumber},
144
                data           => 'Second Title',
145
                datetime       => '2012-08-12 12:00:10'
146
            },
147
        ],
148
        c => [
149
            {
150
                borrowernumber => $patron1->{borrowernumber},
151
                data           => 'Some dude',
152
                datetime       => '2012-08-12 12:00:10'
153
            },
154
            {
155
                borrowernumber => $patron2->{borrowernumber},
156
                data           => 'Former dude',
157
                datetime       => '2012-08-12 12:00:10'
158
            },
159
        ],
160
    },
161
};
162
163
my $expected_users = {
164
    $patron1->{borrowernumber} => undef,
165
    $patron2->{borrowernumber} => undef,
166
};
167
168
# Tests the decoding and reindexing of the data, without any borrower
169
# information implanted into the history itself.
170
171
my ( $history, $users ) = C4::Biblio::HistoryParse( encode_json($items) );
172
173
is_deeply( $history, $expected_history, 'HistoryParse: Output' );
174
is_deeply( $users,   $expected_users,   'HistoryParse: Users' );
175
176
# Tests the extraction of full borrower details based on the borrowernumber.
177
my $expected_details = {
178
    $patron1->{borrowernumber} => {
179
        firstname => $patron1->{firstname},
180
        surname   => $patron1->{surname},
181
        title     => $patron1->{title},
182
    },
183
    $patron2->{borrowernumber} => {
184
        firstname => $patron2->{firstname},
185
        surname   => $patron2->{surname},
186
        title     => $patron2->{title},
187
    }
188
};
189
190
my $details = HistoryBorrowerDetails($expected_users);
191
is_deeply( $details, $expected_details, 'HistoryBorrowerDetails: Output' );
192
193
{
194
    my $expected_mapped_history = {
195
        245 => {
196
            a => [
197
                {
198
                    borrowernumber => $patron1->{borrowernumber},
199
                    data           => 'First Title',
200
                    firstname      => $patron1->{firstname},
201
                    surname        => $patron1->{surname},
202
                    title          => $patron1->{title},
203
                    datetime       => '2012-08-12 12:00:10'
204
                },
205
                {
206
                    borrowernumber => $patron2->{borrowernumber},
207
                    data           => 'Second Title',
208
                    firstname      => $patron2->{firstname},
209
                    surname        => $patron2->{surname},
210
                    title          => $patron2->{title},
211
                    datetime       => '2012-08-12 12:00:10'
212
                },
213
            ],
214
            c => [
215
                {
216
                    borrowernumber => $patron1->{borrowernumber},
217
                    data           => 'Some dude',
218
                    firstname      => $patron1->{firstname},
219
                    surname        => $patron1->{surname},
220
                    title          => $patron1->{title},
221
                    datetime       => '2012-08-12 12:00:10'
222
                },
223
                {
224
                    borrowernumber => $patron2->{borrowernumber},
225
                    data           => 'Former dude',
226
                    firstname      => $patron2->{firstname},
227
                    surname        => $patron2->{surname},
228
                    title          => $patron2->{title},
229
                    datetime       => '2012-08-12 12:00:10'
230
                },
231
            ]
232
        }
233
    };
234
235
    $history = C4::Biblio::HistoryMapUsers( $expected_history, $details );
236
237
    is_deeply( $history, $expected_mapped_history, 'HistoryMapUsers: Output' );
238
239
  #my $record;
240
  #( $record, $history ) = GetMarcBiblioHistory( $biblioitem2->{biblionumber} );
241
242
    #is_deeply( $record, $record2 );
243
    #is_deeply( $history, $expected_mapped_history );
244
}
245
246
# The C4::Context will provide the expected user borrowernumber.
247
my $record = HistoryRecordNew($record1);
248
249
my $expected_record = {
250
    borrowernumber => C4::Context->userenv()->{number},
251
    marcxml        => $record1->as_xml_record(),
252
    datetime       => POSIX::strftime( '%Y-%m-%d %T', localtime ),
253
};
254
is_deeply( $record, $expected_record );

Return to bug 14367