|
Server : Apache/2.4.62 System : FreeBSD fbsdweb2.web.rcn.net 14.1-RELEASE FreeBSD 14.1-RELEASE releng/14.1-n267679-10e31f0946d8 GENERIC amd64 User : www ( 80) PHP Version : 8.3.8 Disable Function : NONE Directory : /domains/compasssysweb/calendar/CalciumDir39/Calendar/ |
Upload File : |
# Copyright 1999-2003, Fred Steinberg, Brown Bear Software
package BlockView;
use strict;
use CGI;
use Calendar::Date;
use Calendar::Event;
use Calendar::Preferences;
use Calendar::Javascript;
# A BlockView is a big table, representing a bunch of days. It has weekdays (or
# months, years) across the top, and a bunch of days below
# It is composed of 1 or more weeks. Each week is composed of 7 days. (Wow!)
# The constructor expects an Operation object, start date, and end date.
sub new {
my $class = shift;
my ($operation, $startDate, $endDate, $params) = @_;
my $self = {};
bless $self, $class;
my $db = $operation->db;
my $prefs = $operation->prefs;
my $i18n = $operation->I18N;
my ($amount, $navType, $type) = $operation->ParseDisplaySpecs ($prefs);
$self->{_operation} = $operation;
$self->{_prefs} = $prefs;
my ($filterCategories,
$filterText, $filterIn) = $operation->ParseFilterSpecs;
$self->{_filterCategories} = $filterCategories;
$self->{_filterText} = $filterText;
$self->{_filterIn} = $filterIn;
$self->{_username} = $operation->getUsername;
# _canEdit needed for removeTentatives()
$self->{_canEdit} = {$operation->calendarName =>
$operation->permission->permitted
($self->{_username}, 'Edit')};
# If we're looking at a Year (or Quarter), do the special Year thing.
if ($amount =~ /year/i or $amount =~ /quarter/i) {
require Calendar::BlockYear;
my $y = BlockYear->new ($operation, $startDate, $amount);
$self->{html} = $y->getHTML;
return $self;
}
my $weekHeaderBG = $prefs->color ('WeekHeaderBG', 'someColors') || '""';
my $weekHeaderFG = $prefs->color ('WeekHeaderFG', 'someColors') || '""';
my ($weekHeaderFace, $weekHeaderSize) = $prefs->font ('BlockDayOfWeek');
# Store some stuff so we can get if from other methods
($self->{eventFace}, $self->{eventSize}) = $prefs->font ('BlockEvent');
($self->{timeFace}, $self->{timeSize}) = $prefs->font ('BlockEventTime');
($self->{incFace}, $self->{incSize}) = $prefs->font ('BlockInclude');
($self->{catFace}, $self->{catSize}) = $prefs->font ('BlockCategory');
# Check for special cases; planner view
if ($params and $params->{settings}) {
my $displayControl = $params->{settings};
$self->{_isWeekPlanner} = $displayControl =~ /weekPlanner/;
$self->{_plannerInclude} = $displayControl =~ /Included/;
$self->{_hideDayNames} = $displayControl =~ /noDayNames/;
$self->{_onlyDays} = $displayControl =~ /onlyDays/;
}
# Stick the javascript code we'll need in
$self->{html} = Javascript->PopupWindow ($operation);
# See if we are displaying Weekends
my $showWeekend = $prefs->ShowWeekend;
if ($amount !~ /day/i and !$showWeekend) {
while ($startDate->dayOfWeek > 5) { # don't start on weekend
$startDate++;
}
}
# First, create the open table def (unless Weekly Planner view)
unless ($self->{_isWeekPlanner}) {
my $cols = $showWeekend ? 14 : 10;
$cols = 2 if ($amount =~ /day/i);
$self->{html} .= '<center><TABLE border=1 width="100%" cellspacing=0' .
" cols=$cols>";
}
# Now stick the header on, with days of the week; no links for these
# babys, and always start on either Sunday or Monday, depening on prefs
my $startWeekOn = $showWeekend ? $prefs->StartWeekOn || 7 : 1; # 7,1-6
my $weekStart;
$weekStart = $startDate->firstOfWeek ($startWeekOn);
if ($amount =~ /day/i) {
$weekStart = Date->new ($startDate);
}
my $displayingDate = $startDate;
if ($amount !~ /day/i) { # DAY
$startDate = Date->new ($weekStart);
}
my $face = $weekHeaderFace ? " face='$weekHeaderFace'" : '';
my $size = $weekHeaderSize ? " size=$weekHeaderSize" : '';
$self->{_weekHeaderFaceString} = " $face$size ";
my $weekLength = $showWeekend ? 7 : 5;
if ($amount =~ /day/i) { # DAY
$weekLength = 1;
}
unless ($self->{_hideDayNames} or $amount =~ /day/i) {
$self->{'html'} .= "<TR bgcolor=$weekHeaderBG>";
my $pct = int (100/$weekLength);
for (my $i=0; $i<$weekLength; $i++) {
$self->{'html'} .= qq {<TH width="$pct%" colspan=2>} .
"<font$face$size color=$weekHeaderFG>"
. $i18n->get (($weekStart + $i)->dayName())
. "</font></TH>\n";
}
$self->{html} .= '</tr>';
}
# Fixup end date, since we add extra days to fill out the last week
# Rather silly, but it works.
my $addedSomething;
while ($endDate->dayOfWeek() != $startDate->dayOfWeek()) {
$endDate++;
$addedSomething = 1;
}
$endDate-- if $addedSomething;
# Get a hash of list of events which apply in this date range.
# Note that this may, of course, include events from other calendars.
# We add an extra 6 days on, since we may need them to fill out the
# last week.
my $events = $db->getEventDateHash ($startDate, $endDate+6, $prefs);
my $monthIfTail;
$monthIfTail = $displayingDate->month if ($amount =~ /month/i and
$displayingDate->day == 1);
my $addPerm = $operation->permission->permitted ($self->{_username},
'Add');
# And spit out rows for the entire month. Each row consists of 7 Days. We
# need to output 1 table row for a weeks worth of day numbers, then a row
# for the weeks worth of days data.
for (; $startDate <= $endDate; $startDate->addWeeks (1)) {
unless ($self->{_onlyDays}) {
unless ($weekLength == 1) { # no day/month row for day view
$self->{html} .= '<TR>';
$self->{html} .= $self->_weeksHeaderHTML ($operation,
$displayingDate,
$startDate, $weekStart,
$weekLength, $addPerm);
}
$self->{html} .= '</TR>';
$self->{html} .= $self->_weeksDataHTML ($operation->calendarName,
$startDate, $prefs, $events,
$i18n, $monthIfTail,
$weekLength);
}
}
unless ($self->{_isWeekPlanner}) {
$self->{'html'} .= '</TABLE></center>';
}
$self;
}
sub _weeksHeaderHTML {
my ($self, $operation, $displayingDate, $date, $useMonth,
$weekLen, $addPerm) = @_;
my ($html, $theDayNum, $back, $bgcolor, $fgcolor);
my $prefs = $operation->prefs;
my $i18n = $operation->I18N;
my ($face, $size) = $prefs->font ('BlockDayDate');
$face = $face ? " face='$face'" : '';
$size = $size ? " size=$size" : '';
# if a tz offset, and localtime + offset different day, use different day
my $today = Date->todayForTimezone ($prefs->Timezone);
my $weekNum;
$weekNum = $date->weekNumber ($prefs->WhichWeekNums,
$prefs->StartWeekOn)
if ($prefs->ShowWeekNums);
for (my $i=0; $i<$weekLen; $i++) {
$theDayNum = $date->day();
if (!$prefs->PrintPrefs) {
$bgcolor = $date == $today ? $prefs->color ('TodayBG') || '""'
: $prefs->color ('DayHeaderBG') || '""';
$fgcolor = $date == $today ? $prefs->color ('TodayFG') || '""'
: $prefs->color ('DayHeaderFG') || '""';
} else {
$bgcolor = $prefs->color ('DayHeaderBG', 'someColors');
$fgcolor = $prefs->color ('DayHeaderFG', 'someColors');
}
# Display the numbered day of month as an href.
my $colspan = 2;
if ($theDayNum == 1 || $date == $useMonth) {
$colspan = 1;
}
$html .= "<td bgcolor=$bgcolor colspan=$colspan>";
my $didA;
if ($addPerm and $operation->calendarName and !$prefs->PrintPrefs) {
my $url = $operation->makeURL ({Op => 'ShowDay',
Date => $date});
$html .= "<a href='$url'>";
$didA++;
}
$html .= "<font$face$size color=$fgcolor>$theDayNum</font>";
$html .= '</a>' if $didA;
$html .= "<font$face$size color=$fgcolor> " .
"<small><small>[$weekNum]</small></small></font>"
if ($i == 0 and defined $weekNum);
$html .= '</td>';
# Display abbreviated month names if it's the first of any month,
# or if we're explicitly told to. Also, make the name be an href to
# that month, unless it's the month we're already in.
my $tdstart = '<td align="center" bgcolor="' .
$prefs->color ('WeekHeaderBG', 'someColors') . '">';
my $tdend = '<b><font color="' .
$prefs->color ('WeekHeaderFG', 'someColors') . '"'
. $self->{_weekHeaderFaceString} . '>' .
$i18n->get ($date->monthName ('abbrev')) .
'</font></b>';
if ($theDayNum == 1 && $date->month() == $displayingDate->month()) {
$html .= $tdstart . $tdend . '</td>';
} elsif ($theDayNum == 1 || $date == $useMonth) {
my $url = $operation->makeURL ({Op => 'ShowIt',
Date => $date->firstOfMonth});
$html .= $tdstart . "<a href=\"$url\">" . $tdend . '</a></td>';
}
$date++;
}
$html;
}
sub _weeksDataHTML {
my $self = shift;
my ($calName, $date, $prefs, $events, $i18n, $thisMonthForTail,
$weekLen) = @_;
my $html;
my ($eventBG, $eventFG, $linkBG, $linkFG, $tailBG, $tailFG,
$bannerBG, $bannerFG) = ($prefs->color ('EventBG'),
$prefs->color ('EventFG'),
$prefs->color ('LinkBG'),
$prefs->color ('LinkFG'),
$prefs->color ('MonthTailBG'),
$prefs->color ('MonthTailFG'),
$prefs->color ('BannerShadowBG'),
$prefs->color ('BannerShadowFG'));
$bannerFG ||= 'gray';
$bannerBG ||= 'black';
my %regColors = (EventBG => $eventBG,
EventFG => $eventFG,
LinkFG => $linkFG,
LinkFG => $linkFG);
my %tailColors = (EventBG => $tailBG,
EventFG => $tailFG,
LinkFG => $tailFG,
LinkFG => $tailFG);
# set up array for colors
my @colors;
my $theDate = Date->new ($date);
for (my $i=0; $i<$weekLen; $i++) {
$colors[$i] = ($thisMonthForTail and
$theDate->month != $thisMonthForTail) ? \%tailColors
: \%regColors;
$theDate++;
}
# First, make a pass through, finding which events to bannerize, which
# also tells us the number of rows we'll need. (Since we bannerize, it
# could be any number.)
my @bannerThese; # which events to banner
my @bannerCount; # how many bannered on each day
my @unbanneredEvents; # other events for each day
my $endOfWeekDate = $date + ($weekLen - 1);
$theDate = Date->new ($date);
my $numRows = 1;
for (my $i=0; $i<$weekLen; $i++) {
my $daysEvents = $events->{"$theDate"};
# Eliminate events we don't really want
$daysEvents = $self->_filterEvents ($daysEvents);
$bannerCount[$i] = 0;
$unbanneredEvents[$i] = [];
EVENT: foreach (@$daysEvents) {
my $newStart; # banner start, if yesterday was excluded
# If not bannering, just save events
if (!$_->isRepeating or !$_->repeatInfo->bannerize) {
push @{$unbanneredEvents[$i]}, $_;
next EVENT;
}
my $eventID = $_->id . ($_->includedFrom || '');
# If event has excluded dates...
foreach my $exDate (@{$_->repeatInfo->exclusionList}) {
# If this day is exluded, just skip it
next EVENT if ($exDate == $theDate); # (won't happen, actually
# event won't ever appear)
# If yesterday was excluded, make today the start. We
# know today is not excluded, as we wouldn't get here
if ($exDate == $theDate - 1) {
$newStart = $theDate;
last;
}
}
# If event skips weekends and today is Monday, start it.
if ($_->repeatInfo->skipWeekends and $theDate->dayOfWeek == 1) {
$newStart = 1;
}
# Otherwise, it's a bannered repeater
$bannerCount[$i]++;
# If start of week, or first instance of event, or
# first day after exclusion, bannerize
if ($i == 0 # first day of week
or $_->repeatInfo->startDate == $theDate # first day of event
or $newStart # after an exclusion
) {
my $eventEnd = $_->repeatInfo->endDate;
# If exclusions, see if we end this banner early
if ($_->repeatInfo->exclusionList->[0]) {
foreach (@{$_->repeatInfo->exclusionList}) {
next if ($_ <= $theDate or $_ > $endOfWeekDate);
if ($_ < $eventEnd) {
$eventEnd = ($_ - 1);
}
}
}
# If skip weekends, maybe end this banner early
if ($_->repeatInfo->skipWeekends) {
my $endDate = $eventEnd;
if ($endDate > $endOfWeekDate) {
$endDate = $endOfWeekDate;
}
while ($endDate->isWeekend) {
$endDate--;
}
$eventEnd = $endDate;
}
my $endCol;
if ($eventEnd < $endOfWeekDate) {
$endCol = $date->deltaDays ($eventEnd);
} else {
$endCol = $weekLen - 1;
}
my %item = (event => $_,
startCol => $i,
endCol => $endCol);
push @bannerThese, \%item;
}
}
my $x = $bannerCount[$i] + 1;
$numRows = $x if ($x > $numRows);
$theDate++;
}
my @weekTable = map {[]} (0..$numRows-1); # array of rows; each ref to cols
# First, put bannered events in weekTable
foreach (sort {$a->{event}->repeatInfo->startDate <=>
$b->{event}->repeatInfo->startDate}
@bannerThese) {
ROW: foreach my $row (0..$numRows - 1) {
COL: foreach my $col ($_->{startCol}...$_->{endCol}) {
next ROW if $weekTable[$row]->[$col]; # next row if can't fit
}
my $col = $_->{startCol};
my $span = ($_->{endCol} - $_->{startCol} + 1) * 2;
my ($contents, $bgColor) =
$self->_formatEvents ([$_->{event}], $calName, $date, $i18n,
\%regColors, 'noTable!');
$contents = ' ' unless $contents; # shouldn't happen
my $style = '{margin: 3px;' .
' border-top: solid 1px;' .
' border-left: solid 1px;' .
' border-bottom: outset 2px;' .
' border-right: outset 2px;' .
" border-color: $bannerBG $bannerFG $bannerFG " .
"$bannerBG;" .
'}';
# Note; IE breaks if table width is 100% here
$weekTable[$row]->[$col] = "<td colspan=$span" .
" bgcolor='$regColors{EventBG}'>" .
"<table style=\"$style\" border=0 width=\"99%\" cellspacing=0 " .
'cellpadding=0>' .
"<tr><td align=\"center\" bgcolor=\"$bgColor\">" .
"$contents</td></tr></table></td>";
foreach my $col ($_->{startCol}...$_->{endCol}) {
$weekTable[$row]->[$col] ||= '1';
}
last ROW;
}
}
my @lastBannerRow; # last row w/banner in it
# And fill 'holes' at end of banners, if there are banners beneath them
foreach my $i (0..$weekLen-1) {
$lastBannerRow[$i] = undef;
foreach my $row (0..$numRows-1) {
$lastBannerRow[$i] = $row if ($weekTable[$row]->[$i]);
}
if ($lastBannerRow[$i] and $bannerCount[$i] <= $lastBannerRow[$i]) {
my $bg = $colors[$i]->{EventBG};
$bg = defined $bg ? "bgcolor=\"$bg\"" : '';
my $row = 0;
while ($row < $lastBannerRow[$i]) {
if ($weekTable[$row]->[$i]) { # occupied by banner
$row++;
next;
}
my $rowSpan = 1;
my $maxSpan = $lastBannerRow[$i] - $row;
while ($rowSpan < $maxSpan and !$weekTable[$row]->[$i]) {
$rowSpan++;
}
$weekTable[$row]->[$i] ||= "<td $bg rowSpan=$rowSpan " .
"colspan=2> </td>";
$row += $rowSpan;
}
}
}
my $maxUnbannered = 0;
# Then put unbannered events in first unoccupied row each column
$theDate = Date->new ($date);
for (my $i=0; $i<$weekLen; $i++) {
my $bg = $colors[$i]->{EventBG};
$bg = defined $bg ? "bgcolor=\"$bg\"" : '';
my $row = defined $lastBannerRow[$i] ? $lastBannerRow[$i] + 1 : 0;
my $rowSpan = $numRows - $row;
my $contents = $self->_formatEvents ($unbanneredEvents[$i], $calName,
$theDate, $i18n, $colors[$i]);
my $height = ($rowSpan == $numRows ? 'height=80' : '');
$contents ||= ' ';
$weekTable[$row]->[$i] = "<td rowspan=$rowSpan $height $bg " .
'colspan=2 valign="top">' .
$contents .
'</td>';
$theDate++;
my $numEvents = @{$unbanneredEvents[$i]};
$maxUnbannered = $numEvents if ($numEvents > $maxUnbannered);
}
my $num = (@weekTable + $maxUnbannered) || 1;
my $height = 'height: ' . int (80/$num) . 'px';
# And finally, create the HTML rows
foreach my $row (@weekTable) {
# $html .= '<tr align="center">';
$html .= qq /<tr style="{$height;}" align="center">/;
foreach my $col (@$row) {
next if (!$col or $col eq '1'); # just an 'occupied' flag
$html .= $col;
}
$html .= '</tr>';
}
$self->{_numRowsLastWeek} = scalar (@weekTable); # for weekly planner view
$html;
}
sub numRowsLastWeek {
return shift->{_numRowsLastWeek} || 0;
}
# Pass list of events; remove ones we don't really want, and sort
sub _filterEvents {
my ($self, $events) = @_;
return [] unless $events;
# First, eliminate Tentative events we don't have Edit perm for;
# op must be ShowIt
$events = [$self->{_operation}->removeTentatives ($events)];
my @keepers;
foreach (@$events) {
next unless defined;
# if Planner view and not looking at main calendar, skip Private
# events.
next if ($self->{_plannerInclude} and $_->private);
# filter by Category
next if ($self->{_filterCategories} and
!$_->inCategory ($self->{_filterCategories}));
# filter by Text
next if (defined $self->{_filterText} and
!$_->matchesText ($self->{_filterText}, $self->{_filterIn}));
push @keepers, $_;
}
my @sorted = Event->sort (\@keepers, $self->{_prefs}->EventSorting);
return \@sorted;
}
# Pass list of events; return HTML to display in a day
sub _formatEvents {
my ($self, $events, $calName, $date, $i18n, $colors, $noTable) = @_;
my $incInfo = $self->{_prefs}->Includes;
my ($bgEvent, $fgEvent, $bgLink, $fgLink) = ($colors->{EventBG},
$colors->{EventFG},
$colors->{LinkBG},
$colors->{LinkFG});
my ($html, $bgColor);
foreach (@$events) {
my ($fgColor, $border, $textID);
# If planner view, this event might actually be an included one
# (We need the space to workaround Event::getHTML cache fix)
$_->includedFrom ($calName . ' ') if ($self->{_plannerInclude});
if ($_->includedFrom || '' ne $calName) {
($fgColor, $bgColor, $border, $textID) =
$_->getIncludedOverrides ($incInfo);
}
my $thisOnesBG = $_->bgColor;
my $thisOnesFG = $_->fgColor;
if ((!$fgColor || !$bgColor) && $_->category) {
($fgColor, $bgColor, $border) =
$_->getCategoryOverrides ($self->{_prefs},
MasterDB->new->getPreferences);
$fgColor = $thisOnesFG if $thisOnesFG;
$bgColor = $thisOnesBG if $thisOnesBG;
}
if ($self->{_prefs}->inPrintMode ('none')) { # use colors for 'some'
$fgColor = 'black';
$bgColor = 'white';
}
if (!$bgColor) {
my $bg = ($_->popup || $_->link) ? $bgLink : $bgEvent;
$bgColor = $thisOnesBG || $bg || '';
}
if (!$fgColor) {
my $fg = ($_->popup || $_->link) ? $fgLink : $fgEvent;
$fgColor = $thisOnesFG || $fg || 'black';
}
$border = ($_->drawBorder ? 1 : 0) unless defined $border;
if (!$noTable) {
$html .= "<table border=$border width=98% align=center " .
"cellpadding=0 cellspacing=0 bgcolor='$bgColor'><tr><td>";
}
$html .= $_->getHTML ({op => $self->{_operation},
calName => $calName,
date => $date,
prefs => $self->{_prefs},
i18n => $i18n,
textFG => $fgColor,
eventFace => $self->{eventFace},
eventSize => $self->{eventSize},
timeFace => $self->{timeFace},
timeSize => $self->{timeSize},
includeFace => $self->{incFace},
includeSize => $self->{incSize},
categoryFace => $self->{catFace},
categorySize => $self->{catSize},
textID => $textID});
if (!$noTable) {
$html .= '</td></tr></table>';
}
}
wantarray ? ($html, $bgColor) : $html; # undef if no events
}
sub getHTML {
my $self = shift;
$self->{'html'};
}
1;