| blib/lib/SeeAlso/Source/BeaconAggregator/Publisher.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 7 | 9 | 77.7 |
| branch | n/a | ||
| condition | n/a | ||
| subroutine | 3 | 3 | 100.0 |
| pod | n/a | ||
| total | 10 | 12 | 83.3 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package SeeAlso::Source::BeaconAggregator::Publisher; | ||||||
| 2 | 3 | 3 | 32455 | use strict; | |||
| 3 | 4 | ||||||
| 3 | 102 | ||||||
| 3 | 3 | 3 | 12 | use warnings; | |||
| 3 | 4 | ||||||
| 3 | 206 | ||||||
| 4 | |||||||
| 5 | our $VERSION = "0.2_88"; | ||||||
| 6 | |||||||
| 7 | =head1 NAME | ||||||
| 8 | |||||||
| 9 | SeeAlso::Source::BeaconAggregator::Publisher - additional methods for SeeAlso::Source::BeaconAggregator | ||||||
| 10 | |||||||
| 11 | =head1 SYNOPSIS | ||||||
| 12 | |||||||
| 13 | $source = SeeAlso::Source::BeaconAggregator::Publisher->new(...); | ||||||
| 14 | |||||||
| 15 | =head1 DESCRIPTION | ||||||
| 16 | |||||||
| 17 | This package provides the functionallity to export a BEACON file from the | ||||||
| 18 | data connected with an SeeAlso::Source::BeaconAggregator instance and | ||||||
| 19 | also the additional formats "redirect" and "sources" which universally | ||||||
| 20 | can be used as callbacks for SeeAlso::Server (replacing the default | ||||||
| 21 | "seealso" method yielding JSON data). | ||||||
| 22 | |||||||
| 23 | =cut | ||||||
| 24 | |||||||
| 25 | our %Defaults = ( | ||||||
| 26 | # "REVISIT" => 86400, # one day | ||||||
| 27 | "REVISIT" => undef, # no default (leave empty unless otherwise set) | ||||||
| 28 | "uAformatname" => "sources", | ||||||
| 29 | "beaconformatname" => "beacon", | ||||||
| 30 | "FORMAT" => "BEACON", | ||||||
| 31 | "VERSION" => "0.1", # no other exist | ||||||
| 32 | ); | ||||||
| 33 | |||||||
| 34 | 3 | 3 | 916 | use SeeAlso::Source::BeaconAggregator; | |||
| 0 | |||||||
| 0 | |||||||
| 35 | use Carp; | ||||||
| 36 | |||||||
| 37 | =head2 new ( ... ) | ||||||
| 38 | |||||||
| 39 | Creates an SeeAlso::Source::BeaconAggregator object with additional methods from | ||||||
| 40 | this package enabled | ||||||
| 41 | |||||||
| 42 | =cut | ||||||
| 43 | |||||||
| 44 | sub new { # directly create BeaconAggregator instance with extended features... | ||||||
| 45 | my $class = shift @_; | ||||||
| 46 | push(@SeeAlso::Source::BeaconAggregator::ISA, $class); | ||||||
| 47 | return SeeAlso::Source::BeaconAggregator->new(@_); | ||||||
| 48 | } | ||||||
| 49 | |||||||
| 50 | |||||||
| 51 | =head2 activate () | ||||||
| 52 | |||||||
| 53 | Makes SeeAlso::Source::BeaconAggregator objects member of this class, | ||||||
| 54 | globally enabling the additional methods | ||||||
| 55 | |||||||
| 56 | Usage: | ||||||
| 57 | |||||||
| 58 | $db = SeeAlso::Source::BeaconAggregator::Maintenance->new(...); | ||||||
| 59 | ... | ||||||
| 60 | do stuff | ||||||
| 61 | ... | ||||||
| 62 | require SeeAlso::Source::BeaconAggregator::Publisher | ||||||
| 63 | or die "could not require Publisher extension"; | ||||||
| 64 | SeeAlso::Source::BeaconAggregator::Publisher->activate(); # "recast" all objects | ||||||
| 65 | ... | ||||||
| 66 | do more stuff | ||||||
| 67 | |||||||
| 68 | =cut | ||||||
| 69 | sub activate { # enrich SeeAlso::Source and derived classes with our methods | ||||||
| 70 | my $class = shift @_; | ||||||
| 71 | push(@SeeAlso::Source::BeaconAggregator::ISA, $class); | ||||||
| 72 | return 1; | ||||||
| 73 | } | ||||||
| 74 | |||||||
| 75 | |||||||
| 76 | ### Produktion der Beacon-Datei | ||||||
| 77 | |||||||
| 78 | =head2 beacon ( [dumpmeta arguments] ) | ||||||
| 79 | |||||||
| 80 | produces a BEACON file (however, $cgibase is mandatory) | ||||||
| 81 | |||||||
| 82 | =head2 dumpmeta ( [$cgibase, [$uAformatname, [$headersonly]]] [, $preset]) | ||||||
| 83 | |||||||
| 84 | produces only the meta fields of a BEACON file | ||||||
| 85 | |||||||
| 86 | Meta fields are generated from the $preset Hashref, falling back to | ||||||
| 87 | values stored in the database, falling back to reasonable default | ||||||
| 88 | values. | ||||||
| 89 | |||||||
| 90 | Arguments: | ||||||
| 91 | |||||||
| 92 | =over 8 | ||||||
| 93 | |||||||
| 94 | =item $cgibase | ||||||
| 95 | |||||||
| 96 | URL of the SeeAlso service the BEACON file is provided for | ||||||
| 97 | |||||||
| 98 | =item $uAformatname | ||||||
| 99 | |||||||
| 100 | unAPI format name to be used as target (Default: "sources") | ||||||
| 101 | |||||||
| 102 | =item $headersonly | ||||||
| 103 | |||||||
| 104 | currently unused | ||||||
| 105 | |||||||
| 106 | =item $preset | ||||||
| 107 | |||||||
| 108 | Hashref of Beacon header fields overriding the contents of the database | ||||||
| 109 | |||||||
| 110 | =back | ||||||
| 111 | |||||||
| 112 | Regular Usage: | ||||||
| 113 | |||||||
| 114 | $db = SeeAlso::Source::BeaconAggregator::Publisher->new(...); | ||||||
| 115 | binmode(STDOUT, ":utf8"); | ||||||
| 116 | my $cgibase = "http://address/of/service"; | ||||||
| 117 | my ( $error, $headerref) = $db->beacon($cgibase, @ARGV, {'FORMAT' => 'PND-BEACON'}); | ||||||
| 118 | |||||||
| 119 | |||||||
| 120 | CGI Usage: | ||||||
| 121 | |||||||
| 122 | $format = $CGI->param('format') || ""; | ||||||
| 123 | if ( $format eq "beacon" ) { # bypass SeeAlso::Server->query() b/c performance / interim storage | ||||||
| 124 | insert access restrictions here... | ||||||
| 125 | do_beacon($source, $CGI); | ||||||
| 126 | } | ||||||
| 127 | ... | ||||||
| 128 | |||||||
| 129 | sub do_beacon { | ||||||
| 130 | my ($self, $cgi) = @_; # Of type SeeAlso::Source::BeaconAggregator | ||||||
| 131 | unless ( $self->can("beacon") ) { | ||||||
| 132 | croak "On the fly generation of beacon Files not supported by this service";} | ||||||
| 133 | my $cgibase = $cgi->url(-path_info=>1); | ||||||
| 134 | |||||||
| 135 | print $cgi->header( -status => 200, | ||||||
| 136 | -expires => '+1d', | ||||||
| 137 | -type => 'text/plain', | ||||||
| 138 | -charset => 'utf-8', | ||||||
| 139 | ); | ||||||
| 140 | return $self->beacon($cgibase, "sources", {}); # prints directly to stdout..., returns $error, $headerref | ||||||
| 141 | } | ||||||
| 142 | |||||||
| 143 | =cut | ||||||
| 144 | |||||||
| 145 | sub beacon { | ||||||
| 146 | my ($self) = shift @_ or croak("beacon is a method!"); # Of type SeeAlso::Source::BeaconAggregator | ||||||
| 147 | my ($error, $headerref) = $self->dumpmeta(@_); | ||||||
| 148 | croak("Error generating Header, will not proceed") if $error; | ||||||
| 149 | |||||||
| 150 | print @$headerref; | ||||||
| 151 | |||||||
| 152 | my $c = (defined $self->{identifierClass}) ? $self->{identifierClass} : $self->autoIdentifier(); | ||||||
| 153 | |||||||
| 154 | my ($sth, $sthexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 155 | SELECT hash, COUNT(DISTINCT seqno) FROM beacons GROUP BY hash ORDER BY hash; | ||||||
| 156 | XxX | ||||||
| 157 | $self->stmtExplain($sthexpl) if $ENV{'DBI_PROFILE'}; | ||||||
| 158 | $sth->execute() or croak("Could not execute >".$sth->{Statement}."<: ".$sth->errstr); | ||||||
| 159 | my $rows = 0; | ||||||
| 160 | while ( my $row = $sth->fetchrow_arrayref ) { | ||||||
| 161 | $rows++; | ||||||
| 162 | my $expanded = $row->[0]; | ||||||
| 163 | if ( defined $c ) { | ||||||
| 164 | # compat: hash might not take an argument, must resort to value, has to be cleared before... | ||||||
| 165 | $c->value(""); | ||||||
| 166 | my $did = $c->hash($row->[0]) || $c->value($row->[0]); | ||||||
| 167 | $expanded = $c->can("pretty") ? $c->pretty() : $c->value(); | ||||||
| 168 | # illegal identifier b/c different classes for loading and exporting? | ||||||
| 169 | next unless defined $expanded && ($expanded ne ""); | ||||||
| 170 | } | ||||||
| 171 | print $expanded.(($row->[1] > 1) ? "|".$row->[1] : "")."\n"; | ||||||
| 172 | } | ||||||
| 173 | |||||||
| 174 | return $rows, $headerref; | ||||||
| 175 | } | ||||||
| 176 | |||||||
| 177 | sub dumpmeta { # cgibase unAPIformatname headers_only {preset} | ||||||
| 178 | my ($self) = shift @_ or croak("dumpmeta is a method!"); # Of type SeeAlso::Source::BeaconAggregator | ||||||
| 179 | my ($error, @result) = (0, ()); | ||||||
| 180 | |||||||
| 181 | my $cgibase = shift @_ if @_ && !ref($_[0]); | ||||||
| 182 | my $uAformatname = shift @_ if @_ && !ref($_[0]); | ||||||
| 183 | $uAformatname ||= $Defaults{'uAformatname'}; | ||||||
| 184 | my $headersonly = shift @_ if @_ && !ref($_[0]); | ||||||
| 185 | my $preset = (@_ && ref($_[0])) ? (shift @_) : {}; | ||||||
| 186 | |||||||
| 187 | my ($metasth, $metasthexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 188 | SELECT key, val FROM osd; | ||||||
| 189 | XxX | ||||||
| 190 | $self->stmtExplain($metasthexpl) if $ENV{'DBI_PROFILE'}; | ||||||
| 191 | $metasth->execute() or croak("Could not execute >".$metasth->{Statement}."<: ".$metasth->errstr); | ||||||
| 192 | |||||||
| 193 | my (%osd, %beaconmeta); | ||||||
| 194 | while ( my $aryref = $metasth->fetchrow_arrayref ) { | ||||||
| 195 | my ($key, $val) = @$aryref; | ||||||
| 196 | next unless $val; | ||||||
| 197 | if ($key =~ s/^bc// ) { # BeaconMeta Fields | ||||||
| 198 | $beaconmeta{$key} = $val} | ||||||
| 199 | elsif ( exists $osd{$key} ) { | ||||||
| 200 | if ( ref($osd{$key}) ) { | ||||||
| 201 | push(@{$osd{$key}}, $val)} | ||||||
| 202 | else { | ||||||
| 203 | $osd{$key} = [$osd{$key}, $val]}; | ||||||
| 204 | } | ||||||
| 205 | else { | ||||||
| 206 | $osd{$key} = $val}; | ||||||
| 207 | }; | ||||||
| 208 | my @osdexamples; | ||||||
| 209 | if ( $osd{'Examples'} && ref($osd{'Examples'}) ) { | ||||||
| 210 | foreach my $expl ( @{$osd{'Examples'}} ) { | ||||||
| 211 | $expl =~ s/\s*\|.*$//; | ||||||
| 212 | push(@osdexamples, $expl); | ||||||
| 213 | } | ||||||
| 214 | } | ||||||
| 215 | elsif ( my $expl = $osd{'Examples'} ) { | ||||||
| 216 | $expl =~ s/\s*\|.*$//; | ||||||
| 217 | push(@osdexamples, $expl); | ||||||
| 218 | }; | ||||||
| 219 | |||||||
| 220 | foreach ( grep /^[A-Z]+$/, keys %$preset ) { | ||||||
| 221 | $beaconmeta{$_} = $preset->{$_}} | ||||||
| 222 | # Mandatory fields | ||||||
| 223 | push(@result, "#FORMAT: ".($beaconmeta{'FORMAT'} || $Defaults{'FORMAT'})."\n"); | ||||||
| 224 | push(@result, "#VERSION: ".($beaconmeta{'VERSION'} || $Defaults{'VERSION'})."\n"); | ||||||
| 225 | if ( $beaconmeta{'TARGET'} ) { | ||||||
| 226 | $beaconmeta{'TARGET'} =~ s/^\{BASE\}/$cgibase/; | ||||||
| 227 | push(@result, "#TARGET: $beaconmeta{'TARGET'}\n"); | ||||||
| 228 | } | ||||||
| 229 | elsif ( $cgibase ) { | ||||||
| 230 | push(@result, "#TARGET: $cgibase?format=$uAformatname&id={ID}\n")} | ||||||
| 231 | else { | ||||||
| 232 | carp "Don't know how to construct the mandatory #TARGET field!"; | ||||||
| 233 | $error ++; | ||||||
| 234 | } | ||||||
| 235 | |||||||
| 236 | my $timestamp = $preset->{'TIMESTAMP'} || $osd{DateModified} || $^T; | ||||||
| 237 | push(@result, "#TIMESTAMP: ".SeeAlso::Source::BeaconAggregator::tToISO($timestamp)."\n") if $timestamp > 0; | ||||||
| 238 | my $revisit = ($beaconmeta{'REVISIT'} || $Defaults{'REVISIT'}) || ""; | ||||||
| 239 | $revisit =~ tr/ //d; | ||||||
| 240 | $revisit =~ s/(\d+)mo\w*/($1*30)."d"/ei; | ||||||
| 241 | $revisit =~ s/(\d+)M\w*/($1*30)."d"/e; | ||||||
| 242 | $revisit =~ s/(\d+)w\w*/($1*7)."d"/ei; | ||||||
| 243 | $revisit =~ s/(\d+)d\w*/($1*24)."h"/ei; | ||||||
| 244 | $revisit =~ s/(\d+)h\w*/($1*60)."m"/ei; | ||||||
| 245 | $revisit =~ s/(\d+)m\w*/($1*60)."s"/ei; | ||||||
| 246 | $revisit =~ s/(\d+)s\w*/$1/i; | ||||||
| 247 | push(@result, "#REVISIT: ".SeeAlso::Source::BeaconAggregator::tToISO($timestamp + $revisit)."\n") if $revisit && ($revisit =~ /^[+-]?\d+$/) && ($revisit > 0);; | ||||||
| 248 | |||||||
| 249 | # $beaconmeta{'UPDATE'} ||= "daily"; | ||||||
| 250 | $beaconmeta{'FEED'} ||= "$cgibase?format=".$Defaults{'beaconformatname'} if $cgibase; | ||||||
| 251 | $beaconmeta{'EXAMPLES'} ||= join("|", @osdexamples); | ||||||
| 252 | $beaconmeta{'CONTACT'} ||= $self->{Contact} || $osd{'Contact'}; | ||||||
| 253 | $beaconmeta{'DESCRIPTION'} ||= $self->{Description} || $osd{'Description'}; | ||||||
| 254 | $beaconmeta{'NAME'} ||= $self->{ShortName} || $osd{'ShortName'}; | ||||||
| 255 | foreach ( grep !/^(FORMAT|REVISIT|TARGET|TIMESTAMP|VERSION)$/, SeeAlso::Source::BeaconAggregator->beaconfields() ) { | ||||||
| 256 | next unless my $val = $beaconmeta{$_}; | ||||||
| 257 | next if $val =~ /^-/; | ||||||
| 258 | $val =~ s/\s+/ /g; $val =~ s/^\s+//; $val =~ s/\s+$//; | ||||||
| 259 | push(@result, "#$_: $val\n"); | ||||||
| 260 | } | ||||||
| 261 | |||||||
| 262 | # extract admin info of last transaction (i.e. last possible modification of underlying data) | ||||||
| 263 | # alternatively: SELECT seqno, utime FROM repos WHERE seqno=(SELECT MAX(seqno) FROM repos); | ||||||
| 264 | my ($laststh, $laststhexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 265 | SELECT MAX(seqno), MAX(mtime) FROM repos; | ||||||
| 266 | XxX | ||||||
| 267 | $self->stmtExplain($laststhexpl) if $ENV{'DBI_PROFILE'}; | ||||||
| 268 | $laststh->execute() or croak("Could not execute >".$laststh->{Statement}."<: ".$laststh->errstr); | ||||||
| 269 | if ( my $aryref = $laststh->fetchrow_arrayref ) { | ||||||
| 270 | my ($sq, $ut) = @$aryref; | ||||||
| 271 | push(@result, "#X-REVISION: $sq [".SeeAlso::Source::BeaconAggregator::tToISO($ut)."]\n") if $sq; | ||||||
| 272 | }; | ||||||
| 273 | my $admref = $self->admhash(); | ||||||
| 274 | if ( my $cu = $admref->{'gcountu'} ) { | ||||||
| 275 | my $type = $admref->{'IDENTIFIER_CLASS'} || ""; | ||||||
| 276 | push(@result, "#X-EXTENT: $cu unique identifiers".($type ? " of type $type" : "")."\n"); | ||||||
| 277 | }; | ||||||
| 278 | |||||||
| 279 | |||||||
| 280 | ## PND-BEACON | ||||||
| 281 | # CONTACT => ['VARCHAR(63)'], | ||||||
| 282 | # INSTITUTION => ['VARCHAR(255)'], | ||||||
| 283 | # ISIL => ['VARCHAR(63)'], | ||||||
| 284 | # DESCRIPTION => ['VARCHAR(255)'], | ||||||
| 285 | ## BEACON | ||||||
| 286 | # MESSAGE => ['VARCHAR(255)'], # enthaelt {hits} | ||||||
| 287 | # ONEMESSAGE => ['VARCHAR(255)'], | ||||||
| 288 | # SOMEMESSAGE => ['VARCHAR(255)'], | ||||||
| 289 | # PREFIX => ['VARCHAR(255)'], | ||||||
| 290 | ## WInofficial | ||||||
| 291 | # NAME => ['VARCHAR(255)'], | ||||||
| 292 | |||||||
| 293 | return $error, \@result; | ||||||
| 294 | } | ||||||
| 295 | |||||||
| 296 | |||||||
| 297 | =head2 redirect ( $server, $format, $extra, $query ) | ||||||
| 298 | |||||||
| 299 | Produces an HTTP redirect page, HTML content contains very terse details in case | ||||||
| 300 | of multiple results. | ||||||
| 301 | |||||||
| 302 | This subroutine may be used as callback method in SeeAlso::Server | ||||||
| 303 | |||||||
| 304 | Usage is a bit cludgy due to author's lack of understanding of SeeAlso::Server | ||||||
| 305 | |||||||
| 306 | $source = SeeAlso::Sources::BeaconAggregator::Publisher->new(...); | ||||||
| 307 | $CGI = CGI->new(); | ||||||
| 308 | |||||||
| 309 | $formats = { | ||||||
| 310 | ... | ||||||
| 311 | redirect => { | ||||||
| 312 | type => "text/html", | ||||||
| 313 | docs => "http://www.endofthe.net/", | ||||||
| 314 | # method => \&SeeAlso::Source::BeaconAggregator::Publisher::redirect, | ||||||
| 315 | #redirect_300 => 'sources', | ||||||
| 316 | } | ||||||
| 317 | }; | ||||||
| 318 | |||||||
| 319 | $server = SeeAlso::Server->new ( | ||||||
| 320 | 'cgi' => $CGI, | ||||||
| 321 | 'formats' => $formats, | ||||||
| 322 | ... | ||||||
| 323 | ); | ||||||
| 324 | |||||||
| 325 | # Closure as fix: Server.pm does not expose self, $source and the CGI object to the format methods | ||||||
| 326 | my $oref = \&SeeAlso::Source::BeaconAggregator::Publisher::redirect; | ||||||
| 327 | $server->{'formats'}->{'redirect'}->{method} | ||||||
| 328 | = sub {return &$oref($source, $server, $method, $formats->{$method}, @_)}; | ||||||
| 329 | |||||||
| 330 | my $result = $server->query($source); | ||||||
| 331 | |||||||
| 332 | Arguments: | ||||||
| 333 | |||||||
| 334 | =over 8 | ||||||
| 335 | |||||||
| 336 | =item $server | ||||||
| 337 | |||||||
| 338 | SeeAlso::Server object. Must contain a CGI object | ||||||
| 339 | |||||||
| 340 | =item $format | ||||||
| 341 | |||||||
| 342 | Name of a format registered with the $server object () | ||||||
| 343 | |||||||
| 344 | =item $extra | ||||||
| 345 | |||||||
| 346 | Hashref with the following configuration directives | ||||||
| 347 | |||||||
| 348 | redirect_300 => CGI 'format' parameter to be used in HTML content (eg. format=sources) | ||||||
| 349 | |||||||
| 350 | force_single => Only regard the first hit (thus always redirect) | ||||||
| 351 | |||||||
| 352 | =item $query | ||||||
| 353 | |||||||
| 354 | Identifier to be queried | ||||||
| 355 | |||||||
| 356 | =back | ||||||
| 357 | |||||||
| 358 | =cut | ||||||
| 359 | |||||||
| 360 | sub redirect { # Liste der Beacon-Header fuer Treffer oder einfaches redirect | ||||||
| 361 | my ($self, $server, $format, $extra, $query) = @_; | ||||||
| 362 | my $formatprops = $server->{'formats'}->{$format} || {}; | ||||||
| 363 | my $cgi = $server->{'cgi'} or croak("I rely on a prepared CGI.pm object"); | ||||||
| 364 | |||||||
| 365 | my %headerdefaults = ( -type => ($formatprops->{'type'} || 'text/html'), | ||||||
| 366 | # ($formatprops->{'charset'} ? (-charset => $formatprops->{'charset'}) : ()), | ||||||
| 367 | -charset => ($formatprops->{'charset'} || 'UTF-8'), | ||||||
| 368 | -expires => ($server->{'expires'} || '+1h'), | ||||||
| 369 | ); | ||||||
| 370 | |||||||
| 371 | my ($hash, $pretty, $canon) = $self->prepare_query($query); | ||||||
| 372 | unless ( $hash ) { | ||||||
| 373 | print $cgi->header(-status => "400 Bad Request (Identifier '$query' not valid)", | ||||||
| 374 | -expires => "+1y", | ||||||
| 375 | -type => 'text/html', | ||||||
| 376 | ), | ||||||
| 377 | $cgi->start_html (-dtd => "-//W3C//DTD HTML 3.2 Final//EN", | ||||||
| 378 | -title => "No valid identifier", | ||||||
| 379 | ), | ||||||
| 380 | $cgi->p("Malformed identifier '$query'"), | ||||||
| 381 | $cgi->end_html; | ||||||
| 382 | return ""; | ||||||
| 383 | }; | ||||||
| 384 | |||||||
| 385 | my $clusterid; | ||||||
| 386 | if ( $self->{cluster} ) { | ||||||
| 387 | my ($clusterh, $clusterexpl) = $self->stmtHdl("SELECT beacons.altid FROM cluster.beacons WHERE beacons.hash=? OR beacons.altid=? LIMIT 1;"); | ||||||
| 388 | $self->stmtExplain($clusterexpl, $hash, $hash) if $ENV{'DBI_PROFILE'}; | ||||||
| 389 | $clusterh->execute($hash, $hash); | ||||||
| 390 | while ( my $onerow = $clusterh->fetchrow_arrayref() ) { | ||||||
| 391 | $clusterid = $onerow->[0];} | ||||||
| 392 | } | ||||||
| 393 | |||||||
| 394 | my $clause = $extra->{force_single} ? "LIMIT 1" : "ORDER BY repos.sort, repos.alias"; | ||||||
| 395 | my ( $tfield,$afield, $gfield, $mfield,$nfield,$ifield) = map{ scalar $self->beaconfields($_) } | ||||||
| 396 | qw(TARGET ALTTARGET IMGTARGET MESSAGE NAME INSTITUTION); | ||||||
| 397 | # above 5 6 7 8 9 10 | ||||||
| 398 | # below 0 1 2 3 4 | ||||||
| 399 | # 11 | ||||||
| 400 | my ($sth, $sthexpl); | ||||||
| 401 | if ( $clusterid ) { # query IN cluster | ||||||
| 402 | ($sth, $sthexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 403 | SELECT beacons.hash, beacons.altid, beacons.hits, beacons.info, beacons.link, | ||||||
| 404 | repos.$tfield, repos.$afield, repos.$gfield, repos.$mfield, repos.$nfield, repos.$ifield, | ||||||
| 405 | repos.alias | ||||||
| 406 | FROM beacons NATURAL LEFT JOIN repos | ||||||
| 407 | WHERE ( (beacons.hash=?) | ||||||
| 408 | OR (beacons.hash IN (SELECT cluster.beacons.hash FROM cluster.beacons WHERE cluster.beacons.altid=?)) ) | ||||||
| 409 | $clause; | ||||||
| 410 | XxX | ||||||
| 411 | $self->stmtExplain($sthexpl, $clusterid, $clusterid) if $ENV{'DBI_PROFILE'}; | ||||||
| 412 | $sth->execute($clusterid, $clusterid) or croak("Could not execute >".$sth->{Statement}."<: ".$sth->errstr); | ||||||
| 413 | } | ||||||
| 414 | else { | ||||||
| 415 | ($sth, $sthexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 416 | SELECT beacons.hash, beacons.altid, beacons.hits, beacons.info, beacons.link, | ||||||
| 417 | repos.$tfield, repos.$afield, repos.$gfield, repos.$mfield, repos.$nfield, repos.$ifield, | ||||||
| 418 | repos.alias | ||||||
| 419 | FROM beacons NATURAL LEFT JOIN repos | ||||||
| 420 | WHERE beacons.hash=? | ||||||
| 421 | $clause; | ||||||
| 422 | XxX | ||||||
| 423 | $self->stmtExplain($sthexpl, $hash) if $ENV{'DBI_PROFILE'}; | ||||||
| 424 | $sth->execute($hash) or croak("Could not execute >".$sth->{Statement}."<: ".$sth->errstr); | ||||||
| 425 | } | ||||||
| 426 | |||||||
| 427 | my $c = $self->{identifierClass} || undef; | ||||||
| 428 | my @rawres; | ||||||
| 429 | my %didalready; | ||||||
| 430 | while ( my $onerow = $sth->fetchrow_arrayref ) { | ||||||
| 431 | next if $onerow->[11] && exists $self->{'aliasfilter'}->{$onerow->[11]}; | ||||||
| 432 | my $uri = $onerow->[4]; # Evtl. Expliziter Link | ||||||
| 433 | my $guri = ""; | ||||||
| 434 | |||||||
| 435 | my $h = $onerow->[0]; | ||||||
| 436 | my $p; | ||||||
| 437 | if ( $h eq $hash ) { | ||||||
| 438 | $p = $pretty} | ||||||
| 439 | elsif ( $clusterid && ref($c) ) { | ||||||
| 440 | $c->value(""); | ||||||
| 441 | my $did = $c->hash($h) || $c->value($h) || $h; | ||||||
| 442 | $p = $c->can("pretty") ? $c->pretty() : $c->value(); | ||||||
| 443 | }; | ||||||
| 444 | $p = ($clusterid ? $h : $pretty) unless defined $p; | ||||||
| 445 | |||||||
| 446 | if ( $onerow->[1] ) { # Konkordanzformat | ||||||
| 447 | $uri ||= sprintf($onerow->[6] || $onerow->[5], $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($onerow->[1])); | ||||||
| 448 | $guri = sprintf($onerow->[7], $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($onerow->[1])) if $onerow->[7]; | ||||||
| 449 | } | ||||||
| 450 | elsif ( $onerow->[5] ) { # normales Beacon-Format | ||||||
| 451 | $uri ||= sprintf($onerow->[5], $p); | ||||||
| 452 | $guri = sprintf($onerow->[7], $p) if $onerow->[7]; | ||||||
| 453 | }; | ||||||
| 454 | next unless $uri; | ||||||
| 455 | |||||||
| 456 | # #NAME #INSTITUTION _alias | ||||||
| 457 | my $label; | ||||||
| 458 | if ( $label = $onerow->[8] ) { #MESSAGE | ||||||
| 459 | $label = sprintf($label, $onerow->[2] || "...")} | ||||||
| 460 | elsif ( $label = $onerow->[9] || $onerow->[10] || $onerow->[11] || "???" ) { | ||||||
| 461 | $label .= " (".$onerow->[1].")" if $onerow->[1]} | ||||||
| 462 | |||||||
| 463 | push(@rawres, [$uri, $guri, $label, $onerow->[11], $onerow->[3]]) unless $didalready{join("\x7f", $label, $uri)}++;; | ||||||
| 464 | }; | ||||||
| 465 | my $hits = scalar @rawres; | ||||||
| 466 | |||||||
| 467 | if ( ! $hits ) { | ||||||
| 468 | print $cgi->header(-status => "404 Not Found (identifier '$canon')", | ||||||
| 469 | %headerdefaults), | ||||||
| 470 | $cgi->start_html (-dtd => "-//W3C//DTD HTML 3.2 Final//EN", | ||||||
| 471 | -title => "No References for $pretty", | ||||||
| 472 | ), | ||||||
| 473 | $cgi->p("No References found for ", $cgi->a({href=>"$canon"}, $pretty)), | ||||||
| 474 | $cgi->end_html; | ||||||
| 475 | return ""; | ||||||
| 476 | } | ||||||
| 477 | elsif ( $hits == 1 ) { | ||||||
| 478 | return $cgi->redirect(-status => "302 Found (Redirecting for identifier '$canon')", | ||||||
| 479 | -uri => $rawres[0]->[0], | ||||||
| 480 | %headerdefaults); | ||||||
| 481 | } | ||||||
| 482 | |||||||
| 483 | my $sources = new CGI($cgi); | ||||||
| 484 | $sources->param(-name => 'id', -value=>"$canon"); | ||||||
| 485 | unless ( $canon =~ /:\/\// ) { | ||||||
| 486 | my ($osd, $beaconmeta) = $self->get_meta; | ||||||
| 487 | my $prefix = $beaconmeta->{'PREFIX'} || ""; | ||||||
| 488 | $canon = "$prefix$pretty" if $prefix; | ||||||
| 489 | }; | ||||||
| 490 | if ( my $multired = $extra->{redirect_300} ) { | ||||||
| 491 | $sources->param(-name => 'format', -value=>$multired); | ||||||
| 492 | print $cgi->redirect(-status => "300 Multiple Choices for identifier '$canon'", | ||||||
| 493 | -uri => $sources->url(-path_info=>1, -query=>1), | ||||||
| 494 | %headerdefaults); | ||||||
| 495 | } | ||||||
| 496 | else { | ||||||
| 497 | print $cgi->header(-status => "300 Multiple Choices for identifier '$canon'", | ||||||
| 498 | # -nph => 1, # for older CGI/mod_perl??? | ||||||
| 499 | %headerdefaults); | ||||||
| 500 | # mod_perl overrides the header and adds a custom document at the end of everything | ||||||
| 501 | # therefore we force the header out (a simple print "" does not suffice) and then can | ||||||
| 502 | # safely reset the status to OK via CGI.pm leaking the Apache2::Request object | ||||||
| 503 | if ( my $r = $sources->r ) { | ||||||
| 504 | local($|) = 1; | ||||||
| 505 | print "\n"; | ||||||
| 506 | $r->status(200); | ||||||
| 507 | }; | ||||||
| 508 | }; | ||||||
| 509 | my @result; | ||||||
| 510 | push(@result, $cgi->start_html ( -title => "$hits References for $pretty", | ||||||
| 511 | -dtd => "-//W3C//DTD HTML 3.2 Final//EN"), | ||||||
| 512 | $cgi->h1("$hits References for ", $cgi->a({href=>"$canon"}, $pretty)), | ||||||
| 513 | '
|
||||||
| 514 | |||||||
| 515 | my $rowcnt = 0; | ||||||
| 516 | foreach ( @rawres ) { # uri, guri, label, alias, info | ||||||
| 517 | if ( $_->[1] ) { | ||||||
| 518 | my $tooltip = $_->[4] ? ($_->[4]." [".$_->[2]."]") : $_->[2]; | ||||||
| 519 | my $img = $cgi->a({href=>$_->[0], title=>$tooltip}, $cgi->img({src=>$_->[1], alt=>$_->[4]||$_->[2], style=>"width: 5em; border: 0pt;"})); | ||||||
| 520 | push(@result, $cgi->li({id=>"$_->[3]".++$rowcnt}, $img, $cgi->a({href=>$_->[0]}, $_->[2]), ($_->[4] ? " [".$_->[4]."]" : ""))); | ||||||
| 521 | } | ||||||
| 522 | else { | ||||||
| 523 | push(@result, $cgi->li({id=>"$_->[3]".++$rowcnt}, $cgi->a({href=>$_->[0]}, $_->[2]), $_->[4] ? " [".$_->[4]."]" : ""))}; | ||||||
| 524 | }; | ||||||
| 525 | |||||||
| 526 | push(@result, ''); | ||||||
| 527 | |||||||
| 528 | if ( $server->{'formats'}->{'sources'} ) { | ||||||
| 529 | $sources->param(-name => 'format', -value=>"sources"); | ||||||
| 530 | push(@result, $cgi->p("[", $cgi->a({href=>($sources->url(-path_info=>1, -query=>1))}, "Details"), "]")); | ||||||
| 531 | }; | ||||||
| 532 | |||||||
| 533 | my($tu, $ts, $tcu, $tcs) = times(); | ||||||
| 534 | push(@result, sprintf("", $tu, $ts, $tu+$ts), $cgi->end_html()); | ||||||
| 535 | return join("\n", @result); | ||||||
| 536 | } | ||||||
| 537 | |||||||
| 538 | =head2 sources ( $server, $format, $extra, $query ) | ||||||
| 539 | |||||||
| 540 | Produces an HTML page with details to the queried identifier (description of sources) | ||||||
| 541 | |||||||
| 542 | This subroutine may be used as callback method in SeeAlso::Server (cf. description | ||||||
| 543 | of redirect above | ||||||
| 544 | |||||||
| 545 | =over 8 | ||||||
| 546 | |||||||
| 547 | =item $server | ||||||
| 548 | |||||||
| 549 | SeeAlso::Server object | ||||||
| 550 | |||||||
| 551 | |||||||
| 552 | =item $format | ||||||
| 553 | |||||||
| 554 | Format selected for $server | ||||||
| 555 | |||||||
| 556 | |||||||
| 557 | =item $extra | ||||||
| 558 | |||||||
| 559 | Hashref with the following configuration directives | ||||||
| 560 | |||||||
| 561 | css => URL of css file to be referenced | ||||||
| 562 | |||||||
| 563 | =item $query | ||||||
| 564 | |||||||
| 565 | Identifier to be queried | ||||||
| 566 | |||||||
| 567 | =back | ||||||
| 568 | |||||||
| 569 | =cut | ||||||
| 570 | |||||||
| 571 | sub sources { # Liste der Beacon-Header fuer Treffer | ||||||
| 572 | # We escape all characters except US-ASCII, because older CGI.pm's set an xml declaration | ||||||
| 573 | # which somehow interferes with IE8's adherence to the character set... | ||||||
| 574 | my ($self, $server, $format, $extra, $query) = @_; | ||||||
| 575 | my $formatprops = $server->{'formats'}->{$format} || {}; | ||||||
| 576 | my $cgi = $server->{'cgi'} || CGI->new(); | ||||||
| 577 | |||||||
| 578 | my ($hash, $pretty, $canon) = $self->prepare_query($query); | ||||||
| 579 | unless ( $hash ) { | ||||||
| 580 | print $cgi->header(-status => "400 Bad Request (Identifier '$query' not valid)", | ||||||
| 581 | -expires => "+1y", | ||||||
| 582 | -type => 'text/html', | ||||||
| 583 | ), | ||||||
| 584 | $cgi->start_html (-dtd => "-//W3C//DTD HTML 3.2 Final//EN", | ||||||
| 585 | -title => "No valid identifier", | ||||||
| 586 | ), | ||||||
| 587 | $cgi->p("Malformed identifier '$query'"), | ||||||
| 588 | $cgi->end_html; | ||||||
| 589 | return ""; | ||||||
| 590 | }; | ||||||
| 591 | |||||||
| 592 | my ($clusterid, %idlist); | ||||||
| 593 | my $c = $self->{identifierClass} || undef; | ||||||
| 594 | if ( $self->{cluster} ) { | ||||||
| 595 | my ($clusterh, $clusterexpl) = $self->stmtHdl("SELECT beacons.hash, beacons.altid FROM cluster.beacons WHERE beacons.hash=? OR beacons.altid=? LIMIT 1;"); | ||||||
| 596 | $self->stmtExplain($clusterexpl, $hash, $hash) if $ENV{'DBI_PROFILE'}; | ||||||
| 597 | $clusterh->execute($hash, $hash) or croak("Could not execute >".$clusterh->{Statement}."<: ".$clusterh->errstr); | ||||||
| 598 | while ( my $onerow = $clusterh->fetchrow_arrayref() ) { | ||||||
| 599 | $clusterid = $onerow->[1]; | ||||||
| 600 | my $h = $onerow->[0]; | ||||||
| 601 | if ( $c ) { | ||||||
| 602 | $c->value(""); | ||||||
| 603 | my $did = $c->hash($h) || $c->value($h); | ||||||
| 604 | my $p = $c->can("pretty") ? $c->pretty() : $c->value(); | ||||||
| 605 | $idlist{$p} = ""; | ||||||
| 606 | } | ||||||
| 607 | else { | ||||||
| 608 | $idlist{$h} = ""; | ||||||
| 609 | } | ||||||
| 610 | }; | ||||||
| 611 | $idlist{$pretty} = "queriedid"; | ||||||
| 612 | if ( $clusterid ) { | ||||||
| 613 | if ( $clusterid eq $hash ) { | ||||||
| 614 | $idlist{$pretty} .= " preferredid"} | ||||||
| 615 | elsif ( $c ) { | ||||||
| 616 | $c->value(""); | ||||||
| 617 | my $did = $c->hash($clusterid) || $c->value($clusterid); | ||||||
| 618 | my $p = $c->can("pretty") ? $c->pretty() : $c->value(); | ||||||
| 619 | $idlist{$p} = "variantid preferredid"; | ||||||
| 620 | } | ||||||
| 621 | else { | ||||||
| 622 | $idlist{$clusterid} = "variantid preferredid"; | ||||||
| 623 | }; | ||||||
| 624 | my ($varianth, $variantexpl) = $self->stmtHdl("SELECT beacons.hash FROM cluster.beacons WHERE beacons.altid=?;"); | ||||||
| 625 | $self->stmtExplain($variantexpl, $clusterid) if $ENV{'DBI_PROFILE'}; | ||||||
| 626 | $varianth->execute($clusterid) or croak("Could not execute >".$varianth->{Statement}."<: ".$varianth->errstr); | ||||||
| 627 | while ( my $onerow = $varianth->fetchrow_arrayref() ) { | ||||||
| 628 | my $v = $onerow->[0]; | ||||||
| 629 | if ( $c ) { | ||||||
| 630 | $c->value(""); | ||||||
| 631 | my $did = $c->hash($v) || $c->value($v); | ||||||
| 632 | my $p = $c->can("pretty") ? $c->pretty() : $c->value(); | ||||||
| 633 | (exists $idlist{$p}) || ($idlist{$p} = "variantid"); | ||||||
| 634 | } | ||||||
| 635 | else { | ||||||
| 636 | (exists $idlist{$v}) || ($idlist{$v} = "variantid"); | ||||||
| 637 | } | ||||||
| 638 | } | ||||||
| 639 | } | ||||||
| 640 | } | ||||||
| 641 | |||||||
| 642 | my ($countsth, $countexpl); | ||||||
| 643 | if ( $clusterid ) { | ||||||
| 644 | ($countsth, $countexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 645 | SELECT COUNT(DISTINCT seqno) FROM beacons | ||||||
| 646 | WHERE ( (hash=?) OR (hash IN (SELECT beacons.hash FROM cluster.beacons WHERE cluster.beacons.altid=?)) ); | ||||||
| 647 | XxX | ||||||
| 648 | $self->stmtExplain($countexpl, $clusterid, $clusterid) if $ENV{'DBI_PROFILE'}; | ||||||
| 649 | $countsth->execute($clusterid, $clusterid) or croak("Could not execute >".$countsth->{Statement}."<: ".$countsth->errstr); | ||||||
| 650 | } | ||||||
| 651 | else { | ||||||
| 652 | ($countsth, $countexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 653 | SELECT COUNT(DISTINCT seqno) FROM beacons WHERE hash=?; | ||||||
| 654 | XxX | ||||||
| 655 | $self->stmtExplain($countexpl, $hash) if $ENV{'DBI_PROFILE'}; | ||||||
| 656 | $countsth->execute($hash) or croak("Could not execute >".$countsth->{Statement}."<: ".$countsth->errstr); | ||||||
| 657 | }; | ||||||
| 658 | my $hitsref = $countsth->fetchrow_arrayref; | ||||||
| 659 | my $hits = $hitsref->[0] || 0; | ||||||
| 660 | |||||||
| 661 | my ($osd, $beaconmeta) = $self->get_meta; | ||||||
| 662 | my $prefix = $beaconmeta->{'PREFIX'} || ""; | ||||||
| 663 | (my $servicename = $beaconmeta->{'NAME'} || $osd->{'ShortName'} || "") =~ s/([<>&"]|[^\x00-\x7f])/''.ord($1).';'/ge; | ||||||
| 664 | |||||||
| 665 | my $target = $cgi->url(-path=>1); | ||||||
| 666 | |||||||
| 667 | my @result; | ||||||
| 668 | push(@result, $cgi->start_html( | ||||||
| 669 | -encoding => "UTF-8", | ||||||
| 670 | -title => "$servicename referring ".$query->as_string(), | ||||||
| 671 | -meta => {'robots'=>'noindex'}, | ||||||
| 672 | ($extra->{'css'} ? (-style => {'src'=>$extra->{'css'}}) : ()), | ||||||
| 673 | -head => [$cgi->Link({-rel=>'unapi-server', | ||||||
| 674 | -type=>'application/xml', | ||||||
| 675 | title=>'unAPI', | ||||||
| 676 | -href=>$target}), | ||||||
| 677 | $cgi->Link({-rel=>'start', | ||||||
| 678 | -href=>$target}), | ||||||
| 679 | ], | ||||||
| 680 | )); | ||||||
| 681 | |||||||
| 682 | push(@result, ''); | ||||||
| 683 | push(@result, ''); | ||||||
| 684 | |||||||
| 685 | push(@result, $cgi->h1("$hits References for ".$cgi->abbr({class=>"unapi-id", title=>"$canon"}, $query))); | ||||||
| 686 | |||||||
| 687 | push(@result, ' '); |
||||||
| 688 | push(@result, $cgi->p($cgi->span("Identifier:"), $cgi->a({href=>"$prefix$pretty"}, "$prefix$pretty"))) if $prefix; | ||||||
| 689 | # delete $idlist{$pretty} if $prefix; | ||||||
| 690 | push(@result, $cgi->p($cgi->span("Variant Identifiers:"), map {$cgi->span({class=>($idlist{$_} || "variantid")}, $_)} sort keys %idlist)) if %idlist; | ||||||
| 691 | push(@result, ''); | ||||||
| 692 | |||||||
| 693 | my ($srcsth, $srcexpl); | ||||||
| 694 | if ( $clusterid ) { | ||||||
| 695 | ($srcsth, $srcexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 696 | SELECT beacons.*, repos.* | ||||||
| 697 | FROM beacons NATURAL LEFT JOIN repos | ||||||
| 698 | WHERE ( (beacons.hash=?) | ||||||
| 699 | OR (beacons.hash IN (SELECT beacons.hash FROM cluster.beacons WHERE cluster.beacons.altid=?)) ) | ||||||
| 700 | ORDER BY repos.sort, repos.alias; | ||||||
| 701 | XxX | ||||||
| 702 | $self->stmtExplain($srcexpl, $clusterid, $clusterid) if $ENV{'DBI_PROFILE'}; | ||||||
| 703 | $srcsth->execute($clusterid, $clusterid) or croak("Could not execute >".$srcsth->{Statement}."<: ".$srcsth->errstr); | ||||||
| 704 | } | ||||||
| 705 | else { | ||||||
| 706 | ($srcsth, $srcexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 707 | SELECT beacons.*, repos.* | ||||||
| 708 | FROM beacons NATURAL LEFT JOIN repos | ||||||
| 709 | WHERE beacons.hash=? | ||||||
| 710 | ORDER BY repos.sort, repos.alias; | ||||||
| 711 | XxX | ||||||
| 712 | $self->stmtExplain($srcexpl, $hash) if $ENV{'DBI_PROFILE'}; | ||||||
| 713 | $srcsth->execute($hash) or croak("Could not execute >".$srcsth->{Statement}."<: ".$srcsth->errstr); | ||||||
| 714 | } | ||||||
| 715 | |||||||
| 716 | my $rows = 0; | ||||||
| 717 | push(@result, ' '); |
||||||
| 718 | my ($lastseq, @groups) = (0, ()); | ||||||
| 719 | while ( my $onerow = $srcsth->fetchrow_hashref ) { | ||||||
| 720 | $rows ++; | ||||||
| 721 | if ( $lastseq and $onerow->{'seqno'} == $lastseq ) { | ||||||
| 722 | my %vary; | ||||||
| 723 | foreach my $key ( grep /^(hash|altid|hits|info|link)$/, keys %$onerow ) { | ||||||
| 724 | my $pval = $onerow->{$key}; | ||||||
| 725 | next unless defined $pval; | ||||||
| 726 | $pval =~ s/([<>&"]|[^\x00-\x7f])/''.ord($1).';'/ge if $key eq "link"; | ||||||
| 727 | $vary{$key} = $pval; | ||||||
| 728 | } | ||||||
| 729 | push(@{$groups[$#groups]}, \%vary); | ||||||
| 730 | } | ||||||
| 731 | else { | ||||||
| 732 | my (%vary, %repos, %meta); | ||||||
| 733 | while ( my($key, $val) = each %$onerow ) { | ||||||
| 734 | my $pval = $val; | ||||||
| 735 | unless ( $key =~ /altid|feed|target|uri|link/i ) { | ||||||
| 736 | $pval =~ s/([<>&"]|[^\x00-\x7f])/''.ord($1).';'/ge if defined $pval}; | ||||||
| 737 | if ( $key =~ /time|revisit/i ) { | ||||||
| 738 | next unless $val; | ||||||
| 739 | $pval = HTTP::Date::time2str($val); | ||||||
| 740 | }; | ||||||
| 741 | if ( $key =~ /^bc(\w+)$/ ) { | ||||||
| 742 | $repos{$1} = $pval if $pval} | ||||||
| 743 | elsif ( $key =~ /^(hash|altid|hits|info|link)$/ ) { | ||||||
| 744 | $vary{$key} = $pval} | ||||||
| 745 | else { | ||||||
| 746 | $meta{"_$key"} = $pval if $pval} | ||||||
| 747 | }; | ||||||
| 748 | push(@groups, [\%repos, \%meta, \%vary]); | ||||||
| 749 | }; | ||||||
| 750 | $lastseq = $onerow->{'seqno'}; | ||||||
| 751 | }; | ||||||
| 752 | # Grouping done, now display... | ||||||
| 753 | |||||||
| 754 | my %didalreadysee; | ||||||
| 755 | foreach my $groupref ( @groups ) { | ||||||
| 756 | my ($repos, $meta, @vary) = @$groupref; | ||||||
| 757 | |||||||
| 758 | my $aos = $meta->{'_alias'} || $meta->{'_seqno'}; | ||||||
| 759 | |||||||
| 760 | my $multi = (scalar @vary > 1) ? "multi" : "single"; | ||||||
| 761 | push(@result, qq! !); |
||||||
| 762 | push(@result, $cgi->h3({class=>"aggregator", onClick=>"toggle('ag$aos')"}, "Administrative Details")); | ||||||
| 763 | |||||||
| 764 | push(@result, $cgi->h3({class=>"beacon", onClick=>"toggle('bc$aos')"}, "Repository Details")); | ||||||
| 765 | |||||||
| 766 | if ( $multi eq "single" ) { | ||||||
| 767 | push(@result, $cgi->h3({class=>"hit", onClick=>"toggle('ht$aos')"}, "Result Details")); | ||||||
| 768 | |||||||
| 769 | my $vary = $vary[0]; | ||||||
| 770 | |||||||
| 771 | my $hits = $vary->{'hits'}; | ||||||
| 772 | my $description = $hits; | ||||||
| 773 | |||||||
| 774 | my $h = $vary->{'hash'}; | ||||||
| 775 | my $variantid = ($h eq $hash) ? "" : "variantid"; | ||||||
| 776 | my $p; | ||||||
| 777 | if ( $h eq $hash ) { | ||||||
| 778 | $p = $pretty} | ||||||
| 779 | elsif ( $clusterid && ref($c) ) { | ||||||
| 780 | $c->value(""); | ||||||
| 781 | my $did = $c->hash($h) || $c->value($h) || $h; | ||||||
| 782 | $p = $c->can("pretty") ? $c->pretty() : $c->value(); | ||||||
| 783 | }; | ||||||
| 784 | $p = ($clusterid ? $h : $pretty) unless defined $p; | ||||||
| 785 | |||||||
| 786 | my $uri = "???"; | ||||||
| 787 | if ( $uri = $vary->{'link'} ) { # o.k. | ||||||
| 788 | } | ||||||
| 789 | elsif ( $repos->{'ALTTARGET'} && $vary->{'altid'} ) { | ||||||
| 790 | $uri = sprintf($repos->{'ALTTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($vary->{'altid'}))} | ||||||
| 791 | elsif ( $repos->{'TARGET'} ) { | ||||||
| 792 | $uri = sprintf($repos->{'TARGET'}, $p)} | ||||||
| 793 | elsif ( $repos->{'ALTTARGET'} ) { | ||||||
| 794 | $uri = sprintf($repos->{'ALTTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($p))}; | ||||||
| 795 | |||||||
| 796 | my $redundant = ($didalreadysee{$uri}++) ? "subsequent" : ""; | ||||||
| 797 | |||||||
| 798 | my $guri = ""; | ||||||
| 799 | if ( $repos->{'IMGTARGET'} ) { | ||||||
| 800 | $guri = sprintf($repos->{'IMGTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($vary->{'altid'}))} | ||||||
| 801 | |||||||
| 802 | my @labels = grep /\S/, $repos->{'NAME'} || "", $repos->{'DESCRIPTION'} || "", $repos->{'INSTITUTION'} || ""; | ||||||
| 803 | my $rlabel; | ||||||
| 804 | if ( $hits == 1 ) { | ||||||
| 805 | $rlabel = $repos->{'ONEMESSAGE'} if $repos->{'ONEMESSAGE'}} | ||||||
| 806 | elsif ( $hits == 0 ) { | ||||||
| 807 | $rlabel = $repos->{'SOMEMESSAGE'} if $repos->{'SOMEMESSAGE'}}; | ||||||
| 808 | unless ( $rlabel ) { | ||||||
| 809 | $rlabel = $repos->{'MESSAGE'} || shift @labels || "???"}; | ||||||
| 810 | my $label = sprintf($rlabel, $hits); | ||||||
| 811 | |||||||
| 812 | my $ttip = pop @labels || ""; | ||||||
| 813 | $ttip =~ s/(\d+);/chr($1)/ge; | ||||||
| 814 | |||||||
| 815 | push(@result, $cgi->a({style=>"float: right; clear: right;", href=>$uri}, $cgi->img({alt=>$vary->{'info'}||$label,src=>$guri}))) if $guri; | ||||||
| 816 | |||||||
| 817 | push(@result, $cgi->h2({class=>"label $redundant $variantid ident_$p", id=>"head$aos"}, $cgi->a({href=>$uri, title=>$ttip}, $label))); | ||||||
| 818 | |||||||
| 819 | push(@result, qq! !); |
||||||
| 820 | push(@result, $cgi->span($vary->{'info'})) if $vary->{'info'}; | ||||||
| 821 | push(@result, $cgi->span("($hits Treffer)")) if $hits && ($rlabel !~ /%s/); | ||||||
| 822 | push(@result, ''); | ||||||
| 823 | |||||||
| 824 | push(@result, qq! | ||||||
| 825 | push(@result, $cgi->p({class=>"ht_uri"}, $cgi->span("Target URL:"), $cgi->a({href=>$uri}, CGI::escapeHTML($uri)))); | ||||||
| 826 | push(@result, $cgi->p({class=>"ht_guri"}, $cgi->span("Preview URL:"), $cgi->a({href=>$guri}, $guri))) if $guri; | ||||||
| 827 | push(@result, $cgi->p({class=>"ht_hits"}, $cgi->span("Hits:"), $hits)) if $hits; | ||||||
| 828 | push(@result, $cgi->p({class=>"ht_info"}, $cgi->span("Additional Info:"), $vary->{'info'})) if $vary->{'info'}; | ||||||
| 829 | push(@result, $cgi->p({class=>"ht_idnote"}, $cgi->span("Variant Identifier:"), $p)) if $variantid; | ||||||
| 830 | push(@result, ''); | ||||||
| 831 | } | ||||||
| 832 | else { | ||||||
| 833 | push(@result, $cgi->h3({class=>"hit", onClick=>"mtoggle('res$aos', 'hit')"}, "Result Details")); | ||||||
| 834 | my $hits = scalar @vary; | ||||||
| 835 | |||||||
| 836 | my @labels = grep /\S/, $repos->{'NAME'} || "", $repos->{'DESCRIPTION'} || "", $repos->{'INSTITUTION'} || ""; | ||||||
| 837 | my $rlabel = $repos->{'MESSAGE'} || shift @labels || "???"; | ||||||
| 838 | my $ttip = pop @labels || ""; | ||||||
| 839 | $ttip =~ s/(\d+);/chr($1)/ge; | ||||||
| 840 | |||||||
| 841 | # my $rlabel = $repos->{'SOMEMESSAGE'} || $repos->{'MESSAGE'} || $repos->{'DESCRIPTION'} || $repos->{'NAME'} || $repos->{'INSTITUTION'} || "???"; | ||||||
| 842 | |||||||
| 843 | # my $ttip = $repos->{'MESSAGE'} ? $repos->{'DESCRIPTION'} || $repos->{'NAME'} || $repos->{'INSTITUTION'} || "" | ||||||
| 844 | |||||||
| 845 | # : $repos->{'INSTITUTION'} || $repos->{'NAME'} || ""; | ||||||
| 846 | |||||||
| 847 | # $ttip = "" if $ttip eq $rlabel; | ||||||
| 848 | $ttip =~ s/(\d+);/chr($1)/ge; | ||||||
| 849 | |||||||
| 850 | my $label = sprintf($rlabel, $hits); | ||||||
| 851 | push(@result, $cgi->h2({class=>"label", id=>"head$aos"}, $label)); | ||||||
| 852 | |||||||
| 853 | push(@result, qq!
|
||||||
| 854 | my $cnt = 0; | ||||||
| 855 | foreach my $vary ( @vary ) { | ||||||
| 856 | $cnt ++; | ||||||
| 857 | |||||||
| 858 | my $h = $vary->{'hash'}; | ||||||
| 859 | my $variantid = ($h eq $hash) ? "" : "variantid"; | ||||||
| 860 | my $p; | ||||||
| 861 | if ( $h eq $hash ) { | ||||||
| 862 | $p = $pretty} | ||||||
| 863 | elsif ( $clusterid && ref($c) ) { | ||||||
| 864 | $c->value(""); | ||||||
| 865 | my $did = $c->hash($h) || $c->value($h) || $h; | ||||||
| 866 | $p = $c->can("pretty") ? $c->pretty() : $c->value(); | ||||||
| 867 | }; | ||||||
| 868 | $p = ($clusterid ? $h : $pretty) unless defined $p; | ||||||
| 869 | |||||||
| 870 | my $uri = "???"; | ||||||
| 871 | if ( $uri = $vary->{'link'} ) { # o.k. | ||||||
| 872 | } | ||||||
| 873 | elsif ( $repos->{'ALTTARGET'} && $vary->{'altid'} ) { | ||||||
| 874 | $uri = sprintf($repos->{'ALTTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($vary->{'altid'}))} | ||||||
| 875 | elsif ( $repos->{'TARGET'} ) { | ||||||
| 876 | $uri = sprintf($repos->{'TARGET'}, $p)} | ||||||
| 877 | elsif ( $repos->{'ALTTARGET'} ) { | ||||||
| 878 | $uri = sprintf($repos->{'ALTTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($p))}; | ||||||
| 879 | |||||||
| 880 | my $redundant = ($didalreadysee{$uri}++) ? "subsequent" : ""; | ||||||
| 881 | |||||||
| 882 | my $guri = ""; | ||||||
| 883 | if ( $repos->{'IMGTARGET'} ) { | ||||||
| 884 | $guri = sprintf($repos->{'IMGTARGET'}, $p, SeeAlso::Source::BeaconAggregator::urlpseudoescape($vary->{'altid'}))} | ||||||
| 885 | |||||||
| 886 | my $hits = $vary->{hits} if $vary->{hits} and $vary->{hits} != 1; | ||||||
| 887 | |||||||
| 888 | push(@result, qq! |
||||||
| 889 | push(@result, $cgi->div({style=>"float: right;"}, $cgi->a({href=>$uri}, $cgi->img({src=>$guri})))) if $guri; | ||||||
| 890 | push(@result, $cgi->a({href=>$uri}, $cgi->span($vary->{'info'} || "[$cnt.]"))); | ||||||
| 891 | push(@result, $cgi->span("($hits Treffer)")) if $hits; | ||||||
| 892 | push(@result, ''); | ||||||
| 893 | |||||||
| 894 | push(@result, qq! | ||||||
| 895 | push(@result, $cgi->p({class=>"ht_uri"}, $cgi->span("Target URL:"), $cgi->a({href=>$uri}, $uri))); | ||||||
| 896 | push(@result, $cgi->p({class=>"ht_guri"}, $cgi->span("Preview URL:"), $cgi->a({href=>$guri}, $guri))) if $guri; | ||||||
| 897 | push(@result, $cgi->p({class=>"ht_hits"}, $cgi->span("Hits:"), $vary->{hits})) if $vary->{hits}; | ||||||
| 898 | push(@result, $cgi->p({class=>"ht_info"}, $cgi->span("Additional Info:"), $vary->{'info'})) if $vary->{'info'}; | ||||||
| 899 | push(@result, $cgi->p({class=>"ht_idnote"}, $cgi->span("Variant Identifier:"), $p)) if $variantid; | ||||||
| 900 | |||||||
| 901 | push(@result, ''); | ||||||
| 902 | push(@result, ''); | ||||||
| 903 | }; | ||||||
| 904 | push(@result, qq!!); | ||||||
| 905 | } | ||||||
| 906 | |||||||
| 907 | push(@result, qq! | ||||||
| 908 | foreach ( sort keys %$repos ) { | ||||||
| 909 | next if /(MESSAGE|TARGET)$/; | ||||||
| 910 | next unless $repos->{$_}; | ||||||
| 911 | $repos->{$_} =~ s!([a-z]+://\S+)!$cgi->a({href=>"$1", target=>"_blank"}, "$1")!ge; # URL | ||||||
| 912 | $repos->{$_} =~ s!(?:\<\s*)?(\w[\w.-]*)\@((?:\w[\w-]*\.)+\w+)(?:\s*\>)?!<$1 (at) $2>!g; # Mail Addr | ||||||
| 913 | $repos->{$_} =~ s/\s*\|\s*/ | /g; # Examples | ||||||
| 914 | next if /^(FORMAT|PREFIX|REVISIT|VERSION)$/; # Postpone to "administrative Details" | ||||||
| 915 | push(@result, $cgi->p({class=>"bc_$_"}, $cgi->span("#$_:"), $repos->{$_})); | ||||||
| 916 | }; | ||||||
| 917 | push(@result, $cgi->p({class=>"ag_mtime"}, $cgi->span("Modified:"), $meta->{'_mtime'})) if $meta->{'_mtime'}; | ||||||
| 918 | push(@result, ''); | ||||||
| 919 | |||||||
| 920 | push(@result, qq! | ||||||
| 921 | foreach ( sort keys %$repos ) { | ||||||
| 922 | next unless /^(FORMAT|PREFIX|REVISIT|VERSION)$/; | ||||||
| 923 | next unless $repos->{$_}; | ||||||
| 924 | push(@result, $cgi->p({class=>"bc_$_"}, $cgi->span("#$_:"), $repos->{$_})); | ||||||
| 925 | }; | ||||||
| 926 | push(@result, $cgi->p({class=>"ag_ftime"}, $cgi->span("Loaded:"), $meta->{'_ftime'})) if $meta->{'_ftime'}; | ||||||
| 927 | push(@result, $cgi->p({class=>"ag_fstat"}, $cgi->span("Load status:"), $meta->{'_fstat'})) if $meta->{'_fstat'}; | ||||||
| 928 | push(@result, $cgi->p({class=>"ag_utime"}, $cgi->span("Update attempt:"), $meta->{'_utime'})) if $meta->{'_utime'}; | ||||||
| 929 | push(@result, $cgi->p({class=>"ag_ustat"}, $cgi->span("Update status:"), $meta->{'_ustat'})) if $meta->{'_ustat'}; | ||||||
| 930 | push(@result, $cgi->p({class=>"ag_counti"}, $cgi->span("Identifiers:"), $meta->{'_counti'})) if $meta->{'_counti'}; | ||||||
| 931 | push(@result, $cgi->p({class=>"ag_countu"}, $cgi->span("Distinct Ids:"), $meta->{'_countu'})) if $meta->{'_countu'}; | ||||||
| 932 | push(@result, $cgi->p({class=>"ag_sort"}, $cgi->span("Sort key:"), $meta->{'_sort'})) if $meta->{'_sort'}; | ||||||
| 933 | push(@result, $cgi->p({class=>"ag_admin"}, $cgi->span("Remark:"), $meta->{'_admin'})) if $meta->{'_admin'}; | ||||||
| 934 | push(@result, ''); | ||||||
| 935 | |||||||
| 936 | push(@result, ''); | ||||||
| 937 | |||||||
| 938 | push(@result, ''); | ||||||
| 939 | }; | ||||||
| 940 | push(@result, ''); | ||||||
| 941 | |||||||
| 942 | push(@result, ' '); |
||||||
| 943 | # $cgi->span("provided by:"), | ||||||
| 944 | push(@result, $cgi->p({class=>"mt_NAME"}, $cgi->a({href=>$target}, $servicename))); | ||||||
| 945 | # $cgi->span("Service description:"), | ||||||
| 946 | (my $descr = $beaconmeta->{'DESCRIPTION'} || $osd->{'Description'} || "") =~ s/([<>&"]|[^\x00-\x7f])/''.ord($1).';'/ge; | ||||||
| 947 | push(@result, $cgi->p({class=>"mt_DESCRIPTION"}, $descr)); | ||||||
| 948 | push(@result, ''); | ||||||
| 949 | |||||||
| 950 | my($tu, $ts, $tcu, $tcs) = times(); | ||||||
| 951 | push(@result, sprintf("", $tu, $ts, $tu+$ts), $cgi->end_html()); | ||||||
| 952 | return join("\n", @result); | ||||||
| 953 | } | ||||||
| 954 | |||||||
| 955 | |||||||
| 956 | =head2 get_meta () | ||||||
| 957 | |||||||
| 958 | Returns a pair of hash references: | ||||||
| 959 | |||||||
| 960 | =over 8 | ||||||
| 961 | |||||||
| 962 | =item 1 | ||||||
| 963 | |||||||
| 964 | OSD fields | ||||||
| 965 | |||||||
| 966 | =item 2 | ||||||
| 967 | |||||||
| 968 | Beacon header fields | ||||||
| 969 | |||||||
| 970 | =back | ||||||
| 971 | |||||||
| 972 | =cut | ||||||
| 973 | |||||||
| 974 | sub get_meta { | ||||||
| 975 | my ($self) = @_; | ||||||
| 976 | |||||||
| 977 | my ($metasth, $metaexpl) = $self->stmtHdl(<<"XxX"); | ||||||
| 978 | SELECT key, val FROM osd; | ||||||
| 979 | XxX | ||||||
| 980 | $self->stmtExplain($metaexpl) if $ENV{'DBI_PROFILE'}; | ||||||
| 981 | $metasth->execute() or croak("Could not execute >".$metasth->{Statement}."<: ".$metasth->errstr); | ||||||
| 982 | my (%osd, %beaconmeta); | ||||||
| 983 | while ( my $aryref = $metasth->fetchrow_arrayref ) { | ||||||
| 984 | my ($key, $val) = @$aryref; | ||||||
| 985 | next unless $val; | ||||||
| 986 | if ($key =~ s/^bc// ) { # BeaconMeta Fields | ||||||
| 987 | $beaconmeta{$key} = $val} | ||||||
| 988 | elsif ( exists $osd{$key} ) { | ||||||
| 989 | if ( ref($osd{$key}) ) { | ||||||
| 990 | push(@{$osd{$key}}, $val)} | ||||||
| 991 | else { | ||||||
| 992 | $osd{$key} = [$osd{$key}, $val]}; | ||||||
| 993 | } | ||||||
| 994 | else { | ||||||
| 995 | $osd{$key} = $val}; | ||||||
| 996 | }; | ||||||
| 997 | return (\%osd, \%beaconmeta); | ||||||
| 998 | } | ||||||
| 999 | |||||||
| 1000 | =head1 AUTHOR | ||||||
| 1001 | |||||||
| 1002 | Thomas Berger | ||||||
| 1003 | CPAN ID: THB | ||||||
| 1004 | gymel.com | ||||||
| 1005 | THB@cpan.org | ||||||
| 1006 | |||||||
| 1007 | =head1 COPYRIGHT | ||||||
| 1008 | |||||||
| 1009 | This program is free software; you can redistribute | ||||||
| 1010 | it and/or modify it under the same terms as Perl itself. | ||||||
| 1011 | |||||||
| 1012 | The full text of the license can be found in the | ||||||
| 1013 | LICENSE file included with this module. | ||||||
| 1014 | |||||||
| 1015 | |||||||
| 1016 | =cut | ||||||
| 1017 | |||||||
| 1018 | 1; | ||||||
| 1019 | # The preceding line will help the module return a true value | ||||||
| 1020 |