diff --git a/docs/upgrades/templates-7.3.0/default-calendar-day.tmpl b/docs/upgrades/templates-7.3.0/default-calendar-day.tmpl new file mode 100644 index 000000000..670676a3c --- /dev/null +++ b/docs/upgrades/templates-7.3.0/default-calendar-day.tmpl @@ -0,0 +1,280 @@ +#CalendarDay00000000001 +#create +#namespace:Calendar/Day +#url:default-calendar-day +#title:Default Calendar Day +#menuTitle:Default Calendar Day + + +

+
+ + +

+
+ + +

+
+ + + + + + + + + + + + + + +
+ Day + Week + Month + Search + +
+ Add Event + +
+
+
+ « Previous Day + • + + , . • + Next Day » +
+
+
+ , , +
+
+ + + + + +
+
:00
+
+
    +
  • + +
  • +
+
+
+~~~ + diff --git a/docs/upgrades/templates-7.3.0/default-calendar-event-edit.tmpl b/docs/upgrades/templates-7.3.0/default-calendar-event-edit.tmpl new file mode 100644 index 000000000..15fdbf106 --- /dev/null +++ b/docs/upgrades/templates-7.3.0/default-calendar-event-edit.tmpl @@ -0,0 +1,167 @@ +#CalendarEventEdit00001 +#create +#namespace:Calendar/EventEdit +#url:default-calendar-event-edit +#title:Default Calendar Event Edit +#menuTitle:Default Calendar Event Edit + + + + +

Errors!

+ +
+ + + + + +
+ Events + Recurrence + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Event Title
Short Title
Location
Description
Start Date
End Date
Time
 
Related Links
+
+ + + + + + + + + + + + + + + + + +~~~ + + + + + + + + + + + + + + diff --git a/docs/upgrades/templates-7.3.0/default-calendar-event.tmpl b/docs/upgrades/templates-7.3.0/default-calendar-event.tmpl new file mode 100644 index 000000000..19e07a590 --- /dev/null +++ b/docs/upgrades/templates-7.3.0/default-calendar-event.tmpl @@ -0,0 +1,220 @@ +#CalendarEvent000000001 +#create +#namespace:Calendar/Event +#url:default-calendar-event +#title:Default Calendar Event +#menuTitle:Default Calendar Event + + + + + + + + + + + + + + +
+ Day + Week + Month + Search + +
+ EditDelete +
+
+ + +
+ Event Details +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
Event Title
+
+
+ +
+
+
Location
+
+
+ +
+
+
Description
+
+
+
+
Scheduled
+
+
+ , + + + : , + + + : +
+
+
Related Material
+
+
+
+
+
+ +~~~ + + diff --git a/docs/upgrades/templates-7.3.0/default-calendar-month.tmpl b/docs/upgrades/templates-7.3.0/default-calendar-month.tmpl new file mode 100644 index 000000000..825a58f20 --- /dev/null +++ b/docs/upgrades/templates-7.3.0/default-calendar-month.tmpl @@ -0,0 +1,388 @@ +#CalendarMonth000000001 +#create +#namespace:Calendar/Month +#url:default-calendar-month +#title:Default Calendar Month +#menuTitle:Default Calendar Month + +

+
+ + +

+
+ + + + + +
+ +
+ Day + Week + Month + Search +
+ + +
+ Add Event + +
+ + + + + + +

+ + + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ +~~~ + + diff --git a/docs/upgrades/templates-7.3.0/default-calendar-search.tmpl b/docs/upgrades/templates-7.3.0/default-calendar-search.tmpl new file mode 100644 index 000000000..895dbb1af --- /dev/null +++ b/docs/upgrades/templates-7.3.0/default-calendar-search.tmpl @@ -0,0 +1,316 @@ +#CalendarSearch00000001 +#create +#namespace:Calendar/Search +#url:default-calendar-search +#title:Default Calendar Search +#menuTitle:Default Calendar Search + + + + + + + + + + + + + + + +
+ Day + Week + Month + Search +
+   +
+ + + + + + + + + + + + + + + + +
+
Keyword
+
+ +
+
Start Date
+
+ +
+
End Date
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + +
+
+ Search Results + Displaying page of +
+
+
+ + + +
+
+ + + + + +
+
+
+ +
+
+
+ + + +
+
+
+ + +~~~ + diff --git a/docs/upgrades/templates-7.3.0/default-calendar-week.tmpl b/docs/upgrades/templates-7.3.0/default-calendar-week.tmpl new file mode 100644 index 000000000..b0ef427c5 --- /dev/null +++ b/docs/upgrades/templates-7.3.0/default-calendar-week.tmpl @@ -0,0 +1,263 @@ +#CalendarWeek0000000001 +#create +#namespace:Calendar/Week +#url:default-calendar-week +#title:Default Calendar Week +#menuTitle:Default Calendar Week + + +

+
+ + +

+
+ + + + + +
+ +
+ Day + Week + Month + Search +
+ + +
+ Add Event + +
+ + + + + + +

, to ,

+ + + + + + + + +
+
+
    +
  • +
+
+~~~ + diff --git a/docs/upgrades/upgrade_7.2.3-7.3.0.pl b/docs/upgrades/upgrade_7.2.3-7.3.0.pl index 4d1b42a7c..8ea5f6a08 100644 --- a/docs/upgrades/upgrade_7.2.3-7.3.0.pl +++ b/docs/upgrades/upgrade_7.2.3-7.3.0.pl @@ -23,6 +23,9 @@ addWikiAssets($session); deleteOldFiles($session); addFileFieldsToDataForm($session); makeRSSFromParentAlwaysHidden($session); +#addNewCalendar($session); +#migrateCalendars($session); +#removeOldCalendar($session); finish($session); # this line required #------------------------------------------------- @@ -129,6 +132,176 @@ EOT ); } + +sub addNewCalendar { + my $session = shift; + print "\tCreating Calendar and Event tables.\n" unless $quiet; + + $session->db->write($_) for (<<'ENDSQL', +CREATE TABLE `Event` ( + `assetId` varchar(22) NOT NULL, + `revisionDate` bigint(20) unsigned NOT NULL, + `feedId` varchar(22) default NULL, + `startDate` date default NULL, + `endDate` date default NULL, + `userDefined1` text, + `userDefined2` text, + `userDefined3` text, + `userDefined4` text, + `userDefined5` text, + `recurId` varchar(22) default NULL, + `description` longtext, + `startTime` time default NULL, + `endTime` time default NULL, + `relatedLinks` longtext, + `location` varchar(255) default NULL, + PRIMARY KEY (`assetId`,`revisionDate`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 +ENDSQL + <<'ENDSQL', +CREATE TABLE `Calendar` ( + `assetId` varchar(22) NOT NULL, + `revisionDate` bigint(20) unsigned NOT NULL default '0', + `defaultDate` enum('current','first','last') default "current", + `defaultView` enum('month','week','day') default "month", + `visitorCacheTimeout` int(11) unsigned default NULL, + `templateIdMonth` varchar(22) default "CalendarMonth000000001", + `templateIdWeek` varchar(22) default "CalendarWeek0000000001", + `templateIdDay` varchar(22) default "CalendarDay00000000001", + `templateIdEvent` varchar(22) default "CalendarEvent000000001", + `templateIdEventEdit` varchar(22) default "CalendarEventEdit00001", + `templateIdSearch` varchar(22) default "CalendarSearch00000001", + `templateIdPrintMonth` varchar(22) default "CalendarPrintMonth0001", + `templateIdPrintWeek` varchar(22) default "CalendarPrintWeek00001", + `templateIdPrintDay` varchar(22) default "CalendarPrintDay000001", + `templateIdPrintEvent` varchar(22) default "CalendarPrintEvent0001", + `groupIdEventEdit` varchar(22) default "3", + `groupIdSubscribed` varchar(22) default NULL, + `subscriberNotifyOffset` int(11) default NULL, + PRIMARY KEY (`assetId`,`revisionDate`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 +ENDSQL + <<'ENDSQL', +CREATE TABLE `Event_recur` ( + `recurId` varchar(22) NOT NULL, + `recurType` varchar(16) default NULL, + `pattern` varchar(255) default NULL, + `startDate` date default NULL, + `endDate` varchar(10) default NULL, + PRIMARY KEY (`recurId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8 +ENDSQL + <<'ENDSQL', +CREATE TABLE `Calendar_feeds` ( + `feedId` varchar(22) NOT NULL, + `assetId` varchar(22) NOT NULL, + `url` varchar(255) NOT NULL, + `lastUpdated` int(16) default NULL, + `lastResult` varchar(255) default NULL, + `feedType` varchar(30) NOT NULL, + PRIMARY KEY (`feedId`,`assetId`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; +ENDSQL +); + + $session->config->addToArray('assets', 'WebGUI::Asset::Wobject::Calendar'); +} + + +sub migrateCalendars { + my $session = shift; + + print "\tMigrating EventsCalendar to Calendar wobjects.\n" unless $quiet; + use WebGUI::DateTime; + + # For every EventsCalendar + #EventsCalendar.defaultMonth = Calendar.defaultDate + my $calendars = WebGUI::Asset->getRoot($session)->getLineage(['descendents'], + { + includeOnlyClasses => ['WebGUI::Asset::Wobject::EventsCalendar'], + returnObjects => 1, + }); + + for my $asset (@{$calendars}) + { + my $properties = {%{$asset->get}}; + $properties->{defaultDate} = delete $properties->{defaultMonth}; + warn "Found calendar ".$properties->{title}; + $properties->{className} = "WebGUI::Asset::Wobject::Calendar"; + + + # Add the new asset + my $newAsset = $asset->getParent->addChild($properties); + warn "Added Calendar ".$newAsset->get("title")." ".$newAsset->get("className"); + + # Get this calendar's events and change to new parent + my $events = $asset->getLineage(['descendants'], + { + includeOnlyClasses => ['WebGUI::Asset::Event'], + }); + warn "Got lineage"; + + + # Set the new asset's rank + $newAsset->swapRank($asset->getRank); + warn "Swapped rank"; + + + for my $event (@{$events}) + { + warn "Got event: $event"; + + # Add a child to the new calendar using the properties + # from EventsCalendar_event + my %eventProperties = $session->db->quickHash("select * from asset left join assetData on asset.assetId=assetData.assetId left join EventsCalendar_event on asset.assetId = EventsCalendar_event.assetId where asset.assetId = ?",[$event]); + + my ($startDate, $startTime) = split / /, WebGUI::DateTime->new(delete $eventProperties{eventStartDate})->toMysql; + my ($endDate, $endTime) = split / /, WebGUI::DateTime->new(delete $eventProperties{eventEndDate})->toMysql; + + $eventProperties{startDate} = $startDate; + $eventProperties{startTime} = $startTime; + $eventProperties{endDate} = $endDate; + $eventProperties{endTime} = $endTime; + #use Data::Dumper; + #warn Dumper \%eventProperties; + + $newAsset->addChild(\%eventProperties); + + # Remove this event from the old calendar + #$session->db->write("delete from EventsCalendar_event where assetId=?",[$event]); + #$session->db->write("delete from asset where assetId=?",[$event]); + #$session->db->write("delete from assetData where assetId=?",[$event]); + #$session->db->write("delete from assetIndex where assetId=?",[$event]); + #$session->db->write("delete from assetHistory where assetId=?",[$event]); + } + warn "Set parents on events"; + + + # Remove the old asset + $asset->purge; + warn "Purged old calendar"; + } +} + + +sub removeOldCalendar { + my $session = shift; + print "\tRemoving old EventsCalendar tables, templates, .\n" unless $quiet; + + # Remove tables + $session->db->write("drop table EventsCalendar"); + $session->db->write("drop table EventsCalendar_event"); + + # Remove Plainblack's EventsCalendar / Events templates + #PBtmpl0000000000000022 + WebGUI::Asset->newByDynamicClass($session,"PBtmpl0000000000000022")->purge; + #PBtmpl0000000000000023 + WebGUI::Asset->newByDynamicClass($session,"PBtmpl0000000000000023")->purge; + + $session->config->deleteFromArray("assets","WebGUI::Asset::Wobject::EventsCalendar"); +} + + # ---- DO NOT EDIT BELOW THIS LINE ---- #------------------------------------------------- @@ -175,7 +348,7 @@ sub updateTemplates { my %properties = (className=>"WebGUI::Asset::Template"); while (my $line = ) { if ($first) { - $line =~ m/^\#(.*)$/; + $line =~ m/^\#(.{0,22})$/; $properties{id} = $1; $first = 0; } elsif ($line =~ m/^\#create$/) { @@ -191,7 +364,7 @@ sub updateTemplates { } } close(FILE); - if ($create) { + if ($create && !WebGUI::Asset->newByDynamicClass($session,$properties{id})) { $newFolder = createNewTemplatesFolder($importNode) unless (defined $newFolder); my $template = $newFolder->addChild(\%properties, $properties{id}); } else { diff --git a/lib/WebGUI/Asset/Event.pm b/lib/WebGUI/Asset/Event.pm index b82471df9..9ccfcb2bf 100644 --- a/lib/WebGUI/Asset/Event.pm +++ b/lib/WebGUI/Asset/Event.pm @@ -1,357 +1,2133 @@ package WebGUI::Asset::Event; -#------------------------------------------------------------------- +$VERSION = "0.0.0"; + +#################################################################### # WebGUI is Copyright 2001-2006 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 strict; -use Tie::CPHash; -use WebGUI::Form; -use WebGUI::Cache; -use WebGUI::HTML; -use WebGUI::HTMLForm; +use warnings; + +use Tie::IxHash; +use Storable qw(nfreeze thaw); + use WebGUI::International; -use WebGUI::SQL; -use WebGUI::Asset; +use WebGUI::Asset::Template; +use WebGUI::Form; -our @ISA = qw(WebGUI::Asset); +use base 'WebGUI::Asset'; + +use DateTime; -#------------------------------------------------------------------- + +=head1 Name + + +=head1 Description + + +=head1 Synopsis + + +=head1 Methods + + +=cut + +#################################################################### + sub definition { - my $class = shift; - my $session = shift; - my $definition = shift; - my $i18n = WebGUI::International->new($session,"Asset_Event"); + my $class = shift; + my $session = shift; + my $definition = shift; + + my $i18n = WebGUI::International->new($session, 'Asset_Event'); + + my $dt = WebGUI::DateTime->new(time); + + ### Set up list options ### + + + + ### Build properties hash ### + my %properties; + tie %properties, 'Tie::IxHash'; + %properties = ( + + ##### DEFAULTS ##### + 'description' => { + fieldType => "HTMLArea", + defaultValue => "", + }, + 'startDate' => { + fieldType => "Date", + defaultValue => $dt->toMysqlDate, + }, + 'endDate' => { + fieldType => "Date", + defaultValue => $dt->toMysqlDate, + }, + 'startTime' => { + fieldType => "TimeField", + defaultValue => $dt->toMysqlTime, + }, + 'endTime' => { + fieldType => "TimeField", + defaultValue => $dt->toMysqlTime, + }, + + 'recurId' => { + fieldType => "Text", + defaultValue => undef, + }, + + 'relatedLinks' => { + fieldType => "Textarea", + defaultValue => undef, + }, + 'location' => { + fieldType => "Text", + defaultValue => undef, + }, + ); + + + ### Add user defined fields + for my $num (1..5) + { + $properties{"UserDefined".$num} = { + fieldType => "text", + defaultValue => "", + }; + } + + push(@{$definition}, { - assetName=>$i18n->get('assetName'), - icon=>'calendar.gif', - tableName=>'EventsCalendar_event', - className=>'WebGUI::Asset::Event', - properties=>{ - description => { - fieldType=>"HTMLArea", - defaultValue=>undef - }, - eventStartDate => { - fieldType=>"dateTime", - defaultValue=>$session->datetime->time() - }, - eventEndDate => { - fieldType=>"dateTime", - defaultValue=>$session->datetime->time() - }, - EventsCalendar_recurringId => { - fieldType=>"hidden", - defaultValue=>undef - }, - eventLocation => { - fieldType=>"text", - defaultValue=>undef - }, - templateId => { - fieldType=>"template", - defaultValue=>'PBtmpl0000000000000023' - } - } - }); - return $class->SUPER::definition($session,$definition); -} - -#------------------------------------------------------------------- -sub getEditForm { - my $self = shift; - my $tabform = $self->SUPER::getEditForm(); - my $i18n = WebGUI::International->new($self->session,"Asset_Event"); - $tabform->getTab("properties")->HTMLArea( - -name=>"description", - -label=>$i18n->get(512), - -hoverHelp=>$i18n->get('Description description'), - -value=>$self->getValue("description") - ); - $tabform->getTab("properties")->dateTime( - -name=>"eventStartDate", - -label=>$i18n->get(513), - -hoverHelp=>$i18n->get('Start Date description'), - -extras=>'onblur="this.form.eventEndDate.value=this.form.eventStartDate.value;this.form.until.value=this.form.eventStartDate.value;"', - -value=>$self->getValue("eventStartDate") - ); - $tabform->getTab("properties")->dateTime( - -name=>"eventEndDate", - -label=>$i18n->get(514), - -hoverHelp=>$i18n->get('End Date description'), - -extras=>'onblur="this.form.until.value=this.form.eventEndDate.value;"', - -value=>$self->getValue("eventEndDate") - ); - $tabform->getTab("properties")->text( - -name=>"eventLocation", - -label=>$i18n->get(515), - -hoverHelp=>$i18n->get('515 description'), - -value=>$self->getValue("eventLocation") - ); - if ($self->session->form->process("func") eq "add") { - my %recursEvery; - tie %recursEvery, 'Tie::IxHash'; - %recursEvery = ( - 'never'=>$i18n->get(4), - 'day'=>$i18n->get(700), - 'week'=>$i18n->get(701), - 'month'=>$i18n->get(702), - 'year'=>$i18n->get(703), - ); - $tabform->getTab("properties")->readOnly( - -label=>$i18n->get(8), - -hoverHelp=>$i18n->get('Recurs every description'), - -value=>WebGUI::Form::integer($self->session, { - name=>"interval", - defaultValue=>1 - }) - .WebGUI::Form::selectBox($self->session, { - name=>"recursEvery", - options=>\%recursEvery - }) - .' '.$i18n->get(9).' ' - .WebGUI::Form::date($self->session, { - name=>"until" - }) - ); - } - $tabform->getTab("display")->template( - -name=>"templateId", - -value=>$self->getValue("templateId"), - -namespace=>"EventsCalendar/Event", - -label=>$i18n->get(530), - -hoverHelp=>$i18n->get('530 description'), - ); - return $tabform; + assetName =>$i18n->get('assetName'), + icon =>'calendar.gif', + tableName =>'Event', + className =>'WebGUI::Asset::Event', + properties =>\%properties + }); + + return $class->SUPER::definition($session, $definition); } -#------------------------------------------------------------------- +#################################################################### + +=head2 canAdd + +Returns true if a user can add this asset. -=head2 prepareView ( ) -See WebGUI::Asset::prepareView() for details. =cut -sub prepareView { - my $self = shift; - $self->SUPER::prepareView(); - my $template = WebGUI::Asset::Template->new($self->session, $self->get("templateId")); - $template->prepare; - $self->{_viewTemplate} = $template; +sub canAdd +{ + my $self = shift; + my $session = shift; + + return $session->user->isInGroup($self->getParent->get("groupIdEventEdit")); } -#------------------------------------------------------------------- -sub processPropertiesFromFormPost { - my $self = shift; - $self->SUPER::processPropertiesFromFormPost; - if ($self->session->form->process("assetId") eq "new") { - $self->update({eventEndDate=>$self->get("eventStartDate")}) unless ($self->get("eventEndDate") >= $self->get("eventStartDate")); - if ($self->session->form->process("recursEvery") && $self->session->form->process("recursEvery") ne "never") { - my $until = $self->session->datetime->setToEpoch($self->session->form->process("until")); - $until = $self->get("eventEndDate") unless ($until >= $self->get("eventEndDate")); - my $interval = ($self->session->form->process("interval") < 1) ? 1 : $self->session->form->process("interval"); - my $recurringEventId = $self->session->id->generate(); - $self->update({EventsCalendar_recurringId=>$recurringEventId}); - my $start = $self->get("eventStartDate"); - my $end = $self->get("eventEndDate"); - my $i = 0; - while ($start < $until) { - $i++; - if ($self->session->form->process("recursEvery") eq "day") { - $start = $self->session->datetime->addToDate($self->get("eventStartDate"),0,0,($i*$interval)); - $end = $self->session->datetime->addToDate($self->get("eventEndDate"),0,0,($i*$interval)); - } elsif ($self->session->form->process("recursEvery") eq "week") { - $start = $self->session->datetime->addToDate($self->get("eventStartDate"),0,0,(7*$i*$interval)); - $end = $self->session->datetime->addToDate($self->get("eventEndDate"),0,0,(7*$i*$interval)); - } elsif ($self->session->form->process("recursEvery") eq "month") { - $start = $self->session->datetime->addToDate($self->get("eventStartDate"),0,($i*$interval),0); - $end = $self->session->datetime->addToDate($self->get("eventEndDate"),0,($i*$interval),0); - } elsif ($self->session->form->process("recursEvery") eq "year") { - $start = $self->session->datetime->addToDate($self->get("eventStartDate"),($i*$interval),0,0); - $end = $self->session->datetime->addToDate($self->get("eventEndDate"),($i*$interval),0,0); - } - my $newEvent = $self->duplicate; - $newEvent->update({ - eventStartDate=>$start, - eventEndDate=>$end - }); - } - } - } -} -#------------------------------------------------------------------- -=head2 purgeCache ( ) -See WebGUI::Asset::purgeCache() for details. +#################################################################### + +=head2 canEdit + +Returns true if a user can edit this asset. =cut -sub purgeCache { - my $self = shift; - my $cache = WebGUI::Cache->new($self->session,"view_".$self->getId); - $cache->delete if $cache; - $self->SUPER::purgeCache; - my $parent = $self->getParent; - $parent->purgeCache if defined $parent; +sub canEdit +{ + my $self = shift; + my $session = $self->session; + + return $session->user->isInGroup($self->getParent->get("groupIdEventEdit")); } -#------------------------------------------------------------------- -=head2 setParent ( newParent ) -We're overloading the setParent in Asset because we don't want events to be able to be posted to anything other than the events calendar. -=head3 newParent -An asset object to make the parent of this asset. + +#################################################################### + +=head2 generateRecurringEvents ( [ \%recurrence_data ] ) + +Generate a series of events. + +If given a hashref of recurrence data, will create a new recurrence row in the +database and set the event to this new recurrence pattern. Will return undef if +there is an error creating the recurrence pattern. + +If not given recurrence data, will use the event's existing recurrence. This is +used for generating future occurrences of events that don't end. =cut -sub setParent { - my $self = shift; - my $newParent = shift; - return 0 unless ($newParent->get("className") eq "WebGUI::Asset::Wobject::EventsCalendar"); - return $self->SUPER::setParent($newParent); +sub generateRecurringEvents +{ + my $self = shift; + my $recur = shift; + my $parent = $self->getParent; + my $id; + + if ($recur) + { + $id = $self->setRecurrence($recur); + return () unless $id; + } + else + { + $id = $self->get("recurId"); + $recur = {$self->getRecurrence}; + } + + my $properties = {%{$self->get}}; + $properties->{recurId} = $id; + + # Get the distance between the event startDate and endDate + my $duration_days = 0; + + my $event_start = WebGUI::DateTime->new(delete($properties->{startDate})." 00:00:00"); + my $event_end = WebGUI::DateTime->new(delete($properties->{endDate})." 00:00:00"); + $duration_days = $event_end->subtract_datetime($event_start)->days; + + my @dates = $self->getRecurrenceDates($recur); + + for my $date (@dates) + { + my $dt = WebGUI::DateTime->new($date." 00:00:00"); + + ### TODO: Only generate if the recurId does not exist on this day + $properties->{startDate} = $dt->strftime('%F'); + $properties->{endDate} = $dt->clone->add(days => $duration_days)->strftime('%F'); + + + $parent->addChild($properties)->requestCommit; + } + + return 1; } -#------------------------------------------------------------------- -sub view { - my $self = shift; - if ($self->session->user->userId eq '1') { - my $out = WebGUI::Cache->new($self->session,"view_".$self->getId)->get; - return $out if $out; + + + + + +#################################################################### + +=head2 getDateTimeStart + +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 +adjusted. + +=cut + +sub getDateTimeStart +{ + my $self = shift; + my $date = $self->get("startDate"); + my $time = $self->get("startTime"); + my $tz = $self->session->user->profileField("timeZone"); + + #$self->session->errorHandler->warn($self->getId.":: Date: $date -- Time: $time"); + unless ($date) + { + $self->session->errorHandler->warn("This event (".$self->get("assetId").") has no date."); + return; } - my ($output, $id); - my %var = $self->get; - my $dt = $self->session->datetime; - my $i18n = WebGUI::International->new($self->session,"Asset_Event"); - $var{'title'} = $self->getValue('title'); - $var{'start.label'} = $i18n->get(14); - $var{'start.date'} = $dt->epochToHuman($self->getValue('eventStartDate'),"%z"); - $var{'start.time'} = $dt->epochToHuman($self->getValue('eventStartDate'),"%Z"); - $var{'end.label'} = $i18n->get(15); - $var{'end.date'} = $dt->epochToHuman($self->getValue('eventEndDate'),'%z'); - $var{'end.time'} = $dt->epochToHuman($self->getValue('eventEndDate'),'%Z'); - $var{'canEdit'} = $self->canEdit; - $var{'edit.url'} = $self->session->url->page('func=edit'); - $var{'edit.label'} = $i18n->get(575); - $var{'delete.url'} = $self->session->url->page('func=deleteEvent;rid='.$self->getValue('EventsCalendar_recurringId')); - $var{'delete.label'} = $i18n->get(576); - my @others; - my ($start, $garbage, $end); - ($start, $garbage) = $dt->dayStartEnd($self->get('eventStartDate')); - ($garbage, $end) = $dt->dayStartEnd($self->get('eventEndDate')); - - my $calendar = $self->getParent(); - my $scope = $calendar->get('scope'); - my $events; - if ($scope == 0) { # Calendar Scope is Regular - $events = $calendar->getLineage(['children'], - {returnObjects=>1, - includeOnlyClasses=>['WebGUI::Asset::Event']}); - } elsif ($scope == 2) { # Calendar Scope is Master - $events = $calendar->getLineage(['descendants'], - {returnObjects=>1, - includeOnlyClasses=>['WebGUI::Asset::Event']}); - } elsif ($scope == 1) { # Calendar Scope is Global - my $root = WebGUI::Asset->getRoot($self->session); - $events = $root->getLineage(['descendants'], - {returnObjects=>1, - includeOnlyClasses=>['WebGUI::Asset::Event']}); + + + if ($time) + { + my $dt = new WebGUI::DateTime($date." ".$time); + $dt->set_time_zone($tz); + return $dt; } + else + { + my $dt = new WebGUI::DateTime($date." 00:00:00"); + return $dt; + } +} - foreach my $event (@{$events}) { - # skip events that are already ended - next if ($event->get('eventEndDate') < $start); - # skip events that are in the future - next if ($event->get('eventStartDate') > $end); - # skip events we are not allowed to see - next unless ($event->canView); - # skip self - next if ($event->get('assetId') eq $self->get('assetId')); - push(@others,{ - url=>$event->getUrl, - title=>$event->getTitle, + + + +#################################################################### + +=head2 getDateTimeEnd + +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 +adjusted. + +=cut + +sub getDateTimeEnd +{ + my $self = shift; + my $date = $self->get("endDate"); + my $time = $self->get("endTime"); + my $tz = $self->session->user->profileField("timeZone"); + + #$self->session->errorHandler->warn($self->getId.":: Date: $date -- Time: $time"); + unless ($date) + { + $self->session->errorHandler->warn("This event (".$self->get("assetId").") has no date."); + return; + } + + + if ($time) + { + my $dt = new WebGUI::DateTime($date." ".$time); + $dt->set_time_zone($tz); + return $dt; + } + else + { + my $dt = new WebGUI::DateTime($date." 23:59:59"); + return $dt; + } +} + + + + + +#################################################################### + +=head2 getEventNext + +Gets the event that occurs after this event in the calendar. Returns the +Event object. + +=cut + +sub getEventNext +{ + my $self = shift; + + my $where = 'Event.startDate > "'.$self->get("startDate").'" || (Event.startDate = "'.$self->get("startDate").'" && '; + + # All day events must either look for null time or greater than 00:00:00 + if ($self->isAllDay) + { + $where .= "(Event.startTime IS NULL && assetData.title > '".$self->get("title")."') || Event.startTime >= '00:00:00'"; + } + # Non all-day events must look for greater than time + else + { + $where .= "(Event.startTime = '".$self->get("startTime")."' && assetData.title > '".$self->get("title")."') || Event.startTime > '".$self->get("startTime")."'"; + } + $where .= ")"; + + my $events = $self->getLineage(['siblings'], + { + #returnObjects => 1, + includeOnlyClasses => ['WebGUI::Asset::Event'], + joinClass => 'WebGUI::Asset::Event', + orderByClause => 'Event.startDate, Event.startTime, Event.endDate, Event.endDate, assetData.title, assetData.assetId', + whereClause => $where, + limit => 1, }); - } - $var{others_loop} = \@others; - my $out = $self->processTemplate(\%var,undef,$self->{_viewTemplate}); - if ($self->session->user->userId eq '1') { - my $cache = WebGUI::Cache->new($self->session,'view_'.$self->getId); - $cache->set($out,$self->getParent->get('visitorCacheTimeout')); - } - return $out; + + return WebGUI::Asset->newByDynamicClass($self->session,$events->[0]); } -#------------------------------------------------------------------- -sub www_deleteEvent { - my $self = shift; - return $self->session->privilege->insufficient() unless ($self->canEdit); - my $i18n = WebGUI::International->new($self->session,"Asset_Event"); - my ($output); - $output = '

'.$i18n->get(42).'

'; - $output .= $i18n->get(75).'

'; - $output .= ''.$i18n->get(76).'

'; - $output .= '' - .$i18n->get(77).'

' if (($self->session->form->process("rid") ne "") and ($self->session->form->process("rid") ne "0")); - $output .= ''.$i18n->get(78).''; - $output .= '

'; - return $self->session->style->process($output,$self->getParent->getValue("styleTemplateId")); + + + + +#################################################################### + +=head2 getEventPrev + +Gets the event that occurs before this event in the calendar. Returns the Event +object. + +=cut + +sub getEventPrev +{ + my $self = shift; + + my $where = 'Event.startDate < "'.$self->get("startDate").'" || (Event.startDate = "'.$self->get("startDate").'" && '; + + # All day events must either look for null time or greater than 00:00:00 + if ($self->isAllDay) + { + $where .= "(Event.startTime IS NULL && assetData.title < '".$self->get("title")."')"; + } + # Non all-day events must look for greater than time + else + { + $where .= "(Event.startTime = '".$self->get("startTime")."' && assetData.title < '".$self->get("title")."') || Event.startTime < '".$self->get("startTime")."'"; + } + $where .= ")"; + + my $events = $self->getLineage(['siblings'], + { + #returnObjects => 1, + includeOnlyClasses => ['WebGUI::Asset::Event'], + joinClass => 'WebGUI::Asset::Event', + orderByClause => 'Event.startDate DESC, Event.startTime DESC, Event.endDate DESC, Event.endDate DESC, assetData.title DESC, assetData.assetId DESC', + whereClause => $where, + limit => 1, + }); + + return WebGUI::Asset->newByDynamicClass($self->session,$events->[0]); } -#------------------------------------------------------------------- -sub www_deleteEventConfirm { - my $self = shift; - return $self->session->privilege->insufficient() unless ($self->canEdit); - if (($self->session->form->process("rid") ne "") and ($self->session->form->process("rid") ne "0")) { - my $series = $self->getParent->getLineage(["descendants"],{returnObjects=>1}); - foreach my $event (@{$series}) { - $event->trash if $event->get("EventsCalendar_recurringId") eq $self->session->form->process("rid"); + + + +#################################################################### + +=head2 getIcalStart + +If this event is an all-day event, gets an iCalendar (RFC 2445) Date string, not +adjusted for time zone. +. +Otherwise returns an iCalendar Date/Time string in the UTC time zone. + +=cut + +sub getIcalStart +{ + my $self = shift; + + if ($self->isAllDay) + { + my $date = $self->get("startDate"); + $date =~ s/\D//g; + return $date; + } + else + { + my $date = $self->get("startDate"); + my $time = $self->get("startTime"); + + $date =~ s/\D//g; + $time =~ s/\D//g; + + return $date."T".$time."Z"; + } +} + + + + + +#################################################################### + +=head2 getIcalEnd + +If this event is an all-day event, gets an iCalendar (RFC 2445) Date string, not +adjusted for time zone. +. +Otherwise returns an iCalendar Date/Time string in the UTC time zone. + +=cut + +sub getIcalEnd +{ + my $self = shift; + + if ($self->isAllDay) + { + my $date = $self->get("endDate"); + $date =~ s/\D//g; + return $date; + } + else + { + my $date = $self->get("endDate"); + my $time = $self->get("endTime"); + + $date =~ s/\D//g; + $time =~ s/\D//g; + + return $date."T".$time."Z"; + } +} + + + + + +#################################################################### + +=head2 getRecurrence + +Returns a hash 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, +yearWeek) + +=item startDate + +The MySQL date to start creating recurring events + +=item endDate + +The MySQL date to end creating recurring events. If either this or endAfter does +not exist, the event recurs forever. + +=item endAfter + +The number of occurences this event ends after. If neither this nor endDate +exists, the event recurs forever. + +=item every + +The number of (days, weeks, months, years) between each recurrence. + +=item dayNames + +A list of day names that this event recurs on. + + u - Sunday + m - Monday + t - Tuesday + w - Wednesday + r - Thursday + f - Friday + s - Saturday + +=item dayNumber + +The day number that this event recurs on + +=item weeks + +A list of weeks that this event recurs on + + first + second + third + fourth + last + +=item months + +A list of months that this event recurs on + + jan - January + feb - February + mar - March + apr - April + may - May + jun - June + jul - July + aug - August + sep - September + oct - October + nov - November + dec - December + +=back + +=cut + +sub getRecurrence +{ + my $self = shift; + use Data::Dumper; + #$self->session->errorHandler->warn("recurId: ".$self->get("recurId")); + return () unless $self->get("recurId"); + + my %data = $self->session->db->quickHash("select * from Event_recur where recurId=?",[$self->get("recurId")]); + my %recurrence = ( + recurType => $data{recurType}, + ); + + + # 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]; + } + + $recurrence{startDate} = $data{startDate}; + if ($data{endDate} && $data{endDate} =~ /^after (\d+)/i) + { + $recurrence{endAfter} = $1; + } + elsif ($data{endDate}) + { + $recurrence{endDate} = $data{endDate}; + } + + return %recurrence; +} + + + + + +#################################################################### + +=head2 getRecurrenceDates + +Gets a series of dates in the specified recurrence pattern. + +This is quite possibly the worst algorithm I've ever created. We should be using +DateTime::Event::ICal instead. + +=cut + +sub getRecurrenceDates +{ + my $self = shift; + my $recur = shift; + + my %date; + + return undef unless $recur->{recurType}; + + my %dayNames = ( + monday => "m", + tuesday => "t", + wednesday => "w", + thursday => "r", + friday => "f", + saturday => "s", + sunday => "u", + ); + my %weeks = ( + 0 => "first", + 1 => "second", + 2 => "third", + 3 => "fourth", + 4 => "fifth", + ); + + + my $dt = WebGUI::DateTime->new($recur->{startDate}." 00:00:00"); + my $dt_start = $dt->clone; # Keep track of the initial start date + my $dt_end = WebGUI::DateTime->new($recur->{endDate}." 00:00:00") + if $recur->{endDate}; + # Set an end for events with no end + #!!! TODO !!! - Get the appropriate configuration + $dt_end = $dt->clone->add(years=>2) + if (!$recur->{endDate} && !$recur->{endAfter}); + + + 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{lc $dt_day->day_name}; + + 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($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{lc $dt_day->day_name}; + + 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{lc $dt_day->day_name}; + + 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($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_name}; + + 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{lc $dt_day->day_name}; + + 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; } - } else { - $self->trash; } - - return $self->session->asset($self->getParent->getContainer)->www_view; + + + return sort keys %date; } -#------------------------------------------------------------------- -sub www_edit { - my $self = shift; - return $self->session->privilege->insufficient() unless $self->canEdit; - $self->getAdminConsole->setHelp("event add/edit","Asset_Event"); - my $i18n = WebGUI::International->new($self->session,"Asset_Event"); - return $self->getAdminConsole->render($self->getEditForm->print,$i18n->get('13')); + + + +#################################################################### + +=head2 getRecurrenceFromForm + +Returns a recurrence hash based on the form parameters. + +The hash keys are the same as getRecurrence. + +=cut + +sub getRecurrenceFromForm +{ + my $self = shift; + my $form = $self->session->form; + + my %recurrence = (); + my $type = lc $form->param("recurType"); + + return () unless ($type && $type !~ /none/i); + + if ($type eq "daily") + { + if (lc($form->param("recurSubType")) eq "weekday") + { + $recurrence{recurType} = "weekday"; + } + else + { + $recurrence{recurType} = "daily"; + } + + $recurrence{every} = $form->param("recurDay"); + } + elsif ($type eq "weekly") + { + $recurrence{recurType} = "weekly"; + $recurrence{dayNames} = [$form->param("recurWeekDay")]; + $recurrence{every} = $form->param("recurWeek"); + } + elsif ($type eq "monthly") + { + if (lc($form->param("recurSubType")) eq "monthweek") + { + $recurrence{recurType} = "monthWeek"; + $recurrence{weeks} = [$form->param("recurMonthWeekNumber")]; + $recurrence{dayNames} = [$form->param("recurMonthWeekDay")]; + } + elsif (lc($form->param("recurSubType")) eq "monthday") + { + $recurrence{recurType} = "monthDay"; + $recurrence{dayNumber} = $form->param("recurMonthDay"); + } + + $recurrence{every} = $form->param("recurMonth"); + } + elsif ($type eq "yearly") + { + if (lc($form->param("recurSubType")) eq "yearweek") + { + $recurrence{recurType} = "yearWeek"; + $recurrence{weeks} = [$form->param("recurYearWeekNumber")]; + $recurrence{dayNames} = [$form->param("recurYearWeekDay")]; + $recurrence{months} = [$form->param("recurYearWeekMonth")]; + } + elsif (lc($form->param("recurSubType")) eq "yearday") + { + $recurrence{recurType} = "yearDay"; + $recurrence{dayNumber} = $form->param("recurYearDay"); + $recurrence{months} = [$form->param("recurYearDayMonth")]; + } + + $recurrence{every} = $form->param("recurYear"); + } + + $recurrence{every} ||= 1; + $recurrence{startDate} = $form->param("recurStart"); + + if (lc $form->param("recurEndType") eq "date") + { + $recurrence{endDate} = $form->param("recurEndDate"); + } + elsif (lc $form->param("recurEndType") eq "after") + { + $recurrence{endAfter} = $form->param("recurEndAfter"); + } + + return %recurrence; } -#------------------------------------------------------------------- + + + + +#################################################################### + +=head2 getRelatedLinks + +Gets the related links. + +=cut + +sub getRelatedLinks +{ + my $self = shift; + return () unless $self->get("relatedLinks"); + return split /\n+/, $self->get("relatedLinks"); +} + + + + + +#################################################################### + +=head2 getTemplateVars + +Returns a hash of additional parameters to be used in templates, beyond the +standard definition. + +Uses the current user's locale and timezone. + +=cut + +sub getTemplateVars +{ + my $self = shift; + my $i18n = WebGUI::International->new($self->session,"Asset_Event"); + my %var; + + # Some miscellaneous stuff + $var{"isPublic"} = 1 + if $self->get("groupIdView") eq "7"; + $var{"groupToView"} = $self->get("groupIdView"); # Todo: Remove this? + + # Start date/time + my $dtStart = $self->getDateTimeStart; + $dtStart->set_locale($i18n->get("locale") || "en_US"); + + $var{"startDateSecond"} = sprintf "%02d", $dtStart->second; + $var{"startDateMinute"} = sprintf "%02d", $dtStart->minute; + $var{"startDateHour24"} = $dtStart->hour; + $var{"startDateHour"} = $dtStart->hour_12; + $var{"startDateM"} = ( $dtStart->hour < 12 ? "AM" : "PM" ); + $var{"startDateDayName"} = $dtStart->day_name; + $var{"startDateDayAbbr"} = $dtStart->day_abbr; + $var{"startDateDayOfMonth"} = $dtStart->day_of_month; + $var{"startDateDayOfWeek"} = $dtStart->day_of_week; + $var{"startDateMonthName"} = $dtStart->month_name; + $var{"startDateMonthAbbr"} = $dtStart->month_abbr; + $var{"startDateYear"} = $dtStart->year; + $var{"startDateYmd"} = $dtStart->ymd; + $var{"startDateMdy"} = $dtStart->mdy; + $var{"startDateDmy"} = $dtStart->dmy; + $var{"startDateHms"} = $dtStart->hms; + $var{"startDateEpoch"} = $dtStart->epoch; + + # End date/time + my $dtEnd = $self->getDateTimeEnd; + $dtEnd->set_locale($i18n->get("locale") || "en_US"); + + $var{"endDateSecond"} = sprintf "%02d", $dtEnd->second; + $var{"endDateMinute"} = sprintf "%02d", $dtEnd->minute; + $var{"endDateHour24"} = $dtEnd->hour; + $var{"endDateHour"} = $dtEnd->hour_12; + $var{"endDateM"} = ( $dtEnd->hour < 12 ? "AM" : "PM" ); + $var{"endDateDayName"} = $dtEnd->day_name; + $var{"endDateDayAbbr"} = $dtEnd->day_abbr; + $var{"endDateDayOfMonth"} = $dtEnd->day_of_month; + $var{"endDateDayOfWeek"} = $dtEnd->day_of_week; + $var{"endDateMonthName"} = $dtEnd->month_name; + $var{"endDateMonthAbbr"} = $dtEnd->month_abbr; + $var{"endDateYear"} = $dtEnd->year; + $var{"endDateYmd"} = $dtEnd->ymd; + $var{"endDateMdy"} = $dtEnd->mdy; + $var{"endDateDmy"} = $dtEnd->dmy; + $var{"endDateHms"} = $dtEnd->hms; + $var{"endDateEpoch"} = $dtEnd->epoch; + + + + $var{isAllDay} = $self->isAllDay; + + + # Make some friendly URLs + $dtStart->truncate(to=>"day"); + $var{"urlDay"} = "/".$self->getParent->get("url")."?type=day;start=" + . $dtStart->toMysql; + $var{"urlWeek"} = "/".$self->getParent->get("url")."?type=week;start=" + . $dtStart->toMysql; + $var{"urlMonth"} = "/".$self->getParent->get("url")."?type=month;start=" + . $dtStart->toMysql; + $var{"urlParent"} = "/".$self->getParent->get("url"); + + + # Related links + $var{"relatedLinks"} = []; + push @{$var{"relatedLinks"}}, { "linkUrl" => $_ } + for ($self->getRelatedLinks); + + + return %var; +} + + + + + +#################################################################### + +=head2 isAllDay + +Returns true if this event is an all day event. + +=cut + +sub isAllDay +{ + return 1 unless ($_[0]->get("startTime") || $_[0]->get("endTime")); + return 0; +} + + + + + +#################################################################### + +=head2 prepareView + +Prepares the view template to be used later. The template to be used is found +from this asset's parent (Usually a Calendar). + +=cut + +sub prepareView +{ + my $self = shift; + my $parent = $self->getParent; + my $templateId; + + if ($parent) + { + $templateId = $parent->get("templateIdEvent") + } + else + { + $templateId = "CalendarEvent000000001"; + } + + my $template = WebGUI::Asset::Template->new($self->session,$templateId); + $template->prepare; + + $self->{_viewTemplate} = $template; +} + + + + + +#################################################################### + +=head2 processPropertiesFromFormPost + +Processes the Event Edit form. + +Makes the event hide from navigation (since there could be possibly thousands of +events across dozens of years. How would we even go about sorting such a list?) + +If "allday" is "yes", sets the startTime and endTime to NULL. + +If there's a time, convert from the user's timezone to UTC. + +Updates the Event_recur table if necessary (creates a new recurId, points the +saved event to the new recurId, creates all the events under this new +recurId, and then deletes all the events under the old recurId). + +Requests that the events be committed + +=cut + +sub processPropertiesFromFormPost +{ + my $self = shift; + $self->SUPER::processPropertiesFromFormPost; # Updates the event + my $session = $self->session; + my $form = $self->session->form; + + ### Verify the form was filled out correctly... + # The end must be after the start + if (!$form->param("allday") + && $self->get("startDate") gt $self->get("endDate") + || ($self->get("startDate") eq $self->get("endDate") && $self->get("startTime") gt $self->get("endTime"))) + { + return ["The event end must be after the event start."]; + } + + + ### Form is verified + # Events are always hidden from navigation + $self->update({ isHidden => 1 }); + + # Fix times according to input (allday, timezone) + if ($self->session->form->param("allday")) + { + $self->update({ startTime => undef, + endTime => undef, + }); + } + else + { + # Convert timezone + my $tz = $self->session->user->profileField("timeZone"); + + my ($startDate,$startTime) = split / /, WebGUI::DateTime->new(mysql => $self->get("startDate")." ".$self->get("startTime"), time_zone => $tz) + ->set_time_zone("UTC")->toMysql; + + my ($endDate,$endTime) = split / /, WebGUI::DateTime->new(mysql => $self->get("endDate")." ".$self->get("endTime"), time_zone => $tz) + ->set_time_zone("UTC")->toMysql; + + $self->update({ startDate => $startDate, + startTime => $startTime, + endDate => $endDate, + endTime => $endTime, + }); + } + + + # Determine if the pattern has changed + if ($form->param("recurType")) + { + # Create the new recurrence hash + 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 + $Storable::canonical = 1; + + #use Data::Dumper; + #$session->errorHandler->warn("OLD (".$self->get("recurId")."): ".Dumper \%recurrence_old); + #$session->errorHandler->warn("NEW: ".Dumper \%recurrence_new); + + # Pattern keys + if (nfreeze(\%recurrence_new) ne nfreeze(\%recurrence_old)) + { + # Delete all old events and create new ones + my $old_id = $self->get("recurId"); + + #use Data::Dumper; + #$self->session->errorHandler->warn("Form recurrence: ".Dumper \%recurrence_new); + + return ["There's something wrong with your recurrence pattern."] + unless $self->generateRecurringEvents(\%recurrence_new); + + + ## Delete old events + my $events = $self->getLineage(["siblings"], + { + returnObjects => 1, + includeOnlyClasses => ['WebGUI::Asset::Event'], + joinClass => 'WebGUI::Asset::Event', + whereClause => 'Event.recurId = "'.$old_id.'"', + }); + + $_->purge for @$events; + } + # Include / exclude keys + #elsif () + #{ + # # Delete / create necessary events + # + #} + # No change + else + { + # Just update related events + #$self->session->errorHandler->warn("No changes to recurrence."); + + my $properties = $self->get; + delete $properties->{startDate}; + delete $properties->{endDate}; + + my $events = $self->getLineage(["siblings"], + { + returnObjects => 1, + includeOnlyClasses => ['WebGUI::Asset::Event'], + joinClass => 'WebGUI::Asset::Event', + whereClause => 'Event.recurId = "'.$self->get("recurId").'"', + }); + + for my $event (@$events) + { + # Add a revision + $properties->{startDate} = $event->get("startDate"); + $properties->{endDate} = $event->get("endDate"); + + $event->addRevision($self->get); + + # Request the commit + $event->requestCommit; + } + } + } + + + $self->requestCommit; +} + + + +#################################################################### + +=head2 requestCommit + +Requests that this event (and all events with the same recurId) are committed +using the parent element's approval workflow. + +If the user is already working under a version tag, will remove the events from +the version tag and place them under a new version tag, then try to commit using +the parent element's approval workflow. + +=cut + +sub requestCommit +{ + my $self = shift; + my $currentTag = WebGUI::VersionTag->getWorking($self->session); + my $workflowId = "pbworkflow000000000003"; ###!!TODO: make a config value for this + + if ($currentTag->getAssetCount <= 1) + { + $currentTag->set({workflowId=>$workflowId}); + $currentTag->requestCommit; + } + else + { + my $newTag = WebGUI::VersionTag->create($self->session, + { + name => $self->getTitle." / ".$self->session->user->username, + workflowId => $workflowId, + }); + $self->session->db->write("update assetData set tagId=? where assetId=? and tagId=?",[$newTag->getId, $self->getId, $currentTag->getId]); + $self->purgeCache; + $newTag->requestCommit; + } +} + + + + + +#################################################################### + +=head2 setRecurrence ( hashref ) + +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. + +Returns the ID of the row if success, otherwise returns 0. + +=cut + +sub setRecurrence +{ + my $self = shift; + my $vars = shift; + + my $type = $vars->{recurType} || return; + my $pattern; + + if ($type eq "daily" || $type eq "weekday") + { + return 0 unless ($vars->{every}); + #(\d+) + $pattern = $vars->{every}; + } + elsif ($type eq "weekly") + { + return 0 unless ($vars->{every} && $vars->{dayNames}); + #(\d+) ([umtwrfs]+) + $pattern = $vars->{every}." ".join("",@{$vars->{dayNames}}); + } + elsif ($type eq "monthWeek") + { + return 0 unless ($vars->{every} && $vars->{weeks} && $vars->{dayNames}); + #(\d+) (first,second,third,fourth,last) ([umtwrfs]+) + $pattern = $vars->{every}." ".join(",",@{$vars->{weeks}})." ".join("",@{$vars->{dayNames}}); + } + elsif ($type eq "monthDay") + { + return 0 unless ($vars->{every} && $vars->{dayNumber}); + #(\d+) on (\d+) + $pattern = $vars->{every}." ".$vars->{dayNumber}; + } + elsif ($type eq "yearWeek") + { + return 0 unless ($vars->{every} && $vars->{weeks} && $vars->{dayNames} && $vars->{months}); + #(\d+) (first,second,third,fourth,last) ([umtwrfs]+)? (jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec) + $pattern = $vars->{every}." ".join(",",@{$vars->{weeks}})." ".join("",@{$vars->{dayNames}})." ".join(",",@{$vars->{months}}); + } + elsif ($type eq "yearDay") + { + return 0 unless ($vars->{every} && $vars->{dayNumber} && $vars->{months}); + #(\d+) on (\d+) (jan,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec) + $pattern = $vars->{every}." ".$vars->{dayNumber}." ".join(",",@{$vars->{months}}); + } + + + my $end = undef; + if ($vars->{endAfter}) + { + $end = "after ".$vars->{endAfter}; + } + elsif ($vars->{endDate}) + { + $end = $vars->{endDate}; + } + + + my $data = { + recurId => "new", + recurType => $type, + pattern => $pattern, + startDate => $vars->{startDate}, + endDate => $end, + }; + + #use Data::Dumper; + #$self->session->errorHandler->warn("Setting recurrence: ".Dumper $data); + + ## Set to the database + ## Return the new recurId + return $self->session->db->setRow("Event_recur","recurId",$data); +} + + + + + +#################################################################### + +=head2 setRelatedLinks ( @links ) + +Sets the event's related links. + +=cut + +sub setRelatedLinks +{ + my $self = shift; + my @links = @_; + + $self->update({ + relatedLinks => join("\n", @links), + }); +} + + + + + +#################################################################### + +=head2 view + +Returns the template to be viewed. + +=cut + +sub view +{ + my $self = shift; + my $session = $self->session; + + # Get, of course, the event data + my $var = $self->get; + + + + # Get some more template vars + my %dates = $self->getTemplateVars; + $var->{$_} = $dates{$_} for keys %dates; + + # Next and previous events + my $next = $self->getEventNext; + $var->{"nextUrl"} = "/".$next->get("url") + if ($next); + + my $prev = $self->getEventPrev; + $var->{"prevUrl"} = "/".$prev->get("url") + if ($prev); + + + return $self->processTemplate($var, undef, $self->{_viewTemplate}); +} + + + + + +#################################################################### + +=head2 www_edit + +Edit the event. + +=cut + +sub www_edit +{ + my $self = shift; + my $session = $self->session; + my $form = $self->session->form; + my $tz = $session->user->profileField("timeZone"); + my $func = lc $session->form->param("func"); + my $var = {}; + + + if ($func eq "add" || $form->param("assetId") eq "new") + { + $var->{"formHeader"} = WebGUI::Form::formHeader($session, + { + action => $self->getParent->getUrl, + }) + . WebGUI::Form::hidden($self->session, + { + name =>"assetId", + value =>"new", + }) + . WebGUI::Form::hidden($self->session, + { + name =>"class", + value =>$self->session->form->process("class","className") + }); + } + else + { + $var->{"formHeader"} = WebGUI::Form::formHeader($session, + { + action => $self->getUrl, + }); + } + + $var->{"formHeader"} .= WebGUI::Form::hidden($self->session, + { + name => "func", + value => "editSave" + }) + . WebGUI::Form::hidden($self->session, + { + name => "recurId", + value => $self->get("recurId"), + }); + + $var->{"formFooter"} = WebGUI::Form::formFooter($session); + + + ###### Event Tab + # title AS long title + $var->{"formTitle"} = WebGUI::Form::text($session, + { + name => "title", + value => $form->process("title") || $self->get("title"), + }); + + # menu title AS short title + $var->{"formMenuTitle"} = WebGUI::Form::text($session, + { + name => "menuTitle", + value => $form->process("menuTitle") || $self->get("menuTitle"), + maxlength => 15, + size => 16, + }); + + # location + $var->{"formLocation"} = WebGUI::Form::text($session, + { + name => "location", + value => $form->process("location") || $self->get("location"), + }); + + # description + $var->{"formDescription"}= WebGUI::Form::HTMLArea($session, + { + name => "description", + value => $form->process("description") || $self->get("description"), + }); + + # start date + my $default_start = WebGUI::DateTime->new($session->form->param("start") || time) + ->set_time_zone($tz); + my ($startDate,$startTime) = split / /, $self->getDateTimeStart->toMysql + unless $func eq "add" || $self->get("assetId") eq "new"; + + $var->{"formStartDate"}= WebGUI::Form::date($session, + { + name => "startDate", + value => $form->process("startDate") || $startDate, + defaultValue => $default_start->toMysqlDate, + }); + $var->{"formStartTime"} = WebGUI::Form::timeField($session, + { + name => "startTime", + value => $form->process("startTime") || $startTime, + defaultValue => $default_start->toMysqlTime, + }); + + # end date + $default_start->add(hours => 1); + my ($endDate,$endTime) = split / /, $self->getDateTimeEnd->toMysql + unless $func eq "add" || $self->get("assetId") eq "new"; + $var->{"formEndDate"} = WebGUI::Form::date($session, + { + name => "endDate", + value => $form->process("endDate") || $endDate, + defaultValue => $default_start->toMysqlDate, + }); + $var->{"formEndTime"} = WebGUI::Form::timeField($session, + { + name => "endTime", + value => $form->process("endTime") || $endTime, + defaultValue => $default_start->toMysqlTime, + }); + + # time + my $allday = ($form->param("allday") eq "yes" ? 1 : $self->isAllDay); + $var->{"formTime"} = + q| + +
+ + +
+
| + .q|Start: |.$var->{"formStartTime"} + .q|
End: |.$var->{"formEndTime"} + .q|
+ |; + + # related links + $var->{"formRelatedLinks"} = WebGUI::Form::textarea($session, + { + name => "relatedLinks", + value => $form->process("relatedLinks") || $self->get("relatedLinks"), + }); + + + + ###### Recurrence tab + # Pattern + my %recur = $self->getRecurrenceFromForm || $self->getRecurrence; + $recur{every} ||= 1; + + #use Data::Dumper; + #$self->session->errorHandler->warn("Assigning recurrence: ".Dumper \%recur); + + $var->{"formRecurPattern"} = + q| +

+

+ + +

+

+
+ Every
+ +
+ + +
+ + +

+

+
+ Every week(s) on
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + +

+

+
+

Every month(s) on

+

+ +

+ +

+ + week on + +

+
+ + +

+

+
+

Every years(s) on

+

+ + + +

+ +

+ + + of + +

+
+ + + + |; + + + # Start + $var->{"formRecurStart"} = WebGUI::Form::date($session, + { + name => "recurStart", + value => $recur{startDate}, + defaultValue => $self->get("startDate"), + }); + + # End + $var->{"formRecurEnd"} = q| + +
+ + + | + . WebGUI::Form::date($session,{ name => "recurEndDate", value => $recur{endDate}, defaultValue => $recur{endDate} }) + . q| +
+ + + + + occurences. + |; + + + # Include + + # Exclude + + + + + # Add button + $var->{"formSave"} = WebGUI::Form::submit($session, + { + name => "save", + value => "save", + }); + # Cancel button + $var->{"formCancel"} = WebGUI::Form::button($session, + { + name => "cancel", + value => "cancel", + extras => 'onClick="window.history.go(-1)"', + }); + + + ### Show any errors if necessary + if ($self->session->stow->get("editFormErrors")) + { + my $errors = $self->session->stow->get("editFormErrors"); + push @{$var->{"formErrors"}}, { message => $_ } + for @{$errors}; + } + + + + ### Load the template + my $parent = $self->getParent; + my $template; + if ($parent) + { + $template = WebGUI::Asset::Template->new($session,$parent->get("templateIdEventEdit")); + } + else + { + $template = WebGUI::Asset::Template->new($session,"CalendarEventEdit00001"); + } + + + + ### Show the processed template + $session->http->sendHeader; + my $style = $session->style->process("~~~",$self->getParent->get("styleTemplateId")); + my ($head, $foot) = split("~~~",$style); + $self->session->output->print($head, 1); + $self->session->output->print($self->processTemplate($var, undef, $template)); + $self->session->output->print($foot, 1); + return "chunked"; +} + + + + + +#################################################################### + +=head2 www_view + +Shows the event based on the parent asset's style and Event Details template + +=cut + sub www_view { - my $self = shift; - return $self->session->privilege->insufficient() unless ($self->canView); + my $self = shift; + return $self->session->privilege->noAccess() unless $self->canView; + my $check = $self->checkView; + return $check if (defined $check); $self->session->http->setCacheControl($self->get("visitorCacheTimeout")) if ($self->session->user->userId eq "1"); - $self->prepareView; - return $self->session->style->process($self->view,$self->getParent->getValue("styleTemplateId")); + $self->session->http->sendHeader; + $self->prepareView; + my $style = $self->getParent->processStyle("~~~"); + my ($head, $foot) = split("~~~",$style); + $self->session->output->print($head,1); + $self->session->output->print($self->view); + $self->session->output->print($foot,1); + return "chunked"; } + +=head1 Todo + +Pages for Next Event >> and Prev Event << on the Event Details page + +Pages for Next Occurence >> and Prev Occurrence << on the Event Details page + +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 +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? + +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 +asynchronously with Spectre rather than making the browser wait for a long time +(to make many many events). + +Related links need to be implemented using a separate table. + +Optimizations!!! + +BUG: Events with the same menuTitle, date, and time will not get their Next or +Previous event correctly because of the title. We must check if the title is +equal and then choose by assetId. + +=cut + 1; - diff --git a/lib/WebGUI/Asset/Wobject/Calendar.pm b/lib/WebGUI/Asset/Wobject/Calendar.pm new file mode 100644 index 000000000..a61a5a55b --- /dev/null +++ b/lib/WebGUI/Asset/Wobject/Calendar.pm @@ -0,0 +1,1471 @@ +package WebGUI::Asset::Wobject::Calendar; + +$VERSION = "0.0.0"; + +#################################################################### +# WebGUI is Copyright 2001-2006 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 strict; +use warnings; + +use Tie::IxHash; + +use WebGUI::Utility; +use WebGUI::International; +use WebGUI::Search; +use WebGUI::Form; +use WebGUI::HTML; +use WebGUI::DateTime; + +use base 'WebGUI::Asset::Wobject'; + +use DateTime; + + +=head1 Name + + +=head1 Description + + +=head1 Synopsis + + +=head1 Methods + + +=cut + +#################################################################### + +sub definition +{ + my $class = shift; + my $session = shift; + my $definition = shift || []; + + my $i18n = WebGUI::International->new($session, 'Asset_Calendar'); + + ### Set up list options ### + tie (my %optionsDefaultView, 'Tie::IxHash', + month => $i18n->get("defaultView value month"), + week => $i18n->get("defaultView value week"), + day => $i18n->get("defaultView value day"), + ); + tie (my %optionsDefaultDate, 'Tie::IxHash', + current => $i18n->get("defaultDate value current"), + first => $i18n->get("defaultDate value first"), + last => $i18n->get("defaultDate value last"), + ); + + + + ### Build properties hash ### + tie my %properties, 'Tie::IxHash'; + %properties = ( + + ##### DEFAULTS ##### + defaultView => { + fieldType => "SelectBox", + defaultValue => "month", + options => \%optionsDefaultView, + tab => "display", + label => $i18n->get("defaultView label"), + hoverHelp => $i18n->get("defaultView description"), + }, + + defaultDate => { + fieldType => "SelectBox", + defaultValue => 'current', + options => \%optionsDefaultDate, + tab => "display", + label => $i18n->get("defaultDate label"), + hoverHelp => $i18n->get("defaultDate description"), + }, + + ##### GROUPS / ACCESS ##### + # Edit events + groupIdEventEdit => { + fieldType => "group", + defaultValue => "3", + tab => "security", + label => $i18n->get("groupIdEventEdit label"), + hoverHelp => $i18n->get("groupIdEventEdit description"), + }, + groupIdSubscribed => { + fieldType => 'hidden', + }, + + + ##### TEMPLATES - DISPLAY ##### + # Month + templateIdMonth =>{ + fieldType =>"template", + defaultValue =>'CalendarMonth000000001', + tab =>"display", + namespace =>"Calendar/Month", + hoverHelp =>$i18n->get('templateIdMonth description'), + label =>$i18n->get('templateIdMonth label'), + }, + + # Week + templateIdWeek =>{ + fieldType =>"template", + defaultValue =>'CalendarWeek0000000001', + tab =>"display", + namespace =>"Calendar/Week", + hoverHelp =>$i18n->get('templateIdWeek description'), + label =>$i18n->get('templateIdWeek label'), + }, + + # Day + templateIdDay =>{ + fieldType =>"template", + defaultValue =>'CalendarDay00000000001', + tab =>"display", + namespace =>"Calendar/Day", + hoverHelp =>$i18n->get('templateIdDay description'), + label =>$i18n->get('templateIdDay label'), + }, + + # Event Details + templateIdEvent =>{ + fieldType =>"template", + defaultValue =>'CalendarEvent000000001', + tab =>"display", + namespace =>"Calendar/Event", + hoverHelp =>$i18n->get('templateIdEvent description'), + label =>$i18n->get('templateIdEvent label'), + }, + + # Event Edit + templateIdEventEdit =>{ + fieldType =>"template", + defaultValue =>'CalendarEventEdit00001', + tab =>"display", + namespace =>"Calendar/EventEdit", + hoverHelp =>$i18n->get('templateIdEventEdit description'), + label =>$i18n->get('templateIdEventEdit label'), + }, + + # Search + templateIdSearch =>{ + fieldType =>"template", + defaultValue =>'CalendarSearch00000001', + tab =>"display", + namespace =>"Calendar/Search", + hoverHelp =>$i18n->get('templateIdSearch description'), + label =>$i18n->get('templateIdSearch label'), + }, + + + ##### TEMPLATES - PRINT ##### + # Month + templateIdPrintMonth =>{ + fieldType =>"template", + defaultValue =>'CalendarPrintMonth0001', + tab =>"display", + namespace =>"Calendar/Print/Month", + hoverHelp =>$i18n->get('templateIdPrintMonth description'), + label =>$i18n->get('templateIdPrintMonth label'), + }, + + # Week + templateIdPrintWeek =>{ + fieldType =>"template", + defaultValue =>'CalendarPrintWeek00001', + tab =>"display", + namespace =>"Calendar/Print/Week", + hoverHelp =>$i18n->get('templateIdPrintWeek description'), + label =>$i18n->get('templateIdPrintWeek label'), + }, + + # Day + templateIdPrintDay =>{ + fieldType =>"template", + defaultValue =>'CalendarPrintDay000001', + tab =>"display", + namespace =>"Calendar/Print/Day", + hoverHelp =>$i18n->get('templateIdPrintDay description'), + label =>$i18n->get('templateIdPrintDay label'), + }, + + # Event Details + templateIdPrintEvent =>{ + fieldType =>"template", + defaultValue =>'CalendarPrintEvent0001', + tab =>"display", + namespace =>"Calendar/Print/Day", + hoverHelp =>$i18n->get('templateIdPrintEvent description'), + label =>$i18n->get('templateIdPrintEvent label'), + }, + + + ##### Miscellany ##### + visitorCacheTimeout => { + fieldType => "integer", + defaultValue => "60", + tab => "display", + hoverHelp => $i18n->get('visitorCacheTimeout description'), + label => $i18n->get('visitorCacheTimeout label'), + }, + + subscriberNotifyOffset => { + fieldType => "integer", + defaultValue => "2", + tab => "properties", + hoverHelp => $i18n->get('subscriberNotifyOffset description'), + label => $i18n->get('subscriberNotifyOffset label'), + }, + + + ); + + push(@{$definition}, { + assetName =>$i18n->get('assetName'), + icon =>'calendar.gif', + tableName =>'Calendar', + className =>'WebGUI::Asset::Wobject::Calendar', + properties =>\%properties, + autoGenerateForms=>1, + }); + + return $class->SUPER::definition($session, $definition); +} + + + + + + +#################################################################### + +=head2 addChild ( ) + +Only allows Events to be added as a child of this asset. + +=cut + +sub addChild +{ + my $self = shift; + my $properties = shift; + my @other = @_; + + unless ($properties->{className} eq "WebGUI::Asset::Event") + { + $self->session->errorHandler->security("add a ".$properties->{className}." to a ".$self->get("className")); + return undef; + } + + return $self->SUPER::addChild($properties, @other); +} + + + + + +#################################################################### + +=head2 canEdit + +Returns true if the user can edit this asset. + +Also returns true if the user is adding an Event and is allowed to do so (to get +around the canEdit check when www_editSave is being used to add an asset). + +=cut + +sub canEdit +{ + my $self = shift; + my $form = $self->session->form; + my $user = $self->session->user; + + no warnings qw(uninitialized); + return 1 if ($form->param("assetId") eq "new" + && $form->param("func") eq "editSave" + && $form->param("class") eq "WebGUI::Asset::Event" + && $user->isInGroup($self->get("groupIdEventEdit")) + ); + use warnings qw(all); + + return $self->SUPER::canEdit; +} + + + + + +#################################################################### + +=head2 createSubscriptionGroup + +Creates the group for users that are subscribed to the Calendar. + +Stolen from WebGUI::Asset::Wobject::Collaboration. + +=cut + +sub createSubscriptionGroup +{ + my $self = shift; + my $group = WebGUI::Group->new($self->session, "new"); + $group->name($self->getId); + $group->description("The group to store subscriptions for the calendar ".$self->getId); + $group->isEditable(0); + $group->showInForms(0); + $group->deleteGroups([3]); # admins don't want to be auto subscribed to this thing + $self->update({ + groupIdSubscription=>$group->getId + }); +} + + + + + +#################################################################### + +=head2 duplicate ( ) + +Duplicates an Event Calendar. Duplicates all the events in this Event Calendar. + +=cut + +sub duplicate +{ + my $self = shift; + my $newAsset = $self->SUPER::duplicate(@_); + + my @events = $self->getLineage(["descendents"], + { + returnObjects => 1, + includeOnlyClasses => 'WebGUI::Asset::Event', + }); + + + for my $event (@events) + { + $newAsset->addChild($event->get); + } + + return $newAsset; +} + + + + + +#################################################################### + +=head2 getEditForm + +Adds an additional tab for feeds. + +=cut + +sub getEditForm +{ + my $self = shift; + my $session = $self->session; + my $form = $self->SUPER::getEditForm; + my $i18n = WebGUI::International->new($session,"Asset_Calendar"); + + my $tab = $form->addTab("feeds",$i18n->get("feeds")); + + + + return $form; +} + + + + + +#################################################################### + +=head2 getEvent ( assetId ) + +Gets an Event object from the database. Returns a WebGUI::Asset::Event object +or undef if the event cannot be found, or is otherwise unable to be seen from +this Calendar. + +=cut + +sub getEvent +{ + my $self = shift; + my $assetId = shift; + # Warn and return if no assetId + $self->session->errorHandler->warn("WebGUI::Asset::Wobject::Calendar->getEvent :: No asset ID."), return + unless $assetId; + + # ? Perhaps use Stow to cache events ? + + return Asset::newByDynamicClass($self->session, $assetId); +} + + + + + +#################################################################### + +=head2 getEventsIn ( startDate, endDate ) + +Returns a list of Event objects that fall between two dates, ordered by their +start date/time. + +If no Events can be found, returns an empty list. + +This method expects that startDate and endDate are already adjusted for the +user's time zone. + +TODO: Allow WebGUI::DateTime objects to be passed as the parameters. + +=cut + +sub getEventsIn +{ + my $self = shift; + my $start = shift; + my $end = shift; + my $tz = $self->session->user->profileField("timeZone"); + my $events; + + # Warn and return if no startDate or endDate + unless ($start && $end) + { + $self->session->errorHandler->warn("WebGUI::Asset::Wobject::Calendar->getEventsIn() called with not enough arguments at ".join('::',(caller)[1,2])); + return; + } + + # Create objects and adjust for timezone + + my ($startDate,$startTime) = split / /, $start; + my ($endDate,$endTime) = split / /, $end; + + my $startTz = WebGUI::DateTime->new(mysql => $start, time_zone => $tz) + ->set_time_zone("UTC")->toMysql; + my $endTz = WebGUI::DateTime->new(mysql => $end, time_zone => $tz) + ->set_time_zone("UTC")->toMysql; + + my $where = qq{(Event.startTime IS NULL && Event.endTime IS NULL && Event.startDate >= '$startDate' && Event.startDate < '$endDate')} + . qq{ || ( CONCAT(Event.startDate,' ',Event.startTime) >= '$startTz' && CONCAT(Event.startDate,' ',Event.startTime) < '$endTz')}; + my $orderby = "Event.startDate, Event.startTime, Event.endDate, Event.endTime, assetData.title, assetData.assetId"; + + $events = $self->getLineage(["descendants"], + { + returnObjects => 1, + includeOnlyClasses => ['WebGUI::Asset::Event'], + joinClass => 'WebGUI::Asset::Event', + orderByClause => $orderby, + whereClause => $where, + }); + + # ? Perhaps use Stow to cache events ? + #$self->session->errorHandler->warn("Got ".scalar @{$events}." events in ($start -- $end || $startTz -- $endTz)"); + #$self->session->errorHandler->warn("Ref: ".ref $events->[0]); + return @{$events}; +} + + + + + +#################################################################### + +=head2 getFirstEvent ( ) + +Gets the first event in this calendar. Returns the Event object. + +=cut + +sub getFirstEvent +{ + my $self = shift; + my $event; + + $self->session->errorHandler->warn("getFirstEvent is not yet implemented"); + + return $event; +} + + + + + +#################################################################### + +=head2 getLastEvent ( ) + +Gets the last event in this calendar. Returns the Event object. + +=cut + +sub getLastEvent +{ + my $self = shift; + my $event; + + $self->session->errorHandler->warn("getLastEvent is not yet implemented"); + + return $event; +} + + + +#################################################################### + +=head2 prepareView ( ) + +Loads the template to be used by the view() method. + + +=cut + +sub prepareView +{ + my $self = shift; + $self->SUPER::prepareView(); + + my $view = ucfirst lc $self->session->form->param("type") + || ucfirst $self->get("defaultView") + || "Month"; + $self->session->errorHandler->warn("Prepare view ".$view." with template ".$self->get("templateId".$view)); + + my $template = WebGUI::Asset::Template->new($self->session, $self->get("templateId".$view)); + $template->prepare; + + $self->{_viewTemplate} = $template; +} + + + + + +#################################################################### + +=head2 processPropertiesFromFormPost ( ) + +Process the Calendar Edit form. + +Adds a subscription group if none exists. + +Adds / removes feeds from the feed trough. + +=cut + +sub processPropertiesFromFormPost +{ + my $self = shift; + + # The super does most of the real work + $self->SUPER::processPropertiesFromFormPost; + + + unless ($self->get("groupIdSubscribed")) + { + $self->createSubscriptionGroup(); + } + + +} + + + + +#################################################################### + +=head2 view ( ) + +Method called by the www_view method. + +Calls the appropriate viewMonth, viewWeek, or viewDay method to get a template, +and then adds additional global template variables. + +Returns a processed template to be displayed within the page style. + +=cut + +sub view +{ + my $self = shift; + my $session = $self->session; + my $form = $session->form; + my $var; + + + ## INTERRUPT: If user is only a Visitor and we have a cached version + # and is not expired, use it. + + # Get the form parameters + my $params = {}; + $params->{type} = $form->param("type"); + $params->{start} = $form->param("start"); + + + # Set defaults if necessary + unless ($params->{start}) + { + if ($self->get("defaultDate") eq "first") + { + #!! TODO: Get the first event's date + # select startDate from Events + # join assetLineage + # order by startDate ASC, revisionDate DESC + # limit 1 + } + elsif ($self->get("defaultDate") eq "last") + { + #!! TODO: Get the last event's date + # select startDate from Events + # join assetLineage + # order by startDate DESC, revisionDate DESC + # limit 1 + } + else + { + $params->{start} = WebGUI::DateTime->from_epoch(epoch => time(), time_zone => $session->user->profileField("timeZone"))->toMysql; + } + } + $params->{type} ||= $self->get("defaultView") || "Month"; + + + + # Get the template from the appropriate view* method + if (lc $params->{type} eq "month") + { + $var = $self->viewMonth($params); + } + elsif (lc $params->{type} eq "week") + { + $var = $self->viewWeek($params); + } + elsif (lc $params->{type} eq "day") + { + $var = $self->viewDay($params); + } + else + { + ### ERROR ### + } + + + ##### Add any global variables + # Admin + if ($self->session->var->isAdminOn) + { + $var->{'admin'} = 1; + $var->{'adminControls'} = $self->getToolbar; + } + + # Event editor + if ($self->session->user->isInGroup(3)) + { + $var->{'editor'} = 1; + } + + # URLs + $var->{"urlDay"} = "/".$session->url->append($self->get("url") + , "type=day;start=".$params->{start}); + $var->{"urlWeek"} = "/".$session->url->append($self->get("url") + , "type=week;start=".$params->{start}); + $var->{"urlMonth"} = "/".$session->url->append($self->get("url") + , "type=month;start=".$params->{start}); + + # Parameters + $var->{"paramStart"} = $params->{start}; + $var->{"paramType"} = $params->{type}; + + + + ##### Process the template + # If user is only a Visitor and we've gotten this far, update the cache + + # Return the processed template to be displayed for the user + #use Data::Dumper; $session->errorHandler->warn(Dumper $var); + + return $self->processTemplate($var, undef, $self->{_viewTemplate}); +} + + + + + +#################################################################### + +=head2 viewDay ( \%params ) + +Shows the day view. Returns the template parameters as a hash reference. + +%params keys: + +=over 4 + +=item start + +The day to look at. + +=back + +=cut + +sub viewDay +{ + my $self = shift; + my $session = $self->session; + my $params = shift; + my $i18n = WebGUI::International->new($session,"Asset_Calendar"); + my $var = {}; + + ### Get all the events in this time period + # Get the range of the epoch of this day + my $dt = WebGUI::DateTime->new($params->{start}); + $dt->set_locale($i18n->get("locale")); + $dt->truncate( to => "day"); + + my @events = $self->getEventsIn($dt->toMysql,$dt->clone->add(days => 1)->toMysql); + + #### Create the template parameters + # The events + my $pos = -1; + my $last_hour = -1; # Keep track of hours for dividers + for my $event (@events) + { + my $dt = $event->getDateTimeStart; + + my $hour = $dt->clone->truncate(to=>"hour")->hour; + + + # Update position if necessary + # Fill in hour labels + unless ($hour == $last_hour) + { + $pos++; + $last_hour = $hour; + $var->{hours}->[$pos] = { + "hour12" => sprintf("%02d",($hour % 12) || 12), + "hour24" => sprintf("%02d",$hour), + "hourM" => ( $hour < 12 ? "am" : "pm"), + }; + } + + my $eventVar = $event->get; + my %eventDates = $event->getTemplateVars; + push @{$var->{hours}->[$pos]->{events}}, { + # Fill in event stuff + (map { "event".ucfirst($_) => $eventVar->{$_} } keys %$eventVar), + (map { "event".ucfirst($_) => $eventDates{$_} } keys %eventDates), + }; + } + + + # Make the navigation bars + $var->{"pageNextStart"} = $dt->clone->add(days=>1)->toMysql; + $var->{"pageNextUrl"} = "/".$self->get("url")."?type=day;start=" + . $var->{"pageNextStart"}; + $var->{"pagePrevStart"} = $dt->clone->subtract(days=>1)->toMysql; + $var->{"pagePrevUrl"} = "/".$self->get("url")."?type=day;start=" + . $var->{"pagePrevStart"}; + # Some friendly dates + $var->{"dayName"} = $dt->day_name; + $var->{"dayAbbr"} = $dt->day_abbr; + $var->{"dayOfMonth"} = $dt->day_of_month; + $var->{"dayOfWeek"} = $dt->day_of_week; + $var->{"monthName"} = $dt->month_name; + $var->{"monthAbbr"} = $dt->month_abbr; + $var->{"year"} = $dt->year; + $var->{"ymd"} = $dt->ymd; + $var->{"mdy"} = $dt->mdy; + $var->{"dmy"} = $dt->dmy; + $var->{"epoch"} = $dt->epoch; + + + # Return the template parameters + return $var; +} + + + + + + +#################################################################### + +=head2 viewMonth ( \%params ) + +Prepares the month view. Returns the template parameters as a hash reference. + +%params keys: + +=over 4 + +=item start + +A day inside the month to look at. Required. + +=back + +=cut + +sub viewMonth +{ + my $self = shift; + my $session = $self->session; + my $params = shift; + my $i18n = WebGUI::International->new($session,"Asset_Calendar"); + my $var = {}; + my $tz = $session->user->profileField("timeZone"); + my $today = WebGUI::DateTime->new(time)->set_time_zone($tz) + ->toMysqlDate; + + #### Get all the events in this time period + # Get the range of the epoch of this month + my $dt = WebGUI::DateTime->new($params->{start}); + $dt->set_locale($i18n->get("locale")); + $dt->truncate( to => "month"); + + my @events = $self->getEventsIn($dt->toMysql,$dt->clone->add(months => 1)->toMysql); + + + #### Create the template parameters + # The grid + my $first_dow = $session->user->profileField("firstDayOfWeek") || 0; + # 0 - sunday + # 1 - mon + # 2 - tue + # etc... + my $days_in_month = $dt->clone->add(months=>1)->subtract(seconds=>1)->day_of_month; + # Adjustment for first day of week + my $adjust = ( $dt->day_of_week_0 - $first_dow + 1) % 7; + + # First create the days that are in this month + for my $day (0..$days_in_month-1) + { + my $dt_day = $dt->clone->add(days=>$day); + + # Calculate what position this day should be in + my $week = int(($adjust + $dt_day->day_of_month_0) / 7); + my $position = ($adjust + $dt_day->day_of_month_0) % 7; + + # Add the day in the appropriate position + $var->{weeks}->[$week]->{days}->[$position] = { + "dayMonth" => $dt_day->day_of_month, + "dayUrl" => "/" + . $self->get("url") + . "?type=day;start=" + . $dt_day->toMysql, + "dayCurrent" => ($today eq $dt_day->toMysqlDate ? 1 : 0 ), + }; + } + + # Add any remaning trailing empty spaces + push @{$var->{weeks}->[-1]->{days}},undef + until @{$var->{weeks}->[-1]->{days}} >= 7; + + # The events + for my $event (@events) + { + # Get the WebGUI::DateTime objects + my $dt_event_start = $event->getDateTimeStart; + my $dt_event_end = $event->getDateTimeEnd; + + # Prepare the template variables + my %eventVar = %{$event->get}; + %eventVar = (map { "event".ucfirst($_) => $eventVar{$_} } keys %eventVar); + my %eventDates = $event->getTemplateVars; + %eventDates = (map { "event".ucfirst($_) => $eventDates{$_} } keys %eventDates); + + # Make the event show on each day it spans + for my $mday ($dt_event_start->day_of_month_0..$dt_event_end->day_of_month_0) + { + my $week = int(($adjust + $mday) / 7); + my $position = ($adjust + $mday) % 7; + + push @{$var->{weeks}->[$week]->{days}->[$position]->{events}}, { + ## Event data + (%eventVar), + (%eventDates), + }; + } + } + + # Make the navigation bars + my $dt_year = $dt->clone->truncate(to => "year"); + for my $m (0..11) + { + my $dt_month = $dt_year->clone->add(months=>$m); + + push @{$var->{months}}, { + "monthName" => $dt_month->month_name, + "monthAbbr" => $dt_month->month_abbr, + "monthEpoch" => $dt_month->epoch, + "monthUrl" => "/" + . $self->get("url") + . "?type=month;start=" + . $dt_month->toMysql, + "monthCurrent" => ($dt_month->month eq $dt->month ? 1 : 0), + + }; + } + + # Day names + my @dayNames = @{$dt->locale->day_names}[6,0..5]; # Put sunday first + my @dayAbbrs = @{$dt->locale->day_abbreviations}[6,0..5]; + # Take from FirstDOW to the end and put it on the beginning + unshift @dayNames,splice(@dayNames,$first_dow); + unshift @dayAbbrs,splice(@dayAbbrs,$first_dow); + push @{$var->{dayNames}}, + { + "dayName" => $dayNames[$_], + "dayAbbr" => $dayAbbrs[$_], + } + for (0..$#dayNames); + + + $var->{"pageNextYear"} = $dt->year + 1; + $var->{"pageNextUrl"} = "/".$self->get("url")."?type=month;start=" + . $dt->clone->add(years=>1)->toMysql; + $var->{"pagePrevYear"} = $dt->year - 1; + $var->{"pagePrevUrl"} = "/".$self->get("url")."?type=month;start=" + . $dt->clone->subtract(years=>1)->toMysql; + $var->{"monthName"} = $dt->month_name; + $var->{"monthAbbr"} = $dt->month_abbr; + $var->{"year"} = $dt->year; + + + # Return the template + return $var; +} + + + + + + +#################################################################### + +=head2 viewWeek ( \%params ) + +Shows the week view. Returns the template parameters as a hash reference. + +%params keys: + +=over 4 + +=item start + +The day to start this week. + +=back + +=cut + +sub viewWeek +{ + my $self = shift; + my $session = $self->session; + my $params = shift; + my $i18n = WebGUI::International->new($session,"Asset_Calendar"); + my $var = {}; + my $tz = $session->user->profileField("timeZone"); + my $today = WebGUI::DateTime->new(time)->set_time_zone($tz) + ->toMysqlDate; + + + #### Get all the events in this time period + # Get the range of the epoch of this week + my $dt = WebGUI::DateTime->new($params->{start}); + $dt->truncate( to => "day"); + + # Apply First Day of Week settings + # subtract because we want to include the day that was passed + my $first_dow = $session->user->profileField("firstDayOfWeek") || 0; + # 0 - sunday + # 1 - monday + # 2 - tuesday, etc... + $dt->subtract(days => $dt->day_of_week % 7 - $first_dow); + + my $start = $dt->toMysql; + my $dtEnd = $dt->clone->add(days => 7); + my $end = $dtEnd->toMysql; # Clone to prevent saving change + + my @events = $self->getEventsIn($start,$end); + + + #### Create the template parameters + # Some friendly dates + for my $i (0..6) + { + my $day = {}; + my $dt_day = $dt->clone->add(days=>$i); + + $day->{"dayName"} = $dt_day->day_name; + $day->{"dayAbbr"} = $dt_day->day_abbr; + $day->{"dayOfMonth"} = $dt_day->day_of_month; + $day->{"dayOfWeek"} = $dt_day->day_of_week; + $day->{"monthName"} = $dt_day->month_name; + $day->{"monthAbbr"} = $dt_day->month_abbr; + $day->{"year"} = $dt_day->year; + $day->{"ymd"} = $dt_day->ymd; + $day->{"mdy"} = $dt_day->mdy; + $day->{"dmy"} = $dt_day->dmy; + $day->{"epoch"} = $dt_day->epoch; + + if ($dt_day->toMysqlDate eq $today) + { + $day->{"dayCurrent"} = 1; + } + + push @{$var->{days}}, $day; + } + + # The events + for my $event (@events) + { + # ??? TODO ??? Show event in each day it spans a la month view? + # Get the week this event is in, and add it to that week in + # the template variables + my $dt_event = $event->getDateTimeStart; + $dt_event->set_locale($i18n->get("locale")); + + my $day = $dt_event->day_of_week - $dt->day_of_week; + + my $eventVar = $event->get; + my %eventDates = $event->getTemplateVars; + push @{$var->{days}->[$day]->{events}}, { + ## Event information + (map { "event".ucfirst($_) => $eventVar->{$_} } keys %$eventVar), + (map { "event".ucfirst($_) => $eventDates{$_} } keys %eventDates), + }; + } + + # Make the navigation bars + $var->{"pageNextUrl"} = "/".$self->get("url")."?type=week;start=" + . $dt->clone->add(weeks=>1)->toMysql; + $var->{"pagePrevUrl"} = "/".$self->get("url")."?type=week;start=" + . $dt->clone->subtract(weeks=>1)->toMysql; + + $var->{"startMonthName"} = $dt->month_name; + $var->{"startMonthAbbr"} = $dt->month_abbr; + $var->{"startDayOfMonth"} = $dt->day_of_month; + $var->{"startDayName"} = $dt->day_name; + $var->{"startDayAbbr"} = $dt->day_abbr; + $var->{"startYear"} = $dt->year; + + $var->{"endMonthName"} = $dtEnd->month_name; + $var->{"endMonthAbbr"} = $dtEnd->month_abbr; + $var->{"endDayOfMonth"} = $dtEnd->day_of_month; + $var->{"endDayName"} = $dtEnd->day_name; + $var->{"endDayAbbr"} = $dtEnd->day_abbr; + $var->{"endYear"} = $dtEnd->year; + + + # Return the template + return $var; +} + + + + + +#################################################################### + +=head2 unwrapIcal ( text ) + +Unwraps and unescapes an iCalendar string according to RFC 2445, which says that +lines should be wrapped at 75 characters with a CRLF followed by a space, and +that ; , \ and newlines should be escaped. + +=cut + +sub unwrapIcal +{ + my $self = shift; + my $text = shift; + + + +} + + + + + +#################################################################### + +=head2 wrapIcal ( text ) + +Wraps and escapes an iCalendar string according to RFC 2445, which says that +lines should be wrapped at 75 characters with a CRLF followed by a space, and +that ; , \ and newlines should be escaped. + +=cut + +sub wrapIcal +{ + my $self = shift; + my $text = shift; + + $text =~ s/([,;\\])/\\$1/g; + $text =~ s/\n/\\n/g; + + my @text = ($text =~ m/.{0,75}/g); + return join "\x0D\x0A ",@text; +} + + + + + +#################################################################### + +=head2 www_edit ( ) + +Adds a submenu to the default edit page that includes links to Add an Event. + +=cut + +sub www_edit +{ + my $self = shift; + my $session = $self->session; + my $i18n = WebGUI::International->new($session, 'Asset_Calendar'); + + return WebGUI::Privilege::insufficient() unless $self->canEdit; + + $self->getAdminConsole->setHelp("Calendar add/edit", "Calendar"); + + return $self->getAdminConsole->render( + $self->getEditForm->print, + $i18n->get("assetName") + ); +} + + + + + + +#################################################################### + +=head2 www_ical + +Export an iCalendar feed of this Events Calendar's events. + +=cut + +sub www_ical +{ + my $self = shift; + my $session = $self->session; + my $form = $self->session->form; + + #!!! Events from what time period should we show? Default perpage? + # By default show the events for a month + my $type = $form->param("type") || $self->get("defaultView") || "month"; + my $start = $form->param("start"); + my $end = $form->param("end"); + + + my $dt_start; + unless ($start) + { + if ($self->get("defaultDate") eq "first") + { + #!! TODO: Get the first event's date + # select startDate from Events + # join assetLineage + # order by startDate ASC, revisionDate DESC + # limit 1 + } + elsif ($self->get("defaultDate") eq "last") + { + #!! TODO: Get the last event's date + # select startDate from Events + # join assetLineage + # order by startDate DESC, revisionDate DESC + # limit 1 + } + else + { + $dt_start = WebGUI::DateTime->from_epoch(epoch => time(), time_zone => $session->user->profileField("timeZone")); + } + } + else + { + $dt_start = WebGUI::DateTime->new($start)->set_time_zone($session->user->profileField("timeZone")); + } + + + + my $dt_end; + unless ($end) + { + if ($type eq "month") + { + $dt_end = $dt_start->clone->add(months => 1); + } + elsif ($type eq "week") + { + $dt_end = $dt_start->clone->add(weeks => 1); + } + elsif ($type eq "day") + { + $dt_end = $dt_start->clone->add(days => 1); + } + } + else + { + $dt_end = WebGUI::DateTime->new($end)->set_time_zone($session->user->profileField("timeZone")); + } + + + + # Get all the events we're going to display + my @events = $self->getEventsIn($dt_start->toMysql,$dt_end->toMysql); + + + my $ical = qq{BEGIN:VCALENDAR\n} + . qq{PRODID:WebGUI \n} # TODO: Get WebGUI version! + . qq{VERSION:2.0\n}; + + # VEVENT: + for my $event (@events) + { + $ical .= qq{BEGIN:VEVENT\n}; + + # Currently we only need + # UID + my $domain = $session->config->get("sitename")->[0]; + $ical .= qq{UID:}.$event->get("assetId").'@'.$domain."\n"; + + # LAST-MODIFIED (revisionDate) + $ical .= qq{LAST-MODIFIED:} + . WebGUI::DateTime->new($event->get("revisionDate"))->toIcal + . "\n"; + + # CREATED (creationDate) + $ical .= qq{CREATED:} + . WebGUI::DateTime->new($event->get("creationDate"))->toIcal + . "\n"; + + # DTSTART + $ical .= qq{DTSTART:}.$event->getIcalStart."\n"; + + # DTEND + $ical .= qq{DTEND:}.$event->getIcalEnd."\n"; + + # Summary (the title) + # Wrapped at 75 columns + $ical .= $self->wrapIcal("SUMMARY:".$event->get("title"))."\n"; + + # Description (the text) + # Wrapped at 75 columns + $ical .= $self->wrapIcal("DESCRIPTION:".$event->get("description"))."\n"; + + $ical .= qq{END:VEVENT\n}; + } + # ENDVEVENT + + $ical .= qq{END:VCALENDAR\n}; + + + # Set mime of text/icalendar + #$self->session->http->setMimeType("text/plain"); + $self->session->http->setMimeType("text/calendar"); + return $ical; +} + + + + + +#################################################################### + +=head2 www_importIcal + +!!!TODO!!! - This will be a future addition. I'm here to whet your whistle. + +Import an iCalendar file into the Events Calendar. + +=cut + +sub www_importIcal +{ + ### TODO: Everything + + return $_[0]->session->privilege->noAccess; +} + + + + + +#################################################################### + +=head2 www_search ( ) + +Shows the search view + +=cut + +sub www_search +{ + my $self = shift; + my $session = $self->session; + my $form = $session->form; + + # Get the search parameters from the form + my $keywords = $form->param("keywords"); + my $startDate = $form->process("startdate"); + my $endDate = $form->process("enddate"); + my $perpage = $form->param("perpage"); + + my $var = $self->get; + + + # If there is a search to perform + if ($keywords || $startDate || $endDate) + { + my $search = new WebGUI::Search($session); + my %rules = ( + keywords => $keywords, + classes => ['WebGUI::Asset::Event'], + lineage => [$self->get("lineage")], + join => "join Event on assetIndex.assetId=Event.assetId", + columns => ['Event.startDate','Event.startTime'], + ); + + # If the start and/or end dates are not filled in, do not limit + # to a certain time period + $rules{where} .= "Event.startDate >= '$startDate'" + if ($startDate); + $rules{where} .= " && " if ($startDate && $endDate); + $rules{where} .= "Event.endDate <= '$endDate'" + if ($endDate); + + + # Prepare the paginator + my @results = (); + $search->search(\%rules); + my $rs = $search->getResultSet; + while (my $data = $rs->hashRef) + { + if ($self->session->user->userId eq $data->{ownerUserId} || $self->session->user->isInGroup($data->{groupIdView}) || $self->session->user->isInGroup($data->{groupIdEdit})) + { + # Format the date + my $dt = WebGUI::DateTime->new($data->{startDate}." ".($data->{startTime}?$data->{startTime}:"00:00:00")); + $dt->set_time_zone($self->session->user->profileField("timeZone")) + if ($data->{startTime}); + + push(@results, + { + url => $data->{url}, + title => $data->{title}, + synopsis => $data->{synopsis}, + startDate => $dt->strftime('%B %e, %Y'), + }); + } + } + my $p = WebGUI::Paginator->new($self->session,$self->getUrl('func=search;keywords='.$self->session->url->escape($keywords).';startdate='.$startDate.';enddate='.$endDate),$perpage); + $p->setDataByArrayRef(\@results); + $p->appendTemplateVars($var); + $var->{results} = $p->getPageData; + } + + # Prepare the form + my $default_dt = WebGUI::DateTime->new(time); + my $default_start = $default_dt->toMysqlDate; + my $default_end = $default_dt->add(years => 1)->toMysqlDate; + + $var->{"form.header"} = WebGUI::Form::formHeader($session, + { + action => $self->getUrl, + }) + . WebGUI::Form::hidden($self->session, + { + name => "func", + value => "search", + }); + + $var->{"form.footer"} = WebGUI::Form::formFooter($session); + + $var->{"form.keywords"} = WebGUI::Form::text($session, + { + name => "keywords", + value => $keywords, + }); + $var->{"form.perpage"} = WebGUI::Form::text($session, + { + name => "perpage", + value => $perpage, + }); + $var->{"form.startDate"} = WebGUI::Form::date($session, + { + name => "startDate", + value => $startDate, + defaultValue => $default_start, + }); + $var->{"form.endDate"} = WebGUI::Form::date($session, + { + name => "endDate", + value => $endDate, + defaultValue => $default_end, + }); + + $var->{"form.submit"} = WebGUI::Form::submit($session, + { + name => "submit", + value => "Search", + }); + + $self->session->http->sendHeader; + my $template = WebGUI::Asset::Template->new($self->session,$self->get("templateIdSearch")); + my $style = $self->session->style->process("~~~",$self->get("styleTemplateId")); + my ($head, $foot) = split("~~~",$style); + $self->session->output->print($head, 1); + $self->session->output->print($self->processTemplate($var, undef, $template)); + $self->session->output->print($foot, 1); + return "chunked"; +} + + + + + + + +=head1 Templates + +The templates provided by this Wobject and the parameters they contain + + + + +=head1 BUGS / RFE + +In the calendar edit form on the Default View field on the display tab, put the +current date of the first and last event, so that user's understand EXACTLY +where the calendar will be going. + +AM/PM is not localized + +Ordinal numbers (1st, 2nd, 3rd, etc...) are not handled, due to translation +issues. + +TODO: More abstraction so that certain methods can be tested. + + +=cut + +1; diff --git a/lib/WebGUI/i18n/English/Asset_Calendar.pm b/lib/WebGUI/i18n/English/Asset_Calendar.pm new file mode 100755 index 000000000..681b09d74 --- /dev/null +++ b/lib/WebGUI/i18n/English/Asset_Calendar.pm @@ -0,0 +1,274 @@ +package WebGUI::i18n::English::Asset_Calendar; + +our $I18N = { + #'key1' => { + # message => q{}, + # lastUpdated => 0, + # context => q{}, + #}, + + +#################### DATETIME LOCALE #################### + 'locale' => { + message => q{en_US}, + lastUpdated => 0, + context => q{The ISO locale name for month and day labels.}, + }, + + +#################### CALENDAR PROPERTIES FIELDS #################### + + ##### Subscriber Notify Offset ##### + 'subscriberNotifyOffset label' => { + message => q{Subscriber Notify Offset}, + lastUpdated => 0, + context => q{The label for the Subscriber Notify Offset field}, + }, + 'subscriberNotifyOffset description' => { + message => q{Number of days before a subscriber is notified that an event is about to happen.}, + lastUpdated => 0, + context => q{The Hover Help for the Subscriber Notify Offset field}, + }, + + + +#################### CALENDAR DISPLAY FIELDS #################### + + ##### Default View ##### + 'defaultView label' => { + message => q{Default View}, + lastUpdated => 0, + context => q{The label for the Default View field}, + }, + 'defaultView description' => { + message => q{The default view to show the user.}, + lastUpdated => 0, + context => q{Hover Help for the Default View field}, + }, + 'defaultView value month' => { + message => q{Month}, + lastUpdated => 0, + context => q{A value for the Default View field.}, + }, + 'defaultView value week' => { + message => q{Week}, + lastUpdated => 0, + context => q{A value for the Default View field.}, + }, + 'defaultView value day' => { + message => q{Day}, + lastUpdated => 0, + context => q{A value for the Default View field.}, + }, + + + ##### Default Date ##### + 'defaultDate label' => { + message => q{Default Date}, + lastUpdated => 0, + context => q{The label for the default date field.}, + }, + 'defaultDate description' => { + message => q{The default date to show the user.}, + lastUpdated => 0, + context => q{Hover Help for the default date field.}, + }, + 'defaultDate value current' => { + message => q{The current date}, + lastUpdated => 0, + context => q{A value for the Default Date display field.}, + }, + 'defaultDate value first' => { + message => q{The first event in the calendar}, + lastUpdated => 0, + context => q{A value for the Default Date display field.}, + }, + 'defaultDate value last' => { + message => q{The last event in the calendar}, + lastUpdated => 0, + context => q{A value for the Default Date display field.}, + }, + + + ##### Visitor Cache Timeout ##### + 'visitorCacheTimeout label' => { + message => q{Visitor Cache Timeout}, + lastUpdated => 0, + context => q{The label for the Visitor Cache Timeout field.}, + }, + 'visitorCacheTimeout description' => { + message => q{The number of minutes before the visitor cache will be refreshed.}, + lastUpdated => 0, + context => q{Hover Help for the Visitor Cache Timeout field.}, + }, + + + + + + +#################### CALENDAR SECURITY FIELDS #################### + + ##### Group to add/edit events ##### + 'groupIdEventEdit label' => { + message => q{Who can add/edit Events?}, + lastUpdated => 0, + context => q{The label for the Group to Edit Events field.}, + }, + 'groupIdEventEdit description' => { + message => q{Members of this group can add and edit Events in this calendar.}, + lastUpdated => 0, + context => q{Hover Help for the Group to Edit Events field.}, + }, + + + + +#################### CALENDAR FEEDS FIELDS #################### + + ##### Feeds tab ##### + 'feeds' => { + message => q{Feeds}, + lastUpdated => 0, + context => q{The label for the Feeds tab.}, + }, + + + +#################### TEMPLATES #################### + + + + ##### Template - Month ##### + 'templateIdMonth label' => { + message => q{Month View Template}, + lastUpdated => 0, + context => q{The label for the default month template.}, + }, + 'templateIdMonth description' => { + message => q{This template shows the calendar in month form.}, + lastUpdated => 0, + context => q{Hover Help for the default month template.}, + }, + + ##### Template - Week ##### + 'templateIdWeek label' => { + message => q{Week View Template}, + lastUpdated => 0, + context => q{The label for the default Week template.}, + }, + 'templateIdWeek description' => { + message => q{This template shows the calendar in Week form.}, + lastUpdated => 0, + context => q{Hover Help for the default Week template.}, + }, + + ##### Template - Day ##### + 'templateIdDay label' => { + message => q{Day View Template}, + lastUpdated => 0, + context => q{The label for the default Day template.}, + }, + 'templateIdDay description' => { + message => q{This template shows the calendar in Day form.}, + lastUpdated => 0, + context => q{Hover Help for the default Day template.}, + }, + + ##### Template - Event ##### + 'templateIdEvent label' => { + message => q{Event Details Template}, + lastUpdated => 0, + context => q{The label for the default Event template.}, + }, + 'templateIdEvent description' => { + message => q{The template to show the details for an event.}, + lastUpdated => 0, + context => q{Hover Help for the default Event template.}, + }, + + ##### Template - EventEdit ##### + 'templateIdEventEdit label' => { + message => q{Event Edit Template}, + lastUpdated => 0, + context => q{The label for the default Event Edit template.}, + }, + 'templateIdEventEdit description' => { + message => q{The template to Edit Events.}, + lastUpdated => 0, + context => q{Hover Help for the default Event Edit template.}, + }, + + ##### Template - Search ##### + 'templateIdSearch label' => { + message => q{Search View Template}, + lastUpdated => 0, + context => q{The label for the default Search template.}, + }, + 'templateIdSearch description' => { + message => q{This template shows the calendar in Search form.}, + lastUpdated => 0, + context => q{Hover Help for the default Search template.}, + }, + + + + ##### Template - Print Month ##### + 'templateIdPrintMonth label' => { + message => q{Print Month Template}, + lastUpdated => 0, + context => q{The label for the default month template.}, + }, + 'templateIdPrintMonth description' => { + message => q{This template to print the calendar in month form.}, + lastUpdated => 0, + context => q{Hover Help for the default month template.}, + }, + + ##### Template - Print Week ##### + 'templateIdPrintWeek label' => { + message => q{Print Week Template}, + lastUpdated => 0, + context => q{The label for the default Week template.}, + }, + 'templateIdPrintWeek description' => { + message => q{This template to print the calendar in Week form.}, + lastUpdated => 0, + context => q{Hover Help for the default Week template.}, + }, + + ##### Template - Print Day ##### + 'templateIdPrintDay label' => { + message => q{Print Day Template}, + lastUpdated => 0, + context => q{The label for the default Day template.}, + }, + 'templateIdPrintDay description' => { + message => q{This template to print the calendar in Day form.}, + lastUpdated => 0, + context => q{Hover Help for the default Day template.}, + }, + + ##### Template - Print Event ##### + 'templateIdPrintEvent label' => { + message => q{Print Event Details Template}, + lastUpdated => 0, + context => q{The label for the default Event template.}, + }, + 'templateIdPrintEvent description' => { + message => q{The template to print the details for an event.}, + lastUpdated => 0, + context => q{Hover Help for the default Event template.}, + }, + + + +#################### ASSET NAME #################### + 'assetName' => { + message => q{Calendar}, + lastUpdated => 1131394072, + }, + +}; + +1; diff --git a/lib/WebGUI/i18n/English/Asset_Event.pm b/lib/WebGUI/i18n/English/Asset_Event.pm index 6e3163816..1e7e8f9b3 100644 --- a/lib/WebGUI/i18n/English/Asset_Event.pm +++ b/lib/WebGUI/i18n/English/Asset_Event.pm @@ -1,328 +1,30 @@ package WebGUI::i18n::English::Asset_Event; our $I18N = { - - 'trash after' => { - message => q|Trash After|, - lastUpdated => 0, - context=> q|a label for the workflow activity property that sets how long old events stick around| + #'key1' => { + # message => q{}, + # lastUpdated => 0, + # context => q{}, + #}, + +#################### DATETIME LOCALE #################### + 'locale' => { + message => q{en_US}, + lastUpdated => 0, + context => q{The ISO locale name for month and day labels.}, }, - - 'trash after help' => { - message => q|How long should old events stay in the calendar before being trashed?|, - lastUpdated => 0, - context=> q|hover help for the trash after field| + + + + + + + +#################### ASSET NAME #################### + 'assetName' => { + message => q{Event}, + lastUpdated => 1131394072, }, +}; - 'trash expired events' => { - message => q|Trash Expired Events|, - lastUpdated => 0, - context=> q|The label for the workflow activity that trashes old events.| - }, - - '72' => { - message => q|Event, Add/Edit|, - lastUpdated => 1038887363 - }, - - 'Title description' => { - message => q|The title for this event.|, - lastUpdated => 1119072889, - }, - - 'Description description' => { - message => q|The activities of this event or information about where the event is to be held.|, - lastUpdated => 1119072889, - }, - - 'Start Date description' => { - message => q|The date and time when the event begins.|, - lastUpdated => 1119072889, - }, - - 'End Date description' => { - message => q|The date and time when the event ends.|, - lastUpdated => 1119072889, - }, - - 'Recurs every description' => { - message => q|How many times and how often the event recurs.|, - lastUpdated => 1119072889, - }, - - '73' => { - message => q|

Event Assets are created when Events are added to an Events Calendar.

|, - lastUpdated => 1130294387 - }, - - '96' => { - message => q|Event Template|, - lastUpdated => 1078568518 - }, - - 'title' => { - message => q|The title of this event.|, - lastUpdated => 1149392729, - }, - - 'start.label' => { - message => q|The translated label for the start date.|, - lastUpdated => 1149392729, - }, - - 'eventStartDate' => { - message => q|The date and time this event starts as an epoch.|, - lastUpdated => 1164744850, - }, - - 'start.date' => { - message => q|The date this event starts in the user's default date format.|, - lastUpdated => 1164744850, - }, - - 'start.time' => { - message => q|The time this event starts in the user's default time format.|, - lastUpdated => 1164744852, - }, - - 'eventEndDate' => { - message => q|The date and time this event ends as an epoch.|, - lastUpdated => 1164744850, - }, - - 'end.date' => { - message => q|The date this event ends in the user's default date format.|, - lastUpdated => 1164744853, - }, - - 'end.time' => { - message => q|The time this event ends in the user's default time format.|, - lastUpdated => 1164744855, - }, - - 'end.label' => { - message => q|The translated label for the end date.|, - lastUpdated => 1149392729, - }, - - 'canEdit' => { - message => q|A condition indicating whether the current user can edit an event.|, - lastUpdated => 1149392729, - }, - - 'edit.url' => { - message => q|The URL to edit this event.|, - lastUpdated => 1149392729, - }, - - 'edit.label' => { - message => q|The translated label for the edit URL.|, - lastUpdated => 1149392729, - }, - - 'delete.url' => { - message => q|The URL to delete this event.|, - lastUpdated => 1149392729, - }, - - 'delete.label' => { - message => q|The translated label for the delete URL.|, - lastUpdated => 1149392729, - }, - - 'description' => { - message => q|The description of this event.|, - lastUpdated => 1149392729, - }, - - 'eventLocation' => { - message => q|Where the event will occur.|, - lastUpdated => 1149392729, - }, - - - '97' => { - message => q|

The following is the list of template variables available in when displaying an event from the calendar. -

-|, - lastUpdated => 1149392756, - }, - - '519' => { - 'lastUpdated' => 1108397891, - 'message' => 'Notify a group when a registration occurs?' - }, - '524' => { - 'lastUpdated' => 1108397891, - 'message' => 'Reminders END' - }, - '523' => { - 'lastUpdated' => 1108397891, - 'message' => 'Reminders Start' - }, - '520' => { - 'lastUpdated' => 1108397891, - 'message' => 'Registration Starts' - }, - '521' => { - 'lastUpdated' => 1108397891, - 'message' => 'Registration Ends' - }, - '575' => { - 'lastUpdated' => 1031514049, - 'message' => 'Edit' - }, - '526' => { - 'lastUpdated' => 1108397891, - 'message' => 'Fee for first attendee' - }, - '532' => { - 'lastUpdated' => 1108397891, - 'message' => 'Recurring Email Reminder Template' - }, - '522' => { - 'lastUpdated' => 1108397891, - 'message' => 'May users subscribe to Event Reminders?' - }, - '576' => { - 'lastUpdated' => 1031514049, - 'message' => 'Delete' - }, - '525' => { - 'lastUpdated' => 1108397891, - 'message' => 'Add event item to Shopping Cart upon Registration?' - }, - '533' => { - 'lastUpdated' => 1108397891, - 'message' => 'Group that may register for this Event' - }, - '512' => { - 'lastUpdated' => 1108397891, - 'message' => 'Description' - }, - '75' => { - 'lastUpdated' => 1031514049, - 'message' => 'Which do you wish to do?' - }, - '517' => { - 'lastUpdated' => 1108397891, - 'message' => 'May users unregister for this event?' - }, - '534' => { - 'lastUpdated' => 1108397891, - 'message' => 'Group that may subscribe to Event reminders' - }, - '531' => { - 'lastUpdated' => 1108397891, - 'message' => 'Registration Notification Template' - }, - '14' => { - 'lastUpdated' => 1031514049, - 'message' => 'Start Date' - }, - '529' => { - 'lastUpdated' => 1108397891, - 'message' => 'Registration Confirmation (Accept/Cancel) Template' - }, - '516' => { - 'lastUpdated' => 1108397891, - 'message' => 'May users register for this event?' - }, - '703' => { - 'lastUpdated' => 1031514049, - 'message' => 'Year(s)' - }, - '700' => { - 'lastUpdated' => 1031514049, - 'message' => 'Day(s)' - }, - '78' => { - 'lastUpdated' => 1031514049, - 'message' => 'Don\'t delete anything, I made a mistake.' - }, - 'assetName' => { - 'lastUpdated' => 1128639297, - 'message' => 'Event' - }, - '702' => { - 'lastUpdated' => 1031514049, - 'message' => 'Month(s)' - }, - '535' => { - 'lastUpdated' => 1108397891, - 'message' => 'Group to notify upon Registration' - }, - '42' => { - 'lastUpdated' => 1031514049, - 'message' => 'Please Confirm' - }, - '701' => { - 'lastUpdated' => 1031514049, - 'message' => 'Week(s)' - }, - '77' => { - 'lastUpdated' => 1031514049, - 'message' => 'Delete this event and all of its recurrences.' - }, - '514' => { - 'lastUpdated' => 1108397891, - 'message' => 'Event Ends' - }, - '13' => { - 'lastUpdated' => 1031514049, - 'message' => 'Edit Event' - }, - '513' => { - 'lastUpdated' => 1108397891, - 'message' => 'Event Starts' - }, - '515' => { - 'lastUpdated' => 1108397891, - 'message' => 'Event Location' - }, - '515 description' => { - 'lastUpdated' => 1119073282, - 'message' => 'Where the event will occur' - }, - '518' => { - 'lastUpdated' => 1108397891, - 'message' => 'Present EULA-type accept/cancel form
for user to confirm registration?' - }, - '9' => { - 'lastUpdated' => 1031514049, - 'message' => 'until' - }, - '15' => { - 'lastUpdated' => 1031514049, - 'message' => 'End Date' - }, - '8' => { - 'lastUpdated' => 1031514049, - 'message' => 'This event recurs every' - }, - '4' => { - 'lastUpdated' => 1031514049, - 'message' => 'Happens only once.' - }, - '527' => { - 'lastUpdated' => 1108397891, - 'message' => 'Fee for second attendee' - }, - '528' => { - 'lastUpdated' => 1108397891, - 'message' => 'Number of Seats Available (-1 for unlimited)' - }, - '530' => { - 'lastUpdated' => 1119073382, - 'message' => 'Event Template' - }, - '530 description' => { - 'lastUpdated' => 1119073378, - 'message' => 'Template used to display the information about the event' - }, - '76' => { - 'lastUpdated' => 1031514049, - 'message' => 'Delete only this event.' - } - }; 1; diff --git a/www/extras/wobject/Calendar/images/delete.gif b/www/extras/wobject/Calendar/images/delete.gif new file mode 100755 index 000000000..030394f8b Binary files /dev/null and b/www/extras/wobject/Calendar/images/delete.gif differ diff --git a/www/extras/wobject/Calendar/images/more.gif b/www/extras/wobject/Calendar/images/more.gif new file mode 100755 index 000000000..e51220179 Binary files /dev/null and b/www/extras/wobject/Calendar/images/more.gif differ diff --git a/www/extras/wobject/Calendar/images/start.jpg b/www/extras/wobject/Calendar/images/start.jpg new file mode 100755 index 000000000..31d930848 Binary files /dev/null and b/www/extras/wobject/Calendar/images/start.jpg differ diff --git a/www/extras/wobject/Calendar/images/stop.jpg b/www/extras/wobject/Calendar/images/stop.jpg new file mode 100755 index 000000000..b3d855f1b Binary files /dev/null and b/www/extras/wobject/Calendar/images/stop.jpg differ