LP1808006 - Sitemap generator fix to allow shortname parameter.
[Evergreen.git] / Open-ILS / src / support-scripts / sitemap_generator
1 #!/usr/bin/perl
2 # Copyright (C) 2014 Laurentian University
3 # Author: Dan Scott <dscott@laurentian.ca>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 use strict; use warnings;
16 use XML::LibXML;
17 use File::Copy;
18 use Getopt::Long;
19 use File::Spec;
20 use File::Basename;
21 use DBI qw(:sql_types);
22 use DBD::Pg qw(:pg_types);
23
24 my ($dbhost, $dbport, $dbname, $dbuser, $dbpw, $help);
25 my $config_file = '';
26 my $sysconfdir = '';
27
28 =item create_sitemaps() - Write the sitemap files
29
30 With a maximum of 50,000 URLs per sitemap, this method
31 automatically increments the sitemap file numbers and
32 generates a corresponding sitemap index that lists all
33 of the individual sitemap files.
34
35 See http://www.sitemaps.org/ for the specification
36
37 =cut
38 sub create_sitemaps {
39     my ($settings, $bibs, $aou_id) = @_;
40
41     my $f_cnt = 1;
42     my $r_cnt = 0;
43     my @sitemaps;
44     my $fn = $settings->{'prefix'} . "sitemap$f_cnt.xml";
45     push(@sitemaps, $fn);
46     open(FH, '>', $fn) or die "Could not write sitemap $f_cnt\n";
47     print FH '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
48     print FH '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
49
50     foreach my $bib (@$bibs) {
51         print FH "<url><loc>" . $settings->{'lib-hostname'} . "/eg/opac/record/" . $bib->[0];
52         if ($aou_id) {
53             print FH "?locg=$aou_id";
54         }
55         print FH "</loc><lastmod>" . $bib->[1] . "</lastmod></url>\n";
56         $r_cnt++;
57         if ($r_cnt % 50000 == 0) {
58             $f_cnt++;
59             print FH "</urlset>\n";
60             close(FH);
61             my $fn = $settings->{'prefix'} . "sitemap$f_cnt.xml";
62             push(@sitemaps, $fn);
63             open(FH, '>', $fn) or die "Could not write bibs\n";
64             print FH '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
65             print FH '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
66         }
67     }
68     print FH "</urlset>\n";
69     close(FH);
70
71     open(INDEXFH, '>', $settings->{'prefix'} . "sitemapindex.xml") or die "Could not write sitemap index\n";
72     print INDEXFH '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
73     print INDEXFH '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
74     foreach my $fn (@sitemaps) {
75         print INDEXFH "<sitemap><loc>" . $settings->{'lib-hostname'} . "/$fn</loc></sitemap>\n";
76     }
77     print INDEXFH "</sitemapindex>\n";
78     close(INDEXFH);
79     
80
81 }
82
83 =item get_settings() - Extracts database settings from opensrf.xml
84 =cut
85 sub get_settings {
86     my $settings = shift;
87
88     my $host = "/opensrf/default/apps/open-ils.reporter-store/app_settings/database/host/text()";
89     my $port = "/opensrf/default/apps/open-ils.reporter-store/app_settings/database/port/text()";
90     my $dbname = "/opensrf/default/apps/open-ils.reporter-store/app_settings/database/db/text()";
91     my $user = "/opensrf/default/apps/open-ils.reporter-store/app_settings/database/user/text()";
92     my $pw = "/opensrf/default/apps/open-ils.reporter-store/app_settings/database/pw/text()";
93
94     my $parser = XML::LibXML->new();
95     my $opensrf_config = $parser->parse_file($config_file);
96
97     # If the user passed in settings at the command line,
98     # we don't want to override them
99     $settings->{host} = $settings->{host} || $opensrf_config->findnodes($host);
100     $settings->{port} = $settings->{port} || $opensrf_config->findnodes($port);
101     $settings->{db} = $settings->{db} || $opensrf_config->findnodes($dbname);
102     $settings->{user} = $settings->{user} || $opensrf_config->findnodes($user);
103     $settings->{pw} = $settings->{pw} || $opensrf_config->findnodes($pw);
104 }
105
106 =item get_record_ids() - Gets a list of record IDs
107 =cut
108 sub get_record_ids {
109     my $settings = shift;
110     my $aou_id;
111
112     my $dbh = DBI->connect('dbi:Pg:dbname=' . $settings->{db} . 
113         ';host=' . $settings->{host} . ';port=' . $settings->{port} . ';',
114          $settings->{user} . "", $settings->{pw} . "", {AutoCommit => 1}
115     );
116     if ($dbh->err) {
117         print STDERR "Could not connect to database. ";
118         print STDERR "Error was " . $dbh->errstr . "\n";
119         return;
120     }
121
122     if ($settings->{'lib-shortname'}) {
123         my $stmt = $dbh->prepare("SELECT id FROM actor.org_unit WHERE shortname = ?");
124         $stmt->execute(($settings->{'lib-shortname'}));
125         my $rv = $stmt->bind_columns(\$aou_id);
126         $stmt->fetch();
127     }
128
129     my $q = "
130         WITH date_floor AS (
131             SELECT ?::date AS val
132         )
133     ";
134     if ($aou_id) {
135         $q .= "
136         , copy_orgs AS (
137             SELECT id
138             FROM actor.org_unit
139             WHERE id IN (SELECT id FROM actor.org_unit_descendants(?))
140         ),
141         uri_orgs AS (
142             SELECT id
143             FROM actor.org_unit
144             WHERE id IN (SELECT id FROM actor.org_unit_ancestors(?))
145                 AND id NOT IN (SELECT id FROM org_top())
146         )
147         ";
148     }
149     $q .= "
150         SELECT DISTINCT id, edit_date FROM (
151             SELECT bre.id,
152                 CASE
153                     WHEN bre.edit_date::date < (SELECT val FROM date_floor LIMIT 1) THEN (SELECT val FROM date_floor LIMIT 1)
154                     ELSE bre.edit_date::date
155                 END AS edit_date
156             FROM biblio.record_entry bre
157                  INNER JOIN asset.copy_vis_attr_cache vc ON (bre.id = vc.record
158                      AND vc.vis_attr_vector @@ (
159                          SELECT  c_attrs::query_int
160                            FROM  asset.patron_default_visibility_mask()
161                            LIMIT 1
162                     )
163                  )
164     ";
165     if ($aou_id) {
166         $q .= "
167        INNER JOIN asset.copy ac ON (vc.target_copy = ac.id)
168        WHERE ac.circ_lib IN (SELECT id FROM copy_orgs)
169     ";
170     }
171     $q .= "
172             UNION 
173             SELECT bre.id,
174                 CASE
175                     WHEN bre.edit_date::date < (SELECT val FROM date_floor LIMIT 1) THEN (SELECT val FROM date_floor LIMIT 1)
176                     ELSE bre.edit_date::date
177                 END AS edit_date
178             FROM biblio.record_entry bre
179                 INNER JOIN asset.call_number acn ON bre.id = acn.record
180             WHERE bre.deleted IS FALSE AND acn.deleted IS FALSE 
181     ";
182     if ($aou_id) {
183         $q .= "
184             AND owning_lib IN (SELECT id FROM uri_orgs) AND label = '##URI##'
185         ";
186     }
187     $q .= "
188         ) x
189         ORDER BY edit_date DESC, id DESC
190     ";
191     my $stmt = $dbh->prepare($q);
192     if ($aou_id) {
193         $stmt->bind_param(1, $settings->{'date'}, { pg_type => PG_DATE });
194         $stmt->bind_param(2, $aou_id, SQL_INTEGER);
195         $stmt->bind_param(3, $aou_id, SQL_INTEGER);
196     } else {
197         $stmt->bind_param(1, $settings->{'date'}, { pg_type => PG_DATE });
198     }
199     $stmt->execute();
200
201     my $bibs = $stmt->fetchall_arrayref([0, 1]);
202
203     if ($dbh->err) {
204         print STDERR "Error was " . $dbh->errstr . "\n";
205         return;
206     }
207     return ($bibs, $aou_id);
208 }
209
210 my $hostname;
211 my $aou_shortname;
212 my %settings = (
213     prefix => '',
214     date => '2010-01-01'
215 );
216
217 GetOptions(
218         "lib-hostname=s" => \$settings{'lib-hostname'},
219         "lib-shortname=s" => \$settings{'lib-shortname'},
220         "prefix=s" => \$settings{'prefix'},
221         "date-floor=s" => \$settings{'date'},
222         "config-file=s" => \$config_file,
223         "user=s" => \$settings{'user'},
224         "password=s" => \$settings{'pw'},
225         "database=s" => \$settings{'db'},
226         "hostname=s" => \$settings{'host'},
227         "port=i" => \$settings{'port'}, 
228         "help" => \$help
229 );
230
231 if (!$config_file) { 
232     my @temp = `eg_config --sysconfdir`;
233     chomp $temp[0];
234     $sysconfdir = $temp[0];
235     $config_file = File::Spec->catfile($sysconfdir, "opensrf.xml");
236 }
237
238 unless (-e $config_file) { die "Error: $config_file does not exist. \n"; }
239
240 if ($settings{'lib-hostname'}) {
241     # Get additional settings from the config file
242     get_settings(\%settings);
243
244     my ($bibs, $aou_id) = get_record_ids(\%settings);
245     create_sitemaps(\%settings, $bibs, $aou_id);
246 } else {
247     $help = 1;
248 }
249
250 if ($help) {
251     print <<HERE;
252
253 SYNOPSIS
254     sitemap_generator [OPTION] ... [COMMAND] ... [CONFIG OPTIONS]
255
256 DESCRIPTION
257     Creates a set of sitemaps for enabling web crawlers to crawl
258     freshly changed bibliographic records.
259
260 OPTIONS
261     --config-file
262         specifies the opensrf.xml file
263
264     --lib-hostname
265         REQUIRED: hostname for the catalog (e.g "https://example.com")
266
267     --prefix
268         filename to add as a prefix to the generated set of sitemap files
269
270     --date-floor
271         a date in YYYY-MM-DD format that specifies the minimum date that
272         should be reflected for when a record was last updated; useful if
273         you enrich or change the HTML without changing records. Defaults
274         to 2010-01-01
275
276     --lib-shortname
277         include all records for the specified library and its children;
278         defaults to all records
279
280 EXAMPLES
281    This script will normally be run as a cron job by the opensrf user from
282    the web root directory.
283
284    sitemap_generator --lib-hostname https://example.com --lib-shortname BR1 \
285       --prefix example_
286
287    This generates a set of sitemap files like so:
288      * example_sitemapindex.xml
289      * example_sitemap1.xml
290      * example_sitemap2.xml
291      * ...
292
293 HERE
294 }
295