5 SimpleFormPage.pm -- an abstract class that implements a simple webpage that can be use to add, view and modify information in the database.
9 SimpleFormPage.pm works with the CXGN::Page::Form classes and user-defined database classes that have to follow certain guidelines for this class to work properly (in essence, the database classes need to follow the rules for the CXGN::Page::Form framework. For more information, see the documentation of the L<CXGN::Page::Form> classes).
11 SimpleFormPage.pm implements a simple authentication for the calls that modify database content. The function check_modify_privileges should return 1 if the current user had edit/delete privileges, otherwise it should return 0. The default implementation returns 1 for the owner and any logged in curators, 0 for all others.
13 SimpleFormPage contains a number of pre-populated accessors for often used CXGN features, such as CXGN::Page (get_page()), CXGN::DB::Connection (get_dbh()), and CXGN::Login (get_user()).
15 When creating a derived class, you need to override the following functions:
17 C<sub define_object()>
19 C<sub generate_form()>
21 If you want to create a page layout different from the default layout, you can also override:
27 Lukas Mueller (lam87@cornell.edu)
30 =head1 FUNCTIONS AND INTERFACES
32 The following is a list of object functions. Some of these functions are used internally, and some need to be overridden in child classes. See the description above and the individual functions for more details.
39 use CXGN
::Tools
::Text qw
| sanitize_string
|;
41 use CXGN
::Page
::Form
::Static
;
42 use CXGN
::Page
::Form
::Editable
;
43 use CXGN
::Page
::Form
::ConfirmStore
;
46 use CXGN
::DB
::Connection
;
49 use CXGN
::People
::Person
;
51 package CXGN
::Page
::Form
::SimpleFormPage
;
53 use CXGN
::Page
::FormattingHelpers qw
/ page_title_html blue_section_html /;
57 Usage: my $s = CXGN::Page::SimpleFormPageSubClass->new();
58 Desc: This class needs to be subclassed and certain functions
59 overridden (the functions are listed above). The constructor
60 should not be overridden or called from the overridden
72 my $self = bless {}, $class;
74 my $primary_key = shift;
76 $self->set_page(CXGN
::Page
->new("SGN", "Lukas"));
77 my $dbh=$self->get_page()->get_dbh(); # reuse the dbh from the Page object
79 $dbh->add_search_path("$schema");
81 $self->set_login(CXGN
::Login
->new($self->get_dbh()));
82 $self->get_page->{request
}->no_cache(1);
83 my %args = $self->get_page()->cgi_params(); #multi-valued parameters have values in a string, delimited by \0
85 # sanitize the inputs, we don't want to end up like bobby tables school.
87 foreach my $k (keys (%args)) {
88 $args{$k} = CXGN
::Tools
::Text
::sanitize_string
($args{$k});
91 $self->set_args(%args);
93 $self->define_object();
95 $self->set_action($args{action
});
97 if (!$self->get_action()) {
98 $self->set_action("view");
101 if (!$self->get_object_id() && $self->get_action()!~/new|store|confirm_store/) {
102 $self->get_page()->message_page("No identifier provided to display data of this page for action view.");
105 if ($self->get_action()!~/new|view|edit|store|delete|confirm_delete|confirm_store/) {
106 $self->get_page()->error_page("Undefined input. Cannot proceed. Sorry.\n");
110 if ($self->get_action() eq "view") {
113 elsif ($self->get_action() eq "edit") {
116 elsif ($self->get_action() eq "new") {
117 $self->set_object_id(0);
118 my %args = $self->get_args();
119 $args{$self->get_primary_key()}=0;
120 $self->set_args(%args);
123 elsif ($self->get_action() eq "store") {
127 elsif ($self->get_action() eq "confirm_delete") {
128 my $id = $args{$self->get_primary_key()};
130 $self->get_page()->error_page("need an id for deleting");
132 $self->delete_dialog("Delete", "Object",
133 $self->get_primary_key(),
135 "<a href=\"".$self->get_script_name()."?".$self->get_primary_key()."=".$id."&action=view\">Go back to detail page without deleting</a>");
138 elsif ($self->get_action() eq "delete") {
141 elsif ($self->get_action() eq "confirm_store") {
143 $self->confirm_store();
148 =head2 check_modify_privileges
150 Usage: if ($s->check_modifiy_privileges) { ...}
151 Desc: checks if the currently logged in user has sufficient
152 privileges for the current action. This can be overridden
153 if the default privileges are undesirable. The default is:
154 logged-in curators can do everything
155 logged-in owners can do everything
156 logged-in users can only view
157 non-logged-in users can only view
159 Ret: true if the user has sufficient privileges for the action.
166 sub check_modify_privileges
{
169 # implement quite strict access controls by default
171 my $person_id = $self->get_login()->verify_session();
172 my $user = CXGN
::People
::Person
->new($self->get_dbh(), $person_id);
173 my $user_id = $user->get_sp_person_id();
174 if ($user->get_user_type() eq 'curator') {
177 if ($user->get_user_type() !~ /submitter|sequencer|curator/) {
178 $self->get_page()->message_page("You must have an account of type submitter to be able to submit data. Please contact SGN to change your account type.");
181 my @owners = $self->get_owners();
182 if ((@owners) && (!(grep { $_ =~ /^$user_id$/ } @owners) )) {
183 # check the owner only if the action is not new
185 #my $owner_id = $self->get_object_owner();
186 # if (($owner_id) && ($owner_id != $user_id))
190 $self->get_page()->message_page("You do not have rights to modify this database entry because you do not own it. [$user_id, @owners]");
197 # override to check privileges for edit, store, delete.
198 # return 0 for allow, 1 for not allow.
206 Desc: define the object that this simple form page operates on.
207 The object needs to implement L<CXGN::DB::ModifiableI>
218 # in the subclass, instantiate your object here and call
220 $self->set_object_id();
221 $self->set_primary_key();
225 # edit is an internally used function to show the editable form.
230 $self->check_modify_privileges();
231 $self->generate_form();
232 $self->display_page();
235 # add is an internally used function to generate an 'emtpy' form.
240 $self->check_modify_privileges();
241 $self->generate_form();
242 $self->display_page();
249 Desc: stores the form into the database. In this default implementation,
250 which can but should not overridden, first, the privileges are checked
251 and all the form fields are validated for correctness. If this fails,
252 the input form is shown again with an appropriate error message.
253 Else, the store is issued for the object (set using set_object).
254 If this succeeds, the page is re-directed to the same page, but with
255 the 'view' action parameter.
266 my $dont_show_form = shift;
268 $self->check_modify_privileges();
270 # for the store we need a properly formatted form so that we can
271 # use its validate and store functions.
273 $self->generate_form();
276 my %errors = $self->get_form()->validate($self->get_args());
280 #give the user the opportunity to modify, add or remove form parameters before committing them
281 #(this needs to be done after validate() because that assumes it'll have the parameters given on the form as displayed)
282 $self->validate_parameters_before_store();
284 # the form validated. Now let's check if it passes the uniqueness
286 # Assume that the database accessor class inherits from
287 # CXGN::DB::Modifiable and thus has a exists_in_database
288 # function - but don't make it a requirement. If it doesn't -- never mind.
290 # if ($self->get_object()->can("exists_in_database")) {
291 # if ($self->get_object()->exists_in_database()) {
292 # $self->get_page()->message_page("Some fields in this object must be unique in the database.
293 # Please modify your input and try to submit again.");
299 #print STDERR "**** about to call the get_form->store() ****";
300 $self->get_form()->store($self->get_args());
302 #give the user the opportunity to do anything related to the form after storing
303 $self->process_parameters_after_store();
306 # $self->get_page()->message_page("An error occurred while attempting to store data. Please verify your input and try again. $@");
309 # was it an insert? get the insert id
311 if (!$self->get_object_id()) {
312 my $id = $self->get_form()->get_insert_id();
313 $self->set_object_id($id);
315 my %args = $self->get_args();
317 # if we are supposed to show the form, redirect to the
318 # script this script with the appropriate parameters,
319 # the object_id, and the action = view.
321 if (!$dont_show_form) {
323 $self->get_script_name()."?".
324 $self->get_primary_key()."=".
325 $self->get_object_id()."&action=view";
327 $self->get_page()->client_redirect($url);
332 # if there was an error, re-display the same page.
333 # the errors will be displayed on the page for each
334 # field that did not validate.
336 $self->display_page();
345 Description: Displays the page in the 'view' mode.
352 # make sure we get a static form.
354 $self->set_action("view");
356 $self->generate_form();
357 $self->display_page();
363 Desc: returns a CXGN::People::Person object
364 that represents the currently logged in user
365 or an empty user if the page is being viewed
376 my $person_id = $self->get_login()->has_session();
377 return CXGN
::People
::Person
->new($self->get_dbh(), $person_id);
382 Usage: $s->generate_form()
383 Desc: this method needs to be overridden. In this method,
384 a new form must be generated (as a CXGN::Page::Form object)
385 which can be instantiated either as a Static or Editable
396 warn "Please subclass 'generate_form' function!\n";
401 Desc: Displays a verification dialog for a delete action.
407 $self->check_modify_privileges();
409 my $object_name = shift;
410 my $field_name = shift;
411 my $object_id = shift;
412 my $back_link = shift;
414 $self->get_page()->header();
416 page_title_html
("$title");
419 Delete database object
$object_name (id
=$object_id)?
420 <input type
="hidden" name
="action" value
="delete" />
421 <input type
="hidden" name
="$field_name" value
="$object_id" />
422 <input type
="submit" value
="Delete" />
429 $self->get_page()->footer();
436 Desc: actually performs the delete in the database.
437 Database objects should implement the delete as a
438 obsoletion. See L<CXGN::DB::ModifiableI>
448 warn "Override 'delete' function in derived class\n";
450 $self->get_page()->message_page("Deleting is not implemented for this object");
456 Desc: displays a confirmation dialog for before certain store
464 $self->get_page()->header();
467 page_title_html
("Confirm store");
470 Store object
in the database?
471 <input type
="hidden" name
="action" value
="store" />
473 <input type
="submit" value
="Store" />
476 <a href
="javascript:history.back(1)">Go back without storing the object
</a
>
480 $self->get_page()->footer();
486 Usage: $s->display_page()
487 Desc: can be overridden in the subclass. In the default
488 implementation, displays the header and footer, the
489 appropriate edit links and the form in table format,
490 in the appropriate Editable or Static form.
501 $self->get_page()->header();
502 print $self->get_edit_links();
503 $self->get_form()->as_table();
505 $self->get_page()->footer();
510 =head2 get_page, set_page
512 Usage: $p = $s->get_page();
513 Desc: get the page object of the current page.
514 the simple form page automatically initializes
525 return $self->{page
};
536 Usage: $dbh = $s->get_dbh();
537 Desc: get the dbh connection of this simple form page.
538 the simple form page constructor initializes this
560 Usage: my $l = $s->get_login()
561 Desc: get the login object of the simple form page.
562 the simple form page constructor initializes this
573 return $self->{login
};
579 $self->{login
}=shift;
584 Usage: my %args = $s ->get_args()
585 Desc: gets the page arguments as a hash. The
586 simple form page constructor initializes
597 if (!$self->{args
}) { %{$self->{args
}} = (); }
598 return %{$self->{args
}};
608 =head2 get_object, set_object
610 Usage: $s->define_object(CXGN::Phenome::Locus->new($self->get_dbh(), $self->get_object_id())
611 Desc: sets the object that this simple form page operates on.
612 this needs to be called in the define_object method, which must
613 be overridden. see define_object for more info.
623 return $self->{object
};
629 $self->{object
}=shift;
634 Usage: my @owners = $self->get_owners()
635 Desc: find the owner(s) of your object
636 most tables have a single owner (the field sp_person_id)
637 and this accessor will return an array with one element.
638 However, if the object has multiple owners
639 (such as 'Locus' - group of owners is defined in locus_owner table)
640 this function will need to be overriden (see CXGN/Phenome/Locus.pm)
650 return @
{$self->{owners
}};
656 @
{$self->{owners
}}=@_;
659 =head2 get_action, set_action
661 Usage: my $action = $s ->get_action()
662 Desc: the current action of the simple form page.
663 can be either edit, save, view, etc.
664 the action is set by the constructor, so the
665 setter should never be called.
675 return $self->{action
};
681 $self->{action
}=shift;
685 =head2 get_primary_key, set_primary_key
687 Usage: $s->set_primary_key("read_id")
688 Desc: sets the name of the primary key of the data at hand.
696 sub get_primary_key
{
698 return $self->{primary_key
};
703 sub set_primary_key
{
705 $self->{primary_key
}=shift;
708 =head2 get_script_name, set_script_name
710 Usage: $s->get_script_name()
711 Desc: gets the name of the script that uses the simple form page.
712 useful for constructing links.
720 sub get_script_name
{
722 if (!exists($self->{script_name
})) {
723 #return CXGN::Apache::Request::page_name();
724 return $ENV{SCRIPT_NAME
};
727 return $self->{script_name
};
732 sub set_script_name
{
734 $self->{script_name
}=shift;
737 =head2 get_object_id, set_object_id
739 Usage: my $id = $s->get_object_id()
740 Desc: get/set the primary id for the object at hand
750 return $self->{object_id
};
756 $self->{object_id
}=shift;
759 =head2 get_form, set_form
761 Usage: my $f = $s->get_form()
762 Desc: get the form object associated with the simple
763 form page. The form is initialized automatically to the
764 appropriate subclass, either L<CXGN::Page::Form::Static> or
765 L<CXGN::Page::Form::Editable>, depending on the action,
766 according to the form object defined in the generate_form
767 method, which needs to be overridden in the subclass.
778 return $self->{form
};
789 Usage: $s->init_form()
790 Desc: initializes the correct form subclass, either
791 CXGN::Page::Form::Editable or CXGN::Page::Form::Static,
792 or CXGN::Page::Form::ConfirmStore.
803 if ($self->get_action() =~/edit|^store|new/) {
804 $self->set_form( CXGN
::Page
::Form
::Editable
-> new
() );
806 }elsif ($self->get_action() =~/confirm_store/) {
807 $self->set_form( CXGN
::Page
::Form
::ConfirmStore
->new() ) ;
810 $self->set_form( CXGN
::Page
::Form
::Static
-> new
() );
815 =head2 get_request, set_request
817 Usage: my $r = $s->get_request()
818 Desc: returns the apache request object for the
829 return $self->{request
};
835 $self->{request
}=shift;
838 =head2 validate_parameters_before_store
840 Desc: Allow for custom validation of the form as a whole,
841 and in perl rather than javascript; parameters can be
842 removed, modified or added as necessary before the database
843 is touched. Meant to be overridden.
845 Complements ElementI::validate(), which simply allows for
846 checking format of individual fields.
847 validate_parameters_before_store() is called after all
848 Elements have validate()d themselves.
850 To avoid letting the store go through, create an error or
851 message page and call exit().
857 sub validate_parameters_before_store
{
860 =head2 process_parameters_after_store
862 Desc: Allow for custom postprocessing, eg logging to disk, sending
863 e-mail confirmations, or changing the template of the form
864 before it\'s viewed again. Meant to be overridden.
869 sub process_parameters_after_store
{
872 =head2 get_edit_links
874 Usage: my $edit_links = $s->get_edit_links()
875 Desc: gets a string corresponding to the edit links to be placed
876 on the page, as appropriate for the current action. The edit
877 link, for example, is replaced with a 'cancel edit' in edit mode
889 my $form_name = shift;
890 return $self->get_new_link_html($form_name)." ".
891 $self->get_edit_link_html($form_name)." ".$self->get_delete_link_html($form_name);
895 =head2 function get_new_link_html
898 Description: Creates an appropriate 'new' link for the edit links.
902 sub get_new_link_html
{
904 my $form_name = shift;
906 my $script_name = $self->get_script_name();
907 my $primary_key = $self->get_primary_key();
908 my $object_id = $self->get_object_id();
910 my $new_link = qq { <a href
="$script_name?action=new&form=$form_name">[New
]</a
> };
911 if ($self->get_action() eq "edit") {
912 $new_link = qq { <span
class="ghosted">[New
]</span
> };
914 if ($self->get_action() eq "new") {
915 $new_link = qq { <a onClick
="history.go(-1)">[Cancel
]</a
> };
920 =head2 function get_edit_link_html
922 Description: Creates an appropriate 'edit' link for the edit links.
926 sub get_edit_link_html
{
928 my $form_name = shift;
931 my $script_name = $self->get_script_name();
932 my $primary_key = $self->get_primary_key();
933 my $object_id = $self->get_object_id();
935 my $user_id= $self->get_user()->get_sp_person_id();
936 my @owners= $self->get_owners();
937 if (($self->get_user()->get_user_type() eq "curator") || grep{/^$user_id$/} @owners ) {
938 $edit_link = qq { <a href
="$script_name?action=edit&form=$form_name&$primary_key=$object_id">[Edit
]</a
> };
941 $edit_link = qq { <span
class="ghosted">[Edit
]</span
> };
945 if ($self->get_action() eq "edit") {
946 $edit_link = qq { <a href
="$script_name?action=view&form=$form_name&$primary_key=$object_id">[Cancel Edit
]</a
> };
949 if ($self->get_action() eq "new") {
950 $edit_link = qq { <span
class="ghosted">[Edit
]</span
> };
956 =head2 function get_delete_link_html
958 Description: Generates and appropriate 'delete' link for the edit links.
962 sub get_delete_link_html
{
964 my $form_name = shift;
966 my $delete_link = "";
967 my $script_name = $self->get_script_name();
968 my $primary_key = $self->get_primary_key();
969 my $object_id = $self->get_object_id();
970 my $user_id= $self->get_user()->get_sp_person_id();
971 my @owners= $self->get_owners();
972 if (($self->get_user()->get_user_type() eq "curator") || grep{/^$user_id$/} @owners ) {
974 $delete_link = qq { <a href
="$script_name?action=confirm_delete&form=$form_name&$primary_key=$object_id">[Delete
]</a
> };
977 $delete_link = qq { <span
class="ghosted">[Delete
]</span
> };
980 if ($self->get_action() eq "edit") {
981 $delete_link = qq { <span
class="ghosted">[Delete
]</span
> };
984 if ($self->get_action() eq "new") {
985 $delete_link = qq { <span
class="ghosted">[Delete
]</span
> };