package EPrints::Plugin::Screen::Report; # Abstract class that handles the Report tools use JSON qw(encode_json); use EPrints::Plugin::Screen; @ISA = ( 'EPrints::Plugin::Screen' ); use strict; sub new { my( $class, %params ) = @_; my $self = $class->SUPER::new(%params); push @{$self->{actions}}, qw( export search newsearch update ); $self->{sconf} = "report"; $self->{appears} = [ { place => "key_tools", position => 1000, }, ]; return $self; } sub get_report { shift->{report} } sub can_be_viewed { my( $self ) = @_; return 1 if( $self->{public} ); #allow a report to be publicly available return 0 if( !defined $self->{repository}->current_user ); return $self->allow( 'report' ); } sub allow_export { shift->can_be_viewed } sub action_export {} sub wishes_to_export { $_[0]->repository->param( 'export' ) || $_[0]->repository->param( 'ajax' ); } sub export_mimetype { my( $self ) = @_; my $plugin = $self->{processor}->{plugin}; if( !defined $plugin ) { if( $self->repository->param( "ajax" ) ) { return "application/json; charset=utf-8"; } return "text/html; charset=utf-8"; } return $plugin->param( "mimetype" ); } sub export { my( $self ) = @_; my $part = $self->repository->param( "ajax" ); my $f = "ajax_$part"; if( $self->can( $f ) ) { binmode(STDOUT, ":utf8"); return $self->$f; } my $plugin = $self->{processor}->{plugin}; return $self->SUPER::export if !defined $plugin; my $items = $self->items; if( $plugin->{grouped} && defined $self->{processor}->{group} ) #some export plugins can accept groupings { $items = $self->get_grouped_items( $items, 0 ); } $plugin->initialise_fh( \*STDOUT ); $plugin->output_list( list => $items, fh => \*STDOUT, exportfields => $self->{processor}->{exportfields}, dataset => $self->{processor}->{dataset}, plugin => $self, ); } sub allow_search { return 1; } #generates search config sub _create_search { my( $self ) = @_; my $session = $self->{session}; my $report_plugin = $self->{processor}->screen; # Do not create a search config if this is not configured in the report return if ! defined $report_plugin->{searchdatasetid}; $self->{processor}->{report_plugin} = $report_plugin; my $report_ds = $session->dataset( $report_plugin->{searchdatasetid} ); if( defined $report_ds ) { $self->{processor}->{datasetid} = $report_ds->base_id; my $sconf = $report_ds->search_config( $report_plugin->{sconf} ); my $format = "report/" . $report_ds->base_id; $self->{processor}->{search} = $session->plugin( "Search" )->plugins( { keep_cache => 1, session => $self->{session}, dataset => $report_ds, %{$sconf} }, type => "Search", can_search => $format, ); } } sub allow_newsearch { return 1; } sub allow_update { return 1; } sub action_newsearch { my( $self ) = @_; my $session = $self->{session}; #$self->{processor}->{report} = $session->param( 'report' ); #$self->{processor}->{screenid} = $self->{processor}->{report}; #$self->{processor}->{action} = "newsearch"; #$self->_create_search; } sub action_update { my( $self ) = @_; my $session = $self->{session}; #$self->{processor}->{report} = $session->param( 'report' ); #$self->{processor}->{screenid} = $self->{processor}->{report}; #$self->{processor}->{action} = "newsearch"; #$self->_create_search; } sub action_search { my( $self ) = @_; $self->{processor}->{action} = "search"; #read parameters my $session = $self->{session}; $self->{processor}->{report} = $session->param( 'report' ); $self->{processor}->{screenid} = $self->{processor}->{report}; $self->_create_search; my $loaded = 0; my $id = $session->param( "cache" ); if( defined $id ) { $loaded = $self->{processor}->{search}->from_cache( $id ); } if( !$loaded ) { my $exp = $session->param( "exp" ); if( defined $exp ) { $self->{processor}->{search}->from_string( $exp ); # cache expired... $loaded = 1; } } my @problems; if( !$loaded ) { for( $self->{processor}->{search}->from_form ) { $self->{processor}->add_message( "warning", $_ ); } } #display the results $self->render; } sub properties_from { my( $self ) = @_; my $repo = $self->repository; $self->SUPER::properties_from; if( defined ( my $dsid = $self->param( "datasetid" ) ) ) { $self->{processor}->{dataset} = $self->repository->dataset( $dsid ); } # sf2 - TODO - bark if dataset is not set? perhaps there are other ways to get the objects from... my $report = $self->get_report(); #get a search object if we have one from a previous search action, so that we might later use it to do an export or search action $self->_create_search; if( defined $self->repository->param( "search" ) ) { $self->{processor}->{search}->from_string( $self->repository->param( "search" ) ) if defined $self->{processor}->{search}; $self->{processor}->{export_search} = 1; } my $format = $self->repository->param( "export" ); if( $format && $report ) { my $plugin = $self->repository->plugin( "Export::$format", report => $report ); if( defined $plugin && ( $plugin->can_accept( "report/$report" ) || ($plugin->can_accept( "report/generic" ) ) ) ) { $self->{processor}->{plugin} = $plugin; } } #list of export fields retrieved from non-abstract instances of reports my @exportfields; if( defined $repo->config( $self->{export_conf}, "exportfields" ) ) { my @keys; if( defined $repo->config( $self->{export_conf}, "exportfield_order" ) ) { @keys = @{$repo->config( $self->{export_conf}, "exportfield_order" )}; } else { @keys = keys %{$repo->config( $self->{export_conf}, "exportfields" )}; } foreach my $key ( @keys ) { foreach my $fieldname ( @{$repo->config( $self->{export_conf}, "exportfields" )->{$key}} ) { push @exportfields, $fieldname if defined $self->repository->param( $fieldname ); } } } my $sort = $self->repository->param( "sort" ); if( defined $sort ) { $self->{processor}->{sort} = $sort; } my $group = $self->repository->param( "group" ); $self->{processor}->{group_exp} = $group; #store the group expression for later use #decode the group expression my %group_opts; if( defined $group ) { #get field and associated configuration about the group, e.g. res, truncate, reverse_order etc. my @group = split(/;/, $group ); my $first = 1; foreach my $g (@group) { if( $first ) { $group = $g; } else { my @opts = split(/=/, $g ); $group_opts{$opts[0]} = $opts[1]; } $first = 0; } $self->{processor}->{group} = $group; $self->{processor}->{group_opts} = \%group_opts; } $self->{processor}->{exportfields} = \@exportfields; } # \@({meta_fields=>[ "field1", "field2" "document.field3" ], merge=>"ANY", match=>"EX", value=>"bees"}, {meta_fields=>[ "field4" ], value=>"honey"}); # e.g. # return [ { meta_fields => [ 'type' ], value => 'article' } ] sub filters { return []; } # how to select items i.e. the slice of data we want to validate/export? # sub items { my( $self ) = @_; if( $self->{processor}->{action} eq "search" || $self->{processor}->{export_search} ) { my $report = $self->{processor}->{report_plugin}; $report->apply_filters if $report->can( 'apply_filters' ); my $items = $self->{processor}->{search}->perform_search; if( defined $self->{processor}->{sort} ) { $items = $items->reorder( $self->{processor}->{sort} ); } return $items; } elsif( defined $self->{processor}->{dataset} ) { my %search_opts = ( filters => $self->filters, satisfy_all => 1 ); if( defined $self->param( 'custom_order' ) ) { $search_opts{custom_order} = $self->param( 'custom_order' ); } if( defined $self->{processor}->{sort} ) #an ordering specified by the user should trump the reports custom order { $search_opts{custom_order} = $self->{processor}->{sort}; } return $self->{processor}->{dataset}->search( %search_opts ); } # we can't return an EPrints::List if {dataset} is not defined return undef; } # from Reports/ROS/Journals.pm # TODO Note quite a lot of replication between this and Export::Report::CSV::output_dataobj sub validate_dataobj { my( $plugin, $dataobj ) = @_; my $repo = $plugin->repository; my $report_fields = $plugin->report_fields( $dataobj ); my $val_fields = $plugin->validate_fields( $dataobj ); # related objects and their datasets my $objects = $plugin->get_related_objects( $dataobj ); my $valid_ds = {}; foreach my $dsid ( keys %$objects ) { $valid_ds->{$dsid} = $repo->dataset( $dsid ); } my @problems; foreach my $field ( @{ $plugin->report_fields_order( $dataobj ) || [] } ) { # validation action my $v_field = $val_fields->{$field}; next unless defined $v_field; # no validation required # simple case - code handles validation if( ref( $v_field ) eq 'CODE' ) { # a sub{} we need to run eval { &$v_field( $plugin, $objects, \@problems ); }; if( $@ ) { $repo->log( "Validation Runtime error: $@" ); } next; } elsif( lc $v_field ne "required" ) { $repo->log( "Validation Runtime error: $v_field must be code ref or 'required'" ); next; } # check required values my $value; # the value to validate my $ep_field = $report_fields->{$field}; if( ref( $ep_field ) eq 'CODE' ) { # a sub{} we need to run eval { $value = &$ep_field( $plugin, $objects ); }; if( $@ ) { $repo->log( "Validation Runtime error: $@" ); } } elsif( $ep_field =~ /^([a-z_]+)\.([0-9a-z_]+)$/ ) { # a straight mapping with an EPrints field my( $ds_id, $ep_fieldname ) = ( $1, $2 ); my $ds = $valid_ds->{$ds_id}; if( defined $ds && $ds->has_field( $ep_fieldname ) ) { $value = $objects->{$ds_id}->value( $ep_fieldname ); } else { # dataset or field doesn't exist $repo->log( "Validation Runtime error: dataset $ds_id or field $ep_fieldname doesn't exist" ); } } # is field set? if( !EPrints::Utils::is_set( $value ) ) { push @problems, "Missing required field $field"; } } return @problems; } # TODO Note copy of Export::Report::get_related_objects sub get_related_objects { my( $plugin, $dataobj ) = @_; my $cmd = [ 'reports', $plugin->get_report, 'get_related_objects' ]; if( $plugin->repository->can_call( @$cmd ) ) { return $plugin->repository->call( $cmd, $plugin->repository, $dataobj ) || {}; } # just pass the dataobj itself return { $dataobj->dataset->confid => $dataobj, }; } # TODO Note copy of Export::Report::report_fields_order sub report_fields_order { my( $plugin ) = @_; return $plugin->{report_fields_order} if( defined $plugin->{report_fields_order} ); my $report = $plugin->get_report(); return [] unless( defined $report ); $plugin->{report_fields_order} = $plugin->repository->config( 'reports', $report, 'fields' ); return $plugin->{report_fields_order}; } # TODO Note copy of Export::Report::report_fields sub report_fields { my( $plugin ) = @_; return $plugin->{report_fields} if( defined $plugin->{report_fields} ); my $report = $plugin->get_report(); return [] unless( defined $report ); $plugin->{report_fields} = $plugin->repository->config( 'reports', $report, 'mappings' ); return $plugin->{report_fields}; } sub validate_fields { my( $plugin ) = @_; return $plugin->{validate_fields} if( defined $plugin->{validate_fields} ); my $report = $plugin->get_report(); return [] unless( defined $report ); $plugin->{validate_fields} = $plugin->repository->config( 'reports', $report, 'validate' ); return $plugin->{validate_fields}; } ## rendering # The "splash page" sub render_splash_page { my( $self ) = @_; my $repo = $self->repository; my @plugins = $self->report_plugins; if( !scalar( @plugins ) ) { return $self->html_phrase( "no_reports" ); } my @labels; my @panels; #preset reports push @labels, $repo->html_phrase( "reports_preset" ); my $preset = $repo->make_element( "div" ); my $presets_added = 0; # top category: by classname > Report::ROS::SomeReport1, Report::ROS::SomeReport2 my $ul = $self->repository->make_element( 'ul', class => 'ep_report_category' ); # cat ~ category - !meeow my $cat = ""; my $cat_li = undef; my $cat_ul = undef; #prepare hash of reporting plugins my %report_hash; foreach my $report_plugin ( sort { $a->get_subtype cmp $b->get_subtype } @plugins ) { my $plugin_cat = $report_plugin->get_subtype; $plugin_cat =~ s/^Report::([^:]+):?:?(.*)$/$1/g; if( $cat ne $plugin_cat ) #we have a top-category { $cat = $plugin_cat; $report_hash{$cat} = []; } if( EPrints::Utils::is_set( $2 ) ) { push @{$report_hash{$cat}}, $report_plugin; } } for my $top_cat( sort keys %report_hash ) { my @report_plugins = @{$report_hash{$top_cat}}; if( scalar( @report_plugins ) > 0 ) { #render top-category $cat_li = $ul->appendChild( $self->repository->make_element( 'li' ) ); $cat_li->appendChild( $self->repository->html_phrase( "Plugin/Screen/Report/$top_cat:title" ) ); $cat_ul = $cat_li->appendChild( $self->repository->make_element( 'ul', class => 'ep_report_items' ) ); foreach my $r ( @report_plugins ) { my $sub_li = $cat_ul->appendChild( $self->repository->make_element( 'li' ) ); $sub_li->appendChild( $r->render_action_link ); $presets_added++; } } } $preset->appendChild( $ul ); push @panels, $preset; #custom reports push @labels, $repo->html_phrase( "reports_custom" ); my $custom = $repo->make_element( "div", id=>"custom_report" ); my $form = $repo->render_form( "get" ); $form->appendChild( $self->render_controls( 1 ) ); #add each report to the select component and generate search form if required my $report_select = $repo->make_element( "select", name=>"report", id=>"select_report" ); my %search_forms; my $custom_reports = 0; #foreach my $report_plugin ( @plugins ) foreach my $report_plugin ( sort { $a->get_subtype cmp $b->get_subtype } @plugins ) { if( $report_plugin->param( "custom" ) ) { $custom_reports++; my $formid = $report_plugin->{sconf}; #add to select component my $id = $report_plugin->{report}; my $option = $repo->make_element( "option", value => $report_plugin->get_subtype, form => $formid ); #set default option for select component if required (i.e. we have come from a refine search or new search link) my $refine_sconf = $self->{session}->param( "sconf" ); if( ( $self->{processor}->{action} eq "update" || $self->{processor}->{action} eq "newsearch" ) && defined $refine_sconf && $refine_sconf eq $formid ) { $option->setAttribute( selected => "selected" ); } #set option text and add to select $option->appendChild( $report_plugin->render_title ); $report_select->appendChild( $option ); #create search form #get report dataset and appropriate search config my $report_ds = $repo->dataset( $report_plugin->{searchdatasetid} ); my $sconf = $report_ds->search_config( $report_plugin->{sconf} ) ; #my $search = EPrints::Search->new( # keep_cache => 1, # session => $repo, # dataset => $report_ds, # %{$sconf} #); my $format = "report/" . $report_ds->base_id; my $searchexp = $repo->plugin( "Search" )->plugins( { keep_cache => 1, session => $self->{session}, dataset => $report_ds, %{$sconf} }, type => "Search", can_search => $format, ); $searchexp->from_form; #generate the form my $frag = $self->render_search_fields( $searchexp, $formid ); $search_forms{$formid} = $frag unless exists $search_forms{$formid}; } } $form->appendChild( $report_select ); $form->appendChild( $repo->render_hidden_field( "screen", $self->{screenid} ) ); #render possible search forms foreach my $formid (keys %search_forms) { my $table = $repo->make_element( "table", class=>"ep_search_fields", id=>$formid, style=>"display: none" ); $form->appendChild( $table ); $table->appendChild( $search_forms{$formid} ); } $form->appendChild( $self->render_controls ); $custom->appendChild( $form ); #javascript for changing forms based on report selection $custom->appendChild( $repo->make_javascript( 'initReportForm();' ) ); if( $custom_reports && $presets_added > 0 ) #set up tab interface { my @labels; my @panels; push @labels, $repo->html_phrase( "reports_preset" ); push @labels, $repo->html_phrase( "reports_custom" ); push @panels, $preset; push @panels, $custom; my %opts; if( $self->{processor}->{action} eq "newsearch" || $self->{processor}->{action} eq "update" ) { $opts{current} = 1; } return $repo->xhtml->tabs(\@labels, \@panels, %opts ); } elsif( $presets_added > 0 ) { return $preset; } else { return $custom; } } sub render_search_fields { my( $self, $search, $formid ) = @_; my $exp = $self->{session}->param( "exp" ); my $sconf = $self->{session}->param( "sconf" ); if( defined $exp && defined $sconf && $sconf eq $formid ) { $search->from_string( $exp ); } my $frag = $self->{session}->make_doc_fragment; foreach my $sf ( $search->get_non_filter_searchfields ) { $frag->appendChild( $self->{session}->render_row_with_help( help_prefix => $sf->get_form_prefix."_help", help => $sf->render_help, label => $sf->render_name, field => $sf->render, no_toggle => ( $sf->{show_help} eq "always" ), no_help => ( $sf->{show_help} eq "never" ), ) ); } return $frag; } sub render_controls { my( $self, $with_js ) = @_; my $div = $self->{session}->make_element( "div" , class => "ep_search_buttons" ); $div->appendChild( $self->{session}->render_action_buttons( _order => [ "search" ], #newsearch => $self->{session}->phrase( "lib/searchexpression:action_reset" ), search => $self->{session}->phrase( "lib/searchexpression:action_search" ) ) ); my $xml = $self->{session}->xml; if( $with_js ) { my $clear_form = $div->appendChild( $self->render_clearform( $xml ) ); } my $clear_btn = $div->appendChild( $xml->create_element( "button", type => "button", onclick => "clearForm();", class => "ep_form_action_button clear_button", ) ); $clear_btn->appendChild( $xml->create_text_node( $self->{session}->html_phrase( "lib/searchexpression:action_reset" ) ) ); return $div; } sub render { my( $self ) = @_; # if users access Screen::Report directly we want to display some sort of menu # where users can select viewable reports if( ( "EPrints::Plugin::".$self->get_id eq __PACKAGE__ && $self->{processor}->{action} ne "search" ) || $self->{processor}->{action} eq "newsearch" ) { return $self->render_splash_page; } my $repo = $self->repository; my $chunk = $repo->make_doc_fragment; $chunk->appendChild( $self->render_export_bar ); $chunk->appendChild( $self->render_group_options ); $chunk->appendChild( $self->render_sort_options ); if( $self->{processor}->{action} eq "search" ) { $chunk->appendChild( $self->render_refine_search ); } my $items = $self->items; if( !defined $items || $items->count == 0 ) { # No items message } my $json; if( defined $self->{processor}->{group} && $self->{processor}->{group} ne "" ) { my $grouped = $self->get_grouped_items( $items, 1 ); $json = encode_json $grouped; } else { my $item_ids = defined $items ? $items->ids : []; $json = "[".join(',',@$item_ids)."]"; } my $url = $repo->current_url( host => 1 ); my $parameters = URI->new; $parameters->query_form( $self->hidden_bits, ); $parameters = $parameters->query; my $ds = $repo->dataset( $self->param( 'datasetid' ) ) if defined $self->param( 'datasetid' ); my $prefix = $ds->base_id if defined $ds; # the main
my $container_id = sprintf( "ep_report_%s\_container", $self->get_report ); #update javascript parameters if coming from a search request if( $self->{processor}->{action} eq "search" ) { my $plugin = $self->{processor}->{report}; $plugin =~ s/:/%3A/g; $parameters = "screen=$plugin"; $prefix = $self->{processor}->{datasetid}; $container_id = sprintf( "ep_report_%s\_container", $self->{processor}->{report_plugin}->{report} ); } #show/hide compliance my $show_compliance = 1; $show_compliance = $self->{show_compliance} if defined $self->{show_compliance}; #custom labels my $labels = 0; $labels = encode_json $self->{labels} if defined $self->{labels}; $chunk->appendChild( $repo->make_javascript( <<"EOJ" ) ); document.observe("dom:loaded", function() { new EPrints_Screen_Report_Loader( { ids: $json, step: 20, prefix: '$prefix', url: '$url', parameters: '$parameters', container_id: '$container_id', show_compliance: $show_compliance, labels: $labels } ).execute(); }); EOJ $chunk->appendChild( $repo->make_element( 'div', class => 'ep_report_page', id => $container_id ) ); #show search controls after the results too if( $self->{processor}->{action} eq "search" ) { $chunk->appendChild( $self->render_refine_search ); } return $chunk; } sub render_export_bar { my( $self ) = @_; my $repo = $self->repository; my $chunk = $repo->make_doc_fragment; my @plugins = $self->export_plugins; return $chunk unless( scalar( @plugins ) || defined( $repo->config( $self->{export_conf}, "exportfields" ) ) ); my $report_ds = $repo->dataset( $self->{datasetid} ); my $form = $self->render_form; $form->setAttribute( method => "get" ); if( defined $self->repository->param( "search" ) || $self->{processor}->{action} eq "search" ) { $form->appendChild( $repo->render_hidden_field( "search", $self->{processor}->{search}->serialise) ); } if( defined $self->{processor}->{sort} ) { $form->appendChild( $repo->render_hidden_field( "sort", $self->{processor}->{sort} ) ); } if( defined $self->{processor}->{group_exp} ) { $form->appendChild( $repo->render_hidden_field( "group", $self->{processor}->{group_exp} ) ); } if( !defined( $repo->config( $self->{export_conf}, "exportfields" ) ) ) { #no custom export fields defined, use export plugins designed for this report my $select = $form->appendChild( $repo->render_option_list( name => 'export', values => [map { $_->get_subtype } @plugins], labels => {map { $_->get_subtype => $_->get_name } @plugins}, ) ); } else { #provide list of default export plugins for reports @plugins = $self->export_plugins( "generic" ); my $select = $form->appendChild( $repo->render_option_list( name => 'export', values => [map { $_->get_subtype } @plugins], labels => {map { $_->get_subtype => $_->get_name } @plugins}, ) ); #create labels and panels for tabbed interfaced my $xml = $repo->xml; my $xhtml = $repo->xhtml; my $select_all = $form->appendChild( $self->render_selectall( $xml ) ); my $select_btn = $form->appendChild( $xml->create_element( "button", type => "button", onclick => "toggleCheckboxes();", class => "ep_form_action_button select_button", ) ); $select_btn->appendChild( $xml->create_text_node( $repo->html_phrase( "report_select" ) ) ); #allow user to choose which fields they want to export my $export_options = $repo->make_element( "div" ); my @keys; if( defined $repo->config( $self->{export_conf}, "exportfield_order" ) ) { @keys = @{$repo->config( $self->{export_conf}, "exportfield_order" )}; } else { @keys = keys %{$repo->config( $self->{export_conf}, "exportfields" )}; } foreach my $key ( @keys ) { #create a new list my $ul = $repo->make_element( "ul", style => "list-style-type: none" ); my $count = 0; #count how many fields we add foreach my $fieldname( @{$repo->config( $self->{export_conf}, "exportfields" )->{$key}} ) { if( defined $repo->config( $self->{export_conf}, "custom_export" ) && exists ${$repo->config( $self->{export_conf}, "custom_export" )}{$fieldname} ) #we have a custom export function instead { $count++; $self->_export_field_checkbox( $repo, $fieldname, $ul, $repo->html_phrase( "exportfieldoptions:$fieldname" ) ); } elsif( defined EPrints::Utils::field_from_config_string( $report_ds, $fieldname ) ) { my $field = EPrints::Utils::field_from_config_string( $report_ds, $fieldname ); $count++; $self->_export_field_checkbox( $repo, $fieldname, $ul, $field->render_name ); } } if( $count ) #only add options if we have any fields to show { my $div = $repo->make_element( "div", class=>"report_export_options" ); $div->appendChild( my $h = $repo->make_element( "h4" ) ); $h->appendChild( $repo->html_phrase( "exportfields:$key" ) ); $div->appendChild( $ul ); $export_options->appendChild( $div ); } } $form->appendChild( $export_options ); } $form->appendChild( $repo->render_button( name => "_action_export", class => "ep_form_action_button", value => $repo->phrase( 'cgi/users/edit_eprint:export' ) ) ); #create a collapsible box my $imagesurl = $repo->current_url( path => "static", "style/images" ); my %options; $options{session} = $repo; $options{id} = "ep_report_export"; $options{title} = $repo->html_phrase( "export_title" ); $options{collapsed} = 1; $options{content} = $form; $options{show_icon_url} = "$imagesurl/multi_down.png"; $options{hide_icon_url} = "$imagesurl/multi_up.png"; my $box = $repo->make_element( "div", style=>"text-align: left" ); $box->appendChild( EPrints::Box::render( %options ) ); $chunk->appendChild( $box ); return $chunk; } sub render_sort_options { my( $self ) = @_; my $repo = $self->repository; my $chunk = $repo->make_doc_fragment; return $chunk unless( defined( $repo->config( $self->{sort_conf}, "sortfields" ) ) ); my $sort_conf = $repo->config( $self->{sort_conf}, "sortfields" ); #build the form my $form = $self->render_form; $form->setAttribute( name => "sort_report" ); $form->setAttribute( method => "get" ); $chunk->appendChild( $form ); if( defined $repo->param( "search" ) || $self->{processor}->{action} eq "search" ) { $form->appendChild( $repo->render_hidden_field( "search", $self->{processor}->{search}->serialise) ); } if( defined $self->{processor}->{group_exp} ) { $form->appendChild( $repo->render_hidden_field( "group", $self->{processor}->{group_exp} ) ); } #display the links that will trigger the form my $first = 1; my $sort_links = $repo->make_doc_fragment; foreach my $sort_name ( keys %{$sort_conf} ) { my $sort_value = $sort_conf->{$sort_name}; if( $first ) { $form->appendChild( $repo->render_hidden_field( "sort", $sort_value) ); } if( !$first ) { $sort_links->appendChild( $repo->html_phrase( "Update/Views:group_seperator" ) ); } if( defined $self->{processor}->{sort} && $self->{processor}->{sort} eq $sort_value ) { my $strong = $repo->make_element( "strong" ); $strong->appendChild( $repo->html_phrase( $self->{sort_conf} . ":sort:" . $sort_name ) ); $sort_links->appendChild( $strong ); } else { my $link = $repo->render_link( 'javascript:sort_report("'.$sort_value.'")' ); $link->appendChild( $repo->html_phrase( $self->{sort_conf} . ":sort:" . $sort_name ) ); $sort_links->appendChild( $link ); } $first = 0; } $chunk->appendChild( $repo->html_phrase( "Report:sort_links", links=>$sort_links ) ); return $chunk; } sub render_group_options { my( $self ) = @_; my $repo = $self->repository; my $chunk = $repo->make_doc_fragment; return $chunk unless( defined( $repo->config( $self->{group_conf}, "groupfields" ) ) ); my $group_conf = $repo->config( $self->{group_conf}, "groupfields" ); #build the form my $form = $self->render_form; $form->setAttribute( name => "group_report" ); $form->setAttribute( method => "get" ); $chunk->appendChild( $form ); if( defined $repo->param( "search" ) || $self->{processor}->{action} eq "search" ) { $form->appendChild( $repo->render_hidden_field( "search", $self->{processor}->{search}->serialise) ); } if( defined $self->{processor}->{sort} ) { $form->appendChild( $repo->render_hidden_field( "sort", $self->{processor}->{sort} ) ); } #display the links that will trigger the form my $first = 1; my $group_links = $repo->make_doc_fragment; foreach my $group_value ( @{$group_conf} ) { my ($group_field) = split(/;/, $group_value ); if( $first ) { $form->appendChild( $repo->render_hidden_field( "group", $group_value) ); } if( !$first ) { $group_links->appendChild( $repo->html_phrase( "Update/Views:group_seperator" ) ); } if( defined $self->{processor}->{group} && $self->{processor}->{group} eq $group_field ) { my $strong = $repo->make_element( "strong" ); $strong->appendChild( $repo->html_phrase( $self->{group_conf} . ":group:" . $group_field ) ); $group_links->appendChild( $strong ); } else { my $link = $repo->render_link( 'javascript:group_report("'.$group_value.'")' ); $link->appendChild( $repo->html_phrase( $self->{group_conf} . ":group:" . $group_field ) ); $group_links->appendChild( $link ); } $first = 0; } #no grouping link at the end $group_links->appendChild( $repo->html_phrase( "Update/Views:group_seperator" ) ); if( defined $self->{processor}->{group} && $self->{processor}->{group} ne "" ) { my $link = $repo->render_link( 'javascript:group_report("")' ); $link->appendChild( $repo->html_phrase( "report:no_grouping" ) ); $group_links->appendChild( $link ); } else { my $strong = $repo->make_element( "strong" ); $strong->appendChild( $repo->html_phrase( "report:no_grouping" ) ); $group_links->appendChild( $strong ); } $chunk->appendChild( $repo->html_phrase( "Report:group_links", links=>$group_links ) ); return $chunk; } sub render_refine_search { my( $self ) = @_; my $repo = $self->repository; my $chunk = $repo->make_doc_fragment; if( defined $repo->param( "search" ) || $self->{processor}->{action} eq "search" ) { my $escexp = $self->{processor}->{search}->serialise; my $cacheid = $repo->param( "cache" ); my $sconf = $self->{sconf}; #set up new search link my $new_baseurl = URI->new( $self->{session}->get_uri ); $new_baseurl->query_form( screen => "Report", sconf => $sconf, ); my $search_links = $repo->make_doc_fragment; my $new_link = $repo->render_link( "$new_baseurl&_action_newsearch=1" ); $new_link->appendChild( $repo->html_phrase( "lib/searchexpression:new" ) ); $search_links->appendChild( $new_link ); #add a separator... $search_links->appendChild( $repo->html_phrase( "Update/Views:group_seperator" ) ); #set up refine search link my $refine_baseurl = URI->new( $self->{session}->get_uri ); $refine_baseurl->query_form( cache => $cacheid, exp => $escexp, screen => "Report", dataset => $self->{datasetid}, order => $self->{processor}->{search}->{custom_order}, sconf => $sconf, ); my $refine_link = $repo->render_link( "$refine_baseurl&_action_update=1" ); $refine_link->appendChild( $repo->html_phrase( "lib/searchexpression:refine" ) ); $search_links->appendChild( $refine_link ); $chunk->appendChild( $repo->html_phrase( "Report:search_links", links=>$search_links ) ); } return $chunk; } #adds a new checkbox to allow the user to choose which fields to export sub _export_field_checkbox { my( $self, $repo, $fieldname, $ul, $fieldlabel ) = @_; my $li = $repo->make_element( "li" ); $ul->appendChild( $li ); my $checkbox = $repo->make_element( "input", type => "checkbox", id => $fieldname, name => $fieldname, value => $fieldname ); if( defined $repo->config( $self->{export_conf}, "exportfield_defaults" ) ) { if( ( grep { $fieldname eq $_ } @{$repo->config( $self->{export_conf}, "exportfield_defaults" )} ) || ( scalar( @{$repo->config( $self->{export_conf}, "exportfield_defaults" )} ) == 0 ) ) { #only check defaults or check everything if defaults not defined $checkbox->setAttribute( "checked", "yes" ); } } my $label = $repo->make_element( "label", for => $fieldname ); $label->appendChild( $fieldlabel ); $li->appendChild( $checkbox ); $li->appendChild( $label ); } ### utility methods # TODO should use "JSON" package sub to_json { my( $self, $object ) = @_; return "" if( !defined $object ); # UTF-8 issues: # return JSON->new->utf8(1)->encode( $object ); if( ref( $object ) eq 'HASH' ) { my @stuff; while( my( $k, $v ) = each( %$object ) ) { next if( !EPrints::Utils::is_set( $v ) ); # or 'null' ? push @stuff, EPrints::Utils::js_string( $k ).':'.$self->to_json( $v ) } return '{' . join( ",", @stuff ) . '}'; } elsif( ref( $object ) eq 'ARRAY' ) { my @stuff; foreach( @$object ) { next if( !EPrints::Utils::is_set( $_ ) ); push @stuff, $self->to_json( $_ ); } return '[' . join( ",", @stuff ) . ']'; } return EPrints::Utils::js_string( $object ); } sub export_plugins { my( $self, $generic ) = @_; my @plugin_ids; my $repo = $self->repository; if( defined $repo->config( $self->{export_conf}, "export_plugins" ) ) { @plugin_ids = @{$repo->config( $self->{export_conf}, "export_plugins" )}; } elsif( $generic ) { @plugin_ids = $repo->plugin_list( type => "Export", can_accept => "report/generic", is_visible => "staff", is_advertised => 1, ); } else { @plugin_ids = $repo->plugin_list( type => "Export", can_accept => "report/".$self->get_report, is_visible => "staff", is_advertised => 1, ); } my @plugins; foreach my $id ( @plugin_ids ) { my $p = $repo->plugin( "$id" ) or next; push @plugins, $p; } return @plugins; } sub report_plugins { my( $self ) = @_; # sf2 - can't list via type => "Search::Report" ? my @plugin_ids = $self->repository->plugin_list( type => "Screen", ); my @plugins; foreach my $id ( @plugin_ids ) { next if( $id !~ /^Screen::Report::/ ); # note this also filters out $self (aka Screen::Report) my $p = $self->repository->plugin( "$id" ); next if( !defined $p || !$p->can_be_viewed ); push @plugins, $p; } return @plugins; } #returns a hash of values mapped to a label and a list #if ids_only is set the list of items are just represented by their id (used by report JS) sub get_grouped_items { my( $self, $items, $ids_only ) = @_; my $session = $self->{session}; my %grouped; my $grouping = $self->{processor}->{group}; my $metafield = $items->get_dataset->field( $grouping ); #set group_opts if appropriate if( defined $self->{processor}->{group_opts} ) { if( $metafield->type eq "date" ) { $metafield->{render_res} = $self->{processor}->{group_opts}->{res} if exists $self->{processor}->{group_opts}->{res}; } } #create a hash of field values to items (or item ids) $items->map( sub { my( $session, $dataset, $item ) = @_; my $multiple = $metafield->get_property( "multiple" ); my @group_values; if( $multiple ) { @group_values = @{$item->value( $grouping )}; } else { @group_values = ($item->value( $grouping )); } if( scalar @group_values > 0 ) { foreach my $group_value ( @group_values ) { my $group_id = $metafield->get_id_from_value( $session, $group_value ); #truncate group if appropriate if( defined $self->{processor}->{group_opts} ) { $group_id = substr( "\u$group_id", 0, $self->{processor}->{group_opts}->{truncate} ) if exists $self->{processor}->{group_opts}->{truncate}; } if( exists $grouped{$group_id} ) #we've already set this list up, push a new item on to the list { if( $ids_only ) #sometimes we only want ids rather than the whole item { push @{$grouped{$group_id}}, $item->id; } else { push @{$grouped{$group_id}}, $item; } } else #set up a list for this group { my @grouped_items; if( $ids_only ) #sometimes we only want ids rather than the whole item { @grouped_items = ($item->id); } else { @grouped_items = ($item); } $grouped{$group_id} = [@grouped_items]; } } } else { my $group_id = "Unspecified " . $metafield->name; if( exists $grouped{$group_id} ) #we've already set this list up, push a new item on to the list { if( $ids_only ) #sometimes we only want ids rather than the whole item { push @{$grouped{$group_id}}, $item->id; } else { push @{$grouped{$group_id}}, $item; } } else #set up a list for this group { my @grouped_items; if( $ids_only ) #sometimes we only want ids rather than the whole item { @grouped_items = ($item->id); } else { @grouped_items = ($item); } $grouped{$group_id} = [@grouped_items]; } } } ); #now sort the groups and add human readable labels my @sorted_groups; my $reverse = 0; $reverse = $self->{processor}->{group_opts}->{reverse_order} if exists $self->{processor}->{group_opts}->{reverse_order}; if( $metafield->type eq "namedset" ) { my( $tags, $labels ) = $metafield->tags_and_labels( $session ); #convert tags to a hash of values and a priority (for ordering) my %priority; my $index = 1; foreach my $tag (@{$tags} ) { $priority{$tag} = $index; $index++; } foreach my $key (sort {$priority{$a} <=> $priority{$b}} keys %grouped) { push @sorted_groups, $self->_make_grouped_item( $grouped{$key}, $labels->{$key} ); } } elsif( $metafield->type eq "subject" ) { my $ds = $session->dataset( "subject" ); my @values = keys %grouped; my $sorted = $metafield->sort_values( $session, \@values ); my %priority; my $index = 1; foreach my $value (@{$sorted} ) { $priority{$value} = $index; $index++; } foreach my $key (sort {$priority{$a} <=> $priority{$b}} keys %grouped) { my $subj = $ds->dataobj( $key ); push @sorted_groups, $self->_make_grouped_item( $grouped{$key}, EPrints::Utils::tree_to_utf8( $subj->render_description ) ) if defined $subj; } } elsif( $metafield->type eq "date" ) { if( $reverse ) { foreach my $key ( sort {$b <=> $a} keys %grouped ) { push @sorted_groups, $self->_make_grouped_item( $grouped{$key}, $key ); } } else { foreach my $key ( sort {$a <=> $b} keys %grouped ) { push @sorted_groups, $self->_make_grouped_item( $grouped{$key}, $key ); } } } else { if( $reverse ) { foreach my $key ( sort {$b cmp $a} keys %grouped ) { push @sorted_groups, $self->_make_grouped_item( $grouped{$key}, $key ); } } else { foreach my $key ( sort keys %grouped ) { push @sorted_groups, $self->_make_grouped_item( $grouped{$key}, $key ); } } } #check for any unspecified groups my $group_id = "Unspecified " . $metafield->name; if( exists $grouped{$group_id} ) { push @sorted_groups, $self->_make_grouped_item( $grouped{$group_id}, $group_id ); } return \@sorted_groups; } sub _make_grouped_item { my( $self, $list, $label ) = @_; my %group; $group{list} = $list; $group{label} = $label; return \%group; } sub render_selectall { my( $self, $xml ) = @_; my $toggle_function = ' var isChecked = true; function toggleCheckboxes() { var export_options = document.getElementsByClassName("report_export_options"); for( export_option of export_options ) { var checkboxes = export_option.getElementsByTagName( "input" ) for( checkbox of checkboxes ) { if(isChecked) { checkbox.checked = ""; } else { checkbox.checked = "checked"; } } } isChecked = !isChecked; }'; my $js_tag = $xml->create_element( "script" ); $js_tag->appendChild( $xml->create_text_node( $toggle_function ) ); return $js_tag; } sub render_clearform { my( $self, $xml ) = @_; my $clear_function = ' function clearForm(){ var report = document.getElementById( "custom_report" ); var form = report.getElementsByClassName("selected_form")[0]; var inputs = form.getElementsByTagName( "input" ); for( input of inputs ) { field_type = input.type.toLowerCase(); console.log(field_type); switch (field_type) { case "text": case "textarea": input.value = ""; break; case "radio": case "checkbox": if (input.checked) { input.checked = false; } break; case "select-one": case "select-multi": input.selectedIndex = -1; break; default: break; } } var selects = form.getElementsByTagName( "select" ); for( select of selects ) { if( select.hasAttribute("multiple") ) { select.selectedIndex = -1; } } }'; my $js_tag = $xml->create_element( "script" ); $js_tag->appendChild( $xml->create_text_node( $clear_function ) ); return $js_tag; } 1;