diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt
index 598406d0f..57483294b 100644
--- a/docs/changelog/7.x.x.txt
+++ b/docs/changelog/7.x.x.txt
@@ -40,6 +40,9 @@
- WebGUI::TabForm->addTab now returns the WebGUI::HTMLForm created.
- WebGUI::AssetLineage::getLineage can now limit the number of records returned
- fix: IP addresses for adminModeSubnets not using X-Forwarded-For properly
+ - The Events Calendar is now the new Calendar with some fun new features.
+ All your existing Events Calendars will be migrated automatically.
+ *** PLEASE READ THE GOTCHAS ***
7.2.3
- fix: minor bug with new template vars in Auth::createAccount
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 ee8e54c17..258dd95c9 100644
--- a/docs/upgrades/upgrade_7.2.3-7.3.0.pl
+++ b/docs/upgrades/upgrade_7.2.3-7.3.0.pl
@@ -23,9 +23,9 @@ addWikiAssets($session);
deleteOldFiles($session);
addFileFieldsToDataForm($session);
makeRSSFromParentAlwaysHidden($session);
-#addNewCalendar($session);
-#migrateCalendars($session);
-#removeOldCalendar($session);
+addNewCalendar($session);
+migrateCalendars($session);
+removeOldCalendar($session);
finish($session); # this line required
#-------------------------------------------------
@@ -121,6 +121,7 @@ CREATE TABLE `Event` (
`assetId` varchar(22) NOT NULL,
`revisionDate` bigint(20) unsigned NOT NULL,
`feedId` varchar(22) default NULL,
+ `feedUid` varchar(255) default NULL,
`startDate` date default NULL,
`endDate` date default NULL,
`userDefined1` text,
diff --git a/lib/WebGUI/Asset/Event.pm b/lib/WebGUI/Asset/Event.pm
index 9ccfcb2bf..a7d9bb867 100644
--- a/lib/WebGUI/Asset/Event.pm
+++ b/lib/WebGUI/Asset/Event.pm
@@ -97,6 +97,14 @@ sub definition {
fieldType => "Text",
defaultValue => undef,
},
+ 'feedId' => {
+ fieldType => "Text",
+ defaultValue => undef,
+ },
+ 'feedUid' => {
+ fieldType => "Text",
+ defaultValue => undef,
+ },
);
diff --git a/lib/WebGUI/Asset/Wobject/Calendar.pm b/lib/WebGUI/Asset/Wobject/Calendar.pm
index a61a5a55b..757a6212b 100644
--- a/lib/WebGUI/Asset/Wobject/Calendar.pm
+++ b/lib/WebGUI/Asset/Wobject/Calendar.pm
@@ -27,6 +27,7 @@ use WebGUI::DateTime;
use base 'WebGUI::Asset::Wobject';
use DateTime;
+use JSON;
=head1 Name
@@ -348,7 +349,7 @@ sub duplicate
my @events = $self->getLineage(["descendents"],
{
returnObjects => 1,
- includeOnlyClasses => 'WebGUI::Asset::Event',
+ includeOnlyClasses => ['WebGUI::Asset::Event'],
});
@@ -368,7 +369,10 @@ sub duplicate
=head2 getEditForm
-Adds an additional tab for feeds.
+Adds an additional tab for feeds.
+
+TODO: Abstract the Javascript enough to export into extras/yui-webgui for use
+in other areas.
=cut
@@ -379,10 +383,146 @@ sub getEditForm
my $form = $self->SUPER::getEditForm;
my $i18n = WebGUI::International->new($session,"Asset_Calendar");
- my $tab = $form->addTab("feeds",$i18n->get("feeds"));
+ my $tab = $form->addTab("feeds",$i18n->get("feeds"));
+ $tab->raw("
");
+
+
+ $tab->raw(<<'ENDJS');
+
+ENDJS
+
+
+ $tab->raw(<<'ENDHTML');
+ Add a feed
+
+
+
+
+
+
+ Feed URL
+ Status
+ Last Updated
+
+
+
+ENDHTML
+ # Add the existing feeds
+ my $feeds = $self->getFeeds();
+ $tab->raw('');
+
+
+ $tab->raw(" ");
return $form;
}
@@ -481,6 +621,31 @@ sub getEventsIn
+####################################################################
+
+=head2 getFeeds ( )
+
+Gets a hashref of hashrefs of all the feeds attached to this calendar.
+
+TODO: Format lastUpdated into the user's time zone
+
+=cut
+
+sub getFeeds
+{
+ my $self = shift;
+
+ return $self->session->db->buildHashRefOfHashRefs(
+ "select * from Calendar_feeds where assetId=?",
+ [$self->get("assetId")],
+ "feedId"
+ );
+}
+
+
+
+
+
####################################################################
=head2 getFirstEvent ( )
@@ -540,7 +705,7 @@ sub 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));
+ #$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;
@@ -566,9 +731,9 @@ Adds / removes feeds from the feed trough.
sub processPropertiesFromFormPost
{
- my $self = shift;
-
- # The super does most of the real work
+ my $self = shift;
+ my $session = $self->session;
+ my $form = $self->session->form;
$self->SUPER::processPropertiesFromFormPost;
@@ -578,6 +743,33 @@ sub processPropertiesFromFormPost
}
+ ### Get feeds from the form
+ # Workaround WebGUI::Session::Form->param bug
+ my %feeds;
+ $feeds{$_}++
+ for map { s/^feeds-//; $_; } grep /^feeds-/,($form->param());
+ my @feeds = keys %feeds;
+
+ # Delete old feeds that are not in @feeds
+ for my $feedId ($session->db->buildArray("select feedId from Calendar_feeds where assetId=?",[$self->get("assetId")]))
+ {
+ unless (grep /^$feedId$/, @feeds)
+ {
+ $session->db->write("delete from Calendar_feeds where feedId=? and assetId=?",[$feedId,$self->get("assetId")]);
+ }
+ }
+
+
+ # Create new feeds
+ for my $feedId (grep /^new(\d+)/, @feeds)
+ {
+ $session->db->setRow("Calendar_feeds","feedId",{
+ feedId => "new",
+ assetId => $self->get("assetId"),
+ url => $form->param("feeds-".$feedId),
+ feedType => "ical",
+ });
+ }
}
@@ -616,26 +808,26 @@ sub view
# Set defaults if necessary
unless ($params->{start})
{
- if ($self->get("defaultDate") eq "first")
- {
+ #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")
- {
+ #}
+ #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
- {
+ #}
+ #else
+ #{
$params->{start} = WebGUI::DateTime->from_epoch(epoch => time(), time_zone => $session->user->profileField("timeZone"))->toMysql;
- }
+ #}
}
$params->{type} ||= $self->get("defaultView") || "Month";
@@ -1172,34 +1364,51 @@ sub www_ical
#!!! 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 $type = $form->param("type") || lc($self->get("defaultView")) || "month";
my $start = $form->param("start");
my $end = $form->param("end");
+
+
+ #!!! KLUDGE:
+ # An "adminId" may be passed as a parameter in order to facilitate
+ # calls between calendars on the same server getting administrator
+ # privileges
+ # I do not know how dangerous this could possibly be, so THIS MUST
+ # CHANGE
+ my $adminId = $form->param("adminId");
+ if ($adminId
+ && ($self->session->db->quickArray("SELECT value FROM userSessionScratch WHERE sessionId=? and name=?",[$adminId,$self->get("assetId")]))[0] eq "SPECTRE")
+ {
+ $self->session->user({userId => 3});
+ }
+ #/KLUDGE
+
+
my $dt_start;
unless ($start)
{
- if ($self->get("defaultDate") eq "first")
- {
+ #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")
- {
+ #}
+ #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
- {
+ #}
+ #else
+ #{
$dt_start = WebGUI::DateTime->from_epoch(epoch => time(), time_zone => $session->user->profileField("timeZone"));
- }
+ #}
}
else
{
@@ -1211,18 +1420,18 @@ sub www_ical
my $dt_end;
unless ($end)
{
- if ($type eq "month")
- {
+ #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);
- }
+ #}
+ #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
{
@@ -1246,32 +1455,41 @@ sub www_ical
# Currently we only need
# UID
+ # TODO: Use feedUid if one exists
my $domain = $session->config->get("sitename")->[0];
- $ical .= qq{UID:}.$event->get("assetId").'@'.$domain."\n";
+ $ical .= qq{UID:}.$event->get("assetId").'@'.$domain."\x0D\x0A";
# LAST-MODIFIED (revisionDate)
$ical .= qq{LAST-MODIFIED:}
. WebGUI::DateTime->new($event->get("revisionDate"))->toIcal
- . "\n";
+ . "\x0D\x0A";
# CREATED (creationDate)
$ical .= qq{CREATED:}
. WebGUI::DateTime->new($event->get("creationDate"))->toIcal
- . "\n";
+ . "\x0D\x0A";
# DTSTART
- $ical .= qq{DTSTART:}.$event->getIcalStart."\n";
+ $ical .= qq{DTSTART:}.$event->getIcalStart."\x0D\x0A";
# DTEND
- $ical .= qq{DTEND:}.$event->getIcalEnd."\n";
+ $ical .= qq{DTEND:}.$event->getIcalEnd."\x0D\x0A";
# Summary (the title)
# Wrapped at 75 columns
- $ical .= $self->wrapIcal("SUMMARY:".$event->get("title"))."\n";
+ $ical .= $self->wrapIcal("SUMMARY:".$event->get("title"))."\x0D\x0A";
# Description (the text)
# Wrapped at 75 columns
- $ical .= $self->wrapIcal("DESCRIPTION:".$event->get("description"))."\n";
+ $ical .= $self->wrapIcal("DESCRIPTION:".$event->get("description"))."\x0D\x0A";
+
+
+
+ # X-WEBGUI lines
+ $ical .= "X-WEBGUI-GROUPIDVIEW:".$event->get("groupIdView")."\x0D\x0A";
+ $ical .= "X-WEBGUI-GROUPIDEDIT:".$event->get("groupIdEdit")."\x0D\x0A";
+ $ical .= "X-WEBGUI-URL:".$event->get("url")."\x0D\x0A";
+
$ical .= qq{END:VEVENT\n};
}
diff --git a/lib/WebGUI/DateTime.pm b/lib/WebGUI/DateTime.pm
index 19d034aa8..e920aacea 100755
--- a/lib/WebGUI/DateTime.pm
+++ b/lib/WebGUI/DateTime.pm
@@ -47,6 +47,7 @@ dealing with time zones.
=cut
+
#######################################################################
=head2 new ( string )
diff --git a/lib/WebGUI/Workflow/Activity/CalendarUpdateFeeds.pm b/lib/WebGUI/Workflow/Activity/CalendarUpdateFeeds.pm
new file mode 100755
index 000000000..408f08ebb
--- /dev/null
+++ b/lib/WebGUI/Workflow/Activity/CalendarUpdateFeeds.pm
@@ -0,0 +1,341 @@
+package WebGUI::Workflow::Activity::CalendarUpdateFeeds;
+
+
+=head1 LEGAL
+
+ -------------------------------------------------------------------
+ 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
+ -------------------------------------------------------------------
+
+=cut
+
+use strict;
+use warnings;
+use base 'WebGUI::Workflow::Activity';
+
+use WebGUI::Asset::Wobject::Calendar;
+use WebGUI::Asset::Event;
+use WebGUI::DateTime;
+
+use LWP::UserAgent;
+
+
+=head1 NAME
+
+Package WebGUI::Workflow::Activity::CalendarUpdateFeeds;
+
+=head1 DESCRIPTION
+
+Imports calendar events from Calendar feeds.
+
+=head1 SYNOPSIS
+
+See WebGUI::Workflow::Activity for details on how to use any activity.
+
+=head1 METHODS
+
+These methods are available from this class:
+
+=cut
+
+
+#-------------------------------------------------------------------
+
+=head2 definition ( session, definition )
+
+See WebGUI::Workflow::Activity::defintion() for details.
+
+=cut
+
+sub definition {
+ my $class = shift;
+ my $session = shift;
+ my $definition = shift;
+ my $i18n = WebGUI::International->new($session, "Asset_Calendar");
+ push(@{$definition}, {
+ name=>$i18n->get("workflow updateFeeds"),
+ properties=> { }
+ });
+ return $class->SUPER::definition($session,$definition);
+}
+
+
+#-------------------------------------------------------------------
+
+=head2 execute ( )
+
+See WebGUI::Workflow::Activity::execute() for details.
+
+=cut
+
+sub execute {
+ my $self = shift;
+ $self->session->user({userId => 3});
+
+
+ my $ua = LWP::UserAgent->new(agent => "WebGUI");
+ my $dt = WebGUI::DateTime->new(time)->toMysql;
+
+ my $sth = $self->session->db->prepare("select * from Calendar_feeds");
+ $sth->execute();
+
+
+ FEED:while (my $feed = $sth->hashRef)
+ {
+ #!!! KLUDGE - If the feed is on the same server, set a scratch value
+ # I do not know how dangerous this is, so THIS MUST CHANGE!
+ # Preferably: Spectre would add a userSession to the database,
+ # and send the appropriate cookie with the request.
+ my $sitename = $self->session->config->get("sitename")->[0];
+ if ($feed->{url} =~ m{http://[^/]*$sitename})
+ {
+ my $sessionId = $self->session->id->generate;
+ $feed->{url} .= ";adminId=".$sessionId;
+ $self->session->db->write("INSERT INTO userSessionScratch (sessionId,name,value) VALUES (?,?,?)",
+ [$sessionId,$feed->{assetId},"SPECTRE"]);
+ }
+ #/KLUDGE
+ #warn "FEED URL: ".$feed->{url} ."\n";
+
+
+
+ ## Somebody point me to a DECENT iCalendar parser...
+ # Text::vFile perhaps?
+
+ # Get the feed
+ my $response = $ua->get($feed->{url});
+
+ if ($response->is_success)
+ {
+ my $data = $response->content;
+
+ # If doesn't start with BEGIN:VCALENDAR then error
+ unless ($data =~ /^BEGIN:VCALENDAR/i)
+ {
+ # Update the result and last updated fields
+ $self->session->db->write("update Calendar_feeds set lastResult=?,lastUpdated=? where feedId=?",
+ ["Not an iCalendar feed",$dt,$feed->{feedId}]);
+ next FEED;
+ }
+
+
+ my $active = 0; # Parser on/off
+ my %current_event = ();
+ my $current_entry = "";
+ my %events;
+ my $line_number = 0;
+ for my $line (split /[\r\n]+/,$data)
+ {
+ chomp $line;
+ $line_number++;
+ next unless $line =~ /\w/;
+
+ #warn "LINE $line_number: $line\n";
+
+ if ($line =~ /^BEGIN:VEVENT$/i)
+ {
+ $active = 1;
+ }
+ elsif ($line =~ /^END:VEVENT$/i)
+ {
+ $active = 0;
+ # Flush event
+ my $uid = lc $current_event{uid}[1];
+ delete $current_event{uid};
+ $events{$uid} = {%current_event};
+ %current_event = ();
+ }
+ elsif ($line =~ /^ /)
+ {
+ # Add to entry data
+ $current_entry .= substr $line, 1;
+ }
+ else
+ {
+ # Flush old entry
+ # KEY;ATTRIBUTE=VALUE;ATTRIBUTE=VALUE:KEYVALUE
+ my ($key_attrs,$value) = split /:/,$current_entry,2;
+
+ my @attrs = split /;/, $key_attrs;
+ my $key = shift @attrs;
+ my %attrs;
+ while (my $attribute = shift @attrs)
+ {
+ my ($attr_key, $attr_value) = split /=/, $attribute, 2;
+ $attrs{lc $attr_key} = $attr_value;
+ }
+
+ # Unescape value
+
+
+ $current_event{lc $key} = [\%attrs,$value];
+
+ # Start new entry
+ $current_entry = $line;
+ }
+ }
+
+ my $added = 0;
+ my $updated = 0;
+ for my $id (keys %events)
+ {
+ #use Data::Dumper;
+ #warn "EVENT: $id; ".Dumper $events{$id};
+
+ # Prepare event data
+ my $properties = {
+ feedUid => $id,
+ feedId => $feed->{feedId},
+ description => $events{$id}->{description}->[1],
+ title => $events{$id}->{summary}->[1],
+ menuTitle => substr($events{$id}->{summary}->[1],0,15),
+ className => 'WebGUI::Asset::Event',
+ isHidden => 1,
+ };
+
+ # Prepare the date
+ my $dtstart = $events{$id}->{dtstart}->[1];
+ if ($dtstart =~ /T/)
+ {
+ my ($date, $time) = split /T/, $dtstart;
+
+ my ($year, $month, $day) = $date =~ /(\d{4})(\d{2})(\d{2})/;
+ my ($hour, $minute, $second) = $time =~ /(\d{2})(\d{2})(\d{2})/;
+
+ ($properties->{startDate}, $properties->{startTime}) =
+ split / /, WebGUI::DateTime(
+ year => $year,
+ month => $month,
+ day => $day,
+ hour => $hour,
+ minute => $minute,
+ second => $second,
+ time_zone => "UTC",
+ )->toMysql;
+ }
+ elsif ($dtstart =~ /(\d{4})(\d{2})(\d{2})/)
+ {
+ my ($year, $month, $day) = $dtstart =~ /(\d{4})(\d{2})(\d{2})/;
+
+ $properties->{startDate} = join "-",$year,$month,$day;
+ }
+
+ my $dtend = $events{$id}->{dtend}->[1];
+ if ($dtend =~ /T/)
+ {
+ my ($date, $time) = split /T/, $dtend;
+
+ my ($year, $month, $day) = $date =~ /(\d{4})(\d{2})(\d{2})/;
+ my ($hour, $minute, $second) = $time =~ /(\d{2})(\d{2})(\d{2})/;
+
+ ($properties->{endDate}, $properties->{endTime}) =
+ split / /, WebGUI::DateTime(
+ year => $year,
+ month => $month,
+ day => $day,
+ hour => $hour,
+ minute => $minute,
+ second => $second,
+ time_zone => "UTC",
+ )->toMysql;
+ }
+ elsif ($dtend =~ /(\d{4})(\d{2})(\d{2})/)
+ {
+ my ($year, $month, $day) = $dtend =~ /(\d{4})(\d{2})(\d{2})/;
+
+ $properties->{endDate} = join "-",$year,$month,$day;
+ }
+
+
+
+ # If there are X-WebGUI-* fields
+ for my $key (grep /^X-WEBGUI-/, keys %{$events{$id}})
+ {
+ my $property_name = $key;
+ $property_name =~ s/^X-WEBGUI-//;
+
+ if (lc $property_name eq "groupidedit")
+ {
+ $properties->{groupIdEdit} = $events{$id}->{$key}->[1];
+ }
+ elsif (lc $property_name eq "groupidview")
+ {
+ $properties->{groupIdView} = $events{$id}->{$key}->[1];
+ }
+ elsif (lc $property_name eq "url")
+ {
+ $properties->{url} = $events{$id}->{$key}->[1];
+ }
+ }
+
+
+ # Update event
+ my ($assetId) = $self->session->db->quickArray("select assetId from Event where feedUid=?",[$id]);
+
+ # If this event already exists, update
+ if ($assetId)
+ {
+ #warn "Updating $assetId\n";
+
+ my $event = WebGUI::Asset->newByDynamicClass($self->session,$assetId);
+
+ if ($event)
+ {
+ $event->update($properties);
+ $event->requestCommit;
+ $updated++;
+ }
+ }
+ else
+ {
+ my $calendar = WebGUI::Asset->newByDynamicClass($self->session,$feed->{assetId});
+ my $event = $calendar->addChild($properties);
+ $event->requestCommit;
+ $added++;
+ }
+
+ # TODO: Only update if last-updated field is
+ # greater than the event's lastUpdated property
+ }
+
+ # Update the result and last updated fields
+ $self->session->db->write("update Calendar_feeds set lastResult=?,lastUpdated=? where feedId=?",
+ ["Success! $added added, $updated updated",$dt,$feed->{feedId}]);
+ }
+ else
+ {
+ # Update the result and last updated fields
+ $self->session->db->write("update Calendar_feeds set lastResult=?,lastUpdated=? where feedId=?",
+ [$response->message,$dt,$feed->{feedId}]);
+ }
+ }
+
+ $sth->finish;
+
+ return $self->COMPLETE;
+}
+
+
+=head1 BUGS
+
+We should probably be using some sort of parser for the iCalendar files. I did
+not have time to make a decent observation but the following were observed and
+rejected
+
+ Data::ICal - Best one I saw. Rejected because I've run out of time
+ Text::vFile
+ Net::ICal
+ iCal::Parser - Bad data structure
+ Tie::iCal
+
+=cut
+
+1;
+
+
diff --git a/lib/WebGUI/i18n/English/Asset_Calendar.pm b/lib/WebGUI/i18n/English/Asset_Calendar.pm
index 681b09d74..fdb96e47c 100755
--- a/lib/WebGUI/i18n/English/Asset_Calendar.pm
+++ b/lib/WebGUI/i18n/English/Asset_Calendar.pm
@@ -263,6 +263,14 @@ our $I18N = {
+#################### WORKFLOW ACTIVITIES ####################
+ 'workflow updateFeeds' => {
+ message => q{Update Calendar Feeds},
+ lastUpdated => 0,
+ context => q{The name of the CalendarUpdateFeeds workflow activity},
+ },
+
+
#################### ASSET NAME ####################
'assetName' => {
message => q{Calendar},