[MKDoc-dev] Events Component: Milestone 3 Complete
Sam Tregar
sam at tregar.com
Wed Nov 3 20:52:17 GMT 2004
Please find a patch implementing the final milestone of the events
component project, newletter support for events. This is a patch
against the mkdoc-1-6 branch which has some of the code from the first
milestone already committed.
I still have one bug to fix in the TimeRange component code concerning
deleting from the Document_TimeRange table when time-ranges are
removed from a document, which I will work on later this week. Aside
from that I believe that all the goals of the project have been met.
Please let me know if you find any problems or have any questions.
Unless other bugs come up I plan to spend the remainder of the time
allocated on the project (around 8 hours) writing a document
describing how to add new document components to MKDoc. This will
obviously be of use to future developers but it will also serve to
expand my understanding of what I just did and double-check my
assumptions.
-sam
-------------- next part --------------
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/flo/editor/Headlines.pm mkd/flo/editor/Headlines.pm
--- mkd-1.6/flo/editor/Headlines.pm 2004-09-16 05:55:45.000000000 -0400
+++ mkd/flo/editor/Headlines.pm 2004-11-02 22:28:09.000000000 -0500
@@ -1,7 +1,7 @@
# -------------------------------------------------------------------------------------
# flo::editor::Headlines
# -------------------------------------------------------------------------------------
-# Author : Jean-Michel Hiver.
+# Author : Jean-Michel Hiver, Sam Tregar
# Copyright : (c) MKDoc Holdings Ltd, 2003
#
# This file is part of MKDoc.
@@ -52,9 +52,31 @@
$self->{title} = $args->{title} || '';
$self->{from_path} = $args->{'from_path'} || '';
$self->{max_headlines} = $args->{'max_headlines'} || '';
- $self->{leaf_only} = $args->{'leaf_only'} || '';
+ $self->{leaf_only} = $args->{'leaf_only'} || '';
+ $self->{mode} = $args->{'mode'} || 'newest';
}
+# getter for mode
+sub mode {
+ my $self = shift;
+ croak("Too many args for getter!") if @_;
+ return $self->{mode};
+}
+
+# true when mode == newest
+sub mode_is_newest { shift->{mode} eq 'newest' }
+
+# true when mode == upcoming
+sub mode_is_upcoming { shift->{mode} eq 'upcoming' }
+
+# setter for mode, must be newest or upcoming
+sub set_mode {
+ my ($self, $value) = @_;
+ croak("Wrong number of args for setter!") if @_ != 2;
+ croak("Mode must be 'newest' or 'upcoming' not '$value'.")
+ unless $value eq 'newest' or $value eq 'upcoming';
+ $self->{mode} = $value;
+}
sub leaf_only
{
@@ -73,9 +95,20 @@
return $self->validate_title() &
$self->validate_from_path() &
- $self->validate_max_headlines();
+ $self->validate_max_headlines() &
+ $self->validate_mode;
}
+# check mode value
+sub validate_mode {
+ my $self = shift;
+ unless (defined $self->{mode} and
+ ($self->{mode} eq 'newest' or $self->{mode} eq 'upcoming')) {
+ new MKDoc::Ouch 'component/headlines/mode_invalid';
+ return 0;
+ }
+ return 1;
+}
sub validate_title
{
@@ -210,7 +243,6 @@
} @path;
my $path = join '|', @path;
- warn $path;
return "^($path)\$";
}
else
@@ -264,7 +296,80 @@
sub default_headlines
{
my $self = shift;
+ my $mode = $self->{mode};
+
+ # switch on mode
+ if ($mode eq 'newest') {
+ return $self->_default_headlines_newest();
+ } else {
+ return $self->_default_headlines_upcoming();
+ }
+}
+
+sub _default_headlines_upcoming {
+ my $self = shift;
+ my $limit = $self->max_default_headlines();
+
+ # as far as I can tell the lib::sql system can't handle a simple
+ # join, so this has to be done in straight SQL. Sigh.
+ my $sql = "SELECT Document.ID as ID,
+ Document_TimeRange.ID as Document_TimeRange_ID
+ FROM Document, Document_TimeRange
+ WHERE Document.ID = Document_TimeRange.Document_ID
+ AND Document_TimeRange.FromDate >= NOW()
+ AND Full_Path REGEXP ?
+ ORDER BY Document_TimeRange.FromDate ASC";
+
+ my $dbh = lib::sql::DBH->get;
+ my $sth = $dbh->prepare_cached($sql);
+ $sth->execute($self->from_path_search_value());
+ my $query = new lib::sql::Query (sth => $sth, bless_into => 'flo::Record::Document');
+ my @res = $query->fetch_all;
+
+ my $doc_t = flo::Standard::table ('Document');
+
+ # get document objects for results
+ my @documents = map { $doc_t->get ( $_->{ID} ) } @res;
+
+ # get timerange objects from documents
+ my @timerange_ids = map { $_->{Document_TimeRange_ID} } @res;
+ my @timeranges;
+ foreach my $doc (@documents) {
+ my $timerange_id = shift @timerange_ids;
+ # find this timerange in the list of components for this doc
+ my ($timerange) =
+ grep { $_->isa('flo::editor::TimeRange') and
+ $_->Document_TimeRange_ID == $timerange_id }
+ $doc->components;
+ push(@timeranges, $timerange);
+ }
+
+ # combine documents and their timeranges into a single results
+ # stream, weeding out missing timeranges. This can happen when
+ # the Document_TimeRange table gets out of sync with the Document
+ # table
+ @res = grep { defined $_->{timerange} }
+ map { { document => $documents[$_],
+ timerange => $timeranges[$_] } } (0 .. $#documents);
+
+ # limit to documents which are showable
+ @res = grep { $_->{document}->is_showable() } @res;
+ # limit to leaves
+ if ($self->leaf_only()) {
+ @res = map { my @children = $_->{document}->children_showable(); @children ? () : $_ } @res;
+ }
+
+ # limit to max number of headlines
+ @res = splice @res, 0, $self->max_default_headlines();
+
+ return wantarray ? @res : \@res;
+}
+
+
+sub _default_headlines_newest {
+ my $self = shift;
+
# performs the query and put results in the stash
my $document_t = flo::Standard::table ('Document');
@@ -317,15 +422,33 @@
sub personalized_headlines
{
my $self = shift;
-
my $user = flo::Standard::current_user() || return [];
-
+ my $mode = $self->{mode};
+
+ # concoct SQL needed for upcoming or newest mode
+ my ($extra_from, $extra_where, $extra_select, $order_by, $group_by);
+ if ($mode eq 'upcoming') {
+ $order_by = "Document_TimeRange.FromDate ASC";
+ $group_by = "Document_TimeRange.ID";
+
+ $extra_from = ", Document_TimeRange";
+ $extra_where = "AND Document.ID = Document_TimeRange.Document_ID";
+ $extra_select = ", Document_TimeRange.ID as Document_TimeRange_ID";
+ } else {
+ $order_by = "Date_Created DESC";
+ $group_by = "Document.ID";
+ ($extra_from, $extra_where, $extra_select) = ("") x 3;
+ }
+
+
$self->{_personalized_headlines} ||= do {
# This is horrible, but I really don't see how to get around it
# until we get some kind of RSS search engine
my $sql = <<SQL;
-SELECT Document.ID AS ID, SUM(Preference_Audience.Value) AS Pref_Score
+SELECT Document.ID AS ID, SUM(Preference_Audience.Value) AS Pref_Score
+ $extra_select
FROM Document, Document_Audience, Audience, Preference_Audience, Editor, Preference_Language
+ $extra_from
WHERE
-- join the tables together
(
@@ -345,8 +468,12 @@
AND
-- remove languages which are not wanted
Preference_Language.Value = 1
-GROUP BY (Document.ID)
-ORDER BY Date_Created DESC
+
+$extra_where
+
+GROUP BY $group_by
+HAVING Pref_Score > 0
+ORDER BY $order_by
SQL
# $sql .= "\nLIMIT 0, " . $self->max_personalized_headlines;
my $dbh = lib::sql::DBH->get;
@@ -355,25 +482,56 @@
my $query = new lib::sql::Query (sth => $sth, bless_into => 'flo::Record::Document');
my @res = $query->fetch_all;
+ # get documents from ID list
my $doc_t = flo::Standard::table ('Document');
- @res = map { $doc_t->get ( $_ ) }
- map { $_->{Pref_Score} > 0 ? $_->{ID} : () }
- @res;
-
- # limit to documents which are showable
- @res = map { $_->is_showable() ? $_ : () } @res;
-
- if ($self->leaf_only())
- {
- @res = map { my @children = $_->children_showable(); @children ? () : $_ } @res;
+ my @documents = map { $doc_t->get( $_->{ID} ) } @res;
+
+ # deal with timerange data for upcoming events list
+ if ($mode eq 'upcoming') {
+ # get timerange objects from documents
+ my @timerange_ids = map { $_->{Document_TimeRange_ID} } @res;
+ my @timeranges;
+ foreach my $doc (@documents) {
+ my $timerange_id = shift @timerange_ids;
+ # find this timerange in the list of components for this doc
+ my ($timerange) =
+ grep { $_->isa('flo::editor::TimeRange') and
+ $_->Document_TimeRange_ID == $timerange_id }
+ $doc->components;
+ push(@timeranges, $timerange);
+ }
+
+ # combine documents and their timeranges into a single
+ # results stream, weeding out missing timeranges. This
+ # can happen when the Document_TimeRange table gets out of
+ # sync with the Document table
+ @res = grep { defined $_->{timerange} }
+ map { { document => $documents[$_],
+ timerange => $timeranges[$_] } } (0..$#documents);
+
+ } else {
+ @res = @documents;
}
-
- # limit to max number of headlines
- @res = splice @res, 0, $self->max_personalized_headlines();
-
+
+ # limit to documents which are showable
+ @res = grep { ($mode eq 'newest' ? $_->is_showable() :
+ $_->{document}->is_showable) }
+ @res;
+
+ if ($self->leaf_only()) {
+ @res = grep { my @children = ($mode eq 'newest' ?
+ $_->children_showable() :
+ $_->{document}->children_showable());
+ @children } @res;
+ }
+
+ # limit to max number of headlines
+ @res = splice @res, 0, $self->max_personalized_headlines();
+
\@res;
};
-
+
+
my $res = $self->{_personalized_headlines};
return wantarray ? @{$res} : $res;
}
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/flo/editor/TimeRange.pm mkd/flo/editor/TimeRange.pm
--- mkd-1.6/flo/editor/TimeRange.pm 1969-12-31 19:00:00.000000000 -0500
+++ mkd/flo/editor/TimeRange.pm 2004-10-23 19:58:21.000000000 -0400
@@ -0,0 +1,533 @@
+# -------------------------------------------------------------------------------------
+# flo::editor::TimeRange
+# -------------------------------------------------------------------------------------
+# Author : Sam Tregar
+# Copyright : (c) MKDoc Holdings Ltd, 2004
+#
+# This file is part of MKDoc.
+#
+# MKDoc is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# MKDoc is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with MKDoc; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# -------------------------------------------------------------------------------------
+package flo::editor::TimeRange;
+
+=head1 NAME
+
+flo::editor::TimeRange - an editor for time-ranges
+
+=head1 DESCRIPTION
+
+The module provides an editor for time-range components. A time-range
+component has a title, a start date and an end date. Documents with
+time-ranges will appear in the upcoming-events list in headlines and
+newsletters.
+
+=head1 INTERFACE
+
+=over 4
+
+=cut
+
+use flo::Standard;
+use Carp qw(croak);
+use strict;
+use warnings;
+use utf8;
+
+use flo::Record::Editor;
+use Text::Unidecode;
+use MKDoc::Config;
+use flo::Editor;
+use flo::Standard;
+use MKDoc::CGI;
+use MKDoc::Util::Text2HTML;
+use flo::RedirectManager;
+use DateTime;
+use DateTime::TimeZone;
+
+use base qw /flo::Component
+ flo::editor::Mixin::compute_name
+ flo::editor::Mixin::normalize_name/;
+
+use constant DATETIME_PARTS => qw(month day year hour minute tz);
+use constant DATE_PARTS => qw(month day year);
+
+sub preferred_extension { 'html' };
+
+sub _initialize {
+ my $self = shift;
+ my $args = $self->cgi_args();
+
+ # setup simple fields
+ $self->{Document_TimeRange_ID} = $args->{Document_TimeRange_ID} || undef;
+ $self->{title} = $args->{title} || '';
+
+ # setup date fields, defaulting date parts to now and time to 00:00
+ my $now = DateTime->now(time_zone => "local");
+ foreach my $prefix (qw(from to)) {
+ foreach my $part (DATE_PARTS) {
+ $self->{"${prefix}_$part"} = $args->{"${prefix}_$part"} || $now->$part;
+ }
+ $self->{"${prefix}_hour"} = $args->{"${prefix}_hour"} || "00";
+ $self->{"${prefix}_minute"} = $args->{"${prefix}_minute"}|| "00";
+ $self->{"${prefix}_tz"} = $args->{"${prefix}_tz"} || $now->time_zone->name;
+ }
+}
+
+=item C<< $self->validate() >>
+
+Check the data entered for validity. Makes sure everything has a
+value and that the dates aren't invalid. Returns 1 if everything is
+ok, 0 otherwise. When errors are found MKDoc::Ouch objects are
+created with codes to describe the problem (ex:
+component/timerange/title_empty).
+
+=cut
+
+sub validate {
+ my $self = shift;
+ my $problems = 0;
+
+ # set up the callback for errors
+ local $MKDoc::Ouch::CALLBACK;
+ $MKDoc::Ouch::CALLBACK = sub { $self->add_error (@_) };
+
+ # look at dates individually
+ foreach my $prefix (qw(from to)) {
+ $problems += not $self->validate_date($prefix);
+ }
+
+ # compare the dates and make sure from is before to. Only do this
+ # if everything else is ok since it won't work on incomplete dates
+ if (not $problems) {
+ my $cmp = $self->from_datetime <=> $self->to_datetime;
+ if ($cmp == 0) {
+ new MKDoc::Ouch "component/timerange/dates_equal";
+ $problems++;
+ } elsif ($cmp == 1) {
+ new MKDoc::Ouch "component/timerange/dates_reversed";
+ $problems++;
+ }
+ }
+
+ # validate title, must have something aside from space
+ if (not defined $self->{title} or $self->{title} =~ /^\s*$/) {
+ new MKDoc::Ouch 'component/timerange/title_empty';
+ $problems++;
+ }
+
+ return not $problems;
+}
+
+sub validate_date {
+ my ($self, $prefix) = @_;
+ my $problems = 0;
+
+ # these must be non-zero
+ foreach my $part (qw(month day year)) {
+ next if $self->{"${prefix}_$part"};
+ new MKDoc::Ouch "component/timerange/${prefix}_${part}_empty";
+ $problems++;
+ }
+
+ # these just need to be defined
+ foreach my $part (qw(hour minute tz)) {
+ next if defined $self->{"${prefix}_$part"};
+ new MKDoc::Ouch "component/timerange/${prefix}_${part}_empty";
+ $problems++;
+ }
+
+ # make sure the date is valid if everything else was ok
+ if (not $problems and not $self->_gen_datetime($prefix)) {
+ new MKDoc::Ouch "component/timerange/${prefix}_invalid";
+ $problems++;
+ }
+
+ return not $problems;
+}
+
+=item C<< $title = $self->title >>
+
+Returns the title attribute
+
+=cut
+
+sub title {
+ my $self = shift;
+ croak("Too many args for getter!") if @_;
+ $self->{title};
+}
+
+=item C<< $self->set_title($new_title); >>
+
+Sets the title attribute
+
+=cut
+
+sub set_title {
+ my $self = shift;
+ $self->{title} = shift;
+ $self->{title} = join ' ', split /(?:\n|\r)/, $self->{title};
+ $self->{title} =~ s/^\s+//;
+ $self->{title} =~ s/\s+$//;
+}
+
+=item C<< $id = $self->Document_TimeRange_ID >>
+
+Returns the ID for the row in Document_TimeRange corresponding to this
+object or undef if no row exists.
+
+=cut
+
+sub Document_TimeRange_ID {
+ my $self = shift;
+ croak("Too many args for getter!") if @_;
+ $self->{Document_TimeRange_ID};
+}
+
+=item C<< $self->set_Document_TimeRange_ID($new_ID); >>
+
+Sets the ID for the row in Document_TimeRange corresponding to this
+object.
+
+=cut
+
+sub set_Document_TimeRange_ID {
+ my ($self, $value) = @_;
+ croak("Wrong number of args for setter!") if @_ != 2;
+ $self->{Document_TimeRange_ID} = $value;
+}
+
+=item C<< $month = $self->from_month >>
+
+=item C<< $self->set_from_month($month) >>
+
+=item C<< $day = $self->from_day >>
+
+=item C<< $self->set_from_day($day) >>
+
+=item C<< $year = $self->from_year >>
+
+=item C<< $self->set_from_year($year) >>
+
+=item C<< $hour = $self->from_hour >>
+
+=item C<< $self->set_from_hour($hour) >>
+
+=item C<< $minute = $self->from_minute >>
+
+=item C<< $self->set_from_minute($minute) >>
+
+=item C<< $tz = $self->from_tz >>
+
+=item C<< $self->set_from_tz($tz) >>
+
+Get/set parts of the from date. Months are numbered from 1 to 12,
+hours are 0 to 23 and timezones are offsets from UTC in +/-HH:MM format
+(ex. +05:00, -13:50).
+
+=item C<< $month = $self->to_month >>
+
+=item C<< $self->set_to_month($month) >>
+
+=item C<< $day = $self->to_day >>
+
+=item C<< $self->set_to_day($day) >>
+
+=item C<< $year = $self->to_year >>
+
+=item C<< $self->set_to_year($year) >>
+
+=item C<< $hour = $self->to_hour >>
+
+=item C<< $self->set_to_hour($hour) >>
+
+=item C<< $minute = $self->to_minute >>
+
+=item C<< $self->set_to_minute($minute) >>
+
+=item C<< $tz = $self->to_tz >>
+
+=item C<< $self->set_to_tz($tz) >>
+
+Get/set parts of the to date. Months are numbered from 1 to 12, hours
+are 0 to 23 and timezones are offsets from UTC in +/-HH:MM format
+(ex. +05:00, -13:50).
+
+=item C<< $date = $self->from_datetime >>
+
+Returns a DateTime object representing the from_date. Returns undef
+if the date is invalid.
+
+=item C<< $date = $self->to_datetime >>
+
+Returns a DateTime object representing the to_date. Returns undef if
+the date is invalid.
+
+=cut
+
+# auto-generate accessors and mutators for date parts
+BEGIN {
+ no strict 'refs';
+ foreach my $prefix qw(from to) {
+ foreach my $part (DATETIME_PARTS) {
+ my $name = "${prefix}_$part";
+ *{$name} = sub {
+ my $self = shift;
+ croak("Too many args for getter!") if @_;
+ return $self->{$name};
+ };
+ *{"set_$name"} = sub {
+ my ($self, $value) = @_;
+ croak("Wrong number of args for setter!") if @_ != 2;
+ $self->{$name} = $value;
+ }
+ }
+ }
+}
+
+# from_datetime and to_datetime share a common worker, _gen_datetime
+sub from_datetime {
+ return shift->_gen_datetime("from");
+}
+
+sub to_datetime {
+ return shift->_gen_datetime("to");
+}
+
+sub _gen_datetime {
+ my ($self, $which) = @_;
+
+ # catch error for bad dates and just return undef
+ my $dt;
+ eval {
+ $dt = DateTime->new( (map { ($_ => $self->{"${which}_$_"}) }
+ (qw(year month day hour minute))),
+ second => 0,
+ time_zone => $self->{"${which}_tz"},
+ );
+ };
+
+ return $dt;
+}
+
+=item C<< $self->month_select($which) >>
+
+Returns an array of hashes for use in building the month selector in
+the time-range editor. Pass "from" or "to" as the sole parameter
+depending on which selector is being built. Keys are 'label', 'value'
+and 'is_selected'.
+
+=cut
+
+sub month_select {
+ my $self = shift;
+ croak("Wrong number of params for month_select().") unless @_ == 1;
+ my $which = shift;
+ croak("Bad value for which: '$which'. Should be 'from' or 'to'.")
+ unless $which eq 'from' or $which eq 'to';
+ my $x = 0;
+ return [ map { $x++;
+ { label => $_,
+ value => $x,
+ is_selected => (($self->{"${which}_month"} || 0) == $x) ?
+ "selected" : undef
+ } }
+ (qw(January February March April May June July August September October November December)) ];
+}
+
+=item C<< $self->day_select($which) >>
+
+Returns an array of hashes for use in building the day selector in
+the time-range editor. Pass "from" or "to" as the sole parameter
+depending on which selector is being built. Keys are 'label', 'value'
+and 'is_selected'.
+
+=cut
+
+sub day_select {
+ my $self = shift;
+ croak("Wrong number of params for day_select().") unless @_ == 1;
+ my $which = shift;
+ croak("Bad value for which: '$which'. Should be 'from' or 'to'.")
+ unless $which eq 'from' or $which eq 'to';
+ return [ map { { label => $_,
+ value => $_,
+ is_selected => (($self->{"${which}_day"} || 0) == $_) ?
+ "selected" : undef
+ } }
+ (1 .. 31) ];
+}
+
+=item C<< $self->year_select($which) >>
+
+Returns an array of hashes for use in building the year selector in
+the time-range editor. Pass "from" or "to" as the sole parameter
+depending on which selector is being built. Keys are 'label', 'value'
+and 'is_selected'.
+
+=cut
+
+sub year_select {
+ my $self = shift;
+ croak("Wrong number of params for year_select().") unless @_ == 1;
+ my $which = shift;
+ croak("Bad value for which: '$which'. Should be 'from' or 'to'.")
+ unless $which eq 'from' or $which eq 'to';
+ my $year = (localtime())[5] + 1900;
+ $year = $self->{"${which}_year"} if $self->{"${which}_year"} and
+ $year > ($self->{"${which}_year"});
+ return [ map { { label => $_,
+ value => $_,
+ is_selected => (($self->{"${which}_year"} || 0) == $_) ?
+ "selected" : undef
+ } }
+ ($year .. $year + 25) ];
+}
+
+=item C<< $self->hour_select($which) >>
+
+Returns an array of hashes for use in building the hour selector in
+the time-range editor. Pass "from" or "to" as the sole parameter
+depending on which selector is being built. Keys are 'label', 'value'
+and 'is_selected'.
+
+=cut
+
+sub hour_select {
+ my $self = shift;
+ croak("Wrong number of params for hour_select().") unless @_ == 1;
+ my $which = shift;
+ croak("Bad value for which: '$which'. Should be 'from' or 'to'.")
+ unless $which eq 'from' or $which eq 'to';
+ return [ map { { label => sprintf("%02d", $_),
+ value => $_,
+ is_selected => (($self->{"${which}_hour"} || 0) == $_) ?
+ "selected" : undef
+ } }
+ (0 .. 23) ];
+}
+
+=item C<< $self->minute_select($which) >>
+
+Returns an array of hashes for use in building the minute selector in
+the time-range editor. Pass "from" or "to" as the sole parameter
+depending on which selector is being built. Keys are 'label', 'value'
+and 'is_selected'.
+
+=cut
+
+sub minute_select {
+ my $self = shift;
+ croak("Wrong number of params for minute_select().") unless @_ == 1;
+ my $which = shift;
+ croak("Bad value for which: '$which'. Should be 'from' or 'to'.")
+ unless $which eq 'from' or $which eq 'to';
+ return [ map { { label => sprintf("%02d", $_),
+ value => $_,
+ is_selected => (($self->{"${which}_minute"} || 0) == $_) ?
+ "selected" : undef
+ } }
+ (0 .. 59) ];
+}
+
+=item C<< $self->tz_select($which) >>
+
+Returns an array of hashes for use in building the tz selector in
+the time-range editor. Pass "from" or "to" as the sole parameter
+depending on which selector is being built. Keys are 'label', 'value'
+and 'is_selected'.
+
+=cut
+
+# I wish there was a way to do this that used documented
+# functionality. Maybe we'll switch to DateTime::TimeZone. Also, the
+# list of timezones is much too small.
+
+sub tz_select {
+ my $self = shift;
+ croak("Wrong number of params for tz_select().") unless @_ == 1;
+ my $which = shift;
+ croak("Bad value for which: '$which'. Should be 'from' or 'to'.")
+ unless $which eq 'from' or $which eq 'to';
+ my $value = $self->{"${which}_tz"};
+
+ # return all available names with current selected
+ return [ map { { label => $_,
+ value => $_,
+ is_selected => $_ eq $value ? 1 : undef,
+ } } DateTime::TimeZone->all_names ];
+}
+
+=item C<< $self->generate_xml() >>
+
+This class overrides generate_xml() in order to update the
+Document_TimeRange table when the user submits a change.
+
+=cut
+
+# it might be better to have an explicit on_save() hook available,
+# since it's possible that something else might want to use
+# generate_xml() someday. At the moment it appears to be used only to
+# save changes to the database making it acceptable for this use.
+
+sub generate_xml {
+ my $self = shift;
+ $self->write_index();
+ return $self->SUPER::generate_xml(@_);
+}
+
+=item C<< $self->write_index() >>
+
+Called by generate_xml() to write to the index table,
+Document_TimeRange. Returns 1 on success.
+
+=cut
+
+sub write_index {
+ my $self = shift;
+ my $dbh = lib::sql::DBH->get();
+ my $id = $self->Document_TimeRange_ID;
+
+ # normalize the dates to UTC to make searches easier and format
+ # them for MySQL
+ my ($from_date, $to_date) =
+ map { my $date = $self->_gen_datetime($_);
+ $date->set_time_zone("UTC");
+ $date->strftime('%Y-%m-%d %H:%M:00') }
+ (qw(from to));
+
+ if ($id) {
+ # if an ID already exists do an UPDATE
+ $dbh->do('UPDATE Document_TimeRange
+ SET Document_ID = ?, FromDate = ?, ToDate = ?
+ WHERE ID = ?', undef, $self->parent_id,
+ $from_date, $to_date, $id);
+ } else {
+ # insert a new record and get back the ID
+ $dbh->do('INSERT INTO Document_TimeRange
+ (Document_ID, FromDate, ToDate) VALUES (?, ?, ?)',
+ undef, $self->parent_id, $from_date, $to_date);
+ $self->set_Document_TimeRange_ID($dbh->{mysql_insertid});
+ }
+
+ return 1;
+}
+
+=back
+
+=cut
+
+1;
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/INSTALL.TXT mkd/INSTALL.TXT
--- mkd-1.6/INSTALL.TXT 2004-09-30 10:49:26.000000000 -0400
+++ mkd/INSTALL.TXT 2004-10-19 12:18:27.000000000 -0400
@@ -160,7 +160,8 @@
MKDoc::XML
MKDoc::Control_List
MKDoc::Text::Structured
-
+ DateTime
+ DateTime::TimeZone
Configuring your MKDoc system
=============================
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/lib/sql/DBH.pm mkd/lib/sql/DBH.pm
--- mkd-1.6/lib/sql/DBH.pm 2003-06-02 05:28:05.000000000 -0400
+++ mkd/lib/sql/DBH.pm 2004-10-23 13:33:58.000000000 -0400
@@ -59,7 +59,7 @@
my $self = shift;
if (not defined $self or not ref $self) { $self = _ETERNAL_ }
defined $self or
- confess "Cannot return \$dbh because $self has not been spawned";
+ confess "Cannot return \$dbh because \$self has not been spawned";
defined _DBH_ and return _DBH_;
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/MKDoc/Site/Deploy/DB/Schema.pm mkd/MKDoc/Site/Deploy/DB/Schema.pm
--- mkd-1.6/MKDoc/Site/Deploy/DB/Schema.pm 2004-09-16 05:55:44.000000000 -0400
+++ mkd/MKDoc/Site/Deploy/DB/Schema.pm 2004-10-16 18:04:37.000000000 -0400
@@ -341,6 +341,29 @@
);
+## DOCUMENT TIMERANGE TABLE ##
+new lib::sql::Table
+ (
+ name => 'Document_TimeRange',
+ pk => [ qw /ID/ ],
+ ai => 1,
+ cols => [
+ { name => 'ID',
+ type => new lib::sql::type::Int( not_null => 1 ), },
+ { name => 'Document_ID',
+ type => new lib::sql::type::Int( not_null => 1 ), },
+ { name => 'FromDate',
+ type => new lib::sql::type::DateTime( not_null => 1 ), },
+ { name => 'ToDate',
+ type => new lib::sql::type::DateTime( not_null => 1 ), },
+ ],
+ index => { FromIndex => [ 'FromDate' ],
+ ToIndex => [ 'ToDate' ],
+ DocIndex => [ 'Document_ID' ],
+ },
+ fk => { Document => { Document_ID => 'ID' } },
+ );
+
1;
__END__
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/MODULES.TXT mkd/MODULES.TXT
--- mkd-1.6/MODULES.TXT 2004-09-29 12:08:29.000000000 -0400
+++ mkd/MODULES.TXT 2004-10-19 12:18:31.000000000 -0400
@@ -35,6 +35,8 @@
# Time & Date manipulation:
Date::Manip v5.52
Time::ParseDate
+ DateTime
+ DateTime::TimeZone
# Image manipulation:
Image::Magick v5.48
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/t/timerange.t mkd/t/timerange.t
--- mkd-1.6/t/timerange.t 1969-12-31 19:00:00.000000000 -0500
+++ mkd/t/timerange.t 2004-10-23 14:00:42.000000000 -0400
@@ -0,0 +1,189 @@
+use strict;
+use warnings;
+use Test::More qw(no_plan);
+use MKDoc;
+use DateTime;
+use DateTime::TimeZone;
+
+# make sure SITE_DIR is set since MKDoc->init needs it
+die "SITE_DIR isn't set. Please source mksetenv.sh from an installed MKDoc site ".
+ "and try again.\n"
+ unless $ENV{SITE_DIR};
+
+# initialize MKDoc, needed for database connection
+MKDoc->init;
+
+use_ok('flo::editor::TimeRange');
+
+my $tr = flo::editor::TimeRange->new();
+isa_ok($tr, 'flo::editor::TimeRange');
+
+# create from now to an hour from now
+my $now = time;
+my $later = $now + 60 * 60;
+my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now);
+
+# get current timezone from DateTime
+my $tz = DateTime->now(time_zone => "local")->time_zone->name;
+ok($tz);
+isa_ok(DateTime::TimeZone->new(name => $tz), 'DateTime::TimeZone');
+
+# setup dates and make sure accessors are working
+$tr->set_from_year($year + 1900);
+is($tr->from_year(), $year + 1900);
+$tr->set_from_month($mon + 1);
+is($tr->from_month(), $mon + 1);
+$tr->set_from_day($mday);
+is($tr->from_day(), $mday);
+$tr->set_from_hour($hour);
+is($tr->from_hour(), $hour);
+$tr->set_from_minute($min);
+is($tr->from_minute(), $min);
+$tr->set_from_tz($tz);
+is($tr->from_tz(), $tz);
+
+# check that from_datetime is working
+my $dt = $tr->from_datetime;
+isa_ok($dt, 'DateTime');
+foreach my $field (qw(year month day hour minute)) {
+ my $meth = "from_$field";
+ is($dt->$field, $tr->$meth, "Check that $field matches in from_datetime");
+}
+
+($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($later);
+
+$tr->set_to_year($year + 1900);
+is($tr->to_year(), $year + 1900);
+$tr->set_to_month($mon + 1);
+is($tr->to_month(), $mon + 1);
+$tr->set_to_day($mday);
+is($tr->to_day(), $mday);
+$tr->set_to_hour($hour);
+is($tr->to_hour(), $hour);
+$tr->set_to_minute($min);
+is($tr->to_minute(), $min);
+$tr->set_to_tz($tz);
+is($tr->to_tz(), $tz);
+
+# check that to_datetime is working
+$dt = $tr->to_datetime;
+isa_ok($dt, 'DateTime');
+foreach my $field (qw(year month day hour minute)) {
+ my $meth = "to_$field";
+ is($dt->$field, $tr->$meth, "Check that $field matches in to_datetime");
+}
+
+# test the month selector
+my $select = $tr->month_select("from");
+is(@$select, 12);
+my @selected = grep { $_->{is_selected} } @$select;
+is(@selected, 1);
+is($selected[0]->{value}, $tr->from_month);
+
+# test the day selector
+$select = $tr->day_select("from");
+is(@$select, 31);
+ at selected = grep { $_->{is_selected} } @$select;
+is(@selected, 1);
+is($selected[0]->{value}, $tr->from_day);
+
+# test the year selector
+$select = $tr->year_select("from");
+is($select->[0]{value}, (localtime)[5] + 1900);
+ at selected = grep { $_->{is_selected} } @$select;
+is(@selected, 1);
+is($selected[0]->{value}, $tr->from_year);
+
+# test the hour selector
+$select = $tr->hour_select("from");
+is(@$select, 24);
+ at selected = grep { $_->{is_selected} } @$select;
+is(@selected, 1);
+is($selected[0]->{value}, $tr->from_hour);
+
+# test the minute selector
+$select = $tr->minute_select("from");
+is(@$select, 60);
+ at selected = grep { $_->{is_selected} } @$select;
+is(@selected, 1);
+is($selected[0]->{value}, $tr->from_minute);
+
+# test the tz selector
+$select = $tr->tz_select("from");
+ok(@$select > 10);
+ at selected = grep { $_->{is_selected} } @$select;
+is(@selected, 1);
+is($selected[0]->{value}, $tr->from_tz);
+
+# test validation
+my $bad = flo::editor::TimeRange->new();
+isa_ok($tr, 'flo::editor::TimeRange');
+
+# empty title is an error
+has_error($bad, 'component/timerange/title_empty');
+
+# test equality detection, both should default to now
+$bad->set_title('foo');
+has_error($bad, 'component/timerange/dates_equal');
+
+# test reversal detection
+$bad->set_from_hour($bad->from_hour + 1);
+has_error($bad, 'component/timerange/dates_reversed');
+
+# now it should be ok
+$bad->set_to_hour($bad->to_hour + 2);
+$bad->validate;
+ok(not $bad->has_errors);
+
+# check that a nonsense date fails
+$bad->set_from_month(2);
+$bad->set_from_day(31);
+has_error($bad, "component/timerange/from_invalid");
+
+
+# set it back good
+$bad->set_from_month($mon + 1);
+$bad->set_from_day($mday);
+
+# try to write to the index
+ok($bad->write_index());
+ok($bad->Document_TimeRange_ID);
+
+# check that data is correct
+my $dbh = lib::sql::DBH->get();
+my ($db_from, $db_to) = $dbh->selectrow_array('SELECT FromDate, ToDate
+ FROM Document_TimeRange
+ WHERE ID = ?', undef,
+ $bad->Document_TimeRange_ID);
+ok($db_from =~ m/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/);
+my ($db_year, $db_month, $db_day, $db_hour, $db_minute, $db_second) =
+ ($1, $2, $3, $4, $5, $6);
+
+# the date should match the current date in UTC
+my $utc = $bad->from_datetime;
+$utc->set_time_zone("UTC");
+cmp_ok($db_year, '==', $utc->year);
+cmp_ok($db_month, '==', $utc->month);
+cmp_ok($db_day, '==', $utc->day);
+cmp_ok($db_hour, '==', $utc->hour);
+cmp_ok($db_second,'==', $utc->second);
+
+# cleanup test data
+$dbh->do('DELETE FROM Document_TimeRange WHERE ID = ?',
+ undef, $bad->Document_TimeRange_ID);
+
+#
+# Utility Routines
+#
+
+# has_error($obj, $key) - calls $obj->validate(), checks that the
+# object has an error and that one of the errors matches the specified
+# key. Clears the list of errors before returning.
+sub has_error {
+ my ($obj, $key) = @_;
+ $bad->validate();
+ ok($obj->has_errors, "Checking for errors at line " . (caller)[2]);
+ ok((grep { $_->is($key) } @{$obj->errors}),
+ "Looking for the $key error at line " . (caller)[2]);
+ $obj->{'.errors'} = [];
+}
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/templates/component/headlines/en.html mkd/templates/component/headlines/en.html
--- mkd-1.6/templates/component/headlines/en.html 2004-11-03 15:34:28.658770072 -0500
+++ mkd/templates/component/headlines/en.html 2004-10-24 15:27:05.000000000 -0400
@@ -76,43 +76,88 @@
>your preferences</a>:
</p>
<dl>
- <div
- petal:repeat="headline self/personalized_headlines"
+ <!--? Generate upcoming events style headlines -->
+ <div
+ petal:condition="true: self/mode_is_upcoming"
+ petal:repeat="row self/personalized_headlines"
petal:omit-tag="string:1"
>
- <dt>
+ <dt petal:define="headline row/document; timerange row/timerange;">
<a
- href="#"
- hreflang="en"
+ href="#"
+ hreflang="en"
+ lang="en"
+ xml:lang="en"
+ dir="ltr"
+ petal:attributes="href headline/uri;
+ hreflang headline/lang;
+ lang headline/lang;
+ xml:lang headline/lang;
+ dir headline/direction;"
+ petal:content="headline/title"
+ >Title of the document</a>
+ </dt>
+ <dd>
+ <span
lang="en"
xml:lang="en"
dir="ltr"
- petal:attributes="
- href headline/uri;
- hreflang headline/lang;
- lang headline/lang;
+ petal:attributes="lang headline/lang;
xml:lang headline/lang;
- dir headline/direction;
- "
- petal:content="headline/title"
- >Pumpkins on fire!</a>
+ dir headline/direction;"
+ petal:content="timerange/title"
+ >
+ A description of the event.
+ </span>
+ <br />
+ From:
+ <small petal:content="timerange/from_datetime/strftime '%B %e, %Y at %H:%M %Z'">
+ DATE
+ </small>
+ To:
+ <small petal:content="timerange/to_datetime/strftime '%B %e, %Y at %H:%M %Z'">
+ DATE
+ </small>
+ </dd>
+ </div>
+
+
+ <!--? Generate newest documents style headlines -->
+ <div
+ petal:condition="true: self/mode_is_newest"
+ petal:repeat="headline self/personalized_headlines"
+ petal:omit-tag="string:1"
+ >
+ <dt>
+ <a
+ href="#"
+ hreflang="en"
+ lang="en"
+ xml:lang="en"
+ dir="ltr"
+ petal:attributes="href headline/uri;
+ hreflang headline/lang;
+ lang headline/lang;
+ xml:lang headline/lang;
+ dir headline/direction;"
+ petal:content="headline/title"
+ >Title of the document</a>
</dt>
<dd>
<span
lang="en"
xml:lang="en"
dir="ltr"
- petal:attributes="
- lang headline/lang;
+ petal:attributes="lang headline/lang;
xml:lang headline/lang;
dir headline/direction;"
petal:content="headline/description"
>
- Some description describing some stuff about fire and pumpkins.
+ A description of the document.
</span>
<br />
<small petal:content="headline/date_created">
- DATE
+ DATE
</small>
</dd>
</div>
@@ -140,41 +185,88 @@
or because none of the documents in the list matched them.
</p>
<dl>
+ <!--? Generate upcoming events style headlines -->
<div
- petal:repeat="headline self/default_headlines"
+ petal:condition="true: self/mode_is_upcoming"
+ petal:repeat="row self/default_headlines"
petal:omit-tag="string:1"
>
- <dt>
+ <dt petal:define="headline row/document; timerange row/timerange;">
<a
- href="#"
- hreflang="en"
+ href="#"
+ hreflang="en"
+ lang="en"
+ xml:lang="en"
+ dir="ltr"
+ petal:attributes="href headline/uri;
+ hreflang headline/lang;
+ lang headline/lang;
+ xml:lang headline/lang;
+ dir headline/direction;"
+ petal:content="headline/title"
+ >Title of the document</a>
+ </dt>
+ <dd>
+ <span
lang="en"
xml:lang="en"
dir="ltr"
- petal:attributes="href headline/uri;
- hreflang headline/lang;
- lang headline/lang;
+ petal:attributes="lang headline/lang;
xml:lang headline/lang;
dir headline/direction;"
- petal:content="headline/title"
- >Pumpkins on fire!</a>
+ petal:content="timerange/title"
+ >
+ A description of the event.
+ </span>
+ <br />
+ From:
+ <small petal:content="timerange/from_datetime/strftime '%B %e, %Y at %H:%M %Z'">
+ DATE
+ </small>
+ To:
+ <small petal:content="timerange/to_datetime/strftime '%B %e, %Y at %H:%M %Z'">
+ DATE
+ </small>
+ </dd>
+ </div>
+
+
+ <!--? Generate newest documents style headlines -->
+ <div
+ petal:condition="true: self/mode_is_newest"
+ petal:repeat="headline self/default_headlines"
+ petal:omit-tag="string:1"
+ >
+ <dt>
+ <a
+ href="#"
+ hreflang="en"
+ lang="en"
+ xml:lang="en"
+ dir="ltr"
+ petal:attributes="href headline/uri;
+ hreflang headline/lang;
+ lang headline/lang;
+ xml:lang headline/lang;
+ dir headline/direction;"
+ petal:content="headline/title"
+ >Title of the document</a>
</dt>
- <dd>
+ <dd>
<span
lang="en"
xml:lang="en"
dir="ltr"
- petal:attributes="
- lang headline/lang;
+ petal:attributes="lang headline/lang;
xml:lang headline/lang;
dir headline/direction;"
petal:content="headline/description"
>
- Some description describing some stuff about fire and pumpkins.
+ A description of the document.
</span>
<br />
<small petal:content="headline/date_created">
- DATE
+ DATE
</small>
</dd>
</div>
@@ -187,7 +279,56 @@
petal:condition="false: self/is_user_logged_in"
>
<dl>
+
+ <!--? Generate upcoming events style headlines -->
+ <div
+ petal:condition="true: self/mode_is_upcoming"
+ petal:repeat="row self/default_headlines"
+ petal:omit-tag="string:1"
+ >
+ <dt petal:define="headline row/document; timerange row/timerange;">
+ <a
+ href="#"
+ hreflang="en"
+ lang="en"
+ xml:lang="en"
+ dir="ltr"
+ petal:attributes="href headline/uri;
+ hreflang headline/lang;
+ lang headline/lang;
+ xml:lang headline/lang;
+ dir headline/direction;"
+ petal:content="headline/title"
+ >Title of the document</a>
+ </dt>
+ <dd>
+ <span
+ lang="en"
+ xml:lang="en"
+ dir="ltr"
+ petal:attributes="lang headline/lang;
+ xml:lang headline/lang;
+ dir headline/direction;"
+ petal:content="timerange/title"
+ >
+ A description of the event.
+ </span>
+ <br />
+ From:
+ <small petal:content="timerange/from_datetime/strftime '%B %e, %Y at %H:%M %Z'">
+ DATE
+ </small>
+ To:
+ <small petal:content="timerange/to_datetime/strftime '%B %e, %Y at %H:%M %Z'">
+ DATE
+ </small>
+ </dd>
+ </div>
+
+
+ <!--? Generate newest documents style headlines -->
<div
+ petal:condition="true: self/mode_is_newest"
petal:repeat="headline self/default_headlines"
petal:omit-tag="string:1"
>
@@ -198,36 +339,34 @@
lang="en"
xml:lang="en"
dir="ltr"
- petal:attributes="
- href headline/uri;
+ petal:attributes="href headline/uri;
hreflang headline/lang;
lang headline/lang;
xml:lang headline/lang;
- dir headline/direction;
- "
+ dir headline/direction;"
petal:content="headline/title"
- >Pumpkins on fire!</a>
+ >Title of the document</a>
</dt>
<dd>
<span
lang="en"
xml:lang="en"
dir="ltr"
- petal:attributes="
- lang headline/lang;
+ petal:attributes="lang headline/lang;
xml:lang headline/lang;
- dir headline/direction;
- "
+ dir headline/direction;"
petal:content="headline/description"
>
- Some description describing some stuff about fire and pumpkins.
+ A description of the document.
</span>
- <br />
- <small petal:content="headline/date_created">
+ <br />
+ <small petal:content="headline/date_created">
DATE
- </small>
+ </small>
</dd>
</div>
+
</dl>
+
</div>
</div>
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/templates/editor/headlines/en.html mkd/templates/editor/headlines/en.html
--- mkd-1.6/templates/editor/headlines/en.html 2004-03-18 10:48:40.000000000 -0500
+++ mkd/templates/editor/headlines/en.html 2004-10-23 19:10:38.000000000 -0400
@@ -15,6 +15,7 @@
name_title string:${self/block_name}_title;
name_from_path string:${self/block_name}_from_path;
name_leaf_only string:${self/block_name}_leaf_only;
+ name_mode string:${self/block_name}_mode;
align self/align;
align_opposite self/align_opposite;
dir self/direction"
@@ -59,6 +60,10 @@
petal:condition="error/is --component/headlines/max_headlines_malformed"
>You must enter a natural number for the 'Number of headlines' field.
No characters other than 0 to 9 are allowed.</p>
+ <p
+ xml:lang="en" lang="en" dir="ltr" class="error"
+ petal:condition="error/is --component/headlines/mode_invalid"
+ >You must choose a mode.</p>
</div>
<p
@@ -179,6 +184,52 @@
xml:lang="en"
lang="en"
dir="ltr"
+ >The mode which determines headline selection. Choose 'Newest Documents' to show the latest documents created. Choose 'Upcoming Events' to show those with time-ranges occuring soonest.</em>
+ <label
+ for="headlines_mode"
+ xml:lang="en"
+ lang="en"
+ dir="ltr"
+ petal:attributes="for name_mode"
+ >Mode</label>
+ <br />
+ <select
+ name="headlines_mode"
+ id="headlines_mode"
+ title="Choose the headline mode here."
+ petal:attributes="name name_mode; id name_mode"
+ >
+ <option
+ selected="selected"
+ value="newest"
+ title="Newest Documents"
+ petal:condition="true: self/mode_is_newest"
+ >Newest Documents</option>
+ <option
+ value="newest"
+ title="Newest Documents"
+ petal:condition="false: self/mode_is_newest"
+ >Newest Documents</option>
+ <option
+ selected="selected"
+ value="upcoming"
+ title="Upcoming Events"
+ petal:condition="true: self/mode_is_upcoming"
+ >Upcoming Events</option>
+ <option
+ value="upcoming"
+ title="Upcoming Events"
+ petal:condition="false: self/mode_is_upcoming"
+ >Upcoming Events</option>
+ </select>
+ </p>
+
+ <p>
+ <em
+ class="help"
+ xml:lang="en"
+ lang="en"
+ dir="ltr"
>
The maximum number of documents to be listed.
</em>
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/templates/editor/timerange/en.html mkd/templates/editor/timerange/en.html
--- mkd-1.6/templates/editor/timerange/en.html 2004-11-03 15:34:29.677615184 -0500
+++ mkd/templates/editor/timerange/en.html 2004-10-24 18:23:58.000000000 -0400
@@ -203,62 +203,50 @@
>
<option
value="1"
- title="1"
petal:attributes="selected from_months/0/is_selected"
>January</option>
<option
value="2"
- title="2"
petal:attributes="selected from_months/1/is_selected"
>February</option>
<option
value="3"
- title="3"
petal:attributes="selected from_months/2/is_selected"
>March</option>
<option
value="4"
- title="4"
petal:attributes="selected from_months/3/is_selected"
>April</option>
<option
value="5"
- title="5"
petal:attributes="selected from_months/4/is_selected"
>May</option>
<option
value="6"
- title="6"
petal:attributes="selected from_months/5/is_selected"
>June</option>
<option
value="7"
- title="7"
petal:attributes="selected from_months/6/is_selected"
>July</option>
<option
value="8"
- title="8"
petal:attributes="selected from_months/7/is_selected"
>August</option>
<option
value="9"
- title="9"
petal:attributes="selected from_months/8/is_selected"
>September</option>
<option
value="10"
- title="10"
petal:attributes="selected from_months/9/is_selected"
>October</option>
<option
value="11"
- title="11"
petal:attributes="selected from_months/10/is_selected"
>November</option>
<option
value="12"
- title="12"
petal:attributes="selected from_months/11/is_selected"
>December</option>
</select>
@@ -280,12 +268,14 @@
>
<option
selected="selected"
- petal:attributes="value day/value;"
+ petal:attributes="value day/value;
+ title day/value"
petal:content="day/label"
petal:condition="true: day/is_selected"
>01</option>
<option
- petal:attributes="value day/value;"
+ petal:attributes="value day/value;
+ title day/value"
petal:content="day/label"
petal:condition="false: day/is_selected"
>02</option>
@@ -309,12 +299,14 @@
>
<option
selected="selected"
- petal:attributes="value year/value;"
+ petal:attributes="value year/value;
+ title year/value"
petal:content="year/label"
petal:condition="true: year/is_selected"
>2004</option>
<option
- petal:attributes="value year/value;"
+ petal:attributes="value year/value;
+ title year/value"
petal:content="year/label"
petal:condition="false: year/is_selected"
>2004</option>
@@ -338,12 +330,14 @@
>
<option
selected="selected"
- petal:attributes="value hour/value;"
+ petal:attributes="value hour/value;
+ title hour/value"
petal:content="hour/label"
petal:condition="true: hour/is_selected"
>00</option>
<option
- petal:attributes="value hour/value;"
+ petal:attributes="value hour/value;
+ title hour/value"
petal:content="hour/label"
petal:condition="false: hour/is_selected"
>01</option>
@@ -367,12 +361,14 @@
>
<option
selected="selected"
- petal:attributes="value minute/value;"
+ petal:attributes="value minute/value;
+ title minute/value"
petal:content="minute/label"
petal:condition="true: minute/is_selected"
>00</option>
<option
- petal:attributes="value minute/value;"
+ petal:attributes="value minute/value;
+ title minute/value"
petal:content="minute/label"
petal:condition="false: minute/is_selected"
>00</option>
@@ -443,62 +439,50 @@
>
<option
value="1"
- title="1"
petal:attributes="selected to_months/0/is_selected"
>January</option>
<option
value="2"
- title="2"
petal:attributes="selected to_months/1/is_selected"
>February</option>
<option
value="3"
- title="3"
petal:attributes="selected to_months/2/is_selected"
>March</option>
<option
value="4"
- title="4"
petal:attributes="selected to_months/3/is_selected"
>April</option>
<option
value="5"
- title="5"
petal:attributes="selected to_months/4/is_selected"
>May</option>
<option
value="6"
- title="6"
petal:attributes="selected to_months/5/is_selected"
>June</option>
<option
value="7"
- title="7"
petal:attributes="selected to_months/6/is_selected"
>July</option>
<option
value="8"
- title="8"
petal:attributes="selected to_months/7/is_selected"
>August</option>
<option
value="9"
- title="9"
petal:attributes="selected to_months/8/is_selected"
>September</option>
<option
value="10"
- title="10"
petal:attributes="selected to_months/9/is_selected"
>October</option>
<option
value="11"
- title="11"
petal:attributes="selected to_months/10/is_selected"
>November</option>
<option
value="12"
- title="12"
petal:attributes="selected to_months/11/is_selected"
>December</option>
</select>
@@ -519,12 +503,14 @@
>
<option
selected="selected"
- petal:attributes="value day/value;"
+ petal:attributes="value day/value;
+ title day/label;"
petal:content="day/label"
petal:condition="true: day/is_selected"
>January</option>
<option
- petal:attributes="value day/value;"
+ petal:attributes="value day/value;
+ title day/value;"
petal:content="day/label"
petal:condition="false: day/is_selected"
>Feburary</option>
@@ -548,12 +534,14 @@
>
<option
selected="selected"
- petal:attributes="value year/value;"
+ petal:attributes="value year/value;
+ title year/value"
petal:content="year/label"
petal:condition="true: year/is_selected"
>2004</option>
<option
- petal:attributes="value year/value;"
+ petal:attributes="value year/value;
+ title year/value"
petal:content="year/label"
petal:condition="false: year/is_selected"
>2005</option>
@@ -577,12 +565,14 @@
>
<option
selected="selected"
- petal:attributes="value hour/value;"
+ petal:attributes="value hour/value;
+ title hour/value;"
petal:content="hour/label"
petal:condition="true: hour/is_selected"
>00</option>
<option
- petal:attributes="value hour/value;"
+ petal:attributes="value hour/value;
+ title hour/value;"
petal:content="hour/label"
petal:condition="false: hour/is_selected"
>01</option>
@@ -606,12 +596,14 @@
>
<option
selected="selected"
- petal:attributes="value minute/value;"
+ petal:attributes="value minute/value;
+ title minute/value"
petal:content="minute/label"
petal:condition="true: minute/is_selected"
>00</option>
<option
- petal:attributes="value minute/value;"
+ petal:attributes="value minute/value;
+ title minute/value"
petal:content="minute/label"
petal:condition="false: minute/is_selected"
>01</option>
@@ -641,7 +633,7 @@
>BST</option>
<option
petal:attributes="value tz/value;
- title tz/value;"
+ title tz/label;"
petal:content="tz/label"
petal:condition="false: tz/is_selected"
>GMT</option>
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/templates/fragments/menu/en.html mkd/templates/fragments/menu/en.html
--- mkd-1.6/templates/fragments/menu/en.html 2004-11-03 15:34:31.921274096 -0500
+++ mkd/templates/fragments/menu/en.html 2004-03-18 10:48:42.000000000 -0500
@@ -38,37 +38,6 @@
petal:attributes="dir dir"
>|</bdo>
- <!--? The following loop generates a list of the non-hidden
- child documents of the root document ?-->
- <div
- petal:omit-tag=""
- petal:repeat="child self/root/children_showable"
- petal:condition="true: self/root/children_showable"
- >
- <a
- href="http://example.com/"
- hreflang="en"
- xml:lang="en"
- lang="en"
- dir="ltr"
- petal:content="child/title"
- petal:attributes="href child/uri;
- hreflang child/lang;
- dir child/direction;
- lang child/lang;
- xml:lang child/lang;
- title child/description;"
- petal:omit-tag="true: self/equals child "
- ><strong
- petal:omit-tag="false: self/equals child"
- petal:content="child/title"
- >Document Title</strong></a>
- <bdo
- dir="ltr"
- petal:attributes="dir dir"
- >|</bdo>
- </div>
-
<!--?
The following can be used to create menu items that don't link to themselves.
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/templates/newsletter/en.xml mkd/templates/newsletter/en.xml
--- mkd-1.6/templates/newsletter/en.xml 2003-09-12 11:02:53.000000000 -0400
+++ mkd/templates/newsletter/en.xml 2004-11-02 23:50:59.000000000 -0500
@@ -46,6 +46,8 @@
matches your preferences.
</p>
+<span petal:condition="self/has_newest">
+
<p petal:condition="self/is_daily">
The following documents added yesterday may be of interest to you:
</p>
@@ -56,19 +58,41 @@
The following documents added in the last month may be of interest to you:
</p>
-<pre petal:repeat="result self/results">${result/Title}
+<pre petal:repeat="result self/results --newest">${result/Title}
<${result/Full_Path}>
${result/Description}
(Created by ${result/Creator_First_Name} ${result/Creator_Family_Name} on ${result/Date_Created})</pre>
- <p>
+</span>
+
+<span petal:condition="self/has_upcoming">
+
+<p petal:condition="self/is_daily">
+ The following events occuring today may be of interest to you:
+</p>
+<p petal:condition="self/is_weekly">
+ The following events occuring this week may be of interest to you:
+</p>
+<p petal:condition="self/is_monthly">
+ The following events occuring this month may be of interest to you:
+</p>
+
+<pre petal:repeat="result self/results --upcoming">${result/Title}
+ <${result/Full_Path}>
+${result/TimeRange_Title}
+ (${result/TimeRange_FromDate/strftime '%B %e, %Y at %H:%M %Z'} - ${result/TimeRange_ToDate/strftime '%B %e, %Y at %H:%M %Z'})</pre>
+
+
+ <p>
If you no longer want to<br /> receive these email updates or want to
change your preferences you will need to edit your account at:
</p>
<pre><span petal:replace="string: <${details/user_domain_notrailingslash}/${preferences/name}>"><http://example.com/preferences.mumble></span></pre>
- <p>
+</span>
+
+ <p>
If you can't remember your account password, you can get an email
reminder by visiting this link:
</p>
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/timerange_update_db.pl mkd/timerange_update_db.pl
--- mkd-1.6/timerange_update_db.pl 1969-12-31 19:00:00.000000000 -0500
+++ mkd/timerange_update_db.pl 2004-10-23 14:34:15.000000000 -0400
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use MKDoc;
+
+# make sure SITE_DIR is set since MKDoc->init needs it
+die "SITE_DIR isn't set. Please source mksetenv.sh from an installed MKDoc site ".
+ "and try again.\n"
+ unless $ENV{SITE_DIR};
+
+# initialize MKDoc, needed for database connection
+MKDoc->init;
+
+print "Creating the Document_TimeRange table...\n";
+my $dbh = lib::sql::DBH->get();
+$dbh->do(<<END);
+ CREATE TABLE Document_TimeRange (
+ ID int(11) NOT NULL auto_increment,
+ Document_ID int(11) NOT NULL default '0',
+ FromDate datetime NOT NULL default '0000-00-00 00:00:00',
+ ToDate datetime NOT NULL default '0000-00-00 00:00:00',
+ PRIMARY KEY (ID),
+ KEY DocIndex (Document_ID),
+ KEY FromIndex (FromDate),
+ KEY ToIndex (ToDate)
+ ) TYPE=MyISAM
+END
diff --exclude=CVS --exclude=conf --exclude=mksetenv.sh --exclude=ECommerce --exclude=shop -Naur mkd-1.6/tools/cron/020..newsletter.pl mkd/tools/cron/020..newsletter.pl
--- mkd-1.6/tools/cron/020..newsletter.pl 2004-03-29 10:24:45.000000000 -0500
+++ mkd/tools/cron/020..newsletter.pl 2004-11-03 15:30:50.763895128 -0500
@@ -8,6 +8,7 @@
use Encode;
use base qw /flo::Plugin/;
use Petal::Mail;
+use Carp qw(croak);
sub template_path { '/newsletter' }
@@ -43,16 +44,73 @@
return 1;
}
+# returns true if there are newest documents to include in the newsletter
+sub has_newest {
+ my $self = shift;
+ my $res = $self->results("newest");
+ return 1 if @$res;
+ return 0;
+}
-sub results
-{
+# returns true if there are upcoming events to include in the newsletter
+sub has_upcoming {
+ my $self = shift;
+ my $res = $self->results("upcoming");
+ return 1 if @$res;
+ return 0;
+}
+
+# return ref to array of hashes containing newsletter results. Takes
+# a single parameter specifying the mode, either "newest" or
+# "upcoming". This method cache the results for each mode.
+sub results {
my $self = shift;
+ my $mode = shift || croak("Missing required mode parameter");
+
+ # return cached data if available
+ return $self->{"_cache_$mode"} if $self->{"_cache_$mode"};
+
my $user_login = $self->user()->login();
- my $date_since = $self->compute_date_since();
-
+
+ # concoct SQL needed for upcoming or newest mode
+ my ($extra_from, $extra_where, $extra_select, @extra_params,
+ $order_by, $group_by);
+
+ if ($mode eq 'upcoming') {
+ # setup for selecting by timerange
+ $order_by = "Document_TimeRange.FromDate ASC";
+ $group_by = "Document_TimeRange.ID";
+ $extra_from = ", Document_TimeRange";
+ $extra_select = ", Document_TimeRange.ID as Document_TimeRange_ID";
+
+ # select documents with timeranges dates from now to the end
+ # of the range
+ $extra_where = <<END;
+ AND Document.ID = Document_TimeRange.Document_ID
+ AND ((Document_TimeRange.FromDate >= ? AND
+ Document_TimeRange.FromDate <= ?)
+ OR
+ (Document_TimeRange.ToDate >= ? AND
+ Document_TimeRange.ToDate <= ?))
+END
+ @extra_params = ($self->compute_date_now(),
+ $self->compute_date_forward()) x 2;
+ } else {
+ # select documents created in the chosen period
+ $order_by = "Date_Created DESC";
+ $group_by = "Document.ID";
+ $extra_where = "AND Document.Date_Created > ?";
+ ($extra_from, $extra_select) = ("") x 2;
+ @extra_params = $self->compute_date_since();
+ }
+
my $sql = <<SQL;
-SELECT Document.ID AS ID, SUM(Preference_Audience.Value) AS Pref_Score
+SELECT Document.ID AS ID, SUM(Preference_Audience.Value) AS Pref_Score,
+ Creator.First_Name as Creator_First_Name,
+ Creator.Family_Name as Creator_Family_Name
+ $extra_select
FROM Document, Document_Audience, Audience, Preference_Audience, Editor, Editor as Creator, Preference_Language
+ $extra_from
WHERE
-- join the tables together
(
@@ -70,68 +128,92 @@
AND
-- remove languages which are not wanted
Preference_Language.Value = 1
-AND
- -- get only documents which are recent enough
- Document.Date_Created > ?
+$extra_where
+
-GROUP BY (Document.ID)
-ORDER BY Document.Full_Path
+GROUP BY $group_by
+ORDER BY $order_by
SQL
my $dbh = lib::sql::DBH->get();
my $sth = $dbh->prepare_cached ($sql);
- $sth->execute ($user_login, $date_since);
+ $sth->execute ($user_login, @extra_params);
- my @res = ();
+ my @res;
my $doc_t = flo::Standard::table ('Document');
- while (my $h = $sth->fetchrow_hashref)
- {
+ ROW: while (my $h = $sth->fetchrow_hashref) {
next unless ($h->{Pref_Score} > 0);
my $doc = $doc_t->get ( $h->{ID} );
next unless ($doc->is_showable());
$Text::Wrap::columns = 72;
$Text::Wrap::columns = 72;
- $h->{Full_Path} =~ s/^\//$::PUBLIC_DOMAIN/;
- $h->{Date_Created} =~ s/\s+.*//;
- $h->{Description} = join '', fill (' ', ' ', $h->{Description});
- push @res, $h;
+
+ # assemble data for template from the document object and the
+ # query results
+ my %row;
+ $row{doc} = $doc;
+ ($row{Full_Path} = $doc->{Full_Path}) =~ s/^\//$::PUBLIC_DOMAIN/;
+ ($row{Date_Created} = $doc->date_created) =~ s/\s+.*//;
+ $row{Description} = join '',fill (' ', ' ', $doc->description);
+ $row{Title} = $doc->title;
+ $row{Creator_First_Name} = $h->{Creator_First_Name};
+ $row{Creator_Family_Name} = $h->{Creator_Family_Name};
+
+ # deal with timerange data for upcoming events list
+ if ($mode eq 'upcoming') {
+ # find this timerange in the list of components for this doc
+ my $timerange_id = $h->{Document_TimeRange_ID};
+ my ($timerange) =
+ grep { $_->isa('flo::editor::TimeRange') and
+ $_->Document_TimeRange_ID == $timerange_id }
+ $doc->components;
+ # ignore hits with no matching timerange.
+ next ROW unless $timerange;
+
+ $row{TimeRange_Title} = $timerange->title;
+ $row{TimeRange_FromDate} = $timerange->from_datetime;
+ $row{TimeRange_ToDate} = $timerange->to_datetime;
+ }
+
+ push @res, \%row;
}
- return \@res;
+ # save for future requests
+ return $self->{"_cache_$mode"} = \@res;
+}
+
+# translate a time to day-month-year for use with MySQL
+sub _time2date {
+ my $self = shift;
+ my ($mday, $mon, $year) = (localtime(shift))[3,4,5];
+ $mon += 1;
+ $year += 1900;
+ return "$year-$mon-$mday";
}
+sub compute_date_now { shift->_time2date(time()) }
package MKDoc::Misc::Newsletter::Daily;
use strict;
use warnings;
our @ISA = qw /MKDoc::Misc::Newsletter/;
-sub compute_date_since
-{
- my $self = shift;
- my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime (time - 24 * 3600);
- $mon += 1;
- $year += 1900;
- return "$year-$mon-$mday";
-}
+sub compute_date_since { shift->_time2date(time - 24 * 3600) }
-sub is_daily { 1 }
+sub compute_date_forward { shift->_time2date(time + 24 * 3600) . " 23:59:59" }
+sub is_daily { 1 }
package MKDoc::Misc::Newsletter::Weekly;
use strict;
use warnings;
our @ISA = qw /MKDoc::Misc::Newsletter/;
-sub compute_date_since
-{
- my $self = shift;
- my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime (time - 7 * 24 * 3600);
- $mon += 1;
- $year += 1900;
- return "$year-$mon-$mday";
-}
+sub compute_date_since { shift->_time2date(time - 7 * 24 * 3600) }
+
+sub compute_date_forward { shift->_time2date(time + 7 * 24 * 3600) .
+ " 23:59:59" }
sub is_weekly { 1 }
@@ -141,10 +223,9 @@
use warnings;
our @ISA = qw /MKDoc::Misc::Newsletter/;
-sub compute_date_since
-{
+sub compute_date_since {
my $self = shift;
- my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime (time);
+ my ($mday, $mon, $year) = (localtime(time))[3,4,5];
$year += 1900;
($mon == 0) and do {
$year--;
@@ -154,6 +235,19 @@
return "$year-$mon-$mday";
}
+sub compute_date_forward {
+ my $self = shift;
+ my ($mday, $mon, $year) = (localtime(time))[3,4,5];
+ $year += 1900;
+ $mon += 2;
+ ($mon == 13) and do {
+ $year++;
+ $mon = 1;
+ };
+
+ return "$year-$mon-$mday 23:59:59";
+}
+
sub is_monthly { 1 }
@@ -206,8 +300,8 @@
($periodicity eq WEEKLY) and $news = new MKDoc::Misc::Newsletter::Weekly (%args);
($periodicity eq MONTHLY) and $news = new MKDoc::Misc::Newsletter::Monthly (%args);
- my $res = $news->results();
- @{$res} or return;
+ # don't send a newsletter if there's no news
+ return unless $news->has_upcoming or $news->has_newest;
return $news;
}
More information about the MKDoc-dev
mailing list