From c356a7acc68034ce2238c72c66efcaa31d385d01 Mon Sep 17 00:00:00 2001 From: Paul Driver Date: Thu, 1 Apr 2010 11:53:09 -0700 Subject: [PATCH] DateTime::Event::ICal in Calendar, workflow to extend recurrence --- docs/changelog/7.x.x.txt | 3 + docs/upgrades/upgrade_7.9.1-7.9.2.pl | 12 + lib/WebGUI/Asset/Event.pm | 1068 +++++++---------- .../Activity/ExtendCalendarRecurrences.pm | 185 +++ .../Activity_ExtendCalendarRecurrences.pm | 14 + sbin/testEnvironment.pl | 1 + t/Asset/Event/recur.t | 81 ++ .../Activity/ExtendCalendarRecurrences.t | 102 ++ 8 files changed, 825 insertions(+), 641 deletions(-) create mode 100644 lib/WebGUI/Workflow/Activity/ExtendCalendarRecurrences.pm create mode 100644 lib/WebGUI/i18n/English/Activity_ExtendCalendarRecurrences.pm create mode 100644 t/Asset/Event/recur.t create mode 100644 t/Workflow/Activity/ExtendCalendarRecurrences.t diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 3d9038602..852516bc7 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -1,4 +1,7 @@ 7.9.2 + - added: Workflow to extend recurring Calendar events 2 years from the + current date (now part of weekly maintenence) + - new dependency: DateTime::Event::ICal - fixed #11507: Spectre Reports Wrong Workflow Count - added #11412: Additional navigation in Gallery Photo View - added: Sort Items switch to Syndicated Content asset diff --git a/docs/upgrades/upgrade_7.9.1-7.9.2.pl b/docs/upgrades/upgrade_7.9.1-7.9.2.pl index a12b9b425..0fcae01e2 100644 --- a/docs/upgrades/upgrade_7.9.1-7.9.2.pl +++ b/docs/upgrades/upgrade_7.9.1-7.9.2.pl @@ -33,6 +33,7 @@ my $session = start(); # this line required # upgrade functions go here addSortItemsSCColumn($session); removeTranslationCruft($session); +addExtensionWorkflow($session); finish($session); # this line required @@ -48,6 +49,17 @@ sub removeTranslationCruft { print "DONE!\n" unless $quiet; } +#---------------------------------------------------------------------------- +sub addExtensionWorkflow { + print "\tAdding calendar event extension to weekly maintenence..." + unless $quiet; + my $workflow = WebGUI::Workflow->new($session, 'pbworkflow000000000002'); + my $activity = $workflow->addActivity('WebGUI::Workflow::Activity::ExtendCalendarRecurrences'); + $activity->set(title => 'Extend Calendar Recurrences'); + $activity->set(description => 'Create events for live recurrences up to two years from the current date'); + print "Done\n" unless $quiet; +} + #---------------------------------------------------------------------------- sub addSortItemsSCColumn { my $session = shift; diff --git a/lib/WebGUI/Asset/Event.pm b/lib/WebGUI/Asset/Event.pm index b309e7ecd..40856d9f5 100644 --- a/lib/WebGUI/Asset/Event.pm +++ b/lib/WebGUI/Asset/Event.pm @@ -25,7 +25,9 @@ use WebGUI::International; use WebGUI::Asset::Template; use WebGUI::Form; use WebGUI::Storage; -use Storable; +use Test::Deep::NoTest qw(eq_deeply); +use DateTime::Event::ICal; +use DateTime::Set; use base 'WebGUI::Asset'; @@ -72,6 +74,141 @@ sub addRevision { return $newRev; } +{ + +my %dayNamesToICal = ( + m => 'mo', + t => 'tu', + w => 'we', + r => 'th', + f => 'fr', + s => 'sa', + u => 'su', +); + +my %weekNamesToICal = ( + first => 1, + second => 2, + third => 3, + fourth => 4, + fifth => -1, +); + +my %monthNamesToICal = ( + jan => 1, + feb => 2, + mar => 3, + apr => 4, + may => 5, + jun => 6, + jul => 7, + aug => 8, + sep => 9, + oct => 10, + nov => 11, + dec => 12, +); + +#################################################################### + +=head2 dateSet ( recur, start, end ) + +Produces a DateTime::Set for the given recurrence (see getRecurrence) spanning +from start to end (DateTime objects). End is only required if the recurrence +does not have an 'endAfter'. This can be called as a class or object method. + +=cut + +sub dateSet { + my ($self, $recur, $start, $end) = @_; + + my $type = $recur->{recurType}; + my $interval = $recur->{every}; + $end or my $count = $recur->{endAfter} or croak('No end for dateSet'); + + # The "every n weekdays" concept isn't supported in ical lingo. + if ($type eq 'weekday') { + my $i = 0; + my $weeks = int($interval / 5); + my $days = $interval % 5; + my $d = $start->clone; + my @dates = (); + my $c = $end ? sub { $d < $end } : sub { $i++ < $count }; + while ($c->()) { + my $dw = $d->dow; + my $n = $days; + if ($dw == 6) { + $n++; + } elsif ($n + $dw > 5) { + $n += 2; + } + $d->add(weeks => $weeks, days => $n); + push(@dates, $d->clone); + } + return DateTime::Set->from_datetimes(dates => \@dates); + } + + my %a = ( + dtstart => $start, + interval => $interval, + ); + if ($end) { + $a{until} = $end; + } + else { + $a{count} = $count; + } + + my $daynames = [ + map { $dayNamesToICal{$_} } + @{ $recur->{dayNames} } + ]; + + my $weeknos = [ + map { $weekNamesToICal{$_} } + @{ $recur->{weeks} } + ]; + + my $monthnos = [ + map { $monthNamesToICal{$_} } + @{ $recur->{months} } + ]; + + my $day = [ $recur->{dayNumber} ]; + + { + daily => sub { + $a{freq} = 'daily'; + }, + weekly => sub { + $a{freq} = 'weekly'; + $a{byday} = $daynames, + }, + monthDay => sub { + $a{freq} = 'monthly'; + $a{bymonthday} = $day; + }, + monthWeek => sub { + $a{freq} = 'monthly'; + $a{byday} = $daynames; + $a{bysetpos} = $weeknos; + }, + yearDay => sub { + $a{freq} = 'yearly'; + $a{bymonth} = $monthnos; + $a{bymonthday} = $day; + }, + yearWeek => sub { + $a{freq} = 'yearly'; + $a{bymonth} = $monthnos; + $a{byday} = $daynames; + $a{bysetpos} = $weeknos; + }, + }->{$type}->(); + return DateTime::Event::ICal->recur(%a); +} + +} #################################################################### @@ -171,8 +308,12 @@ sub definition { } +#------------------------------------------------------------------- - +sub duration { + my $self = shift; + return $self->getDateTimeEnd - $self->getDateTimeStart; +} #------------------------------------------------------------------- @@ -197,7 +338,7 @@ sub canAdd { =head2 canEdit ( [userId] ) -Returns true if the given userId can edit this asset. If userId is not given, +Returns true if the given userId can edit this asset. If userId is not given, the userId of the current session is used. Users can edit this event if they are the owner of the event, or if they are @@ -217,95 +358,66 @@ sub canEdit { return $self->getParent->canEdit( $userId ); } +#------------------------------------------------------------------- + +=head2 generateRecurrence (date) + +Creates an recurrence event in the parent calendar for the given date + +=cut + +sub generateRecurrence { + my ($self, $start) = @_; + my $end = $start + $self->duration; + my ($sdb, $edb) = map { $_->ymd } ($start, $end); + + # Only generate if the recurId does not exist on this day + my $sql = q{ + SELECT COUNT(*) + FROM Event + WHERE recurId = ? AND startDate = ? + }; + my $db = $self->session->db; + unless ($db->quickScalar($sql, [$self->get('recurId'), $sdb])) { + my $child = $self->get; + $child->{startDate} = $sdb; + $child->{endDate} = $edb; + $self->getParent->addChild( + $child, undef, undef, { skipAutoCommitWorkflows => 1 } + ); + } +} #################################################################### -=head2 generateRecurringEvents ( ) +=head2 generateRecurringEvents() Generates Events according to this Event's recurrence pattern. -Will croak on failure. - =cut sub generateRecurringEvents { my $self = shift; - my $parent = $self->getParent; - my $session = $self->session; + my $recur = $self->getRecurrence or croak( + 'Cannot generate recurring events: Event has no recurrence pattern.' + ); - my $properties = $self->get; - my $recurId = $self->get("recurId"); - my $recur = {$self->getRecurrence}; + my $rstart = WebGUI::DateTime->new($self->session, $recur->{startDate}); + my $rend = !$recur->{endAfter} && $self->limitedEndDate($rstart->clone->add(years => 2)); + my $i = $self->dateSet($recur, $rstart, $rend)->iterator; - # This method only works on events that have recurrence patterns - if (!$recurId) { - croak("Cannot generate recurring events: Event has no recurrence pattern."); + while (my $d = $i->next) { + $self->generateRecurrence($d); } - # Get the distance between the event startDate and endDate - # Include the time, as recurrence can change it when crossing a daylight - # savings time border. - # TODO: Allow recurrence patterns of less than a single day. - - my $initialStart - = WebGUI::DateTime->new($session, $properties->{startDate} . " " - . ($properties->{startTime} || "00:00:00")); - my $initialEnd - = WebGUI::DateTime->new($session, $properties->{endDate} . " " - . ($properties->{endTime} || "00:00:00")); - my $duration = $initialEnd->subtract_datetime($initialStart); - - my $localTime; - if ($properties->{startTime}) { - $localTime = $initialStart->clone->set_time_zone($properties->{timeZone})->toMysqlTime; - } - $properties->{feedUid} = undef; - - my @dates = $self->getRecurrenceDates; - - for my $date (@dates) { - my $startDate; - if ($localTime) { - $startDate = WebGUI::DateTime->new($session, - mysql => $date . " " . $localTime, - time_zone => $properties->{timeZone}, - ); - } - else { - $startDate = WebGUI::DateTime->new($session, $date." 00:00:00"); - } - my $endDate = $startDate->clone->add($duration); - my $dbDate = $startDate->toDatabaseDate; - # Only generate if the recurId does not exist on this day - my ($exists) - = $session->db->quickArray( - "select count(*) from Event where recurId=? and startDate=?", - [$properties->{recurId}, $dbDate], - ); - - if (!$exists) { - $properties->{startDate} = $dbDate; - $properties->{endDate} = $endDate->toDatabaseDate; - if ($localTime) { - $properties->{startTime} = $startDate->toDatabaseTime; - $properties->{endTime} = $endDate->toDatabaseTime; - } - my $newEvent = $parent->addChild($properties, undef, undef, { skipAutoCommitWorkflows => 1 }); - } - } - - return $recurId; + return $recur->{recurId}; } - - - - #################################################################### =head2 getAutoCommitWorkflowId -Gets the WebGUI::VersionTag workflow to use to automatically commit Events. +Gets the WebGUI::VersionTag workflow to use to automatically commit Events. By specifying this method, you activate this feature. =cut @@ -328,7 +440,7 @@ sub getAutoCommitWorkflowId { =head2 getDateTimeStart -Returns a WebGUI::DateTime object based on the startDate and startTime values, +Returns a WebGUI::DateTime object based on the startDate and startTime values, adjusted for the current user's time zone. If this is an all-day event, the start time is 00:00:00 and the timezone is not @@ -367,7 +479,7 @@ sub getDateTimeStart { =head2 getDateTimeEnd -Returns a WebGUI::DateTime object based on the endDate and endTime values, +Returns a WebGUI::DateTime object based on the endDate and endTime values, adjusted for the current user's time zone. If this is an all-day event, the end time is 23:59:59 and the timezone is not @@ -431,7 +543,7 @@ sub getDateTimeEndNI { =head2 getEventNext -Gets the event that occurs after this event in the calendar. Returns the +Gets the event that occurs after this event in the calendar. Returns the Event object. =cut @@ -459,11 +571,11 @@ sub getEventNext { my @orderByColumns = ( - 'Event.startDate', - 'Event.startTime', - 'Event.endDate', - 'Event.endDate', - 'assetData.title', + 'Event.startDate', + 'Event.startTime', + 'Event.endDate', + 'Event.endDate', + 'assetData.title', 'assetData.assetId', ); @@ -471,13 +583,13 @@ sub getEventNext { #returnObjects => 1, includeOnlyClasses => ['WebGUI::Asset::Event'], joinClass => 'WebGUI::Asset::Event', - orderByClause => join(",", @orderByColumns), + orderByClause => join(",", @orderByColumns), whereClause => $where, limit => 1, }); - return undef unless $events->[0]; + return undef unless $events->[0]; return WebGUI::Asset->newByDynamicClass($self->session,$events->[0]); } @@ -516,12 +628,12 @@ sub getEventPrev { $where .= ")"; my @orderByColumns = ( - 'Event.startDate DESC', - 'Event.startTime DESC', - 'Event.endDate DESC', - 'Event.endDate DESC', - 'assetData.title DESC', - 'assetData.assetId DESC', + 'Event.startDate DESC', + 'Event.startTime DESC', + 'Event.endDate DESC', + 'Event.endDate DESC', + 'assetData.title DESC', + 'assetData.assetId DESC', ); my $events = $self->getLineage(['siblings'], { @@ -613,18 +725,18 @@ sub getIcalEnd { =head2 getRecurrence -Returns a hash of recurrence information. Some of the keys are only relevant -to certain recurrence types. +Returns a hashref of recurrence information. Some of the keys are only +relevant to certain recurrence types. -B =over 8 =item recurType -The recurrence type (daily, weekdays, weekly, monthDay, monthWeek, yearDay, +The recurrence type (daily, weekdays, weekly, monthDay, monthWeek, yearDay, yearWeek) =item startDate @@ -647,7 +759,7 @@ The number of (days, weeks, months, years) between each recurrence. =item dayNames -A list of day names that this event recurs on. +A list of day names that this event recurs on. u - Sunday m - Monday @@ -693,453 +805,115 @@ A list of months that this event recurs on =cut sub getRecurrence { - my $self = shift; - #use Data::Dumper; - #$self->session->errorHandler->warn("recurId: ".$self->get("recurId")); - return () unless $self->get("recurId"); + my $self = shift; + return undef unless $self->get("recurId"); - my %data - = $self->session->db->quickHash( - "select * from Event_recur where recurId=?", - [$self->get("recurId")] + return $self->{recurrence} ||= do { + #use Data::Dumper; + #$self->session->errorHandler->warn("recurId: ".$self->get("recurId")); + + my %data + = $self->session->db->quickHash( + "select * from Event_recur where recurId=?", + [$self->get("recurId")] + ); + + my %recurrence = ( + recurType => $data{recurType}, ); - my %recurrence = ( - recurType => $data{recurType}, - ); + # We do not need the recurId, and in fact will screw up our later comparisons + delete $data{"recurId"}; - # We do not need the recurId, and in fact will screw up our later comparisons - delete $data{"recurId"}; + my $type = lc $data{"recurType"}; + if ($type eq "daily" || $type eq "weekday") { + $recurrence{every} = $data{pattern}; + } + elsif ($type eq "weekly") { + #(\d+) ([umtwrfs]+) + $data{pattern} =~ /(\d+) ([umtwrfs]+)/; + $recurrence{every} = $1; + $recurrence{dayNames} = [split //, $2]; + } + elsif ($type eq "monthweek") { + #(\d+) (first,second,third,fourth,last) ([umtwrfs]+) + $data{pattern} =~ /(\d+) ([a-z,]+) ([umtwrfs]+)/; + $recurrence{every} = $1; + $recurrence{weeks} = [split /,/, $2]; + $recurrence{dayNames} = [split //, $3]; + } + elsif ($type eq "monthday") { + #(\d+) on (\d+) + $data{pattern} =~ /(\d+) (\d+)/; + $recurrence{every} = $1; + $recurrence{dayNumber} = $2; + } + elsif ($type eq "yearweek") { + #(\d+) (first,second,third,fourth,last) ([umtwrfs]+)? (jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec) + $data{pattern} =~ /(\d+) ([a-z,]+) ([umtwrfs]+) ([a-z,]+)/; + $recurrence{every} = $1; + $recurrence{weeks} = [split /,/, $2]; + $recurrence{dayNames} = [split //, $3]; + $recurrence{months} = [split /,/, $4]; + } + elsif ($type eq "yearday") { + #(\d+) on (\d+) (jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec) + $data{pattern} =~ /(\d+) (\d+) ([a-z,]+)/; + $recurrence{every} = $1; + $recurrence{dayNumber} = $2; + $recurrence{months} = [split /,/, $3]; + } - my $type = lc $data{"recurType"}; - if ($type eq "daily" || $type eq "weekday") { - $recurrence{every} = $data{pattern}; - } - elsif ($type eq "weekly") { - #(\d+) ([umtwrfs]+) - $data{pattern} =~ /(\d+) ([umtwrfs]+)/; - $recurrence{every} = $1; - $recurrence{dayNames} = [split //, $2]; - } - elsif ($type eq "monthweek") { - #(\d+) (first,second,third,fourth,last) ([umtwrfs]+) - $data{pattern} =~ /(\d+) ([a-z,]+) ([umtwrfs]+)/; - $recurrence{every} = $1; - $recurrence{weeks} = [split /,/, $2]; - $recurrence{dayNames} = [split //, $3]; - } - elsif ($type eq "monthday") { - #(\d+) on (\d+) - $data{pattern} =~ /(\d+) (\d+)/; - $recurrence{every} = $1; - $recurrence{dayNumber} = $2; - } - elsif ($type eq "yearweek") { - #(\d+) (first,second,third,fourth,last) ([umtwrfs]+)? (jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec) - $data{pattern} =~ /(\d+) ([a-z,]+) ([umtwrfs]+) ([a-z,]+)/; - $recurrence{every} = $1; - $recurrence{weeks} = [split /,/, $2]; - $recurrence{dayNames} = [split //, $3]; - $recurrence{months} = [split /,/, $4]; - } - elsif ($type eq "yearday") { - #(\d+) on (\d+) (jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec) - $data{pattern} =~ /(\d+) (\d+) ([a-z,]+)/; - $recurrence{every} = $1; - $recurrence{dayNumber} = $2; - $recurrence{months} = [split /,/, $3]; - } + $recurrence{startDate} = $data{startDate}; + if ($data{endDate} && $data{endDate} =~ /^after (\d+)/i) { + $recurrence{endAfter} = $1; + } + elsif ($data{endDate}) { + $recurrence{endDate} = $data{endDate}; + } - $recurrence{startDate} = $data{startDate}; - if ($data{endDate} && $data{endDate} =~ /^after (\d+)/i) { - $recurrence{endAfter} = $1; - } - elsif ($data{endDate}) { - $recurrence{endDate} = $data{endDate}; - } - - return %recurrence; + \%recurrence; + }; } - - - #################################################################### -=head2 getRecurrenceDates ( endDate ) +=head2 getRecurrenceDates () -Gets a series of dates in this event's recurrence pattern, up to the -calendar's configured "maintainRecurrenceOffset". - -This is quite possibly the worst algorithm I've ever created. We should be -using DateTime::Event::ICal instead. +DEPRECATED. Gets a series of dates in this event's recurrence pattern. This +is now implemented in terms of the dateSet method, which is what you should be +using. This method remains for backwards compatibility. =cut sub getRecurrenceDates { - my $self = shift; + my $self = shift; + my $recur = $self->getRecurrence; + return undef unless $recur && $recur->{recurType}; - my %date; - my $recur = {$self->getRecurrence}; - return undef unless $recur->{recurType}; + my $start = WebGUI::DateTime->new($self->session, "$recur->{startDate} 00:00:00"); - my %dayNames = ( - 1 => "m", - 2 => "t", - 3 => "w", - 4 => "r", - 5 => "f", - 6 => "s", - 7 => "u", - ); + my $end = $recur->{endDate} && WebGUI::DateTime->new($self->session, + "$recur->{endDate} 00:00:00"); - my %weeks = ( - 0 => "first", - 1 => "second", - 2 => "third", - 3 => "fourth", - 4 => "fifth", - ); - - - my $dt = WebGUI::DateTime->new($self->session, $recur->{startDate}." 00:00:00"); - my $dt_start = $dt->clone; # Keep track of the initial start date - my $dt_end; - if ($recur->{endDate}) { - $dt_end = WebGUI::DateTime->new($self->session, $recur->{endDate}." 00:00:00"); - } # Set an end for events with no end - # TODO: Get the maintainRecurrenceOffset value - elsif (!$recur->{endDate} && !$recur->{endAfter}) { - $dt_end = $dt->clone->add(years=>2); + # TODO: Get the maintainRecurrenceOffset value + if (!$recur->{endDate} && !$recur->{endAfter}) { + $end = $start->clone->add(years=>2); } - - RECURRENCE: while (1) { - ####### daily - if ($recur->{recurType} eq "daily") { - ### Add date - $date{$dt->strftime('%F')}++; - - # Add interval - $dt->add(days => $recur->{every}); - - # Test for quit - if (($recur->{endAfter} && keys %date >= $recur->{endAfter}) || ($dt_end && $dt > $dt_end)) { - last RECURRENCE; - } - - # Next - next RECURRENCE; - } - ####### weekday - elsif ($recur->{recurType} eq "weekday") { - my $today = $dt->day_name; - - # If today is not a weekday - unless (grep /$today/i,qw(monday tuesday wednesday thursday friday)) { - # Add a day - $dt->add(days => 1); - - # Test for quit - if (($recur->{endAfter} && keys %date >= $recur->{endAfter}) || ($dt_end && $dt > $dt_end)) { - last RECURRENCE; - } - - # next - next RECURRENCE; - } - else { - ### Add date - $date{$dt->strftime('%F')}++; - - $dt->add(days => $recur->{every}); - - # Test for quit - if (($recur->{endAfter} && keys %date >= $recur->{endAfter}) || ($dt_end && $dt > $dt_end)) { - last RECURRENCE; - } - - # Next - next RECURRENCE; - } - } - ####### weekly - elsif ($recur->{recurType} eq "weekly") { - for (0..6) { # Work through the week - my $dt_day = $dt->clone->add(days => $_); - - # If today is past the endDate, quit. - last RECURRENCE - if ($recur->{endDate} && $dt_day > $dt_end); - - my $today = $dayNames{ $dt_day->day_of_week }; - - if (grep /$today/i, @{$recur->{dayNames}}) { - ### Add date - $date{$dt_day->strftime('%F')}++; - } - - # If occurrences is past the endAfter, quit - last RECURRENCE - if ($recur->{endAfter} && keys %date >= $recur->{endAfter}); - } - - # Add interval - $dt->add(weeks => $recur->{every}); - - # Test for quit - if (($recur->{endAfter} && keys %date >= $recur->{endAfter}) || ($dt_end && $dt > $dt_end)) { - last RECURRENCE; - } - - # Next - next RECURRENCE; - - } - ####### monthday - elsif ($recur->{recurType} eq "monthDay") { - # Pick out the correct day - my $startDate = $dt->year."-".$dt->month."-".$recur->{dayNumber}; - - my $dt_day = WebGUI::DateTime->new($self->session, $startDate." 00:00:00"); - - # Only if today is not before the recurrence start - if ($dt_day->clone->truncate(to => "day") >= $dt_start->clone->truncate(to=>"day")) { - # If today is past the endDate, quit. - last RECURRENCE - if ($recur->{endDate} && $dt_day > $dt_end); - - ### Add date - $date{$dt_day->strftime('%F')}++; - } - - # Add interval - $dt->add(months => $recur->{every})->truncate(to => "month"); - - # Test for quit - if (($recur->{endAfter} && keys %date >= $recur->{endAfter}) || ($dt_end && $dt > $dt_end)) { - last RECURRENCE; - } - - # Next - next RECURRENCE; - } - ###### monthweek - elsif ($recur->{recurType} eq "monthWeek") { - # For each week remaining in this month - my $dt_week = $dt->clone; - while ($dt->month eq $dt_week->month) { - my $week = int($dt_week->day_of_month / 7); - - if (grep /$weeks{$week}/i, @{$recur->{weeks}}) { - # Pick out the correct days - for (0..6) { # Work through the week - my $dt_day = $dt_week->clone->add(days => $_); - - # If today is past the endDate, quit. - last RECURRENCE - if ($recur->{endDate} && $dt_day > $dt_end); - - # If today isn't in the month, stop looking - last if ($dt_day->month ne $dt->month); - - my $today = $dayNames{ $dt_day->day_of_week }; - - if (grep /$today/i, @{$recur->{dayNames}}) - { - ### Add date - $date{$dt_day->strftime('%F')}++; - } - - # If occurrences is past the endAfter, quit - last RECURRENCE - if ($recur->{endAfter} && keys %date >= $recur->{endAfter}); - } - } - - # Add a week - $dt_week->add(days => 7); - } - - ### If last is selected - if (grep /last/, @{$recur->{weeks}}) { - my $dt_last = $dt->clone->truncate(to => "month") - ->add(months => 1)->subtract(days => 1); - - for (0..6) { - my $dt_day = $dt_last->clone->subtract(days => $_); - - # If today is before the startDate, don't even bother - last if ($dt_day < $dt_start); - # If today is past the endDate, try the next one - next if ($recur->{endDate} && $dt_day > $dt_end); - - my $today = $dayNames{ $dt_day->day_of_week }; - - if (grep /$today/i, @{$recur->{dayNames}}) { - ### Add date - $date{$dt_day->strftime('%F')}++; - } - - # If occurrences is past the endAfter, quit - last RECURRENCE - if ($recur->{endAfter} && keys %date >= $recur->{endAfter}); - } - } - - - # Add interval - $dt->add(months => $recur->{every})->truncate(to => "month"); - - # Test for quit - if (($recur->{endAfter} && keys %date >= $recur->{endAfter}) || ($dt_end && $dt > $dt_end)) { - last RECURRENCE; - } - - # Next - next RECURRENCE; - } - ####### yearday - elsif ($recur->{recurType} eq "yearDay") { - # For each month - my $dt_month = $dt->clone; - while ($dt->year eq $dt_month->year) { - my $mon = $dt_month->month_abbr; - if (grep /$mon/i, @{$recur->{months}}) { - # Pick out the correct day - my $startDate = $dt_month->year."-".$dt_month->month."-".$recur->{dayNumber}; - - my $dt_day = WebGUI::DateTime->new($self->session, $startDate." 00:00:00"); - - # Only if today is not before the recurrence start - if ($dt_day->clone->truncate(to => "day") >= $dt_start->clone->truncate(to=>"day")) { - # If today is past the endDate, quit. - last RECURRENCE - if ($recur->{endDate} && $dt_day > $dt_end); - - ### Add date - $date{$dt_day->strftime('%F')}++; - - } - - # If occurrences is past the endAfter, quit - last RECURRENCE - if ($recur->{endAfter} && keys %date >= $recur->{endAfter}); - } - - $dt_month->add(months=>1); - } - - # Add interval - $dt->add(years => $recur->{every})->truncate(to => "year"); - - # Test for quit - if (($recur->{endAfter} && keys %date >= $recur->{endAfter}) || ($dt_end && $dt > $dt_end)) { - last RECURRENCE; - } - - # Next - next RECURRENCE; - } - ####### yearweek - elsif ($recur->{recurType} eq "yearWeek") { - # For each month - my $dt_month = $dt->clone; - while ($dt->year eq $dt_month->year) { - my $mon = $dt_month->month_abbr; - if (grep /$mon/i, @{$recur->{months}}) { - # For each week remaining in this month - my $dt_week = $dt_month->clone; - while ($dt_month->month eq $dt_week->month) { - my $week = int($dt_week->day_of_month / 7); - - if (grep /$weeks{$week}/i, @{$recur->{weeks}}) { - for (0..6) { # Work through the week - my $dt_day = $dt_week->clone->add(days => $_); - - # If today is past the endDate, quit. - last RECURRENCE - if ($recur->{endDate} && $dt_day > $dt_end); - - # If today isn't in the month, stop looking - last if ($dt_day->month ne $dt_month->month); - - my $today = $dayNames{ lc $dt_day->day_of_week }; - - if (grep /$today/i, @{$recur->{dayNames}}) { - ### Add date - $date{$dt_day->strftime('%F')}++; - } - - # If occurrences is past the endAfter, quit - last RECURRENCE - if ($recur->{endAfter} && keys %date >= $recur->{endAfter}); - } - } - - # Next week - $dt_week->add(days => 7); - } - - ### If last is selected - if (grep /last/, @{$recur->{weeks}}) { - my $dt_last = $dt_month->clone->add(months => 1)->subtract(days => 1); - - for (0..6) { - my $dt_day = $dt_last->clone->subtract(days => $_); - - # If today is past the endDate, try the next one - next - if ($recur->{endDate} && $dt_day > $dt_end); - - my $today = $dayNames{ $dt_day->day_of_week }; - - if (grep /$today/i, @{$recur->{dayNames}}) { - ### Add date - $date{$dt_day->strftime('%F')}++; - } - - # If occurrences is past the endAfter, quit - last RECURRENCE - if ($recur->{endAfter} && keys %date >= $recur->{endAfter}); - } - } - - } - - # Next month - $dt_month->add(months=>1); - } - - # Add interval - $dt->add(years => $recur->{every})->truncate(to => "year"); - - # Test for quit - if (($recur->{endAfter} && keys %date >= $recur->{endAfter}) || ($dt_end && $dt > $dt_end)) { - last RECURRENCE; - } - - # Next - next RECURRENCE; - } - } - - - return sort keys %date; + return + map { $_->strftime('%F') } + $self->dateSet($recur, $start, $end)->as_list; } - - - - #################################################################### =head2 getRecurrenceFromForm -Returns a recurrence hash based on the form parameters. +Returns a recurrence hashref based on the form parameters. The hash keys are the same as getRecurrence. @@ -1152,7 +926,7 @@ sub getRecurrenceFromForm { my %recurrence = (); my $type = lc $form->param("recurType"); - return () unless ($type && $type !~ /none/i); + return undef unless ($type && $type !~ /none/i); if ($type eq "daily") { if (lc($form->param("recurSubType")) eq "weekday") { @@ -1208,7 +982,7 @@ sub getRecurrenceFromForm { $recurrence{endAfter} = $form->param("recurEndAfter"); } - return %recurrence; + return \%recurrence; } @@ -1227,7 +1001,7 @@ sub getRelatedLinks { my $self = shift; my $sth - = $self->session->db->prepare( + = $self->session->db->prepare( "SELECT * FROM Event_relatedlink WHERE assetId=? ORDER BY sequenceNumber", ); $sth->execute([ $self->getId ]); @@ -1266,7 +1040,7 @@ sub getStorageLocation { =head2 getTemplateVars -Returns a hash of additional parameters to be used in templates, beyond the +Returns a hash of additional parameters to be used in templates, beyond the standard definition. Uses the current user's locale and timezone. @@ -1283,7 +1057,7 @@ sub getTemplateVars { $var{"isPublic"} = 1 if $self->get("groupIdView") eq "7"; $var{"groupToView"} = $self->get("groupIdView"); - $var{"timeZone"} = $self->get('timeZone'); + $var{"timeZone"} = $self->get('timeZone'); # Start date/time my $dtStart = $self->getDateTimeStart; @@ -1335,7 +1109,7 @@ sub getTemplateVars { ; # Make a Friendly date span. - $var{dateSpan} + $var{dateSpan} = $var{startDateDayName}.", " . $var{startDateMonthName}." " . $var{startDateDayOfMonth}; @@ -1371,7 +1145,7 @@ sub getTemplateVars { $var{ "urlSearch" } = $self->getParent->getSearchUrl; # Related links - $var{ relatedLinks } = $self->getRelatedLinks; + $var{ relatedLinks } = $self->getRelatedLinks; # Attachments my $gotImage; @@ -1391,7 +1165,7 @@ sub getTemplateVars { $var{ "attachment.icon" } = $storage->getFileIconUrl($filename); $var{ "attachment.name" } = $filename; $gotAttachment = 1; - } + } # All attachments get added to the loop push @{$var{"attachment_loop"}}, { @@ -1447,15 +1221,34 @@ sub isAllDay { return 0; } +#------------------------------------------------------------------- +=head2 limitedEndDate ( date ) +A datetime object for recurrence end or the supplied date, whichever comes +first. If there is a count for the recurrence, there is no end date +(counted recurrences are always fully generated). + +=cut + +sub limitedEndDate { + my ($self, $limit) = @_; + my $recur = $self->getRecurrence; + return undef if $recur->{endAfter}; + + my $natural = do { + my $mysql = $recur->{endDate}; + $mysql && WebGUI::DateTime->new($self->session, $mysql); + }; + return ($natural and $limit > $natural) ? $natural : $limit; +} #################################################################### =head2 prepareView -Prepares the view template to be used later. The template to be used is found +Prepares the view template to be used later. The template to be used is found from this asset's parent (Usually a Calendar). If the "print" form parameter is set, will prepare the print template. @@ -1573,13 +1366,13 @@ sub processPropertiesFromFormPost { my $tz = $self->get('timeZone'); my $dtStart - = WebGUI::DateTime->new($session, + = WebGUI::DateTime->new($session, mysql => $self->get("startDate") . " " . $self->get("startTime"), time_zone => $tz, ); my $dtEnd - = WebGUI::DateTime->new($session, + = WebGUI::DateTime->new($session, mysql => $self->get("endDate") . " " . $self->get("endTime"), time_zone => $tz, ); @@ -1663,23 +1456,18 @@ sub processPropertiesFromFormPost { # Determine if the pattern has changed if ($form->param("recurType")) { # Create the new recurrence hash - my %recurrence_new = $self->getRecurrenceFromForm; + my $recurrence_new = $self->getRecurrenceFromForm; # Get the old recurrence hash and range - my %recurrence_old = $self->getRecurrence; - - - # Set storable to canonical so that we can compare data structures - local $Storable::canonical = 1; - + my $recurrence_old = $self->getRecurrence; # Pattern keys - if (Storable::freeze(\%recurrence_new) ne Storable::freeze(\%recurrence_old)) { + if (!eq_deeply($recurrence_new, $recurrence_old)) { # Delete all old events and create new ones my $old_id = $self->get("recurId"); # Set the new recurrence pattern - if (%recurrence_new) { - my $new_id = $self->setRecurrence(\%recurrence_new); + if ($recurrence_new) { + my $new_id = $self->setRecurrence($recurrence_new); if (! $new_id) { $activeVersionTag->setWorking if $activeVersionTag; @@ -1777,7 +1565,7 @@ sub purgeRevision { =head2 setRecurrence ( hashref ) -Sets a hash of recurrence information to the database. The hash keys are the +Sets a hash of recurrence information to the database. The hash keys are the same as the ones in getRecurrence() This will always create a new row in the recurrence table. @@ -1845,14 +1633,12 @@ sub setRecurrence { ## Set to the database ## Return the new recurId my $recurId = $self->session->db->setRow("Event_recur","recurId",$data); - $self->update({recurId => $recurId}); + $self->update({recurId => $recurId}); + + $self->{recurrence} = $vars; return $recurId; } - - - - #################################################################### =head2 setRelatedLinks ( links ) @@ -1996,7 +1782,7 @@ sub www_edit { my $func = lc $session->form->param("func"); my $var = {}; - return $self->session->privilege->noAccess() unless $self->getParent->canAddEvent(); + return $self->session->privilege->noAccess() unless $self->getParent->canAddEvent(); if ($func eq "add" || $form->param("assetId") eq "new") { $var->{"formHeader"} @@ -2018,7 +1804,7 @@ sub www_edit { ; } else { - $var->{"formHeader"} + $var->{"formHeader"} = WebGUI::Form::formHeader($session, { action => $self->getUrl, }) @@ -2055,7 +1841,7 @@ sub www_edit { }); # menu title AS short title - $var->{"formMenuTitle"} + $var->{"formMenuTitle"} = WebGUI::Form::text($session, { name => "menuTitle", value => $form->process("menuTitle") || $self->get("menuTitle"), @@ -2064,14 +1850,14 @@ sub www_edit { }); # synopsis - $var->{"formSynopsis"} + $var->{"formSynopsis"} = WebGUI::Form::textarea($session, { name => "synopsis", value => $form->process("synopsis") || $self->get("synopsis"), }); # Group to View - $var->{"formGroupIdView"} + $var->{"formGroupIdView"} = WebGUI::Form::Group($session, { name => "groupIdView", value => $form->process("groupIdView") || $self->get("groupIdView"), @@ -2131,8 +1917,8 @@ sub www_edit { # Try to get a default start date from the form if ($session->form->param("start")) { - $default_start - = WebGUI::DateTime->new($session, + $default_start + = WebGUI::DateTime->new($session, mysql => $session->form->param("start"), time_zone => $tz, ); @@ -2160,7 +1946,7 @@ sub www_edit { value => $form->param("startDate") || $startDate, defaultValue => $default_start->toUserTimeZoneDate, }); - $var->{"formStartTime"} + $var->{"formStartTime"} = WebGUI::Form::timeField($session, { name => "startTime", value => $form->param("startTime") || $startTime, @@ -2184,19 +1970,19 @@ sub www_edit { } } - $var->{"formEndDate"} + $var->{"formEndDate"} = WebGUI::Form::date($session, { name => "endDate", value => $form->param("endDate") || $endDate, defaultValue => $default_end->toUserTimeZoneDate, }); - $var->{"formEndTime"} + $var->{"formEndTime"} = WebGUI::Form::timeField($session, { name => "endTime", value => $form->param("endTime") || $endTime, defaultValue => $default_end->toUserTimeZoneTime, }); - $var->{"formTimeZone"} + $var->{"formTimeZone"} = WebGUI::Form::TimeZone($session, { name => "timeZone", value => $tz, @@ -2205,7 +1991,7 @@ sub www_edit { # time my $allday = defined $form->param("allday") - ? $form->param("allday") + ? $form->param("allday") : $self->isAllDay ; @@ -2259,130 +2045,130 @@ sub www_edit { ###### Recurrence tab # Pattern - my %recur = $self->getRecurrenceFromForm || $self->getRecurrence; - $recur{every} ||= 1; + my $recur = $self->getRecurrenceFromForm || $self->getRecurrence; + $recur->{every} ||= 1; - $var->{"formRecurPattern"} + $var->{"formRecurPattern"} = q|

-

+

{recurType} =~ /^(daily|weekday)$/ ? q|checked="checked"| : q||).q|/>

- |. $i18n->get('Every'). q|
- + |. $i18n->get('Every'). q|
+ {recurType} eq "daily" ? q|checked="checked"| : q||).q|/>
- + {recurType} eq "weekday" ? q|checked="checked"| : q||).q|/>
-

+

{recurType} eq "weekly" ? q|checked="checked"| : q||).q|/>

- |. sprintf($i18n->get('Every N weeks on'), q||). q|
- + |. sprintf($i18n->get('Every N weeks on'), q||). q|
+ {dayNames}}) ? 'checked="checked"' : '' ).q|/>
- + {dayNames}}) ? 'checked="checked"' : '' ).q|/>
- + {dayNames}}) ? 'checked="checked"' : '' ).q|/>
- + {dayNames}}) ? 'checked="checked"' : '' ).q|/>
- + {dayNames}}) ? 'checked="checked"' : '' ).q|/>
- + {dayNames}}) ? 'checked="checked"' : '' ).q|/>
- + {dayNames}}) ? 'checked="checked"' : '' ).q|/>
-

+

{recurType} =~ /^month/ ? q|checked="checked"| : q||).q|/>

-

|. sprintf($i18n->get('Every N months on'), q||). q|

-

+

|. sprintf($i18n->get('Every N months on'), q||). q|

+

{recurType} eq "monthDay" ? q|checked="checked"| : q||).q|/> -

+

- + {recurType} eq "monthWeek" ? q|checked="checked"| : q||).q|/> |. $i18n->get('week on'). q|

-

+

{recurType} =~ /^year/ ? q|checked="checked"| : q||).q|/>

-

|. sprintf($i18n->get('Every N years on'),q||). q|

+

|. sprintf($i18n->get('Every N years on'),q||). q|

- + {recurType} eq "yearDay" ? q|checked="checked"| : q||).q|/> - +

- + {recurType} eq "yearWeek" ? q|checked="checked"| : q||).q|/> + + + + + + |. $i18n->get('of'). q|

@@ -2391,28 +2177,28 @@ sub www_edit { # Start - $var->{"formRecurStart"} + $var->{"formRecurStart"} = WebGUI::Form::date($session, { name => "recurStart", - value => $recur{startDate}, + value => $recur->{startDate}, defaultValue => $self->get("startDate"), }); # End - $var->{"formRecurEnd"} + $var->{"formRecurEnd"} = q| -
+
{endDate} && !$recur->{endAfter} ? 'checked="checked"' : '').q|/>
- + {endDate} ? 'checked="checked"' : '' ).q| /> | - . WebGUI::Form::date($session,{ name => "recurEndDate", value => $recur{endDate}, defaultValue => $recur{endDate} }) + . WebGUI::Form::date($session,{ name => "recurEndDate", value => $recur->{endDate}, defaultValue => $recur->{endDate} }) . q|
- + {endAfter} ? 'checked="checked"' : '' ).q| /> - + |. $i18n->get('occurences'). q|.
|; @@ -2495,11 +2281,11 @@ ENDJS my $parent = $self->getParent; my $template; if ($parent) { - $template + $template = WebGUI::Asset::Template->new($session,$parent->get("templateIdEventEdit")); } else { - $template + $template = WebGUI::Asset::Template->new($session,"CalendarEventEdit00001"); } @@ -2543,7 +2329,7 @@ sub www_view { my $check = $self->checkView; return $check if (defined $check); $self->session->http->setCacheControl($self->get("visitorCacheTimeout")) if ($self->session->user->isVisitor); - $self->session->http->sendHeader; + $self->session->http->sendHeader; $self->prepareView; my $style = $self->getParent->processStyle($self->getSeparator); my ($head, $foot) = split($self->getSeparator,$style); @@ -2560,19 +2346,19 @@ sub www_view { Pages for Next Occurence >> and Prev Occurrence << on the Event Details page -Shared package global to set how many user defined fields there are. +Shared package global to set how many user defined fields there are. Fix the Recurrence form. Use WebGUI::Form elements and combine them to create -the form.recurPattern field. If users want to create their own way to make the +the form.recurPattern field. If users want to create their own way to make the pattern, let them. Fix the Recurrence storage. Add DateTime::Event::ICal and dependencies to WebGUI -and use ICal recurrence rules. Why did I not do this before? +and use ICal recurrence rules. Why did I not do this before? When sending ICalendar feeds, send the Recurrence Rule. Currently I'm not going to be able to do that. -Recurring events should be created by the commit process, so that it's done +Recurring events should be created by the commit process, so that it's done asynchronously with Spectre rather than making the browser wait for a long time (to make many many events). diff --git a/lib/WebGUI/Workflow/Activity/ExtendCalendarRecurrences.pm b/lib/WebGUI/Workflow/Activity/ExtendCalendarRecurrences.pm new file mode 100644 index 000000000..29aacee8a --- /dev/null +++ b/lib/WebGUI/Workflow/Activity/ExtendCalendarRecurrences.pm @@ -0,0 +1,185 @@ +package WebGUI::Workflow::Activity::ExtendCalendarRecurrences; + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2009 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; +use warnings; + +use base 'WebGUI::Workflow::Activity'; +use WebGUI::International; +use WebGUI::Asset; +use DateTime; + +=head1 NAME + +WebGUI::Workflow::Activity::ExtendCalendarRecurrences + +=head1 DESCRIPTION + +Generates events for all active calendar recurring events up to 2 years in the +future. + +=head1 SYNOPSIS + +See WebGUI::Workflow::Activity for details on how to use any activity. + +=head1 METHODS + +These methods are available from this class: + +=cut + +#------------------------------------------------------------------- + +=head2 definition ( session, definition ) + +See WebGUI::Workflow::Activity::definition() for details. + +=cut + +sub definition { + my ( $class, $session, $definition ) = @_; + my $i18n = WebGUI::International->new( $session, 'Activity_ExtendCalendarRecurrences' ); + push( @$definition, { name => $i18n->get('topicName') } ); + return $class->SUPER::definition( $session, $definition ); +} + +#------------------------------------------------------------------- + +=head2 execute ( [ object ] ) + +See WebGUI::Workflow::Activity::execute() for details. + +=cut + +sub execute { + my ( $self, $obj, $instance ) = @_; + my $timeLimit = time + 55; + + my $piped = $instance->getScratch('recurrences') + || $self->generateRecurrenceList(); + + while (time < $timeLimit ) { + return $self->COMPLETE unless $piped; + my ( $recurId, $rest ) = split /\|/, $piped, 2; + + $self->processRecurrence( $recurId, $timeLimit ) + and $piped = $rest; + } + + $instance->setScratch( recurrences => $piped ); + return $self->WAITING(1); +} ## end sub execute + +#------------------------------------------------------------------- + +=head2 findRecurrenceIds ( calendarId ) + +Find all recurIds for the given calendarId. + +=cut + +sub findRecurrenceIds { + my ( $self, $calendarId ) = @_; + my $sql = q{ + SELECT r.recurId + FROM Event_recur r + INNER JOIN Event e ON r.recurId = e.recurId + INNER JOIN asset a ON e.assetId = a.assetId + WHERE a.parentId = ? + }; + return $self->session->db->buildArrayRef( $sql, [ $calendarId ] ); +} + +#------------------------------------------------------------------- + +=head2 findCalendarIds + +Returns an arrayref of assetIds for all Calendar assets in the asset tree. + +=cut + +sub findCalendarIds { + my $self = shift; + my $root = WebGUI::Asset->getRoot( $self->session ); + return $root->getLineage( ['descendants'], { includeOnlyClasses => ['WebGUI::Asset::Wobject::Calendar'] } ); +} + +#------------------------------------------------------------------- + +=head2 findLastEventId ( recurId ) + +Returns the assetId of the most WebGUI::Asset::Event generated for the given +recurrence. + +=cut + +sub findLastEventId { + my ( $self, $recurId ) = @_; + my $sql = q{ + SELECT assetId + FROM Event + WHERE recurId = ? + ORDER BY startDate + LIMIT 1 + }; + return $self->session->db->quickScalar( $sql, [$recurId] ); +} + +#------------------------------------------------------------------- + +=head2 generateRecurrenceList () + +Returns a string of pipe-seperated recurrence IDs for all the calendars in the +asset tree. This is called exactly once per workflow instance. + +=cut + +sub generateRecurrenceList { + my $self = shift; + return join( '|', map { @{ $self->findRecurrenceIds($_) } } @{ $self->findCalendarIds } ); +} + +#------------------------------------------------------------------- + +=head2 processRecurrence (recurId, timeLimit) + +Generates as many WebGUI::Asset::Event objects as it can before timeLimit is +up or the recurrence is finished for the given recurId. Returns true if it +exhausted the recurrence, false otherwise. + +=cut + +sub processRecurrence { + my ( $self, $recurId, $timeLimit ) = @_; + my $eventId = $self->findLastEventId($recurId); + my $event = WebGUI::Asset::Event->new( $self->session, $eventId ); + my $recur = $event->getRecurrence; + + my $start = $event->getDateTimeStart->truncate(to => 'day'); + my $limit = DateTime->today->add( years => 2 ); + my $end = $event->limitedEndDate($limit); + my $set = $event->dateSet( $recur, $start, $end ); + my $i = $set->iterator; + + while ( my $d = $i->next ) { + return if ( time > $timeLimit ); + $event->generateRecurrence($d); + } + + return 1; +} ## end sub processRecurrence + +1; diff --git a/lib/WebGUI/i18n/English/Activity_ExtendCalendarRecurrences.pm b/lib/WebGUI/i18n/English/Activity_ExtendCalendarRecurrences.pm new file mode 100644 index 000000000..df8c7e2f1 --- /dev/null +++ b/lib/WebGUI/i18n/English/Activity_ExtendCalendarRecurrences.pm @@ -0,0 +1,14 @@ +package WebGUI::i18n::English::Activity_ExtendCalendarRecurrences; + +use strict; + +our $I18N = { + 'topicName' => { + message => q{Extend Calendar Recurrences}, + lastUpdated => 1270240660, + }, +}; + +1; + +#vim:ft=perl diff --git a/sbin/testEnvironment.pl b/sbin/testEnvironment.pl index 027d94129..624498e98 100755 --- a/sbin/testEnvironment.pl +++ b/sbin/testEnvironment.pl @@ -138,6 +138,7 @@ checkModule("Readonly", "1.03" ); checkModule("Business::PayPal::API", "0.62" ); checkModule("Locales", "0.10" ); checkModule("Test::Harness", "3.17" ); +checkModule("DateTime::Event::ICal", "0.10" ); failAndExit("Required modules are missing, running no more checks.") if $missingModule; diff --git a/t/Asset/Event/recur.t b/t/Asset/Event/recur.t new file mode 100644 index 000000000..29efce18d --- /dev/null +++ b/t/Asset/Event/recur.t @@ -0,0 +1,81 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------ +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------ + +# Tests the recurrence functionality of calendar events + +use strict; + +use FindBin; +use lib "$FindBin::Bin/../../../lib"; + +use Test::More; +use DateTime; +use WebGUI::Asset::Event; + +my $startDate = DateTime->new( + year => 2000, + month => 1, + day => 1 +); + +sub recur { + my ($type, $interval, $count, @extra) = @_; + my $r = { + recurType => $type, + every => $interval, + endAfter => $count, + @extra, + }; + return [ + map {$_->ymd} WebGUI::Asset::Event->dateSet($r, $startDate)->as_list + ] +} + +is_deeply recur(daily => 3 => 5), [ + '2000-01-01', '2000-01-04', '2000-01-07', '2000-01-10', '2000-01-13', +]; + +is_deeply recur(weekday => 3 => 5), [ + '2000-01-05', '2000-01-10', '2000-01-13', '2000-01-18', '2000-01-21', +]; + +is_deeply recur(weekly => 2 => 10 => dayNames => [qw(m w f)]), [ + '2000-01-10', '2000-01-12', '2000-01-14', + '2000-01-24', '2000-01-26', '2000-01-28', + '2000-02-07', '2000-02-09', '2000-02-11', '2000-02-21' +]; + +is_deeply recur(monthDay => 3 => 4 => dayNumber => 4), [ + '2000-01-04', '2000-04-04', '2000-07-04', '2000-10-04' +]; + +is_deeply recur(monthWeek => 1 => 6 => dayNames => ['w'], weeks => ['fifth']), [ + '2000-01-26', '2000-02-23', '2000-03-29', + '2000-04-26', '2000-05-31', '2000-06-28', +]; + +is_deeply recur(yearDay => 2 => 3, months => ['feb'], dayNumber => 2), [ + '2000-02-02', '2002-02-02', '2004-02-02', +]; + +my %labor_day = ( + months => ['sep'], + weeks => ['first'], + dayNames => ['m'], +); +is_deeply recur(yearWeek => 3 => 3, %labor_day), [ + '2000-09-04', '2003-09-01', '2006-09-04' +]; + +done_testing; + +#---------------------------------------------------------------------------- +#vim:ft=perl diff --git a/t/Workflow/Activity/ExtendCalendarRecurrences.t b/t/Workflow/Activity/ExtendCalendarRecurrences.t new file mode 100644 index 000000000..0d25871b5 --- /dev/null +++ b/t/Workflow/Activity/ExtendCalendarRecurrences.t @@ -0,0 +1,102 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------ +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------ + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; +use lib "$FindBin::Bin/../../t/lib"; +use Test::More; +use WebGUI::Test; +use WebGUI::Session; +use WebGUI::Workflow::Activity::ExtendCalendarRecurrences; +use DateTime; +use Data::Dumper; + +my $session = WebGUI::Test->session; +my $temp = WebGUI::Asset->getTempspace($session); + +my $tag = WebGUI::VersionTag->getWorking($session); +WebGUI::Test::addToCleanup($tag); + +my $calendar = $temp->addChild( + { className => 'WebGUI::Asset::Wobject::Calendar' } +); + +my $one_year_ago = DateTime->today->subtract(years => 1)->ymd; + +my $event = $calendar->addChild( + { className => 'WebGUI::Asset::Event', + startDate => $one_year_ago, + endDate => $one_year_ago, + } +); + +my $recurId = $event->setRecurrence( + { recurType => 'monthDay', + every => 2, + startDate => $event->get('startDate'), + dayNumber => DateTime->today->day, + } +); + +my $workflow = WebGUI::Workflow->create( + $session, { + enabled => 1, + objectType => 'None', + mode => 'realtime', + }, +); + +WebGUI::Test::addToCleanup($workflow); + +my $activity = +$workflow->addActivity('WebGUI::Workflow::Activity::ExtendCalendarRecurrences'); + +my $calendars = [ $calendar->getId ]; +{ + # We only want to be testing our calendar, not any others in the asset + # tree. + no warnings 'redefine'; + sub WebGUI::Workflow::Activity::ExtendCalendarRecurrences::findCalendarIds { + return $calendars; + } +} + +is $activity->findCalendarIds, $calendars, 'mocking worked'; + +my $instance = WebGUI::Workflow::Instance->create( + $session, { + workflowId => $workflow->getId, + skipSpectreNotification => 1, + } +); + + +while (my $status = $instance->run ne 'complete') { + note $status; + $instance->run; +} + +my $sql = q{ + select e.startDate, e.endDate + from asset a + inner join event e on e.assetId = a.assetId + and a.parentId = ? + order by e.startDate +}; + +my $dates = $session->db->buildArrayRefOfHashRefs($sql, [$calendar->getId]); +# 3 years at every other month (6 times) plus the one we started with +is(@$dates, 19) or diag Dumper $dates; + +done_testing; + +#vim:ft=perl