Merge branch 'master' into topic/analyze_phenotypes_page
[sgn.git] / lib / CXGN / Calendar.pm
blobb627ed4388e46d5aa8d20c504443e2fd1d9c9f5f
2 =head1 NAME
4 CXGN::Calendar - helper class for calendar
6 =head1 SYNOPSYS
8 my $calendar_funcs = CXGN::Calendar->new( { } );
9 $calendar_funcs->check_value_format("2012/01/01 00:00:00");
11 etc.
13 =head1 AUTHOR
15 Nicolas Morales <nm529@cornell.edu>
17 =head1 METHODS
19 =cut
21 package CXGN::Calendar;
23 use Moose;
24 use Try::Tiny;
25 use SGN::Model::Cvterm;
26 use Time::Piece;
27 use Time::Seconds;
28 use Data::Dumper;
31 has 'bcs_schema' => (
32 isa => 'Bio::Chado::Schema',
33 is => 'rw',
36 has 'sp_person_id' => (
37 isa => "Int",
38 is => 'rw',
41 has 'roles' => (
42 isa => "ArrayRef",
43 is => 'rw',
47 sub check_value_format {
48 my $self = shift;
49 my $value = shift;
51 if ($value) {
52 #Events saved through the calendar will have this format
53 if ($value =~ /^{"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d","\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d","/) {
54 return $value;
56 #Dates saved through the trial 'Add Harvest Date' or 'Add Planting Date' will have this format
57 elsif ($value =~ /^\d\d\d\d\/\d\d\/\d\d\s\d\d:\d\d:\d\d$/) {
58 $value = $self->format_time($value)->datetime;
59 return '{"'.$value.'","'.$value.'","","#"}';
61 #Historical dates in teh database often have this format
62 elsif ($value =~ /^(\d{4})-(Jan|January|Feb|February|March|Mar|April|Apr|May|June|Jun|July|Jul|August|Aug|September|Sep|October|Oct|November|Nov|December|Dec)-(\d)/) {
63 $value = $self->format_time($value)->datetime;
64 return '{"'.$value.'","'.$value.'","","#"}';
66 else {
67 return;
69 } else {
70 return;
74 sub parse_calendar_array {
75 my $self = shift;
76 my $raw_value = shift;
78 $raw_value =~ tr/{}"//d;
79 my @calendar_array = split(/,/, $raw_value);
80 return @calendar_array;
83 #Displaying 00:00:00 time on mouseover and mouseclick is ugly, so this sub is used to determine date display format, given a datetime string.
84 sub format_display_date {
85 my $self = shift;
86 my $date_display;
87 my $formatted_time = shift;
89 if ($formatted_time->hms('') == '000000') {
90 $date_display = $formatted_time->strftime("%Y-%B-%d");
91 } else {
92 $date_display = $formatted_time->strftime("%Y-%B-%d %H:%M:%S");
94 return $date_display;
97 #FullCalendar's end datetime is exclusive for allday events in the month view. Since all events in the month view are allday = 1, a full day must be added so that it is displayed correctly on the calendar. In the agendaWeek view, not all events are allday = 1, so the end is only modified for allday events.
98 sub calendar_end_display {
99 my $self = shift;
100 my $formatted_time = shift;
101 my $view = shift;
102 my $allday = shift;
104 my $end_time;
105 $end_time = $formatted_time->epoch;
106 if ($view eq 'month' || ($view eq 'agendaWeek' && $allday == 1)) {
107 $end_time += ONE_DAY;
109 $end_time = Time::Piece->strptime($end_time, '%s')->datetime;
110 return $end_time;
113 #On the agendaWeek view, events with start dates with 00:00:00 time are displayed as allDay=true.
114 sub determine_allday {
115 my $self = shift;
116 my $allday;
117 my $formatted_time = shift;
119 if ($formatted_time->hms('') == '000000') {
120 $allday = 1;
121 } else {
122 $allday = 0;
124 return $allday;
127 #This function is used to return a Time::Piece object, which is useful for format consistency. It can take a variety of formats, which is important to match historic date data in teh database.
128 sub format_time {
129 my $self = shift;
130 my $input_time = shift;
132 #print STDERR $input_time."\n";
134 my $formatted_time;
136 if ($input_time =~ /^\d{4}-\d\d-\d\d$/) {
137 #print STDERR '1 '.$input_time."\n";
138 $formatted_time = Time::Piece->strptime($input_time, '%Y-%m-%d');
140 if ($input_time =~ /^\d\d\d\d\/\d\d\/\d\d\s\d\d:\d\d:\d\d$/) {
141 #print STDERR '2 '.$input_time."\n";
142 $formatted_time = Time::Piece->strptime($input_time, '%Y/%m/%d %H:%M:%S');
144 if ($input_time =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})$/) {
145 #print STDERR '3 '.$input_time."\n";
146 $formatted_time = Time::Piece->strptime($input_time, '%Y-%m-%dT%H:%M:%S');
148 if ($input_time =~ /^(\d{4})-(Jan|Feb|Mar|Apr|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2})$/) {
149 #print STDERR '4 '.$input_time."\n";
150 $formatted_time = Time::Piece->strptime($input_time, '%Y-%b-%d');
152 if ($input_time =~ /^(\d{4})-(Jan|Feb|Mar|Apr|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{1})$/) {
153 my $single_digit_date = substr($input_time, -1);
154 my $input_time_1 = substr($input_time, 0, -1);
155 $input_time = $input_time_1.'0'.$single_digit_date;
156 #print STDERR '5 '.$input_time."\n";
157 $formatted_time = Time::Piece->strptime($input_time, '%Y-%b-%d');
159 if ($input_time =~ /^(\d{4})-(January|February|March|April|May|June|July|August|September|October|November|December)-(\d{2})$/) {
160 #print STDERR '6 '.$input_time."\n";
161 $formatted_time = Time::Piece->strptime($input_time, '%Y-%B-%d');
163 if ($input_time =~ /^(\d{4})-(January|February|March|April|May|June|July|August|September|October|November|December)-(\d{1})$/) {
164 my $single_digit_date = substr($input_time, -1);
165 my $input_time_1 = substr($input_time, 0, -1);
166 $input_time = $input_time_1.'0'.$single_digit_date;
167 #print STDERR '7 '.$input_time."\n";
168 $formatted_time = Time::Piece->strptime($input_time, '%Y-%B-%d');
170 return $formatted_time;
174 sub get_calendar_events_personal {
175 my $self = shift;
176 my $schema = $self->bcs_schema;
177 my $dbh = $schema->storage->dbh;
178 my $person_id = $self->sp_person_id;
179 my @roles = @{$self->roles};
180 #print STDERR Dumper \@roles;
182 my @search_project_ids = '-1';
183 foreach (@roles) {
184 my $q="SELECT project_id FROM project WHERE name=?";
185 my $sth = $dbh->prepare($q);
186 $sth->execute($_);
187 while (my ($project_id) = $sth->fetchrow_array ) {
188 push(@search_project_ids, $project_id);
190 my $q="SELECT subject_project_id FROM project_relationship JOIN cvterm ON (type_id=cvterm_id) WHERE object_project_id=? and cvterm.name='breeding_program_trial_relationship'";
191 my $sth = $dbh->prepare($q);
192 $sth->execute($project_id);
193 while (my ($trial) = $sth->fetchrow_array ) {
194 push(@search_project_ids, $trial);
199 @search_project_ids = map{$_='me.project_id='.$_; $_} @search_project_ids;
200 my $search_projects = join(" OR ", @search_project_ids);
201 my $search_rs = $schema->resultset('Project::Project')->search(
202 undef,
203 {join=>{'projectprops'=>{'type'=>'cv'}},
204 '+select'=> ['projectprops.projectprop_id', 'type.name', 'projectprops.value', 'type.cvterm_id'],
205 '+as'=> ['pp_id', 'cv_name', 'pp_value', 'cvterm_id'],
208 $search_rs = $search_rs->search([$search_projects]);
209 return $search_rs;
212 sub populate_calendar_events {
213 my $self = shift;
214 my $search_rs = shift;
215 my $view = shift;
216 my @events;
217 my $allday;
218 my $start_time;
219 my $start_drag;
220 my $start_display;
221 my $end_time;
222 my $end_drag;
223 my $end_display;
224 my $formatted_time;
225 my @calendar_array;
226 my $title;
227 my $property;
228 while (my $result = $search_rs->next) {
230 #Check if project property value is an event, and if it is not, then skip to next result.
231 my $calendar_formatted_value = $self->check_value_format($result->get_column('pp_value'));
232 if (!$calendar_formatted_value) {
233 next;
236 #print STDERR $calendar_formatted_value;
238 @calendar_array = $self->parse_calendar_array($calendar_formatted_value);
239 if (!$calendar_array[0]) {
240 next;
243 #We begin with the start datetime, or the first element in the @calendar_array.
244 #A time::piece object is returned from format_time(). Calling ->datetime on this object returns an ISO8601 datetime string. This string is what is used as the calendar event's start. Using format_display_date(), a nice date to display on mouse over is returned.
245 $formatted_time = $self->format_time($calendar_array[0]);
246 $start_time = $formatted_time->datetime;
247 $start_display = $self->format_display_date($formatted_time);
249 #Because fullcalendar does not allow event resizing of allDay=false events in the month view, the allDay parameter must be set depending on the view. The allDay parameter for the agendaWeek view is important and is set using determine_allday().
250 if ($view eq 'month') {
251 $allday = 1;
252 } elsif ($view eq 'agendaWeek') {
253 $allday = $self->determine_allday($formatted_time);
256 #Then we process the end datetime, which is the second element in the calendar_array. calendar_end_display determines what the calendar should display as the end, and format_display_date() returns a nice date to display on mouseover.
257 $formatted_time = $self->format_time($calendar_array[1]);
258 $end_time = $self->calendar_end_display($formatted_time, $view, $allday);
259 $end_display = $self->format_display_date($formatted_time);
261 #Because FullCallendar's end date is exclusive, an end datetime with 00:00:00 will be displayed as one day short on the calendar, and so corrections to the event's end must be made. To facilitate event dragging, an event.end_drag property is used.
262 $end_drag = $formatted_time->datetime;
264 #To display the project name and project properties nicely in the mouseover and more info, we capitalize the first letter of each word.
265 $title = $result->name;
266 #$title =~ s/([\w']+)/\u\L$1/g;
267 $property = $result->get_column('cv_name');
268 $property =~ s/([\w']+)/\u\L$1/g;
270 #Variables are pushed into the event array and will become properties of Fullcalendar events, like event.start, event.cvterm_url, etc.
271 push(@events, {projectprop_id=>$result->get_column('pp_id'), title=>$title, property=>$property, start=>$start_time, start_drag=>$start_time, start_display=>$start_display, end=>$end_time, end_drag=>$end_drag, end_display=>$end_display, project_id=>$result->project_id, project_url=>'/breeders_toolbox/trial/'.$result->project_id.'/', cvterm_id=>$result->get_column('cvterm_id'), cvterm_url=>'/cvterm/'.$result->get_column('cvterm_id').'/view', allDay=>$allday, p_description=>$result->description, event_description=>$calendar_array[2], event_url=>$calendar_array[3]});
273 return \@events;
276 #Takes an event string, which is the value stored in the database for events, and return a nice start date.
277 sub display_start_date {
278 my $self = shift;
279 my $value = shift;
281 my $checked_value = $self->check_value_format($value);
282 if ($checked_value) {
283 my @calendar_array = $self->parse_calendar_array($checked_value);
284 if ($calendar_array[0]) {
285 my $formatted_time = $self->format_time($calendar_array[0]);
286 my $start_display = $self->format_display_date($formatted_time);
287 return $start_display;
288 } else {
289 return;
291 } else {
292 return;
296 #Takes an event string, which is the value stored in the database for events, and return a nice end date.
297 sub display_end_date {
298 my $self = shift;
299 my $value = shift;
301 my $checked_value = $self->check_value_format($value);
302 if ($checked_value) {
303 my @calendar_array = $self->parse_calendar_array($checked_value);
304 if ($calendar_array[1]) {
305 my $formatted_time = $self->format_time($calendar_array[1]);
306 my $end_display = $self->format_display_date($formatted_time);
307 return $end_display;
308 } else {
309 return;
311 } else {
312 return;
316 #Takes an event string, which is the value stored in the database for events, and returns the description.
317 sub display_description {
318 my $self = shift;
319 my $value = shift;
321 my $checked_value = $self->check_value_format($value);
322 if ($checked_value) {
323 my @calendar_array = $self->parse_calendar_array($checked_value);
324 if ($calendar_array[2]) {
325 my $description = $calendar_array[2];
326 return $description;
327 } else {
328 return;
330 } else {
331 return;
335 #Takes an event string, which is the value stored in the database for events, and returns the url.
336 sub display_url {
337 my $self = shift;
338 my $value = shift;
340 my $checked_value = $self->check_value_format($value);
341 if ($checked_value) {
342 my @calendar_array = $self->parse_calendar_array($checked_value);
343 if ($calendar_array[3]) {
344 my $url = $calendar_array[3];
345 return $url;
346 } else {
347 return;
349 } else {
350 return;