Merge remote-tracking branch 'origin/new/bug_7889'
[koha.git] / tools / export.pl
1 #!/usr/bin/perl
2
3 #
4 # This file is part of Koha.
5 #
6 # Koha is free software; you can redistribute it and/or modify it under the
7 # terms of the GNU General Public License as published by the Free Software
8 # Foundation; either version 2 of the License, or (at your option) any later
9 # version.
10 #
11 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
12 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along with
16 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
17 # Suite 330, Boston, MA  02111-1307 USA
18
19
20 use strict;
21 use warnings;
22 use C4::Auth;
23 use C4::Output;
24 use C4::Biblio;  # GetMarcBiblio GetXmlBiblio
25 use C4::AuthoritiesMarc; # GetAuthority
26 use CGI;
27 use C4::Koha;    # GetItemTypes
28 use C4::Branch;  # GetBranches
29 use Data::Dumper;
30
31 my $query = new CGI;
32 my $op=$query->param("op") || '';
33 my $filename=$query->param("filename");
34 $filename =~ s/(\r|\n)//;
35 my $dbh=C4::Context->dbh;
36 my $marcflavour = C4::Context->preference("marcflavour");
37
38 my ($template, $loggedinuser, $cookie, $flags)
39     = get_template_and_user
40     (
41         {
42             template_name => "tools/export.tmpl",
43             query => $query,
44             type => "intranet",
45             authnotrequired => 0,
46             flagsrequired => {tools => 'export_catalog'},
47             debug => 1,
48             }
49     );
50
51         my $limit_ind_branch=(C4::Context->preference('IndependantBranches') &&
52               C4::Context->userenv &&
53               !(C4::Context->userenv->{flags} & 1) &&
54               C4::Context->userenv->{branch}?1:0);
55         my $branches = GetBranches($limit_ind_branch);    
56     my $branch                = $query->param("branch") || '';
57         if ( C4::Context->preference("IndependantBranches") &&
58          !(C4::Context->userenv->{flags} & 1) ) {
59         $branch = C4::Context->userenv->{'branch'};
60         }
61
62 my $backupdir = C4::Context->config('backupdir');
63
64 if ($op eq "export") {
65     my $charset  = 'utf-8';
66     my $mimetype = 'application/octet-stream';
67     binmode STDOUT, ':encoding(UTF-8)';
68     if ( $filename =~ m/\.gz$/ ) {
69         $mimetype = 'application/x-gzip';
70         $charset = '';
71         binmode STDOUT;
72     } elsif ( $filename =~ m/\.bz2$/ ) {
73         $mimetype = 'application/x-bzip2';
74         binmode STDOUT;
75         $charset = '';
76     }
77     print $query->header(   -type => $mimetype,
78                             -charset => $charset,
79                             -attachment=>$filename);
80      
81     my $record_type        = $query->param("record_type");
82     my $output_format      = $query->param("output_format");
83     my $dont_export_fields = $query->param("dont_export_fields");
84     my @sql_params;
85     my $sql_query;
86
87     my $StartingBiblionumber = $query->param("StartingBiblionumber");
88     my $EndingBiblionumber   = $query->param("EndingBiblionumber");
89     my $itemtype             = $query->param("itemtype");
90     my $start_callnumber     = $query->param("start_callnumber");
91     my $end_callnumber       = $query->param("end_callnumber");
92     my $start_accession =
93       ( $query->param("start_accession") )
94       ? C4::Dates->new( $query->param("start_accession") )
95       : '';
96     my $end_accession =
97       ( $query->param("end_accession") )
98       ? C4::Dates->new( $query->param("end_accession") )
99       : '';
100     my $dont_export_items    = $query->param("dont_export_item");
101     my $strip_nonlocal_items = $query->param("strip_nonlocal_items");
102
103     my $starting_authid = $query->param('starting_authid');
104     my $ending_authid   = $query->param('ending_authid');
105     my $authtype        = $query->param('authtype');
106
107     if ( $record_type eq 'bibs' ) {
108         my $items_filter =
109             $branch || $start_callnumber || $end_callnumber ||
110             $start_accession || $end_accession ||
111             ($itemtype && C4::Context->preference('item-level_itypes'));
112         $sql_query = $items_filter ?
113             "SELECT DISTINCT biblioitems.biblionumber
114             FROM biblioitems JOIN items
115             USING (biblionumber) WHERE 1"
116             :
117             "SELECT biblioitems.biblionumber FROM biblioitems WHERE biblionumber >0 ";
118
119         if ( $StartingBiblionumber ) {
120             $sql_query .= " AND biblioitems.biblionumber >= ? ";
121             push @sql_params, $StartingBiblionumber;
122         }
123
124         if ( $EndingBiblionumber ) {
125             $sql_query .= " AND biblioitems.biblionumber <= ? ";
126             push @sql_params, $EndingBiblionumber;
127         }
128
129         if ($branch) {
130             $sql_query .= " AND homebranch = ? ";
131             push @sql_params, $branch;
132         }
133
134         if ($start_callnumber) {
135             $sql_query .= " AND itemcallnumber <= ? ";
136             push @sql_params, $start_callnumber;
137         }
138
139         if ($end_callnumber) {
140             $sql_query .= " AND itemcallnumber >= ? ";
141             push @sql_params, $end_callnumber;
142         }
143         if ($start_accession) {
144             $sql_query .= " AND dateaccessioned >= ? ";
145             push @sql_params, $start_accession->output('iso');
146         }
147
148         if ($end_accession) {
149             $sql_query .= " AND dateaccessioned <= ? ";
150             push @sql_params, $end_accession->output('iso');
151         }
152
153         if ( $itemtype ) {
154             $sql_query .= (C4::Context->preference('item-level_itypes')) ? " AND items.itype = ? " : " AND biblioitems.itemtype = ?";
155             push @sql_params, $itemtype;
156         }
157     }
158     elsif ( $record_type eq 'auths' ) {
159         $sql_query =
160           "SELECT DISTINCT auth_header.authid FROM auth_header WHERE 1";
161
162         if ($starting_authid) {
163             $sql_query .= " AND auth_header.authid >= ? ";
164             push @sql_params, $starting_authid;
165         }
166
167         if ($ending_authid) {
168             $sql_query .= " AND auth_header.authid <= ? ";
169             push @sql_params, $ending_authid;
170         }
171
172         if ($authtype) {
173             $sql_query .= " AND auth_header.authtypecode = ? ";
174             push @sql_params, $authtype;
175         }
176     }
177     elsif ( $record_type eq 'db' ) {
178         my $successful_export;
179         if ( $flags->{superlibrarian} && C4::Context->config('backup_db_via_tools') ) {
180             $successful_export = download_backup( { directory => "$backupdir", extension => 'sql', filename => "$filename" } )
181         }
182         unless ( $successful_export ) {
183             my $remotehost = $query->remote_host();
184             $remotehost =~ s/(\n|\r)//;
185             warn "A suspicious attempt was made to download the db at '$filename' by someone at " . $remotehost . "\n";
186         }
187         exit;
188     }
189     elsif ( $record_type eq 'conf' ) {
190         my $successful_export;
191         if ( $flags->{superlibrarian} && C4::Context->config('backup_conf_via_tools') ) {
192             $successful_export = download_backup( { directory => "$backupdir", extension => 'tar', filename => "$filename" } )
193         }
194         unless ( $successful_export ) {
195             my $remotehost = $query->remote_host();
196             $remotehost =~ s/(\n|\r)//;
197             warn "A suspicious attempt was made to download the configuration at '$filename' by someone at " . $remotehost . "\n";
198         }
199         exit;
200     }
201     else {
202         # Someone is trying to mess us up
203         exit;
204     }
205
206     my $sth = $dbh->prepare($sql_query);
207     $sth->execute(@sql_params);
208
209     while ( my ($recordid) = $sth->fetchrow ) {
210         my $record;
211         if ( $record_type eq 'bibs' ) {
212             $record = eval { GetMarcBiblio($recordid); };
213
214      # FIXME: decide how to handle records GetMarcBiblio can't parse or retrieve
215             if ($@) {
216                 next;
217             }
218             next if not defined $record;
219             C4::Biblio::EmbedItemsInMarcBiblio( $record, $recordid )
220               unless $dont_export_items;
221             if ( $strip_nonlocal_items || $limit_ind_branch ) {
222                 my ( $homebranchfield, $homebranchsubfield ) =
223                   GetMarcFromKohaField( 'items.homebranch', '' );
224                 for my $itemfield ( $record->field($homebranchfield) ) {
225
226 # if stripping nonlocal items, use loggedinuser's branch if they didn't select one
227                     $branch = C4::Context->userenv->{'branch'} unless $branch;
228                     $record->delete_field($itemfield)
229                       if (
230                         $itemfield->subfield($homebranchsubfield) ne $branch );
231                 }
232             }
233         }
234         elsif ( $record_type eq 'auths' ) {
235             $record = C4::AuthoritiesMarc::GetAuthority($recordid);
236             next if not defined $record;
237         }
238
239         if ( $dont_export_fields ) {
240             my @fields = split " ", $dont_export_fields;
241             foreach ( @fields ) {
242                 /^(\d*)(\w)?$/;
243                 my $field = $1;
244                 my $subfield = $2;
245                 # skip if this record doesn't have this field
246                 next if not defined $record->field($field);
247                 if( $subfield ) {
248                     $record->field($field)->delete_subfields($subfield);
249                 }
250                 else {
251                     $record->delete_field($record->field($field));
252                 }
253             }
254         }
255         if ( $output_format eq "xml" ) {
256             if ($marcflavour eq 'UNIMARC' && $record_type eq 'auths') {
257                 print $record->as_xml_record('UNIMARCAUTH');
258             } else {
259                 print $record->as_xml_record($marcflavour);
260             }
261         }
262         else {
263             print $record->as_usmarc();
264         }
265     }
266     exit;
267
268 }    # if export
269
270 else {
271
272     my $itemtypes = GetItemTypes;
273     my @itemtypesloop;
274     foreach my $thisitemtype (sort keys %$itemtypes) {
275         my %row =
276             (
277                 value => $thisitemtype,
278                 description => $itemtypes->{$thisitemtype}->{'description'},
279             );
280        push @itemtypesloop, \%row;
281     }
282     my @branchloop;
283     for my $thisbranch (
284         sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} }
285         keys %{$branches}
286       ) {
287         push @branchloop,
288           { value      => $thisbranch,
289             selected   => $thisbranch eq $branch,
290             branchname => $branches->{$thisbranch}->{'branchname'},
291           };
292     }
293
294     my $authtypes = getauthtypes;
295     my @authtypesloop;
296     foreach my $thisauthtype ( sort keys %$authtypes ) {
297         next unless $thisauthtype;
298         my %row = (
299             value       => $thisauthtype,
300             description => $authtypes->{$thisauthtype}->{'authtypetext'},
301         );
302         push @authtypesloop, \%row;
303     }
304
305     if ( $flags->{superlibrarian} && C4::Context->config('backup_db_via_tools') && $backupdir && -d $backupdir ) {
306         $template->{VARS}->{'allow_db_export'} = 1;
307         $template->{VARS}->{'dbfiles'} = getbackupfilelist( { directory => "$backupdir", extension => 'sql' } );
308     }
309
310     if ( $flags->{superlibrarian} && C4::Context->config('backup_conf_via_tools') && $backupdir && -d $backupdir ) {
311         $template->{VARS}->{'allow_conf_export'} = 1;
312         $template->{VARS}->{'conffiles'} = getbackupfilelist( { directory => "$backupdir", extension => 'tar' } );
313     }
314
315     $template->param(
316         branchloop               => \@branchloop,
317         itemtypeloop             => \@itemtypesloop,
318         DHTMLcalendar_dateformat => C4::Dates->DHTMLcalendar(),
319         authtypeloop             => \@authtypesloop,
320     );
321
322     output_html_with_http_headers $query, $cookie, $template->output;
323 }
324
325 sub getbackupfilelist {
326     my $args = shift;
327     my $directory = $args->{directory};
328     my $extension = $args->{extension};
329     my @files;
330
331     if ( opendir(my $dir, $directory) ) {
332         while (my $file = readdir($dir)) {
333             next unless ( $file =~ m/\.$extension(\.(gz|bz2|xz))?/ );
334             push @files, $file if ( -f "$backupdir/$file" && -r "$backupdir/$file" );
335         }
336         closedir($dir);
337     }
338     return \@files;
339 }
340
341 sub download_backup {
342     my $args = shift;
343     my $directory = $args->{directory};
344     my $extension = $args->{extension};
345     my $filename  = $args->{filename};
346
347     return unless ( $directory && -d $directory );
348     return unless ( $filename =~ m/$extension(\.(gz|bz2|xz))?$/ && not $filename =~ m#|# );
349     $filename = "$directory/$filename";
350     return unless ( -f $filename && -r $filename );
351     return unless ( open(my $dump, '<', $filename) );
352     binmode $dump;
353     while (read($dump, my $data, 64 * 1024)) {
354         print $data;
355     }
356     close ($dump);
357     return 1;
358 }