add extra newline after headings in error emails, easier to read.
[sgn.git] / cgi-bin / solpeople / personal-info.pl
blob663b5112d73de7efcbe54635729220634b5dcced
1 # Evan, 1 / 15 / 07: combines contact-info.pl and research-info.pl
3 #if a user is logged in, look at the logged-in user's info;
4 #else, can call with an id parameter to get a read-only view of any user's info
5 #(if no login and no 'id=', no good)
7 #(we don't want to encourage users to go directly to this page to edit their own info;
8 # they should go through top-level.pl, so they'll already be logged in when they get here)
10 use strict;
11 use CXGN::Login;
13 my $contact_info_page = new SolPeoplePersonalInfoPage();
15 package SolPeoplePersonalInfoPage;
17 use CXGN::Page;
18 use CXGN::Page::FormattingHelpers qw/page_title_html/;
19 use CXGN::People;
20 use File::Spec;
21 use HTML::Entities;
22 use CXGN::Phenome::Locus;
24 use base qw | CXGN::Page::Form::SimpleFormPage |;
26 sub new {
27 my $class = shift;
28 my $person_id = shift;
29 my $self = $class->SUPER::new(); #arguments are parsed from the Page here
30 my (undef, undef, $this_script_filename) = File::Spec->splitpath($0); #$0 contains current filename with some path or other prepended
31 $self->set_script_name($this_script_filename);
32 return $self;
35 sub define_object {
36 my $self = shift;
37 # call set_object_id, set_object and set_primary_key here
38 my %args = $self->get_args();
39 $self->set_object_id($args{sp_person_id});
40 $self->set_object(new CXGN::People::Person($self->get_dbh(), $args{sp_person_id}));
41 $self->set_primary_key("sp_person_id");
42 $self->set_owners($self->get_object()->get_sp_person_id());
45 #specified in SimpleFormPage
46 sub check_modify_privileges {
47 my $self = shift;
49 # implement quite strict access controls by default
51 my $person_id = $self->get_login()->has_session();
52 my $user = CXGN::People::Person->new($self->get_dbh(), $person_id);
53 my $user_id = $user->get_sp_person_id();
54 if ($user->get_user_type() eq 'curator') {
55 return 0;
58 # check the owner only if the action is not new
60 my @owners= $self->get_owners();
63 if (!(grep {/^$user_id$/} @owners ))
65 $self->get_page()->message_page("You do not have rights to modify this database entry because you do not own it. [$user_id, @owners]");
67 else {
68 return 0;
72 # override to check privileges for edit, store, delete.
73 # return 0 for allow, 1 for not allow.
74 return 0;
78 #specified in SimpleFormPage
79 sub validate_parameters_before_store
81 my $self = shift;
82 my %args = $self->get_args();
84 my ($keywords, $format, $research_interests, $organism_codes_str, $unlisted_organisms_str) =
85 @args{qw(keywords format research_interests organisms unlisted_organisms)};
86 $format = "auto" unless $format eq "html";
88 #after checking for legal values, do the actual storing of existing organism IDs here
89 my @organism_codes = map {$_ =~ tr/[0-9]//cds; $_} split(/\0/, $organism_codes_str);
90 foreach my $c ( @organism_codes )
92 my $test = new CXGN::People::Organism($self->get_dbh(), $c);
93 if(!defined($test))
95 $self->get_page()->message_page("Organism code \"$_\" is not defined in the database.\n");
98 my %selected_organisms = map {$_ => 1} @organism_codes; #ids of only those organisms selected with the multiselect appear in the hash
99 my $person = $self->get_object();
100 foreach my $o ($person->get_organisms())
102 $o->set_selected(0);
103 if($selected_organisms{$o->get_sp_organism_id()})
105 $o->set_selected(1);
109 #deal with unlisted organisms
110 my @unlisted_organisms;
111 foreach (split(/;/, $unlisted_organisms_str))
113 if(m/^\s*((\S.*\S)|\S)\s*$/) #remove leading and trailing spaces
115 push @unlisted_organisms, $1;
118 my $unlisted_organisms = join("\0", @unlisted_organisms);
120 # Quick, dirty hack method of defenestrating disallowed HTML tags in
121 # research interest statements: replace allowed tags with a marker string,
122 # then delete all tokens of the form "<[^>]+>", then replace the marker string
123 # with the original tag.
125 # Using the marker string allows us to "save" the information about the tag
126 # that is allowed -- changing it to a token that won't match the "<[^>]+>"
127 # pattern. Using a random marker string prevents a user learning (through a
128 # website error or something that reveals a static marker string) the marker
129 # and trying to insert illegal tags by typing the marker into their statement
130 # manually. This is further guarded against by only recognizing marker strings
131 # with allowed tags encoded. (Yes, I am paranoid)
133 # The main problem with this is if someone happens to use both a literal "<"
134 # and a matching ">" in their research statement -- if that happens, the
135 # text flanked by these will be deleted -- they'll need to use &lt; and &gt;
136 # instead.
137 my $tag_marker = $self->get_page()->tempname();
138 my $formatted_interests = $research_interests;
139 $formatted_interests =~ s/<(\/{0,1})([pbi]|br)>/$1-$2-$tag_marker/g;
140 $formatted_interests =~ s/\<[^>]+\>//g;
141 $formatted_interests =~ s/(\/{0,1})-([pbi]|br)-$tag_marker/<$1$2>/g;
143 # This is stored separately in the database, without tags, for full-text searching.
144 $research_interests =~ s/\<[^>]+\>//g;
146 if ($format eq "auto")
148 $formatted_interests =~ s/\r{0,1}\n\r{0,1}\n/<\/p><p>/g;
149 $formatted_interests = "<p>$formatted_interests</p>";
152 $args{keywords} = HTML::Entities::encode_entities($keywords);
153 $args{format} = $format;
154 $args{research_interests} = ($format eq "auto") ? HTML::Entities::encode_entities($research_interests) : $formatted_interests;
155 $args{unlisted_organisms} = $unlisted_organisms;
156 $self->set_args(%args); #necessary if you want to affect the arguments that will be stored
159 sub generate_form
161 my $self = shift;
163 my %args = $self->get_args();
164 my $person = $self->get_object();
166 $self->init_form();
167 my $form = $self->get_form();
168 if ($form->is_editable()) {
169 $form->set_submit_method('post'); #avoid long URLs caused by research interests
172 my ($displayed_first_name, $displayed_last_name) = ($person->get_first_name(), $person->get_last_name());
173 #i added some html into empty name fields asking people to update their blank entries in the database.
174 #if we encounter one of these, do not fill in the field for their name with the html! --john
175 if($displayed_first_name=~/^</){$displayed_first_name='';}
176 if($displayed_last_name=~/^</){$displayed_last_name='';}
178 my @organisms = $person->get_organisms();
179 my @organism_ids = map {$_->get_organism_id()} @organisms;
180 my @organism_names = map {$_->get_organism_name()} @organisms;
181 my @organism_selections = map {$_->is_selected() ? '1' : '0'} @organisms;
183 #element IDs for the source elements for the preview panes
184 $self->{html_source_element_id} = 'html_source';
185 $self->{organisms_source_element_id} = 'organisms_source';
187 my $default_field_length = 22;
188 if($form->is_editable()) {
189 $form->add_label(display_name => "",
190 field_name => "lbl1",
191 contents => "If you would like to enter or update information at this time but do not want it displayed publically, check this option.");
193 $form->add_checkbox(display_name => "Censor contact information from directory search and public display",
194 field_name => "censor",
195 contents => "1",
196 selected => $person->get_censored(),
197 object => $person,
198 getter => "get_censored",
199 setter => "set_censor");
201 $form->add_label(display_name => "",
202 field_name => "lbl2",
203 contents => "The first and last names you enter here will be displayed in the SGN directory (if not censored using the box above)."
204 . " Your username for this interface is not displayed.");
206 $form->add_select(display_name => "Salutation",
207 field_name => "salutation",
208 contents => $person->get_salutation(),
209 object => $person,
210 getter => "get_salutation",
211 setter => "set_salutation",
212 select_list_ref => [qw(None Prof. Dr. Mr. Ms. Mrs.)],
213 select_id_list_ref => ['', qw(Prof. Dr. Mr. Ms. Mrs.)]);
214 $form->add_field(display_name => "First name",
215 field_name => "first_name",
216 contents => $displayed_first_name,
217 length => $default_field_length,
218 object => $person,
219 getter => "get_first_name",
220 setter => "set_first_name",
221 validate => "string");
222 $form->add_field(display_name => "Last name",
223 field_name => "last_name",
224 contents => $displayed_last_name,
225 length => $default_field_length,
226 object => $person,
227 getter => "get_last_name",
228 setter => "set_last_name",
229 validate => "string");
230 $form->add_field(display_name => "Organization",
231 field_name => "organization",
232 contents => $person->get_organization(),
233 length => 50,
234 object => $person,
235 getter => "get_organization",
236 setter => "set_organization");
237 $form->add_textarea(display_name => "Address",
238 field_name => "address",
239 contents => $person->get_address(),
240 rows => 4,
241 columns => 50,
242 object => $person,
243 getter => "get_address",
244 setter => "set_address");
245 $form->add_field(display_name => "Country",
246 field_name => "country",
247 contents => $person->get_country(),
248 length => $default_field_length,
249 object => $person,
250 getter => "get_country",
251 setter => "set_country");
252 if($form->is_editable()) {
253 $form->add_label(display_name => "",
254 field_name => "lbl3",
255 contents => "If you wish to be contacted via telephone or fax by interested parties who found your directory entry on SGN, please enter"
256 . " telephone numbers here. These numbers will be displayed publically to all SGN users who view your entry.");
258 $form->add_field(display_name => "Phone",
259 field_name => "phone",
260 contents => $person->get_phone_number(),
261 length => $default_field_length,
262 object => $person,
263 getter => "get_phone_number",
264 setter => "set_phone_number");
265 $form->add_field(display_name => "Fax",
266 field_name => "fax",
267 contents => $person->get_fax(),
268 length => $default_field_length,
269 object => $person,
270 getter => "get_fax",
271 setter => "set_fax");
272 if($form->is_editable())
274 $form->add_label(display_name => "",
275 field_name => "lbl4",
276 contents => "If you would like to be contacted via email by interested parties who found your directory entry on SGN, please enter"
277 . " an address here. This address will be displayed publically to all SGN users who view your entry.");
279 $form->add_field(display_name => "Contact E-mail",
280 field_name => "contact_email",
281 contents => $person->get_contact_email(),
282 length => $default_field_length,
283 object => $person,
284 getter => "get_contact_email",
285 setter => "set_contact_email");
286 $form->add_field(display_name => "Website",
287 field_name => "webpage",
288 contents => $person->get_webpage(),
289 length => $default_field_length,
290 object => $person,
291 getter => "get_webpage",
292 setter => "set_webpage");
293 if($form->is_editable())
295 $form->add_label(display_name => "",
296 field_name => "lbl5",
297 contents => "Enter research keywords (for searching) and a fuller explanation of your research interests (for searching and displaying) below.");
299 $form->add_field(display_name => "Keywords",
300 field_name => "keywords",
301 contents => $person->get_research_keywords(),
302 length => 60,
303 object => $person,
304 getter => 'get_research_keywords',
305 setter => 'set_research_keywords');
306 if($form->is_editable())
308 $form->add_label(display_name => "",
309 field_name => "lbl6",
310 contents => "Please separate terms with a <b>semicolon</b>. Do not use quotation marks or other punctuation. (Example: fruit development;"
311 . " host pathogen interaction; drought tolerance)");
313 $form->add_multiselect(display_name => "Research Organisms",
314 field_name => "organisms",
315 choices => \@organism_ids,
316 labels => \@organism_names,
317 contents => \@organism_selections,
318 object => $person,
319 getter => 'get_organism_id_string');
320 if($form->is_editable())
322 $form->add_label(display_name => "",
323 field_name => "lbl7",
324 contents => "You may select multiple organisms by clicking on more than one while holding the control key down. If an organism is not listed"
325 . " in the selection box, please specify your additional organisms by their official Latin names in the text entry box below, in"
326 . " a <b>semicolon</b>-separated list.");
327 #the contents will be edited by the pre-storing validation function; notice this field never needs to be gotten, only sotten
328 $form->add_field(display_name => "Unlisted Organisms",
329 field_name => "unlisted_organisms",
330 id => $self->{organisms_source_element_id},
331 contents => "",
332 length => 60,
333 object => $person,
334 setter => 'add_organisms');
337 if($form->is_editable()) {
338 $form->add_radio_list(display_name => "",
339 field_name => "format",
340 id_prefix => "format_",
341 choices => ["auto", "html"],
342 labels => ["Auto-format (entry is plain text)", "Use HTML markup tags"],
343 contents => $person->get_user_format(),
344 object => $person,
345 getter => 'get_user_format',
346 setter => 'set_user_format');
348 $form->add_label(display_name => "",
349 field_name => "lbl8",
350 contents => "For auto-formatted plain text entries, separate paragraphs by a single empty line. For HTML entries, please use only paragraph"
351 . " tags (&lt;p&gt;), bold and italic tags (&lt;b&gt;, &lt;i&gt;), and break tags (&lt;br&gt;). All other tags will be removed.");
353 $form->add_textarea(display_name => "Interests",
354 field_name => "research_interests",
355 id => $self->{html_source_element_id},
356 contents => $person->get_research_interests(),
357 rows => 20,
358 columns => 80,
359 object => $person,
360 getter => 'get_research_interests',
361 setter => 'set_research_interests');
362 if($form->get_action() =~ /edit|store/)
364 $form->add_label(display_name => "",
365 field_name => "lbl9",
366 contents => "The preview feature below (requires JavaScript) shows your current interests statement if you've selected HTML format. Also,"
367 . " if you need to define new organisms, the preview will show how the website is parsing the text you've entered above.");
370 #for allowing the form to make changes
371 if($form->is_editable())
373 $form->add_hidden(display_name => "ID", field_name => "sp_person_id", contents => $person->get_sp_person_id());
374 $form->add_hidden(display_name => "Action", field_name => "action", contents => "store");
377 if($form->is_editable())
379 $form->set_reset_button_text("Clear");
380 $form->set_submit_button_text("Submit Changes");
382 if($self->get_action() =~ /view|edit/)
384 $form->from_database();
387 elsif($self->get_action() =~ /store/)
389 $form->from_request($self->get_args());
393 #no arguments
394 #return an HTML string for a toolbar with other possible actions for the form
395 sub get_actions_toolbar
397 my $self = shift;
398 my $user_id= $self->get_user()->get_sp_person_id();
399 my @owners= $self->get_owners();
401 my $script_name = $self->get_script_name();
402 my $user_is_owner = (grep {/^$user_id$/} @owners);
403 my %args = $self->get_args();
404 my $sp_person_id = $args{sp_person_id};
406 my ($login_user_id, $user_type) = CXGN::Login->new($self->get_dbh())->has_session();
407 my $home;
408 if($user_is_owner || $user_type eq 'curator')
410 $home = qq(<a href="top-level.pl?sp_person_id=$sp_person_id">[Directory Update Home]</a>&nbsp;&nbsp;);
412 else
414 $home = qq(<span class="ghosted">[Directory Update Home]</span>);
417 if($self->get_action() eq "edit")
419 if($user_is_owner || $user_type eq 'curator')
421 return $home . qq(<a href="$script_name?action=view&sp_person_id=$sp_person_id">[Cancel Edit]</a>);
423 else
425 return $home . qq(<span class="ghosted">[Cancel Edit]</span>);
428 elsif($self->get_action() eq "view")
430 if($user_is_owner || $user_type eq 'curator')
432 return $home . qq(<a href="$script_name?action=edit&sp_person_id=$sp_person_id">[Edit]</a>);
434 else
436 return $home . qq(<span class="ghosted">[Edit]</span>);
439 elsif($self->get_action() eq "store") {}
442 sub display_page
444 my $self = shift;
445 my $person = $self->get_object();
446 my $page = $self->get_page();
447 #SimpleFormPage takes care of some unknown action strings, but we don't handle the full set of actions it supports
448 if($self->get_action() !~ /^view|edit|store$/)
450 $page->message_page("Illegal parameter: action '" . $self->get_action() . "' is not supported by " . $self->get_script_name());
451 exit();
454 $page->add_style(text => ".subtitle {font-weight: bold; text-align: center}");
455 $page->add_style(text => ".invisible {display: none}");
456 $page->jsan_use("CXGN.DynamicPreviewPane", "MochiKit.DOM", "MochiKit.Signal"); #include javascript modules
457 $page->header("Sol People: contact and research info");
458 print page_title_html("Personal info for " . $person->get_first_name() . " " . $person->get_last_name());
459 print $self->get_actions_toolbar() . "<hr />\n";
460 print $self->get_form()->as_table_string();
463 if($self->get_action() =~ /^edit$/) {
464 #show the preview panes
465 my $html_preview_pane_parent_id = 'html_preview_pane_parent';
466 my $organisms_preview_pane_parent_id = 'organisms_preview_pane_parent';
467 print <<EOHTML;
468 <hr width="90%" />
469 <div class="subtitle">Interests and Organisms Preview</div>
470 <div id="all_html_preview_stuff">
471 Your research interests will be displayed on SGN\'s website as shown in the box below (the box will not be displayed).
472 <div id="$html_preview_pane_parent_id"></div>
473 </div>
474 <br />You have entered the following currently unlisted organisms. Please check that these names are spelled correctly.
475 Once committed, these names cannot be changed by this interface.
476 If you accidentally save an incorrect organism name, please contact us via <a href="mailto:sgn-feedback\@sgn.cornell.edu">sgn-feedback\@sgn.cornell.edu</a>.
477 <div id="$organisms_preview_pane_parent_id"></div>
478 <script type="text/javascript">
479 DynamicPreviewPane.createPreviewPane('html', '$self->{html_source_element_id}', '$html_preview_pane_parent_id');
480 DynamicPreviewPane.createPreviewPane('list(;)', '$self->{organisms_source_element_id}', '$organisms_preview_pane_parent_id');
482 //show or hide the interests preview pane based on user format selection
483 function makeVisible() {MochiKit.DOM.removeElementClass(this, "invisible");}
484 function makeInvisible() {MochiKit.DOM.addElementClass(this, "invisible");}
485 var previewPaneSection = MochiKit.DOM.getElement("all_html_preview_stuff");
486 MochiKit.Signal.connect("format_auto", "onclick", previewPaneSection, makeInvisible);
487 MochiKit.Signal.connect("format_html", "onclick", previewPaneSection, makeVisible);
488 //initialize
489 if(MochiKit.DOM.getElement("format_auto").checked) MochiKit.DOM.addElementClass("all_html_preview_stuff", "invisible");
490 </script>
491 EOHTML
494 my ($locus_annotations, $more_annotations) ;
495 my @annotated_loci = ();
497 if ($self->get_action() !~ /^edit$/) {
498 my $person_id= $person->get_sp_person_id();
499 @annotated_loci = CXGN::Phenome::Locus::get_locus_ids_by_editor($self->get_dbh(), $person_id);
500 my $top= 50;
501 my $more = 0;
502 my $max = @annotated_loci;
503 if (@annotated_loci>24) {
504 $more = @annotated_loci-24;
505 $max = 24;
509 for (my $i=0; $i<$top; $i++) {
510 my $locus = CXGN::Phenome::Locus->new($self->get_dbh(), $annotated_loci[$i]);
511 my $symbol = $locus->get_locus_symbol();
512 my $locus_id = $locus->get_locus_id();
515 if ($locus_id && $symbol) { #deanx jan23 2008
516 if ($i<$max) {
517 $locus_annotations .= qq | <a href="/phenome/locus_display.pl?locus_id=$locus_id&amp;action=view">$symbol</a> | }
518 else {
519 $more_annotations .= qq { <a href="/phenome/locus_display.pl?locus_id=$locus_id&amp;action=view">$symbol</a> };
524 if ($more) {
525 $locus_annotations .= qq|<br><b>and <a href="/search/locus_search.pl?w8e4_editor=$person_id">$more more</a></b><br />|;
531 if (@annotated_loci) {
532 print "<hr />\n";
533 print "Locus editor assignments:&nbsp;\n";
534 print $locus_annotations;
537 my $pop_list = $self->owner_populations();
538 if ($pop_list) {
539 print "<hr />\n $pop_list";
542 $page->footer();
545 sub owner_populations {
546 my $self = shift;
547 my $sp_person_id = $self->get_object_id();
548 my @pops = CXGN::Phenome::Population->my_populations($sp_person_id);
549 my $pop_list = 'Populations:<br/>';
551 if (@pops) {
552 foreach my $pops (@pops) {
553 my $pop_name = $pops->get_name();
554 my $pop_id = $pops->get_population_id();
555 #my $is_public = $pops->get_privacy_status();
556 #if ($is_public) {$is_public = 'is publicly available';}
557 #if (!$is_public) {$is_public = 'is not publicly available yet';}
558 $pop_list .= qq |<a href="/phenome/population.pl?population_id=$pop_id">$pop_name</a><br/>|;
560 return $pop_list;
562 } else {
563 return;