diff options
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/includes/RecurringEvents.php')
-rw-r--r-- | www/wiki/extensions/SemanticMediaWiki/includes/RecurringEvents.php | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/includes/RecurringEvents.php b/www/wiki/extensions/SemanticMediaWiki/includes/RecurringEvents.php new file mode 100644 index 00000000..1a171fec --- /dev/null +++ b/www/wiki/extensions/SemanticMediaWiki/includes/RecurringEvents.php @@ -0,0 +1,374 @@ +<?php + +namespace SMW; + +use SMWDITime; + +/** + * TODO This class needs some real refactoring! + * + * @private This class should not be instantiated directly. + * + * This class determines recurring events based on invoked parameters + * + * @see https://www.semantic-mediawiki.org/wiki/Help:Recurring_events + * + * @license GNU GPL v2+ + * @since 1.9 + * + * @author Yaron Koren + * @author Jeroen De Dauw + * @author mwjames + */ +class RecurringEvents { + + /** + * Defines the property used + */ + private $property = null; + + /** + * Defines the dates + */ + private $dates = []; + + /** + * Defines remaining / unused parameters + */ + private $parameters = []; + + /** + * Defines errors + */ + private $errors = []; + + /** + * @var integer + */ + private $defaultNumRecurringEvents = 25; + + /** + * @var integer + */ + private $maxNumRecurringEvents = 25; + + /** + * @since 2.5 + * + * @param integer $defaultNumRecurringEvents + */ + public function setDefaultNumRecurringEvents( $defaultNumRecurringEvents ) { + $this->defaultNumRecurringEvents = $defaultNumRecurringEvents; + } + + /** + * @since 2.5 + * + * @param integer $maxNumRecurringEvents + */ + public function setMaxNumRecurringEvents( $maxNumRecurringEvents ) { + $this->maxNumRecurringEvents = $maxNumRecurringEvents; + } + + /** + * Returns property used + * + * @since 1.9 + * + * @return string + */ + public function getProperty() { + return $this->property; + } + + /** + * Returns dates + * + * @since 1.9 + * + * @return array + */ + public function getDates() { + return $this->dates; + } + + /** + * Returns unused parameters + * + * @since 1.9 + * + * @return array + */ + public function getParameters() { + return $this->parameters; + } + + /** + * Returns errors + * + * @since 1.9 + * + * @return array + */ + public function getErrors() { + return $this->errors; + } + + /** + * Set error + * + * @since 1.9 + * + * @return mixed + */ + private function setError( $error ) { + $this->errors = array_merge( $error, $this->errors ); + } + + /** + * Returns the "Julian day" value from an object of type + * SMWTimeValue. + */ + public function getJulianDay( $dateDataValue ) { + if ( is_null( $dateDataValue ) ) { + return null; + } + $dateDataItem = $dateDataValue->getDataItem(); + // This might have returned an 'SMWDIError' object. + if ( $dateDataItem instanceof SMWDITime ) { + return $dateDataItem->getJD(); + } + return null; + } + + /** + * Parse parameters and set internal properties + * + * @since 1.9 + * + * @param array $parameters + */ + public function parse( array $parameters ) { + // Initialize variables. + $all_date_strings = []; + $start_date = $end_date = $unit = $period = $week_num = null; + $included_dates = []; + $excluded_dates = []; + $excluded_dates_jd = []; + + // Parse parameters and assign values + foreach ( $parameters as $name => $values ) { + + foreach ( $values as $value ) { + switch( $name ) { + case 'property': + $this->property = $value; + break; + case 'start': + $start_date = DataValueFactory::getInstance()->newTypeIDValue( '_dat', $value ); + break; + case 'end': + $end_date = DataValueFactory::getInstance()->newTypeIDValue( '_dat', $value ); + break; + case 'limit': + // Override default limit with query specific limit + $this->defaultNumRecurringEvents = (int)$value; + break; + case 'unit': + $unit = $value; + break; + case 'period': + $period = (int)$value; + break; + case 'week number': + $week_num = (int)$value; + break; + case 'include': + // This is for compatibility only otherwise we break + // to much at once. Instead of writing include=...;... + // it should be include=...;...|+sep=; because the + // ParameterParser class is conditioned to split those + // parameter accordingly + if ( strpos( $value, ';' ) ){ + $included_dates = explode( ';', $value ); + } else { + $included_dates[] = $value; + } + break; + case 'exclude': + // Some as above + if ( strpos( $value, ';' ) ){ + $excluded_dates = explode( ';', $value ); + } else { + $excluded_dates[] = $value; + } + break; + default: + $this->parameters[$name][] = $value; + } + } + } + + if ( $start_date === null ) { + $this->errors[] = Message::get( 'smw-events-start-date-missing' ); + return; + } elseif ( !( $start_date->getDataItem() instanceof SMWDITime ) ) { + $this->setError( $start_date->getErrors() ); + return; + } + + // Check property + if ( is_null( $this->property ) ) { + $this->errors[] = Message::get( 'smw-events-property-missing' ); + return; + } + + // Exclude dates + foreach ( $excluded_dates as $date_str ) { + $excluded_dates_jd[] = $this->getJulianDay( + DataValueFactory::getInstance()->newTypeIDValue( '_dat', $date_str ) + ); + } + + // If the period is null, or outside of normal bounds, set it to 1. + if ( is_null( $period ) || $period < 1 || $period > 500 ) { + $period = 1; + } + + // Handle 'week number', but only if it's of unit 'month'. + if ( $unit == 'month' && ! is_null( $week_num ) ) { + $unit = 'dayofweekinmonth'; + + if ( $week_num < -4 || $week_num > 5 || $week_num == 0 ) { + $week_num = null; + } + } + + if ( $unit == 'dayofweekinmonth' && is_null( $week_num ) ) { + $week_num = ceil( $start_date->getDay() / 7 ); + } + + // Get the Julian day value for both the start and end date. + $end_date_jd = $this->getJulianDay( $end_date ); + + $cur_date = $start_date; + $cur_date_jd = $this->getJulianDay( $cur_date ); + $i = 0; + + do { + $i++; + $exclude_date = ( in_array( $cur_date_jd, $excluded_dates_jd ) ); + + if ( !$exclude_date ) { + $all_date_strings[] = $cur_date->getLongWikiText(); + } + + // Now get the next date. + // Handling is different depending on whether it's + // month/year or week/day since the latter is a set + // number of days while the former isn't. + if ( $unit === 'year' || $unit == 'month' ) { + $cur_year = $cur_date->getYear(); + $cur_month = $cur_date->getMonth(); + $cur_day = $start_date->getDay(); + $cur_time = $cur_date->getTimeString(); + + if ( $unit == 'year' ) { + $cur_year += $period; + $display_month = $cur_month; + } else { // $unit === 'month' + $cur_month += $period; + $cur_year += (int)( ( $cur_month - 1 ) / 12 ); + $cur_month %= 12; + $display_month = ( $cur_month == 0 ) ? 12 : $cur_month; + } + + // If the date is greater than 28 for February, and it is not + // a leap year, change it to be a fixed 28 otherwise set it to + // 29 (for a leap year date) + if ( $cur_month == 2 && $cur_day > 28 ) { + $cur_day = !date( 'L', strtotime( "$cur_year-1-1" ) ) ? 28 : 29; + } elseif ( $cur_day > 30 ) { + // Check whether 31 is a valid day of a month + $cur_day = ( $display_month - 1 ) % 7 % 2 ? 30 : 31; + } + + $date_str = "$cur_year-$display_month-$cur_day $cur_time"; + $cur_date = DataValueFactory::getInstance()->newTypeIDValue( '_dat', $date_str ); + $all_date_strings = array_merge( $all_date_strings, $included_dates); + if ( $cur_date->isValid() ) { + $cur_date_jd = $cur_date->getDataItem()->getJD(); + } + } elseif ( $unit == 'dayofweekinmonth' ) { + // e.g., "3rd Monday of every month" + $prev_month = $cur_date->getMonth(); + $prev_year = $cur_date->getYear(); + + $new_month = ( $prev_month + $period ) % 12; + if ( $new_month == 0 ) { + $new_month = 12; + } + + $new_year = $prev_year + floor( ( $prev_month + $period - 1 ) / 12 ); + $cur_date_jd += ( 28 * $period ) - 7; + + // We're sometime before the actual date now - + // keep incrementing by a week, until we get there. + do { + $cur_date_jd += 7; + $cur_date = $this->getJulianDayTimeValue( $cur_date_jd ); + $right_month = ( $cur_date->getMonth() == $new_month ); + + if ( $week_num < 0 ) { + $next_week_jd = $cur_date_jd; + + do { + $next_week_jd += 7; + $next_week_date = $this->getJulianDayTimeValue( $next_week_jd ); + $right_week = ( $next_week_date->getMonth() != $new_month ) || ( $next_week_date->getYear() != $new_year ); + } while ( !$right_week ); + + $cur_date_jd = $next_week_jd + ( 7 * $week_num ); + $cur_date = $this->getJulianDayTimeValue( $cur_date_jd ); + } else { + $cur_week_num = ceil( $cur_date->getDay() / 7 ); + $right_week = ( $cur_week_num == $week_num ); + + if ( $week_num == 5 && ( $cur_date->getMonth() % 12 == ( $new_month + 1 ) % 12 ) ) { + $cur_date_jd -= 7; + $cur_date = $this->getJulianDayTimeValue( $cur_date_jd ); + $right_month = $right_week = true; + } + } + } while ( !$right_month || !$right_week); + } else { // $unit == 'day' or 'week' + // Assume 'day' if it's none of the above. + $cur_date_jd += ( $unit === 'week' ) ? 7 * $period : $period; + $cur_date = $this->getJulianDayTimeValue( $cur_date_jd ); + } + + // should we stop? + if ( is_null( $end_date ) ) { + $reached_end_date = $i > $this->defaultNumRecurringEvents; + } else { + $reached_end_date = ( $cur_date_jd > $end_date_jd ) || ( $i > $this->maxNumRecurringEvents ); + } + } while ( !$reached_end_date ); + + // Handle the 'include' dates as well. + $all_date_strings = array_filter( array_merge( $all_date_strings, $included_dates ) ); + + // Set dates + $this->dates = str_replace( ' 00:00:00', '', $all_date_strings ); + } + + /** + * Helper function - creates an object of type SMWTimeValue based + * on a "Julian day" integer + */ + private function getJulianDayTimeValue( $jd ) { + $timeDataItem = SMWDITime::newFromJD( $jd, SMWDITime::CM_GREGORIAN, SMWDITime::PREC_YMDT ); + return DataValueFactory::getInstance()->newDataValueByItem( $timeDataItem ); + } + +} |