[MKDoc-dev] Events Component: Milestone 2 Complete

Sam Tregar sam at tregar.com
Sun Oct 24 23:45:21 BST 2004


Attached please find a patch implementing milestone 2 of the events
component.  This is a patch against the mkdoc-1-6 branch, where part
of milestone 1 has already been committed.  The rest of milestone 1 is
present in this patch as well.

I am on schedule to complete this project next weekend, as planned.
All that's left is to add an upcoming events section to the newsletter
and fix the component deletion bug.

Some notes on development to date:

 => Continued work on the TimeRange component

 - An update script called timerange_update_db.pl is now included
   which will create the necessary DB table and indexes.

 - The timezone format has changed as a result of the switch from
   Date::Manip to DateTime::TimeZone.  If you have any data from the
   previous version of the timerange component you'll find the
   timezone isn't picked up correctly.

 - To run this code you'll need to install DateTime and
   DateTime::TimeZone from CPAN.

 - As requested, the dates in the time-range component now default to
   today.  I left hour and minute defaulting to 00:00 since I wasn't
   sure if you wanted that set to now too.  I also left the minute
   selector as-is since it seemed there wasn't consensus on what it
   should be changed to, text-box or a select-box by 5 minutes
   increments.

 - As requested, the month names are in the time-range editor
   template, ready for i18n.

 - The test script for flo::editor::TimeRange has been expanded
   significantly, uncovering a few bugs which are now fixed.

 - Still to be dealt with: deleting Document_TimeRange rows when
   components are deleted.  Unless a better idea occurs to me I'll
   move the maintenance of these rows into the indexing cron-job.

 
 => New work on the Headlines component

 - The headlines component now optionally displays upcoming events.

 - The date formating for the display of event dates is in the
   headline template for easy i18n/customization:

     <small petal:content="timerange/to_datetime/strftime '%B %e, %Y %H:%M %Z'">

   If this is a good idea then the same thing should be done for
   creation dates in the normal headline mode.

 - templates/components/headline/en.html is quite verbose.  It
   contained three mostly identical branches to show headlines for
   anonymous users, users without personalization and users with
   personalization.  To implement upcoming events I added a branch to
   each of those, bringing the total to six.

   Clearly something should be done about this.  Maybe the METAL macro
   facility could be used here?  I haven't played with it yet but it
   seems pretty slick.

-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-10-24 18:10:09.991310944 -0400
@@ -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-07-06 10:49:04.000000000 -0400
+++ mkd/templates/component/headlines/en.html	2004-10-24 15:27:05.645757560 -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-10-22 10:10:18.000000000 -0400
+++ mkd/templates/editor/timerange/en.html	2004-10-24 18:23:58.814310696 -0400
@@ -199,25 +199,56 @@
         title="Select the start month."
         petal:attributes="name name_from_month; 
                           id   name_from_month;"
+        petal:define="from_months self/month_select --from;"
       >
-        <div 
-          petal:repeat="month self/month_select --from"
-          petal:omit-tag=""
-        >
           <option 
-            selected="selected"
-            petal:attributes="value    month/value;
-                              title    month/value"
-            petal:content="month/label"
-            petal:condition="true: month/is_selected"
+            value="1"
+            petal:attributes="selected from_months/0/is_selected"
           >January</option>
           <option 
-            petal:attributes="value    month/value;
-                              title    month/value"
-            petal:content="month/label"
-            petal:condition="false: month/is_selected"
-          >Feburary</option>
-        </div>
+            value="2"
+            petal:attributes="selected from_months/1/is_selected"
+          >February</option>
+          <option 
+            value="3"
+            petal:attributes="selected from_months/2/is_selected"
+          >March</option>
+          <option 
+            value="4"
+            petal:attributes="selected from_months/3/is_selected"
+          >April</option>
+          <option 
+            value="5"
+            petal:attributes="selected from_months/4/is_selected"
+          >May</option>
+          <option 
+            value="6"
+            petal:attributes="selected from_months/5/is_selected"
+          >June</option>
+          <option 
+            value="7"
+            petal:attributes="selected from_months/6/is_selected"
+          >July</option>
+          <option 
+            value="8"
+            petal:attributes="selected from_months/7/is_selected"
+          >August</option>
+          <option 
+            value="9"
+            petal:attributes="selected from_months/8/is_selected"
+          >September</option>
+          <option 
+            value="10"
+            petal:attributes="selected from_months/9/is_selected"
+          >October</option>
+          <option 
+            value="11"
+            petal:attributes="selected from_months/10/is_selected"
+          >November</option>
+          <option 
+            value="12"
+            petal:attributes="selected from_months/11/is_selected"
+          >December</option>
       </select>
 
       <label
@@ -401,28 +432,59 @@
       <select 
         id="timerange_to_month"
         name="timerange_to_month"
-        title="Select the start month."
+        title="Select the end month."
         petal:attributes="name name_to_month; 
                           id   name_to_month;"
+        petal:define="to_months self/month_select --to;"
       >
-        <div
-          petal:repeat="month self/month_select --to"
-          petal:omit-tag=""
-        >
           <option 
-            selected="selected"
-            petal:attributes="value    month/value;
-                              title    month/value;"
-            petal:content="month/label"
-            petal:condition="true: month/is_selected"
+            value="1"
+            petal:attributes="selected to_months/0/is_selected"
           >January</option>
           <option 
-            petal:attributes="value    month/value;
-                              title    month/value;"
-            petal:content="month/label"
-            petal:condition="false: month/is_selected"
-          >January</option>
-        </div>
+            value="2"
+            petal:attributes="selected to_months/1/is_selected"
+          >February</option>
+          <option 
+            value="3"
+            petal:attributes="selected to_months/2/is_selected"
+          >March</option>
+          <option 
+            value="4"
+            petal:attributes="selected to_months/3/is_selected"
+          >April</option>
+          <option 
+            value="5"
+            petal:attributes="selected to_months/4/is_selected"
+          >May</option>
+          <option 
+            value="6"
+            petal:attributes="selected to_months/5/is_selected"
+          >June</option>
+          <option 
+            value="7"
+            petal:attributes="selected to_months/6/is_selected"
+          >July</option>
+          <option 
+            value="8"
+            petal:attributes="selected to_months/7/is_selected"
+          >August</option>
+          <option 
+            value="9"
+            petal:attributes="selected to_months/8/is_selected"
+          >September</option>
+          <option 
+            value="10"
+            petal:attributes="selected to_months/9/is_selected"
+          >October</option>
+          <option 
+            value="11"
+            petal:attributes="selected to_months/10/is_selected"
+          >November</option>
+          <option 
+            value="12"
+            petal:attributes="selected to_months/11/is_selected"
+          >December</option>
       </select>
 
       <label
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


More information about the MKDoc-dev mailing list