Bug 35345: Add --where option to rebuild_elasticsearch.pl
[koha.git] / misc / export_records.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
7 # under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # Koha is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with Koha; if not, see <http://www.gnu.org/licenses>.
18
19 use Modern::Perl;
20 use MARC::File::XML;
21 use List::MoreUtils qw( uniq );
22 use Getopt::Long qw( GetOptions );
23 use Pod::Usage qw( pod2usage );
24
25 use Koha::Script;
26 use C4::Auth;
27 use C4::Context;
28 use C4::Record;
29
30 use Koha::Biblioitems;
31 use Koha::Database;
32 use Koha::CsvProfiles;
33 use Koha::Exporter::Record;
34 use Koha::DateUtils qw( dt_from_string output_pref );
35
36 my (
37     $output_format,
38     $timestamp,
39     $dont_export_items,
40     $csv_profile_id,
41     $deleted_barcodes,
42     $clean,
43     $filename,
44     $record_type,
45     $id_list_file,
46     $starting_authid,
47     $ending_authid,
48     $authtype,
49     $starting_biblionumber,
50     $ending_biblionumber,
51     $itemtype,
52     $starting_callnumber,
53     $ending_callnumber,
54     $start_accession,
55     $end_accession,
56     $marc_conditions,
57     $embed_see_from_headings,
58     $help
59 );
60
61 GetOptions(
62     'format=s'                => \$output_format,
63     'date=s'                  => \$timestamp,
64     'dont_export_items'       => \$dont_export_items,
65     'csv_profile_id=s'        => \$csv_profile_id,
66     'deleted_barcodes'        => \$deleted_barcodes,
67     'clean'                   => \$clean,
68     'filename=s'              => \$filename,
69     'record-type=s'           => \$record_type,
70     'id_list_file=s'          => \$id_list_file,
71     'starting_authid=s'       => \$starting_authid,
72     'ending_authid=s'         => \$ending_authid,
73     'authtype=s'              => \$authtype,
74     'starting_biblionumber=s' => \$starting_biblionumber,
75     'ending_biblionumber=s'   => \$ending_biblionumber,
76     'itemtype=s'              => \$itemtype,
77     'starting_callnumber=s'   => \$starting_callnumber,
78     'ending_callnumber=s'     => \$ending_callnumber,
79     'start_accession=s'       => \$start_accession,
80     'end_accession=s'         => \$end_accession,
81     'marc_conditions=s'       => \$marc_conditions,
82     'embed_see_from_headings' => \$embed_see_from_headings,
83     'h|help|?'                => \$help
84 ) || pod2usage(1);
85
86 if ($help) {
87     pod2usage(1);
88 }
89
90 $filename ||= 'koha.mrc';
91 $output_format ||= 'iso2709';
92 $record_type ||= 'bibs';
93
94 # Retrocompatibility for the format parameter
95 $output_format = 'iso2709' if $output_format eq 'marc';
96
97 if ( $output_format eq 'csv' and $record_type eq 'auths' ) {
98     pod2usage(q|CSV output is only available for biblio records|);
99 }
100
101 if ( $output_format eq 'csv' and not $csv_profile_id ) {
102     pod2usage(q|Define a csv profile to export in CSV|);
103 }
104
105
106 if ( $record_type ne 'bibs' and $record_type ne 'auths' ) {
107     pod2usage(q|--record_type is not valid|);
108 }
109
110 if ( $deleted_barcodes and $record_type ne 'bibs' ) {
111     pod2usage(q|--deleted_barcodes can only be used with biblios|);
112 }
113
114 $start_accession = dt_from_string( $start_accession ) if $start_accession;
115 $end_accession   = dt_from_string( $end_accession )   if $end_accession;
116
117 # Parse marc conditions
118 my @marc_conditions;
119 if ($marc_conditions) {
120     foreach my $condition (split(/,\s*/, $marc_conditions)) {
121         if ($condition =~ /^(\d{3})([\w\d]?)(=|(?:!=)|>|<)([^,]+)$/) {
122             push @marc_conditions, [$1, $2, $3, $4];
123         }
124         elsif ($condition =~ /^(exists|not_exists)\((\d{3})([\w\d]?)\)$/) {
125             push @marc_conditions, [$2, $3, $1 eq 'exists' ? '?' : '!?'];
126         }
127         else {
128             die("Invalid condititon: $condition");
129         }
130     }
131 }
132
133 my $dbh = C4::Context->dbh;
134
135 # Redirect stdout
136 open STDOUT, '>', $filename if $filename;
137
138
139 my @record_ids;
140
141 $timestamp = ($timestamp) ? output_pref({ dt => dt_from_string($timestamp), dateformat => 'iso', dateonly => 0, }): '';
142
143 if ( $record_type eq 'bibs' ) {
144     if ( $timestamp ) {
145         if (!$dont_export_items) {
146             push @record_ids, $_->{biblionumber} for @{
147                 $dbh->selectall_arrayref(q| (
148                     SELECT biblio_metadata.biblionumber
149                     FROM biblio_metadata
150                       LEFT JOIN items USING(biblionumber)
151                     WHERE biblio_metadata.timestamp >= ?
152                       OR items.timestamp >= ?
153                 ) UNION (
154                     SELECT biblio_metadata.biblionumber
155                     FROM biblio_metadata
156                       LEFT JOIN deleteditems USING(biblionumber)
157                     WHERE biblio_metadata.timestamp >= ?
158                       OR deleteditems.timestamp >= ?
159                 ) |, { Slice => {} }, ( $timestamp ) x 4 );
160             };
161         } else {
162             push @record_ids, $_->{biblionumber} for @{
163                 $dbh->selectall_arrayref(q| (
164                     SELECT biblio_metadata.biblionumber
165                     FROM biblio_metadata
166                     WHERE biblio_metadata.timestamp >= ?
167                 ) |, { Slice => {} }, $timestamp );
168             };
169         }
170     } else {
171         my $conditions = {
172             ( $starting_biblionumber or $ending_biblionumber )
173                 ? (
174                     "me.biblionumber" => {
175                         ( $starting_biblionumber ? ( '>=' => $starting_biblionumber ) : () ),
176                         ( $ending_biblionumber   ? ( '<=' => $ending_biblionumber   ) : () ),
177                     }
178                 )
179                 : (),
180             ( $starting_callnumber or $ending_callnumber )
181                 ? (
182                     callnumber => {
183                         ( $starting_callnumber ? ( '>=' => $starting_callnumber ) : () ),
184                         ( $ending_callnumber   ? ( '<=' => $ending_callnumber   ) : () ),
185                     }
186                 )
187                 : (),
188             ( $start_accession or $end_accession )
189                 ? (
190                     dateaccessioned => {
191                         ( $start_accession ? ( '>=' => $start_accession ) : () ),
192                         ( $end_accession   ? ( '<=' => $end_accession   ) : () ),
193                     }
194                 )
195                 : (),
196             ( $itemtype
197                 ?
198                   C4::Context->preference('item-level_itypes')
199                     ? ( 'items.itype' => $itemtype )
200                     : ( 'me.itemtype' => $itemtype )
201                 : ()
202             ),
203
204         };
205         my $biblioitems = Koha::Biblioitems->search( $conditions, { join => 'items' } );
206         while ( my $biblioitem = $biblioitems->next ) {
207             push @record_ids, $biblioitem->biblionumber;
208         }
209     }
210 }
211 elsif ( $record_type eq 'auths' ) {
212     if ($timestamp) {
213         push @record_ids, $_->{authid} for @{
214             $dbh->selectall_arrayref(
215                 q| (
216                 SELECT authid
217                 FROM auth_header
218                 WHERE modification_time >= ?
219             ) |, { Slice => {} }, $timestamp
220             );
221         };
222     } else {
223         my $conditions = {
224             ( $starting_authid or $ending_authid )
225             ? (
226                 authid => {
227                     ( $starting_authid ? ( '>=' => $starting_authid ) : () ),
228                     ( $ending_authid   ? ( '<=' => $ending_authid )   : () ),
229                 }
230                 )
231             : (),
232             ( $authtype ? ( authtypecode => $authtype ) : () ),
233         };
234
235         # Koha::MetadataRecord::Authority is not a Koha::Object...
236         my $authorities = Koha::Database->new->schema->resultset('AuthHeader')->search($conditions);
237         @record_ids = map { $_->authid } $authorities->all;
238     }
239 }
240
241 @record_ids = uniq @record_ids;
242 if ( @record_ids and $id_list_file ) {
243     open my $fh, '<', $id_list_file or die "Cannot open file $id_list_file ($!)";
244     my @filter_record_ids = <$fh>;
245     @filter_record_ids = map { my $id = $_; $id =~ s/[\r\n]*$//; $id } @filter_record_ids;
246     # intersection
247     my %record_ids = map { $_ => 1 } @record_ids;
248     @record_ids = grep $record_ids{$_}, @filter_record_ids;
249 }
250
251 if ($deleted_barcodes) {
252     for my $record_id ( @record_ids ) {
253         my $barcode = $dbh->selectall_arrayref(q|
254             SELECT DISTINCT barcode
255             FROM deleteditems
256             WHERE deleteditems.biblionumber = ?
257             AND barcode IS NOT NULL AND barcode != ''
258         |, { Slice => {} }, $record_id );
259         say $_->{barcode} for @$barcode;
260     }
261 }
262 else {
263     Koha::Exporter::Record::export(
264         {   record_type        => $record_type,
265             record_ids         => \@record_ids,
266             record_conditions  => @marc_conditions ? \@marc_conditions : undef,
267             format             => $output_format,
268             csv_profile_id     => $csv_profile_id,
269             export_items       => (not $dont_export_items),
270             clean              => $clean || 0,
271             embed_see_from_headings => $embed_see_from_headings || 0,
272         }
273     );
274 }
275 exit;
276
277
278 =head1 NAME
279
280 export records - This script exports record (biblios or authorities)
281
282 =head1 SYNOPSIS
283
284 export_records.pl [-h|--help] [--format=format] [--date=datetime] [--record-type=TYPE] [--dont_export_items] [--deleted_barcodes] [--clean] [--id_list_file=PATH] --filename=outputfile
285
286 =head1 OPTIONS
287
288 =over
289
290 =item B<-h|--help>
291
292 Print a brief help message.
293
294 =item B<--format>
295
296  --format=FORMAT        FORMAT is either 'xml', 'csv' (biblio records only) or 'marc' (default).
297
298 =item B<--date>
299
300  --date=DATETIME        DATETIME should be entered as the 'dateformat' syspref is
301                         set (dd/mm/yyyy[ hh:mm:ss] for metric, yyyy-mm-dd[ hh:mm:ss] for iso,
302                         mm/dd/yyyy[ hh:mm:ss] for us) records exported are the ones that
303                         have been modified since DATETIME.
304
305 =item B<--record-type>
306
307  --record-type=TYPE     TYPE is 'bibs' or 'auths'.
308
309 =item B<--dont_export_items>
310
311  --dont_export_items    If enabled, the item infos won't be exported.
312
313 =item B<--csv_profile_id>
314
315  --csv_profile_id=ID    Generate a CSV file with the given CSV profile id (see tools/csv-profiles.pl)
316                         This can only be used to export biblio records.
317
318 =item B<--deleted_barcodes>
319
320  --deleted_barcodes     If used, a list of barcodes of items deleted since DATE
321                         is produced (or from all deleted items if no date is
322                         specified). Used only if TYPE is 'bibs'.
323
324 =item B<--clean>
325
326  --clean                removes NSE/NSB.
327
328 =item B<--id_list_file>
329
330  --id_list_file=PATH    PATH is a path to a file containing a list of
331                         IDs (biblionumber or authid) with one ID per line.
332                         This list works as a filter; it is compatible with
333                         other parameters for selecting records.
334
335 =item B<--filename>
336
337  --filename=FILENAME   FILENAME used to export the data.
338
339 =item B<--starting_authid>
340
341  --starting_authid=ID  Export authorities with authid >= ID
342
343 =item B<--ending_authid>
344
345  --ending_authid=ID    Export authorities with authid <= ID
346
347 =item B<--authtype>
348
349  --authtype=AUTHTYPE   Export authorities from the given AUTHTYPE
350
351 =item B<--starting_biblionumber>
352
353  --starting_biblionumber=ID  Export biblio with biblionumber >= ID
354
355 =item B<--ending_biblionumber>
356
357  --ending_biblionumber=ID    Export biblio with biblionumber <= ID
358
359 =item B<--itemtype>
360
361  --itemtype=ITEMTYPE         Export biblio from the given ITEMTYPE
362
363 =item B<--starting_callnumber>
364
365  --starting_callnumber=CALLNUMBER Export biblio with callnumber >=CALLNUMBER
366
367 =item B<--ending_callnumber>
368
369  --ending_callnumber=CALLNUMBER Export biblio with callnumber <=CALLNUMBER
370
371 =item B<--start_accession>
372
373  --starting_accession=DATE      Export biblio with an item accessionned after DATE
374
375 =item B<--end_accession>
376
377  --end_accession=DATE           Export biblio with an item accessionned after DATE
378
379 =item B<--marc_conditions>
380
381  --marc_conditions=CONDITIONS   Only include biblios with MARC data matching CONDITIONS.
382                                 CONDITIONS is on the format: <marc_target><binary_operator><value>,
383                                 or <unary_operation>(<marc_target>).
384                                 with multiple conditions separated by commas (,).
385                                 For example: --marc_conditions="035a!=(EXAMPLE)123,041a=swe".
386                                 Multiple conditions are all required to match.
387                                 If <marc_target> has multiple values all values
388                                 are also required to match.
389                                 Valid operators are: = (equal to), != (not equal to),
390                                 > (great than) and < (less than).
391
392                                 Two unary operations are also supported:
393                                 exists(<marc_target>) and not_exists(<marc_target>).
394                                 For example: --marc_conditions="exists(035a)".
395
396                                 "exists(<marc_target)" will include marc records where
397                                 <marc_target> exists regardless of target value, and
398                                 "exists(<marc_target>)" will include marc records where
399                                 no <marc_target> exists.
400
401 =item B<--embed_see_from_headings>
402
403  --embed_see_from_headings      Embed see from (non-preferred form) headings in bibliographic record.
404
405 =back
406
407 =head1 AUTHOR
408
409 Koha Development Team
410
411 =head1 COPYRIGHT
412
413 Copyright Koha Team
414
415 =head1 LICENSE
416
417 This file is part of Koha.
418
419 # Koha is free software; you can redistribute it and/or modify it
420 # under the terms of the GNU General Public License as published by
421 # the Free Software Foundation; either version 3 of the License, or
422 # (at your option) any later version.
423 #
424 # Koha is distributed in the hope that it will be useful, but
425 # WITHOUT ANY WARRANTY; without even the implied warranty of
426 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
427 # GNU General Public License for more details.
428 #
429 # You should have received a copy of the GNU General Public License
430 # along with Koha; if not, see <http://www.gnu.org/licenses>.
431
432 =cut