jenkins-bot has submitted this change and it was merged.

Change subject: (extension/TimelineTable) Refactoring and cleanup
......................................................................


(extension/TimelineTable) Refactoring and cleanup

Use classes for hooks registration and parser.
Use php's 'strftime' function to localize months names.
Use Html::element to create HTML table.
Fix format.
Option to hide years.
v2.0 updates:
-More flexible input date parsing
-Week level
-Arbitrary headers/footers
-depth parameter
-Rebase master to include changes to i18n
-Add output type to error messages
v2.1 updates:
-Hour/minute/second level
-Support for vertical tables
-Some memory cleanup

Change-Id: I742c54a128ae542ae0d0f4e59e315157819eaee3
---
A TimelineTable.DateDiffHelper.php
A TimelineTable.Event.php
A TimelineTable.Hooks.php
A TimelineTable.Table.php
D TimelineTable.body.php
D TimelineTable.i18n.php
M TimelineTable.php
A TimelineTable.style.css
M i18n/en.json
D i18n/fr.json
A i18n/qqq.json
11 files changed, 1,691 insertions(+), 736 deletions(-)

Approvals:
  Siebrand: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/TimelineTable.DateDiffHelper.php b/TimelineTable.DateDiffHelper.php
new file mode 100644
index 0000000..3b36c95
--- /dev/null
+++ b/TimelineTable.DateDiffHelper.php
@@ -0,0 +1,182 @@
+<?php
+
+/**
+ * Class with constant descriptors for depth.
+ */
+abstract class TimelineTableDepthDesc {
+
+       const Year = 0;
+       const Month = 1;
+       const Week = 2;
+       const Day = 3;
+       const Hour = 4;
+       const Minute = 5;
+       const Second = 6;
+
+       static public function decodeDepthDesc( $str ) {
+               switch ( strtolower( $str ) ) {
+                       case "year":
+                               return self::Year;
+                       case "month":
+                               return self::Month;
+                       case "week":
+                               return self::Week;
+                       case "day":
+                               return self::Day;
+                       case "hour":
+                               return self::Hour;
+                       case "minute":
+                               return self::Minute;
+                       case "second":
+                               return self::Second;
+                       default:
+                               return null;
+               }
+       }
+}
+
+/**
+ * Helper class for date operations:
+ *  -calculate the number of cells between two dates depending on the depth
+ *  -get first/last day of a given year
+ */
+class TimelineTableDateDiffHelper {
+
+       /**
+        * Get first day in year (modify input $date)
+        */
+       public static function getFirstDay( &$date, $year ) {
+               $date->modify( "first day of january " . $year );
+               $date->setTime(0, 0, 0);
+       }
+
+       /**
+        * Get last day in year (modify input $date)
+        */
+       public static function getLastDay( &$date, $year ) {
+               $date->modify( "last day of december " . $year );
+               $date->setTime(23, 59, 59);
+       }
+
+       /**
+        * Calculate the number of cells between two dates (public interface)
+        */
+       public static function getNumCells( $date1, $date2, $depth ) {
+
+               if ( !is_a( $date1, "DateTime" ) || !is_a( $date2, "DateTime" ) 
) {
+                       return null;
+               }
+               $startYear = intval( $date1->format( "Y" ) );
+               $endYear = intval( $date2->format( "Y" ) );
+
+               switch ( $depth ) {
+
+                       case TimelineTableDepthDesc::Year:
+                               $int = $date1->diff( $date2 );
+                               $nYears = $endYear - $startYear + 1;
+
+                               return ( $int->invert ? -1 : 1 ) * $nYears;
+
+                       case TimelineTableDepthDesc::Month:
+                               $m1 = intval( $date1->format( "n" ) );
+                               $m2 = intval( $date2->format( "n" ) );
+                               if ( $startYear == $endYear ) {
+                                       $nMonths = $m2 - $m1 + 1;
+                               } else {
+                                       $datecur = clone $date1;
+                                       self::getLastDay( $datecur, $startYear 
);
+                                       $nMonths = 0;
+
+                                       $nMonths += intval( $datecur->format( 
"n" ) ) -
+                                               $m1 + 1;
+                                       $nMonths += 12 * ( $endYear - 
$startYear - 1 );
+                                       $nMonths += intval( $date2->format( "n" 
) );
+                               }
+                               $int = $date1->diff( $date2 );
+
+                               return ( $int->invert ? -1 : 1 ) * $nMonths;
+
+                       case TimelineTableDepthDesc::Week:
+
+                               if ( $startYear == $endYear ) {
+                                       $w1 = intval( $date1->format( "W" ) );
+                                       $w2 = intval( $date2->format( "W" ) );
+                                       if ( $date1 <= $date2 ) {
+                                               if ( $w1 <= $w2 ) {
+                                                       $nWeeks = $w2 - $w1 + 1;
+                                               } else {
+                                                       // Wrap around: last 
day in current year is in the first
+                                                       // week of the 
following year
+                                                       $datecur = new 
DateTime( $startYear . "-12-28" );
+                                                       $nWeeks = intval( 
$datecur->format( "W" ) ) - $w1 + 1;
+                                               }
+                                       } else {
+                                               if ( $w1 >= $w2 ) {
+                                                       $nWeeks = $w2 - $w1 + 1;
+                                               } else {
+                                                       // Wrap around: last 
day in current year is in the first
+                                                       // week of the 
following year
+                                                       $datecur = new 
DateTime( $startYear . "-12-28" );
+                                                       $nWeeks = intval( 
$datecur->format( "W" ) ) - $w2 + 1;
+                                               }
+                                       }
+                               } else {
+                                       $datecur = new DateTime( $startYear . 
"-12-28" );
+                                       $nWeeks = 0;
+
+                                       if ( $date1 < $datecur ) {
+                                               $nWeeks += intval( 
$datecur->format( "W" ) ) -
+                                                       intval( $date1->format( 
"W" ) ) + 1;
+                                       }
+
+                                       for ( $y = $startYear + 1; $y < 
$endYear; $y++ ) {
+                                               $datecur = new DateTime( $y . 
"-12-28" );
+                                               $nWeeks += intval( 
$datecur->format( "W" ) );
+                                       }
+                                       $nWeeks += intval( $date2->format( "W" 
) );
+                               }
+                               $int = $date1->diff( $date2 );
+
+                               return ( $int->invert ? -1 : 1 ) * $nWeeks;
+
+                       case TimelineTableDepthDesc::Day:
+                               $int = $date1->diff( $date2 );
+                               $nDays = $int->days + 1;
+
+                               return ( $int->invert ? -1 : 1 ) * $nDays;
+
+                       case TimelineTableDepthDesc::Hour:
+                               $int = $date1->diff( $date2 );
+                               $h1 = intval( $date1->format( "H" ) );
+                               $h2 = intval( $date2->format( "H" ) );
+                               $nHours = $int->days * 24 + $h2 - $h1 + 1;
+
+                               return ( $int->invert ? -1 : 1 ) * $nHours;
+
+                       case TimelineTableDepthDesc::Minute:
+                               $int = $date1->diff( $date2 );
+                               $h1 = intval( $date1->format( "H" ) );
+                               $h2 = intval( $date2->format( "H" ) );
+                               $m1 = intval( $date1->format( "i" ) );
+                               $m2 = intval( $date2->format( "i" ) );
+                               $nMinutes = $int->days * 24 * 60 + ($h2 - $h1) 
* 60 +
+                                           $m2 - $m1 + 1;
+
+                               return ( $int->invert ? -1 : 1 ) * $nMinutes;
+
+                       case TimelineTableDepthDesc::Second:
+                               $int = $date1->diff( $date2 );
+                               $h1 = intval( $date1->format( "H" ) );
+                               $h2 = intval( $date2->format( "H" ) );
+                               $m1 = intval( $date1->format( "i" ) );
+                               $m2 = intval( $date2->format( "i" ) );
+                               $s1 = intval( $date1->format( "s" ) );
+                               $s2 = intval( $date2->format( "s" ) );
+                               $nSeconds = $int->days * 24 * 3600 + ($h2 - 
$h1) * 3600 +
+                                           ($m2 - $m1) * 60 + $s2 - $s1 + 1;
+
+                               return ( $int->invert ? -1 : 1 ) * $nSeconds;
+               }
+       }
+}
+
diff --git a/TimelineTable.Event.php b/TimelineTable.Event.php
new file mode 100644
index 0000000..70cb616
--- /dev/null
+++ b/TimelineTable.Event.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * TimelineTable Event object
+ */
+class TimelineTableEvent {
+
+       /// Start date
+       private $startDate;
+
+       /// End date
+       private $endDate;
+
+       /// Number of cells in block (only used for freetime block)
+       private $nCells;
+
+       /// Event description
+       private $text;
+
+       /// Event comment
+       private $comment;
+
+       /// Lenght of event text (for headers)
+       private $substr;
+
+       /// Cell type (td/th)
+       private $cellType;
+
+       /// Tooltip for event cell
+       private $tooltip;
+
+       /// Custom CSS-style for event cell
+       private $cellCSSStyle;
+
+       /// CSS class for event block
+       private $cellCSSClass;
+
+       /// Event validity
+       private $errMsg = "";
+
+       /**
+        * Parse event from string
+        */
+       public function parse( $input, $separator ) {
+
+               $fields = explode( $separator, trim( $input ) );
+               $nFields = count( $fields );
+               if ( $nFields >= 2 ) {
+
+                       // If date is YYYY (old style date), add "-01-01" to 
make it
+                       // parseable by DateTime
+                       if ( preg_match( "([0-9]{4})", $fields[0] ) ) {
+                               $fields[0] .= "-01-01";
+                       }
+                       // Parse date (return exception message on failure)
+                       try {
+                               $this->startDate = new DateTime( $fields[0] );
+                       } catch ( Exception $e ) {
+                               $this->errMsg =
+                                       wfMessage( 
'timelinetable-error-parsestart',
+                                               $e->getMessage() )->escaped();
+
+                               return false;
+                       }
+                       // Process second date entry
+                       if ( preg_match( "([0-9]{4})", $fields[1] ) ) {
+                               $fields[1] .= "-01-01";
+                       }
+                       try {
+                               $this->endDate = new DateTime( $fields[1] );
+                       } catch ( Exception $e ) {
+                               $this->errMsg =
+                                       wfMessage( 
'timelinetable-error-parseend',
+                                               $e->getMessage() )->escaped();
+
+                               return false;
+                       }
+
+                       // Check that startDate is before endDate
+                       if ( $this->startDate > $this->endDate ) {
+                               $this->errMsg =
+                                       wfMessage( 
'timelinetable-error-negdate',
+                                               $input )->escaped();
+
+                               return false;
+                       }
+
+                       // Read event text / comment / CSS style
+                       if ( $nFields > 2 ) {
+                               $this->text = $fields[2];
+                               if ( $nFields > 3 ) {
+                                       $this->comment = $fields[3];
+                                       if ( $nFields > 4 ) {
+                                               $this->cellCSSStyle = 
$fields[4];
+                                       }
+                               }
+                       }
+
+                       // Set other fields
+                       $this->cellCSSClass = 'tl_event';
+                       $this->cellType = 'td';
+                       $this->substr = 0;
+                       $this->tooltip = $this->startDate->format( "Y-m-d" ) . 
" / " .
+                               $this->endDate->format( "Y-m-d" );
+               } else {
+                       // Need at least two dates (start/end) to parse event
+                       $this->errMsg =
+                               wfMessage( 'timelinetable-error-parseargs' 
)->escaped();
+
+                       return false;
+               }
+
+               return true;
+       }
+
+       /**
+        * Create event for table header/footer (e.g. year/month/week/day 
header)
+        */
+       public function createEvent( $t_startDate, $t_endDate, $t_text, 
$t_tooltip,
+               $t_class, $t_type, $t_substr = 0 ) {
+               $this->startDate = $t_startDate;
+               $this->endDate = $t_endDate;
+               $this->text = $t_text;
+               $this->tooltip = $t_tooltip;
+               $this->cellCSSClass = $t_class;
+               $this->cellType = $t_type;
+               $this->substr = $t_substr;
+       }
+
+       /**
+        * Create "event" block for free-time (determined by the number of cells
+        * instead of start/end dates)
+        */
+       public function createEventBlock( $t_nCells, $t_text, $t_tooltip,
+               $t_class ) {
+               $this->nCells = $t_nCells;
+               $this->text = $t_text;
+               $this->tooltip = $t_tooltip;
+               $this->cellCSSClass = $t_class;
+               $this->cellType = 'td';
+               $this->substr = 0;
+       }
+
+       /**
+        * Test invalid event
+        */
+       public function isValid() {
+               return strlen( $this->errMsg ) == 0;
+       }
+
+       /**
+        * Get length of event (in number of cells for desired depth)
+        */
+       public function getNumCells( $depth ) {
+               // Determine number of cells in current block
+               if ( $this->nCells > 0 ) {
+                       $nEventCells = $this->nCells;
+               } else {
+                       $nEventCells = TimelineTableDateDiffHelper::getNumCells(
+                               $this->startDate, $this->endDate, $depth );
+                       if ( $nEventCells === null ) {
+                               wfDebugLog( "", "Trying to render empty 
event\n" );
+                       }
+               }
+               return $nEventCells;
+       }
+
+       /**
+        * Render HTML cell in table
+        */
+       public function render( $parser, $depth, $flagVert = false ) {
+
+               $spanDir = ( $flagVert ) ? 'rowspan' : 'colspan';
+
+               // Determine number of cells in current block
+               $nEventCells = $this->getNumCells( $depth );
+
+               // Create the event cell
+               $cellopts = array( $spanDir => $nEventCells,
+                       'class' => $this->cellCSSClass );
+               if ( strcmp( trim( $this->cellCSSStyle ), "" ) ) {
+                       $cellopts['style'] = htmlspecialchars( 
$this->cellCSSStyle );
+               }
+               if ( strcmp( trim( $this->tooltip ), "" ) ) {
+                       $cellopts['title'] = htmlspecialchars( $this->tooltip );
+               }
+               $celltext = $parser->recursiveTagParse( $this->text );
+
+               // Add comment field ($substr should not be defined when 
$comment is,
+               // $substr is for headers only)
+               if ( strcmp( trim( $this->comment ), "" ) ) {
+                       $celltext .= '<br />(';
+                       $parsed_comment = $parser->recursiveTagParse( 
$this->comment );
+                       $celltext .= $parsed_comment;
+                       $celltext .= ')';
+               } elseif ( $this->substr > 0 ) {
+                       // Perform substring if necessary
+                       $celltext = substr( $celltext, 0, $this->substr );
+               }
+
+               // Create table cell
+               return Html::rawElement( $this->cellType, $cellopts, $celltext 
);
+       }
+
+       /**
+        * Accessor for error message
+        */
+       public function getErrorMsg() {
+               return $this->errMsg;
+       }
+
+       /**
+        * Accessor for start date
+        */
+       public function getStartDate() {
+               return $this->startDate;
+       }
+
+       /**
+        * Accessor for end date
+        */
+       public function getEndDate() {
+               return $this->endDate;
+       }
+}
+
diff --git a/TimelineTable.Hooks.php b/TimelineTable.Hooks.php
new file mode 100644
index 0000000..b3000d8
--- /dev/null
+++ b/TimelineTable.Hooks.php
@@ -0,0 +1,353 @@
+<?php
+
+/**
+ * TimelineTable hooks and parser
+ */
+class TimelineTableHooks {
+
+       /**
+        * Register <timelinetable> hook
+        */
+       public static function efTimelineTableParserInit( $parser ) {
+               $parser->setHook( 'timelinetable',
+                       'TimelineTableHooks::efTimelineTableRender' );
+
+               return true;
+       }
+
+       /**
+        * After tidy
+        */
+       public static function efTimelineTableAfterTidy( & $parser, & $text ) {
+               // find markers in $text
+               // replace markers with actual output
+               global $markerList;
+               for ( $i = 0; $i < count( $markerList ); $i++ ) {
+                       $text = preg_replace( '/xx-marker' . $i . '-xx/',
+                               $markerList[$i], $text );
+               }
+
+               return true;
+       }
+
+       /**
+        * Define the html code as a marker, then change it back to text in
+        * 'efTimelineAfterTidy'. This is done to prevent the html code from 
being
+        * modified afterwards.
+        */
+       private static function makeOutputString( $str ) {
+               global $markerList;
+               $makercount = count( $markerList );
+               $marker = "xx-marker" . $makercount . "-xx";
+               $markerList[$makercount] = $str;
+
+               return $marker;
+       }
+
+       /**
+        * Add <pre></pre> tags around error message and return
+        */
+       private static function makeErrorOutputString( $errMsg ) {
+               $errMsg = "TimelineTable:<br/>" . $errMsg;
+               $errMsg = Html::rawElement( 'pre', array(), $errMsg );
+
+               return self::makeOutputString( $errMsg );
+       }
+
+       /**
+        * Main function: parse input and create HTML table with events
+        */
+       public static function efTimelineTableRender( $input, array $args,
+               Parser $parser, PPFrame $frame = null
+       ) {
+
+               if ( is_null( $frame ) ) {
+                       return self::makeOutputString( $input );
+               }
+
+               // Extract parameters from global variables
+               global $wgTimelineTableFieldSeparator;
+               global $wgTimelineTableDateSeparator;
+               global $wgTimelineTableLineSeparator;
+               global $wgTimelineTableAbbrMonth;
+               global $wgTimelineTableShortMonthLen;
+
+               // Format of month name
+               $monthFormat = $wgTimelineTableAbbrMonth ? "M" : "F";
+
+               // Parse tag arguments - title/caption
+               if ( isset( $args['title'] ) ) {
+                       $title = $args['title'];
+               } else {
+                       $title = "";
+               }
+               if ( isset( $args['caption'] ) ) {
+                       $caption = $args['caption'];
+               } else {
+                       // Try old style name: footer
+                       if ( isset( $args['footer'] ) ) {
+                               $caption = $args['footer'];
+                       } else {
+                               $caption = "";
+                       }
+               }
+
+               // Use vertical table rendering?
+               $flagVertical = isset( $args['vertical'] );
+
+               // Get desired depth
+               if ( isset( $args['depth'] ) ) {
+                       $depthStr = $args['depth'];
+                       // Get depth descriptor from string
+                       $depth = TimelineTableDepthDesc::decodeDepthDesc( 
$depthStr );
+               } else {
+                       // Parse first date entry (pre v.2.0 inputs)
+                       $depth = TimelineTableTable::getDepthFromFirstDate( 
$input );
+               }
+               if ( $depth === null ) {
+                       $errStr = wfMessage( 'timelinetable-error-depth' 
)->escaped();
+
+                       return self::makeErrorOutputString( $errStr );
+               }
+
+               // Get default option table
+               // loc: 1 (header), -1 (footer), 0 (no line)
+               // format: see [http://www.php.net/manual/en/function.date.php]
+               // substr: number of characters to display (only used for month 
with
+               //         format F or M and days with format D or l)
+               $hDefaultOpts = array(
+                       TimelineTableDepthDesc::Year => array( 'loc' => 1,
+                               'format' => 'Y',
+                               'substr' => 0 ),
+                       TimelineTableDepthDesc::Month => array( 'loc' => 1,
+                               'format' => $monthFormat,
+                               'substr' => 0 ),
+                       TimelineTableDepthDesc::Week => array( 'loc' => 0,
+                               'format' => 'W',
+                               'substr' => 0 ),
+                       TimelineTableDepthDesc::Day => array( 'loc' => 1,
+                               'format' => 'j',
+                               'substr' => 0 ),
+                       TimelineTableDepthDesc::Hour => array( 'loc' => 0,
+                               'format' => 'H',
+                               'substr' => 0 ),
+                       TimelineTableDepthDesc::Minute => array( 'loc' => 0,
+                               'format' => 'i',
+                               'substr' => 0 ),
+                       TimelineTableDepthDesc::Second => array( 'loc' => 0,
+                               'format' => 's',
+                               'substr' => 0 )
+               );
+               // Use weeks
+               if ( isset( $args['useweeks'] ) ||
+                       $depth == TimelineTableDepthDesc::Week
+               ) {
+                       $hDefaultOpts[TimelineTableDepthDesc::Week]['loc'] = 1;
+               }
+
+               // Header/footer list
+               $headerLines = array();
+               $footerLines = array();
+
+               if ( isset( $args['headers'] ) || isset( $args['footers'] ) ) {
+                       // populate the headers (hi=0) / footers (hi=1) table:
+                       // e.g. <timelinetable ... headers=Y/M-F-1 footers=D-l 
... >
+                       // Y     => years in header
+                       // M-F-1 => months in header (format "F", substring of 
length 1)
+                       // D-l   => days in footer (format "l", keep full 
string)
+                       for ( $hi = 0; $hi < 2; $hi++ ) {
+                               switch ( $hi ) {
+                                       case 0:
+                                               $HFLines = & $headerLines;
+                                               $argName = 'headers';
+                                               break;
+                                       case 1:
+                                               $HFLines = & $footerLines;
+                                               $argName = 'footers';
+                                               break;
+                               }
+                               if ( isset( $args[$argName] ) ) {
+                                       if ( strlen( trim( $args[$argName] ) ) 
== 0 ) {
+                                               // Skip if no entry (e.g. 
headers="")
+                                               continue;
+                                       }
+                                       $fields = explode( '/', trim( 
$args[$argName] ) );
+
+                                       foreach ( $fields as $hField ) {
+
+                                               $opts = explode( '-', trim( 
$hField ) );
+                                               $nOpts = count( $opts );
+                                               if ( $nOpts > 0 ) {
+                                                       switch ( strtolower( 
$opts[0] ) ) {
+                                                               case 'y':
+                                                                       $level 
= TimelineTableDepthDesc::Year;
+                                                                       $css = 
'tl_years';
+                                                                       break;
+                                                               case 'm':
+                                                                       $level 
= TimelineTableDepthDesc::Month;
+                                                                       $css = 
'tl_months';
+                                                                       break;
+                                                               case 'w':
+                                                                       $level 
= TimelineTableDepthDesc::Week;
+                                                                       $css = 
'tl_weeks';
+                                                                       break;
+                                                               case 'd':
+                                                                       $level 
= TimelineTableDepthDesc::Day;
+                                                                       $css = 
'tl_days';
+                                                                       break;
+                                                               case 'h':
+                                                                       $level 
= TimelineTableDepthDesc::Hour;
+                                                                       $css = 
'tl_days';
+                                                                       break;
+                                                               case 'i':
+                                                                       $level 
= TimelineTableDepthDesc::Minute;
+                                                                       $css = 
'tl_days';
+                                                                       break;
+                                                               case 's':
+                                                                       $level 
= TimelineTableDepthDesc::Second;
+                                                                       $css = 
'tl_days';
+                                                                       break;
+                                                               default:
+                                                                       // Only 
y/m/w/d in headers/footers argument
+                                                                       $eMsg = 
wfMessage( 'timelinetable-error-hf',
+                                                                               
$argName,
+                                                                               
$args[$argName] )->escaped();
+
+                                                                       return 
self::makeErrorOutputString( $eMsg );
+                                                                       break;
+                                                       }
+                                               }
+
+                                               // Read format options
+                                               if ( $level > $depth ) {
+                                                       continue;
+                                               }
+                                               if ( $nOpts > 1 ) {
+                                                       $format = $opts[1];
+                                               } else {
+                                                       $format = 
$hDefaultOpts[$level]['format'];
+                                               }
+                                               if ( $nOpts > 2 ) {
+                                                       $substr = $opts[2];
+                                               } else {
+                                                       $substr = 
$hDefaultOpts[$level]['substr'];
+                                               }
+
+                                               // Add header/footer to list
+                                               array_push( $HFLines, array( 
'level' => $level,
+                                                       'format' => $format,
+                                                       'substr' => $substr,
+                                                       'cssclass' => $css ) );
+                                       }
+                               }
+                       }
+               } else {
+                       // Old style options (pre v2.0)
+                       $topLevel = TimelineTableDepthDesc::Year;
+                       for ( $level = $topLevel; $level <= $depth; $level++ ) {
+
+                               $loc = $hDefaultOpts[$level]['loc'];
+
+                               if ( $loc != 0 ) {
+                                       $format = 
$hDefaultOpts[$level]['format'];
+                                       $substr = 
$hDefaultOpts[$level]['substr'];
+                                       $css = '';
+                                       $doContinue = false;
+                                       switch ( $level ) {
+                                               case 
TimelineTableDepthDesc::Year:
+                                                       if ( isset( 
$args['noyears'] ) ) {
+                                                               // Hide years
+                                                               $doContinue = 
true;
+                                                       }
+                                                       $css = 'tl_years';
+                                                       break;
+                                               case 
TimelineTableDepthDesc::Month:
+                                                       if ( isset( 
$args['nomonths'] ) ) {
+                                                               // Hide months
+                                                               $doContinue = 
true;
+                                                       }
+                                                       if ( $depth < 
TimelineTableDepthDesc::Day ) {
+                                                               // Abbreviate 
months
+                                                               $substr = 
$wgTimelineTableShortMonthLen;
+                                                       }
+                                                       $css = 'tl_months';
+                                                       break;
+                                               case 
TimelineTableDepthDesc::Week:
+                                                       $css = 'tl_weeks';
+                                                       break;
+                                               case 
TimelineTableDepthDesc::Day:
+                                                       if ( isset( 
$args['daynames'] ) ) {
+                                                               // Show day 
names (if option passed)
+                                                               $format = 'D';
+                                                       }
+                                                       $css = 'tl_days';
+                                                       break;
+                                               case 
TimelineTableDepthDesc::Hour:
+                                                       $css = 'tl_days';
+                                                       break;
+                                               case 
TimelineTableDepthDesc::Minute:
+                                                       $css = 'tl_days';
+                                                       break;
+                                               case 
TimelineTableDepthDesc::Second:
+                                                       $css = 'tl_days';
+                                                       break;
+                                       }
+                                       if ( $doContinue ) {
+                                               continue;
+                                       }
+                                       if ( $loc == 1 || $loc == 2 ) {
+                                               array_push( $headerLines, 
array( 'level' => $level,
+                                                       'format' => $format,
+                                                       'substr' => $substr,
+                                                       'cssclass' => $css ) );
+                                       }
+                                       if ( $loc == -1 || $loc == 2 ) {
+                                               array_push( $footerLines, 
array( 'level' => $level,
+                                                       'format' => $format,
+                                                       'substr' => $substr,
+                                                       'cssclass' => $css ) );
+                                       }
+                               }
+                       }
+               }
+
+               // Create table
+               $table = new TimelineTableTable( $title, $caption );
+
+               // Parse input (events)
+               $errParse = $table->parse( $input, $depth );
+
+               // Check parse output
+               if ( strcmp( $errParse, "" ) != 0 ) {
+                       return self::makeErrorOutputString( $errParse );
+               }
+
+               // Setup headers
+               for ( $hi = 0; $hi < 2; $hi++ ) {
+                       switch ( $hi ) {
+                               case 0:
+                                       $HFLines = & $headerLines;
+                                       break;
+                               case 1:
+                                       $HFLines = & $footerLines;
+                                       break;
+                       }
+                       foreach ( $HFLines as $hLines ) {
+
+                               $level = $hLines['level'];
+                               $format = $hLines['format'];
+                               $substr = $hLines['substr'];
+                               $cssClass = $hLines['cssclass'];
+                               $table->addHeader( $level, $depth, $cssClass, 
$hi == 0,
+                                       $format, $substr );
+                       }
+               }
+
+               // Render table
+               $timeline_str = $table->render( $parser, $depth, $flagVertical 
);
+
+               // Return output
+               return self::makeOutputString( $timeline_str );
+       }
+}
+
diff --git a/TimelineTable.Table.php b/TimelineTable.Table.php
new file mode 100644
index 0000000..7859531
--- /dev/null
+++ b/TimelineTable.Table.php
@@ -0,0 +1,720 @@
+<?php
+
+/**
+ * Main class handling input parsing and rendering of the table
+ */
+class TimelineTableTable {
+
+       /// Table title
+       public $tableTitle = "";
+
+       /// Table caption
+       public $tableCaption = "";
+
+       /// Table content: list of event lists (one list per table row)
+       public $tableLines = array();
+
+       /// Table header lines
+       public $tableHeaderLines = array();
+
+       /// Table footer lines
+       public $tableFooterLines = array();
+
+       /// Earliest start date
+       public $startDate;
+
+       /// Latest end date;
+       public $endDate;
+
+       /**
+        * Constructor
+        */
+       public function __construct( $title, $caption ) {
+               $this->tableTitle = $title;
+               $this->tableCaption = $caption;
+       }
+
+       /**
+        * Render HTML table
+        */
+       public function render( $parser, $depth, $flagVert = false ) {
+               if ( $flagVert ) {
+                       return $this->renderVertical( $parser, $depth );
+               } else {
+                       return $this->renderHorizontal( $parser, $depth );
+               }
+       }
+
+       /**
+        * Render table vertically
+        */
+       public function renderVertical( $parser, $depth ) {
+
+               // Total number of cells
+               $nTotalCells = TimelineTableDateDiffHelper::getNumCells(
+                       $this->startDate, $this->endDate, $depth );
+
+               // Build helper arrays for transposition
+               $locTableHeader = array();
+               $locTableFooter = array();
+               $locTableEntry  = array();
+               $idxMapHeader = array();
+               $idxMapFooter = array();
+               $idxMapentry  = array();
+               if ( count( $this->tableHeaderLines ) > 0 ) {
+                       $locTableHeader = array_fill( 0, count( 
$this->tableHeaderLines ),
+                                                     null );
+                       $this->fillVertTable( $this->tableHeaderLines, 
$nTotalCells,
+                                             $locTableHeader, $depth );
+                       $idxMapHeader = array_fill( 0, count( 
$this->tableHeaderLines ),
+                                           0 );
+               }
+               if ( count( $this->tableFooterLines ) > 0 ) {
+                       $locTableFooter = array_fill( 0, count( 
$this->tableFooterLines ) ,
+                                                     null );
+                       $this->fillVertTable( $this->tableFooterLines, 
$nTotalCells,
+                                             $locTableFooter, $depth );
+                       $idxMapFooter = array_fill( 0, count( 
$this->tableFooterLines ),
+                                                   0 );
+               }
+               if ( count( $this->tableLines ) > 0 ) {
+                       $locTableEntry = array_fill( 0, count( 
$this->tableLines ), null );
+                       $this->fillVertTable( $this->tableLines, $nTotalCells,
+                                             $locTableEntry, $depth );
+                       $idxMapEntry = array_fill( 0, count( $this->tableLines 
), 0 );
+               }
+
+               // Start the table
+               $ts = Html::openElement( 'table', array( 'class' => 'tl_table' 
) );
+
+               for ( $i = 0; $i < $nTotalCells; $i++ ) {
+                       $ts .= Html::openElement( 'tr' );
+
+                       if ( $i == 0 ) {
+                               if ( !empty( $this->tableTitle ) ) {
+                                       $ts .= Html::element( 'th',
+                                                             array( 'rowspan' 
=> $nTotalCells,
+                                                                    'class' => 
'tl_title' ),
+                                                             $this->tableTitle 
);
+                               }
+                       }
+
+                       $ts .= $this->renderVertRow( $this->tableHeaderLines,
+                                                    $locTableHeader, $i, 
$idxMapHeader,
+                                                    $parser, $depth, 'th' );
+                       $ts .= $this->renderVertRow( $this->tableLines,
+                                                    $locTableEntry, $i, 
$idxMapEntry,
+                                                    $parser, $depth, 'td' );
+                       $ts .= $this->renderVertRow( $this->tableFooterLines,
+                                                    $locTableFooter, $i, 
$idxMapFooter,
+                                                    $parser, $depth, 'th' );
+
+                       if ( $i == 0 ) {
+                               if ( !empty( $this->tableCaption ) ) {
+                                       $ts .= Html::element( 'td', array( 
'rowspan' => $nTotalCells,
+                                                                           
'class' => 'tl_foot' ),
+                                                             
$this->tableCaption );
+                               }
+                       }
+
+                       $ts .= Html::closeElement( 'tr' );
+               }
+
+               // Finish table
+               $ts .= Html::closeElement( 'table' );
+
+               return $ts;
+       }
+
+       public function renderHorizontal( $parser, $depth ) {
+
+               // Total number of cells
+               $nTotalCells = TimelineTableDateDiffHelper::getNumCells(
+                       $this->startDate, $this->endDate, $depth );
+
+               // Start the table
+               $ts = Html::openElement( 'table', array( 'class' => 'tl_table' 
) );
+
+               // Header: title line + headers
+               if ( strlen( $this->tableTitle ) > 0 ||
+                       count( $this->tableHeaderLines )
+               ) {
+
+                       $ts .= Html::openElement( 'thead', array( 'class' => 
'tl_header' ) );
+                       $ts .= $this->renderTitleCaption( $this->tableTitle, 
$nTotalCells,
+                               'tl_title', 'th' );
+                       $ts .= $this->renderHeaderFooter( 
$this->tableHeaderLines,
+                               'tl_title', $depth, $parser,
+                               'th' );
+                       $ts .= Html::closeElement( 'thead' );
+               }
+               // Footer: caption line + footers
+               if ( strlen( $this->tableCaption ) > 0 ||
+                       count( $this->tableFooterLines )
+               ) {
+
+                       // Header: title line + headers
+                       $ts .= Html::openElement( 'tfoot', array( 'class' => 
'tl_footer' ) );
+                       $ts .= $this->renderHeaderFooter( 
$this->tableFooterLines,
+                               'tl_foot', $depth, $parser,
+                               'th' );
+                       $ts .= $this->renderTitleCaption( $this->tableCaption, 
$nTotalCells,
+                               'tl_foot', 'td' );
+                       $ts .= Html::closeElement( 'tfoot' );
+               }
+
+               // Body: Events
+               $ts .= Html::openElement( 'tbody', array( 'class' => 'tl_body' 
) );
+               foreach ( $this->tableLines as $hLine ) {
+                       $ts .= Html::openElement( 'tr' );
+                       foreach ( $hLine as $hEvent ) {
+                               if ( $hEvent->isValid() ) {
+                                       $ts .= $hEvent->render( $parser, $depth 
);
+                               } else {
+                                       $errMsg = $hEvent->getErrorMsg();
+                                       $ts .= Html::element( 'td', null, 
$errMsg );
+                                       break;
+                               }
+                       }
+                       $ts .= Html::closeElement( 'tr' );
+               }
+               $ts .= Html::closeElement( 'tbody' );
+
+               // Finish table
+               $ts .= Html::closeElement( 'table' );
+
+               return $ts;
+       }
+
+       /**
+        * Render title/caption line
+        */
+       private function renderTitleCaption( $HFText, $nTotalCells, $CSSclass,
+               $type
+       ) {
+               $ts = "";
+               if ( !empty( $HFText ) ) {
+
+                       $ts .= Html::openElement( 'tr' );
+                       $ts .= Html::element( $type, array( 'colspan' => 
$nTotalCells,
+                                       'class' => $CSSclass ),
+                               $HFText );
+                       $ts .= Html::closeElement( 'tr' );
+               }
+
+               return $ts;
+       }
+
+       /**
+        * Render header/footer sections
+        */
+       private function renderHeaderFooter( &$tableHFLines, $CSSclass, $depth,
+               $parser, $HTMLtype ) {
+               $ts = "";
+
+               if ( count( $tableHFLines ) > 0 ) {
+                       // Header/footer cells
+                       foreach ( $tableHFLines as $hLine ) {
+                               $ts .= Html::openElement( 'tr' );
+                               foreach ( $hLine as $hEvent ) {
+                                       if ( $hEvent->isValid() ) {
+                                               $ts .= $hEvent->render( 
$parser, $depth );
+                                       } else {
+                                               $errMsg = $hEvent->getErrMsg();
+                                               $ts .= Html::element( 
$HTMLtype, null, $errMsg );
+                                               break;
+                                       }
+                               }
+                               $ts .= Html::closeElement( 'tr' );
+                       }
+               }
+
+               return $ts;
+       }
+
+       /**
+        * Create helper table for vertical table
+        */
+       private function fillVertTable( $tableLines, $nTotalCells,
+                                       &$outLocTable, $depth ) {
+               for ( $hi = 0; $hi < count( $tableLines ); $hi++) {
+                       $outLocTable[ $hi ] = array_fill( 0, $nTotalCells, 
false);
+
+                       $idxStart = 0;
+                       for ( $ei = 0; $ei < count( $tableLines[ $hi ] ); 
$ei++) {
+                               $event = $tableLines[ $hi ][ $ei ];
+                               $nCells = $event->getNumCells( $depth );
+                               $outLocTable[ $hi ][ $idxStart ] = true;
+                               $idxStart += $nCells;
+                       }
+               }
+       }
+
+       /**
+        * Render single row of vertical table
+        */
+       private function renderVertRow( $tableLines, $locTable, $rowIdx, 
&$idxMap,
+                                       $parser, $depth, $HTMLtype ) {
+               $ts = '';
+               for ( $hi = 0; $hi < count( $tableLines ); $hi++) {
+                       if ( $locTable[ $hi ][ $rowIdx ] ) {
+                               $hEvent = $tableLines[ $hi ][ $idxMap[ $hi ] ];
+                               if ( $hEvent->isValid() ) {
+                                       $ts .= $hEvent->render( $parser, 
$depth, true );
+                               } else {
+                                       $errMsg = $hEvent->getErrMsg();
+                                       $ts .= Html::element( $HTMLtype, null, 
$errMsg );
+                                       break;
+                               }
+                               $idxMap[ $hi ]++;
+                       }
+               }
+               return $ts;
+       }
+
+
+       /**
+        * Parse timelinetable input
+        */
+       public function parse( $input, $depth ) {
+
+               // Extract parameters from global variables
+               global $wgTimelineTableFieldSeparator;
+               global $wgTimelineTableLineSeparator;
+               global $wgTimelineTableEventSeparator;
+
+               // Get lines
+               $lines = explode( $wgTimelineTableLineSeparator, trim( $input ) 
);
+
+               // Get lists of events in each line
+               $flagFirstEvent = true;
+               $k = 0;
+               foreach ( $lines as $val ) {
+
+                       $eventList = explode( $wgTimelineTableEventSeparator,
+                               trim( $val ) );
+                       $flagFirstEventLine = true;
+                       $this->tableLines[$k] = array();
+
+                       // Loop over events in the current line
+                       foreach ( $eventList as $eventStr ) {
+
+                               $event = new TimelineTableEvent();
+                               // Parse event
+                               if ( !$event->parse( $eventStr,
+                                       $wgTimelineTableFieldSeparator )
+                               ) {
+                                       return $event->getErrorMsg();
+                               }
+                               $eventStartDate = $event->getStartDate();
+                               $eventEndDate = $event->getEndDate();
+
+                               // Update table first/last event if necessary
+                               if ( $flagFirstEvent || $eventStartDate < 
$this->startDate ) {
+                                       $this->startDate = $eventStartDate;
+                               }
+                               if ( $flagFirstEvent || $eventEndDate > 
$this->endDate ) {
+                                       $this->endDate = $eventEndDate;
+                               }
+
+                               // Create free-time block between successive 
events
+                               if ( !$flagFirstEventLine ) {
+                                       $nCellsFreeTime = 
TimelineTableDateDiffHelper::getNumCells(
+                                               $prevEndDate, $eventStartDate, 
$depth );
+                                       // Remove two cells (exclusive range)
+                                       $nCellsFreeTime -= 2;
+                                       if ( $nCellsFreeTime < 0 ) {
+                                               // If events overlap or are not 
in increasing order,
+                                               // return an error
+                                               return wfMessage( 
'timelinetable-error-overlap',
+                                                       $val )->escaped();
+                                       }
+                                       if ( $nCellsFreeTime > 0 ) {
+                                               $ftEvent = new 
TimelineTableEvent();
+                                               $ftEvent->createEventBlock( 
$nCellsFreeTime, '', '',
+                                                       'tl_freetime' );
+                                               array_push( 
$this->tableLines[$k], $ftEvent );
+                                       }
+                               }
+
+                               // Add event to list
+                               array_push( $this->tableLines[$k], $event );
+
+                               // Prepare next entry
+                               $flagFirstEvent = false;
+                               $flagFirstEventLine = false;
+                               $prevEndDate = $eventEndDate;
+                       }
+                       $k++;
+               }
+
+               // Add leading/trailing freetime blocks
+               for ( $k = 0; $k < count( $this->tableLines ); $k++ ) {
+
+                       $firstEvent = $this->tableLines[$k][0];
+                       $nEvents = count( $this->tableLines[$k] );
+                       $lastEvent = $this->tableLines[$k][$nEvents - 1];
+                       $firstDate = $firstEvent->getStartDate();
+                       $lastDate = $lastEvent->getEndDate();
+
+                       if ( $firstDate > $this->startDate ) {
+                               // Leading freetime block
+                               $nCellsFreeTime = 
TimelineTableDateDiffHelper::getNumCells(
+                                       $this->startDate, $firstDate, $depth );
+                               // Remove one cell (exclusive range)
+                               $nCellsFreeTime--;
+                               if ( $nCellsFreeTime < 0 ) {
+                                       // Something went wrong: there is a 
date earlier than the
+                                       // overall start date, return an error
+                                       return wfMessage( 
'timelinetable-error-free', $k )->escaped();
+                               }
+                               if ( $nCellsFreeTime > 0 ) {
+                                       $ftEvent = new TimelineTableEvent();
+                                       $ftEvent->createEventBlock( 
$nCellsFreeTime, '', '',
+                                               'tl_freetime' );
+                                       array_unshift( $this->tableLines[$k], 
$ftEvent );
+                               }
+                       }
+
+                       if ( $lastDate < $this->endDate ) {
+                               // Trailing freetime block
+                               $nCellsFreeTime = 
TimelineTableDateDiffHelper::getNumCells(
+                                       $lastDate, $this->endDate, $depth );
+                               // Remove one cell (exclusive range)
+                               $nCellsFreeTime--;
+                               if ( $nCellsFreeTime < 0 ) {
+                                       // Something went wrong: there is a 
date later than the
+                                       // overall end date, return an error
+                                       return wfMessage( 
'timelinetable-error-free', $k )->escaped();
+                               }
+                               if ( $nCellsFreeTime > 0 ) {
+                                       $ftEvent = new TimelineTableEvent();
+                                       $ftEvent->createEventBlock( 
$nCellsFreeTime, '', '',
+                                               'tl_freetime' );
+                                       array_push( $this->tableLines[$k], 
$ftEvent );
+                               }
+                       }
+               }
+
+               // Return an empty string on succes (no error message)
+               return "";
+       }
+
+       /**
+        * Parse first date entry and guess depth (must be in format: 
YYYY-MM-DD,
+        * where MM and DD may be omitted).  Returns a TimelineTableDepthDesc
+        * constant or null if first date could not be parsed.
+        *
+        * This is for backward compatibility (pre 2.0) when depth is not 
passed as
+        * an input argument.
+        */
+       public static function getDepthFromFirstDate( $input ) {
+
+               // Extract parameters from global variables
+               global $wgTimelineTableFieldSeparator;
+               global $wgTimelineTableLineSeparator;
+               global $wgTimelineTableEventSeparator;
+               global $wgTimelineTableDateSeparator;
+
+               // Get events
+               $lines = explode( $wgTimelineTableLineSeparator, trim( $input ) 
);
+
+               // Get lists of events in each line
+               $flagFirstEvent = true;
+               $k = 0;
+               foreach ( $lines as $val ) {
+
+                       $eventList = explode( $wgTimelineTableEventSeparator,
+                               trim( $val ) );
+                       foreach ( $eventList as $eventStr ) {
+
+                               $tmp = explode( $wgTimelineTableFieldSeparator,
+                                       trim( $eventStr ) );
+                               if ( count( $tmp ) >= 2 ) {
+                                       $dateField = explode( 
$wgTimelineTableDateSeparator,
+                                               trim( $tmp[0] ) );
+                                       switch ( count( $dateField ) ) {
+                                               case 1:
+                                                       // Only one entry in 
date, it must be a year (YYYY)
+                                                       if ( preg_match( 
"/^([0-9]{4})$/", $dateField[0] ) ) {
+                                                               return 
TimelineTableDepthDesc::Year;
+                                                       } else {
+                                                               return null;
+                                                       }
+                                               case 2:
+                                                       // Two entries in date, 
it must be year-month (YYYY-MM)
+                                                       if ( preg_match( 
"/^([0-9]{4})$/", $dateField[0] ) &&
+                                                               preg_match( 
"/^([0-9]{1,2})$/", $dateField[1] )
+                                                       ) {
+                                                               return 
TimelineTableDepthDesc::Month;
+                                                       } else {
+                                                               return null;
+                                                       }
+                                               case 3:
+                                                       // Three entries in 
date, it must be year-month-day
+                                                       // (YYYY-MM-DD)
+                                                       if ( preg_match( 
"/^([0-9]{4})$/", $dateField[0] ) &&
+                                                               preg_match( 
"/^([0-9]{1,2})$/", $dateField[1] ) &&
+                                                               preg_match( 
"/^([0-9]{1,2})$/", $dateField[2] )
+                                                       ) {
+                                                               return 
TimelineTableDepthDesc::Day;
+                                                       } else {
+                                                               return null;
+                                                       }
+                                               default:
+                                                       return null;
+                                       }
+                               } else {
+                                       return null;
+                               }
+                       }
+               }
+
+               return null;
+       }
+
+       /**
+        * Create table lines for headers
+        */
+       public function addHeader( $level, $depth, $cssClass, $isHeader, 
$format,
+               $substr
+       ) {
+
+               // Select header/footer mode
+               if ( $isHeader ) {
+                       $k = count( $this->tableHeaderLines );
+                       $this->tableHeaderLines[$k] = array();
+               } else {
+                       $k = count( $this->tableFooterLines );
+                       $this->tableFooterLines[$k] = array();
+               }
+
+               // Use header style for both header and footer
+               $type = 'th';
+
+               switch ( $level ) {
+
+                       case TimelineTableDepthDesc::Year:
+                               $curDate = clone $this->startDate;
+                               while ( $curDate <= $this->endDate ) {
+                                       $str = $curDate->format( $format );
+                                       $hEvent = new TimelineTableEvent();
+                                       $startDate = clone $curDate;
+                                       $endDate = clone $curDate;
+                                       // TODO: make this more general 
(first/last day of year)
+                                       
TimelineTableDateDiffHelper::getLastDay( $endDate, $str );
+                                       if ( $endDate > $this->endDate ) {
+                                               $endDate = $this->endDate;
+                                       }
+                                       $hEvent->createEvent( $startDate, 
$endDate, $str, '',
+                                               $cssClass, $type, $substr );
+                                       if ( $isHeader ) {
+                                               array_push( 
$this->tableHeaderLines[$k], $hEvent );
+                                       } else {
+                                               array_push( 
$this->tableFooterLines[$k], $hEvent );
+                                       }
+                                       $nextYearStr = strval( intval( $str ) + 
1 );
+                                       
TimelineTableDateDiffHelper::getFirstDay( $curDate,
+                                                                               
  $nextYearStr );
+                                       unset( $startDate );
+                                       unset( $endDate );
+                                       unset( $hEvent );
+                               }
+                               break;
+
+                       case TimelineTableDepthDesc::Month:
+                               $curDate = clone $this->startDate;
+                               while ( $curDate <= $this->endDate ) {
+                                       $hEvent = new TimelineTableEvent();
+                                       $startDate = clone $curDate;
+                                       $endDate = clone $curDate;
+                                       $endDate->modify( 'last day of this 
month' );
+                                       if ( $endDate > $this->endDate ) {
+                                               $endDate = $this->endDate;
+                                       }
+                                       $str = $curDate->format( $format );
+                                       $hEvent->createEvent( $startDate, 
$endDate, $str, '',
+                                               $cssClass, $type, $substr );
+                                       if ( $isHeader ) {
+                                               array_push( 
$this->tableHeaderLines[$k], $hEvent );
+                                       } else {
+                                               array_push( 
$this->tableFooterLines[$k], $hEvent );
+                                       }
+                                       $curDate->modify( 'first day of next 
month' );
+                                       unset( $startDate );
+                                       unset( $endDate );
+                                       unset( $hEvent );
+                               }
+                               break;
+
+                       case TimelineTableDepthDesc::Day:
+                               $curDate = clone $this->startDate;
+                               // Update format if displaying day names and 
timeline is longer than
+                               // a week
+                               if ( ( strcmp( $format, 'D' ) == 0 ||
+                                               strcmp( $format, 'l' ) == 0 ) &&
+                                       $this->endDate->diff( $curDate )->days 
> 7
+                               ) {
+                                       $flagAddDayNumber = true;
+                               } else {
+                                       $flagAddDayNumber = false;
+                               }
+                               while ( $curDate <= $this->endDate ) {
+                                       $hEvent = new TimelineTableEvent();
+                                       $str = $curDate->format( $format );
+                                       if ( $flagAddDayNumber ) {
+                                               if ( $substr > 0 ) {
+                                                       $str = substr( $str, 0, 
$substr );
+                                               }
+                                               $str .= " " . $curDate->format( 
"j" );
+                                               $substrEvent = 0;
+                                       } else {
+                                               $substrEvent = $substr;
+                                       }
+                                       $startDate = clone $curDate;
+                                       $endDate = clone $curDate;
+                                       $endDate->setTime(23, 59, 59);
+                                       if ( $endDate > $this->endDate ) {
+                                               $endDate = $this->endDate;
+                                       }
+                                       $hEvent->createEvent( $startDate, 
$endDate, $str, '',
+                                               $cssClass, $type, $substrEvent 
);
+                                       if ( $isHeader ) {
+                                               array_push( 
$this->tableHeaderLines[$k], $hEvent );
+                                       } else {
+                                               array_push( 
$this->tableFooterLines[$k], $hEvent );
+                                       }
+                                       unset( $curDate );
+                                       $curDate = clone $endDate;
+                                       $curDate->modify( '+1 second' );
+                                       unset( $startDate );
+                                       unset( $endDate );
+                                       unset( $hEvent );
+                               }
+                               break;
+
+                       case TimelineTableDepthDesc::Week:
+                               $curDate = clone $this->startDate;
+                               while ( $curDate <= $this->endDate ) {
+                                       $hEvent = new TimelineTableEvent();
+                                       $startDate = clone $curDate;
+                                       // Move to first day of week (Monday)
+                                       $startDayIdx = $startDate->format( "N" 
) - 1;
+                                       $endDate = clone $startDate;
+                                       $dayDiff = 6 - $startDayIdx;
+                                       $endDate->modify( '+' . $dayDiff . ' 
days' );
+                                       if ( $endDate > $this->endDate ) {
+                                               $endDate = $this->endDate;
+                                       }
+                                       $str = $curDate->format( $format );
+                                       $tooltip = $startDate->format( "Y-m-d" 
) . " / " .
+                                               $endDate->format( "Y-m-d" );
+                                       $hEvent->createEvent( $startDate, 
$endDate, $str, $tooltip,
+                                               $cssClass, $type, $substr );
+                                       if ( $isHeader ) {
+                                               array_push( 
$this->tableHeaderLines[$k], $hEvent );
+                                       } else {
+                                               array_push( 
$this->tableFooterLines[$k], $hEvent );
+                                       }
+                                       unset( $curDate );
+                                       $curDate = clone $endDate;
+                                       $curDate->modify( '+1 day' );
+                                       unset( $startDate );
+                                       unset( $endDate );
+                                       unset( $hEvent );
+                               }
+                               break;
+
+                       case TimelineTableDepthDesc::Hour:
+                               $curDate = clone $this->startDate;
+                               while ( $curDate <= $this->endDate ) {
+                                       $hEvent = new TimelineTableEvent();
+                                       $startDate = clone $curDate;
+                                       $endDate = clone $curDate;
+                                       $curHour = intval( $endDate->format( 
'H' ) );
+                                       $curMinute = intval( $endDate->format( 
'i' ) );
+                                       $curSecond = intval( $endDate->format( 
's' ) );
+                                       $endDate->setTime($curHour, 59, 59);
+                                       if ( $endDate > $this->endDate ) {
+                                               $endDate = $this->endDate;
+                                       }
+                                       $str = $curDate->format( $format );
+                                       $hEvent->createEvent( $startDate, 
$endDate, $str, '',
+                                               $cssClass, $type, $substr );
+                                       if ( $isHeader ) {
+                                               array_push( 
$this->tableHeaderLines[$k], $hEvent );
+                                       } else {
+                                               array_push( 
$this->tableFooterLines[$k], $hEvent );
+                                       }
+                                       unset( $curDate );
+                                       $curDate = clone $endDate;
+                                       $curDate->modify( '+1 second' );
+                                       unset( $startDate );
+                                       unset( $endDate );
+                                       unset( $hEvent );
+                               }
+                               break;
+
+                       case TimelineTableDepthDesc::Minute:
+                               $curDate = clone $this->startDate;
+                               while ( $curDate <= $this->endDate ) {
+                                       $hEvent = new TimelineTableEvent();
+                                       $startDate = clone $curDate;
+                                       $endDate = clone $curDate;
+                                       $curHour = intval( $endDate->format( 
'h' ) );
+                                       $curMinute = intval( $endDate->format( 
'i' ) );
+                                       $curSecond = intval( $endDate->format( 
's' ) );
+                                       $endDate->setTime($curHour, $curMinute, 
59);
+                                       if ( $endDate > $this->endDate ) {
+                                               $endDate = $this->endDate;
+                                       }
+                                       $str = $curDate->format( $format );
+                                       $hEvent->createEvent( $startDate, 
$endDate, $str, '',
+                                               $cssClass, $type, $substr );
+                                       if ( $isHeader ) {
+                                               array_push( 
$this->tableHeaderLines[$k], $hEvent );
+                                       } else {
+                                               array_push( 
$this->tableFooterLines[$k], $hEvent );
+                                       }
+                                       unset( $curDate );
+                                       $curDate = clone $endDate;
+                                       $curDate->modify( '+1 second' );
+                                       unset( $startDate );
+                                       unset( $endDate );
+                                       unset( $hEvent );
+                               }
+                               break;
+
+                       case TimelineTableDepthDesc::Second:
+                               $curDate = clone $this->startDate;
+                               while ( $curDate <= $this->endDate ) {
+                                       $hEvent = new TimelineTableEvent();
+                                       $startDate = clone $curDate;
+                                       $endDate = clone $curDate;
+                                       $curHour = intval( $endDate->format( 
'h' ) );
+                                       $curMinute = intval( $endDate->format( 
'i' ) );
+                                       $curSecond = intval( $endDate->format( 
's' ) );
+                                       //$endDate->setTime($curHour, 
$curMinute, 59);
+                                       if ( $endDate > $this->endDate ) {
+                                               $endDate = $this->endDate;
+                                       }
+                                       $str = $curDate->format( $format );
+                                       $hEvent->createEvent( $startDate, 
$endDate, $str, '',
+                                               $cssClass, $type, $substr );
+                                       if ( $isHeader ) {
+                                               array_push( 
$this->tableHeaderLines[$k], $hEvent );
+                                       } else {
+                                               array_push( 
$this->tableFooterLines[$k], $hEvent );
+                                       }
+                                       unset( $curDate );
+                                       $curDate = clone $endDate;
+                                       $curDate->modify( '+1 second' );
+                                       unset( $startDate );
+                                       unset( $endDate );
+                                       unset( $hEvent );
+                               }
+                               break;
+               }
+       }
+}
+
diff --git a/TimelineTable.body.php b/TimelineTable.body.php
deleted file mode 100755
index 7bbaf2a..0000000
--- a/TimelineTable.body.php
+++ /dev/null
@@ -1,643 +0,0 @@
-<?php
-
-/**
- * Protect against register_globals vulnerabilities.
- * This line must be present before any global variable is referenced.
- */
-if( !defined( 'MEDIAWIKI' ) )
-{
-       echo( "This is an extension to the MediaWiki package and cannot be run 
standalone.\n" );
-       die( -1 );
-}
-
-// Functions
-function isLeapYear($year) {
-       return ($year%4==0) && ($year%100!=0) || ($year%400==0);
-}
-
-function nDaysMonth($month, $year) {
-       // Check leap year
-       switch ( $month ) {
-               case ( $month==1 || $month==3 || $month==5 || $month==7 || 
$month==8 ||
-                          $month==10 || $month==12 ):
-                       return 31;
-                       break;
-               case ( $month==4 || $month==6 || $month==9 || $month==11):
-                       return 30;
-                       break;
-               case 2:
-                       if ( isLeapYear($year) )
-                       {
-                               return 29;
-                       }
-                       else
-                       {
-                               return 28;
-                       }
-                       break;
-       }
-}
-
-function nDaysYear($year) {
-       if ( isLeapYear($year) ) {
-               return 366;
-       } else {
-               return 365;
-       }
-}
-
-// Main function
-function efTimelineTableRender( $input, array $args, Parser $parser, PPFrame 
$frame ) {
-       // Parameters
-       $wgTimelineTableLineSeparator  = "\n"; // Separator for parsing lines 
of the input.
-       $wgTimelineTableFieldSeparator = "|";  // Separator for parsing fields 
of a single
-                                              // event.
-       $wgTimelineTableDateSeparator  = "-";  // Separator for parsing the 
date of an event.
-       $wgTimelineTableMaxCells       = 100;  // If the total length of the 
timetable (in
-                                              // days) is larger than this 
value, do not
-                                              // display days in table.
-       $wgHTMLlr                 = "\n"; // Line return (in the rendered html 
file).
-       $wgHTMLtab                = "\t"; // Tabulation (in the rendered html 
file).
-
-       // Extract parameters from global variables
-//     global $wgTimelineTableFieldSeparator;
-//     global $wgTimelineTableDateSeparator;
-//     global $wgTimelineTableLineSeparator;
-//     global $wgTimelineTableMaxCells;
-//     global $wgHTMLlr;
-//     global $wgHTMLtab;
-
-       // Parse tag arguments
-       //$title = $args['title']; // fix from MikaelLindmark
-       //$footer = $args['footer'];
-       if(isset($args['title'])) {  $title = $args['title'];  } else {  $title 
= ""; }
-       if(isset($args['footer'])){ $footer = $args['footer']; } else { $footer 
= ""; }
-
-       // Get events
-       $lines = explode($wgTimelineTableLineSeparator,trim($input));
-
-       // ---------- Process years (get first and last) ----------
-       // --------------------------------------------------------
-       $allStartYear = 9999; // Should work for a while
-       $allEndYear = -1;
-       foreach ( $lines as $val ) {
-               $tmp=explode($wgTimelineTableFieldSeparator,trim($val));
-               if (count($tmp) >= 2) {
-                       
$year=explode($wgTimelineTableDateSeparator,trim($tmp[0]),2);
-                       if ( (int)($year[0])<$allStartYear ){$allStartYear = 
(int)($year[0]);}
-                       
$year=explode($wgTimelineTableDateSeparator,trim($tmp[1]),2);
-                       if ( (int)($year[0])>$allEndYear ){$allEndYear = 
(int)($year[0]);}
-               }
-       }
-
-       // Number of years to display
-       $nTotalYears = $allEndYear - $allStartYear + 1;
-       // ---------- Process months (get first and last) ----------
-       // ---------------------------------------------------------
-       $allStartMonth = 13;
-       $allEndMonth = 0;
-       $flagShowMonths = true;
-       foreach ( $lines as $val ) {
-               $tmp=explode($wgTimelineTableFieldSeparator,trim($val));
-               $eventStart = explode($wgTimelineTableDateSeparator,$tmp[0]);
-               if ( sizeof($eventStart)>0 ) {
-                       $eventStartYear = $eventStart[0];
-               } else {
-                       $flagShowMonths = false;
-               }
-               if ( sizeof($eventStart)>1 ) {
-                       $eventStartMonth = $eventStart[1];
-               } else {
-                       $eventStartMonth = 0;
-                       $flagShowMonths = false;
-               }
-               $eventEnd = explode($wgTimelineTableDateSeparator,$tmp[1]);
-               if ( sizeof($eventEnd)>0 ) {
-                       $eventEndYear = $eventEnd[0];
-               } else {
-                       $flagShowMonths = false;
-               }
-               if ( sizeof($eventEnd)>1 ) {
-                       $eventEndMonth = $eventEnd[1];
-               } else {
-                       $eventEndMonth = 0;
-                       $flagShowMonths = false;
-               }
-               $eventStartYear = ((int)$eventStartYear);
-               $eventEndYear = ((int)$eventEndYear);
-               $eventStartMonth = (int)$eventStartMonth;
-               $eventEndMonth = (int)$eventEndMonth;
-               if ( $eventStartMonth==0 || $eventEndMonth==0 ) {
-                       $flagShowMonths = false;
-               }
-               if ( $eventStartYear==$allStartYear ) {
-                       if ( $eventStartMonth<$allStartMonth ) {
-                               $allStartMonth = $eventStartMonth;
-                       }
-               }
-               if ( $eventEndYear==$allEndYear ) {
-                       if ( $eventEndMonth>$allEndMonth ) {
-                               $allEndMonth = $eventEndMonth;
-                       }
-               }
-       }
-       $nMonths[] = 12 - $allStartMonth + 1;
-       for ( $year=$allStartYear+1 ; $year<$allEndYear ; $year++ ) {
-               $nMonths[] = 12;
-       }
-       $nMonths[] = $allEndMonth;
-       // $nTotalMonths contains the total number of months over the time range
-       $nTotalMonths = array_sum($nMonths);
-
-       // ---------- Process days (get first and last) ----------
-       // -------------------------------------------------------
-       $allStartDay = 32;
-       $allEndDay = 0;
-       $flagShowDays = $flagShowMonths? true: false;
-       foreach ( $lines as $val ) {
-               $tmp = explode($wgTimelineTableFieldSeparator,trim($val));
-               if (count($tmp) < 2) {
-                       continue;
-               }
-               $eventStarttmp = explode($wgTimelineTableDateSeparator,$tmp[0]);
-               if (count($eventStarttmp) < 3) {
-                       continue;
-               }
-               list($eventStartYear, $eventStartMonth, $eventStartDay) = 
$eventStarttmp;       
-               list($eventEndYear, $eventEndMonth, $eventEndDay) =
-                       explode($wgTimelineTableDateSeparator,$tmp[1]);
-               $eventStartYear = ((int)$eventStartYear);
-               $eventEndYear = ((int)$eventEndYear);
-               $eventStartMonth = (int)$eventStartMonth;
-               $eventEndMonth = (int)$eventEndMonth;
-               $eventStartDay = (int)$eventStartDay;
-               $eventEndDay = (int)$eventEndDay;
-               if ( $eventStartDay==0 || $eventEndDay==0 ) {
-                       $flagShowDays = false;
-               }
-               if ( $eventStartYear==$allStartYear &&
-                        $eventStartMonth==$allStartMonth ) {
-                       if ( $eventStartDay<$allStartDay ) {
-                               $allStartDay = $eventStartDay;
-                       }
-               }
-               if ( $eventEndYear==$allEndYear && $eventEndMonth==$allEndMonth 
) {
-                       if ( $eventEndDay>$allEndDay ) {
-                               $allEndDay = $eventEndDay;
-                       }
-               }
-       }
-       if ( $allStartYear==$allEndYear && $allStartMonth==$allEndMonth ) {
-               $nDays[0] = $allEndDay - $allStartDay + 1;
-       }
-       elseif ( $allStartYear==$allEndYear ) {
-               $nDays[0] = nDaysMonth($allStartMonth,$allStartYear) - 
$allStartDay + 1;
-               for ( $month=$allStartMonth+1 ; $month<$allEndMonth ; $month++ 
) {
-                       $nDays[] = nDaysMonth($month,$allStartYear);
-               }
-       } else {
-               $nDays[0] = nDaysMonth($allStartMonth,$allStartYear) - 
$allStartDay + 1;
-               for ( $month=$allStartMonth+1 ; $month<=12 ; $month++ ) {
-                  $nDays[] = nDaysMonth($month,$allStartYear);
-               }
-               for ( $year=1 ; $year<$nTotalYears-1 ; $year++ ) {
-                       $year1 = $year + $allStartYear;
-                       for ( $month=1 ; $month<=12 ; $month++ ) {
-                               $nDays[] = nDaysMonth($month,$year1);
-                       }
-               }
-               for ( $month=1 ; $month<$allEndMonth ; $month++ ) {
-                       $nDays[] = nDaysMonth($month,$allEndYear);
-               }
-       }
-       $nDays[] = $allEndDay;
-       // $nTotalDays contains the total number of days over the time range
-       $nTotalDays = array_sum($nDays);
-
-       // ----- Display level (days, months or years) -----
-       // -------------------------------------------------
-       if ($flagShowMonths) {
-               if ( $nTotalDays<$wgTimelineTableMaxCells && $flagShowDays ) {
-                       $flagShowDays = true;
-                       $monthList = array( "January" , "February" , "March" , 
"April" , "May" ,
-                                                               "June" , "July" 
, "August" , "September" , "October" ,
-                                                               "November" , 
"December" );
-                       $nTotalCells = $nTotalDays;
-               } else {
-                       $flagShowDays = false;
-                       $monthList = array( "J", "F", "M", "A", "M", "J", "J", 
"A", "S", "O",
-                                                               "N", "D" );
-                       $nTotalCells = $nTotalMonths;
-               }
-       } else {
-               $nTotalCells = $nTotalYears;
-       }
-
-       // ----- Span values -----
-       // -----------------------
-       // Number of cells in each year
-       if ( $flagShowDays ) {
-               for ( $year=0 ; $year<$nTotalYears ; $year++ ) {
-                       $year1 = $year + $allStartYear;
-                       if ( $year1!=$allStartYear && $year1!=$allEndYear ) {
-                               $nCellsYear[$year] = nDaysYear($year1);
-                       }
-                       elseif ( $year1==$allStartYear && $year1==$allEndYear ) 
{
-                               if ( $allStartMonth == $allEndMonth ) {
-                                       $nCellsYear[$year] = $allEndDay - 
$allStartDay + 1;
-                               } else {
-                                       $nCellsYear[$year] = 
nDaysMonth($allStartMonth,$year1) -
-                                               $allStartDay + 1;
-                                       for ( $month=$allStartMonth+1 ; 
$month<=$allEndMonth ;
-                                                 $month++ ) {
-                                               if ( $month==$allEndMonth ) {
-                                                       $nCellsYear[$year] += 
$allEndDay;
-                                               } else {
-                                                       $nCellsYear[$year] += 
nDaysMonth($month,$year1);
-                                               }
-                                       }
-                               }
-                       }
-                       elseif ( $year1==$allStartYear ) {
-                               $nCellsYear[$year] = 
nDaysMonth($allStartMonth,$year1) -
-                                       $allStartDay + 1;
-                               for ( $month=$allStartMonth+1 ; $month<=12 ; 
$month++ ) {
-                                       if ( $month==$allEndMonth ) {
-                                               $nCellsYear[$year]+=$allEndDay;
-                                       } else {
-                                               $nCellsYear[$year] += 
nDaysMonth($month,$year1);
-                                       }
-                               }
-                       }
-                       elseif ( $year1==$allEndYear ) {
-                               $nCellsYear[$year]=0;
-                               for ( $month=1 ; $month<=$allEndMonth ; 
$month++ ) {
-                                       if ( $month==$allEndMonth ) {
-                                               $nCellsYear[$year] += 
$allEndDay;
-                                       } else {
-                                               $nCellsYear[$year] += 
nDaysMonth($month,$year1);
-                                       }
-                               }
-                       }
-               }
-       }
-       elseif($flagShowMonths) {
-               $nCellsYear[0] = 12 - $allStartMonth + 1;
-               for ( $year=1 ; $year<$nTotalYears-1 ; $year++) {
-                       $nCellsYear[$year]=12;
-               }
-               $nCellsYear[$nTotalYears-1] = $allEndMonth;
-       } else {
-               for ( $year=0 ; $year<$nTotalYears ; $year++ ) {
-                       $nCellsYear[$year] = 1;
-               }
-       }
-       // Number of cells in each month (only when displaying days)
-       if ( $flagShowDays ) {
-               for ( $year=0 ; $year<$nTotalYears ; $year++ ) {
-                       $year1 = $year + $allStartYear;
-                       if ( $year1 == $allStartYear ) {
-                               $monthStart = $allStartMonth;
-                       } else {
-                               $monthStart = 1;
-                       }
-                       if ( $year1 == $allEndYear ) {
-                               $monthEnd = $allEndMonth;
-                       } else {
-                               $monthEnd = 12;
-                       }
-                       for ( $month=$monthStart ; $month<=$monthEnd ; $month++ 
) {
-                               if ( $year==0 && $month==$allStartMonth ) {
-                                       if ( $allStartMonth==$allEndMonth &&
-                                                $allStartYear==$allEndYear ) {
-                                               $nCellsMonth[] = $allEndDay - 
$allStartDay + 1;
-                                       } else {
-                                               $nCellsMonth[] = 
nDaysMonth($month,$year1) -
-                                                       $allStartDay + 1;
-                                       }
-                               }
-                               elseif ( $year==$nTotalYears-1 && 
$month==$allEndMonth ) {
-                                       $nCellsMonth[] = $allEndDay;
-                               } else {
-                                       $nCellsMonth[] = 
nDaysMonth($month,$year1);
-                               }
-                       }
-               }
-       }
-
-       
//----------------------------------------------------------------------------
-       // Create the timeline: $timeline_str will contain the html code for 
the table
-       
//----------------------------------------------------------------------------
-
-       // Start the table
-       $timeline_str = "<table class=tl_table>$wgHTMLlr";
-       // Header: title line
-       $timeline_str .= "$wgHTMLtab<thead class=tl_header>\n";
-       $timeline_str .= "$wgHTMLtab$wgHTMLtab<tr>";
-       $timeline_str .= "$wgHTMLlr$wgHTMLtab$wgHTMLtab$wgHTMLtab";
-       $timeline_str .= '<th colspan="' . $nTotalCells;
-       $timeline_str .= '" class=tl_title>';
-       $timeline_str .= htmlspecialchars($title);
-       $timeline_str .= "</th>$wgHTMLlr";
-       $timeline_str .= "$wgHTMLtab$wgHTMLtab</tr>$wgHTMLlr";
-
-       // Header: Years timeline
-       $timeline_str .= "$wgHTMLtab$wgHTMLtab<tr>$wgHTMLlr";
-       for ( $year=0 ; $year<$nTotalYears ; $year++ ) {
-               $timeline_str .= "$wgHTMLtab$wgHTMLtab$wgHTMLtab";
-               $timeline_str .= '<th colspan="' . $nCellsYear[$year];
-               $timeline_str .= '" class=tl_years>';
-               $timeline_str .= ($year + $allStartYear);
-               $timeline_str .= "</th>$wgHTMLlr";
-       }
-       $timeline_str .= "$wgHTMLtab$wgHTMLtab</tr>$wgHTMLlr";
-       if($flagShowMonths) {
-               $timeline_str .= "$wgHTMLtab$wgHTMLtab<tr>$wgHTMLlr";
-               // Header: Months
-               $monthIdx = 0;
-               for ( $year=0 ; $year<$nTotalYears ; $year++ ) {
-                       $year1 = $year + $allStartYear;
-                       if ( $year1 == $allStartYear ) {
-                               $monthStart = $allStartMonth;
-                       } else {
-                               $monthStart = 1;
-                       }
-                       if ( $year1 == $allEndYear ) {
-                               $monthEnd = $allEndMonth;
-                       } else {
-                               $monthEnd = 12;
-                       }
-                       for ( $month=$monthStart ; $month<=$monthEnd ; $month++ 
) {
-                               $timeline_str .= 
"$wgHTMLtab$wgHTMLtab$wgHTMLtab";
-                               if ( $flagShowDays ) {
-                                       $timeline_str .= '<th colspan="';
-                                       $timeline_str .= 
$nCellsMonth[$monthIdx];
-                                       $timeline_str .= '" class=tl_months>';
-                                       $monthIdx++;
-                               } else {
-                                       $timeline_str .= '<th class=tl_months>';
-                               }
-                               $timeline_str .= $monthList[$month-1];
-                               $timeline_str .= "</th>$wgHTMLlr";
-                       }
-               }
-               $timeline_str .= "$wgHTMLtab$wgHTMLtab</tr>$wgHTMLlr";
-               // Header: Days
-               if ( $flagShowDays ) {
-                       $timeline_str .= "$wgHTMLtab$wgHTMLtab<tr>$wgHTMLlr";
-                       for ( $year=0 ; $year<$nTotalYears ; $year++ ) {
-                               $year1 = $year + $allStartYear;
-                               if ( $year1 == $allStartYear ) {
-                                       $monthStart = $allStartMonth;
-                               } else {
-                                       $monthStart = 1;
-                               }
-                               if ( $year1 == $allEndYear ) {
-                                       $monthEnd = $allEndMonth;
-                               } else {
-                                       $monthEnd = 12;
-                               }
-                               for ( $month=$monthStart ; $month<=$monthEnd ; 
$month++ ) {
-                                       if ($month==$allStartMonth && 
$year1==$allStartYear ) {
-                                               $dayStart = $allStartDay;
-                                       } else {
-                                               $dayStart = 1;
-                                       }
-                                       if ($month==$allEndMonth && 
$year1==$allEndYear ) {
-                                               $dayEnd = $allEndDay;
-                                       } else {
-                                               $dayEnd = 
nDaysMonth($month,$year1);
-                                       }
-                                       for ( $day=$dayStart ; $day<=$dayEnd ; 
$day++ ) {
-                                               $timeline_str .= 
"$wgHTMLtab$wgHTMLtab";
-                                               $timeline_str .= "$wgHTMLtab";
-                                               $timeline_str .= '<th 
class=tl_days>';
-                                               $timeline_str .= $day;
-                                               $timeline_str .= 
"</th>$wgHTMLlr";
-                                       }
-                               }
-                       }
-                       $timeline_str .= "$wgHTMLtab$wgHTMLtab</tr>$wgHTMLlr";
-               }
-       }
-       $timeline_str .= "$wgHTMLtab</thead>$wgHTMLlr";
-
-       // Footer
-       $timeline_str .= "$wgHTMLtab<tfoot class=tl_footer>";
-       $timeline_str .= "$wgHTMLlr$wgHTMLtab$wgHTMLtab<tr>";
-       $timeline_str .= "$wgHTMLlr$wgHTMLtab$wgHTMLtab$wgHTMLtab";
-       $timeline_str .= '<td colspan="' . $nTotalCells;
-       $timeline_str .= '" class=tl_foot>';
-       $timeline_str .= htmlspecialchars($footer);
-       $timeline_str .= "</td>$wgHTMLlr$wgHTMLtab$wgHTMLtab</tr>";
-       $timeline_str .= "$wgHTMLlr$wgHTMLtab</tfoot>$wgHTMLlr";
-
-       // Body: Events (display one event per row)
-       $timeline_str .= "$wgHTMLtab<tbody class=tl_body>$wgHTMLlr";
-       foreach ( $lines as $val ) {
-               $lineTmp = explode($wgTimelineTableFieldSeparator,$val);
-               $nFields = count($lineTmp);
-               $comment = "";
-               $cssStyle = "";
-               switch ($nFields)
-               {
-               case 3:
-                       list($eventStartDate, $eventEndDate, $text) = $lineTmp;
-                       break;
-               case 4:
-                       list($eventStartDate, $eventEndDate, $text, $comment) = 
$lineTmp;
-                       break;
-               case 5:
-                       list($eventStartDate, $eventEndDate, $text, $comment, 
$cssStyle) =
-                               $lineTmp;
-                       break;
-               default:
-                       continue;
-               }
-                       
-               // Parse the event dates and content
-               $startDateTmp = explode($wgTimelineTableDateSeparator,
-                                       $eventStartDate);
-               $endDateTmp = explode($wgTimelineTableDateSeparator,
-                                     $eventEndDate);
-               if ( $flagShowDays ) {
-                       if (count($startDateTmp) < 3) {
-                               continue;
-                       }
-                       list($eventStartYear, $eventStartMonth, $eventStartDay) 
=
-                               $startDateTmp;
-                       list($eventEndYear, $eventEndMonth, $eventEndDay) =
-                               $endDateTmp;
-                       $eventStartDay = (int)$eventStartDay;
-                       $eventEndDay = (int)$eventEndDay;
-                       $eventStartMonth = (int)$eventStartMonth;
-                       $eventEndMonth = (int)$eventEndMonth;
-               } elseif ($flagShowMonths) {
-                       if (count($startDateTmp) < 2) {
-                               continue;
-                       }
-                       list($eventStartYear, $eventStartMonth) = $startDateTmp;
-                       list($eventEndYear, $eventEndMonth) = $endDateTmp;
-                       $eventStartMonth = (int)$eventStartMonth;
-                       $eventEndMonth = (int)$eventEndMonth;
-               } else {
-                       $eventStartYear = $startDateTmp[0];
-                       $eventEndYear = $endDateTmp[0];
-               }
-               $eventStartYear = (int)$eventStartYear;
-               $eventEndYear = (int)$eventEndYear;
-
-               // Find the number of cells between the first column of the 
timeline
-               // table and the first cell of the event
-               if ( $flagShowDays ) {
-                       $nPreviousCells = 0;
-                       $curY = $allStartYear;
-                       $curM = $allStartMonth;
-                       $curD = $allStartDay;
-                       while ( ($curY!=$eventStartYear || 
$curM!=$eventStartMonth ||
-                               $curD!=$eventStartDay) && 
$nPreviousCells<$nTotalCells ) {
-                               if ( $curM==12 && 
$curD==nDaysMonth($curM,$curY) ) {
-                                       $curM = 1;
-                                       $curD = 1;
-                                       $curY++;
-                               }
-                               elseif ( $curD==nDaysMonth($curM,$curY) ) {
-                                       $curM++;
-                                       $curD = 1;
-                               } else {
-                                       $curD++;
-                               }
-                               $nPreviousCells++;
-                       }
-                       $nEventCells = 1;
-                       // Find the length of the event (in days)
-                       while ( $curY!=$eventEndYear || $curM!=$eventEndMonth ||
-                               $curD!=$eventEndDay ) {
-                               if ( $curM==12 && 
$curD==nDaysMonth($curM,$curY) ) {
-                                       $curM = 1;
-                                       $curD = 1;
-                                       $curY++;
-                               }
-                               elseif ( $curD==nDaysMonth($curM,$curY) ) {
-                                       $curM++;
-                                       $curD = 1;
-                               } else {
-                                       $curD++;
-                               }
-                               $nEventCells++;
-                       }
-               }
-               elseif($flagShowMonths) { // if ( $flagShowDays )
-                       // $nPreviousCells = 0;
-                       // $curY = $allStartYear;
-                       // $curM = $allStartMonth;
-                       // while ( $curY!=$eventStartYear || 
$curM!=$eventStartMonth )
-                       // {
-                       //      if ( $curM==12 )
-                       //      {
-                       //              $curM = 1;
-                       //              $curY++;
-                       //      }
-                       //      else
-                       //      {
-                       //              $curM++;
-                       //      }
-                       //      $nPreviousCells++;
-                       // }
-                       $nPreviousCells = array_sum(array_slice($nMonths, 0,
-                           $eventStartYear-$allStartYear));
-                       $nPreviousCells += $eventStartMonth - 1;
-                       if ( $eventStartYear==$allStartYear ) {
-                               $nPreviousCells -= $allStartMonth;
-                       } else {
-                               $nPreviousCells -= 1;
-                       }
-                       if ( $nPreviousCells!=0 ) {
-                               $nPreviousCells++;
-                       }
-                       // Find the length of the event (in months)
-                       $nEventCells = 12 - $eventStartMonth + 1;
-                       $nEventCells = $nEventCells + $eventEndMonth;
-                       $nEventCells = $nEventCells + 
12*($eventEndYear-$eventStartYear-1);
-               } else {
-                       $nPreviousCells = $eventStartYear - $allStartYear;
-                       $nEventCells = $eventEndYear - $eventStartYear + 1;
-               }
-
-               // Define the number of cells between the end of the event and 
the end
-               // of the timeline table
-               $nRemainingCells = $nTotalCells - $nPreviousCells - 
$nEventCells;
-
-               // Merge the cells before the event into a 'freetime' cell
-               $timeline_str .= "$wgHTMLtab$wgHTMLtab<tr>$wgHTMLlr";
-               if ( $nPreviousCells > 0 ) {
-                       $timeline_str .= "$wgHTMLtab$wgHTMLtab$wgHTMLtab";
-                       $timeline_str .= '<td colspan="' . $nPreviousCells;
-                       $timeline_str .= '" class=tl_freetime></td>';
-                       $timeline_str .= "$wgHTMLlr";
-               }
-               // Create the event cell
-               $timeline_str .= "$wgHTMLtab$wgHTMLtab$wgHTMLtab";
-               $timeline_str .= '<td colspan="' . $nEventCells;
-               $timeline_str .= '" class=tl_event ';
-               if ( strcmp(trim($cssStyle),"") ) {
-                       $timeline_str .= 'style="';
-                       $timeline_str .= htmlspecialchars($cssStyle) . '"';
-               }
-               $timeline_str .= ">";
-               if( !defined( 'MEDIAWIKI' ) ) {
-                       $timeline_str .= htmlspecialchars($text);
-               } else {
-                       $timeline_str .= $parser->recursiveTagParse($text);
-               }
-               if ( strcmp(trim($comment),"") ) {
-                       $timeline_str .= '<br />(';
-                       if( !defined( 'MEDIAWIKI' ) ) {
-                               $timeline_str .= htmlspecialchars($comment);
-                       } else {
-                               $parsed_comment = 
$parser->recursiveTagParse($comment);
-                               $timeline_str .= $parsed_comment;
-                       }
-                       $timeline_str .= ')';
-               }
-               $timeline_str .= "</td>$wgHTMLlr";
-               // Merge the cells after the event into a 'freetime' cell
-               if ( $nRemainingCells > 0 ) {
-                       $timeline_str .= "$wgHTMLtab$wgHTMLtab$wgHTMLtab";
-                       $timeline_str .= '<td colspan="' . $nRemainingCells;
-                       $timeline_str .= '" class=tl_freetime></td>';
-                       $timeline_str .= "$wgHTMLlr";
-               }
-               $timeline_str .= "$wgHTMLtab$wgHTMLtab</tr>$wgHTMLlr";
-       }
-       $timeline_str .= "$wgHTMLtab</tbody>$wgHTMLlr";
-
-       // Finish table
-       $timeline_str .= "</table><br />$wgHTMLlr";
-
-       // Define the html code as a marker, then change it back to text in
-       // 'efTimelineAfterTidy'. This is done to prevent the html code from 
being
-       // modified afterwards.
-       global $markerList;
-       $makercount = count($markerList);
-       $marker = "xx-marker".$makercount."-xx";
-       $markerList[$makercount] = $timeline_str;
-       return $marker;
-}
-
-function efTimelineTableAfterTidy(&$parser, &$text) {
-       // find markers in $text
-       // replace markers with actual output
-       global $markerList;
-       for ($i = 0; $i<count($markerList); $i++)
-       $text = preg_replace('/xx-marker'.$i.'-xx/',$markerList[$i],$text);
-       return true;
-}
-
-// TODO
-/*
-       - Options
-       - Check execution time
-       - Check inputs (date order, etc.)
-*/
-
diff --git a/TimelineTable.i18n.php b/TimelineTable.i18n.php
deleted file mode 100755
index bd575db..0000000
--- a/TimelineTable.i18n.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-/**
- * This is a backwards-compatibility shim, generated by:
- * 
https://git.wikimedia.org/blob/mediawiki%2Fcore.git/HEAD/maintenance%2FgenerateJsonI18n.php
- *
- * Beginning with MediaWiki 1.23, translation strings are stored in json files,
- * and the EXTENSION.i18n.php file only exists to provide compatibility with
- * older releases of MediaWiki. For more information about this migration, see:
- * https://www.mediawiki.org/wiki/Requests_for_comment/Localisation_format
- *
- * This shim maintains compatibility back to MediaWiki 1.17.
- */
-$messages = array();
-if ( !function_exists( 'wfJsonI18nShime9fce3e1e1c94520' ) ) {
-       function wfJsonI18nShime9fce3e1e1c94520( $cache, $code, &$cachedData ) {
-               $codeSequence = array_merge( array( $code ), 
$cachedData['fallbackSequence'] );
-               foreach ( $codeSequence as $csCode ) {
-                       $fileName = dirname( __FILE__ ) . "/i18n/$csCode.json";
-                       if ( is_readable( $fileName ) ) {
-                               $data = FormatJson::decode( file_get_contents( 
$fileName ), true );
-                               foreach ( array_keys( $data ) as $key ) {
-                                       if ( $key === '' || $key[0] === '@' ) {
-                                               unset( $data[$key] );
-                                       }
-                               }
-                               $cachedData['messages'] = array_merge( $data, 
$cachedData['messages'] );
-                       }
-
-                       $cachedData['deps'][] = new FileDependency( $fileName );
-               }
-               return true;
-       }
-
-       $GLOBALS['wgHooks']['LocalisationCacheRecache'][] = 
'wfJsonI18nShime9fce3e1e1c94520';
-}
diff --git a/TimelineTable.php b/TimelineTable.php
index 80501c9..c12746a 100755
--- a/TimelineTable.php
+++ b/TimelineTable.php
@@ -4,7 +4,7 @@
  * table.
  *
  * To activate this extension, add the following into your LocalSettings.php 
file:
- * require_once('$IP/extensions/timelinetable.php');
+ * require_once('$IP/extensions/TimelineTable.php');
  *
  * @ingroup Extensions
  * @author Thibault Marin
@@ -12,8 +12,27 @@
  * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 
2.0 or later
  *
  * @revision
- * 1.7.1 -> 1.8.0
+ * 2.0 -> 2.1
+ *  Fix internationalization, error messages
+ *  Add support for time
+ *  Vertical table option
+ *  Requires MediaWiki 1.23 or later
+ * 1.8.1 -> 2.0
+ *  Re-organize code into classes
+ *  More flexible input format 
([http://www.php.net/manual/en/datetime.formats.php])
+ *  Allow multiple events per line (must be in increasing order and cannot 
overlap)
+ *  New option format to control headers display (e.g "headers=Y/M 
footers=D-D")
+ *  New selection of depth of table (e.g. <timelinetable ... depth=day ...>)
+ *  Support for week level (display week number in year, week dates in tooltip)
+ *  Tried to maintain backwards compatibility (except for the
+ *    $wgTimelineTableMaxCells option which is now fully deprecated)
+ * 1.8 -> 1.8.1
+ *  Option to show day names (daynames=1)
+ *  Option to hide months (nomonths=1)
  *  Now using JSON i18n.
+ * 1.7.1 -> 1.8
+ *  Cleanup for mediawiki review
+ *  Option to hide years (noyears=1)
  * 1.7 -> 1.7.1
  *  Rename tag to avoid conflict.
  * 1.6 -> 1.7
@@ -30,38 +49,71 @@
  * Protect against register_globals vulnerabilities.
  * This line must be present before any global variable is referenced.
  */
-if( !defined( 'MEDIAWIKI' ) )
-{
+if ( !defined( 'MEDIAWIKI' ) ) {
        echo( "This is an extension to the MediaWiki package and cannot be run 
standalone.\n" );
        die( -1 );
 }
 
-// Extension credits that will show up on Special:Version
-$wgExtensionCredits['validextensionclass'][] = array(
-       'name'         => __FILE__,
-       'version'      => '1.8.0',
-       'author'       => 'Thibault Marin',
-       'url'          => 
'http://www.mediawiki.org/wiki/Extension:TimelineTable',
-       'description'  => 'Create a table containing a timeline'
+/**
+ * Extension credits that will show up on Special:Version
+ */
+$wgExtensionCredits['parserhook'][] = array(
+       'name' => 'TimelineTable',
+       'path' => __FILE__,
+       'version' => '2.1',
+       'author' => 'Thibault Marin',
+       'url' => 'http://www.mediawiki.org/wiki/Extension:TimelineTable',
+       'descriptionmsg' => 'timelinetable-desc'
 );
 
-//Avoid unstubbing $wgParser on setHook() too early on modern (1.12+) MW
-//versions, as per r35980
-if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) {
-       $wgHooks['ParserFirstCallInit'][] = 'efTimelineTableParserInit';
-       $wgHooks['ParserAfterTidy'][]='efTimelineTableAfterTidy';
-       $wgExtensionFunctions[] = 'efTimelineTableParserInit';
-} else { // Otherwise do things the old fashioned way
-       $wgHooks['ParserAfterTidy'][]='efTimelineTableAfterTidy';
-       $wgExtensionFunctions[] = 'efTimelineTableParserInit';
-}
+/**
+ * Extension class
+ */
+$wgAutoloadClasses['TimelineTableHooks'] =
+       dirname( __FILE__ ) . '/TimelineTable.Hooks.php';
+$wgAutoloadClasses['TimelineTableEvent'] =
+       dirname( __FILE__ ) . '/TimelineTable.Event.php';
+$wgAutoloadClasses['TimelineTableDepthDesc'] =
+       dirname( __FILE__ ) . '/TimelineTable.DateDiffHelper.php';
+$wgAutoloadClasses['TimelineTableDateDiffHelper'] =
+       dirname( __FILE__ ) . '/TimelineTable.DateDiffHelper.php';
+$wgAutoloadClasses['TimelineTableTable'] =
+       dirname( __FILE__ ) . '/TimelineTable.Table.php';
 
-function efTimelineTableParserInit() {
-       global $wgParser;
-       $wgParser->setHook( 'timelinetable', 'efTimelineTableRender' );
-       return true;
-}
+/**
+ * Register hooks
+ */
+$wgHooks['ParserFirstCallInit'][] =
+       'TimelineTableHooks::efTimelineTableParserInit';
+$wgHooks['ParserAfterTidy'][] = 'TimelineTableHooks::efTimelineTableAfterTidy';
 
+/**
+ * Internationalization
+ */
 $wgMessagesDirs['TimelineTable'] = __DIR__ . '/i18n';
-$wgExtensionMessagesFiles['TimelineTable'] = dirname( __FILE__ ) . 
'/TimelineTable.i18n.php';
-require_once dirname(__FILE__) . '/TimelineTable.body.php';
+
+/**
+ * Parameters (modify in LocalSettings.php)
+ */
+
+// Separator for parsing lines of the input.
+$wgTimelineTableLineSeparator = PHP_EOL; //"\n";
+
+// Separator for parsing fields of a single event
+// (default "|" e.g. "date-start|date-end|text|style").
+$wgTimelineTableFieldSeparator = "|";
+
+// Separator for events within the same line
+$wgTimelineTableEventSeparator = "#";
+
+// Separator for parsing the date of an event (old style events, for backward
+// compatibility only).
+// (default: "-" e.g. "MM-DD-YYYY")
+$wgTimelineTableDateSeparator = "-";
+
+// Set this flag to true to abbreviate month names (see 'strftime' doc)
+$wgTimelineTableAbbrMonth = false;
+
+// Length of month/day name in compact mode (e.g. when displaying many years)
+$wgTimelineTableShortMonthLen = 1;
+
diff --git a/TimelineTable.style.css b/TimelineTable.style.css
new file mode 100644
index 0000000..e4214e7
--- /dev/null
+++ b/TimelineTable.style.css
@@ -0,0 +1,102 @@
+/* ***** ***** TimelineTable ***** ***** */
+table.tl_table {
+       border-width: thin;
+       border-spacing: 2px;
+       border-style: outset;
+       border-color: black;
+       border-collapse: separate;
+       background-color: white;
+       border-radius: 9px;
+}
+
+th.tl_title {
+       text-transform: uppercase;
+       text-align: center;
+       border-width: 1px;
+       padding: 1px;
+       border-style: outset;
+       border-color: blue;
+       background-color: rgb(243, 248, 252);
+       border-radius: 9px;
+}
+
+th.tl_years {
+       text-align: center;
+       font-style: italic;
+       border-width: 1px;
+       padding: 1px;
+       border-style: outset;
+       border-color: blue;
+       background-color: rgb(223, 228, 252);
+       border-radius: 4px;
+}
+
+th.tl_months {
+       text-align: center;
+       border-width: 1px;
+       padding: 1px;
+       border-style: outset;
+       border-color: blue;
+       background-color: rgb(243, 248, 252);
+       border-radius: 2px;
+}
+
+th.tl_days {
+       text-align: center;
+       border-width: 1px;
+       padding: 1px;
+       border-style: outset;
+       border-color: blue;
+       background-color: rgb(243, 248, 252);
+       border-radius: 2px;
+}
+
+th.tl_weeks {
+       text-align: center;
+       border-width: 1px;
+       padding: 1px;
+       border-style: outset;
+       border-color: blue;
+       background-color: rgb(243, 248, 252);
+       border-radius: 2px;
+}
+
+td.tl_freetime {
+       background-color: rgb(187, 210, 236);
+       border-width: 1px;
+       border-color: black;
+       border-style: inset;
+       border-radius: 7px;
+}
+
+td.tl_event {
+       text-align: center;
+       padding: 1px;
+       background-color: rgb(61, 114, 194);
+       border-width: 1px;
+       border-color: white;
+       border-style: inset;
+       color: white;
+       border-radius: 7px;
+       white-space: normal
+}
+
+td.tl_foot {
+       text-align: center;
+       padding: 1px;
+       background-color: rgb(243, 248, 252);
+       border-width: 1px;
+       border-color: blue;
+       border-style: ridge;
+       color: gray;
+       border-radius: 9px;
+}
+
+thead.tl_header {
+}
+
+tbody.tl_body {
+}
+
+tfoot.tl_footer {
+}
diff --git a/i18n/en.json b/i18n/en.json
index fd77382..e54d089 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1,17 +1,16 @@
 {
     "@metadata": {
-        "authors": []
+        "authors": [
+            "thibault marin"
+        ]
     },
-    "january": "January",
-    "february": "February",
-    "march": "March",
-    "april": "April",
-    "may": "May",
-    "june": "June",
-    "july": "July",
-    "august": "August",
-    "september": "September",
-    "october": "October",
-    "november": "November",
-    "december": "December"
+    "timelinetable-desc": "Create a table containing a timeline",
+    "timelinetable-error-parsestart": "Error parsing event start: $1.",
+    "timelinetable-error-parseend": "Error parsing event end: $1.",
+    "timelinetable-error-parseargs": "Error parsing event dates: $1.",
+    "timelinetable-error-overlap": "Events on the same line overlap: $1.",
+    "timelinetable-error-free": "Error getting leading/trailing freetime block 
at line $1.",
+    "timelinetable-error-depth": "Could not obtain depth parameter.",
+    "timelinetable-error-hf": "Error reading headers/footers: $1=$2.",
+    "timelinetable-error-negdate": "End date should be later than start date: 
$1."
 }
diff --git a/i18n/fr.json b/i18n/fr.json
deleted file mode 100644
index f4f8ee8..0000000
--- a/i18n/fr.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-    "@metadata": {
-        "authors": []
-    },
-    "january": "Janvier",
-    "february": "Fevrier",
-    "march": "Mars",
-    "april": "Avril",
-    "may": "Mai",
-    "june": "Juin",
-    "july": "Juilet",
-    "august": "Aout",
-    "september": "Septembre",
-    "october": "Octobre",
-    "november": "Novembre",
-    "december": "Decembre"
-}
diff --git a/i18n/qqq.json b/i18n/qqq.json
new file mode 100644
index 0000000..05ae768
--- /dev/null
+++ b/i18n/qqq.json
@@ -0,0 +1,16 @@
+{
+    "@metadata": {
+        "authors": [
+            "thibault marin"
+        ]
+    },
+    "timelinetable-desc": "{{desc}}",
+    "timelinetable-error-parsestart": "This is an error message shown when the 
start of an event is not a proper date entry (parsing failed, $1 is the 
exception message returned by DateTime constructor).",
+    "timelinetable-error-parseend": "This is an error message shown when the 
end of an event is not a proper date entry (parsing failed, $1 is the exception 
message returned by DateTime constructor).",
+    "timelinetable-error-parseargs": "This is an error message shown when the 
start or end date could not be found.",
+    "timelinetable-error-overlap": "This is an error message shown when two 
events in the same table line overlap ($1 is the input with overlapping 
dates).",
+    "timelinetable-error-free": "This is an error message shown when something 
went wrong during input parsing ($1 is index of the erroneous row in the input 
table).",
+    "timelinetable-error-depth": "This is an error message shown when the 
depth argument is missing and could not be guessed from the first date in the 
table.",
+    "timelinetable-error-hf": "This is an error message shown when the 
headers/footers option could not be parsed ($1 is the parameter name, $2 is the 
parameter value).",
+    "timelinetable-error-negdate": "This is an error message shown when an 
event has a start date later than the end date ($1 is the erroneous input 
line)."
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/38485
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I742c54a128ae542ae0d0f4e59e315157819eaee3
Gerrit-PatchSet: 18
Gerrit-Project: mediawiki/extensions/TimelineTable
Gerrit-Branch: master
Gerrit-Owner: thibaultmarin <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Raimond Spekking <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Yaron Koren <[email protected]>
Gerrit-Reviewer: Yurik <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
Gerrit-Reviewer: thibaultmarin <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to