From 74aea78eb0c00591edea2a46edc72caa9de4c07b Mon Sep 17 00:00:00 2001
From: Ere Maijala <ere.maijala@helsinki.fi>
Date: Thu, 21 Feb 2019 14:58:15 +0200
Subject: [PATCH] Bug 21280: Move MARC editor code to a common class

Combines the MARC editor code from addbiblio.pl and authorities.pl to a common class.
---
 Koha/MarcEditor.pm         | 630 +++++++++++++++++++++++++++++++++++++
 authorities/authorities.pl | 490 ++---------------------------
 cataloguing/addbiblio.pl   | 512 +-----------------------------
 3 files changed, 659 insertions(+), 973 deletions(-)
 create mode 100644 Koha/MarcEditor.pm

diff --git a/Koha/MarcEditor.pm b/Koha/MarcEditor.pm
new file mode 100644
index 0000000000..85d6c20cec
--- /dev/null
+++ b/Koha/MarcEditor.pm
@@ -0,0 +1,630 @@
+package Koha::MarcEditor;
+
+# Copyright 2000-2002 Katipo Communications
+# Copyright 2004-2010 BibLibre
+# Copyright 2019 University of Helsinki (The National Library Of Finland)
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+
+use Readonly;
+
+use C4::ClassSource;
+use C4::Context;
+
+use Koha::DateUtils;
+
+use base qw(Class::Accessor);
+
+# Constants to refer to the standard editor types
+Readonly our $EDITOR_BIBLIO    => 'biblio';
+Readonly our $EDITOR_AUTHORITY => 'authority';
+
+=head1 NAME
+
+Koha::MarcEditor - Koha MARC Editor class
+
+=head1 API
+
+=head2 Class Methods
+
+=cut
+
+=head2 new
+
+    my $editor = Koha::MarcEditor->new({
+        type => $Koha::MarcEditor::EDITOR_BIBLIO,
+        tags => $self->{tags},
+        used_tags => $tagsUsedLib,
+        mandatory_z3950 => $mandatory_z3950,
+        hostitemnumber => $hostitemnumber
+    });
+
+Returns a new MarcEditor object.
+
+=cut
+
+sub new {
+    my ($class, $params) = @_;
+
+    my $self = $class->SUPER::new();
+    $self->_init($params);
+    return $self;
+}
+
+=head2 Instance Methods
+
+=head3 build_tabs
+
+    $editor->build_tabs($template, $record, $encoding);
+
+Builds tab data in the template
+
+=cut
+
+sub build_tabs {
+    my ( $self, $template, $record, $encoding ) = @_;
+
+    # fill arrays
+    my @loop_data = ();
+    my $tag;
+
+    # in this array, we will push all the 10 tabs
+    # to avoid having 10 tabs in the template : they will all be in the same BIG_LOOP
+    my @BIG_LOOP;
+    my %seen;
+    my @tab_data; # all tags to display
+    my $max_num_tab=-1;
+
+    if (defined $self->{used_tags}) {
+        foreach my $used ( @{$self->{used_tags}} ) {
+            push @tab_data,$used->{tagfield} if not $seen{$used->{tagfield}};
+            $seen{$used->{tagfield}}++;
+        }
+
+        foreach (@{$self->{used_tags}}) {
+            if($_->{tab} > -1 && $_->{tab} >= $max_num_tab && $_->{tagfield} ne '995'){ # FIXME : MARC21 ?
+                $max_num_tab = $_->{tab};
+            }
+        }
+        if ($max_num_tab >= 9) {
+            $max_num_tab = 9;
+        }
+    } else {
+        foreach my $used ( keys %{$self->{tags}} ) {
+            push @tab_data,$used if not $seen{$used};
+            $seen{$used}++;
+        }
+        @tab_data = sort @tab_data;
+        $max_num_tab = 9;
+    }
+
+    # loop through each tab 0 through 9
+    for ( my $tabloop = 0 ; $tabloop <= $max_num_tab ; $tabloop++ ) {
+        my @loop_data = (); #innerloop in the template.
+        my $i = 0;
+        foreach my $tag (@tab_data) {
+            $i++;
+            next if ! $tag;
+            my ($indicator1, $indicator2);
+            my $index_tag = $self->_create_key();
+
+            # if MARC::Record is not empty =>use it as master loop, then add missing subfields that should be in the tab.
+            # if MARC::Record is empty => use tab as master loop.
+            if ( $record ne -1 && ( $record->field($tag) || $tag eq '000' ) ) {
+                my @fields;
+                if ( $tag ne '000' ) {
+                    @fields = $record->field($tag);
+                }
+                else {
+                    push @fields, $record->leader(); # if tag == 000
+                }
+                # loop through each field
+                foreach my $field (@fields) {
+
+                    my @subfields_data;
+                    if ( $tag < 10 ) {
+                        my ( $value, $subfield );
+                        if ( $tag ne '000' ) {
+                            $value    = $field->data();
+                            $subfield = "@";
+                        }
+                        else {
+                            $value    = $field;
+                            $subfield = '@';
+                        }
+                        next if ( $self->{tags}->{$tag}->{$subfield}->{tab} ne $tabloop );
+                        next if $self->{tags}->{$tag}->{$subfield}->{hidden} && $subfield ne '9'
+                            && $self->{type} eq $Koha::MarcEditor::EDITOR_AUTHORITY;
+                        next
+                          if ( $self->{tags}->{$tag}->{$subfield}->{kohafield} eq
+                            'biblio.biblionumber' );
+                        push(
+                            @subfields_data,
+                            $self->_create_input(
+                                $tag, $subfield, $value, $index_tag, $tabloop, $record,
+                            )
+                        );
+                    }
+                    else {
+                        my @subfields = $field->subfields();
+                        foreach my $subfieldcount ( 0 .. $#subfields ) {
+                            my $subfield = $subfields[$subfieldcount][0];
+                            my $value    = $subfields[$subfieldcount][1];
+                            next if ( length $subfield != 1 );
+                            next if ( $self->{tags}->{$tag}->{$subfield}->{tab} ne $tabloop );
+                            next if $self->{tags}->{$tag}->{$subfield}->{hidden} && $subfield ne '9'
+                                && $self->{type} eq $Koha::MarcEditor::EDITOR_AUTHORITY;
+                            push(
+                                @subfields_data,
+                                $self->_create_input(
+                                    $tag, $subfield, $value, $index_tag, $tabloop,
+                                    $record
+                                )
+                            );
+                        }
+                    }
+
+                    # now, loop again to add parameter subfield that are not in the MARC::Record
+                    foreach my $subfield ( sort( keys %{ $self->{tags}->{$tag} } ) ) {
+                        next if ( length $subfield != 1 );
+                        next if ( $self->{tags}->{$tag}->{$subfield}->{tab} ne $tabloop );
+                        next if ( $tag < 10 );
+                        next if $self->{tags}->{$tag}->{$subfield}->{hidden} && $subfield ne '9'
+                            && $self->{type} eq $Koha::MarcEditor::EDITOR_AUTHORITY;
+                        next
+                          if ( ( $self->{tags}->{$tag}->{$subfield}->{hidden} <= -4 )
+                            or ( $self->{tags}->{$tag}->{$subfield}->{hidden} >= 5 ) )
+                            and $self->{type} ne $Koha::MarcEditor::EDITOR_AUTHORITY
+                            and not ( $subfield eq "9" and
+                                      exists($self->{tags}->{$tag}->{'a'}->{authtypecode}) and
+                                      defined($self->{tags}->{$tag}->{'a'}->{authtypecode}) and
+                                      $self->{tags}->{$tag}->{'a'}->{authtypecode} ne ""
+                                    )
+                          ;    #check for visibility flag
+                               # if subfield is $9 in a field whose $a is authority-controlled,
+                               # always include in the form regardless of the hidden setting - bug 2206
+                        next if ( defined( $field->subfield($subfield) ) );
+                        push(
+                            @subfields_data,
+                            $self->_create_input(
+                                $tag, $subfield, '', $index_tag, $tabloop, $record
+                            )
+                        );
+                    }
+                    if ( $#subfields_data >= 0 ) {
+                        # build the tag entry.
+                        # note that the random() field is mandatory. Otherwise, on repeated fields, you'll
+                        # have twice the same "name" value, and cgi->param() will return only one, making
+                        # all subfields to be merged in a single field.
+                        my %tag_data = (
+                            tag           => $tag,
+                            index         => $index_tag,
+                            tag_lib       => $self->{tags}->{$tag}->{lib},
+                            repeatable    => $self->{tags}->{$tag}->{repeatable},
+                            mandatory     => $self->{tags}->{$tag}->{mandatory},
+                            subfield_loop => \@subfields_data,
+                            fixedfield    => $tag < 10 ? 1 : 0,
+                            random        => $self->_create_key(),
+                        );
+                        if ($tag >= 10){ # no indicator for 00x tags
+                           $tag_data{indicator1} = $self->_format_indicator($field->indicator(1)),
+                           $tag_data{indicator2} = $self->_format_indicator($field->indicator(2)),
+                        }
+                        push( @loop_data, \%tag_data );
+                    }
+                 } # foreach $field end
+
+            # if breeding is empty
+            }
+            else {
+                my @subfields_data;
+                foreach my $subfield ( sort( keys %{ $self->{tags}->{$tag} } ) ) {
+                    next if ( length $subfield != 1 );
+                    next if $self->{tags}->{$tag}->{$subfield}->{hidden} && $subfield ne '9'
+                        && $self->{type} eq $Koha::MarcEditor::EDITOR_AUTHORITY;
+                    next
+                      if ( ( $self->{tags}->{$tag}->{$subfield}->{hidden} <= -4 )
+                        or ( $self->{tags}->{$tag}->{$subfield}->{hidden} >= 5 ) )
+                        and $self->{type} ne $Koha::MarcEditor::EDITOR_AUTHORITY
+                        and not ( $subfield eq "9" and
+                                exists($self->{tags}->{$tag}->{'a'}->{authtypecode}) and
+                                defined($self->{tags}->{$tag}->{'a'}->{authtypecode}) and
+                                $self->{tags}->{$tag}->{'a'}->{authtypecode} ne ""
+                              )
+                      ;    #check for visibility flag
+                           # if subfield is $9 in a field whose $a is authority-controlled,
+                           # always include in the form regardless of the hidden setting - bug 2206
+                    next
+                      if ( $self->{tags}->{$tag}->{$subfield}->{tab} ne $tabloop );
+                    push(
+                        @subfields_data,
+                        $self->_create_input(
+                            $tag, $subfield, '', $index_tag, $tabloop, $record
+                        )
+                    );
+                }
+                if ( $#subfields_data >= 0 ) {
+                    my %tag_data = (
+                        tag              => $tag,
+                        index            => $index_tag,
+                        tag_lib          => $self->{tags}->{$tag}->{lib},
+                        repeatable       => $self->{tags}->{$tag}->{repeatable},
+                        mandatory        => $self->{tags}->{$tag}->{mandatory},
+                        indicator1       => ( $indicator1 || $self->{tags}->{$tag}->{ind1_defaultvalue} ), #if not set, try to load the default value
+                        indicator2       => ( $indicator2 || $self->{tags}->{$tag}->{ind2_defaultvalue} ), #use short-circuit operator for efficiency
+                        subfield_loop    => \@subfields_data,
+                        tagfirstsubfield => $subfields_data[0],
+                        fixedfield       => $tag < 10 ? 1 : 0,
+                    );
+
+                    push @loop_data, \%tag_data;
+                }
+            }
+        }
+        if ( $#loop_data >= 0 ) {
+            push @BIG_LOOP, {
+                number    => $tabloop,
+                innerloop => \@loop_data,
+            };
+        }
+    }
+    $template->param( BIG_LOOP => \@BIG_LOOP );
+}
+
+=head2 Internal methods
+
+=cut
+
+=head3 _init
+
+    $self->init(\%params);
+
+Initialize settings
+
+=cut
+
+sub _init {
+    my ($self, $params) = @_;
+
+    $self->{type} = $params->{type};
+    $self->{tags} = $params->{tags};
+    $self->{used_tags} = $params->{used_tags};
+    $self->{mandatory_z3950} = $params->{mandatory_z3950};
+    $self->{hostitemnumber} = $params->{hostitemnumber} // '';
+}
+
+=head3 _build_authorized_values_list
+
+    $subfield_data{marc_value} = $self->_build_authorized_values_list(
+        $tag, $subfield, $value, $index_tag, $index_subfield
+    );
+
+Builds a list of authorized values
+
+=cut
+
+sub _build_authorized_values_list {
+    my ( $self, $tag, $subfield, $value, $index_tag, $index_subfield ) = @_;
+
+    my @authorised_values;
+    my %authorised_lib;
+
+    # builds list, depending on authorised value...
+
+    #---- branch
+    if ( $self->{tags}->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
+        my $libraries = Koha::Libraries->search_filtered({}, {order_by => ['branchname']});
+        while ( my $l = $libraries->next ) {
+            push @authorised_values, $l->branchcode;;
+            $authorised_lib{$l->branchcode} = $l->branchname;
+        }
+    }
+    elsif ( $self->{tags}->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
+        push @authorised_values, "";
+
+        my $itemtype;
+        my $itemtypes = Koha::ItemTypes->search_with_localization;
+        while ( $itemtype = $itemtypes->next ) {
+            push @authorised_values, $itemtype->itemtype;
+            $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
+        }
+        $value = $itemtype unless ($value);
+    }
+    elsif ( $self->{tags}->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
+        push @authorised_values, "";
+
+        my $class_sources = GetClassSources();
+
+        my $default_source = C4::Context->preference("DefaultClassificationSource");
+
+        foreach my $class_source (sort keys %$class_sources) {
+            next unless $class_sources->{$class_source}->{'used'} or
+                        ($value and $class_source eq $value) or
+                        ($class_source eq $default_source);
+            push @authorised_values, $class_source;
+            $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
+        }
+        $value = $default_source unless $value;
+    }
+    else {
+        my $branch_limit = $self->{type} ne $Koha::MarcEditor::EDITOR_AUTHORITY && C4::Context->userenv
+            ? C4::Context->userenv->{'branch'} : '';
+
+        my $query;
+        if ($branch_limit) {
+            $query = "SELECT authorised_value, lib
+                        FROM authorised_values";
+            $query .= qq{ LEFT JOIN authorised_values_branches ON ( id = av_id )} if $branch_limit;
+            $query .= " WHERE category = ?";
+            $query .= " AND ( branchcode = ? OR branchcode IS NULL )" if $branch_limit;
+            $query .= " GROUP BY lib ORDER BY lib, lib_opac";
+        } else {
+            $query = "SELECT authorised_value,lib
+                FROM authorised_values
+                WHERE category=? ORDER BY lib";
+        }
+        my $authorised_values_sth = C4::Context->dbh->prepare( $query );
+
+        $authorised_values_sth->execute(
+            $self->{tags}->{$tag}->{$subfield}->{authorised_value},
+            $branch_limit ? $branch_limit : (),
+        );
+
+        push @authorised_values, "";
+
+        while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
+            push @authorised_values, $value;
+            $authorised_lib{$value} = $lib;
+        }
+        $authorised_values_sth->finish;
+    }
+    return {
+        type     => 'select',
+        id       => "tag_".$tag."_subfield_".$subfield."_".$index_tag."_".$index_subfield,
+        name     => "tag_".$tag."_subfield_".$subfield."_".$index_tag."_".$index_subfield,
+        default  => $value,
+        values   => \@authorised_values,
+        labels   => \%authorised_lib,
+    };
+}
+
+=head3 _create_input
+
+ builds the <input ...> entry for a subfield.
+
+=cut
+
+sub _create_input {
+    my ( $self, $tag, $subfield, $value, $index_tag, $tabloop, $rec ) = @_;
+
+    my $index_subfield = $self->_create_key(); # create a specific key for each subfield
+    my $tagdef = $self->{tags}->{$tag};
+
+    # if there is no value provided but a default value in parameters, get it
+    if ( $value eq '' ) {
+        $value = $tagdef->{$subfield}->{defaultvalue} // q{};
+
+        # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
+        my $today_dt = dt_from_string;
+        my $year = $today_dt->strftime('%Y');
+        my $month = $today_dt->strftime('%m');
+        my $day = $today_dt->strftime('%d');
+        $value =~ s/<<YYYY>>/$year/g;
+        $value =~ s/<<MM>>/$month/g;
+        $value =~ s/<<DD>>/$day/g;
+        # And <<USER>> with surname (?)
+        my $username=(C4::Context->userenv?C4::Context->userenv->{'surname'}:"superlibrarian");
+        $value=~s/<<USER>>/$username/g;
+    }
+
+    # map '@' as "subfield" label for fixed fields
+    # to something that's allowed in a div id.
+    my $id_subfield = $subfield;
+    $id_subfield = "00" if $id_subfield eq "@";
+
+    my %subfield_data = (
+        tag        => $tag,
+        subfield   => $id_subfield,
+        marc_lib       => $tagdef->{$subfield}->{lib},
+        tag_mandatory  => $tagdef->{mandatory},
+        mandatory      => $tagdef->{$subfield}->{mandatory},
+        repeatable     => $tagdef->{$subfield}->{repeatable},
+        kohafield      => $tagdef->{$subfield}->{kohafield},
+        index          => $index_tag,
+        id             => "tag_".$tag."_subfield_".$id_subfield."_".$index_tag."_".$index_subfield,
+        value          => $value,
+        maxlength      => $tagdef->{$subfield}->{maxlength},
+        random         => $self->_create_key(),
+    );
+
+    if(exists $self->{mandatory_z3950}->{$tag.$subfield}){
+        $subfield_data{z3950_mandatory} = $self->{mandatory_z3950}->{$tag.$subfield};
+    }
+
+    if ($self->{type} eq $Koha::MarcEditor::EDITOR_AUTHORITY) {
+        # Authority subfield is hidden unless mandatory or contains something.
+        $subfield_data{visibility} = "display:none;"
+            if( $tagdef->{$subfield}->{hidden} and $value ne ''
+                or ($value eq '' and !$tagdef->{$subfield}->{mandatory})
+            );
+    } else {
+        # Subfield is hidden depending of hidden and mandatory flag, and is always
+        # shown if it contains anything or if its field is mandatory.
+        $subfield_data{visibility} = "display:none;"
+            if $tagdef->{$subfield}->{hidden} % 2 == 1 &&
+            $value eq '' &&
+            !$tagdef->{$subfield}->{mandatory} &&
+            !$tagdef->{mandatory};
+
+        # expand all subfields of 773 if there is a host item provided in the input
+        $subfield_data{visibility} ="" if ($tag eq 773 and $self->{hostitemnumber});
+    }
+
+    # it's an authorised field
+    if ( $tagdef->{$subfield}->{authorised_value} ) {
+        $subfield_data{marc_value} =
+          $self->_build_authorized_values_list( $tag, $subfield, $value, $index_tag, $index_subfield );
+
+    # it's a subfield $9 linking to an authority record - see bug 2206
+    }
+    elsif ($self->{type} eq $Koha::MarcEditor::EDITOR_BIBLIO and
+           $subfield eq "9" and
+           exists($tagdef->{'a'}->{authtypecode}) and
+           defined($tagdef->{'a'}->{authtypecode}) and
+           $tagdef->{'a'}->{authtypecode} ne '') {
+
+        $subfield_data{marc_value} = {
+            type      => 'text',
+            id        => $subfield_data{id},
+            name      => $subfield_data{id},
+            value     => $value,
+            size      => 5,
+            maxlength => $subfield_data{maxlength},
+            readonly  => 1,
+        };
+
+    # it's a thesaurus / authority field
+    }
+    elsif ( $tagdef->{$subfield}->{authtypecode} ) {
+        # when authorities auto-creation is allowed, do not set readonly
+        my $is_readonly = $self->{type} eq $Koha::MarcEditor::EDITOR_BIBLIO && !C4::Context->preference("BiblioAddsAuthorities");
+
+        $subfield_data{marc_value} = {
+            type      => $self->{type} eq $Koha::MarcEditor::EDITOR_AUTHORITY ? 'text1' : 'text',
+            id        => $subfield_data{id},
+            name      => $subfield_data{id},
+            value     => $value,
+            size      => 67,
+            maxlength => $subfield_data{maxlength},
+            readonly  => $is_readonly,
+            authtype  => $tagdef->{$subfield}->{authtypecode},
+        };
+
+    # it's a plugin field
+    } elsif ( $tagdef->{$subfield}->{'value_builder'} ) {
+        require Koha::FrameworkPlugin;
+        my $plugin = Koha::FrameworkPlugin->new( {
+            name => $tagdef->{$subfield}->{'value_builder'},
+        });
+        my $pars= { dbh => C4::Context->dbh, record => $rec, tagslib => $self->{tags},
+            id => $subfield_data{id}, tabloop => $tabloop };
+        $plugin->build( $pars );
+        if( !$plugin->errstr ) {
+            $subfield_data{marc_value} = {
+                type           => $self->{type} eq $Koha::MarcEditor::EDITOR_AUTHORITY ? 'text2' : 'text_complex',
+                id             => $subfield_data{id},
+                name           => $subfield_data{id},
+                value          => $value,
+                size           => 67,
+                maxlength      => $subfield_data{maxlength},
+                javascript     => $plugin->javascript,
+                noclick        => $plugin->noclick,
+            };
+        } else {
+            warn $plugin->errstr;
+            # supply default input form
+            $subfield_data{marc_value} = {
+                type      => 'text',
+                id        => $subfield_data{id},
+                name      => $subfield_data{id},
+                value     => $value,
+                size      => 67,
+                maxlength => $subfield_data{maxlength},
+                readonly  => 0,
+            };
+        }
+
+    # it's an hidden field
+    } elsif ( $tag eq '' ) {
+        $subfield_data{marc_value} = {
+            type      => 'hidden',
+            id        => $subfield_data{id},
+            name      => $subfield_data{id},
+            value     => $value,
+            size      => 67,
+            maxlength => $subfield_data{maxlength},
+        };
+
+    }
+    else {
+        # it's a standard field
+        if (
+            length($value) > 100
+            or
+            ( C4::Context->preference("marcflavour") eq "UNIMARC" && $tag >= 300
+                and $tag < 400 && $subfield eq 'a' )
+            or (    $tag >= 500
+                and $tag < 600
+                && C4::Context->preference("marcflavour") eq "MARC21" )
+          )
+        {
+            $subfield_data{marc_value} = {
+                type      => 'textarea',
+                id        => $subfield_data{id},
+                name      => $subfield_data{id},
+                value     => $value,
+            };
+
+        }
+        else {
+            $subfield_data{marc_value} = {
+                type      => 'text',
+                id        => $subfield_data{id},
+                name      => $subfield_data{id},
+                value     => $value,
+                size      => 67,
+                maxlength => $subfield_data{maxlength},
+                readonly  => 0,
+            };
+
+        }
+    }
+    $subfield_data{'index_subfield'} = $index_subfield;
+    return \%subfield_data;
+}
+
+
+=head3 _format_indicator
+
+Translate indicator value for output form - specifically, map
+indicator = ' ' to ''.  This is for the convenience of a cataloger
+using a mouse to select an indicator input.
+
+=cut
+
+sub _format_indicator {
+    my ($self, $ind_value) = @_;
+    return '' if not defined $ind_value;
+    return '' if $ind_value eq ' ';
+    return $ind_value;
+}
+
+=head3 _create_key
+
+    $key = $self->_create_key():
+
+Creates a random value to set it into the input name
+
+=cut
+
+sub _create_key {
+    return int(rand(1000000));
+}
+
+
+1;
diff --git a/authorities/authorities.pl b/authorities/authorities.pl
index e422cafb36..9db6fe68aa 100755
--- a/authorities/authorities.pl
+++ b/authorities/authorities.pl
@@ -33,20 +33,21 @@ use MARC::File::XML;
 use C4::Biblio;
 use Koha::Authority::Types;
 use Koha::ItemTypes;
-use vars qw( $tagslib);
-use vars qw( $authorised_values_sth);
+use Koha::MarcEditor;
 use vars qw( $is_a_modif );
 
-my $itemtype; # created here because it can be used in build_authorized_values_list sub
-our($authorised_values_sth,$is_a_modif,$usedTagsLib,$mandatory_z3950);
+our($is_a_modif);
 
 =head1 FUNCTIONS
 
-=over
+=head2 MARCfindbreeding_auth
 
-=item build_authorized_values_list
+    $record = MARCfindbreeding_auth($breedingid);
 
-builds list, depending on authorised value...
+Look up the import record repository for the record with
+record with id $breedingid.  If found, returns the decoded
+MARC::Record; otherwise, -1 is returned (FIXME).
+Returns as second parameter the character encoding.
 
 =cut
 
@@ -65,258 +66,7 @@ sub MARCfindbreeding_auth {
     }
 }
 
-sub build_authorized_values_list {
-    my ( $tag, $subfield, $value, $dbh, $authorised_values_sth,$index_tag,$index_subfield ) = @_;
-
-    my @authorised_values;
-    my %authorised_lib;
-
-
-    #---- branch
-    if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
-        my $sth =
-        $dbh->prepare(
-            "select branchcode,branchname from branches order by branchname");
-        $sth->execute;
-        push @authorised_values, ""
-        unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
-
-        while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
-            push @authorised_values, $branchcode;
-            $authorised_lib{$branchcode} = $branchname;
-        }
-    }
-    elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
-        push @authorised_values, ""
-          unless ( $tagslib->{$tag}->{$subfield}->{mandatory}
-            && ( $value || $tagslib->{$tag}->{$subfield}->{defaultvalue} ) );
-
-        my $itemtype;
-        my $itemtypes = Koha::ItemTypes->search_with_localization;
-        while ( $itemtype = $itemtypes->next ) {
-            push @authorised_values, $itemtype->itemtype;
-            $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
-        }
-        $value = $itemtype unless ($value);
-
-        #---- "true" authorised value
-    }
-    else {
-        $authorised_values_sth->execute(
-            $tagslib->{$tag}->{$subfield}->{authorised_value} );
-
-        push @authorised_values, ""
-          unless ( $tagslib->{$tag}->{$subfield}->{mandatory}
-            && ( $value || $tagslib->{$tag}->{$subfield}->{defaultvalue} ) );
-
-        while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
-            push @authorised_values, $value;
-            $authorised_lib{$value} = $lib;
-        }
-    }
-    return {
-        type     => 'select',
-        id       => "tag_".$tag."_subfield_".$subfield."_".$index_tag."_".$index_subfield,
-        name     => "tag_".$tag."_subfield_".$subfield."_".$index_tag."_".$index_subfield,
-        values   => \@authorised_values,
-        labels   => \%authorised_lib,
-        default  => $value,
-    };
-}
-
-
-=item create_input
-
-builds the <input ...> entry for a subfield.
-
-=cut
-
-sub create_input {
-    my ( $tag, $subfield, $value, $index_tag, $tabloop, $rec, $authorised_values_sth,$cgi ) = @_;
-    
-    my $index_subfield = CreateKey(); # create a specifique key for each subfield
-
-    # determine maximum length; 9999 bytes per ISO 2709 except for leader and MARC21 008
-    my $max_length = 9999;
-    if ($tag eq '000') {
-        $max_length = 24;
-    } elsif ($tag eq '008' and C4::Context->preference('marcflavour') eq 'MARC21')  {
-        $max_length = 40;
-    }
-
-    # if there is no value provided but a default value in parameters, get it
-    if ($value eq '') {
-        $value = $tagslib->{$tag}->{$subfield}->{defaultvalue};
-        if (!defined $value) {
-            $value = q{};
-        }
-
-        # get today date & replace YYYY, MM, DD if provided in the default value
-        my ( $year, $month, $day ) = Today();
-        $month = sprintf( "%02d", $month );
-        $day   = sprintf( "%02d", $day );
-        $value =~ s/YYYY/$year/g;
-        $value =~ s/MM/$month/g;
-        $value =~ s/DD/$day/g;
-    }
-    my $dbh = C4::Context->dbh;
-
-    # map '@' as "subfield" label for fixed fields
-    # to something that's allowed in a div id.
-    my $id_subfield = $subfield;
-    $id_subfield = "00" if $id_subfield eq "@";
-
-    my %subfield_data = (
-        tag        => $tag,
-        subfield   => $id_subfield,
-        marc_lib       => $tagslib->{$tag}->{$subfield}->{lib},
-        tag_mandatory  => $tagslib->{$tag}->{mandatory},
-        mandatory      => $tagslib->{$tag}->{$subfield}->{mandatory},
-        repeatable     => $tagslib->{$tag}->{$subfield}->{repeatable},
-        kohafield      => $tagslib->{$tag}->{$subfield}->{kohafield},
-        index          => $index_tag,
-        id             => "tag_".$tag."_subfield_".$id_subfield."_".$index_tag."_".$index_subfield,
-        value          => $value,
-        random         => CreateKey(),
-    );
-
-    if(exists $mandatory_z3950->{$tag.$subfield}){
-        $subfield_data{z3950_mandatory} = $mandatory_z3950->{$tag.$subfield};
-    }
-    
-    $subfield_data{visibility} = "display:none;"
-        if( $tagslib->{$tag}->{$subfield}->{hidden} and $value ne ''
-            or ($value eq '' and !$tagslib->{$tag}->{$subfield}->{mandatory})
-        );
-    
-    # it's an authorised field
-    if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
-        $subfield_data{marc_value} =
-        build_authorized_values_list( $tag, $subfield, $value, $dbh,
-            $authorised_values_sth,$index_tag,$index_subfield );
-
-    # it's a thesaurus / authority field
-    }
-    elsif ( $tagslib->{$tag}->{$subfield}->{authtypecode} ) {
-        $subfield_data{marc_value} = {
-            type         => 'text1',
-            id           => $subfield_data{id},
-            name         => $subfield_data{id},
-            value        => $value,
-            authtypecode => $tagslib->{$tag}->{$subfield}->{authtypecode},
-        };
-    }
-    elsif ( $tagslib->{$tag}->{$subfield}->{'value_builder'} ) { # plugin
-        require Koha::FrameworkPlugin;
-        my $plugin = Koha::FrameworkPlugin->new({
-            name => $tagslib->{$tag}->{$subfield}->{'value_builder'},
-        });
-        my $pars=  { dbh => $dbh, record => $rec, tagslib =>$tagslib,
-            id => $subfield_data{id}, tabloop => $tabloop };
-        $plugin->build( $pars );
-        if( !$plugin->errstr ) {
-            $subfield_data{marc_value} = {
-                type       => 'text2',
-                id        => $subfield_data{id},
-                name      => $subfield_data{id},
-                value     => $value,
-                maxlength => $max_length,
-                javascript => $plugin->javascript,
-                noclick    => $plugin->noclick,
-            };
-        } else { # warn and supply default field
-            warn $plugin->errstr;
-            $subfield_data{marc_value} = {
-                type      => 'text',
-                id        => $subfield_data{id},
-                name      => $subfield_data{id},
-                value     => $value,
-                maxlength => $max_length,
-            };
-        }
-    }
-    # it's an hidden field
-    elsif ( $tag eq '' ) {
-        $subfield_data{marc_value} = {
-            type      => 'hidden',
-            id        => $subfield_data{id},
-            name      => $subfield_data{id},
-            value     => $value,
-            maxlength => $max_length,
-        }
-    }
-    elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {
-        $subfield_data{marc_value} = {
-            type => 'text',
-            id        => $subfield_data{id},
-            name      => $subfield_data{id},
-            value     => $value,
-            maxlength => $max_length,
-        };
-
-        # it's a standard field
-    }
-    else {
-        if (
-            length($value) > 100
-            or
-            ( C4::Context->preference("marcflavour") eq "UNIMARC" && $tag >= 300
-                and $tag < 400 && $subfield eq 'a' )
-            or (    $tag >= 600
-                and $tag < 700
-                && C4::Context->preference("marcflavour") eq "MARC21" )
-        )
-        {
-            $subfield_data{marc_value} = {
-                type => 'textarea',
-                id        => $subfield_data{id},
-                name      => $subfield_data{id},
-                value     => $value,
-                maxlength => $max_length,
-            };
-
-        }
-        else {
-            $subfield_data{marc_value} = {
-                type => 'text',
-                id        => $subfield_data{id},
-                name      => $subfield_data{id},
-                value     => $value,
-                maxlength => $max_length,
-            };
-
-        }
-    }
-    $subfield_data{'index_subfield'} = $index_subfield;
-    return \%subfield_data;
-}
-
-=item format_indicator
-
-Translate indicator value for output form - specifically, map
-indicator = ' ' to ''.  This is for the convenience of a cataloger
-using a mouse to select an indicator input.
-
-=cut
-
-sub format_indicator {
-    my $ind_value = shift;
-    return '' if not defined $ind_value;
-    return '' if $ind_value eq ' ';
-    return $ind_value;
-}
-
-=item CreateKey
-
-Create a random value to set it into the input name
-
-=cut
-
-sub CreateKey {
-    return int(rand(1000000));
-}
-
-=item GetMandatoryFieldZ3950
+=head2 GetMandatoryFieldZ3950
 
     This function returns a hashref which contains all mandatory field
     to search with z3950 server.
@@ -342,212 +92,6 @@ sub GetMandatoryFieldZ3950 {
     }
 }
 
-sub build_tabs {
-    my ( $template, $record, $dbh, $encoding,$input ) = @_;
-
-    # fill arrays
-    my @loop_data = ();
-    my $tag;
-
-    my $authorised_values_sth = $dbh->prepare(
-        "SELECT authorised_value,lib
-        FROM authorised_values
-        WHERE category=? ORDER BY lib"
-    );
-    
-    # in this array, we will push all the 10 tabs
-    # to avoid having 10 tabs in the template : they will all be in the same BIG_LOOP
-    my @BIG_LOOP;
-    my %seen;
-    my @tab_data; # all tags to display
-    
-    foreach my $used ( keys %$tagslib ){
-        push @tab_data,$used if not $seen{$used};
-        $seen{$used}++;
-    }
-        
-    my $max_num_tab=9;
-    # loop through each tab 0 through 9
-    for ( my $tabloop = 0 ; $tabloop <= $max_num_tab ; $tabloop++ ) {
-        my @loop_data = (); #innerloop in the template.
-        my $i = 0;
-        foreach my $tag (sort @tab_data) {
-            $i++;
-            next if ! $tag;
-            my ($indicator1, $indicator2);
-            my $index_tag = CreateKey;
-
-            # if MARC::Record is not empty =>use it as master loop, then add missing subfields that should be in the tab.
-            # if MARC::Record is empty => use tab as master loop.
-            if ( $record != -1 && ( $record->field($tag) || $tag eq '000' ) ) {
-                my @fields;
-                if ( $tag ne '000' ) {
-                                @fields = $record->field($tag);
-                }
-                else {
-                push @fields, $record->leader(); # if tag == 000
-                }
-                # loop through each field
-                foreach my $field (@fields) {
-                    
-                    my @subfields_data;
-                    if ( $tag < 10 ) {
-                        my ( $value, $subfield );
-                        if ( $tag ne '000' ) {
-                            $value    = $field->data();
-                            $subfield = "@";
-                        }
-                        else {
-                            $value    = $field;
-                            $subfield = '@';
-                        }
-                        next if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
-                        next if $tagslib->{$tag}->{$subfield}->{hidden} && $subfield ne '9';
-                        push(
-                            @subfields_data,
-                            &create_input(
-                                $tag, $subfield, $value, $index_tag, $tabloop, $record,
-                                $authorised_values_sth,$input
-                            )
-                        );
-                    }
-                    else {
-                        my @subfields = $field->subfields();
-                        foreach my $subfieldcount ( 0 .. $#subfields ) {
-                            my $subfield = $subfields[$subfieldcount][0];
-                            my $value    = $subfields[$subfieldcount][1];
-                            next if ( length $subfield != 1 );
-                            next if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
-                            next if $tagslib->{$tag}->{$subfield}->{hidden} && $subfield ne '9';
-                            push(
-                                @subfields_data,
-                                &create_input(
-                                    $tag, $subfield, $value, $index_tag, $tabloop,
-                                    $record, $authorised_values_sth,$input
-                                )
-                            );
-                        }
-                    }
-
-                    # now, loop again to add parameter subfield that are not in the MARC::Record
-                    foreach my $subfield ( sort( keys %{ $tagslib->{$tag} } ) )
-                    {
-                        next if ( length $subfield != 1 );
-                        next if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
-                        next if ( $tag < 10 );
-                        next if $tagslib->{$tag}->{$subfield}->{hidden} && $subfield ne '9';
-                        next if ( defined( $field->subfield($subfield) ) );
-                        push(
-                            @subfields_data,
-                            &create_input(
-                                $tag, $subfield, '', $index_tag, $tabloop, $record,
-                                $authorised_values_sth,$input
-                            )
-                        );
-                    }
-                    if ( $#subfields_data >= 0 ) {
-                        # build the tag entry.
-                        # note that the random() field is mandatory. Otherwise, on repeated fields, you'll 
-                        # have twice the same "name" value, and cgi->param() will return only one, making
-                        # all subfields to be merged in a single field.
-                        my %tag_data = (
-                            tag           => $tag,
-                            index         => $index_tag,
-                            tag_lib       => $tagslib->{$tag}->{lib},
-                            repeatable       => $tagslib->{$tag}->{repeatable},
-                            mandatory       => $tagslib->{$tag}->{mandatory},
-                            subfield_loop => \@subfields_data,
-                            fixedfield    => ($tag < 10)?(1):(0),
-                            random        => CreateKey,
-                        );
-                        if ($tag >= 10){ # no indicator for theses tag
-                            $tag_data{indicator1} = format_indicator($field->indicator(1)),
-                            $tag_data{indicator2} = format_indicator($field->indicator(2)),
-                        }
-                        push( @loop_data, \%tag_data );
-                    }
-                } # foreach $field end
-
-            # if breeding is empty
-            }
-            else {
-                my @subfields_data;
-                foreach my $subfield ( sort( keys %{ $tagslib->{$tag} } ) ) {
-                    next if ( length $subfield != 1 );
-                    next if $tagslib->{$tag}->{$subfield}->{hidden} && $subfield ne '9';
-                    next if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
-                    push(
-                        @subfields_data,
-                        &create_input(
-                            $tag, $subfield, '', $index_tag, $tabloop, $record,
-                            $authorised_values_sth,$input
-                        )
-                    );
-                }
-                if ( $#subfields_data >= 0 ) {
-                    my %tag_data = (
-                        tag              => $tag,
-                        index            => $index_tag,
-                        tag_lib          => $tagslib->{$tag}->{lib},
-                        repeatable       => $tagslib->{$tag}->{repeatable},
-                        mandatory       => $tagslib->{$tag}->{mandatory},
-                        indicator1       => $indicator1,
-                        indicator2       => $indicator2,
-                        subfield_loop    => \@subfields_data,
-                        tagfirstsubfield => $subfields_data[0],
-                        fixedfield       => ($tag < 10)?(1):(0)
-                    );
-                    
-                    push @loop_data, \%tag_data ;
-                }
-            }
-        }
-        if ( $#loop_data >= 0 ) {
-            push @BIG_LOOP, {
-                number    => $tabloop,
-                innerloop => \@loop_data,
-            };
-        }
-    }
-    $template->param( BIG_LOOP => \@BIG_LOOP );
-}
-
-
-sub build_hidden_data {
-    # build hidden data =>
-    # we store everything, even if we show only requested subfields.
-
-    my @loop_data =();
-    my $i=0;
-    foreach my $tag (keys %{$tagslib}) {
-        my $previous_tag = '';
-
-        # loop through each subfield
-        foreach my $subfield (keys %{$tagslib->{$tag}}) {
-            next if ($subfield eq 'lib');
-            next if ($subfield eq 'tab');
-            next if ($subfield eq 'mandatory');
-                next if ($subfield eq 'repeatable');
-            next if ($tagslib->{$tag}->{$subfield}->{'tab'}  ne "-1");
-            my %subfield_data;
-            $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
-            $subfield_data{marc_mandatory}=$tagslib->{$tag}->{$subfield}->{mandatory};
-            $subfield_data{marc_repeatable}=$tagslib->{$tag}->{$subfield}->{repeatable};
-            $subfield_data{marc_value} = {
-                type => 'hidden_simple',
-                name => 'field_value[]',
-            };
-            push(@loop_data, \%subfield_data);
-            $i++
-        }
-    }
-}
-
-=back
-
-=cut
-
-
 # ======================== 
 #          MAIN 
 #=========================
@@ -577,9 +121,6 @@ my ($template, $loggedinuser, $cookie)
                             });
 $template->param(nonav   => $nonav,index=>$myindex,authtypecode=>$authtypecode,breedingid=>$breedingid,);
 
-$tagslib = GetTagsLabels(1,$authtypecode);
-$mandatory_z3950 = GetMandatoryFieldZ3950($authtypecode);
-
 my $record=-1;
 my $encoding="";
 if (($authid) && !($breedingid)){
@@ -589,6 +130,13 @@ if ($breedingid) {
     ( $record, $encoding ) = MARCfindbreeding_auth( $breedingid );
 }
 
+
+my $editor = Koha::MarcEditor->new({
+    type => $Koha::MarcEditor::EDITOR_AUTHORITY,
+    tags => GetTagsLabels(1,$authtypecode),
+    mandatory_z3950 => GetMandatoryFieldZ3950($authtypecode)
+});
+
 my ($oldauthnumtagfield,$oldauthnumtagsubfield);
 my ($oldauthtypetagfield,$oldauthtypetagsubfield);
 $is_a_modif=0;
@@ -628,8 +176,7 @@ if ($op eq "add") {
         exit;
     } else {
     # it may be a duplicate, warn the user and do nothing
-        build_tabs($template, $record, $dbh, $encoding,$input);
-        build_hidden_data;
+        $editor->build_tabs($template, $record, $encoding);
         $template->param(authid =>$authid,
                         duplicateauthid     => $duplicateauthid,
                         duplicateauthvalue  => $duplicateauthvalue->{'authorized'}->[0]->{'heading'},
@@ -649,8 +196,7 @@ if ($op eq "duplicate")
         {
                 $authid = "";
         }
-        build_tabs ($template, $record, $dbh,$encoding,$input);
-        build_hidden_data;
+        $editor->build_tabs ($template, $record, $encoding);
         $template->param(oldauthtypetagfield=>$oldauthtypetagfield, oldauthtypetagsubfield=>$oldauthtypetagsubfield,
                         oldauthnumtagfield=>$oldauthnumtagfield, oldauthnumtagsubfield=>$oldauthnumtagsubfield,
                         authid                      => $authid , authtypecode=>$authtypecode,	);
diff --git a/cataloguing/addbiblio.pl b/cataloguing/addbiblio.pl
index c4ed49da87..4a17d1c303 100755
--- a/cataloguing/addbiblio.pl
+++ b/cataloguing/addbiblio.pl
@@ -31,11 +31,10 @@ use C4::Context;
 use MARC::Record;
 use C4::Log;
 use C4::Koha;
-use C4::ClassSource;
 use C4::ImportBatch;
 use C4::Charset;
 use Koha::BiblioFrameworks;
-use Koha::DateUtils;
+use Koha::MarcEditor;
 
 use Koha::ItemTypes;
 use Koha::Libraries;
@@ -51,7 +50,7 @@ if ( C4::Context->preference('marcflavour') eq 'UNIMARC' ) {
     MARC::File::XML->default_record_format('UNIMARC');
 }
 
-our($tagslib,$authorised_values_sth,$is_a_modif,$usedTagsLib,$mandatory_z3950);
+our($is_a_modif);
 
 =head1 FUNCTIONS
 
@@ -157,89 +156,6 @@ sub MARCfindbreeding {
     return -1;
 }
 
-=head2 build_authorized_values_list
-
-=cut
-
-sub build_authorized_values_list {
-    my ( $tag, $subfield, $value, $dbh, $authorised_values_sth,$index_tag,$index_subfield ) = @_;
-
-    my @authorised_values;
-    my %authorised_lib;
-
-    # builds list, depending on authorised value...
-
-    #---- branch
-    if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
-        my $libraries = Koha::Libraries->search_filtered({}, {order_by => ['branchname']});
-        while ( my $l = $libraries->next ) {
-            push @authorised_values, $l->branchcode;;
-            $authorised_lib{$l->branchcode} = $l->branchname;
-        }
-    }
-    elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
-        push @authorised_values, "";
-
-        my $itemtype;
-        my $itemtypes = Koha::ItemTypes->search_with_localization;
-        while ( $itemtype = $itemtypes->next ) {
-            push @authorised_values, $itemtype->itemtype;
-            $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
-        }
-        $value = $itemtype unless ($value);
-    }
-    elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
-        push @authorised_values, "";
-
-        my $class_sources = GetClassSources();
-
-        my $default_source = C4::Context->preference("DefaultClassificationSource");
-
-        foreach my $class_source (sort keys %$class_sources) {
-            next unless $class_sources->{$class_source}->{'used'} or
-                        ($value and $class_source eq $value) or
-                        ($class_source eq $default_source);
-            push @authorised_values, $class_source;
-            $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
-        }
-        $value = $default_source unless $value;
-    }
-    else {
-        my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
-        $authorised_values_sth->execute(
-            $tagslib->{$tag}->{$subfield}->{authorised_value},
-            $branch_limit ? $branch_limit : (),
-        );
-
-        push @authorised_values, "";
-
-        while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
-            push @authorised_values, $value;
-            $authorised_lib{$value} = $lib;
-        }
-    }
-    $authorised_values_sth->finish;
-    return {
-        type     => 'select',
-        id       => "tag_".$tag."_subfield_".$subfield."_".$index_tag."_".$index_subfield,
-        name     => "tag_".$tag."_subfield_".$subfield."_".$index_tag."_".$index_subfield,
-        default  => $value,
-        values   => \@authorised_values,
-        labels   => \%authorised_lib,
-    };
-
-}
-
-=head2 CreateKey
-
-    Create a random value to set it into the input name
-
-=cut
-
-sub CreateKey {
-    return int(rand(1000000));
-}
-
 =head2 GetMandatoryFieldZ3950
 
     This function returns a hashref which contains all mandatory field
@@ -264,414 +180,6 @@ sub GetMandatoryFieldZ3950 {
     };
 }
 
-=head2 create_input
-
- builds the <input ...> entry for a subfield.
-
-=cut
-
-sub create_input {
-    my ( $tag, $subfield, $value, $index_tag, $tabloop, $rec, $authorised_values_sth,$cgi ) = @_;
-    
-    my $index_subfield = CreateKey(); # create a specifique key for each subfield
-
-    # if there is no value provided but a default value in parameters, get it
-    if ( $value eq '' ) {
-        $value = $tagslib->{$tag}->{$subfield}->{defaultvalue} // q{};
-
-        # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
-        my $today_dt = dt_from_string;
-        my $year = $today_dt->strftime('%Y');
-        my $month = $today_dt->strftime('%m');
-        my $day = $today_dt->strftime('%d');
-        $value =~ s/<<YYYY>>/$year/g;
-        $value =~ s/<<MM>>/$month/g;
-        $value =~ s/<<DD>>/$day/g;
-        # And <<USER>> with surname (?)
-        my $username=(C4::Context->userenv?C4::Context->userenv->{'surname'}:"superlibrarian");
-        $value=~s/<<USER>>/$username/g;
-    
-    }
-    my $dbh = C4::Context->dbh;
-
-    # map '@' as "subfield" label for fixed fields
-    # to something that's allowed in a div id.
-    my $id_subfield = $subfield;
-    $id_subfield = "00" if $id_subfield eq "@";
-
-    my %subfield_data = (
-        tag        => $tag,
-        subfield   => $id_subfield,
-        marc_lib       => $tagslib->{$tag}->{$subfield}->{lib},
-        tag_mandatory  => $tagslib->{$tag}->{mandatory},
-        mandatory      => $tagslib->{$tag}->{$subfield}->{mandatory},
-        repeatable     => $tagslib->{$tag}->{$subfield}->{repeatable},
-        kohafield      => $tagslib->{$tag}->{$subfield}->{kohafield},
-        index          => $index_tag,
-        id             => "tag_".$tag."_subfield_".$id_subfield."_".$index_tag."_".$index_subfield,
-        value          => $value,
-        maxlength      => $tagslib->{$tag}->{$subfield}->{maxlength},
-        random         => CreateKey(),
-    );
-
-    if(exists $mandatory_z3950->{$tag.$subfield}){
-        $subfield_data{z3950_mandatory} = $mandatory_z3950->{$tag.$subfield};
-    }
-    # Subfield is hidden depending of hidden and mandatory flag, and is always
-    # shown if it contains anything or if its field is mandatory.
-    my $tdef = $tagslib->{$tag};
-    $subfield_data{visibility} = "display:none;"
-        if $tdef->{$subfield}->{hidden} % 2 == 1 &&
-           $value eq '' &&
-           !$tdef->{$subfield}->{mandatory} &&
-           !$tdef->{mandatory};
-    # expand all subfields of 773 if there is a host item provided in the input
-    $subfield_data{visibility} ="" if ($tag eq 773 and $cgi->param('hostitemnumber'));
-
-
-    # it's an authorised field
-    if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
-        $subfield_data{marc_value} =
-          build_authorized_values_list( $tag, $subfield, $value, $dbh,
-            $authorised_values_sth,$index_tag,$index_subfield );
-
-    # it's a subfield $9 linking to an authority record - see bug 2206
-    }
-    elsif ($subfield eq "9" and
-           exists($tagslib->{$tag}->{'a'}->{authtypecode}) and
-           defined($tagslib->{$tag}->{'a'}->{authtypecode}) and
-           $tagslib->{$tag}->{'a'}->{authtypecode} ne '') {
-
-        $subfield_data{marc_value} = {
-            type      => 'text',
-            id        => $subfield_data{id},
-            name      => $subfield_data{id},
-            value     => $value,
-            size      => 5,
-            maxlength => $subfield_data{maxlength},
-            readonly  => 1,
-        };
-
-    # it's a thesaurus / authority field
-    }
-    elsif ( $tagslib->{$tag}->{$subfield}->{authtypecode} ) {
-        # when authorities auto-creation is allowed, do not set readonly
-        my $is_readonly = !C4::Context->preference("BiblioAddsAuthorities");
-
-        $subfield_data{marc_value} = {
-            type      => 'text',
-            id        => $subfield_data{id},
-            name      => $subfield_data{id},
-            value     => $value,
-            size      => 67,
-            maxlength => $subfield_data{maxlength},
-            readonly  => ($is_readonly) ? 1 : 0,
-            authtype  => $tagslib->{$tag}->{$subfield}->{authtypecode},
-        };
-
-    # it's a plugin field
-    } elsif ( $tagslib->{$tag}->{$subfield}->{'value_builder'} ) {
-        require Koha::FrameworkPlugin;
-        my $plugin = Koha::FrameworkPlugin->new( {
-            name => $tagslib->{$tag}->{$subfield}->{'value_builder'},
-        });
-        my $pars= { dbh => $dbh, record => $rec, tagslib => $tagslib,
-            id => $subfield_data{id}, tabloop => $tabloop };
-        $plugin->build( $pars );
-        if( !$plugin->errstr ) {
-            $subfield_data{marc_value} = {
-                type           => 'text_complex',
-                id             => $subfield_data{id},
-                name           => $subfield_data{id},
-                value          => $value,
-                size           => 67,
-                maxlength      => $subfield_data{maxlength},
-                javascript     => $plugin->javascript,
-                noclick        => $plugin->noclick,
-            };
-        } else {
-            warn $plugin->errstr;
-            # supply default input form
-            $subfield_data{marc_value} = {
-                type      => 'text',
-                id        => $subfield_data{id},
-                name      => $subfield_data{id},
-                value     => $value,
-                size      => 67,
-                maxlength => $subfield_data{maxlength},
-                readonly  => 0,
-            };
-        }
-
-    # it's an hidden field
-    } elsif ( $tag eq '' ) {
-        $subfield_data{marc_value} = {
-            type      => 'hidden',
-            id        => $subfield_data{id},
-            name      => $subfield_data{id},
-            value     => $value,
-            size      => 67,
-            maxlength => $subfield_data{maxlength},
-        };
-
-    }
-    else {
-        # it's a standard field
-        if (
-            length($value) > 100
-            or
-            ( C4::Context->preference("marcflavour") eq "UNIMARC" && $tag >= 300
-                and $tag < 400 && $subfield eq 'a' )
-            or (    $tag >= 500
-                and $tag < 600
-                && C4::Context->preference("marcflavour") eq "MARC21" )
-          )
-        {
-            $subfield_data{marc_value} = {
-                type      => 'textarea',
-                id        => $subfield_data{id},
-                name      => $subfield_data{id},
-                value     => $value,
-            };
-
-        }
-        else {
-            $subfield_data{marc_value} = {
-                type      => 'text',
-                id        => $subfield_data{id},
-                name      => $subfield_data{id},
-                value     => $value,
-                size      => 67,
-                maxlength => $subfield_data{maxlength},
-                readonly  => 0,
-            };
-
-        }
-    }
-    $subfield_data{'index_subfield'} = $index_subfield;
-    return \%subfield_data;
-}
-
-
-=head2 format_indicator
-
-Translate indicator value for output form - specifically, map
-indicator = ' ' to ''.  This is for the convenience of a cataloger
-using a mouse to select an indicator input.
-
-=cut
-
-sub format_indicator {
-    my $ind_value = shift;
-    return '' if not defined $ind_value;
-    return '' if $ind_value eq ' ';
-    return $ind_value;
-}
-
-sub build_tabs {
-    my ( $template, $record, $dbh, $encoding,$input ) = @_;
-
-    # fill arrays
-    my @loop_data = ();
-    my $tag;
-
-    my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
-    my $query = "SELECT authorised_value, lib
-                FROM authorised_values";
-    $query .= qq{ LEFT JOIN authorised_values_branches ON ( id = av_id )} if $branch_limit;
-    $query .= " WHERE category = ?";
-    $query .= " AND ( branchcode = ? OR branchcode IS NULL )" if $branch_limit;
-    $query .= " GROUP BY lib ORDER BY lib, lib_opac";
-    my $authorised_values_sth = $dbh->prepare( $query );
-
-    # in this array, we will push all the 10 tabs
-    # to avoid having 10 tabs in the template : they will all be in the same BIG_LOOP
-    my @BIG_LOOP;
-    my %seen;
-    my @tab_data; # all tags to display
-    
-    foreach my $used ( @$usedTagsLib ){
-        push @tab_data,$used->{tagfield} if not $seen{$used->{tagfield}};
-        $seen{$used->{tagfield}}++;
-    }
-        
-    my $max_num_tab=-1;
-    foreach(@$usedTagsLib){
-        if($_->{tab} > -1 && $_->{tab} >= $max_num_tab && $_->{tagfield} != '995'){ # FIXME : MARC21 ?
-            $max_num_tab = $_->{tab}; 
-        }
-    }
-    if($max_num_tab >= 9){
-        $max_num_tab = 9;
-    }
-    # loop through each tab 0 through 9
-    for ( my $tabloop = 0 ; $tabloop <= $max_num_tab ; $tabloop++ ) {
-        my @loop_data = (); #innerloop in the template.
-        my $i = 0;
-        foreach my $tag (@tab_data) {
-            $i++;
-            next if ! $tag;
-            my ($indicator1, $indicator2);
-            my $index_tag = CreateKey;
-
-            # if MARC::Record is not empty =>use it as master loop, then add missing subfields that should be in the tab.
-            # if MARC::Record is empty => use tab as master loop.
-            if ( $record ne -1 && ( $record->field($tag) || $tag eq '000' ) ) {
-                my @fields;
-		if ( $tag ne '000' ) {
-                    @fields = $record->field($tag);
-		}
-		else {
-		   push @fields, $record->leader(); # if tag == 000
-		}
-		# loop through each field
-                foreach my $field (@fields) {
-                    
-                    my @subfields_data;
-                    if ( $tag < 10 ) {
-                        my ( $value, $subfield );
-                        if ( $tag ne '000' ) {
-                            $value    = $field->data();
-                            $subfield = "@";
-                        }
-                        else {
-                            $value    = $field;
-                            $subfield = '@';
-                        }
-                        next if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
-                        next
-                          if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq
-                            'biblio.biblionumber' );
-                        push(
-                            @subfields_data,
-                            &create_input(
-                                $tag, $subfield, $value, $index_tag, $tabloop, $record,
-                                $authorised_values_sth,$input
-                            )
-                        );
-                    }
-                    else {
-                        my @subfields = $field->subfields();
-                        foreach my $subfieldcount ( 0 .. $#subfields ) {
-                            my $subfield = $subfields[$subfieldcount][0];
-                            my $value    = $subfields[$subfieldcount][1];
-                            next if ( length $subfield != 1 );
-                            next if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
-                            push(
-                                @subfields_data,
-                                &create_input(
-                                    $tag, $subfield, $value, $index_tag, $tabloop,
-                                    $record, $authorised_values_sth,$input
-                                )
-                            );
-                        }
-                    }
-
-                    # now, loop again to add parameter subfield that are not in the MARC::Record
-                    foreach my $subfield ( sort( keys %{ $tagslib->{$tag} } ) )
-                    {
-                        next if ( length $subfield != 1 );
-                        next if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
-                        next if ( $tag < 10 );
-                        next
-                          if ( ( $tagslib->{$tag}->{$subfield}->{hidden} <= -4 )
-                            or ( $tagslib->{$tag}->{$subfield}->{hidden} >= 5 ) )
-                            and not ( $subfield eq "9" and
-                                      exists($tagslib->{$tag}->{'a'}->{authtypecode}) and
-                                      defined($tagslib->{$tag}->{'a'}->{authtypecode}) and
-                                      $tagslib->{$tag}->{'a'}->{authtypecode} ne ""
-                                    )
-                          ;    #check for visibility flag
-                               # if subfield is $9 in a field whose $a is authority-controlled,
-                               # always include in the form regardless of the hidden setting - bug 2206
-                        next if ( defined( $field->subfield($subfield) ) );
-                        push(
-                            @subfields_data,
-                            &create_input(
-                                $tag, $subfield, '', $index_tag, $tabloop, $record,
-                                $authorised_values_sth,$input
-                            )
-                        );
-                    }
-                    if ( $#subfields_data >= 0 ) {
-                        # build the tag entry.
-                        # note that the random() field is mandatory. Otherwise, on repeated fields, you'll 
-                        # have twice the same "name" value, and cgi->param() will return only one, making
-                        # all subfields to be merged in a single field.
-                        my %tag_data = (
-                            tag           => $tag,
-                            index         => $index_tag,
-                            tag_lib       => $tagslib->{$tag}->{lib},
-                            repeatable       => $tagslib->{$tag}->{repeatable},
-                            mandatory       => $tagslib->{$tag}->{mandatory},
-                            subfield_loop => \@subfields_data,
-                            fixedfield    => $tag < 10?1:0,
-                            random        => CreateKey,
-                        );
-                        if ($tag >= 10){ # no indicator for 00x tags
-                           $tag_data{indicator1} = format_indicator($field->indicator(1)),
-                           $tag_data{indicator2} = format_indicator($field->indicator(2)),
-                        }
-                        push( @loop_data, \%tag_data );
-                    }
-                 } # foreach $field end
-
-            # if breeding is empty
-            }
-            else {
-                my @subfields_data;
-                foreach my $subfield ( sort( keys %{ $tagslib->{$tag} } ) ) {
-                    next if ( length $subfield != 1 );
-                    next
-                      if ( ( $tagslib->{$tag}->{$subfield}->{hidden} <= -4 )
-                        or ( $tagslib->{$tag}->{$subfield}->{hidden} >= 5 ) )
-                      and not ( $subfield eq "9" and
-                                exists($tagslib->{$tag}->{'a'}->{authtypecode}) and
-                                defined($tagslib->{$tag}->{'a'}->{authtypecode}) and
-                                $tagslib->{$tag}->{'a'}->{authtypecode} ne ""
-                              )
-                      ;    #check for visibility flag
-                           # if subfield is $9 in a field whose $a is authority-controlled,
-                           # always include in the form regardless of the hidden setting - bug 2206
-                    next
-                      if ( $tagslib->{$tag}->{$subfield}->{tab} ne $tabloop );
-			push(
-                        @subfields_data,
-                        &create_input(
-                            $tag, $subfield, '', $index_tag, $tabloop, $record,
-                            $authorised_values_sth,$input
-                        )
-                    );
-                }
-                if ( $#subfields_data >= 0 ) {
-                    my %tag_data = (
-                        tag              => $tag,
-                        index            => $index_tag,
-                        tag_lib          => $tagslib->{$tag}->{lib},
-                        repeatable       => $tagslib->{$tag}->{repeatable},
-                        mandatory       => $tagslib->{$tag}->{mandatory},
-                        indicator1       => ( $indicator1 || $tagslib->{$tag}->{ind1_defaultvalue} ), #if not set, try to load the default value
-                        indicator2       => ( $indicator2 || $tagslib->{$tag}->{ind2_defaultvalue} ), #use short-circuit operator for efficiency
-                        subfield_loop    => \@subfields_data,
-                        tagfirstsubfield => $subfields_data[0],
-                        fixedfield       => $tag < 10?1:0,
-                    );
-                    
-                    push @loop_data, \%tag_data ;
-                }
-            }
-        }
-        if ( $#loop_data >= 0 ) {
-            push @BIG_LOOP, {
-                number    => $tabloop,
-                innerloop => \@loop_data,
-            };
-        }
-    }
-    $authorised_values_sth->finish;
-    $template->param( BIG_LOOP => \@BIG_LOOP );
-}
-
 # ========================
 #          MAIN
 #=========================
@@ -748,11 +256,13 @@ $template->param(
     breedingid => $breedingid,
 );
 
-# ++ Global
-$tagslib         = &GetMarcStructure( 1, $frameworkcode );
-$usedTagsLib     = &GetUsedMarcStructure( $frameworkcode );
-$mandatory_z3950 = GetMandatoryFieldZ3950($frameworkcode);
-# -- Global
+my $editor = Koha::MarcEditor->new({
+    type => $Koha::MarcEditor::EDITOR_BIBLIO,
+    tags => GetMarcStructure( 1, $frameworkcode ),
+    used_tags => GetUsedMarcStructure( $frameworkcode ),
+    mandatory_z3950 => GetMandatoryFieldZ3950($frameworkcode),
+    hostitemnumber => $hostitemnumber
+});
 
 my $record   = -1;
 my $encoding = "";
@@ -894,7 +404,7 @@ if ( $op eq "addbiblio" ) {
         }
     } else {
     # it may be a duplicate, warn the user and do nothing
-        build_tabs ($template, $record, $dbh,$encoding,$input);
+        $editor->build_tabs($template, $record, $encoding);
         $template->param(
             biblionumber             => $biblionumber,
             biblioitemnumber         => $biblioitemnumber,
@@ -941,7 +451,7 @@ elsif ( $op eq "delete" ) {
             $record = $urecord;
         };
     }
-    build_tabs( $template, $record, $dbh, $encoding,$input );
+    $editor->build_tabs( $template, $record, $encoding );
     $template->param(
         biblionumber             => $biblionumber,
         biblionumbertagfield        => $biblionumbertagfield,
-- 
2.17.1