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 000000000..ffe18aa29 Binary files /dev/null and b/docs/upgrades/packages-7.5.0/root_import_calendar-templates.wgpkg differ 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.}, + }, + +