| Current File : /home/jvzmxxx/wiki/extensions/InteractiveTimeline/InteractiveTimeline.body.php |
<?php
/**
* This file is part of the InteractiveTimeline Extension to MediaWiki
* https://www.mediawiki.org/wiki/Extension:InteractiveTimeline
*
* @section LICENSE
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Extensions
* @author Chris Page <chris@starforge.co.uk>
* @copyright Copyright © 2014-2016 Chris Page
* @license GNU General Public Licence 2.0 or later
*/
class InteractiveTimeline {
/**
* Determine whether the specified argument is a valid boolean value.
*
* @param string $arg The value to check.
* @param boolean $valid A reference to a variable that will be set to true if
* the argument is valid, false if it is not.
* @return string The validated value or null if validation failed
*/
public static function validBoolean( $arg, &$valid ) {
$arg = trim( $arg );
if ( preg_match( "/^(true|false)$/i", $arg, $matches ) ) {
$valid = true;
return (strtolower($matches[0]) === 'true');
}
$valid = false;
return null;
}
/**
* Determine whether the specified argument represents a valid css size.
*
* @param string $arg The value to check.
* @param boolean $valid A reference to a variable that will be set to true if
* the argument is valid, false if it is not.
* @return string The validated value or null if validation failed
*/
public static function validCSSSize( $arg, &$valid ) {
$arg = trim( $arg );
if ( preg_match( "/^(-?\d+(\.\d+)?(%|cm|mm|in|em|ex|pt|pc|px)?)$/i", $arg, $matches ) ) {
$valid = true;
return $matches[0];
}
$valid = false;
return null;
}
/**
* Determine whether the specified argument is a valid date with optional time.
*
* @param string $arg The value to check.
* @param boolean $valid A reference to a variable that will be set to true if
* the argument is valid, false if it is not.
* @return string The validated value or null if validation failed
*/
public static function validDatetime( $arg, &$valid ) {
$arg = trim( $arg );
// Note that this is not a full IS8601 checker - it does not support periods,
// ordinal dates, or decimals in the time section. It also only supports hours
// in the range 00-23 (ISO8601 allows 24) as *none* of the Date.parse() implementations
// in any browser I've checked will parse '24:00:00' correctly.
// $matches[N] 1 2 3 4 5 6 7 8 9
// YYYY - MM - DD(restricted) T HH :MM :SS Z +/- HH :MM
if ( preg_match( "/^(\d{4})-?(0[1-9]|1[0-2])-?(0[1-9]|[12]\d|3[01])(?:[ T]([01]\d|2[0-3])(?::([0-5]\d))?(?::([0-5]\d))?([-+Z])(0\d|1[0-4])?(?::?([0-5]\d))?)?$/", $arg, $matches)){
$valid = true;
// Date must be specified in full
$date = $matches[1] . "-" . $matches[2] . "-" . $matches[3] . "T";
// Hour, minute, and second are optional and should default to zero
$date .= ( isset( $matches[4] ) && $matches[4] !== '' ? $matches[4] : "00" ) . ":" .
( isset( $matches[5] ) && $matches[5] !== '' ? $matches[5] : "00" ) . ":" .
( isset( $matches[6] ) && $matches[6] !== '' ? $matches[6] : "00" );
// If a timezone is set, use it
if ( isset( $matches[7] ) && ( $matches[7] === 'Z' || isset( $matches[8] ) ) ) {
$date .= $matches[7];
// If the time is not in UTC, and offset must be specified.
if ( $matches[7] !== 'Z' ) {
$date .= $matches[8];
// Minute part of time offset is optional
$date .= ":" . ( isset( $matches[9] ) && $matches[9] !== '' ? $matches[9] : "00" );
}
// Otherwise default to UTC explicitly (if not included the browser
// will use local time)
} else {
$date .= "Z";
}
return $date;
}
$valid = false;
return null;
}
/**
* Determine whether the specified argument is a valid integer value.
*
* @param string $arg The value to check.
* @param boolean $valid A reference to a variable that will be set to true if
* the argument is valid, false if it is not.
* @return string The validated value or null if validation failed
*/
public static function validInteger( $arg, &$valid ) {
$arg = trim( $arg );
if ( preg_match( "/^\d+$/", $arg, $matches) ) {
$valid = true;
return intval( $matches[0] );
}
$valid = false;
return null;
}
/**
* Determine whether the specified argument is a locale supported by
* the CHAP Timeline code. This expects timeline-locales.js to be loaded.
*
* @param string $arg The value to check.
* @param boolean $valid A reference to a variable that will be set to true if
* the argument is valid, false if it is not.
* @return string The validated value or null if validation failed
*/
public static function validLocale( $arg, &$valid ) {
$arg = trim( $arg );
if ( preg_match( "/^(ca(?:_ES)?|en(?:_US|_UK)?|nl(?:_NL|_BE)?|fi(?:_FI)?|fr(?:_FR|_BE|_CA)?|de(?:_DE|_CH)?|da(?:_DK)?|ru(?:_RU)?|es(?:_ES)?|tr(?:_TR)?)$/", $arg, $matches ) ) {
$valid = true;
return $matches[0];
}
$valid = false;
return null;
}
/**
* Determine whether the specified argument is a valid CHAP Timeline
* style value. This only accepts the built-in styles and it does
* not support custom styles.
*
* @param string $arg The value to check.
* @param boolean $valid A reference to a variable that will be set to true if
* the argument is valid, false if it is not.
* @return string The validated value or null if validation failed
*/
public static function validTimestyle( $arg, &$valid ) {
$arg = trim( $arg );
if ( preg_match( "/^(box|dot)$/i", $arg, $matches ) ) {
$valid = true;
return strtolower( $matches[0] );
}
$valid = false;
return null;
}
/**
* Validate the argument with the specified name, and store the validated
* result in the options array. If no value has been set for the named
* argument, or the value specified for the argument is not valid, this
* will not update the options array.
*
* @param array $options A reference to an array to store the validated
* options in.
* @param array $args A reference to an array containing the arguments
* supplied by the tag.
* @param string $name The name of the argument to validate. This may
* contain mixed case: even though MediaWiki will
* lowercase names in the args array, the options may
* require mixed case. The name supplied is converted
* to lowercase to look up the value in args, and
* used 'as is' when setting the value in options.
* @param string $type The type of validation to apply to the value.
* Supported values are 'boolean', 'csssize',
* 'datetime', 'integer', 'locale', and 'timestyle'.
*/
public static function validateArgument( &$options, &$args, $name, $type) {
// All mediawiki args are lowercase, but the options may be lowerCamelCase.
$lcname = strtolower( $name );
// Only bother trying to do anything if the user has set the option
if ( isset( $args[$lcname] ) ) {
$value = null;
// Use the appropriate validator to check the option
switch ( $type ) {
case "boolean": $value = self::validBoolean( $args[$lcname], $valid );
break;
case "csssize": $value = self::validCSSSize( $args[$lcname], $valid );
break;
case "datetime": $value = self::validDatetime( $args[$lcname], $valid );
break;
case "integer": $value = self::validInteger( $args[$lcname], $valid );
break;
case "locale": $value = self::validLocale( $args[$lcname], $valid );
break;
case "timestyle": $value = self::validTimestyle( $args[$lcname], $valid );
break;
}
// If a valid value was obtained, store it.
if ( isset( $value ) && $valid ) {
$options[$name] = $value;
}
}
}
/**
* Construct the options to set for the timeline. This validates the arguments
* set in the itimeline tag and stores valid arguments in the options array.
*
* @param array $options A reference to an array to store the validated options.
* @param array $args A reference to an array containing the arguments
* supplied by the tag.
*/
public static function buildTimelineOptions( &$options, &$args ) {
// Establish any defaults that differ from timeline's own defaults
$options['selectable']= false; // No point in making timeline entries selectable
$options['timeChangeable'] = false; // probably redundant as editable is false, but be sure.
// And now check any user-specified arguments
self::validateArgument( $options, $args, 'animate', 'boolean' );
self::validateArgument( $options, $args, 'animateZoom', 'boolean' );
self::validateArgument( $options, $args, 'axisOnTop', 'boolean' );
self::validateArgument( $options, $args, 'cluster', 'boolean' );
self::validateArgument( $options, $args, 'end', 'datetime' );
self::validateArgument( $options, $args, 'eventMargin', 'integer' );
self::validateArgument( $options, $args, 'eventMarginAxis', 'integer' );
self::validateArgument( $options, $args, 'groupsOnRight', 'boolean' );
self::validateArgument( $options, $args, 'groupsWidth', 'csssize' );
self::validateArgument( $options, $args, 'groupMinheight', 'integer' );
self::validateArgument( $options, $args, 'height', 'csssize' );
self::validateArgument( $options, $args, 'locale', 'locale' );
self::validateArgument( $options, $args, 'max', 'datetime' );
self::validateArgument( $options, $args, 'min', 'datetime' );
self::validateArgument( $options, $args, 'minHeight', 'integer' );
self::validateArgument( $options, $args, 'moveable', 'boolean' );
self::validateArgument( $options, $args, 'stackEvents', 'boolean' );
self::validateArgument( $options, $args, 'start', 'datetime' );
self::validateArgument( $options, $args, 'style', 'timestyle' );
self::validateArgument( $options, $args, 'showCurrentTime', 'boolean' );
self::validateArgument( $options, $args, 'showMajorLabels', 'boolean' );
self::validateArgument( $options, $args, 'showMinorLabels', 'boolean' );
self::validateArgument( $options, $args, 'showNavigation', 'boolean' );
self::validateArgument( $options, $args, 'width', 'csssize' );
self::validateArgument( $options, $args, 'zoomable', 'boolean' );
self::validateArgument( $options, $args, 'zoomMax', 'integer' );
self::validateArgument( $options, $args, 'zoomMin', 'integer' );
}
/**
* Given a timeline event definition line, ensure that it contains the required
* parts (start date and event description, or start, end, and description) and
* that the start and possibly end values are valid dates. This generates the
* divs that allow easier extraction of the event information in javascript.
*
* @param string $line The line containing the timeline event to validate.
* @return string The divs describing the event if the line is valid, null otherwise.
*/
public static function buildTimelineLine( $line ) {
// Sections are delimited by |
$parts = explode( "|", trim( $line ) );
// Lines *must* consist of two or three parts: a date or interval, an optional group, and the text
if ( count( $parts ) == 2 || count( $parts ) == 3 ) {
// The first part might be an interval
$dates = explode( "/", $parts[0]);
// The first part must be a datetime string, or the whole line is rejected
$value = self::validDatetime( $dates[0], $valid );
if ( $valid ) {
$output = Html::element( 'div', array( 'class' => 'itl-start' ), $value );
// If there are two dates, the second is the end date
if ( count( $dates ) == 2) {
$value = self::validDatetime( $dates[1], $valid );
if ( $valid ) {
$output .= Html::element( 'div', array( 'class' => 'itl-end' ), $value );
}
}
// Two parts implies date and text...
if ( count( $parts ) == 2 ) {
$body = $parts[1];
// While three is date, group, and text
} else {
$body = $parts[2];
$output .= Html::rawelement( 'div', array( 'class' => 'itl-group' ), $parts[1] );
}
$output .= Html::rawelement( 'div', array( 'class' => 'itl-body' ), $body );
return $output;
}
}
return null;
}
/**
* Validate the timeline events in the itimeline tag body, and produce a series
* of divs containing the data, one per event, for easier parsing in javascript.
*
* @param string $input The content of the tag.
* @param array $args The attributes of the tag.
* @param Parser $parser Parser instance available to render wikitext into html,
* or parser methods.
* @param PPFrame $frame Can be used to see what template arguments ({{{1}}})
* this hook was used with.
* @return string The itl-event list to use inside the itimeline tag.
*/
public static function buildTimelineEvents( $input, $parser, $frame ) {
// First expand any templates/transclusions
$body = $parser->recursiveTagParse( $input, $frame );
// now split on lines
$lines = explode( "\n", $body );
$output = "";
// Convert the lines to validated output
foreach ( $lines as $line ) {
$linedata = self::buildTimelineLine( $line );
if ( isset( $linedata ) ) {
$output .= Html::rawelement( 'div', array( 'class' => 'itl-event' ), $linedata ) . "\n";
}
}
return $output;
}
/**
* Parser hook handler for <itimeline>
*
* @param string $input The content of the tag.
* @param array $args The attributes of the tag.
* @param Parser $parser Parser instance available to render wikitext into html,
* or parser methods.
* @param PPFrame $frame Can be used to see what template arguments ({{{1}}})
* this hook was used with.
* @return string HTML to insert in the page.
*/
public static function parserHook( $input, $args = array(), $parser, $frame ) {
global $wgOut;
static $tlNumber = 0;
$elemID = 'itimeline-' . ++$tlNumber;
$options = array();
self::buildTimelineOptions( $options, $args );
$timelinedata = self::buildTimelineEvents( $input, $parser, $frame );
// Store the timeline setup
$timelinedata .= Html::rawelement( 'div', array( 'class' => 'itl-config' ), FormatJson::encode( $options ) );
return Html::rawelement( 'div', array( 'id' => $elemID, 'class' => 'itimeline' ), $timelinedata );
}
/**
* Add the Interactive Timeline resource modules to the load queue of all pages.
*
* @param OutputPage $out Instance of OutputPage.
* @param Skin $skin The skin instance.
* @return boolean Always returns true.
*/
public static function onBeforePageDisplay( &$out, &$skin ) {
// Ensure that the required resource modules are loaded
$out->addModules( 'ext.InteractiveTimeline.loader' );
$out->addModules( 'ext.InteractiveTimeline.timeline' );
return true;
}
/**
* Register the <itimeline> tag with the Parser.
*
* @param $parser Parser instance of Parser
* @return boolean Always returns true
*/
public static function onParserFirstCallInit( &$parser ) {
// Adds the <itimeline>...</itimeline> tag to the parser.
$parser -> setHook( 'itimeline', 'InteractiveTimeline::parserHook' );
return true;
}
}