Merge branch 'topic/stock_detail_page'
[sgn.git] / cgi-bin / chado / publication.pl
blob9dc9d6bf5311080386e718ce577b428ef9aa1c12
1 package CXGN::Chado::PublicationDetailPage;
3 use base qw/CXGN::Page::Form::SimpleFormPage/;
5 use strict;
6 use warnings;
8 use CXGN::Page;
9 use CXGN::Page::FormattingHelpers qw/info_section_html
10 page_title_html
11 columnar_table_html
12 info_table_html
13 html_optional_show
14 tooltipped_text
16 use CXGN::People::Person;
17 use CXGN::Chado::Publication;
18 use CXGN::Chado::Pubauthor;
19 use CXGN::Phenome::Locus;
20 use CXGN::Phenome::Locus::LocusRanking;
21 use CXGN::Phenome::Allele;
22 use CXGN::Chado::Dbxref;
23 use CXGN::Contact;
24 use CXGN::People::PageComment;
25 use CXGN::Tools::Text qw/ sanitize_string /;
27 my $publication_detail_page=CXGN::Chado::PublicationDetailPage->new();
29 sub new {
30 my $class=shift;
31 my $self= $class->SUPER::new(@_);
32 $self->set_script_name("publication.pl");
33 return $self;
37 sub define_object {
38 my $self=shift;
39 $self->set_dbh(CXGN::DB::Connection->new() );
40 my %args= $self->get_args();
41 foreach my $k (keys %args) {
42 $args{$k} = sanitize_string($args{$k});
44 my $pub_id= $args{pub_id};
46 unless (!$pub_id || $pub_id =~m /^\d+$/) { $self->get_page->message_page("No publication exists for identifier: $pub_id"); }
47 $self->set_object_id($pub_id);
48 $self->set_object(CXGN::Chado::Publication->new($self->get_dbh, $self->get_object_id));
50 $self->set_primary_key("pub_id");
51 $self->set_owners();
55 sub display_page {
56 my $self=shift;
57 my %args = $self->get_args();
58 my $publication=$self->get_object();
59 my $pub_id = $self->get_object_id();
60 my $pub_title = $publication->get_title();
61 my $page="/chado/publication.pl?pub_id=?";
62 my $action= $args{action} || "";
64 #import javascript libraries
65 $self->get_page()->jsan_use("jquery");
66 $self->get_page()->jsan_use("CXGN.Phenome.Locus");
67 $self->get_page()->jsan_use("CXGN.Phenome.Publication");
69 if (!$pub_title && $action ne 'new' && $action ne 'store') { $self->get_page->message_page("No publication exists for this identifier");}
72 $self->get_page->header("SGN publication $pub_title");
73 print page_title_html("Publication:\t$pub_title\n");
75 my $edit_links=$self->get_edit_links();
77 my $pub_html=$edit_links. "<br />". $self->get_form->as_table_string(). "<br />";
78 if ($action eq 'new') { print qq |<p><b><a href = "/chado/add_publication.pl?action=new&amp;type=$args{type}&amp;type_id=$args{type_id}&amp;reffering_page=$args{reffering_page}">Store PubMed publication.</a></b></p>|; }
79 print info_section_html(title => 'Publication details',
80 contents => $pub_html ,
82 if ($args{refering_page}) { print qq |<a href="$args{refering_page}">Go back to refering page.</a><br />|; }
84 my $dbxref_html=$self->get_dbxref_html($publication->get_dbxrefs());
86 print info_section_html(title => 'External resources',
87 contents => $dbxref_html ,
89 my $loci_link;
90 my @loci= $publication->get_loci();
91 foreach my $locus(@loci) {
92 my $locus_id = $locus->get_locus_id();
93 my $locus_symbol= $locus->get_locus_symbol();
94 my $cname=$locus->get_common_name();
95 $loci_link .= qq|<a href="/phenome/locus_display.pl?locus_id=$locus_id">$cname '$locus_symbol'</a><br />| if $locus->get_obsolete() eq 'f';
97 print info_section_html(title => 'Associated loci',
98 contents => $loci_link ,
100 my $user_type= $self->get_user()->get_user_type();
101 if ($args{get_ranked_loci} && $user_type eq 'curator' ) { $self->rank_loci_now(); }
103 my $ranked_loci= $self->get_ranked_loci();
104 print info_section_html(title => 'Matched loci',
105 contents => $ranked_loci ,
106 collapsible=>1,
107 collapsed =>1,
109 ########
110 if ($user_type eq 'curator') {
111 # the text-indexing function does not do the right thing now
112 # have to modify it so the search will be against just the current publication
113 print info_section_html(title => 'Curator Tools',
114 contents => $self->get_curator_tools(),
117 #######
118 my $page_comment_obj = CXGN::People::PageComment->new($self->get_dbh(), "pub", $pub_id, $self->get_page()->{request}->uri()."?".$self->get_page()->{request}->args());
119 print $page_comment_obj->get_html();
121 $self->get_page()->footer();
124 sub store {
126 my $self = shift;
128 my $publication = $self->get_object();
129 my $sp_person_id=$self->get_user()->get_sp_person_id();
131 my %args = $self->get_args();
133 my $action=$args{action};
134 my $refering_page=$args{refering_page};
135 my $type= $args{type}; #locus or allele or stock or...?
136 my $type_id = $args{type_id}; #the database id of the refering object (locus..)
137 my $script_name= $self->get_script_name();
138 my $db_name= "SGN_ref";
140 #db_name, accession, and uniquename will not be changed when updating..
141 if (!$publication->get_db_name()) { $publication->set_db_name($db_name); }
142 #########
143 $publication->set_cvterm_name('journal'); #this should be implemented in the form framework- maybe a drop down list with publication types from cvterm table??
144 ########
146 $self->SUPER::store(1);
148 my @dbxrefs=$publication->get_dbxrefs();
149 foreach my $dbxref(@dbxrefs) {
150 my ($locus, $allele);
151 if ($type eq 'locus') {
152 $locus= CXGN::Phenome::Locus->new($self->get_dbh(), $type_id);
153 $locus->add_locus_dbxref($dbxref, undef, $sp_person_id);
155 elsif ($type eq 'allele') {
156 $allele= CXGN::Phenome::Allele->new($self->get_dbh(), $type_id);
157 $allele->add_allele_dbxref($dbxref, undef, $sp_person_id);
160 my $pub_id= $publication->get_pub_id();
161 if ($type eq 'stock' ) {
162 my $pub = $publication->bcs_pub;
163 $pub->find_or_create_related('stock_pubs', {
164 stock_id => $type_id
167 if ($refering_page) {
168 $self->get_page()->client_redirect("/chado/add_publication.pl?type=$type&type_id=$type_id&refering_page=$refering_page&action=new");
169 }else {
170 $self->get_page()->client_redirect("/chado/publication.pl?pub_id=$pub_id");
174 sub generate_form {
175 my $self=shift;
176 $self->init_form();
177 my $publication=$self->get_object();
179 my %args=$self->get_args();
180 my $type = $args{type};
181 my $type_id = $args{type_id};
182 my $refering_page= $args{refering_page};
184 my $author_example = tooltipped_text('Authors', 'Author names should be entered in the order of last name, followed by "," then first name followed by ".". e.g Darwin, Charles. van Rijn, Henk. Giorgio,AB');
186 $self->get_form()->add_textarea(
187 display_name => "Title",
188 field_name => "title",
189 object => $publication,
190 getter => "get_title",
191 setter => "set_title",
192 validate=>'string',
193 columns => 80,
194 rows => 1,
197 $self->get_form()->add_field(
198 display_name => "Series name",
199 field_name => "series_name",
200 object => $publication,
201 getter => "get_series_name",
202 setter => "set_series_name",
203 validate=>'string',
205 $self->get_form()->add_field(
206 display_name => "Volume",
207 field_name => "volume",
208 object => $publication,
209 getter => "get_volume",
210 setter => "set_volume",
211 validate=>'integer',
213 $self->get_form()->add_field(
214 display_name => "Issue",
215 field_name => "issue",
216 object => $publication,
217 getter => "get_issue",
218 setter => "set_issue",
221 $self->get_form()->add_field (
222 display_name => "Year",
223 field_name => "pyear",
224 object => $publication,
225 getter => "get_pyear",
226 setter => "set_pyear",
227 validate => 'integer',
229 $self->get_form()->add_field (
230 display_name => "Pages",
231 field_name => "pages",
232 object => $publication,
233 getter => "get_pages",
234 setter => "set_pages",
235 validate => 'string',
237 $self->get_form()->add_textarea (
238 display_name=> $author_example,
239 field_name => "author",
240 object => $publication,
241 getter => "get_authors_as_string",
242 setter => "set_author_string",
243 columns => 80,
244 rows =>1,
246 $self->get_form()->add_textarea (
247 display_name=> "Abstract",
248 field_name => "abstract",
249 object => $publication,
250 getter => "get_abstract",
251 setter => "set_abstract",
252 columns => 80,
253 rows => =>12,
255 $self->get_form()->add_hidden (
256 field_name => "pub_id",
257 contents =>$args{pub_id},
258 object => $publication,
259 getter => "get_pub_id",
260 setter => "set_pub_id",
263 $self->get_form()->add_hidden (
264 field_name => "type",
265 contents => $type,
267 $self->get_form()->add_hidden (
268 field_name => "type_id",
269 contents =>$type_id,
271 $self->get_form()->add_hidden(
272 field_name=>"refering_page",
273 contents=>$refering_page,
275 $self->get_form()->add_hidden( field_name=>"action", contents=>"store" );
277 if ($self->get_action=~ /view|edit/) {
278 $self->get_form->from_database();
280 elsif ($self->get_action=~ /store/) {
281 $self->get_form->from_request($self->get_args());
282 $self->send_publication_email('store');
286 sub delete {
287 my $self = shift;
288 my %args = $self->get_args();
289 $self->check_modify_privileges();
290 my $publication=$self->get_object();
291 my $pub_title;
292 my $pub_id = $publication->get_pub_id();
293 if ($pub_id) {
294 $pub_title = $publication->get_title();
295 my $message = $publication->delete();
296 if (!$message) {
297 $self->send_publication_email('delete');
298 }else { $self->get_page()->message_page($message) ; }
300 $self->get_page()->message_page("Deleted publication $pub_id ($pub_title) from database");
304 #overriding to allow access only to curators
305 sub check_modify_privileges {
306 my $self = shift;
307 # implement quite strict access controls by default
309 my $person_id = $self->get_login()->verify_session();
310 my $user = CXGN::People::Person->new($self->get_dbh(), $person_id);
311 my $user_id = $user->get_sp_person_id();
312 if ($user->get_user_type() eq 'curator' || $user->get_user_type() eq 'submitter' || $user->get_user_type eq 'sequencer') {
313 return 0;
314 }else {
315 $self->get_page()->message_page("This page is only available for SGN curators!!");
319 sub get_dbxref_html {
320 my $self=shift;
321 my @dbxrefs=@_;
322 my $html;
323 foreach my $d(@dbxrefs) {
324 my $db=$d->get_db_name();
325 if ($db ne 'SGN_ref') {
326 my $url = $d->get_urlprefix() . $d->get_url() . $d->get_accession();
327 $html .= qq| <a href= "$url" >| . "$db:" . $d->get_accession() . "</a>";
330 return $html;
333 sub get_ranked_loci {
334 my $self=shift;
335 my $pub=$self->get_object();
336 my $loci_hash=$pub->get_ranked_loci();
337 my $pubs="";
338 my ($val_pubs, $rej_pubs, $pending_pubs, $a_pubs)= ("" x 4);
339 my $locus_link;
340 my @pub;
341 my $user_type= $self->get_user()->get_user_type();
343 foreach(sort { $loci_hash->{$b} <=> $loci_hash->{$a} } keys %$loci_hash ) {
344 my $locus=CXGN::Phenome::Locus->new($self->get_dbh, $_);
345 my $locus_symbol= $locus->get_locus_symbol();
346 my $locus_name= $locus->get_locus_name();
347 my $common_name= $locus->get_common_name();
348 my $pub_id = $pub->get_pub_id();
349 my $dbxref_id = $pub->get_dbxref_id_by_db('PMID');
351 my $locusRank = CXGN::Phenome::Locus::LocusRanking->new($self->get_dbh(), $_, $pub_id);
352 my $validated = $locusRank->get_validate() || "";
353 my $score = $locusRank->get_rank();
355 my $val_form= "<BR><BR>";
356 if ($user_type eq 'curator') {
357 $val_form= qq|
358 <div id='locusPubForm_$pub_id'>
359 <div id='pub_dbxref_id_$dbxref_id'>
360 <input type="hidden"
361 value=$dbxref_id
362 id="dbxref_id_$pub_id">
363 <select id="$dbxref_id" >
364 <option value="" selected></option>
365 <option value="no">no</option>
366 <option value="yes">yes</option>
367 <option value="maybe">maybe</option>
368 </select>
369 <input type="button"
370 id="associate_pub_button"
371 value="Validate match"
372 onclick="Locus.addLocusDbxref('$_', '$dbxref_id');this.disabled=false;">
373 </div>
374 </div>
375 <BR>
378 my $associated= $pub->is_associated_publication('locus', $_);
379 my $val_string;
380 if ($validated) { $val_string = "(validated: $validated)"; }
381 $locus_link .= qq| <a href="/phenome/locus_display.pl?locus_id=$_">$locus_symbol.</a> $common_name '$locus_name' <b> Match score = $score </b> $val_string | . $val_form;
384 return $locus_link;
388 sub send_publication_email {
389 my $self=shift;
390 my %args= $self->get_args();
391 my $refering_page=$args{refering_page};
392 my $type= $args{type}; #locus or allele or stock or...?
393 my $type_id = $args{type_id}; #the database id of the refering object (locus..)
394 my $accession= $args{accession};
396 my $action= $self->get_action();
397 my $username= $self->get_user()->get_first_name()." ".$self->get_user()->get_last_name();
398 my $sp_person_id=$self->get_user()->get_sp_person_id();
400 my $user_link = qq | /solpeople/personal-info.pl?sp_person_id=$sp_person_id|;
402 my $usermail=$self->get_user()->get_contact_email();
403 my $fdbk_body;
404 my $subject;
406 if ($action eq 'store') {
407 $subject="[New non PubMed publication associated with $type: $type_id]";
408 $fdbk_body="$username($user_link) has associated a non pubmed publication $accession with $type : $type_id";
410 elsif($action eq 'delete') {
411 $subject="[A publication-locus association removed from $type : $type_id]";
412 $fdbk_body="$username ($user_link) has removed a publication from $type : $type_id ";
414 CXGN::Contact::send_email($subject,$fdbk_body, 'sgn-db-curation@sgn.cornell.edu');
418 sub get_edit_links {
419 my $self =shift;
420 my $form_name = shift;
421 return $self->get_new_link_html($form_name)." ".
422 $self->get_edit_link_html($form_name) ." ". $self->get_delete_link_html($form_name);
426 sub get_edit_link_html {
427 my $self = shift;
428 my $form_name = shift;
429 my $edit_link = "";
430 my $script_name = $self->get_script_name();
431 my $primary_key = $self->get_primary_key();
432 my $object_id = $self->get_object_id();
434 my $user_id= $self->get_user()->get_sp_person_id();
435 if (($self->get_user()->get_user_type() eq "curator") || ($self->get_user()->get_user_type() eq "submitter") || ( $self->get_user()->get_user_type() eq "sequencer") ) {
436 $edit_link = qq { <a href="$script_name?action=edit&amp;form=$form_name&amp;$primary_key=$object_id">[Edit]</a> };
438 }else {
439 $edit_link = qq { <span class="ghosted">[Edit]</span> };
441 if ($self->get_action() eq "edit") {
442 $edit_link = qq { <a href="$script_name?action=view&amp;form=$form_name&amp;$primary_key=$object_id">[Cancel Edit]</a> };
444 if ($self->get_action() eq "new") {
445 $edit_link = qq { <span class="ghosted">[Edit]</span> };
447 return $edit_link;
450 sub get_delete_link_html {
451 my $self = shift;
452 my $form_name = shift;
453 my $delete_link = "";
454 my $script_name = $self->get_script_name();
455 my $primary_key = $self->get_primary_key();
456 my $object_id = $self->get_object_id();
457 my $user_id= $self->get_user()->get_sp_person_id();
458 if (($self->get_user()->get_user_type() eq "curator") || ($self->get_user()->get_user_type() eq "submitter") || ( $self->get_user()->get_user_type() eq "sequencer") ) {
459 $delete_link = qq { <a href="$script_name?action=confirm_delete&amp;form=$form_name&amp;$primary_key=$object_id">[Delete]</a> };
460 }else {
461 $delete_link = qq { <span class="ghosted">[Delete]</span> };
463 if ($self->get_action() eq "edit") {
464 $delete_link = qq { <span class="ghosted">[Delete]</span> };
466 if ($self->get_action() eq "new") {
467 $delete_link = qq { <span class="ghosted">[Delete]</span> };
469 return $delete_link;
474 sub get_curator_tools {
475 my $self=shift;
476 my $pub_id= $self->get_object_id();
479 #add AJAX form for validating publication status. Current status is selected by default
480 my $stat=$self->get_object()->get_status();
481 my @stat_options= ("curated","pending", "irrelevant", "no gene");
482 my $stat_options= qq|<option value=""></option>|;
483 foreach my $s(@stat_options) {
484 my $selected = qq|selected="selected"| if $s eq $stat || undef;
485 $stat_options .= qq|<option value="$s" $selected >$s</option>|
487 my $stats= qq|<select id="pub_stat" onchange="Publication.updatePubCuratorStat(this.value, $pub_id)">
488 $stat_options
489 </select>
491 #add AJAX form for assigning curator. Assigned curator is selected by default
492 my $assigned_to_id= $self->get_object()->get_curator_id();
493 my @curators= CXGN::People::Person::get_curators($self->get_dbh());
494 my %names = map {$_ => CXGN::People::Person->new($self->get_dbh(), $_)->get_first_name() } @curators;
495 my $curator_options=qq|<option value=""></option>|;
496 for my $curator_id (keys %names) {
497 my $curator= $names{$curator_id};
498 my $selected = qq|selected="selected"| if $curator_id==$assigned_to_id || undef;
499 $curator_options .=qq|<option value="$curator_id" $selected>$curator</option>|;
501 my $curators=qq|<select id="pub_curator_select" onchange="Publication.updatePubCuratorAssigned(this.value, $pub_id)">
502 $curator_options
503 </select>
504 | ;
507 my $form = qq |
508 <form action="" method="get">
509 <input id="" type="hidden" value="1" name="get_ranked_loci"/>
510 <input id="" type="hidden" value="$pub_id" name="pub_id"/>
512 <input type="submit" value="Get ranked loci"/>
513 </form>
516 my $html = <<EOHTML;
517 Publications are automatically indexed when inserted into the database. A nightly cron job connects publications with loci based on text matching. You may run the matching algorithm manually to see now the possible matching loci. This might take a few minutes to load. After clicking this link the page will automatically reload. If matching loci are found these will be printed in the 'Matched loci' section above.
518 $form
520 EOHTML
522 my $search = qq|Go back to <a href="/search/pub_search.pl">literature search page</a>.|;
523 return info_table_html('Publication status'=>$stats,
524 'Assigned to curator'=> $curators ,
525 'Text index'=> $html)
526 . $search;
530 sub rank_loci_now {
531 my $self=shift;
532 my $pub=$self->get_object();
533 my $pub_id= $self->get_object_id();
534 my $title_string= $pub->title_tsvector_string();
535 $title_string =~ s/\'//g;
537 print STDERR "title_string = $title_string ! \n";
538 my @match_words = split (/\s/, $title_string);
539 #print STDERR "match_words= @match_words\n";
540 my $abstract_string= $pub->abstract_tsvector_string();
541 $abstract_string =~ s/\'//g;
542 push (@match_words, (split /\s/, $abstract_string) );
544 #hash for storing unique loci.
545 my %loci_subset=();
546 MATCH: foreach (@match_words) {
547 $_= "%$_%";
548 print STDERR "...matching $_ ...\n";
549 my $get_loci_q= ("SELECT distinct locus_id FROM phenome.locus WHERE locus_name SIMILAR TO ?
550 OR locus_symbol SIMILAR TO ? OR gene_activity SIMILAR TO ? OR description SIMILAR TO ?
551 ORDER BY locus_id");
552 my $l_sth=$self->get_dbh()->prepare($get_loci_q);
553 $l_sth->execute($_, $_, $_, $_);
554 my (@loci)=$l_sth->fetchrow_array();
556 ##limiting the number of hits to 20 to keep this function from extremely slowing down the page.
557 if (scalar(@loci) > 20 ) { print STDERR " Found ". scalar(@loci) . "loci! skipping...\n"; next MATCH; }
558 else { foreach(@loci) { $loci_subset{$_}++; } }
560 foreach my $locus_id (sort {$loci_subset{$a} <=> $loci_subset{$b} } keys %loci_subset) {
561 my $locus = CXGN::Phenome::Locus->new($self->get_dbh(), $locus_id);
562 eval {
563 my %pub= $locus->add_locus_pub_rank($pub_id);
564 #while ( my ($match_type, $value) = each(%pub) ) {
565 # print STDERR ("$match_type=> $value\n");