|
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
# Event
# An Event has a string to display, and a link for either a URL or
# Javascript popup. It can also, optionally, have a start time, end time,
# and/or RepeatInfo object for repeating. And then there are other things, too.
# Methods include:
# new
# getTimeString
# isRepeating
# getHTML
# applies
# equals
# excludeThisInstance
# addToDateHash
# getIncludedOverrides ($incInfo) - check for included color, border overrides
# getCategoryOverrides ($prefs,) - check for included color, border overrides
# private - return true if event is not to be included in other cals
# privatePopup - return true if popup text is not to be included in other cals
# The Get/Set methods are handled by AUTOLOAD.
package Event;
use strict;
use Calendar::EventSorter;
use vars qw ($AUTOLOAD %validField);
use overload ('==' => 'equals',
'fallback' => 'true');
# If mod_perling, This had better be a constant hash.
# Date and TZoffset and Prefs are not stored!
BEGIN {
foreach (qw(text link popup export repeatInfo startTime endTime id owner
drawBorder bgColor fgColor includedFrom category isTentative
mailTo mailCC mailBCC mailText timePeriod
Date TZoffset Prefs
reminderTimes reminderTo subscriptions)) {
$validField{$_}++;
}
}
# Pass hash pairs
sub new {
my $class = shift;
my %args = ('text' => '',
'link' => '',
'popup' => '',
'export' => 'Public',
# repeatInfo => , Notice that
# startTime => , these keys (and others)
# endTime => , are optional
@_);
my $self = {};
bless $self, $class;
my ($key, $value);
while (($key, $value) = (each %args)) {
$self->{$key} = $value if $value;
}
# some are special; false is ok, undef is not. silly.
foreach (qw/startTime endTime/) {
$self->{$_} = $args{$_} if defined ($args{$_});
}
# If a Time Period defined, don't store start/end times
if ($args{timePeriod}) {
delete $self->{startTime};
delete $self->{endTime};
}
$self;
}
sub AUTOLOAD {
my $self = shift;
my $name = $AUTOLOAD;
$name =~ s/.*://; # get rid of package names, etc.
return unless $name =~ /[^A-Z]/; # ignore all cap methods; e.g. DESTROY
# Make sure it's a valid field, eh wot?
die "Bad Field Name to Event! '$name'\n" unless $validField{$name};
$self->{$name} = shift if (@_);
$self->{$name};
}
# Shallow copy; has references to same thingys (e.g. repeatInfo)
sub copy {
my $self = shift;
return Event->new (%$self);
}
# Return passed in text, possibly with HTML possibly escaped and things
# that look something like URLs converted to href links. Newlines are
# converted to <br>, unless simplistic check for HTML tags succeeds.
sub _escapeThis {
my ($text, $escaped, $doHREFs) = @_;
return '' unless $text;
my $noBR = ($text =~ /<[^>]*>/); # not very good, but good enough
if ($escaped) {
$text =~ s/</</g;
$text =~ s/>/>/g;
}
# If there's not href or img in there already, put in href and mailto: tags
if ($doHREFs and $text !~ /<\s*(a\s+)|(img)/i) {
$text =~ s {([-\w.]+@[-\w]+\.[-\w.]+)}
{<a href="mailto:$1">$1</a>}g;
my $target = '';
$target = ' target="_blank"' if ($doHREFs =~ /newwindow/i);
local $^W = undef;
# look for 'http' first, then for 'www' if no 'http' found
$text =~ s {http(s)?://(\S+)}
{<a href="http$1://$2"$target>$2</a>}g
or $text =~ s {(www\.[\w-]+\.[\w-]+)}
{<a href="http://$1"$target>$1</a>}g
}
$text =~ s/\n/<br>/g unless $noBR;
$text;
}
# args are flags for ($escapeIt, $doHREFs)
sub escapedText {
my $self = shift;
_escapeThis ($self->text, @_);
}
sub escapedPopup {
my $self = shift;
_escapeThis ($self->popup, @_);
}
# See if event text matches passed in regex (use quotemeta for exact match).
# 2nd param is where to look; one of ['text', 'popup', 'both']
sub matchesText {
my ($self, $regex, $where) = @_;
return undef unless defined $regex;
$where ||= 'both';
if ($where =~ /text|both/i) {
return 1 if (defined $self->text and $self->text =~ /$regex/);
}
if ($where =~ /popup|both/i) {
return 1 if (defined $self->popup and $self->popup =~ /$regex/);
return 1 if (defined $self->link and $self->link =~ /$regex/);
}
return undef;
}
# See if event is in any of the categories (typically for filtering)
# Pass one category, or ref to list of them.
sub inCategory {
my ($self, $cats) = @_;
$cats = [$cats] unless ref $cats;
my $myCat = $self->category;
return undef unless defined $myCat;
foreach my $cat (@$cats) {
return 1 if ($myCat eq $cat);
}
return undef;
}
sub hasTime {
my $self = shift;
return 1 if ($self->timePeriod or $self->startTime);
}
# Set or return startTime, which might be defined in a time period
sub startTime {
my $self = shift;
$self->{startTime} = shift if (@_);
return $self->{startTime} if exists ($self->{startTime});
return undef unless ($self->timePeriod and $self->Prefs);
$self->_getTimesFromPeriod;
return $self->{startTime};
}
sub endTime {
my $self = shift;
$self->{endTime} = shift if (@_);
return $self->{endTime} if exists ($self->{endTime});
return undef unless ($self->timePeriod and $self->Prefs);
$self->_getTimesFromPeriod;
return $self->{endTime};
}
sub _getTimesFromPeriod {
my $self = shift;
my ($name, $start, $end, $display) =
$self->Prefs->getTimePeriod ($self->timePeriod);
($self->{startTime}, $self->{endTime}) = ($start, $end);
}
# If object method, pass 'start', 'end', or 'both', and prefs object
# - If event has no times, returns undef
# - If event has Time Period, get times for that
# - If event has start but no end and you ask for both, returns just start
# If class method, just pass a time (as int) and militaryTimeP
sub getTimeString {
my $caller = shift;
my (@times, $milTimeP);
if (ref ($caller)) {
my ($which, $prefs) = @_;
$milTimeP = $prefs->MilitaryTime;
my ($start, $end);
$start = $caller->startTime;
$end = $caller->endTime;
push @times, $start if ($which =~ /start|both/ and defined $start);
push @times, $end if ($which =~ /end|both/ and defined $end);
} else {
push @times, shift;
$milTimeP = shift;
}
my ($theString, $secondTime);
foreach my $time (@times) {
next if (!defined $time or $time eq ''); # shouldn't happen, but...
$theString .= ' - ' if ($secondTime++);
my ($hour, $minute) = (int ($time / 60), $time % 60);
if ($milTimeP) {
$theString .= sprintf '%d:%.2d', $hour, $minute;
} else {
my $string;
if ($hour <= 12) {
# midnight = 0;
$string = sprintf '%d:%.2d', ($hour ? $hour : 12), $minute;
} else {
$string = sprintf '%d:%.2d', $hour - 12, $minute;
}
$string .= ($hour > 11) ? 'pm' : 'am';
$theString .= $string;
}
}
$theString;
}
# Return what date we fall on, adjusted for timezone
# Pass date, offset in hours (as stored in prefs)
sub getDisplayDate {
my ($self, $date, $offset) = @_;
my $start = $self->startTime;
return $date unless (defined $start and $offset);
$start += $offset * 60;
if ($start < 0) {
my $numDays = int ($start/-1440) + 1; # 1440 = 24* 60
return $date - $numDays;
} elsif ($start >= 1440) {
my $numDays = int ($start/1440);
return $date + $numDays;
}
return $date;
# return $date - 1 if $start < 0;
# return $date + 1 if $start >= 24*60;
# return $date;
}
# Return (startTime, endTime) adjusted by offset.
sub getDisplayTime {
my ($self, $offset) = @_;
my ($start, $end) = ($self->startTime, $self->endTime);
return ($start, $end) unless $offset;
foreach ($start, $end) {
next unless defined;
$_ += $offset * 60;
$_ %= 1440;
# if ($_ < 0) {
# $_ += 24*60;
# } elsif ($_ >= 24*60) {
# $_ -= 24*60;
# }
}
return ($start, $end);
}
# Change times, store date
# Return -1 if date decremented, 1 if incremented, 0 if unchanged
sub adjustForTimezone {
my ($self, $date, $offsetHours) = @_;
my ($start, $end) = ($self->startTime, $self->endTime);
my $offset = $offsetHours * 60;
my $ret = 0;
return $self->TZoffset if (defined $self->TZoffset);
if (defined $start) {
$start += $offset;
if ($start < 0) {
$date -= int ($start/-1440) + 1;
$start %= 1440;
$ret = -1;
} elsif ($start >= 24*60) {
$date += int ($start/1440);
$start %= 1440;
$ret = 1;
}
$self->startTime ($start);
if (defined $end) {
$end += $offset;
$self->endTime ($end % 1440);
}
}
$self->Date ($date); # not stored on disk
$self->TZoffset ($ret);
return $ret;
}
sub isRepeating {
my $self = shift;
return defined $self->{'repeatInfo'};
}
# Return ('', $linkText) if it's a link, ($popupText, '') if not.
sub textToPopupOrLink {
my ($ref, $text) = @_;
return ('', '') unless $text;
if ($text =~ /^((https?|mailto|ftp|file):)|^www\.[^ .]+\.[^ .]/s) {
return ('', ($1 ? $text : "http://$text"));
} else {
return ($text, '');
}
}
# The popup code is an HTTP link to display the popup
sub getHTML {
my ($self, $args) = @_;
die "Unexpected args to Event::getHTML\n" unless (ref $args eq 'HASH');
my ($op, $calName, $date, $prefs, $i18n, $textFG,
$eventFace, $eventSize, $timeFace, $timeSize, $textID,
$incFace, $incSize, $catFace, $catSize, $hideTimes);
$op = $args->{op};
$calName = $args->{calName};
$date = $args->{date};
$prefs = $args->{prefs};
$i18n = $args->{i18n};
$textFG = $args->{textFG};
$eventFace = $args->{eventFace};
$eventSize = $args->{eventSize};
$timeFace = $args->{timeFace};
$timeSize = $args->{timeSize};
$textID = $args->{textID};
$incFace = $args->{includeFace};
$incSize = $args->{includeSize};
$catFace = $args->{categoryFace};
$catSize = $args->{categorySize};
$hideTimes = $args->{hideTimes};
my $htmlText;
my $tagFG = $textFG ? "color=\"$textFG\"" : '';
# if displaying tentative event, add a tentative tag
if ($self->isTentative) {
$htmlText .= "<i><font $tagFG size=-1>" .
$i18n->get ('Pending Approval') . "</font></i><br>";
}
# includedFrom could be wrong if db is cached
if ($self->includedFrom and $self->includedFrom eq $calName) {
$self->includedFrom (undef);
}
if ($self->includedFrom and $textID) {
my $more = $incFace ? "face=$incFace " : '';
$more .= "size=$incSize" if (defined $incSize);
$htmlText .= "<font $tagFG $more>$textID</font><br>";
}
if (defined $self->category) {
my $cat = $prefs->category ($self->category) ||
MasterDB->new->getPreferences->category ($self->category);
if ($cat and $cat->showName) {
my $more = $catFace ? "face=$catFace " : '';
$more .= "size=$catSize" if (defined $catSize);
$htmlText .= "<font $tagFG $more>" . $cat->name .'</font><br>';
}
}
my @timeStrings;
unless ($hideTimes) {
if (my $period = $self->timePeriod) {
my ($name, $start, $end, $display) =
$prefs->getTimePeriod ($period);
$display ||= ''; # in case period doesn't exist
if ($display eq 'period' or $display eq 'both') {
push @timeStrings, $name;
}
if ($display eq 'times' or $display eq 'both') {
push @timeStrings, $self->getTimeString ('both', $prefs);
}
} elsif ($self->startTime) {
push @timeStrings, $self->getTimeString ('both', $prefs);
}
}
if (@timeStrings) {
my $timefont;
if ($textFG or $timeFace or $timeSize) {
$timefont = "<font $tagFG " .
($timeFace ? "face='$timeFace' " : '' ) .
($timeSize ? "size=$timeSize" : '' ) .
'>';
}
$htmlText .= $timefont if $timefont;
$htmlText .= join ': ', @timeStrings;
$htmlText .= '</font>' if $timefont;
$htmlText .= '<br>';
}
my ($font, $endFont);
if ($textFG or $eventFace or $eventSize) {
$font = "<font $tagFG" .
($eventFace ? "face='$eventFace' " : '') .
($eventSize ? "size=$eventSize" : '') .
'>';
$endFont = '</font>';
} else {
$font = $endFont = '';
}
# Display event text, or maybe special string (e.g. 'Out of Office')
if ($self->includedFrom and !$self->public and !$self->privatePopup) {
$htmlText .= $font . ($self->displayString ($i18n) || '') . $endFont;
} else {
# escape HTML if we need to, converting \n to <br>, and maybe
# detect email address and http links
my $escapeIt = $prefs->EventHTML =~ /none/;
my $eventText;
if ($self->{'link'} and
(!$self->includedFrom or $self->public)) {
$eventText = $self->escapedText ($escapeIt);
$htmlText .= "<a href=\"$self->{'link'}\">" .
"$font$eventText$endFont</a>";
} elsif ($self->{'popup'} and
(!$self->includedFrom or $self->public)) {
$eventText = $self->escapedText ($escapeIt);
my $id = $self->id;
my $source = $self->includedFrom || ' ';
my $width = $prefs->PopupWidth || 250;
my $height = $prefs->PopupHeight || 350;
my $jsParams = "'$calName', '$date', '$id', '$source', " .
"'$width', '$height'";
my $theText = $font . $eventText . $endFont;
my $jsStuff = qq{<a href="JavaScript:PopupWindow ($jsParams)">} .
"$theText</a>";
$jsStuff =~ s/(["'])/\\$1/g; # '"
$htmlText .= qq {
<script>
document.write ("$jsStuff");
</script>
};
my $url = '';
if ($op) {
$url = $op->makeURL ({Op => 'PopupWindow',
ID => $id,
Date => $date,
Source => $source,
DoneURL =>
$op->makeURL ({Op => $op->opName})});
}
$htmlText .= qq {
<noscript>
<a href="$url">$theText</a>
</noscript>
};
} else {
$eventText = $self->escapedText ($escapeIt, 'doHREFs');
$htmlText .= $font . $eventText . $endFont;
}
}
$htmlText;
}
# Return (fg, bg) colors based on event settings, inclusion, category
# return included colors if included (and override set)
# else, return event specific colors (if set)
# else, return category colors (if set)
# else, return colors from prefs
sub colors {
my ($self, $calName, $prefs, $noDefault) = @_;
my ($fgColor, $bgColor, $border, $textID);
# included calendar colors specified?
if ($self->includedFrom || '' ne $calName) {
my ($fg, $bg, $bdr, $id) =
$self->getIncludedOverrides ($prefs->Includes);
$fgColor = $fg unless defined $fgColor;
$bgColor = $bg unless defined $bgColor;
}
return ($fgColor, $bgColor) if (defined $fgColor and defined $bgColor);
# event have it's own colors specified?
$fgColor = $self->fgColor unless defined $fgColor;
$bgColor = $self->bgColor unless defined $bgColor;
return ($fgColor, $bgColor) if (defined $fgColor and defined $bgColor);
# category colors specified?
if ($self->category) {
my ($fg, $bg, $bdr) = $self->getCategoryOverrides ($prefs,
MasterDB->new->getPreferences);
$fgColor = $fg unless defined $fgColor;
$bgColor = $bg unless defined $bgColor;
}
return ($fgColor, $bgColor) if (defined $fgColor and defined $bgColor);
return ($fgColor, $bgColor) if $noDefault;
$fgColor = $prefs->color ('EventFG') if (!defined $fgColor);
$bgColor = $prefs->color ('EventBG') if (!defined $bgColor);
return ($fgColor, $bgColor);
}
sub applies {
my $self = shift;
my ($date) = @_;
# return true right away if it's not a repeating event
return 1 unless $self->isRepeating();
# otherwise, ask the RepeatInfo object
return $self->{'repeatInfo'}->applies ($date);
}
sub equals {
my ($e1, $e2, $backwards) = @_;
return ($e1->{'id'} == $e2->{'id'});
}
# Use this to keep track of which instances of a repeating event we deleted
sub excludeThisInstance {
my $self = shift;
my ($date) = @_;
# Simply pass it along to the RepeatInfo object, unless there isn't
# one.
if ($self->isRepeating()) {
$self->{'repeatInfo'}->excludeThisInstance ($date);
}
}
# Set or Return ref to list of excluded date objs; return undef if not a
# repeating event
sub exclusionList {
my $self = shift;
my $listRef = shift;
# Simply pass it along to the RepeatInfo object, unless there isn't
# one.
if ($self->isRepeating()) {
return $self->{'repeatInfo'}->exclusionList ($listRef);
}
return;
}
# Pass through to EventSorter
sub sort {
my ($class, $eventListRef, $sortPref) = @_;
my $sorter = EventSorter->new (split (',', ($sortPref || ())));
my $sortedListref = $sorter->sortEvents ($eventListRef);
return @$sortedListref;
}
# Find all applicable dates for this event, add to the hash passed in.
# Notice that if there is no repeat info, we don't need to do anything.
sub addToDateHash {
my $self = shift;
my ($hash, $fromDate, $toDate, $prefs) = @_;
return unless $self->isRepeating();
$self->repeatInfo()->addToDateHash ($hash, $fromDate, $toDate,
$self, $prefs);
}
sub getIncludedOverrides
{
my $self = shift;
my ($incInfo) = @_;
my $incCal = $self->includedFrom();
my ($fgColor, $bgColor, $border, $text);
if ($incCal &&
$incInfo->{$incCal}->{'Included'}) {
$text = $incInfo->{$incCal}->{Text} || '';
if ($incInfo->{$incCal}->{'Override'}) {
$fgColor = $incInfo->{$incCal}->{'FG'};
$bgColor = $incInfo->{$incCal}->{'BG'};
$border = $incInfo->{$incCal}->{'Border'} ? 1 : 0;
}
}
($fgColor, $bgColor, $border, $text);
}
sub getCategoryOverrides
{
my $self = shift;
my (@prefs) = @_;
my $catName = $self->category;
return undef unless $catName;
foreach my $prefs (@prefs) {
my $cat = $prefs->category ($catName);
return ($cat->fg, $cat->bg, $cat->border) if $cat;
}
undef;
}
sub public {
my $self = shift;
return (!defined $self->export or
$self->export =~ /Public/i or
$self->export eq '');
}
sub private {
my $self = shift;
return ($self->export and $self->export =~ /Private/i);
}
sub privatePopup {
my $self = shift;
return ($self->export and $self->export =~ /NoPopup/i);
}
sub displayString {
my $self = shift;
my $i18n = shift;
return $i18n->get ('Private') if ($self->private);
return $i18n->get ('Private Popup') if ($self->privatePopup);
return '' unless $self->export;
return $i18n->get ('Unavailable') if ($self->export =~ /Unavailable/i);
return $i18n->get ('Out of Office') if ($self->export =~ /OutOfOffice/i);
return '';
}
# -- Subscriptions
# 'subscriptions' looks like: "calname:[email protected],[email protected];otherCal:[email protected]"
# Need to specify which calendar we're interested in
sub isSubscribed {
my ($self, $address, $calName) = @_;
my $addrs = $self->getSubscribers ($calName); # comma joined list
return ($addrs =~ /\b$address\b/i);
}
sub addSubscriber {
my ($self, $address, $calName) = @_;
my @calStrings = split /;/, ($self->subscriptions || '');
my $foundIt;
foreach (@calStrings) {
next unless /^$calName:(.*)/;
$foundIt = 1;
$_ .= ',' if $1;
$_ .= $address;
last;
}
if (!$foundIt) {
push @calStrings, "$calName:$address";
}
$self->subscriptions (join ';', @calStrings);
}
# Return comma joined list of addresses for specified calendar.
sub getSubscribers {
my ($self, $calName) = @_;
my @calStrings = split /;/, ($self->subscriptions || '');
foreach (@calStrings) {
next unless /^$calName:/;
s/$calName://;
return $_;
}
return '';
}
{
# these are the fields that can get stored
my %map = (a => 'text',
b => 'link',
c => 'popup',
d => 'export',
e => 'startTime',
f => 'endTime',
g => 'id',
h => 'owner',
i => 'drawBorder',
j => 'bgColor',
k => 'fgColor',
l => 'mailTo',
m => 'mailCC',
n => 'mailBCC',
o => 'mailText',
p => 'reminderTimes',
q => 'reminderTo',
r => 'category',
s => 'isTentative',
t => 'subscriptions',
u => 'timePeriod',
A => 'startDate',
B => 'endDate',
C => 'period',
D => 'frequency',
E => 'monthWeek',
F => 'monthMonth',
G => 'exclusions',
H => 'skipWeekends');
# Return a list of ascii elements representing an event. Escape newlines.
sub serialize {
my $self = shift;
my @list;
if ($self->timePeriod) {
delete $self->{startTime};
delete $self->{endTime};
}
foreach ('a'..'u') {
my $val = $self->{$map{$_}};
push @list, ($_, $val) if (defined $val);
}
if ($self->isRepeating) {
foreach ('A'..'H') {
my $val = $self->{'repeatInfo'}->{$map{$_}};
push @list, ($_, $val) if (defined $val);
}
}
for (my $i=1; $i<@list; $i+=2) {
if (ref ($list[$i]) eq 'Date') {
$list[$i] = "$list[$i]";
# some fields could be lists (period, monthWeek, exclusions)
# store them separated by whitespace
} elsif (ref ($list[$i]) eq 'ARRAY') {
$list[$i] = join ' ', @{$list[$i]};
} else {
$list[$i] =~ s/\n/\\n/g;
$list[$i] =~ s/\r//g; # otherwise it writes carriage returns
}
}
@list;
}
sub unserialize {
my $classname = shift;
my (%values) = @_;
my $self = {};
bless $self, $classname;
my $val;
foreach ('a'..'u') {
next unless defined ($val = $values{$_});
($self->{$map{$_}} = $val) =~ s/\\n/\n/g;
}
if ($values{'A'}) { # startDate
require Calendar::RepeatInfo;
$self->{repeatInfo} = RepeatInfo->new (@values{'A'..'F','H'});
if ($values{'G'}) {
require Calendar::Date;
my @exclusions = split /\s+/, $values{'G'};
@exclusions = map {Date->new($_)} @exclusions;
$self->{repeatInfo}->{'exclusions'} = \@exclusions;
}
}
$self;
}
}
1;