3 # Copyright 2000-2002 Katipo Communications
4 # Copyright 2010 BibLibre
5 # Copyright 2011 Equinox Software, Inc.
7 # This file is part of Koha.
9 # Koha is free software; you can redistribute it and/or modify it under the
10 # terms of the GNU General Public License as published by the Free Software
11 # Foundation; either version 2 of the License, or (at your option) any later
14 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License along
19 # with Koha; if not, write to the Free Software Foundation, Inc.,
20 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 use MARC::File::USMARC;
30 use POSIX qw(strftime);
33 use C4::Dates qw/format_date/;
34 use C4::Log; # logaction
41 use vars qw($VERSION @ISA @EXPORT);
47 @ISA = qw( Exporter );
62 &GetBiblioItemByBiblioNumber
63 &GetBiblioFromItemNumber
64 &GetBiblionumberFromItemnumber
87 &GetAuthorisedValueDesc
103 # To delete something
108 # To link headings in a bib record
109 # to authority records.
111 &LinkBibHeadingsToAuthorities
115 # those functions are exported but should not be used
116 # they are usefull is few circumstances, so are exported.
117 # but don't use them unless you're a core developer ;-)
125 &TransformHtmlToMarc2
128 &PrepareItemrecordDisplay
134 my $servers = C4::Context->config('memcached_servers');
136 require Memoize::Memcached;
137 import Memoize::Memcached qw(memoize_memcached);
140 servers => [$servers],
141 key_prefix => C4::Context->config('memcached_namespace') || 'koha',
143 memoize_memcached( 'GetMarcStructure', memcached => $memcached, expire_time => 600 ); #cache for 10 minutes
149 C4::Biblio - cataloging management functions
153 Biblio.pm contains functions for managing storage and editing of bibliographic data within Koha. Most of the functions in this module are used for cataloging records: adding, editing, or removing biblios, biblioitems, or items. Koha's stores bibliographic information in three places:
157 =item 1. in the biblio,biblioitems,items, etc tables, which are limited to a one-to-one mapping to underlying MARC data
159 =item 2. as raw MARC in the Zebra index and storage engine
161 =item 3. as raw MARC the biblioitems.marc and biblioitems.marcxml
165 In the 3.0 version of Koha, the authoritative record-level information is in biblioitems.marcxml
167 Because the data isn't completely normalized there's a chance for information to get out of sync. The design choice to go with a un-normalized schema was driven by performance and stability concerns. However, if this occur, it can be considered as a bug : The API is (or should be) complete & the only entry point for all biblio/items managements.
171 =item 1. Compared with MySQL, Zebra is slow to update an index for small data changes -- especially for proc-intensive operations like circulation
173 =item 2. Zebra's index has been known to crash and a backup of the data is necessary to rebuild it in such cases
177 Because of this design choice, the process of managing storage and editing is a bit convoluted. Historically, Biblio.pm's grown to an unmanagable size and as a result we have several types of functions currently:
181 =item 1. Add*/Mod*/Del*/ - high-level external functions suitable for being called from external scripts to manage the collection
183 =item 2. _koha_* - low-level internal functions for managing the koha tables
185 =item 3. Marc management function : as the MARC record is stored in biblioitems.marc(xml), some subs dedicated to it's management are in this package. They should be used only internally by Biblio.pm, the only official entry points being AddBiblio, AddItem, ModBiblio, ModItem.
187 =item 4. Zebra functions used to update the Zebra index
189 =item 5. internal helper functions such as char_decode, checkitems, etc. Some of these probably belong in Koha.pm
193 The MARC record (in biblioitems.marcxml) contains the complete marc record, including items. It also contains the biblionumber. That is the reason why it is not stored directly by AddBiblio, with all other fields . To save a biblio, we need to :
197 =item 1. save datas in biblio and biblioitems table, that gives us a biblionumber and a biblioitemnumber
199 =item 2. add the biblionumber and biblioitemnumber into the MARC records
201 =item 3. save the marc record
205 When dealing with items, we must :
209 =item 1. save the item in items table, that gives us an itemnumber
211 =item 2. add the itemnumber to the item MARC field
213 =item 3. overwrite the MARC record (with the added item) into biblioitems.marc(xml)
215 When modifying a biblio or an item, the behaviour is quite similar.
219 =head1 EXPORTED FUNCTIONS
223 ($biblionumber,$biblioitemnumber) = AddBiblio($record,$frameworkcode);
225 Exported function (core API) for adding a new biblio to koha.
227 The first argument is a C<MARC::Record> object containing the
228 bib to add, while the second argument is the desired MARC
231 This function also accepts a third, optional argument: a hashref
232 to additional options. The only defined option is C<defer_marc_save>,
233 which if present and mapped to a true value, causes C<AddBiblio>
234 to omit the call to save the MARC in C<bibilioitems.marc>
235 and C<biblioitems.marcxml> This option is provided B<only>
236 for the use of scripts such as C<bulkmarcimport.pl> that may need
237 to do some manipulation of the MARC record for item parsing before
238 saving it and which cannot afford the performance hit of saving
239 the MARC record twice. Consequently, do not use that option
240 unless you can guarantee that C<ModBiblioMarc> will be called.
246 my $frameworkcode = shift;
247 my $options = @_ ? shift : undef;
248 my $defer_marc_save = 0;
249 if ( defined $options and exists $options->{'defer_marc_save'} and $options->{'defer_marc_save'} ) {
250 $defer_marc_save = 1;
253 my ( $biblionumber, $biblioitemnumber, $error );
254 my $dbh = C4::Context->dbh;
256 # transform the data into koha-table style data
257 SetUTF8Flag($record);
258 my $olddata = TransformMarcToKoha( $dbh, $record, $frameworkcode );
259 ( $biblionumber, $error ) = _koha_add_biblio( $dbh, $olddata, $frameworkcode );
260 $olddata->{'biblionumber'} = $biblionumber;
261 ( $biblioitemnumber, $error ) = _koha_add_biblioitem( $dbh, $olddata );
263 _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
265 # update MARC subfield that stores biblioitems.cn_sort
266 _koha_marc_update_biblioitem_cn_sort( $record, $olddata, $frameworkcode );
269 ModBiblioMarc( $record, $biblionumber, $frameworkcode ) unless $defer_marc_save;
271 logaction( "CATALOGUING", "ADD", $biblionumber, "biblio" ) if C4::Context->preference("CataloguingLog");
272 return ( $biblionumber, $biblioitemnumber );
277 ModBiblio( $record,$biblionumber,$frameworkcode);
279 Replace an existing bib record identified by C<$biblionumber>
280 with one supplied by the MARC::Record object C<$record>. The embedded
281 item, biblioitem, and biblionumber fields from the previous
282 version of the bib record replace any such fields of those tags that
283 are present in C<$record>. Consequently, ModBiblio() is not
284 to be used to try to modify item records.
286 C<$frameworkcode> specifies the MARC framework to use
287 when storing the modified bib record; among other things,
288 this controls how MARC fields get mapped to display columns
289 in the C<biblio> and C<biblioitems> tables, as well as
290 which fields are used to store embedded item, biblioitem,
291 and biblionumber data for indexing.
296 my ( $record, $biblionumber, $frameworkcode ) = @_;
297 if ( C4::Context->preference("CataloguingLog") ) {
298 my $newrecord = GetMarcBiblio($biblionumber);
299 logaction( "CATALOGUING", "MODIFY", $biblionumber, "BEFORE=>" . $newrecord->as_formatted );
302 SetUTF8Flag($record);
303 my $dbh = C4::Context->dbh;
305 $frameworkcode = "" unless $frameworkcode;
307 _strip_item_fields($record, $frameworkcode);
309 foreach my $field ($record->fields()) {
310 if (! $field->is_control_field()) {
311 if (scalar($field->subfields()) == 0) {
312 $record->delete_fields($field);
317 # update biblionumber and biblioitemnumber in MARC
318 # FIXME - this is assuming a 1 to 1 relationship between
319 # biblios and biblioitems
320 my $sth = $dbh->prepare("select biblioitemnumber from biblioitems where biblionumber=?");
321 $sth->execute($biblionumber);
322 my ($biblioitemnumber) = $sth->fetchrow;
324 _koha_marc_update_bib_ids( $record, $frameworkcode, $biblionumber, $biblioitemnumber );
326 # load the koha-table data object
327 my $oldbiblio = TransformMarcToKoha( $dbh, $record, $frameworkcode );
329 # update MARC subfield that stores biblioitems.cn_sort
330 _koha_marc_update_biblioitem_cn_sort( $record, $oldbiblio, $frameworkcode );
332 # update the MARC record (that now contains biblio and items) with the new record data
333 &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
335 # modify the other koha tables
336 _koha_modify_biblio( $dbh, $oldbiblio, $frameworkcode );
337 _koha_modify_biblioitem_nonmarc( $dbh, $oldbiblio );
341 =head2 _strip_item_fields
343 _strip_item_fields($record, $frameworkcode)
345 Utility routine to remove item tags from a
350 sub _strip_item_fields {
352 my $frameworkcode = shift;
353 # get the items before and append them to the biblio before updating the record, atm we just have the biblio
354 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
356 # delete any item fields from incoming record to avoid
357 # duplication or incorrect data - use AddItem() or ModItem()
359 foreach my $field ( $record->field($itemtag) ) {
360 $record->delete_field($field);
364 =head2 ModBiblioframework
366 ModBiblioframework($biblionumber,$frameworkcode);
368 Exported function to modify a biblio framework
372 sub ModBiblioframework {
373 my ( $biblionumber, $frameworkcode ) = @_;
374 my $dbh = C4::Context->dbh;
375 my $sth = $dbh->prepare( "UPDATE biblio SET frameworkcode=? WHERE biblionumber=?" );
376 $sth->execute( $frameworkcode, $biblionumber );
382 my $error = &DelBiblio($dbh,$biblionumber);
384 Exported function (core API) for deleting a biblio in koha.
385 Deletes biblio record from Zebra and Koha tables (biblio,biblioitems,items)
386 Also backs it up to deleted* tables
387 Checks to make sure there are not issues on any of the items
389 C<$error> : undef unless an error occurs
394 my ($biblionumber) = @_;
395 my $dbh = C4::Context->dbh;
396 my $error; # for error handling
398 # First make sure this biblio has no items attached
399 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber=?");
400 $sth->execute($biblionumber);
401 if ( my $itemnumber = $sth->fetchrow ) {
403 # Fix this to use a status the template can understand
404 $error .= "This Biblio has items attached, please delete them first before deleting this biblio ";
407 return $error if $error;
409 # We delete attached subscriptions
410 my $subscriptions = &C4::Serials::GetFullSubscriptionsFromBiblionumber($biblionumber);
411 foreach my $subscription (@$subscriptions) {
412 &C4::Serials::DelSubscription( $subscription->{subscriptionid} );
415 # Delete in Zebra. Be careful NOT to move this line after _koha_delete_biblio
416 # for at least 2 reasons :
417 # - we need to read the biblio if NoZebra is set (to remove it from the indexes
418 # - if something goes wrong, the biblio may be deleted from Koha but not from zebra
419 # and we would have no way to remove it (except manually in zebra, but I bet it would be very hard to handle the problem)
421 if ( C4::Context->preference("NoZebra") ) {
423 # only NoZebra indexing needs to have
424 # the previous version of the record
425 $oldRecord = GetMarcBiblio($biblionumber);
427 ModZebra( $biblionumber, "recordDelete", "biblioserver", $oldRecord, undef );
429 # delete biblioitems and items from Koha tables and save in deletedbiblioitems,deleteditems
430 $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
431 $sth->execute($biblionumber);
432 while ( my $biblioitemnumber = $sth->fetchrow ) {
434 # delete this biblioitem
435 $error = _koha_delete_biblioitems( $dbh, $biblioitemnumber );
436 return $error if $error;
439 # delete biblio from Koha tables and save in deletedbiblio
440 # must do this *after* _koha_delete_biblioitems, otherwise
441 # delete cascade will prevent deletedbiblioitems rows
442 # from being generated by _koha_delete_biblioitems
443 $error = _koha_delete_biblio( $dbh, $biblionumber );
445 logaction( "CATALOGUING", "DELETE", $biblionumber, "" ) if C4::Context->preference("CataloguingLog");
450 =head2 LinkBibHeadingsToAuthorities
452 my $headings_linked = LinkBibHeadingsToAuthorities($marc);
454 Links bib headings to authority records by checking
455 each authority-controlled field in the C<MARC::Record>
456 object C<$marc>, looking for a matching authority record,
457 and setting the linking subfield $9 to the ID of that
460 If no matching authority exists, or if multiple
461 authorities match, no $9 will be added, and any
462 existing one inthe field will be deleted.
464 Returns the number of heading links changed in the
469 sub LinkBibHeadingsToAuthorities {
472 my $num_headings_changed = 0;
473 foreach my $field ( $bib->fields() ) {
474 my $heading = C4::Heading->new_from_bib_field($field);
475 next unless defined $heading;
478 my $current_link = $field->subfield('9');
480 # look for matching authorities
481 my $authorities = $heading->authorities();
483 # want only one exact match
484 if ( $#{$authorities} == 0 ) {
485 my $authority = MARC::Record->new_from_usmarc( $authorities->[0] );
486 my $authid = $authority->field('001')->data();
487 next if defined $current_link and $current_link eq $authid;
489 $field->delete_subfield( code => '9' ) if defined $current_link;
490 $field->add_subfields( '9', $authid );
491 $num_headings_changed++;
493 if ( defined $current_link ) {
494 $field->delete_subfield( code => '9' );
495 $num_headings_changed++;
500 return $num_headings_changed;
503 =head2 GetRecordValue
505 my $values = GetRecordValue($field, $record, $frameworkcode);
507 Get MARC fields from a keyword defined in fieldmapping table.
512 my ( $field, $record, $frameworkcode ) = @_;
513 my $dbh = C4::Context->dbh;
515 my $sth = $dbh->prepare('SELECT fieldcode, subfieldcode FROM fieldmapping WHERE frameworkcode = ? AND field = ?');
516 $sth->execute( $frameworkcode, $field );
520 while ( my $row = $sth->fetchrow_hashref ) {
521 foreach my $field ( $record->field( $row->{fieldcode} ) ) {
522 if ( ( $row->{subfieldcode} ne "" && $field->subfield( $row->{subfieldcode} ) ) ) {
523 foreach my $subfield ( $field->subfield( $row->{subfieldcode} ) ) {
524 push @result, { 'subfield' => $subfield };
527 } elsif ( $row->{subfieldcode} eq "" ) {
528 push @result, { 'subfield' => $field->as_string() };
536 =head2 SetFieldMapping
538 SetFieldMapping($framework, $field, $fieldcode, $subfieldcode);
540 Set a Field to MARC mapping value, if it already exists we don't add a new one.
544 sub SetFieldMapping {
545 my ( $framework, $field, $fieldcode, $subfieldcode ) = @_;
546 my $dbh = C4::Context->dbh;
548 my $sth = $dbh->prepare('SELECT * FROM fieldmapping WHERE fieldcode = ? AND subfieldcode = ? AND frameworkcode = ? AND field = ?');
549 $sth->execute( $fieldcode, $subfieldcode, $framework, $field );
550 if ( not $sth->fetchrow_hashref ) {
552 $sth = $dbh->prepare('INSERT INTO fieldmapping (fieldcode, subfieldcode, frameworkcode, field) VALUES(?,?,?,?)');
554 $sth->execute( $fieldcode, $subfieldcode, $framework, $field );
558 =head2 DeleteFieldMapping
560 DeleteFieldMapping($id);
562 Delete a field mapping from an $id.
566 sub DeleteFieldMapping {
568 my $dbh = C4::Context->dbh;
570 my $sth = $dbh->prepare('DELETE FROM fieldmapping WHERE id = ?');
574 =head2 GetFieldMapping
576 GetFieldMapping($frameworkcode);
578 Get all field mappings for a specified frameworkcode
582 sub GetFieldMapping {
583 my ($framework) = @_;
584 my $dbh = C4::Context->dbh;
586 my $sth = $dbh->prepare('SELECT * FROM fieldmapping where frameworkcode = ?');
587 $sth->execute($framework);
590 while ( my $row = $sth->fetchrow_hashref ) {
598 $data = &GetBiblioData($biblionumber);
600 Returns information about the book with the given biblionumber.
601 C<&GetBiblioData> returns a reference-to-hash. The keys are the fields in
602 the C<biblio> and C<biblioitems> tables in the
605 In addition, C<$data-E<gt>{subject}> is the list of the book's
606 subjects, separated by C<" , "> (space, comma, space).
607 If there are multiple biblioitems with the given biblionumber, only
608 the first one is considered.
614 my $dbh = C4::Context->dbh;
616 # my $query = C4::Context->preference('item-level_itypes') ?
617 # " SELECT * , biblioitems.notes AS bnotes, biblio.notes
619 # LEFT JOIN biblioitems ON biblio.biblionumber = biblioitems.biblionumber
620 # WHERE biblio.biblionumber = ?
621 # AND biblioitems.biblionumber = biblio.biblionumber
624 my $query = " SELECT * , biblioitems.notes AS bnotes, itemtypes.notforloan as bi_notforloan, biblio.notes
626 LEFT JOIN biblioitems ON biblio.biblionumber = biblioitems.biblionumber
627 LEFT JOIN itemtypes ON biblioitems.itemtype = itemtypes.itemtype
628 WHERE biblio.biblionumber = ?
629 AND biblioitems.biblionumber = biblio.biblionumber ";
631 my $sth = $dbh->prepare($query);
632 $sth->execute($bibnum);
634 $data = $sth->fetchrow_hashref;
638 } # sub GetBiblioData
640 =head2 &GetBiblioItemData
642 $itemdata = &GetBiblioItemData($biblioitemnumber);
644 Looks up the biblioitem with the given biblioitemnumber. Returns a
645 reference-to-hash. The keys are the fields from the C<biblio>,
646 C<biblioitems>, and C<itemtypes> tables in the Koha database, except
647 that C<biblioitems.notes> is given as C<$itemdata-E<gt>{bnotes}>.
652 sub GetBiblioItemData {
653 my ($biblioitemnumber) = @_;
654 my $dbh = C4::Context->dbh;
655 my $query = "SELECT *,biblioitems.notes AS bnotes
656 FROM biblio LEFT JOIN biblioitems on biblio.biblionumber=biblioitems.biblionumber ";
657 unless ( C4::Context->preference('item-level_itypes') ) {
658 $query .= "LEFT JOIN itemtypes on biblioitems.itemtype=itemtypes.itemtype ";
660 $query .= " WHERE biblioitemnumber = ? ";
661 my $sth = $dbh->prepare($query);
663 $sth->execute($biblioitemnumber);
664 $data = $sth->fetchrow_hashref;
667 } # sub &GetBiblioItemData
669 =head2 GetBiblioItemByBiblioNumber
671 NOTE : This function has been copy/paste from C4/Biblio.pm from head before zebra integration.
675 sub GetBiblioItemByBiblioNumber {
676 my ($biblionumber) = @_;
677 my $dbh = C4::Context->dbh;
678 my $sth = $dbh->prepare("Select * FROM biblioitems WHERE biblionumber = ?");
682 $sth->execute($biblionumber);
684 while ( my $data = $sth->fetchrow_hashref ) {
685 push @results, $data;
692 =head2 GetBiblionumberFromItemnumber
697 sub GetBiblionumberFromItemnumber {
698 my ($itemnumber) = @_;
699 my $dbh = C4::Context->dbh;
700 my $sth = $dbh->prepare("Select biblionumber FROM items WHERE itemnumber = ?");
702 $sth->execute($itemnumber);
703 my ($result) = $sth->fetchrow;
707 =head2 GetBiblioFromItemNumber
709 $item = &GetBiblioFromItemNumber($itemnumber,$barcode);
711 Looks up the item with the given itemnumber. if undef, try the barcode.
713 C<&itemnodata> returns a reference-to-hash whose keys are the fields
714 from the C<biblio>, C<biblioitems>, and C<items> tables in the Koha
720 sub GetBiblioFromItemNumber {
721 my ( $itemnumber, $barcode ) = @_;
722 my $dbh = C4::Context->dbh;
725 $sth = $dbh->prepare(
727 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
728 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
729 WHERE items.itemnumber = ?"
731 $sth->execute($itemnumber);
733 $sth = $dbh->prepare(
735 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
736 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
737 WHERE items.barcode = ?"
739 $sth->execute($barcode);
741 my $data = $sth->fetchrow_hashref;
748 $isbd = &GetISBDView($biblionumber);
750 Return the ISBD view which can be included in opac and intranet
755 my ( $biblionumber, $template ) = @_;
756 my $record = GetMarcBiblio($biblionumber, 1);
757 my $itemtype = &GetFrameworkCode($biblionumber);
758 my ( $holdingbrtagf, $holdingbrtagsubf ) = &GetMarcFromKohaField( "items.holdingbranch", $itemtype );
759 my $tagslib = &GetMarcStructure( 1, $itemtype );
761 my $ISBD = C4::Context->preference('isbd');
766 foreach my $isbdfield ( split( /#/, $bloc ) ) {
768 # $isbdfield= /(.?.?.?)/;
769 $isbdfield =~ /(\d\d\d)([^\|])?\|(.*)\|(.*)\|(.*)/;
770 my $fieldvalue = $1 || 0;
771 my $subfvalue = $2 || "";
773 my $analysestring = $4;
776 # warn "==> $1 / $2 / $3 / $4";
777 # my $fieldvalue=substr($isbdfield,0,3);
778 if ( $fieldvalue > 0 ) {
779 my $hasputtextbefore = 0;
780 my @fieldslist = $record->field($fieldvalue);
781 @fieldslist = sort { $a->subfield($holdingbrtagsubf) cmp $b->subfield($holdingbrtagsubf) } @fieldslist if ( $fieldvalue eq $holdingbrtagf );
783 # warn "ERROR IN ISBD DEFINITION at : $isbdfield" unless $fieldvalue;
784 # warn "FV : $fieldvalue";
785 if ( $subfvalue ne "" ) {
786 foreach my $field (@fieldslist) {
787 foreach my $subfield ( $field->subfield($subfvalue) ) {
788 my $calculated = $analysestring;
789 my $tag = $field->tag();
792 my $subfieldvalue = GetAuthorisedValueDesc( $tag, $subfvalue, $subfield, '', $tagslib );
793 my $tagsubf = $tag . $subfvalue;
794 $calculated =~ s/\{(.?.?.?.?)$tagsubf(.*?)\}/$1$subfieldvalue$2\{$1$tagsubf$2\}/g;
795 if ( $template eq "opac" ) { $calculated =~ s#/cgi-bin/koha/[^/]+/([^.]*.pl\?.*)$#opac-$1#g; }
797 # field builded, store the result
798 if ( $calculated && !$hasputtextbefore ) { # put textbefore if not done
799 $blocres .= $textbefore;
800 $hasputtextbefore = 1;
803 # remove punctuation at start
804 $calculated =~ s/^( |;|:|\.|-)*//g;
805 $blocres .= $calculated;
810 $blocres .= $textafter if $hasputtextbefore;
812 foreach my $field (@fieldslist) {
813 my $calculated = $analysestring;
814 my $tag = $field->tag();
817 my @subf = $field->subfields;
818 for my $i ( 0 .. $#subf ) {
819 my $valuecode = $subf[$i][1];
820 my $subfieldcode = $subf[$i][0];
821 my $subfieldvalue = GetAuthorisedValueDesc( $tag, $subf[$i][0], $subf[$i][1], '', $tagslib );
822 my $tagsubf = $tag . $subfieldcode;
824 $calculated =~ s/ # replace all {{}} codes by the value code.
825 \{\{$tagsubf\}\} # catch the {{actualcode}}
827 $valuecode # replace by the value code
830 $calculated =~ s/\{(.?.?.?.?)$tagsubf(.*?)\}/$1$subfieldvalue$2\{$1$tagsubf$2\}/g;
831 if ( $template eq "opac" ) { $calculated =~ s#/cgi-bin/koha/[^/]+/([^.]*.pl\?.*)$#opac-$1#g; }
834 # field builded, store the result
835 if ( $calculated && !$hasputtextbefore ) { # put textbefore if not done
836 $blocres .= $textbefore;
837 $hasputtextbefore = 1;
840 # remove punctuation at start
841 $calculated =~ s/^( |;|:|\.|-)*//g;
842 $blocres .= $calculated;
845 $blocres .= $textafter if $hasputtextbefore;
848 $blocres .= $isbdfield;
853 $res =~ s/\{(.*?)\}//g;
855 $res =~ s/\n/<br\/>/g;
865 ( $count, @results ) = &GetBiblio($biblionumber);
870 my ($biblionumber) = @_;
871 my $dbh = C4::Context->dbh;
872 my $sth = $dbh->prepare("SELECT * FROM biblio WHERE biblionumber = ?");
875 $sth->execute($biblionumber);
876 while ( my $data = $sth->fetchrow_hashref ) {
877 $results[$count] = $data;
881 return ( $count, @results );
884 =head2 GetBiblioItemInfosOf
886 GetBiblioItemInfosOf(@biblioitemnumbers);
890 sub GetBiblioItemInfosOf {
891 my @biblioitemnumbers = @_;
894 SELECT biblioitemnumber,
898 WHERE biblioitemnumber IN (' . join( ',', @biblioitemnumbers ) . ')
900 return get_infos_of( $query, 'biblioitemnumber' );
903 =head1 FUNCTIONS FOR HANDLING MARC MANAGEMENT
905 =head2 GetMarcStructure
907 $res = GetMarcStructure($forlibrarian,$frameworkcode);
909 Returns a reference to a big hash of hash, with the Marc structure for the given frameworkcode
910 $forlibrarian :if set to 1, the MARC descriptions are the librarians ones, otherwise it's the public (OPAC) ones
911 $frameworkcode : the framework code to read
915 # cache for results of GetMarcStructure -- needed
917 our $marc_structure_cache;
919 sub GetMarcStructure {
920 my ( $forlibrarian, $frameworkcode ) = @_;
921 my $dbh = C4::Context->dbh;
922 $frameworkcode = "" unless $frameworkcode;
924 if ( defined $marc_structure_cache and exists $marc_structure_cache->{$forlibrarian}->{$frameworkcode} ) {
925 return $marc_structure_cache->{$forlibrarian}->{$frameworkcode};
928 # my $sth = $dbh->prepare(
929 # "SELECT COUNT(*) FROM marc_tag_structure WHERE frameworkcode=?");
930 # $sth->execute($frameworkcode);
931 # my ($total) = $sth->fetchrow;
932 # $frameworkcode = "" unless ( $total > 0 );
933 my $sth = $dbh->prepare(
934 "SELECT tagfield,liblibrarian,libopac,mandatory,repeatable
935 FROM marc_tag_structure
936 WHERE frameworkcode=?
939 $sth->execute($frameworkcode);
940 my ( $liblibrarian, $libopac, $tag, $res, $tab, $mandatory, $repeatable );
942 while ( ( $tag, $liblibrarian, $libopac, $mandatory, $repeatable ) = $sth->fetchrow ) {
943 $res->{$tag}->{lib} = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
944 $res->{$tag}->{tab} = "";
945 $res->{$tag}->{mandatory} = $mandatory;
946 $res->{$tag}->{repeatable} = $repeatable;
949 $sth = $dbh->prepare(
950 "SELECT tagfield,tagsubfield,liblibrarian,libopac,tab,mandatory,repeatable,authorised_value,authtypecode,value_builder,kohafield,seealso,hidden,isurl,link,defaultvalue
951 FROM marc_subfield_structure
952 WHERE frameworkcode=?
953 ORDER BY tagfield,tagsubfield
957 $sth->execute($frameworkcode);
960 my $authorised_value;
971 ( $tag, $subfield, $liblibrarian, $libopac, $tab, $mandatory, $repeatable, $authorised_value,
972 $authtypecode, $value_builder, $kohafield, $seealso, $hidden, $isurl, $link, $defaultvalue
976 $res->{$tag}->{$subfield}->{lib} = ( $forlibrarian or !$libopac ) ? $liblibrarian : $libopac;
977 $res->{$tag}->{$subfield}->{tab} = $tab;
978 $res->{$tag}->{$subfield}->{mandatory} = $mandatory;
979 $res->{$tag}->{$subfield}->{repeatable} = $repeatable;
980 $res->{$tag}->{$subfield}->{authorised_value} = $authorised_value;
981 $res->{$tag}->{$subfield}->{authtypecode} = $authtypecode;
982 $res->{$tag}->{$subfield}->{value_builder} = $value_builder;
983 $res->{$tag}->{$subfield}->{kohafield} = $kohafield;
984 $res->{$tag}->{$subfield}->{seealso} = $seealso;
985 $res->{$tag}->{$subfield}->{hidden} = $hidden;
986 $res->{$tag}->{$subfield}->{isurl} = $isurl;
987 $res->{$tag}->{$subfield}->{'link'} = $link;
988 $res->{$tag}->{$subfield}->{defaultvalue} = $defaultvalue;
991 $marc_structure_cache->{$forlibrarian}->{$frameworkcode} = $res;
996 =head2 GetUsedMarcStructure
998 The same function as GetMarcStructure except it just takes field
999 in tab 0-9. (used field)
1001 my $results = GetUsedMarcStructure($frameworkcode);
1003 C<$results> is a ref to an array which each case containts a ref
1004 to a hash which each keys is the columns from marc_subfield_structure
1006 C<$frameworkcode> is the framework code.
1010 sub GetUsedMarcStructure($) {
1011 my $frameworkcode = shift || '';
1014 FROM marc_subfield_structure
1016 AND frameworkcode = ?
1017 ORDER BY tagfield, tagsubfield
1019 my $sth = C4::Context->dbh->prepare($query);
1020 $sth->execute($frameworkcode);
1021 return $sth->fetchall_arrayref( {} );
1024 =head2 GetMarcFromKohaField
1026 ($MARCfield,$MARCsubfield)=GetMarcFromKohaField($kohafield,$frameworkcode);
1028 Returns the MARC fields & subfields mapped to the koha field
1029 for the given frameworkcode
1033 sub GetMarcFromKohaField {
1034 my ( $kohafield, $frameworkcode ) = @_;
1035 return 0, 0 unless $kohafield and defined $frameworkcode;
1036 my $relations = C4::Context->marcfromkohafield;
1037 return ( $relations->{$frameworkcode}->{$kohafield}->[0], $relations->{$frameworkcode}->{$kohafield}->[1] );
1040 =head2 GetMarcBiblio
1042 my $record = GetMarcBiblio($biblionumber, [$embeditems]);
1044 Returns MARC::Record representing bib identified by
1045 C<$biblionumber>. If no bib exists, returns undef.
1046 C<$embeditems>. If set to true, items data are included.
1047 The MARC record contains biblio data, and items data if $embeditems is set to true.
1052 my $biblionumber = shift;
1053 my $embeditems = shift || 0;
1054 my $dbh = C4::Context->dbh;
1055 my $sth = $dbh->prepare("SELECT marcxml FROM biblioitems WHERE biblionumber=? ");
1056 $sth->execute($biblionumber);
1057 my $row = $sth->fetchrow_hashref;
1058 my $marcxml = StripNonXmlChars( $row->{'marcxml'} );
1059 MARC::File::XML->default_record_format( C4::Context->preference('marcflavour') );
1060 my $record = MARC::Record->new();
1063 $record = eval { MARC::Record::new_from_xml( $marcxml, "utf8", C4::Context->preference('marcflavour') ) };
1064 if ($@) { warn " problem with :$biblionumber : $@ \n$marcxml"; }
1065 return unless $record;
1067 C4::Biblio::EmbedItemsInMarcBiblio($record, $biblionumber) if ($embeditems);
1069 # $record = MARC::Record::new_from_usmarc( $marc) if $marc;
1078 my $marcxml = GetXmlBiblio($biblionumber);
1080 Returns biblioitems.marcxml of the biblionumber passed in parameter.
1081 The XML contains both biblio & item datas
1086 my ($biblionumber) = @_;
1087 my $dbh = C4::Context->dbh;
1088 my $sth = $dbh->prepare("SELECT marcxml FROM biblioitems WHERE biblionumber=? ");
1089 $sth->execute($biblionumber);
1090 my ($marcxml) = $sth->fetchrow;
1094 =head2 GetCOinSBiblio
1096 my $coins = GetCOinSBiblio($biblionumber);
1098 Returns the COinS(a span) which can be included in a biblio record
1102 sub GetCOinSBiblio {
1103 my ($biblionumber) = @_;
1104 my $record = GetMarcBiblio($biblionumber);
1106 # get the coin format
1108 # can't get a valid MARC::Record object, bail out at this point
1109 warn "We called GetMarcBiblio with a biblionumber that doesn't exist biblionumber=$biblionumber";
1112 my $pos7 = substr $record->leader(), 7, 1;
1113 my $pos6 = substr $record->leader(), 6, 1;
1116 my ( $aulast, $aufirst ) = ( '', '' );
1125 my $titletype = 'b';
1127 # For the purposes of generating COinS metadata, LDR/06-07 can be
1128 # considered the same for UNIMARC and MARC21
1133 'b' => 'manuscript',
1135 'd' => 'manuscript',
1139 'i' => 'audioRecording',
1140 'j' => 'audioRecording',
1143 'm' => 'computerProgram',
1148 'a' => 'journalArticle',
1152 $genre = $fmts6->{$pos6} ? $fmts6->{$pos6} : 'book';
1154 if ( $genre eq 'book' ) {
1155 $genre = $fmts7->{$pos7} if $fmts7->{$pos7};
1158 ##### We must transform mtx to a valable mtx and document type ####
1159 if ( $genre eq 'book' ) {
1161 } elsif ( $genre eq 'journal' ) {
1164 } elsif ( $genre eq 'journalArticle' ) {
1172 $genre = ( $mtx eq 'dc' ) ? "&rft.type=$genre" : "&rft.genre=$genre";
1174 if ( C4::Context->preference("marcflavour") eq "UNIMARC" ) {
1177 $aulast = $record->subfield( '700', 'a' );
1178 $aufirst = $record->subfield( '700', 'b' );
1179 $oauthors = "&rft.au=$aufirst $aulast";
1182 if ( $record->field('200') ) {
1183 for my $au ( $record->field('200')->subfield('g') ) {
1184 $oauthors .= "&rft.au=$au";
1189 ? "&rft.title=" . $record->subfield( '200', 'a' )
1190 : "&rft.title=" . $record->subfield( '200', 'a' ) . "&rft.btitle=" . $record->subfield( '200', 'a' );
1191 $pubyear = $record->subfield( '210', 'd' );
1192 $publisher = $record->subfield( '210', 'c' );
1193 $isbn = $record->subfield( '010', 'a' );
1194 $issn = $record->subfield( '011', 'a' );
1197 # MARC21 need some improve
1200 if ( $record->field('100') ) {
1201 $oauthors .= "&rft.au=" . $record->subfield( '100', 'a' );
1205 if ( $record->field('700') ) {
1206 for my $au ( $record->field('700')->subfield('a') ) {
1207 $oauthors .= "&rft.au=$au";
1210 $title = "&rft." . $titletype . "title=" . $record->subfield( '245', 'a' );
1211 $subtitle = $record->subfield( '245', 'b' ) || '';
1212 $title .= $subtitle;
1213 if ($titletype eq 'a') {
1214 $pubyear = substr $record->field('008')->data(), 7, 4;
1215 $isbn = $record->subfield( '773', 'z' ) || '';
1216 $issn = $record->subfield( '773', 'x' ) || '';
1217 if ($mtx eq 'journal') {
1218 $title .= "&rft.title=" . (($record->subfield( '773', 't' ) || $record->subfield( '773', 'a')));
1220 $title .= "&rft.btitle=" . (($record->subfield( '773', 't' ) || $record->subfield( '773', 'a')) || '');
1222 foreach my $rel ($record->subfield( '773', 'g' )) {
1229 $pubyear = $record->subfield( '260', 'c' ) || '';
1230 $publisher = $record->subfield( '260', 'b' ) || '';
1231 $isbn = $record->subfield( '020', 'a' ) || '';
1232 $issn = $record->subfield( '022', 'a' ) || '';
1237 "ctx_ver=Z39.88-2004&rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3A$mtx$genre$title&rft.isbn=$isbn&rft.issn=$issn&rft.aulast=$aulast&rft.aufirst=$aufirst$oauthors&rft.pub=$publisher&rft.date=$pubyear&rft.pages=$pages";
1238 $coins_value =~ s/(\ |&[^a])/\+/g;
1239 $coins_value =~ s/\"/\"\;/g;
1241 #<!-- TMPL_VAR NAME="ocoins_format" -->&rft.au=<!-- TMPL_VAR NAME="author" -->&rft.btitle=<!-- TMPL_VAR NAME="title" -->&rft.date=<!-- TMPL_VAR NAME="publicationyear" -->&rft.pages=<!-- TMPL_VAR NAME="pages" -->&rft.isbn=<!-- TMPL_VAR NAME=amazonisbn -->&rft.aucorp=&rft.place=<!-- TMPL_VAR NAME="place" -->&rft.pub=<!-- TMPL_VAR NAME="publishercode" -->&rft.edition=<!-- TMPL_VAR NAME="edition" -->&rft.series=<!-- TMPL_VAR NAME="series" -->&rft.genre="
1243 return $coins_value;
1249 return the prices in accordance with the Marc format.
1253 my ( $record, $marcflavour ) = @_;
1257 if ( $marcflavour eq "MARC21" ) {
1258 @listtags = ('345', '020');
1260 } elsif ( $marcflavour eq "UNIMARC" ) {
1261 @listtags = ('345', '010');
1267 for my $field ( $record->field(@listtags) ) {
1268 for my $subfield_value ($field->subfield($subfield)){
1270 return $subfield_value if ($subfield_value);
1273 return 0; # no price found
1276 =head2 GetMarcQuantity
1278 return the quantity of a book. Used in acquisition only, when importing a file an iso2709 from a bookseller
1279 Warning : this is not really in the marc standard. In Unimarc, Electre (the most widely used bookseller) use the 969$a
1283 sub GetMarcQuantity {
1284 my ( $record, $marcflavour ) = @_;
1288 if ( $marcflavour eq "MARC21" ) {
1290 } elsif ( $marcflavour eq "UNIMARC" ) {
1291 @listtags = ('969');
1297 for my $field ( $record->field(@listtags) ) {
1298 for my $subfield_value ($field->subfield($subfield)){
1300 if ($subfield_value) {
1301 # in France, the cents separator is the , but sometimes, ppl use a .
1302 # in this case, the price will be x100 when unformatted ! Replace the . by a , to get a proper price calculation
1303 $subfield_value =~ s/\./,/ if C4::Context->preference("CurrencyFormat") eq "FR";
1304 return $subfield_value;
1308 return 0; # no price found
1312 =head2 GetAuthorisedValueDesc
1314 my $subfieldvalue =get_authorised_value_desc(
1315 $tag, $subf[$i][0],$subf[$i][1], '', $taglib, $category, $opac);
1317 Retrieve the complete description for a given authorised value.
1319 Now takes $category and $value pair too.
1321 my $auth_value_desc =GetAuthorisedValueDesc(
1322 '','', 'DVD' ,'','','CCODE');
1324 If the optional $opac parameter is set to a true value, displays OPAC
1325 descriptions rather than normal ones when they exist.
1329 sub GetAuthorisedValueDesc {
1330 my ( $tag, $subfield, $value, $framework, $tagslib, $category, $opac ) = @_;
1331 my $dbh = C4::Context->dbh;
1335 return $value unless defined $tagslib->{$tag}->{$subfield}->{'authorised_value'};
1338 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1339 return C4::Branch::GetBranchName($value);
1343 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "itemtypes" ) {
1344 return getitemtypeinfo($value)->{description};
1347 #---- "true" authorized value
1348 $category = $tagslib->{$tag}->{$subfield}->{'authorised_value'};
1351 if ( $category ne "" ) {
1352 my $sth = $dbh->prepare( "SELECT lib, lib_opac FROM authorised_values WHERE category = ? AND authorised_value = ?" );
1353 $sth->execute( $category, $value );
1354 my $data = $sth->fetchrow_hashref;
1355 return ( $opac && $data->{'lib_opac'} ) ? $data->{'lib_opac'} : $data->{'lib'};
1357 return $value; # if nothing is found return the original value
1361 =head2 GetMarcControlnumber
1363 $marccontrolnumber = GetMarcControlnumber($record,$marcflavour);
1365 Get the control number / record Identifier from the MARC record and return it.
1369 sub GetMarcControlnumber {
1370 my ( $record, $marcflavour ) = @_;
1371 my $controlnumber = "";
1372 # Control number or Record identifier are the same field in MARC21 and UNIMARC
1373 # Keep $marcflavour for possible later use
1374 if ($marcflavour eq "MARC21" || $marcflavour eq "UNIMARC") {
1375 my $controlnumberField = $record->field('001');
1376 if ($controlnumberField) {
1377 $controlnumber = $controlnumberField->data();
1380 return $controlnumber;
1385 $marcisbnsarray = GetMarcISBN( $record, $marcflavour );
1387 Get all ISBNs from the MARC record and returns them in an array.
1388 ISBNs stored in differents places depending on MARC flavour
1393 my ( $record, $marcflavour ) = @_;
1395 if ( $marcflavour eq "UNIMARC" ) {
1397 } else { # assume marc21 if not unimarc
1404 foreach my $field ( $record->field($scope) ) {
1405 my $value = $field->as_string();
1406 if ( $isbn ne "" ) {
1407 $marcisbn = { marcisbn => $isbn, };
1408 push @marcisbns, $marcisbn;
1411 if ( $isbn ne $value ) {
1412 $isbn = $isbn . " " . $value;
1417 $marcisbn = { marcisbn => $isbn };
1418 push @marcisbns, $marcisbn; #load last tag into array
1425 $marcnotesarray = GetMarcNotes( $record, $marcflavour );
1427 Get all notes from the MARC record and returns them in an array.
1428 The note are stored in differents places depending on MARC flavour
1433 my ( $record, $marcflavour ) = @_;
1435 if ( $marcflavour eq "UNIMARC" ) {
1437 } else { # assume marc21 if not unimarc
1444 foreach my $field ( $record->field($scope) ) {
1445 my $value = $field->as_string();
1446 if ( $note ne "" ) {
1447 $marcnote = { marcnote => $note, };
1448 push @marcnotes, $marcnote;
1451 if ( $note ne $value ) {
1452 $note = $note . " " . $value;
1457 $marcnote = { marcnote => $note };
1458 push @marcnotes, $marcnote; #load last tag into array
1461 } # end GetMarcNotes
1463 =head2 GetMarcSubjects
1465 $marcsubjcts = GetMarcSubjects($record,$marcflavour);
1467 Get all subjects from the MARC record and returns them in an array.
1468 The subjects are stored in differents places depending on MARC flavour
1472 sub GetMarcSubjects {
1473 my ( $record, $marcflavour ) = @_;
1474 my ( $mintag, $maxtag );
1475 if ( $marcflavour eq "UNIMARC" ) {
1478 } else { # assume marc21 if not unimarc
1488 my $subject_limit = C4::Context->preference("TraceCompleteSubfields") ? 'su,complete-subfield' : 'su';
1490 foreach my $field ( $record->field('6..') ) {
1491 next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
1493 my @subfields = $field->subfields();
1497 # if there is an authority link, build the link with an= subfield9
1499 for my $subject_subfield (@subfields) {
1501 # don't load unimarc subfields 3,4,5
1502 next if ( ( $marcflavour eq "UNIMARC" ) and ( $subject_subfield->[0] =~ /2|3|4|5/ ) );
1504 # don't load MARC21 subfields 2 (FIXME: any more subfields??)
1505 next if ( ( $marcflavour eq "MARC21" ) and ( $subject_subfield->[0] =~ /2/ ) );
1506 my $code = $subject_subfield->[0];
1507 my $value = $subject_subfield->[1];
1508 my $linkvalue = $value;
1509 $linkvalue =~ s/(\(|\))//g;
1510 my $operator = " and " unless $counter == 0;
1513 @link_loop = ( { 'limit' => 'an', link => "$linkvalue" } );
1515 if ( not $found9 ) {
1516 push @link_loop, { 'limit' => $subject_limit, link => $linkvalue, operator => $operator };
1518 my $separator = C4::Context->preference("authoritysep") unless $counter == 0;
1521 my @this_link_loop = @link_loop;
1522 push @subfields_loop, { code => $code, value => $value, link_loop => \@this_link_loop, separator => $separator } unless ( $subject_subfield->[0] eq 9 );
1526 push @marcsubjects, { MARCSUBJECT_SUBFIELDS_LOOP => \@subfields_loop };
1529 return \@marcsubjects;
1530 } #end getMARCsubjects
1532 =head2 GetMarcAuthors
1534 authors = GetMarcAuthors($record,$marcflavour);
1536 Get all authors from the MARC record and returns them in an array.
1537 The authors are stored in differents places depending on MARC flavour
1541 sub GetMarcAuthors {
1542 my ( $record, $marcflavour ) = @_;
1543 my ( $mintag, $maxtag );
1545 # tagslib useful for UNIMARC author reponsabilities
1547 &GetMarcStructure( 1, '' ); # FIXME : we don't have the framework available, we take the default framework. May be buggy on some setups, will be usually correct.
1548 if ( $marcflavour eq "UNIMARC" ) {
1551 } elsif ( $marcflavour eq "MARC21" || $marcflavour eq "NORMARC" ) { # assume marc21 or normarc if not unimarc
1559 foreach my $field ( $record->fields ) {
1560 next unless $field->tag() >= $mintag && $field->tag() <= $maxtag;
1563 my @subfields = $field->subfields();
1566 # if there is an authority link, build the link with Koha-Auth-Number: subfield9
1567 my $subfield9 = $field->subfield('9');
1568 for my $authors_subfield (@subfields) {
1570 # don't load unimarc subfields 3, 5
1571 next if ( $marcflavour eq 'UNIMARC' and ( $authors_subfield->[0] =~ /3|5/ ) );
1572 my $subfieldcode = $authors_subfield->[0];
1573 my $value = $authors_subfield->[1];
1574 my $linkvalue = $value;
1575 $linkvalue =~ s/(\(|\))//g;
1576 my $operator = " and " unless $count_auth == 0;
1578 # if we have an authority link, use that as the link, otherwise use standard searching
1580 @link_loop = ( { 'limit' => 'an', link => "$subfield9" } );
1583 # reset $linkvalue if UNIMARC author responsibility
1584 if ( $marcflavour eq 'UNIMARC' and ( $authors_subfield->[0] eq "4" ) ) {
1585 $linkvalue = "(" . GetAuthorisedValueDesc( $field->tag(), $authors_subfield->[0], $authors_subfield->[1], '', $tagslib ) . ")";
1587 push @link_loop, { 'limit' => 'au', link => $linkvalue, operator => $operator };
1589 $value = GetAuthorisedValueDesc( $field->tag(), $authors_subfield->[0], $authors_subfield->[1], '', $tagslib )
1590 if ( $marcflavour eq 'UNIMARC' and ( $authors_subfield->[0] =~ /4/ ) );
1591 my @this_link_loop = @link_loop;
1592 my $separator = C4::Context->preference("authoritysep") unless $count_auth == 0;
1593 push @subfields_loop, { code => $subfieldcode, value => $value, link_loop => \@this_link_loop, separator => $separator } unless ( $authors_subfield->[0] eq '9' );
1596 push @marcauthors, { MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop };
1598 return \@marcauthors;
1603 $marcurls = GetMarcUrls($record,$marcflavour);
1605 Returns arrayref of URLs from MARC data, suitable to pass to tmpl loop.
1606 Assumes web resources (not uncommon in MARC21 to omit resource type ind)
1611 my ( $record, $marcflavour ) = @_;
1614 for my $field ( $record->field('856') ) {
1616 for my $note ( $field->subfield('z') ) {
1617 push @notes, { note => $note };
1619 my @urls = $field->subfield('u');
1620 foreach my $url (@urls) {
1622 if ( $marcflavour eq 'MARC21' ) {
1623 my $s3 = $field->subfield('3');
1624 my $link = $field->subfield('y');
1625 unless ( $url =~ /^\w+:/ ) {
1626 if ( $field->indicator(1) eq '7' ) {
1627 $url = $field->subfield('2') . "://" . $url;
1628 } elsif ( $field->indicator(1) eq '1' ) {
1629 $url = 'ftp://' . $url;
1632 # properly, this should be if ind1=4,
1633 # however we will assume http protocol since we're building a link.
1634 $url = 'http://' . $url;
1638 # TODO handle ind 2 (relationship)
1643 $marcurl->{'linktext'} = $link || $s3 || C4::Context->preference('URLLinkText') || $url;
1644 $marcurl->{'part'} = $s3 if ($link);
1645 $marcurl->{'toc'} = 1 if ( defined($s3) && $s3 =~ /^[Tt]able/ );
1647 $marcurl->{'linktext'} = $field->subfield('2') || C4::Context->preference('URLLinkText') || $url;
1648 $marcurl->{'MARCURL'} = $url;
1650 push @marcurls, $marcurl;
1656 =head2 GetMarcSeries
1658 $marcseriesarray = GetMarcSeries($record,$marcflavour);
1660 Get all series from the MARC record and returns them in an array.
1661 The series are stored in differents places depending on MARC flavour
1666 my ( $record, $marcflavour ) = @_;
1667 my ( $mintag, $maxtag );
1668 if ( $marcflavour eq "UNIMARC" ) {
1671 } else { # assume marc21 if not unimarc
1681 foreach my $field ( $record->field('440'), $record->field('490') ) {
1684 #my $value = $field->subfield('a');
1685 #$marcsubjct = {MARCSUBJCT => $value,};
1686 my @subfields = $field->subfields();
1688 #warn "subfields:".join " ", @$subfields;
1691 for my $series_subfield (@subfields) {
1693 undef $volume_number;
1695 # see if this is an instance of a volume
1696 if ( $series_subfield->[0] eq 'v' ) {
1700 my $code = $series_subfield->[0];
1701 my $value = $series_subfield->[1];
1702 my $linkvalue = $value;
1703 $linkvalue =~ s/(\(|\))//g;
1704 my $operator = " and " unless $counter == 0;
1705 push @link_loop, { link => $linkvalue, operator => $operator };
1706 my $separator = C4::Context->preference("authoritysep") unless $counter == 0;
1707 if ($volume_number) {
1708 push @subfields_loop, { volumenum => $value };
1710 push @subfields_loop, { code => $code, value => $value, link_loop => \@link_loop, separator => $separator, volumenum => $volume_number } unless ( $series_subfield->[0] eq '9' );
1714 push @marcseries, { MARCSERIES_SUBFIELDS_LOOP => \@subfields_loop };
1716 #$marcsubjct = {MARCSUBJCT => $field->as_string(),};
1717 #push @marcsubjcts, $marcsubjct;
1721 my $marcseriessarray = \@marcseries;
1722 return $marcseriessarray;
1723 } #end getMARCseriess
1725 =head2 GetFrameworkCode
1727 $frameworkcode = GetFrameworkCode( $biblionumber )
1731 sub GetFrameworkCode {
1732 my ($biblionumber) = @_;
1733 my $dbh = C4::Context->dbh;
1734 my $sth = $dbh->prepare("SELECT frameworkcode FROM biblio WHERE biblionumber=?");
1735 $sth->execute($biblionumber);
1736 my ($frameworkcode) = $sth->fetchrow;
1737 return $frameworkcode;
1740 =head2 TransformKohaToMarc
1742 $record = TransformKohaToMarc( $hash )
1744 This function builds partial MARC::Record from a hash
1745 Hash entries can be from biblio or biblioitems.
1747 This function is called in acquisition module, to create a basic catalogue entry from user entry
1751 sub TransformKohaToMarc {
1753 my $sth = C4::Context->dbh->prepare( "SELECT tagfield,tagsubfield FROM marc_subfield_structure WHERE frameworkcode=? AND kohafield=?" );
1754 my $record = MARC::Record->new();
1755 SetMarcUnicodeFlag( $record, C4::Context->preference("marcflavour") );
1756 foreach ( keys %{$hash} ) {
1757 &TransformKohaToMarcOneField( $sth, $record, $_, $hash->{$_}, '' );
1762 =head2 TransformKohaToMarcOneField
1764 $record = TransformKohaToMarcOneField( $sth, $record, $kohafieldname, $value, $frameworkcode );
1768 sub TransformKohaToMarcOneField {
1769 my ( $sth, $record, $kohafieldname, $value, $frameworkcode ) = @_;
1770 $frameworkcode = '' unless $frameworkcode;
1774 if ( !defined $sth ) {
1775 my $dbh = C4::Context->dbh;
1776 $sth = $dbh->prepare( "SELECT tagfield,tagsubfield FROM marc_subfield_structure WHERE frameworkcode=? AND kohafield=?" );
1778 $sth->execute( $frameworkcode, $kohafieldname );
1779 if ( ( $tagfield, $tagsubfield ) = $sth->fetchrow ) {
1780 my @values = split(/\s?\|\s?/, $value, -1);
1782 foreach my $itemvalue (@values){
1783 my $tag = $record->field($tagfield);
1785 $tag->add_subfields( $tagsubfield => $itemvalue );
1786 $record->delete_field($tag);
1787 $record->insert_fields_ordered($tag);
1790 $record->add_fields( $tagfield, " ", " ", $tagsubfield => $itemvalue );
1797 =head2 TransformHtmlToXml
1799 $xml = TransformHtmlToXml( $tags, $subfields, $values, $indicator,
1800 $ind_tag, $auth_type )
1802 $auth_type contains :
1806 =item - nothing : rebuild a biblio. In UNIMARC the encoding is in 100$a pos 26/27
1808 =item - UNIMARCAUTH : rebuild an authority. In UNIMARC, the encoding is in 100$a pos 13/14
1810 =item - ITEM : rebuild an item : in UNIMARC, 100$a, it's in the biblio ! (otherwise, we would get 2 100 fields !)
1816 sub TransformHtmlToXml {
1817 my ( $tags, $subfields, $values, $indicator, $ind_tag, $auth_type ) = @_;
1818 my $xml = MARC::File::XML::header('UTF-8');
1819 $xml .= "<record>\n";
1820 $auth_type = C4::Context->preference('marcflavour') unless $auth_type;
1821 MARC::File::XML->default_record_format($auth_type);
1823 # in UNIMARC, field 100 contains the encoding
1824 # check that there is one, otherwise the
1825 # MARC::Record->new_from_xml will fail (and Koha will die)
1826 my $unimarc_and_100_exist = 0;
1827 $unimarc_and_100_exist = 1 if $auth_type eq 'ITEM'; # if we rebuild an item, no need of a 100 field
1832 for ( my $i = 0 ; $i < @$tags ; $i++ ) {
1834 if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and @$tags[$i] eq "100" and @$subfields[$i] eq "a" ) {
1836 # if we have a 100 field and it's values are not correct, skip them.
1837 # if we don't have any valid 100 field, we will create a default one at the end
1838 my $enc = substr( @$values[$i], 26, 2 );
1839 if ( $enc eq '01' or $enc eq '50' or $enc eq '03' ) {
1840 $unimarc_and_100_exist = 1;
1845 @$values[$i] =~ s/&/&/g;
1846 @$values[$i] =~ s/</</g;
1847 @$values[$i] =~ s/>/>/g;
1848 @$values[$i] =~ s/"/"/g;
1849 @$values[$i] =~ s/'/'/g;
1851 # if ( !utf8::is_utf8( @$values[$i] ) ) {
1852 # utf8::decode( @$values[$i] );
1854 if ( ( @$tags[$i] ne $prevtag ) ) {
1855 $j++ unless ( @$tags[$i] eq "" );
1856 my $indicator1 = eval { substr( @$indicator[$j], 0, 1 ) };
1857 my $indicator2 = eval { substr( @$indicator[$j], 1, 1 ) };
1858 my $ind1 = _default_ind_to_space($indicator1);
1860 if ( @$indicator[$j] ) {
1861 $ind2 = _default_ind_to_space($indicator2);
1863 warn "Indicator in @$tags[$i] is empty";
1867 $xml .= "</datafield>\n";
1868 if ( ( @$tags[$i] && @$tags[$i] > 10 )
1869 && ( @$values[$i] ne "" ) ) {
1870 $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
1871 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
1877 if ( @$values[$i] ne "" ) {
1880 if ( @$tags[$i] eq "000" ) {
1881 $xml .= "<leader>@$values[$i]</leader>\n";
1884 # rest of the fixed fields
1885 } elsif ( @$tags[$i] < 10 ) {
1886 $xml .= "<controlfield tag=\"@$tags[$i]\">@$values[$i]</controlfield>\n";
1889 $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
1890 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
1895 } else { # @$tags[$i] eq $prevtag
1896 my $indicator1 = eval { substr( @$indicator[$j], 0, 1 ) };
1897 my $indicator2 = eval { substr( @$indicator[$j], 1, 1 ) };
1898 my $ind1 = _default_ind_to_space($indicator1);
1900 if ( @$indicator[$j] ) {
1901 $ind2 = _default_ind_to_space($indicator2);
1903 warn "Indicator in @$tags[$i] is empty";
1906 if ( @$values[$i] eq "" ) {
1909 $xml .= "<datafield tag=\"@$tags[$i]\" ind1=\"$ind1\" ind2=\"$ind2\">\n";
1912 $xml .= "<subfield code=\"@$subfields[$i]\">@$values[$i]</subfield>\n";
1915 $prevtag = @$tags[$i];
1917 $xml .= "</datafield>\n" if @$tags > 0;
1918 if ( C4::Context->preference('marcflavour') eq 'UNIMARC' and !$unimarc_and_100_exist ) {
1920 # warn "SETTING 100 for $auth_type";
1921 my $string = strftime( "%Y%m%d", localtime(time) );
1923 # set 50 to position 26 is biblios, 13 if authorities
1925 $pos = 13 if $auth_type eq 'UNIMARCAUTH';
1926 $string = sprintf( "%-*s", 35, $string );
1927 substr( $string, $pos, 6, "50" );
1928 $xml .= "<datafield tag=\"100\" ind1=\"\" ind2=\"\">\n";
1929 $xml .= "<subfield code=\"a\">$string</subfield>\n";
1930 $xml .= "</datafield>\n";
1932 $xml .= "</record>\n";
1933 $xml .= MARC::File::XML::footer();
1937 =head2 _default_ind_to_space
1939 Passed what should be an indicator returns a space
1940 if its undefined or zero length
1944 sub _default_ind_to_space {
1946 if ( !defined $s || $s eq q{} ) {
1952 =head2 TransformHtmlToMarc
1954 L<$record> = TransformHtmlToMarc(L<$params>,L<$cgi>)
1955 L<$params> is a ref to an array as below:
1957 'tag_010_indicator1_531951' ,
1958 'tag_010_indicator2_531951' ,
1959 'tag_010_code_a_531951_145735' ,
1960 'tag_010_subfield_a_531951_145735' ,
1961 'tag_200_indicator1_873510' ,
1962 'tag_200_indicator2_873510' ,
1963 'tag_200_code_a_873510_673465' ,
1964 'tag_200_subfield_a_873510_673465' ,
1965 'tag_200_code_b_873510_704318' ,
1966 'tag_200_subfield_b_873510_704318' ,
1967 'tag_200_code_e_873510_280822' ,
1968 'tag_200_subfield_e_873510_280822' ,
1969 'tag_200_code_f_873510_110730' ,
1970 'tag_200_subfield_f_873510_110730' ,
1972 L<$cgi> is the CGI object which containts the value.
1973 L<$record> is the MARC::Record object.
1977 sub TransformHtmlToMarc {
1981 # explicitly turn on the UTF-8 flag for all
1982 # 'tag_' parameters to avoid incorrect character
1983 # conversion later on
1984 my $cgi_params = $cgi->Vars;
1985 foreach my $param_name ( keys %$cgi_params ) {
1986 if ( $param_name =~ /^tag_/ ) {
1987 my $param_value = $cgi_params->{$param_name};
1988 if ( utf8::decode($param_value) ) {
1989 $cgi_params->{$param_name} = $param_value;
1992 # FIXME - need to do something if string is not valid UTF-8
1996 # creating a new record
1997 my $record = MARC::Record->new();
2000 while ( $params->[$i] ) { # browse all CGI params
2001 my $param = $params->[$i];
2004 # if we are on biblionumber, store it in the MARC::Record (it may not be in the edited fields)
2005 if ( $param eq 'biblionumber' ) {
2006 my ( $biblionumbertagfield, $biblionumbertagsubfield ) = &GetMarcFromKohaField( "biblio.biblionumber", '' );
2007 if ( $biblionumbertagfield < 10 ) {
2008 $newfield = MARC::Field->new( $biblionumbertagfield, $cgi->param($param), );
2010 $newfield = MARC::Field->new( $biblionumbertagfield, '', '', "$biblionumbertagsubfield" => $cgi->param($param), );
2012 push @fields, $newfield if ($newfield);
2013 } elsif ( $param =~ /^tag_(\d*)_indicator1_/ ) { # new field start when having 'input name="..._indicator1_..."
2016 my $ind1 = _default_ind_to_space( substr( $cgi->param($param), 0, 1 ) );
2017 my $ind2 = _default_ind_to_space( substr( $cgi->param( $params->[ $i + 1 ] ), 0, 1 ) );
2021 if ( $tag < 10 ) { # no code for theses fields
2022 # in MARC editor, 000 contains the leader.
2023 if ( $tag eq '000' ) {
2024 # Force a fake leader even if not provided to avoid crashing
2025 # during decoding MARC record containing UTF-8 characters
2027 length( $cgi->param($params->[$j+1]) ) == 24
2028 ? $cgi->param( $params->[ $j + 1 ] )
2032 # between 001 and 009 (included)
2033 } elsif ( $cgi->param( $params->[ $j + 1 ] ) ne '' ) {
2034 $newfield = MARC::Field->new( $tag, $cgi->param( $params->[ $j + 1 ] ), );
2037 # > 009, deal with subfields
2039 while ( defined $params->[$j] && $params->[$j] =~ /_code_/ ) { # browse all it's subfield
2040 my $inner_param = $params->[$j];
2042 if ( $cgi->param( $params->[ $j + 1 ] ) ne '' ) { # only if there is a value (code => value)
2043 $newfield->add_subfields( $cgi->param($inner_param) => $cgi->param( $params->[ $j + 1 ] ) );
2046 if ( $cgi->param( $params->[ $j + 1 ] ) ne '' ) { # creating only if there is a value (code => value)
2047 $newfield = MARC::Field->new( $tag, $ind1, $ind2, $cgi->param($inner_param) => $cgi->param( $params->[ $j + 1 ] ), );
2053 push @fields, $newfield if ($newfield);
2058 $record->append_fields(@fields);
2062 # cache inverted MARC field map
2063 our $inverted_field_map;
2065 =head2 TransformMarcToKoha
2067 $result = TransformMarcToKoha( $dbh, $record, $frameworkcode )
2069 Extract data from a MARC bib record into a hashref representing
2070 Koha biblio, biblioitems, and items fields.
2074 sub TransformMarcToKoha {
2075 my ( $dbh, $record, $frameworkcode, $limit_table ) = @_;
2078 $limit_table = $limit_table || 0;
2079 $frameworkcode = '' unless defined $frameworkcode;
2081 unless ( defined $inverted_field_map ) {
2082 $inverted_field_map = _get_inverted_marc_field_map();
2086 if ( defined $limit_table && $limit_table eq 'items' ) {
2087 $tables{'items'} = 1;
2089 $tables{'items'} = 1;
2090 $tables{'biblio'} = 1;
2091 $tables{'biblioitems'} = 1;
2094 # traverse through record
2095 MARCFIELD: foreach my $field ( $record->fields() ) {
2096 my $tag = $field->tag();
2097 next MARCFIELD unless exists $inverted_field_map->{$frameworkcode}->{$tag};
2098 if ( $field->is_control_field() ) {
2099 my $kohafields = $inverted_field_map->{$frameworkcode}->{$tag}->{list};
2100 ENTRY: foreach my $entry ( @{$kohafields} ) {
2101 my ( $subfield, $table, $column ) = @{$entry};
2102 next ENTRY unless exists $tables{$table};
2103 my $key = _disambiguate( $table, $column );
2104 if ( $result->{$key} ) {
2105 unless ( ( $key eq "biblionumber" or $key eq "biblioitemnumber" ) and ( $field->data() eq "" ) ) {
2106 $result->{$key} .= " | " . $field->data();
2109 $result->{$key} = $field->data();
2114 # deal with subfields
2115 MARCSUBFIELD: foreach my $sf ( $field->subfields() ) {
2116 my $code = $sf->[0];
2117 next MARCSUBFIELD unless exists $inverted_field_map->{$frameworkcode}->{$tag}->{sfs}->{$code};
2118 my $value = $sf->[1];
2119 SFENTRY: foreach my $entry ( @{ $inverted_field_map->{$frameworkcode}->{$tag}->{sfs}->{$code} } ) {
2120 my ( $table, $column ) = @{$entry};
2121 next SFENTRY unless exists $tables{$table};
2122 my $key = _disambiguate( $table, $column );
2123 if ( $result->{$key} ) {
2124 unless ( ( $key eq "biblionumber" or $key eq "biblioitemnumber" ) and ( $value eq "" ) ) {
2125 $result->{$key} .= " | " . $value;
2128 $result->{$key} = $value;
2135 # modify copyrightdate to keep only the 1st year found
2136 if ( exists $result->{'copyrightdate'} ) {
2137 my $temp = $result->{'copyrightdate'};
2138 $temp =~ m/c(\d\d\d\d)/;
2139 if ( $temp =~ m/c(\d\d\d\d)/ and $1 > 0 ) { # search cYYYY first
2140 $result->{'copyrightdate'} = $1;
2141 } else { # if no cYYYY, get the 1st date.
2142 $temp =~ m/(\d\d\d\d)/;
2143 $result->{'copyrightdate'} = $1;
2147 # modify publicationyear to keep only the 1st year found
2148 if ( exists $result->{'publicationyear'} ) {
2149 my $temp = $result->{'publicationyear'};
2150 if ( $temp =~ m/c(\d\d\d\d)/ and $1 > 0 ) { # search cYYYY first
2151 $result->{'publicationyear'} = $1;
2152 } else { # if no cYYYY, get the 1st date.
2153 $temp =~ m/(\d\d\d\d)/;
2154 $result->{'publicationyear'} = $1;
2161 sub _get_inverted_marc_field_map {
2163 my $relations = C4::Context->marcfromkohafield;
2165 foreach my $frameworkcode ( keys %{$relations} ) {
2166 foreach my $kohafield ( keys %{ $relations->{$frameworkcode} } ) {
2167 next unless @{ $relations->{$frameworkcode}->{$kohafield} }; # not all columns are mapped to MARC tag & subfield
2168 my $tag = $relations->{$frameworkcode}->{$kohafield}->[0];
2169 my $subfield = $relations->{$frameworkcode}->{$kohafield}->[1];
2170 my ( $table, $column ) = split /[.]/, $kohafield, 2;
2171 push @{ $field_map->{$frameworkcode}->{$tag}->{list} }, [ $subfield, $table, $column ];
2172 push @{ $field_map->{$frameworkcode}->{$tag}->{sfs}->{$subfield} }, [ $table, $column ];
2178 =head2 _disambiguate
2180 $newkey = _disambiguate($table, $field);
2182 This is a temporary hack to distinguish between the
2183 following sets of columns when using TransformMarcToKoha.
2185 items.cn_source & biblioitems.cn_source
2186 items.cn_sort & biblioitems.cn_sort
2188 Columns that are currently NOT distinguished (FIXME
2189 due to lack of time to fully test) are:
2191 biblio.notes and biblioitems.notes
2196 FIXME - this is necessary because prefixing each column
2197 name with the table name would require changing lots
2198 of code and templates, and exposing more of the DB
2199 structure than is good to the UI templates, particularly
2200 since biblio and bibloitems may well merge in a future
2201 version. In the future, it would also be good to
2202 separate DB access and UI presentation field names
2207 sub CountItemsIssued {
2208 my ($biblionumber) = @_;
2209 my $dbh = C4::Context->dbh;
2210 my $sth = $dbh->prepare('SELECT COUNT(*) as issuedCount FROM items, issues WHERE items.itemnumber = issues.itemnumber AND items.biblionumber = ?');
2211 $sth->execute($biblionumber);
2212 my $row = $sth->fetchrow_hashref();
2213 return $row->{'issuedCount'};
2217 my ( $table, $column ) = @_;
2218 if ( $column eq "cn_sort" or $column eq "cn_source" ) {
2219 return $table . '.' . $column;
2226 =head2 get_koha_field_from_marc
2228 $result->{_disambiguate($table, $field)} =
2229 get_koha_field_from_marc($table,$field,$record,$frameworkcode);
2231 Internal function to map data from the MARC record to a specific non-MARC field.
2232 FIXME: this is meant to replace TransformMarcToKohaOneField after more testing.
2236 sub get_koha_field_from_marc {
2237 my ( $koha_table, $koha_column, $record, $frameworkcode ) = @_;
2238 my ( $tagfield, $subfield ) = GetMarcFromKohaField( $koha_table . '.' . $koha_column, $frameworkcode );
2240 foreach my $field ( $record->field($tagfield) ) {
2241 if ( $field->tag() < 10 ) {
2243 $kohafield .= " | " . $field->data();
2245 $kohafield = $field->data();
2248 if ( $field->subfields ) {
2249 my @subfields = $field->subfields();
2250 foreach my $subfieldcount ( 0 .. $#subfields ) {
2251 if ( $subfields[$subfieldcount][0] eq $subfield ) {
2253 $kohafield .= " | " . $subfields[$subfieldcount][1];
2255 $kohafield = $subfields[$subfieldcount][1];
2265 =head2 TransformMarcToKohaOneField
2267 $result = TransformMarcToKohaOneField( $kohatable, $kohafield, $record, $result, $frameworkcode )
2271 sub TransformMarcToKohaOneField {
2273 # FIXME ? if a field has a repeatable subfield that is used in old-db,
2274 # only the 1st will be retrieved...
2275 my ( $kohatable, $kohafield, $record, $result, $frameworkcode ) = @_;
2277 my ( $tagfield, $subfield ) = GetMarcFromKohaField( $kohatable . "." . $kohafield, $frameworkcode );
2278 foreach my $field ( $record->field($tagfield) ) {
2279 if ( $field->tag() < 10 ) {
2280 if ( $result->{$kohafield} ) {
2281 $result->{$kohafield} .= " | " . $field->data();
2283 $result->{$kohafield} = $field->data();
2286 if ( $field->subfields ) {
2287 my @subfields = $field->subfields();
2288 foreach my $subfieldcount ( 0 .. $#subfields ) {
2289 if ( $subfields[$subfieldcount][0] eq $subfield ) {
2290 if ( $result->{$kohafield} ) {
2291 $result->{$kohafield} .= " | " . $subfields[$subfieldcount][1];
2293 $result->{$kohafield} = $subfields[$subfieldcount][1];
2303 =head1 OTHER FUNCTIONS
2306 =head2 PrepareItemrecordDisplay
2308 PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2310 Returns a hash with all the fields for Display a given item data in a template
2312 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2316 sub PrepareItemrecordDisplay {
2318 my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2320 my $dbh = C4::Context->dbh;
2321 $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2322 my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2323 my $tagslib = &GetMarcStructure( 1, $frameworkcode );
2325 # return nothing if we don't have found an existing framework.
2326 return "" unless $tagslib;
2327 my $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum ) if ($itemnum);
2329 my $authorised_values_sth = $dbh->prepare( "SELECT authorised_value,lib FROM authorised_values WHERE category=? ORDER BY lib" );
2330 foreach my $tag ( sort keys %{$tagslib} ) {
2331 my $previous_tag = '';
2334 # loop through each subfield
2336 foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2337 next if ( subfield_is_koha_internal_p($subfield) );
2338 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2340 $subfield_data{tag} = $tag;
2341 $subfield_data{subfield} = $subfield;
2342 $subfield_data{countsubfield} = $cntsubf++;
2343 $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2345 # $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2346 $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
2347 $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
2348 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2349 $subfield_data{hidden} = "display:none"
2350 if $tagslib->{$tag}->{$subfield}->{hidden};
2351 my ( $x, $defaultvalue );
2353 ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2355 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2356 if ( !defined $defaultvalue ) {
2357 $defaultvalue = q||;
2359 $defaultvalue =~ s/"/"/g;
2361 # search for itemcallnumber if applicable
2362 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2363 && C4::Context->preference('itemcallnumber') ) {
2364 my $CNtag = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2365 my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2366 my $temp = $itemrecord->field($CNtag) if ($itemrecord);
2368 $defaultvalue = $temp->subfield($CNsubfield);
2371 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2373 && $defaultvalues->{'callnumber'} ) {
2374 my $temp = $itemrecord->field($subfield) if ($itemrecord);
2376 $defaultvalue = $defaultvalues->{'callnumber'} if $defaultvalues;
2379 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2381 && $defaultvalues->{'branchcode'} ) {
2382 my $temp = $itemrecord->field($subfield) if ($itemrecord);
2384 $defaultvalue = $defaultvalues->{branchcode} if $defaultvalues;
2387 if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2389 && $defaultvalues->{'location'} ) {
2390 my $temp = $itemrecord->field($subfield) if ($itemrecord);
2392 $defaultvalue = $defaultvalues->{location} if $defaultvalues;
2395 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2396 my @authorised_values;
2399 # builds list, depending on authorised value...
2401 if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2402 if ( ( C4::Context->preference("IndependantBranches") )
2403 && ( C4::Context->userenv->{flags} % 2 != 1 ) ) {
2404 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2405 $sth->execute( C4::Context->userenv->{branch} );
2406 push @authorised_values, ""
2407 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2408 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2409 push @authorised_values, $branchcode;
2410 $authorised_lib{$branchcode} = $branchname;
2413 my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2415 push @authorised_values, ""
2416 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2417 while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2418 push @authorised_values, $branchcode;
2419 $authorised_lib{$branchcode} = $branchname;
2424 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2425 my $sth = $dbh->prepare( "SELECT itemtype,description FROM itemtypes ORDER BY description" );
2427 push @authorised_values, ""
2428 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2429 while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
2430 push @authorised_values, $itemtype;
2431 $authorised_lib{$itemtype} = $description;
2434 } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2435 push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2437 my $class_sources = GetClassSources();
2438 my $default_source = C4::Context->preference("DefaultClassificationSource");
2440 foreach my $class_source (sort keys %$class_sources) {
2441 next unless $class_sources->{$class_source}->{'used'} or
2442 ($class_source eq $default_source);
2443 push @authorised_values, $class_source;
2444 $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2447 #---- "true" authorised value
2449 $authorised_values_sth->execute( $tagslib->{$tag}->{$subfield}->{authorised_value} );
2450 push @authorised_values, ""
2451 unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2452 while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2453 push @authorised_values, $value;
2454 $authorised_lib{$value} = $lib;
2457 $subfield_data{marc_value} = CGI::scrolling_list(
2458 -name => 'field_value',
2459 -values => \@authorised_values,
2460 -default => "$defaultvalue",
2461 -labels => \%authorised_lib,
2466 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2468 my $plugin = C4::Context->intranetdir . "/cataloguing/value_builder/" . $tagslib->{$tag}->{$subfield}->{'value_builder'};
2471 my $extended_param = plugin_parameters( $dbh, $temp, $tagslib, $subfield_data{id}, undef );
2472 my ( $function_name, $javascript ) = plugin_javascript( $dbh, $temp, $tagslib, $subfield_data{id}, undef );
2473 $subfield_data{random} = int(rand(1000000)); # why do we need 2 different randoms?
2474 my $index_subfield = int(rand(1000000));
2475 $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".$index_subfield;
2476 $subfield_data{marc_value} = qq[<input tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255"
2477 onfocus="Focus$function_name($subfield_data{random}, '$subfield_data{id}');"
2478 onblur=" Blur$function_name($subfield_data{random}, '$subfield_data{id}');" />
2479 <a href="#" class="buttonDot" onclick="Clic$function_name('$subfield_data{id}'); return false;" title="Tag Editor">...</a>
2482 warn "Plugin Failed: $plugin";
2483 $subfield_data{marc_value} = qq(<input tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255" />); # supply default input form
2486 elsif ( $tag eq '' ) { # it's an hidden field
2487 $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255" value="$defaultvalue" />);
2489 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
2490 $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255" value="$defaultvalue" />);
2492 elsif ( length($defaultvalue) > 100
2493 or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2494 300 <= $tag && $tag < 400 && $subfield eq 'a' )
2495 or (C4::Context->preference("marcflavour") eq "MARC21" and
2496 500 <= $tag && $tag < 600 )
2498 # oversize field (textarea)
2499 $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="67" maxlength="255">$defaultvalue</textarea>\n");
2501 $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
2503 push( @loop_data, \%subfield_data );
2507 my $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield )
2508 if ( $itemrecord && $itemrecord->field($itemtagfield) );
2510 'itemtagfield' => $itemtagfield,
2511 'itemtagsubfield' => $itemtagsubfield,
2512 'itemnumber' => $itemnumber,
2513 'iteminformation' => \@loop_data
2520 # true ModZebra commented until indexdata fixes zebraDB crashes (it seems they occur on multiple updates
2522 # replaced by a zebraqueue table, that is filled with ModZebra to run.
2523 # the table is emptied by misc/cronjobs/zebraqueue_start.pl script
2524 # =head2 ModZebrafiles
2526 # &ModZebrafiles( $dbh, $biblionumber, $record, $folder, $server );
2530 # sub ModZebrafiles {
2532 # my ( $dbh, $biblionumber, $record, $folder, $server ) = @_;
2536 # C4::Context->zebraconfig($server)->{directory} . "/" . $folder . "/";
2537 # unless ( opendir( DIR, "$zebradir" ) ) {
2538 # warn "$zebradir not found";
2542 # my $filename = $zebradir . $biblionumber;
2545 # open( OUTPUT, ">", $filename . ".xml" );
2546 # print OUTPUT $record;
2553 ModZebra( $biblionumber, $op, $server, $oldRecord, $newRecord );
2555 $biblionumber is the biblionumber we want to index
2557 $op is specialUpdate or delete, and is used to know what we want to do
2559 $server is the server that we want to update
2561 $oldRecord is the MARC::Record containing the previous version of the record. This is used only when
2562 NoZebra=1, as NoZebra indexing needs to know the previous version of a record in order to
2565 $newRecord is the MARC::Record containing the new record. It is usefull only when NoZebra=1, and is used to know what to add to the nozebra database. (the record in mySQL being, if it exist, the previous record, the one just before the modif. We need both : the previous and the new one.
2570 ###Accepts a $server variable thus we can use it for biblios authorities or other zebra dbs
2571 my ( $biblionumber, $op, $server, $oldRecord, $newRecord ) = @_;
2572 my $dbh = C4::Context->dbh;
2574 # true ModZebra commented until indexdata fixes zebraDB crashes (it seems they occur on multiple updates
2576 # replaced by a zebraqueue table, that is filled with ModZebra to run.
2577 # the table is emptied by misc/cronjobs/zebraqueue_start.pl script
2579 if ( C4::Context->preference("NoZebra") ) {
2581 # lock the nozebra table : we will read index lines, update them in Perl process
2582 # and write everything in 1 transaction.
2583 # lock the table to avoid someone else overwriting what we are doing
2584 $dbh->do('LOCK TABLES nozebra WRITE,biblio WRITE,biblioitems WRITE, systempreferences WRITE, auth_types WRITE, auth_header WRITE, auth_subfield_structure READ');
2585 my %result; # the result hash that will be built by deletion / add, and written on mySQL at the end, to improve speed
2586 if ( $op eq 'specialUpdate' ) {
2588 # OK, we have to add or update the record
2589 # 1st delete (virtually, in indexes), if record actually exists
2591 %result = _DelBiblioNoZebra( $biblionumber, $oldRecord, $server );
2594 # ... add the record
2595 %result = _AddBiblioNoZebra( $biblionumber, $newRecord, $server, %result );
2598 # it's a deletion, delete the record...
2599 # warn "DELETE the record $biblionumber on $server".$record->as_formatted;
2600 %result = _DelBiblioNoZebra( $biblionumber, $oldRecord, $server );
2603 # ok, now update the database...
2604 my $sth = $dbh->prepare("UPDATE nozebra SET biblionumbers=? WHERE server=? AND indexname=? AND value=?");
2605 foreach my $key ( keys %result ) {
2606 foreach my $index ( keys %{ $result{$key} } ) {
2607 $sth->execute( $result{$key}->{$index}, $server, $key, $index );
2610 $dbh->do('UNLOCK TABLES');
2614 # we use zebra, just fill zebraqueue table
2616 my $check_sql = "SELECT COUNT(*) FROM zebraqueue
2618 AND biblio_auth_number = ?
2621 my $check_sth = $dbh->prepare_cached($check_sql);
2622 $check_sth->execute( $server, $biblionumber, $op );
2623 my ($count) = $check_sth->fetchrow_array;
2624 $check_sth->finish();
2625 if ( $count == 0 ) {
2626 my $sth = $dbh->prepare("INSERT INTO zebraqueue (biblio_auth_number,server,operation) VALUES(?,?,?)");
2627 $sth->execute( $biblionumber, $server, $op );
2633 =head2 GetNoZebraIndexes
2635 %indexes = GetNoZebraIndexes;
2637 return the data from NoZebraIndexes syspref.
2641 sub GetNoZebraIndexes {
2642 my $no_zebra_indexes = C4::Context->preference('NoZebraIndexes');
2644 INDEX: foreach my $line ( split /['"],[\n\r]*/, $no_zebra_indexes ) {
2645 $line =~ /(.*)=>(.*)/;
2646 my $index = $1; # initial ' or " is removed afterwards
2648 $index =~ s/'|"|\s//g;
2649 $fields =~ s/'|"|\s//g;
2650 $indexes{$index} = $fields;
2655 =head2 EmbedItemsInMarcBiblio
2657 EmbedItemsInMarcBiblio($marc, $biblionumber);
2659 Given a MARC::Record object containing a bib record,
2660 modify it to include the items attached to it as 9XX
2661 per the bib's MARC framework.
2665 sub EmbedItemsInMarcBiblio {
2666 my ($marc, $biblionumber) = @_;
2668 my $frameworkcode = GetFrameworkCode($biblionumber);
2669 _strip_item_fields($marc, $frameworkcode);
2671 # ... and embed the current items
2672 my $dbh = C4::Context->dbh;
2673 my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
2674 $sth->execute($biblionumber);
2676 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2677 while (my ($itemnumber) = $sth->fetchrow_array) {
2678 my $item_marc = C4::Items::GetMarcItem($biblionumber, $itemnumber);
2679 push @item_fields, $item_marc->field($itemtag);
2681 $marc->insert_fields_ordered(@item_fields);
2684 =head1 INTERNAL FUNCTIONS
2686 =head2 _DelBiblioNoZebra($biblionumber,$record,$server);
2688 function to delete a biblio in NoZebra indexes
2689 This function does NOT delete anything in database : it reads all the indexes entries
2690 that have to be deleted & delete them in the hash
2692 The SQL part is done either :
2693 - after the Add if we are modifying a biblio (delete + add again)
2694 - immediatly after this sub if we are doing a true deletion.
2696 $server can be 'biblioserver' or 'authorityserver' : it indexes biblios or authorities (in the same table, $server being part of the table itself
2700 sub _DelBiblioNoZebra {
2701 my ( $biblionumber, $record, $server ) = @_;
2704 my $dbh = C4::Context->dbh;
2709 if ( $server eq 'biblioserver' ) {
2710 %index = GetNoZebraIndexes;
2712 # get title of the record (to store the 10 first letters with the index)
2713 my ( $titletag, $titlesubfield ) = GetMarcFromKohaField( 'biblio.title', '' ); # FIXME: should be GetFrameworkCode($biblionumber) ??
2714 $title = lc( $record->subfield( $titletag, $titlesubfield ) );
2717 # for authorities, the "title" is the $a mainentry
2718 my ( $auth_type_tag, $auth_type_sf ) = C4::AuthoritiesMarc::get_auth_type_location();
2719 my $authref = C4::AuthoritiesMarc::GetAuthType( $record->subfield( $auth_type_tag, $auth_type_sf ) );
2720 warn "ERROR : authtype undefined for " . $record->as_formatted unless $authref;
2721 $title = $record->subfield( $authref->{auth_tag_to_report}, 'a' );
2722 $index{'mainmainentry'} = $authref->{'auth_tag_to_report'} . 'a';
2723 $index{'mainentry'} = $authref->{'auth_tag_to_report'} . '*';
2724 $index{'auth_type'} = "${auth_type_tag}${auth_type_sf}";
2729 # remove blancks comma (that could cause problem when decoding the string for CQL retrieval) and regexp specific values
2730 $title =~ s/ |,|;|\[|\]|\(|\)|\*|-|'|=//g;
2732 # limit to 10 char, should be enough, and limit the DB size
2733 $title = substr( $title, 0, 10 );
2736 my $sth2 = $dbh->prepare('SELECT biblionumbers FROM nozebra WHERE server=? AND indexname=? AND value=?');
2737 foreach my $field ( $record->fields() ) {
2739 #parse each subfield
2740 next if $field->tag < 10;
2741 foreach my $subfield ( $field->subfields() ) {
2742 my $tag = $field->tag();
2743 my $subfieldcode = $subfield->[0];
2746 # check each index to see if the subfield is stored somewhere
2747 # otherwise, store it in __RAW__ index
2748 foreach my $key ( keys %index ) {
2750 # warn "examining $key index : ".$index{$key}." for $tag $subfieldcode";
2751 if ( $index{$key} =~ /$tag\*/ or $index{$key} =~ /$tag$subfieldcode/ ) {
2753 my $line = lc $subfield->[1];
2755 # remove meaningless value in the field...
2756 $line =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|<|>|&|\+|\*|\/|=|:/ /g;
2758 # ... and split in words
2759 foreach ( split / /, $line ) {
2760 next unless $_; # skip empty values (multiple spaces)
2761 # if the entry is already here, do nothing, the biblionumber has already be removed
2762 unless ( defined( $result{$key}->{$_} ) && ( $result{$key}->{$_} =~ /$biblionumber,$title\-(\d);/ ) ) {
2764 # get the index value if it exist in the nozebra table and remove the entry, otherwise, do nothing
2765 $sth2->execute( $server, $key, $_ );
2766 my $existing_biblionumbers = $sth2->fetchrow;
2769 if ($existing_biblionumbers) {
2771 # warn " existing for $key $_: $existing_biblionumbers";
2772 $result{$key}->{$_} = $existing_biblionumbers;
2773 $result{$key}->{$_} =~ s/$biblionumber,$title\-(\d);//;
2780 # the subfield is not indexed, store it in __RAW__ index anyway
2782 my $line = lc $subfield->[1];
2783 $line =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|<|>|&|\+|\*|\/|=|:/ /g;
2785 # ... and split in words
2786 foreach ( split / /, $line ) {
2787 next unless $_; # skip empty values (multiple spaces)
2788 # if the entry is already here, do nothing, the biblionumber has already be removed
2789 unless ( $result{'__RAW__'}->{$_} =~ /$biblionumber,$title\-(\d);/ ) {
2791 # get the index value if it exist in the nozebra table and remove the entry, otherwise, do nothing
2792 $sth2->execute( $server, '__RAW__', $_ );
2793 my $existing_biblionumbers = $sth2->fetchrow;
2796 if ($existing_biblionumbers) {
2797 $result{'__RAW__'}->{$_} = $existing_biblionumbers;
2798 $result{'__RAW__'}->{$_} =~ s/$biblionumber,$title\-(\d);//;
2808 =head2 _AddBiblioNoZebra
2810 _AddBiblioNoZebra($biblionumber, $record, $server, %result);
2812 function to add a biblio in NoZebra indexes
2816 sub _AddBiblioNoZebra {
2817 my ( $biblionumber, $record, $server, %result ) = @_;
2818 my $dbh = C4::Context->dbh;
2823 if ( $server eq 'biblioserver' ) {
2824 %index = GetNoZebraIndexes;
2826 # get title of the record (to store the 10 first letters with the index)
2827 my ( $titletag, $titlesubfield ) = GetMarcFromKohaField( 'biblio.title', '' ); # FIXME: should be GetFrameworkCode($biblionumber) ??
2828 $title = lc( $record->subfield( $titletag, $titlesubfield ) );
2831 # warn "server : $server";
2832 # for authorities, the "title" is the $a mainentry
2833 my ( $auth_type_tag, $auth_type_sf ) = C4::AuthoritiesMarc::get_auth_type_location();
2834 my $authref = C4::AuthoritiesMarc::GetAuthType( $record->subfield( $auth_type_tag, $auth_type_sf ) );
2835 warn "ERROR : authtype undefined for " . $record->as_formatted unless $authref;
2836 $title = $record->subfield( $authref->{auth_tag_to_report}, 'a' );
2837 $index{'mainmainentry'} = $authref->{auth_tag_to_report} . 'a';
2838 $index{'mainentry'} = $authref->{auth_tag_to_report} . '*';
2839 $index{'auth_type'} = "${auth_type_tag}${auth_type_sf}";
2842 # remove blancks comma (that could cause problem when decoding the string for CQL retrieval) and regexp specific values
2843 $title =~ s/ |\.|,|;|\[|\]|\(|\)|\*|-|'|:|=|\r|\n//g;
2845 # limit to 10 char, should be enough, and limit the DB size
2846 $title = substr( $title, 0, 10 );
2849 my $sth2 = $dbh->prepare('SELECT biblionumbers FROM nozebra WHERE server=? AND indexname=? AND value=?');
2850 foreach my $field ( $record->fields() ) {
2852 #parse each subfield
2853 ###FIXME: impossible to index a 001-009 value with NoZebra
2854 next if $field->tag < 10;
2855 foreach my $subfield ( $field->subfields() ) {
2856 my $tag = $field->tag();
2857 my $subfieldcode = $subfield->[0];
2860 # warn "INDEXING :".$subfield->[1];
2861 # check each index to see if the subfield is stored somewhere
2862 # otherwise, store it in __RAW__ index
2863 foreach my $key ( keys %index ) {
2865 # warn "examining $key index : ".$index{$key}." for $tag $subfieldcode";
2866 if ( $index{$key} =~ /$tag\*/ or $index{$key} =~ /$tag$subfieldcode/ ) {
2868 my $line = lc $subfield->[1];
2870 # remove meaningless value in the field...
2871 $line =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|<|>|&|\+|\*|\/|=|:|\r|\n/ /g;
2873 # ... and split in words
2874 foreach ( split / /, $line ) {
2875 next unless $_; # skip empty values (multiple spaces)
2876 # if the entry is already here, improve weight
2878 # warn "managing $_";
2879 if ( exists $result{$key}->{$_} && $result{$key}->{"$_"} =~ /$biblionumber,\Q$title\E\-(\d+);/ ) {
2880 my $weight = $1 + 1;
2881 $result{$key}->{"$_"} =~ s/$biblionumber,\Q$title\E\-(\d+);//g;
2882 $result{$key}->{"$_"} .= "$biblionumber,$title-$weight;";
2885 # get the value if it exist in the nozebra table, otherwise, create it
2886 $sth2->execute( $server, $key, $_ );
2887 my $existing_biblionumbers = $sth2->fetchrow;
2890 if ($existing_biblionumbers) {
2891 $result{$key}->{"$_"} = $existing_biblionumbers;
2892 my $weight = defined $1 ? $1 + 1 : 1;
2893 $result{$key}->{"$_"} =~ s/$biblionumber,\Q$title\E\-(\d+);//g;
2894 $result{$key}->{"$_"} .= "$biblionumber,$title-$weight;";
2896 # create a new ligne for this entry
2899 # warn "INSERT : $server / $key / $_";
2900 $dbh->do( 'INSERT INTO nozebra SET server=' . $dbh->quote($server) . ', indexname=' . $dbh->quote($key) . ',value=' . $dbh->quote($_) );
2901 $result{$key}->{"$_"} .= "$biblionumber,$title-1;";
2908 # the subfield is not indexed, store it in __RAW__ index anyway
2910 my $line = lc $subfield->[1];
2911 $line =~ s/-|\.|\?|,|;|!|'|\(|\)|\[|\]|{|}|"|<|>|&|\+|\*|\/|=|:|\r|\n/ /g;
2913 # ... and split in words
2914 foreach ( split / /, $line ) {
2915 next unless $_; # skip empty values (multiple spaces)
2916 # if the entry is already here, improve weight
2917 my $tmpstr = $result{'__RAW__'}->{"$_"} || "";
2918 if ( $tmpstr =~ /$biblionumber,\Q$title\E\-(\d+);/ ) {
2919 my $weight = $1 + 1;
2920 $result{'__RAW__'}->{"$_"} =~ s/$biblionumber,\Q$title\E\-(\d+);//;
2921 $result{'__RAW__'}->{"$_"} .= "$biblionumber,$title-$weight;";
2924 # get the value if it exist in the nozebra table, otherwise, create it
2925 $sth2->execute( $server, '__RAW__', $_ );
2926 my $existing_biblionumbers = $sth2->fetchrow;
2929 if ($existing_biblionumbers) {
2930 $result{'__RAW__'}->{"$_"} = $existing_biblionumbers;
2931 my $weight = ( $1 ? $1 : 0 ) + 1;
2932 $result{'__RAW__'}->{"$_"} =~ s/$biblionumber,\Q$title\E\-(\d+);//;
2933 $result{'__RAW__'}->{"$_"} .= "$biblionumber,$title-$weight;";
2935 # create a new ligne for this entry
2937 $dbh->do( 'INSERT INTO nozebra SET server=' . $dbh->quote($server) . ', indexname="__RAW__",value=' . $dbh->quote($_) );
2938 $result{'__RAW__'}->{"$_"} .= "$biblionumber,$title-1;";
2950 ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2952 Find the given $subfield in the given $tag in the given
2953 MARC::Record $record. If the subfield is found, returns
2954 the (indicators, value) pair; otherwise, (undef, undef) is
2958 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2959 I suggest we export it from this module.
2964 my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2967 if ( $tagfield < 10 ) {
2968 if ( $record->field($tagfield) ) {
2969 push @result, $record->field($tagfield)->data();
2974 foreach my $field ( $record->field($tagfield) ) {
2975 my @subfields = $field->subfields();
2976 foreach my $subfield (@subfields) {
2977 if ( @$subfield[0] eq $insubfield ) {
2978 push @result, @$subfield[1];
2979 $indicator = $field->indicator(1) . $field->indicator(2);
2984 return ( $indicator, @result );
2987 =head2 _koha_marc_update_bib_ids
2990 _koha_marc_update_bib_ids($record, $frameworkcode, $biblionumber, $biblioitemnumber);
2992 Internal function to add or update biblionumber and biblioitemnumber to
2997 sub _koha_marc_update_bib_ids {
2998 my ( $record, $frameworkcode, $biblionumber, $biblioitemnumber ) = @_;
3000 # we must add bibnum and bibitemnum in MARC::Record...
3001 # we build the new field with biblionumber and biblioitemnumber
3002 # we drop the original field
3003 # we add the new builded field.
3004 my ( $biblio_tag, $biblio_subfield ) = GetMarcFromKohaField( "biblio.biblionumber", $frameworkcode );
3005 my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.biblioitemnumber", $frameworkcode );
3007 if ( $biblio_tag != $biblioitem_tag ) {
3009 # biblionumber & biblioitemnumber are in different fields
3011 # deal with biblionumber
3012 my ( $new_field, $old_field );
3013 if ( $biblio_tag < 10 ) {
3014 $new_field = MARC::Field->new( $biblio_tag, $biblionumber );
3016 $new_field = MARC::Field->new( $biblio_tag, '', '', "$biblio_subfield" => $biblionumber );
3019 # drop old field and create new one...
3020 $old_field = $record->field($biblio_tag);
3021 $record->delete_field($old_field) if $old_field;
3022 $record->append_fields($new_field);
3024 # deal with biblioitemnumber
3025 if ( $biblioitem_tag < 10 ) {
3026 $new_field = MARC::Field->new( $biblioitem_tag, $biblioitemnumber, );
3028 $new_field = MARC::Field->new( $biblioitem_tag, '', '', "$biblioitem_subfield" => $biblioitemnumber, );
3031 # drop old field and create new one...
3032 $old_field = $record->field($biblioitem_tag);
3033 $record->delete_field($old_field) if $old_field;
3034 $record->insert_fields_ordered($new_field);
3038 # biblionumber & biblioitemnumber are in the same field (can't be <10 as fields <10 have only 1 value)
3039 my $new_field = MARC::Field->new(
3040 $biblio_tag, '', '',
3041 "$biblio_subfield" => $biblionumber,
3042 "$biblioitem_subfield" => $biblioitemnumber
3045 # drop old field and create new one...
3046 my $old_field = $record->field($biblio_tag);
3047 $record->delete_field($old_field) if $old_field;
3048 $record->insert_fields_ordered($new_field);
3052 =head2 _koha_marc_update_biblioitem_cn_sort
3054 _koha_marc_update_biblioitem_cn_sort($marc, $biblioitem, $frameworkcode);
3056 Given a MARC bib record and the biblioitem hash, update the
3057 subfield that contains a copy of the value of biblioitems.cn_sort.
3061 sub _koha_marc_update_biblioitem_cn_sort {
3063 my $biblioitem = shift;
3064 my $frameworkcode = shift;
3066 my ( $biblioitem_tag, $biblioitem_subfield ) = GetMarcFromKohaField( "biblioitems.cn_sort", $frameworkcode );
3067 return unless $biblioitem_tag;
3069 my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
3071 if ( my $field = $marc->field($biblioitem_tag) ) {
3072 $field->delete_subfield( code => $biblioitem_subfield );
3073 if ( $cn_sort ne '' ) {
3074 $field->add_subfields( $biblioitem_subfield => $cn_sort );
3078 # if we get here, no biblioitem tag is present in the MARC record, so
3079 # we'll create it if $cn_sort is not empty -- this would be
3080 # an odd combination of events, however
3082 $marc->insert_grouped_field( MARC::Field->new( $biblioitem_tag, ' ', ' ', $biblioitem_subfield => $cn_sort ) );
3087 =head2 _koha_add_biblio
3089 my ($biblionumber,$error) = _koha_add_biblio($dbh,$biblioitem);
3091 Internal function to add a biblio ($biblio is a hash with the values)
3095 sub _koha_add_biblio {
3096 my ( $dbh, $biblio, $frameworkcode ) = @_;
3100 # set the series flag
3101 unless (defined $biblio->{'serial'}){
3102 $biblio->{'serial'} = 0;
3103 if ( $biblio->{'seriestitle'} ) { $biblio->{'serial'} = 1 }
3106 my $query = "INSERT INTO biblio
3107 SET frameworkcode = ?,
3118 my $sth = $dbh->prepare($query);
3120 $frameworkcode, $biblio->{'author'}, $biblio->{'title'}, $biblio->{'unititle'}, $biblio->{'notes'},
3121 $biblio->{'serial'}, $biblio->{'seriestitle'}, $biblio->{'copyrightdate'}, $biblio->{'abstract'}
3124 my $biblionumber = $dbh->{'mysql_insertid'};
3125 if ( $dbh->errstr ) {
3126 $error .= "ERROR in _koha_add_biblio $query" . $dbh->errstr;
3132 #warn "LEAVING _koha_add_biblio: ".$biblionumber."\n";
3133 return ( $biblionumber, $error );
3136 =head2 _koha_modify_biblio
3138 my ($biblionumber,$error) == _koha_modify_biblio($dbh,$biblio,$frameworkcode);
3140 Internal function for updating the biblio table
3144 sub _koha_modify_biblio {
3145 my ( $dbh, $biblio, $frameworkcode ) = @_;
3150 SET frameworkcode = ?,
3159 WHERE biblionumber = ?
3162 my $sth = $dbh->prepare($query);
3165 $frameworkcode, $biblio->{'author'}, $biblio->{'title'}, $biblio->{'unititle'}, $biblio->{'notes'},
3166 $biblio->{'serial'}, $biblio->{'seriestitle'}, $biblio->{'copyrightdate'}, $biblio->{'abstract'}, $biblio->{'biblionumber'}
3167 ) if $biblio->{'biblionumber'};
3169 if ( $dbh->errstr || !$biblio->{'biblionumber'} ) {
3170 $error .= "ERROR in _koha_modify_biblio $query" . $dbh->errstr;
3173 return ( $biblio->{'biblionumber'}, $error );
3176 =head2 _koha_modify_biblioitem_nonmarc
3178 my ($biblioitemnumber,$error) = _koha_modify_biblioitem_nonmarc( $dbh, $biblioitem );
3180 Updates biblioitems row except for marc and marcxml, which should be changed
3185 sub _koha_modify_biblioitem_nonmarc {
3186 my ( $dbh, $biblioitem ) = @_;
3189 # re-calculate the cn_sort, it may have changed
3190 my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
3192 my $query = "UPDATE biblioitems
3193 SET biblionumber = ?,
3199 publicationyear = ?,
3203 collectiontitle = ?,
3205 collectionvolume= ?,
3206 editionstatement= ?,
3207 editionresponsibility = ?,
3221 where biblioitemnumber = ?
3223 my $sth = $dbh->prepare($query);
3225 $biblioitem->{'biblionumber'}, $biblioitem->{'volume'}, $biblioitem->{'number'}, $biblioitem->{'itemtype'},
3226 $biblioitem->{'isbn'}, $biblioitem->{'issn'}, $biblioitem->{'publicationyear'}, $biblioitem->{'publishercode'},
3227 $biblioitem->{'volumedate'}, $biblioitem->{'volumedesc'}, $biblioitem->{'collectiontitle'}, $biblioitem->{'collectionissn'},
3228 $biblioitem->{'collectionvolume'}, $biblioitem->{'editionstatement'}, $biblioitem->{'editionresponsibility'}, $biblioitem->{'illus'},
3229 $biblioitem->{'pages'}, $biblioitem->{'bnotes'}, $biblioitem->{'size'}, $biblioitem->{'place'},
3230 $biblioitem->{'lccn'}, $biblioitem->{'url'}, $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'},
3231 $biblioitem->{'cn_item'}, $biblioitem->{'cn_suffix'}, $cn_sort, $biblioitem->{'totalissues'},
3232 $biblioitem->{'biblioitemnumber'}
3234 if ( $dbh->errstr ) {
3235 $error .= "ERROR in _koha_modify_biblioitem_nonmarc $query" . $dbh->errstr;
3238 return ( $biblioitem->{'biblioitemnumber'}, $error );
3241 =head2 _koha_add_biblioitem
3243 my ($biblioitemnumber,$error) = _koha_add_biblioitem( $dbh, $biblioitem );
3245 Internal function to add a biblioitem
3249 sub _koha_add_biblioitem {
3250 my ( $dbh, $biblioitem ) = @_;
3253 my ($cn_sort) = GetClassSort( $biblioitem->{'biblioitems.cn_source'}, $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'} );
3254 my $query = "INSERT INTO biblioitems SET
3261 publicationyear = ?,
3265 collectiontitle = ?,
3267 collectionvolume= ?,
3268 editionstatement= ?,
3269 editionresponsibility = ?,
3285 my $sth = $dbh->prepare($query);
3287 $biblioitem->{'biblionumber'}, $biblioitem->{'volume'}, $biblioitem->{'number'}, $biblioitem->{'itemtype'},
3288 $biblioitem->{'isbn'}, $biblioitem->{'issn'}, $biblioitem->{'publicationyear'}, $biblioitem->{'publishercode'},
3289 $biblioitem->{'volumedate'}, $biblioitem->{'volumedesc'}, $biblioitem->{'collectiontitle'}, $biblioitem->{'collectionissn'},
3290 $biblioitem->{'collectionvolume'}, $biblioitem->{'editionstatement'}, $biblioitem->{'editionresponsibility'}, $biblioitem->{'illus'},
3291 $biblioitem->{'pages'}, $biblioitem->{'bnotes'}, $biblioitem->{'size'}, $biblioitem->{'place'},
3292 $biblioitem->{'lccn'}, $biblioitem->{'marc'}, $biblioitem->{'url'}, $biblioitem->{'biblioitems.cn_source'},
3293 $biblioitem->{'cn_class'}, $biblioitem->{'cn_item'}, $biblioitem->{'cn_suffix'}, $cn_sort,
3294 $biblioitem->{'totalissues'}
3296 my $bibitemnum = $dbh->{'mysql_insertid'};
3298 if ( $dbh->errstr ) {
3299 $error .= "ERROR in _koha_add_biblioitem $query" . $dbh->errstr;
3303 return ( $bibitemnum, $error );
3306 =head2 _koha_delete_biblio
3308 $error = _koha_delete_biblio($dbh,$biblionumber);
3310 Internal sub for deleting from biblio table -- also saves to deletedbiblio
3312 C<$dbh> - the database handle
3314 C<$biblionumber> - the biblionumber of the biblio to be deleted
3318 # FIXME: add error handling
3320 sub _koha_delete_biblio {
3321 my ( $dbh, $biblionumber ) = @_;
3323 # get all the data for this biblio
3324 my $sth = $dbh->prepare("SELECT * FROM biblio WHERE biblionumber=?");
3325 $sth->execute($biblionumber);
3327 if ( my $data = $sth->fetchrow_hashref ) {
3329 # save the record in deletedbiblio
3330 # find the fields to save
3331 my $query = "INSERT INTO deletedbiblio SET ";
3333 foreach my $temp ( keys %$data ) {
3334 $query .= "$temp = ?,";
3335 push( @bind, $data->{$temp} );
3338 # replace the last , by ",?)"
3340 my $bkup_sth = $dbh->prepare($query);
3341 $bkup_sth->execute(@bind);
3345 my $del_sth = $dbh->prepare("DELETE FROM biblio WHERE biblionumber=?");
3346 $del_sth->execute($biblionumber);
3353 =head2 _koha_delete_biblioitems
3355 $error = _koha_delete_biblioitems($dbh,$biblioitemnumber);
3357 Internal sub for deleting from biblioitems table -- also saves to deletedbiblioitems
3359 C<$dbh> - the database handle
3360 C<$biblionumber> - the biblioitemnumber of the biblioitem to be deleted
3364 # FIXME: add error handling
3366 sub _koha_delete_biblioitems {
3367 my ( $dbh, $biblioitemnumber ) = @_;
3369 # get all the data for this biblioitem
3370 my $sth = $dbh->prepare("SELECT * FROM biblioitems WHERE biblioitemnumber=?");
3371 $sth->execute($biblioitemnumber);
3373 if ( my $data = $sth->fetchrow_hashref ) {
3375 # save the record in deletedbiblioitems
3376 # find the fields to save
3377 my $query = "INSERT INTO deletedbiblioitems SET ";
3379 foreach my $temp ( keys %$data ) {
3380 $query .= "$temp = ?,";
3381 push( @bind, $data->{$temp} );
3384 # replace the last , by ",?)"
3386 my $bkup_sth = $dbh->prepare($query);
3387 $bkup_sth->execute(@bind);
3390 # delete the biblioitem
3391 my $del_sth = $dbh->prepare("DELETE FROM biblioitems WHERE biblioitemnumber=?");
3392 $del_sth->execute($biblioitemnumber);
3399 =head1 UNEXPORTED FUNCTIONS
3401 =head2 ModBiblioMarc
3403 &ModBiblioMarc($newrec,$biblionumber,$frameworkcode);
3405 Add MARC data for a biblio to koha
3407 Function exported, but should NOT be used, unless you really know what you're doing
3413 # pass the MARC::Record to this function, and it will create the records in the marc field
3414 my ( $record, $biblionumber, $frameworkcode ) = @_;
3415 my $dbh = C4::Context->dbh;
3416 my @fields = $record->fields();
3417 if ( !$frameworkcode ) {
3418 $frameworkcode = "";
3420 my $sth = $dbh->prepare("UPDATE biblio SET frameworkcode=? WHERE biblionumber=?");
3421 $sth->execute( $frameworkcode, $biblionumber );
3423 my $encoding = C4::Context->preference("marcflavour");
3425 # deal with UNIMARC field 100 (encoding) : create it if needed & set encoding to unicode
3426 if ( $encoding eq "UNIMARC" ) {
3427 my $string = $record->subfield( 100, "a" );
3428 if ( ($string) && ( length( $record->subfield( 100, "a" ) ) == 36 ) ) {
3429 my $f100 = $record->field(100);
3430 $record->delete_field($f100);
3432 $string = POSIX::strftime( "%Y%m%d", localtime );
3434 $string = sprintf( "%-*s", 35, $string );
3436 substr( $string, 22, 6, "frey50" );
3437 unless ( $record->subfield( 100, "a" ) ) {
3438 $record->insert_grouped_field( MARC::Field->new( 100, "", "", "a" => $string ) );
3442 #enhancement 5374: update transaction date (005) for marc21/unimarc
3443 if($encoding =~ /MARC21|UNIMARC/) {
3444 my @a= (localtime) [5,4,3,2,1,0]; $a[0]+=1900; $a[1]++;
3445 # YY MM DD HH MM SS (update year and month)
3446 my $f005= $record->field('005');
3447 $f005->update(sprintf("%4d%02d%02d%02d%02d%04.1f",@a)) if $f005;
3451 if ( C4::Context->preference("NoZebra") ) {
3453 # only NoZebra indexing needs to have
3454 # the previous version of the record
3455 $oldRecord = GetMarcBiblio($biblionumber);
3457 $sth = $dbh->prepare("UPDATE biblioitems SET marc=?,marcxml=? WHERE biblionumber=?");
3458 $sth->execute( $record->as_usmarc(), $record->as_xml_record($encoding), $biblionumber );
3460 ModZebra( $biblionumber, "specialUpdate", "biblioserver", $oldRecord, $record );
3461 return $biblionumber;
3464 =head2 z3950_extended_services
3466 z3950_extended_services($serviceType,$serviceOptions,$record);
3468 z3950_extended_services is used to handle all interactions with Zebra's extended serices package, which is employed to perform all management of the MARC data stored in Zebra.
3470 C<$serviceType> one of: itemorder,create,drop,commit,update,xmlupdate
3472 C<$serviceOptions> a has of key/value pairs. For instance, if service_type is 'update', $service_options should contain:
3474 action => update action, one of specialUpdate, recordInsert, recordReplace, recordDelete, elementUpdate.
3478 recordidOpaque => Opaque Record ID (user supplied) or recordidNumber => Record ID number (system number).
3479 syntax => the record syntax (transfer syntax)
3480 databaseName = Database from connection object
3482 To set serviceOptions, call set_service_options($serviceType)
3484 C<$record> the record, if one is needed for the service type
3486 A record should be in XML. You can convert it to XML from MARC by running it through marc2xml().
3490 sub z3950_extended_services {
3491 my ( $server, $serviceType, $action, $serviceOptions ) = @_;
3493 # get our connection object
3494 my $Zconn = C4::Context->Zconn( $server, 0, 1 );
3496 # create a new package object
3497 my $Zpackage = $Zconn->package();
3500 $Zpackage->option( action => $action );
3502 if ( $serviceOptions->{'databaseName'} ) {
3503 $Zpackage->option( databaseName => $serviceOptions->{'databaseName'} );
3505 if ( $serviceOptions->{'recordIdNumber'} ) {
3506 $Zpackage->option( recordIdNumber => $serviceOptions->{'recordIdNumber'} );
3508 if ( $serviceOptions->{'recordIdOpaque'} ) {
3509 $Zpackage->option( recordIdOpaque => $serviceOptions->{'recordIdOpaque'} );
3512 # this is an ILL request (Zebra doesn't support it, but Koha could eventually)
3513 #if ($serviceType eq 'itemorder') {
3514 # $Zpackage->option('contact-name' => $serviceOptions->{'contact-name'});
3515 # $Zpackage->option('contact-phone' => $serviceOptions->{'contact-phone'});
3516 # $Zpackage->option('contact-email' => $serviceOptions->{'contact-email'});
3517 # $Zpackage->option('itemorder-item' => $serviceOptions->{'itemorder-item'});
3520 if ( $serviceOptions->{record} ) {
3521 $Zpackage->option( record => $serviceOptions->{record} );
3523 # can be xml or marc
3524 if ( $serviceOptions->{'syntax'} ) {
3525 $Zpackage->option( syntax => $serviceOptions->{'syntax'} );
3529 # send the request, handle any exception encountered
3530 eval { $Zpackage->send($serviceType) };
3531 if ( $@ && $@->isa("ZOOM::Exception") ) {
3532 return "error: " . $@->code() . " " . $@->message() . "\n";
3535 # free up package resources
3536 $Zpackage->destroy();
3539 =head2 set_service_options
3541 my $serviceOptions = set_service_options($serviceType);
3543 C<$serviceType> itemorder,create,drop,commit,update,xmlupdate
3545 Currently, we only support 'create', 'commit', and 'update'. 'drop' support will be added as soon as Zebra supports it.
3549 sub set_service_options {
3550 my ($serviceType) = @_;
3553 # FIXME: This needs to be an OID ... if we ever need 'syntax' this sub will need to change
3554 # $serviceOptions->{ 'syntax' } = ''; #zebra doesn't support syntaxes other than xml
3556 if ( $serviceType eq 'commit' ) {
3560 if ( $serviceType eq 'create' ) {
3564 if ( $serviceType eq 'drop' ) {
3565 die "ERROR: 'drop' not currently supported (by Zebra)";
3567 return $serviceOptions;
3570 =head2 get_biblio_authorised_values
3572 find the types and values for all authorised values assigned to this biblio.
3576 MARC::Record of the bib
3578 returns: a hashref mapping the authorised value to the value set for this biblionumber
3580 $authorised_values = {
3581 'Scent' => 'flowery',
3582 'Audience' => 'Young Adult',
3583 'itemtypes' => 'SER',
3586 Notes: forlibrarian should probably be passed in, and called something different.
3590 sub get_biblio_authorised_values {
3591 my $biblionumber = shift;
3594 my $forlibrarian = 1; # are we in staff or opac?
3595 my $frameworkcode = GetFrameworkCode($biblionumber);
3597 my $authorised_values;
3599 my $tagslib = GetMarcStructure( $forlibrarian, $frameworkcode )
3600 or return $authorised_values;
3602 # assume that these entries in the authorised_value table are bibliolevel.
3603 # ones that start with 'item%' are item level.
3604 my $query = q(SELECT distinct authorised_value, kohafield
3605 FROM marc_subfield_structure
3606 WHERE authorised_value !=''
3607 AND (kohafield like 'biblio%'
3608 OR kohafield like '') );
3609 my $bibliolevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
3611 foreach my $tag ( keys(%$tagslib) ) {
3612 foreach my $subfield ( keys( %{ $tagslib->{$tag} } ) ) {
3614 # warn "checking $subfield. type is: " . ref $tagslib->{ $tag }{ $subfield };
3615 if ( 'HASH' eq ref $tagslib->{$tag}{$subfield} ) {
3616 if ( defined $tagslib->{$tag}{$subfield}{'authorised_value'} && exists $bibliolevel_authorised_values->{ $tagslib->{$tag}{$subfield}{'authorised_value'} } ) {
3617 if ( defined $record->field($tag) ) {
3618 my $this_subfield_value = $record->field($tag)->subfield($subfield);
3619 if ( defined $this_subfield_value ) {
3620 $authorised_values->{ $tagslib->{$tag}{$subfield}{'authorised_value'} } = $this_subfield_value;
3628 # warn ( Data::Dumper->Dump( [ $authorised_values ], [ 'authorised_values' ] ) );
3629 return $authorised_values;
3638 Koha Development Team <http://koha-community.org/>
3640 Paul POULAIN paul.poulain@free.fr
3642 Joshua Ferraro jmf@liblime.com