package GO::Tool::AddTool; use strict; use lib '/Users/gwg/go/scratch/tools'; use base 'GO::Tool::GenericGoTool'; ### Various modules required use Data::Dumper; use CGI::Carp qw(fatalsToBrowser); use Data::FormValidator::Constraints qw(:closures); use GO::Object::Generic; use GO::Object::Tool; use GO::Object::Developer; use GO::Object::ToolPreview; use GO::TestSet qw(dfv_test); use GO::Utilities qw(:all); use GO::GeneralPurposeParser; use URI::Escape; #use Utility::TSLParser; my $valid_run_modes = [ qw( tool_form preview submit ) ]; sub _specification { my $self = shift; my $spec = GO::Object::ToolPreview->get_spec(); my $extra = { "developer" => { subtext => 'Please supply the organization name and URL', }, "description" => { data_type => 'longtext', subtext => 'Please provide a brief description of the tool\'s functionality', }, "other_license_text" => { data_type => 'longtext', human_name => 'Other license (please specify)', }, "compatible_os" => { subtext => 'If the tool is standalone, please specify which operating systems it will run on', }, "other_feature_text" => { data_type => 'longtext', human_name => 'Other tool features (please specify)', allow_multiple => 1, }, "publication" => { subtext => 'Please give PubMed IDs or DOIs', }, "is_open_source" => { # data_type => 'boolean', test => dfv_test( 'is_in_list_p', { list => [ 'true', 'false' ], this => 1 } ), human_name => 'Is the tool open source?', list_values => [ 'true', 'false' ], list_values_human_name_h => { 'true' => 'yes', 'false' => 'no', }, required => 1, default => 'false', }, "comments", { data_type => 'longtext', human_name => 'Any other information or comments', # allow_multiple => 1, }, "tool_type" => { human_name => 'Tool type', allow_multiple => 1, test => dfv_test('is_in_list_p', { list => [ 'is_standalone_tool', 'is_online_tool' ], this => 1 } ), list_values => [ 'is_standalone_tool', 'is_online_tool' ], list_values_human_name_h => { is_standalone_tool => 'Standalone (downloadable) tool', is_online_tool => 'Web-based online tool' }, required => 1, dependencies => { 'is_standalone_tool' => [ 'compatible_os' ], }, }, ## what GO data is used "go_data_used" => { human_name => 'What GO data does the tool display?', }, ## GO data source "go_data_source" => { human_name => 'Where does the GO data come from?', }, ## GO data update frequency "go_data_update_frequency" => { human_name => 'How often is the GO data updated?', # daily, weekly, monthly, n_per_week, n_per_month, n_per_year, unknown }, ## runmode rm => { test => dfv_test('is_in_list_p', { list => $valid_run_modes, this => 1 } ), input_type => 'hidden', default => 'tool_form', list_values => $valid_run_modes, }, }; my @field_order = qw(name url email developer license other_license_text description tool_type compatible_os feature other_feature_text publication go_data_used go_data_update_frequency is_open_source comments rm); my $error_text = { name => 'should be more than two characters long', url => 'should start with http:// and contain valid URL characters', email => 'should be in the form name@domain.com', developer => 'should contain an URL', license => 'please pick an option', # other_license_text => '', description => 'should be at least 50 characters long', tool_type => 'please pick one or more options', compatible_os => 'please pick one or more options', feature => 'please pick one or more options', publication => 'should be at least 10 characters long', go_data_used => 'please pick one or more options', go_data_source => 'should be at least 5 characters long', go_data_update_frequency => 'should be at least 5 characters long', }; return ( map { if ($extra->{$_}) { foreach my $x (keys %{$extra->{$_}}) { $spec->{$_}{$x} = $extra->{$_}{$x}; } } if ($error_text->{$_}) { $spec->{$_}{error_text} = $error_text->{$_}; } ( $_, $spec->{$_} ) } @field_order ); # my @return; # push @return, ($_, $tool_h{$_}) foreach @field_order; # return @return; # return ( map { ( $_, $spec->{$_} ) } @field_order ); } sub setup { my $self = shift; $self->start_mode('tool_form'); $self->mode_param(\&set_run_mode); $self->error_mode('die_and_output_error'); $self->run_modes( 'tool_form' => 'tool_form', 'preview' => 'preview', 'submit' => 'submit', 'fatal_error' => 'die_and_output_error', 'AUTOLOAD' => 'tool_form', ); } sub set_run_mode { # return 'rm'; my $self = shift; my %cgi_h = $self->query->Vars; if (defined $cgi_h{rm} && grep { $cgi_h{rm} eq $_ } @$valid_run_modes) { return $cgi_h{rm}; } return 'tool_form'; } # configure the template before we do anything else sub cgiapp_init { my $self = shift; $self->tt_config( TEMPLATE_OPTIONS => { INCLUDE_PATH=> [ qw( /Users/gwg/go/scratch/tools/templates/ /Users/gwg/go/www/) ], TRIM=>1, }, ); } ## stuff to do before we run the page-specific code # if the mode is preview or submit and we don't have all the required data, # just show the query form # if we do have all the required data and the mode is preview, # show a preview of the tool plus the query data in case they want # to change the data # if the mode is submit, verify the data and if it's OK, show a # thank you message and send the data to the tool person sub cgiapp_prerun { my $self = shift; $self->debugme("Starting cgiapp_prerun in run mode ". $self->get_current_runmode); if ($self->get_current_runmode eq 'tool_form') { # get the spec and put default values into validated_query my $valid_h; my $spec = $self->get_spec(); foreach (keys %$spec) { $valid_h->{$_} = $spec->{$_}{default} if $spec->{$_}{default}; } if ($valid_h) { $self->param('validated_query', $valid_h); } } else { # check the input query, replace dodgy params with the defaults my $results = $self->check_input_query({ replace_with_defaults => 1, spec_profile => $self->get_current_runmode, report_multiples => 1 }); # print STDERR "results: ".Dumper($results); if (!$results->success) { # put the tool back into the tool form mode and create an error message $self->prerun_mode('tool_form'); $self->param('validator_results', $results); # summarize the errors and add a fatal message $self->fatal_msg({ MSG => "There were some errors with your tool submission. Please check you have entered everything correctly and try again.", results_obj => $results }); } } } =head2 tool_form This is the form for people to submit their tool. How exciting! If there were lotsa errors, they will be in the param 'error' =cut sub tool_form { my $self = shift; $self->startme; # get the spec info and the form stuff my $output = $self->add_tool_form; $output->{next_run_mode} = 'preview'; # set up all the other stuff and output the template return $self->tt_process("add_tool.tmpl", $self->set_up_output($output)) || die "Template toolkit messed up, the bastard!: ", $self->tt_obj->error(), "Dying"; } =head2 preview The user has submitted a decent set of data - good on 'em! Show them the tool as it would appear on the tools page, plus the tool submission form, in case they want to make any more changes =cut sub preview { my $self = shift; $self->startme; # get the spec info and the form stuff my $output = $self->add_tool_form; # create a tool preview with the data my $data = $self->convert_form_values($self->param('validated_query')); my $results = GO::Object::ToolPreview->new({ data => $data, return_as => 'success_hash', with_checks => 1, }); # $self->debugme("results of creating a ToolPreview:\n".Dumper($results)); # add the errors / messages if there are any $self->add_message_list($results->{ERROR_LIST}) if $results->{ERROR_LIST}; if (! $results->{SUCCESS}) { # this shouldn't have happened... hmmm! What's going on? die "Something very strange has happened here... hmmm! Dying"; } $output->{tool} = $results->{OBJECT}; $output->{tool_spec} = GO::Object::ToolPreview->get_spec(); $output->{validated_query} = $self->param('validated_query'); # the data was OK, so we can allow the user to submit the data $output->{next_run_mode} = 'submit'; # set up all the other stuff and output the template return $self->tt_process("preview_tool.tmpl", $self->set_up_output($output)) || die "Template toolkit messed up, the bastard!: ", $self->tt_obj->error(), "\nDying"; } =head2 submit The user has submitted a decent set of data. Show them a thank you page, and format the data into a suitable state for putting into the tools db. =cut sub submit { my $self = shift; $self->startme; # # get the spec info and the form stuff # my $output = $self->add_tool_form; # create a tool preview with the data my $data = $self->convert_form_values($self->param('validated_query')); my $results = GO::Object::ToolPreview->new({ data => $data, with_checks => 1, }); ### where to save the file (if appropriate) my $save_file = $self->param('save_file') || $ENV{GO_TOOL_ADDTOOL_SAVE_FILE} || undef; ### the tool master, official adder of tools to the GO tools db ### new tools will be sent by email to this address for checking my $tool_master = $self->param('tool_master') || $ENV{GO_TOOL_ADDTOOL_TOOL_MASTER} || undef; # see how we're going to save our data if (defined $save_file && -e $save_file) { my $text = $results->obj_to_text; # add a little extra info $text .= "submission_date: ".localtime() ."\nsubmitted_from: ".$self->cgiapp_get_query->remote_host()."\n"; $self->write_data_to_file({ file_name => $save_file, string => $text, file_separator => '\n' }); } elsif (defined $tool_master) { # send an email # the data has been checked and given a thorough working over # format the data and email it to the GO tools person # ? send an email to the tool submitter for their records my $message = "GO tool submission on " . localtime() . " from " . $self->cgiapp_get_query->remote_host() . "\n\nTool data:"; # format the tools data for writing to a file $message .= $results->obj_to_text; $self->debugme("results as obj_to_text: ".Dumper($results->obj_to_text)); $message .= '\nIf you do not receive a response regarding your tool within a week of submission, please forward this message to the GO helpdesk, gohelp\@geneontology.org.\nThank you!\n'; # email the data to the tools master $self->sendmail({ subject => 'GO Tool submission', from => 'gohelp@geneontology.org', to => $tool_master, message => $message, }); } else { $self->warning_msg("No save method specified: writing to STDERR"); print STDERR Dumper($results->obj_to_text); } # prepare the template my $output = { message_list => { level => 'info', list => [{ CLASS => 'info', MSG => 'Thank you for submitting your tool! The information has been submitted and will be added to the GO tools database once it has been checked.', }], }, page_title => 'GO Tool Submission Successful', h2_title => 'Thank you!', }; # set up all the other stuff and output the template return $self->tt_process("message.tmpl", $self->set_up_output($output)) || die "Template toolkit messed up, the bastard!: ", $self->tt_obj->error(), "Dying"; } =head2 add_tool_form This is the form for people to submit their tool. How exciting! =cut sub add_tool_form { my $self = shift; my $arg_h = shift; $self->startme; # my $ordered_object_spec = $arg_h->{spec}; # my $query = $arg_h->{query}; # my $results = $arg_h->{validator_results}; my $temp_h; my $q = $self->param('validated_query'); # don't forget that if the validator didn't validate properly, $validator_results # will return undef unless you use 'defined'. my $validator_results = $self->param('validator_results'); # Gather up the data needed for the form # read in the list of valid params my $ordered_object_spec = $self->get_spec({ 'ordered' => 1 }); my %spec_h = ( @$ordered_object_spec ); # $self->debugme("ordered object spec: ".Dumper($ordered_object_spec)); # get the keys from this array (every other array item) my $n = 0; my @p_list = grep { $_ if ! ( $n++ % 2 ) } @$ordered_object_spec; foreach my $param (@p_list) { my $param_spec = $spec_h{$param}; next if $param_spec->{constructor} && $param_spec->{constructor} eq 'automatic'; $param_spec->{name} = $param; if (defined $validator_results) { #$self->debugme("$param validator_results->valid: ".Dumper($validator_results->valid($param))); if (! $validator_results->valid($param) ) { $self->debugme("error with $param"); $param_spec->{error} = 'missing' if $validator_results->missing($param); $param_spec->{error} = 'invalid' if $validator_results->invalid($param); $param_spec->{error} = 'unknown' if $validator_results->unknown($param); } } my $selected = $q->{$param} || undef; # the input is values from a list if ($param_spec->{list_values}) { my @list_opts; if ($param_spec->{allow_multiple}) { foreach my $val (@{$param_spec->{list_values}}) { push @list_opts, { value => $val , label => $param_spec->{list_values_human_name_h}{$val} }; if ( grep { $val eq $_ } @$selected ) { $list_opts[-1]->{selected} = 1; } } } else { foreach my $val (@{$param_spec->{list_values}}) { push @list_opts, { value => $val , label => $param_spec->{list_values_human_name_h}{$val} }; if ($val eq $selected ) { $list_opts[-1]->{selected} = 1; } } } $param_spec->{"options"} = [ @list_opts ]; } else # we don't know what these params might be! { if ($param_spec->{allow_multiple}) { $param_spec->{value} = join("\n", @$selected) if $selected; } elsif ($param_spec->{data_type} && $param_spec->{data_type} eq 'longtext') { $param_spec->{value} = $selected if $selected; } else # everything else { $param_spec->{value} = $selected if $selected; } } push @{$temp_h->{spec_list}}, $param_spec; } return $temp_h; } =head2 tt_pre_process { Populates various useful variables from the environment Args: $template_vars # any data to go in the template =cut #sub tt_pre_process { sub set_up_output { my $self = shift; my $tmpl_vars = shift || {}; # the checked query if ($self->param('validated_query')) { $tmpl_vars->{query_h} = $self->param('validated_query'); $tmpl_vars->{url_for_query} = $self->create_url($tmpl_vars->{query_h}); } # validator results if (defined $self->param('validator_results')) { $tmpl_vars->{validator_results} = $self->param('validator_results'); } $tmpl_vars->{install_dir} = 'http://127.0.0.1/go/'; # any error messages if ($self->has_msgs) { $tmpl_vars->{ message } = $self->get_all_msgs('with_level'); } if ($ENV{VERBOSE} || $ENV{TMPL_VERBOSE} || $self->verbosity =~ /verbose/) { $tmpl_vars->{verbose} = 1; } return $tmpl_vars; } sub make_urls { my $tool_list = shift; foreach my $t (@$tool_list) { if ($t->publication) { foreach (@{$t->publication}) { if (lc($_->{db}) eq 'pmid') { $_->{url} = 'http://www.ncbi.nlm.nih.gov/pubmed/' . $_->{key}; } elsif (lc($_->{db}) eq 'doi') { $_->{url} = 'http://dx.doi.org/'.$_->{key}; } elsif (lc($_->{db}) eq 'url') { $_->{url} = $_->{key}; } } } } } # simple sub to send mail sub sendmail { my $self = shift; my $msg = shift; $ENV{PATH} = "/usr/sbin"; if (open (MAIL, "|/usr/sbin/sendmail -oi -t")) { #send to GO help and the form submitter? print MAIL "To: " . $msg->{to} . "\n" . "From: " . $msg->{from} . "\n" . "Subject: " . $msg->{subject} . "\n\n" . $msg->{message}; close(MAIL); } else { $self->fatal_msg("Could not send mail: $!"); } } sub convert_form_values { my $self = shift; my $data = shift; my $prvw_data; foreach (keys %$data) { $prvw_data->{$_} = $data->{$_}; } # change the data so we can create a ToolPreview object out of it if ($prvw_data->{tool_type}) { #$self->debugme("prvw_data->{tool_type}: ".Dumper($prvw_data->{tool_type})); foreach (@{$prvw_data->{tool_type}}) { $prvw_data->{$_} = 'true'; } delete $prvw_data->{tool_type}; } if ($prvw_data->{is_open_source} && $prvw_data->{is_open_source} == 'false') { delete $prvw_data->{is_open_source}; } return $prvw_data; } 1;