From 5292426cc5bcaf65a8bffd77ac4af53e5cd93b88 Mon Sep 17 00:00:00 2001 From: Doug Bell Date: Wed, 23 Jan 2008 02:41:38 +0000 Subject: [PATCH] add: Events in calendar Week view can now be ordered arbitrarily\ fix: Event Related Links are now proper collateral in their own table. The HTML area is parsed and the table populated accordingly. No more silly HTML Area. --- docs/changelog/7.x.x.txt | 4 + docs/gotcha.txt | 7 + .../root_import_calendar-templates.wgpkg | Bin 0 -> 34304 bytes docs/upgrades/upgrade_7.4.20-7.5.0.pl | 243 ++++++++++++++++ lib/WebGUI/Asset/Event.pm | 181 ++++++++++-- lib/WebGUI/Asset/Wobject/Calendar.pm | 259 ++++++++++++++++-- lib/WebGUI/i18n/English/Asset_Calendar.pm | 23 ++ 7 files changed, 666 insertions(+), 51 deletions(-) create mode 100644 docs/upgrades/packages-7.5.0/root_import_calendar-templates.wgpkg diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index ad653b669..ff09febfa 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -24,6 +24,10 @@ height, templateId); where assetId is the asset of the widget to widgetize and templateId is the template for the widget itself. If templateId isn't specified, uses ajaxInlineView. + - add: Events in the Calendar Week View can now be ordered + arbitrarily. + - fix: Event Related Links are now proper collateral in their own table + instead of a silly HTML area 7.4.21 - fix: Mails sent from WebGUI now wrap at 78 characters, as the SMTP diff --git a/docs/gotcha.txt b/docs/gotcha.txt index f5b9c7b1f..bb7df50b8 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -7,6 +7,13 @@ upgrading from one version to the next, or even between multiple versions. Be sure to heed the warnings contained herein as they will save you many hours of grief. +7.5.0 +-------------------------------------------------------------------- + * Event related links are now displayed using a template loop + rather than a template variable. See the default templates for + details. + Your custom Event Edit and Event View templates may need fixing. + 7.4.12 -------------------------------------------------------------------- * Any customizations made to the Matrix default Search, Compare or diff --git a/docs/upgrades/packages-7.5.0/root_import_calendar-templates.wgpkg b/docs/upgrades/packages-7.5.0/root_import_calendar-templates.wgpkg new file mode 100644 index 0000000000000000000000000000000000000000..ffe18aa295bcef4bc50aacd08bd78a81ec5c9724 GIT binary patch literal 34304 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 O_$q;~68Qfjf&T-0$y@FK literal 0 HcmV?d00001 diff --git a/docs/upgrades/upgrade_7.4.20-7.5.0.pl b/docs/upgrades/upgrade_7.4.20-7.5.0.pl index ea708dbe4..c3fe2a8a8 100644 --- a/docs/upgrades/upgrade_7.4.20-7.5.0.pl +++ b/docs/upgrades/upgrade_7.4.20-7.5.0.pl @@ -31,6 +31,11 @@ installGalleryAsset($session); installGalleryAlbumAsset($session); installPhotoAsset($session); +createEvent_relatedlinkTable($session); +updateRelatedLinkData($session); +alterEventTableForSequence($session); +populateSequenceNumbers($session); + finish($session); # this line required @@ -280,6 +285,244 @@ sub addIsExportable { print "DONE!\n" unless $quiet; } +#-------------------------------------------------------------------------- +# Populate the initial sequence numbers +sub populateSequenceNumbers { + my $session = shift; + + my $dbh = $session->db->dbh; + + my $seed = 16384; + my $curr_seed = 32768; + + my $sql =<selectcol_arrayref($sql); + + for my $assetId (@$ar_assetIds) { + my ($event) = $dbh->selectrow_hashref("SELECT revisionDate FROM Event WHERE assetId = ? ORDER BY revisionDate DESC LIMIT 1",undef,$assetId); + + $dbh->do("UPDATE Event SET sequenceNumber = ? WHERE assetId = ? AND revisionDate = ?",{},$curr_seed,$assetId,$event->{revisionDate}); + + $curr_seed += $seed; + } + $dbh->do("UPDATE Calendar SET sortEventsBy = 'sequenceNumber'"); +} + +#-------------------------------------------------------------------------- +# Create event relatedlink table +sub createEvent_relatedlinkTable { + my $session = shift; + print "\tCreate Event_relatedlink table.\n" unless $quiet; + + my $sql =<db->write($sql) or die "Failed to create Event_relatedlink table\n"; +} + +#----------------------------------------------------------------------------- +# Update the related links from the Event table to Event_relatedlink +sub updateRelatedLinkData { + my $session = shift; + use HTML::Parser; + + my $p = HTML::Parser->new(api_version =>3); + + print "\tConverting Related Links from Event table to Event_relatedlink table\n" unless $quiet; + + my $sth = $session->db->read("SELECT Event.assetId,relatedLinks,groupIdView FROM Event,assetData WHERE Event.assetId = assetData.assetId order by Event.revisionDate desc"); + $sth->execute; + my (%asset_used, %event_asset_of, %snippet_asset_of); + + while (my ($assetId, $relatedLinks, $groupIdView) = $sth->array) { + + if (defined $asset_used{$assetId}) { +# print "\tAlready defined\n"; +# print "$assetId, $relatedLinks\n"; + next; + } +# print "\n\tUsing\n"; +# print "$assetId, $relatedLinks\n"; + + $asset_used{$assetId} = $groupIdView; + + $event_asset_of{$assetId} = parse_html_to_link($p, $relatedLinks); +# print Dumper ( $event_asset_of{ $assetId } )."\n"; + $p->eof; + } + + # Scan all records for active AssetProxy macros and convert them to a + # Real url / display text pair. + # + for my $assetId (keys %event_asset_of) { + for my $hr (@{$event_asset_of{$assetId}}) { + next unless ($hr->{url} =~ /AssetProxy/); + + $hr->{text} =~ s/^\///; +# print "*** NEW ***\n".$hr->{text}."\n"; + my ($assetId_snippet, $groupIdView) = $session->db->quickArray("SELECT assetId, groupIdView FROM assetData WHERE url = ? ORDER BY revisionDate DESC LIMIT 1",[$hr->{text}]); + + unless ($assetId_snippet) { + delete $event_asset_of{$assetId}; + next; + } + $asset_used{$assetId_snippet} = $groupIdView; + + my ($snippet) = $session->db->quickArray("SELECT snippet FROM snippet WHERE assetId = ? ORDER BY revisionDate DESC LIMIT 1",[$assetId_snippet]); +# print "\tsnippetId: ($assetId_snippet), assetId($assetId):\n$snippet\n"; + my $links = parse_html_to_link($p, $snippet); +# print $assetId.":\n".Dumper ($links)."\n"; + for (@$links) { + push @{$snippet_asset_of{$assetId}{$assetId_snippet}}, $_; + } + $hr = undef; + } + } + + # Extracted data now stored as Event_relatedlink rows + my $sql =<id->generate(); + next unless (defined (my $hr_link = $event_asset_of{$assetId}[$a_idx])); + my $groupToView = $asset_used{$assetId}; + +# printf "'%s', '%s', '%s', '%s', '%s', '%s'\n",$assetId,$groupToView,$hr_link->{url},$hr_link->{text},$a_idx+1,$eventlinkId; + $session->db->write($sql,[$assetId,$groupToView,$hr_link->{url},$hr_link->{text},$a_idx+1,$eventlinkId]); + } + } +# print "Snippets\n"; + for my $assetId (keys %snippet_asset_of) { + my $hrs_asset_of = \%{$snippet_asset_of{$assetId}}; +# print "\tEvent: $assetId\n"; +# print Dumper ($hrs_asset_of)."\n"; + for my $s_assetId (keys %$hrs_asset_of) { +# print "\t\tSnippet: $s_assetId\n"; + for my $a_idx (0..@{$hrs_asset_of->{$s_assetId}}) { +# print "\t\t\tIDX: $a_idx\n"; + my $eventlinkId = $session->id->generate(); + next unless (defined (my $hr_link = $hrs_asset_of->{$s_assetId}[$a_idx])); + my $groupToView = $asset_used{$s_assetId}; + +# printf "'%s', '%s', '%s', '%s', '%s', '%s'\n",$assetId,$groupToView,$hr_link->{url},$hr_link->{text},$a_idx+1,$eventlinkId; + $session->db->write($sql,[$assetId,$groupToView,$hr_link->{url},$hr_link->{text},$a_idx+1,$eventlinkId]); + } + } + } + return; +} + +#----------------------------------------------------------------------------- +# Alter the Event table to add the Sequence Number field +sub alterEventTableForSequence { + my $session = shift; + + print "\tAdding sequenceNumber to Event table.\n" unless $quiet; + my $sql =<db->write($sql) or die "Failed to modify Event table\n"; + + $sql =<db->write($sql) or die "Failed to modify Calendar table\n"; +} + +######## +# Convert HTML::Parser output to something useful +# Results in a array of hashrefs with keys 'url' and 'text' +# +sub parse_html_to_link { + my ($p, $rl, $verbose) = @_; + + $rl =~ s/<\/a\>\s*
handler( start => \@result, 'attr' ); + $p->handler( text => \@result, 'text' ); + $p->parse($rl."
"); + if ($verbose) { + print "=========================================\n"; + print Dumper (@result)."\n"; + print "------\n"; + } + + my (@text, @links, $key); + for (@result) { + if (ref ($_->[0]) ne "HASH") { + if ($_->[0] =~ /^\^AssetProxy/) { + push @text, $_->[0]; + push @links, link_to_hashref('', \@text); + } + elsif ($_->[0] =~ /\w/) { + push @text, $_->[0]; + } + } + else { + if ($_->[0]->{href}) { + $key = $_->[0]->{href}; + } + else { + push @links, link_to_hashref($key, \@text); + } + } + } + return \@links; +} + +######## +# Given a key (URL) and an array_ref containing strings +# build a hash value according to certain rules +# +sub link_to_hashref { + my ($key, $ar_text) = @_; + + return unless $ar_text->[0]; + my %h; + if ($key) { + # Both hash key and values provided + $h{url} = $key; + $h{text} = (join " ",@$ar_text) || $key; + $key = ''; + } + elsif ($ar_text->[0] =~ /^\//) { + # Only a file reference is provided + $h{url} = join " ",@$ar_text; + $h{text} = join " ",@$ar_text; + } + elsif ($ar_text->[0] =~ /^\^AssetProxy\(([^\)]+)\)/) { + # Snippet macro provided + $h{text} = $1; + $h{url} = 'AssetProxy'; + } + + # prevent surprise array expansion + @$ar_text = (); + + return \%h; +} + + + + # --------------- DO NOT EDIT BELOW THIS LINE -------------------------------- #---------------------------------------------------------------------------- diff --git a/lib/WebGUI/Asset/Event.pm b/lib/WebGUI/Asset/Event.pm index 5415ba7f2..c60f4974a 100644 --- a/lib/WebGUI/Asset/Event.pm +++ b/lib/WebGUI/Asset/Event.pm @@ -91,10 +91,6 @@ sub definition { defaultValue => undef, }, - 'relatedLinks' => { - fieldType => "HTMLArea", - defaultValue => undef, - }, 'location' => { fieldType => "Text", defaultValue => undef, @@ -115,6 +111,9 @@ sub definition { 'timeZone' => { fieldType => 'TimeZone', }, + sequenceNumber => { + fieldType => 'hidden', + }, ); @@ -400,6 +399,8 @@ sub getEventNext { limit => 1, }); + + return unless $events->[0]; return WebGUI::Asset->newByDynamicClass($self->session,$events->[0]); } @@ -455,6 +456,7 @@ sub getEventPrev { limit => 1, }); + return unless $events->[0]; return WebGUI::Asset->newByDynamicClass($self->session,$events->[0]); } @@ -1140,13 +1142,26 @@ sub getRecurrenceFromForm { =head2 getRelatedLinks -Gets an array of hashrefs of related links. +Gets an arrayref of hashrefs of related links. =cut sub getRelatedLinks { my $self = shift; + + my $sth + = $self->session->db->prepare( + "SELECT * FROM Event_relatedlink WHERE assetId=? ORDER BY sequenceNumber", + ); + $sth->execute([ $self->getId ]); + my @links; + while ( my $link = $sth->hashRef ) { + next unless $self->session->user->isInGroup( $link->{ groupIdView } ); + push @links, $link; + } + + return \@links; } #------------------------------------------------------------------- @@ -1275,10 +1290,8 @@ sub getTemplateVars { $var{ "urlSearch" } = $self->getParent->getSearchUrl; # Related links - $var{ "relatedLinks" } = []; - push @{$var{"relatedLinks"}}, { "linkUrl" => $_ } - for ($self->getRelatedLinks); - + $var{ relatedLinks } = $self->getRelatedLinks; + # Attachments my $gotImage; my $gotAttachment; @@ -1487,6 +1500,74 @@ sub processPropertiesFromFormPost { }); } + my $top_val = $session->db->dbh->selectcol_arrayref("SELECT sequenceNumber FROM Event ORDER BY sequenceNumber desc LIMIT 1")->[0]; + $top_val += 16384; + my $assetId = $self->get('assetId'); + my $revisionDate = $self->get('revisionDate'); + + $session->db->write("UPDATE Event SET sequenceNumber =? WHERE assetId = ? AND revisionDate =?",[($form->param('sequenceNumber') || $top_val), $assetId, $revisionDate]); + + + # Pre-process Related Links and manage changes + # These parameters are the important ones + # + my @rel_keys = grep {/^rel_(?:delconfirm|url|text|group|seq)_/} $form->param; + + # Organize results + my %rel_link_for; + for (@rel_keys) { + if (/^rel_group_id_(.+)$/) { # Group assignment + $rel_link_for{$1}{groupIdView} = $form->param($_); + } + elsif (/^rel_url_(.+)$/) { + my $eventlinkId = $1; + my $url = $form->param($_); + $url =~ s/^\s+//; + $url =~ s/\s+$//; + if (0 && $url && $url !~ /^http:\/\//) { + $url =~ s/ht+p[^\w]+//i; + $url = "http://$url"; + } + $rel_link_for{$eventlinkId}{linkurl} = $url || ''; + } + elsif (/^rel_seq_(.+)$/) { + $rel_link_for{$1}{sequenceNumber} = $form->param($_); + } + elsif (/^rel_text_(.+)$/) { + my $eventlinkId = $1; + my $text = $form->param($_); + $text =~ s/^\s+//; + $text =~ s/\s+$//; + $rel_link_for{$eventlinkId}{linktext} = $text; + } + elsif (/^rel_delconfirm_(.+)$/) { + $rel_link_for{$1}{delete} = $form->param($_); + } + } + + # The database entries for this assetId are compared and + # then replaced by these (possibly new) values. Deletions + # are marked and passed on. + # + my @rel_link_saves; + + for (keys %rel_link_for) { + if (!$rel_link_for{$_}{linkurl}) { + $rel_link_for{$_}{delete}++; + next; + } + if (/^new_/) { + $rel_link_for{$_}{eventlinkId} = $self->session->id->generate(); + $rel_link_for{$_}{new_event}++; + } + else { + $rel_link_for{$_}{eventlinkId} = $_; + } + push @rel_link_saves, \%{$rel_link_for{$_}}; + } + + $self->setRelatedLinks(\@rel_link_saves); + # Determine if the pattern has changed if ($form->param("recurType")) { @@ -1497,7 +1578,8 @@ sub processPropertiesFromFormPost { # Set storable to canonical so that we can compare data structures - $Storable::canonical = 1; + local $Storable::canonical = 1; + # Pattern keys if (nfreeze(\%recurrence_new) ne nfreeze(\%recurrence_old)) { @@ -1669,19 +1751,45 @@ sub setRecurrence { #################################################################### -=head2 setRelatedLinks ( @links ) +=head2 setRelatedLinks ( links ) -Sets the event's related links. +Sets the event's related links. C is an array reference of +hash reference of links. =cut sub setRelatedLinks { my $self = shift; - my @links = @_; + my $links = shift; - $self->update({ - relatedLinks => join("\n", @links), - }); + my $assetId = $self->getId; + + # Don't make any changes unless asked, and then only change the known records + # + if (@$links) { + for my $hr (@{$links}) { + if ($hr->{new_event} && !$hr->{delete}) { + $self->session->db->write( + q{INSERT INTO Event_relatedlink (assetId,sequenceNumber,linkurl,linktext,groupIdView,eventlinkId) VALUES (?,?,?,?,?,?)}, + [ $assetId, @{$hr}{('sequenceNumber','linkurl','linktext','groupIdView','eventlinkId')} ] + ); + } + elsif ($hr->{delete}) { + $self->session->db->write( + q{DELETE FROM Event_relatedlink WHERE assetId = ? AND eventlinkId = ?}, + [ $assetId, $hr->{eventlinkId} ], + ); + } + else { + $self->session->db->write( + q{UPDATE Event_relatedlink set sequenceNumber=?,linkurl=?,linktext=?,groupIdView=? where eventlinkId = ?}, + [ @{$hr}{('sequenceNumber','linkurl','linktext','groupIdView','eventlinkId')} ], + ); + } + } + } + + return; } #################################################################### @@ -1782,6 +1890,10 @@ sub www_edit { $var->{"formHeader"} = WebGUI::Form::formHeader($session, { action => $self->getUrl, + }) + . WebGUI::Form::hidden($self->session, { + name => "sequenceNumber", + value => $self->get("sequenceNumber"), }); } @@ -1971,13 +2083,34 @@ sub www_edit { . q|
Time Zone: |.$var->{formTimeZone} . q||; - # related links - $var->{"formRelatedLinks"} - = WebGUI::Form::HTMLArea($session, { - name => "relatedLinks", - value => $form->process("relatedLinks") || $self->get("relatedLinks"), - }); + ###### related links + my $relatedLinks = $self->getRelatedLinks(); + my $seqNum = 1; + for (@$relatedLinks) { + + $_->{row_id} = "rel_row_".$_->{eventlinkId}; + $_->{div_id} = "rel_div_".$_->{eventlinkId}; + $_->{delete_name} = "rel_del_".$_->{eventlinkId}; + $_->{delete_id} = "rel_del_id_".$_->{eventlinkId}; + $_->{group_id} = WebGUI::Form::Group($session, { + name => "rel_group_id_".$_->{eventlinkId}, + value => $form->process("rel_group_id_".$_->{eventlinkId}) || $_->{groupIdView} || $self->getParent->get("groupIdView"), + defaultValue => $self->getParent->get("groupIdView"), + }); + $_->{seq_num_name} = "rel_seq_".$_->{eventlinkId}; + $_->{seq_num_id} = "rel_seq_id_".$_->{eventlinkId}; + $_->{seq_num_value} = $seqNum++; + } + $var->{"relatedLinks"} = $relatedLinks; + + $var->{"genericGroup"} = WebGUI::Form::Group($session, { + name => "rel_group_id_ZZZZZZZZZZ", + value => $self->getParent->get("groupIdView"), + defaultValue => $self->getParent->get("groupIdView"), + }); + chomp $var->{"genericGroup"}; + ###### Recurrence tab @@ -2142,11 +2275,11 @@ sub www_edit { |; - # Include + # TODO! # Exclude - + # TODO! diff --git a/lib/WebGUI/Asset/Wobject/Calendar.pm b/lib/WebGUI/Asset/Wobject/Calendar.pm index 77ab86e4f..da40ba68d 100644 --- a/lib/WebGUI/Asset/Wobject/Calendar.pm +++ b/lib/WebGUI/Asset/Wobject/Calendar.pm @@ -62,8 +62,10 @@ sub definition { first => $i18n->get("defaultDate value first"), last => $i18n->get("defaultDate value last"), ); - - + tie (my %optionsEventSort, 'Tie::IxHash', + time => $i18n->get("sortEventsBy value time"), + sequencenumber => $i18n->get("sortEventsBy value sequencenumber"), + ); ### Build properties hash ### tie my %properties, 'Tie::IxHash'; @@ -214,7 +216,15 @@ sub definition { hoverHelp => $i18n->get('visitorCacheTimeout description'), label => $i18n->get('visitorCacheTimeout label'), }, - + sortEventsBy => { + fieldType => "SelectBox", + defaultValue => "time", + options => \%optionsEventSort, + tab => "display", + label => $i18n->get("sortEventsBy label"), + hoverHelp => $i18n->get("sortEventsBy description"), + }, + # This doesn't function currently #subscriberNotifyOffset => { # fieldType => "integer", @@ -589,7 +599,7 @@ sub getEvent { #################################################################### -=head2 getEventsIn ( startDate, endDate ) +=head2 getEventsIn ( startDate, endDate, options ) Returns a list of Event objects that fall between two dates, ordered by their start date/time. @@ -601,17 +611,25 @@ user's time zone. TODO: Allow WebGUI::DateTime objects to be passed as the parameters. -TODO: Allow for a hashref of options as the third parameter to specify such -things as a limit clause, or additional where clause, or something. - This is the main API method to get events from a calendar, so it must be flexible. +C is a hash reference with the following keys: + + order - The order to return the events. Will default to the + sortEventsBy asset property. Valid values are: + 'time', 'sequenceNumber' + =cut sub getEventsIn { my $self = shift; my $start = shift; my $end = shift; + my $params = shift; + + $params->{order} = '' if $params->{order} !~ /^(?:time|sequencenumber)/i; + my $order_by_type = $params->{order} ? lc($params->{order}) : $self->get('sortEventsBy'); + my $tz = $self->session->user->profileField("timeZone"); # Warn and return if no startDate or endDate @@ -647,15 +665,20 @@ sub getEventsIn { ) }; - my $orderby - = join ',', - 'Event.startDate', + my @order_priority + = ( 'Event.startDate', 'Event.startTime', 'Event.endDate', 'Event.endTime', 'assetData.title', 'assetData.assetId', - ; + ); + if ($order_by_type eq 'sequencenumber') { + unshift @order_priority, 'Event.sequenceNumber'; + } + + my $orderby = join ',', @order_priority; + my $events = $self->getLineage(["descendants"], { @@ -933,12 +956,25 @@ sub view { # Get the form parameters my $params = {}; - $params->{type} = $form->param("type"); + $params->{type} = $form->param("type") || $self->get( 'defaultView' ); $params->{start} = $form->param("start"); - ### TODO: Parse user input for sanity. - # {start} must be of the form: YYYY-MM-DD%20HH:MM:SS - # {type} must be "month", "week", or "day" + # Validate type passed, or recover from session scratchpad + if ($params->{type} =~ /^(?:month|week|day)$/) { + $session->scratch->set('cal_view_type', $params->{'type'}); + } + else { + $params->{type} = $session->scratch->get('cal_view_type') || $self->get( 'defaultView' ) || 'month'; + $session->scratch->set('cal_view_type', $params->{'type'}); + } + + # Validate start passed or recover from session scratchpad + if ($params->{ start } =~ /^\d\d\d\d\-\d\d\-\d\d.+?\d\d\:\d\d\:\d\d$/) { + $session->scratch->set('cal_view_start', $params->{ start }); + } + else { + $params->{ start } = $session->scratch->get('cal_view_start') || 0; + } # Set defaults if necessary if (!$params->{start}) { @@ -949,9 +985,8 @@ sub view { ? $self->getLastEvent->getDateTimeStart : WebGUI::DateTime->new($session, time)->toUserTimeZone ; - } - if (!$params->{type}) { - $params->{type} = $self->get("defaultView") || "Month"; + + $session->scratch->set('cal_view_start', $params->{'start'}); } # Get the template from the appropriate view* method @@ -971,7 +1006,7 @@ sub view { # Event editor if ($self->canAddEvent) { $var->{'editor'} = 1; - $var->{"urlAdd"} = $self->getUrl("func=add;class=WebGUI::Asset::Event;start=$params->{start}"); + $var->{"urlAdd"} = $self->getUrl("func=add;class=WebGUI::Asset::Event;type=".$params->{type}.";start=$params->{start}"); } # URLs @@ -1283,12 +1318,165 @@ sub viewWeek { $dt->subtract(days => $dt->day_of_week % 7 - $first_dow); my $start = $dt->toMysql; - my $dtEnd = $dt->clone->add(days => 7); + my $dtEnd = $dt->clone->add(days => 7)->add( seconds => -1); my $end = $dtEnd->toMysql; # Clone to prevent saving change - $dtEnd->add(seconds => -1); - - my @events = $self->getEventsIn($start,$end); + my $sort_by_sequence++ if $self->get('sortEventsBy') eq 'sequencenumber'; + my $can_edit_order++ if $self->canEdit && $sort_by_sequence; + + my $reorder_request++ if $can_edit_order && $session->form->param( 'eventMove' ) =~ /^(?:UP|DOWN)$/; + if ($reorder_request) { + + # Someone clicked an UP or DOWN request + # + my @events = $self->getEventsIn( $start, $end ); + + my (%event_asset_of, %seq_key_of, %week_day_of, @event_days); + + # The events + for my $event ( @events ) { + next unless $event->canView(); + + my $event_asset_id = $event->get( 'assetId' ); + + # Add Event object use by assetId + $event_asset_of{ $event_asset_id }{ object } = $event; + + # Get the week this event is in, and add it to that week in + # the template variables + my $dt_event_start = $event->getDateTimeStart; + my $dt_event_end = $event->getDateTimeEnd; + + #Handle events that start before this week or end after this week. + if ($dt_event_start < $dt) { + $dt_event_start = $dt; + } + + if ($dt_event_end > $dtEnd) { + $dt_event_end = $dtEnd; + } + + my $start_dow = ($dt_event_start->day_of_week - $first_dow) % 7; + my $end_dow = ($dt_event_end->day_of_week - $first_dow) % 7; + + my $sequence_number = $session->db->dbh->selectcol_arrayref("SELECT sequenceNumber FROM Event WHERE assetId += ? ORDER BY revisionDate desc LIMIT 1",{},$event_asset_id)->[0]; + + foreach my $weekDay ($start_dow .. $end_dow) { + + push @{ $event_days[ $weekDay ] }, $event; + my $event_day_pos = $#{ $event_days[ $weekDay ]}; + + # Monitor duplicates in sequence list; + push @{ $seq_key_of{ $sequence_number } }, $event_asset_id; + + # Add find assetId by day/order pos + $week_day_of{ $weekDay }{ $event_day_pos } = $event_asset_id; + + # Add find order pos by assetId and day + $event_asset_of{ $event_asset_id }{ $weekDay } = $event_day_pos; + } + } + + # Process the event sequence change request + # + # Based upon binary values beginning at 16384 sequence + # number separtion. Collisions are expected, in fact, + # designed for, with the move increment divided by two + # repeatedly until a non-collision situation is detected and + # then used. In worst case behavior, this practice will + # fail at 16 repositions, but to cause this someone would + # have to be applying extremely abusive reorder behavior. + # + # Abusive consists of applying move-up or move-down between + # two select events, in a leap frog fashion, towards yet another + # event in the same time frame. This causes the increment to + # progressively be divided by two until it hits the value '1'. + # At that point, duplication of sequence number is inevitable, + # and the order list may behave in unexpected ways. + # + # CAVEAT: This service functions on a week view. This + # behavior could move the reprioritized event ahead or + # behind interventing events listed on other days. The + # logic to compensate for calendar events spanning to + # non-target weeks is ignored. + + # + my $direction = $session->form->param( 'eventMove' ); + my $event_asset_id = $session->form->param( 'assetId' ); + my $event_day = $session->form->param( 'day' ); + my $event_day_pos = $event_asset_of{ $event_asset_id }{ $event_day }; + my $event_object = $event_asset_of{ $event_asset_id }{ object }; + my $event_seq_num = $session->db->dbh->selectcol_arrayref("SELECT sequenceNumber FROM Event WHERE assetId = ? ORDER BY revisionDate desc LIMIT 1",{},$event_asset_id)->[0]; + + my @seq_list = sort keys %seq_key_of; + my $incr = 8192; + my $day_entries = \@{ $event_days[ $event_day ] }; +# warn "@seq_list\n"; +# warn "Moving assetId: $event_asset_id, seqNum: $event_seq_num, day: $event_day.$event_day_pos\n"; + + if ($direction eq 'UP' && $event_day_pos > 0) { + my $prev_asset_id = $week_day_of{ $event_day }{ $event_day_pos - 1 }; + my $prev_day_pos = $event_asset_of{ $prev_asset_id }{ $event_day }; + my $prev_event_object = $event_asset_of{ $prev_asset_id }{ object }; + my $prev_seq_num = $session->db->dbh->selectcol_arrayref("SELECT sequenceNumber FROM Event WHERE assetId = ? ORDER BY revisionDate desc LIMIT 1",{},$prev_asset_id)->[0]; + +# warn "Before Asset: $prev_asset_id, seqNum: $prev_seq_num, day: $event_day.$prev_day_pos\n"; + + my $seq_idx; + for my $i (0..$#seq_list) { + next if $seq_list[ $i ] < $prev_seq_num; + $seq_idx = $i - 1; + last; + } +# warn "\tmove between: $seq_list[ $seq_idx] and $prev_seq_num\n"; + + if ($seq_idx >= 0) { + + while ($prev_seq_num - $incr <= $seq_list[ $seq_idx ] && $incr > 1) { + $incr /= 2; + } + + } + + + $session->db->dbh->do + ("UPDATE Event SET sequenceNumber = ? WHERE assetId = ? AND revisionDate = ?",{}, + $prev_seq_num-$incr, $event_asset_id, $event_object->get( 'revisionDate' ) + ); +# warn "Moved Asset New Seq Num: ".($prev_seq_num - $incr)." by $incr\n"; + + } + elsif ($direction eq 'DOWN' && $event_day_pos < $#{ $day_entries }) { + my $next_asset_id = $week_day_of{ $event_day }{ $event_day_pos + 1 }; + my $next_day_pos = $event_asset_of{ $next_asset_id }{ $event_day }; + my $next_event_object = $event_asset_of{ $next_asset_id }{ object }; + my $next_seq_num = $session->db->dbh->selectcol_arrayref("SELECT sequenceNumber FROM Event WHERE assetId = ? ORDER BY revisionDate desc LIMIT 1",{},$next_asset_id)->[0]; + +# warn "After Asset: $next_asset_id, seqNum: $next_seq_num, day: $event_day.$next_day_pos\n"; + + my $seq_idx; + for my $i (0..$#seq_list) { + next if $seq_list[ $i ] < $next_seq_num; + $seq_idx = $i; + last; + } +# warn "\tmove between: $next_seq_num and $seq_list[ $seq_idx]\n"; + + if ($seq_idx <= $#seq_list) { + while ($next_seq_num + $incr >= $seq_list[ $seq_idx + 1 ] && $incr > 1) { + $incr /= 2; + } + } + + $session->db->dbh->do + ("UPDATE Event SET sequenceNumber = ? WHERE assetId = ? AND revisionDate = ?",{}, + $next_seq_num + $incr, $event_asset_id, $event_object->get( 'revisionDate' ) + ); +# warn "Moved Asset New Seq Num: ".($next_seq_num + $incr)." by $incr\n"; + } + } + #### Create the template parameters # Some friendly dates @@ -1316,9 +1504,9 @@ sub viewWeek { } # The events - - EVENT: for my $event (@events) { - next EVENT unless $event->canView(); + my @events = $self->getEventsIn( $start, $end ); + for my $event ( @events ) { + next unless $event->canView(); # Get the week this event is in, and add it to that week in # the template variables my $dt_event_start = $event->getDateTimeStart; @@ -1339,7 +1527,24 @@ sub viewWeek { my %eventTemplateVariables = $self->getEventVars($event); foreach my $weekDay ($start_dow .. $end_dow) { - push @{$var->{days}->[$weekDay]->{events}}, \%eventTemplateVariables; + my $eventAssetId = $event->get( 'assetId' ); + + my %hash = %eventTemplateVariables; + + if ($sort_by_sequence && $can_edit_order) { + if (1) { + $hash{ iconCallbackUP } + = $session->icon->moveUp( qq|eventMove=UP;day=$weekDay;assetId=$eventAssetId;type=week;start=|.$params->{start} ); + $hash{ iconCallbackDOWN } + = $session->icon->moveDown( qq|eventMove=DOWN;day=$weekDay;assetId=$eventAssetId;type=week;start=|.$params->{start} ); + } + else { + $hash{ callbackUP } = "day=$weekDay;eventMove=UP;assetId=$eventAssetId;type=week;start=".$params->{start}; + $hash{ callbackDOWN } = "day=$weekDay;eventMove=DOWN;assetId=$eventAssetId;type=week;start=".$params->{start}; + } + } + push @{ $var->{ days }->[ $weekDay ]->{ events }}, \%hash; + } } diff --git a/lib/WebGUI/i18n/English/Asset_Calendar.pm b/lib/WebGUI/i18n/English/Asset_Calendar.pm index 6389fb534..07ba78d22 100755 --- a/lib/WebGUI/i18n/English/Asset_Calendar.pm +++ b/lib/WebGUI/i18n/English/Asset_Calendar.pm @@ -104,6 +104,29 @@ our $I18N = { }, + ##### Default Daily Event Sort Order ##### + 'sortEventsBy label' => { + message => q{Daily Events Sort Order}, + lastUpdated => 0, + context => q{A specification for determining daily Event display order.}, + }, + 'sortEventsBy description' => { + message => q{The order in which daily Events are displayed.}, + lastUpdated => 0, + context => q{Hover Help for the Daily Events Sort Order field.}, + }, + 'sortEventsBy value time' => { + message => q{Order by Start Date/End Date.}, + lastUpdated => 0, + context => q{A value for the Daily Event Sort Order field.}, + }, + 'sortEventsBy value sequencenumber' => { + message => q{Order by Sequence Number.}, + lastUpdated => 0, + context => q{A value for the Daily Events Sort Order field.}, + }, + +