485 lines
No EOL
18 KiB
PHP
485 lines
No EOL
18 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Helper for recurrence rules.
|
|
*
|
|
* @author Time.ly Network Inc.
|
|
* @since 2.0
|
|
*
|
|
* @package AI1EC
|
|
* @subpackage AI1EC.Recurrence
|
|
*/
|
|
class Ai1ec_Recurrence_Rule extends Ai1ec_Base {
|
|
|
|
/**
|
|
* Return given recurrence data as text.
|
|
*
|
|
* @param string $rrule Recurrence rule
|
|
*
|
|
* @return string
|
|
*/
|
|
public function rrule_to_text( $rrule = '' ) {
|
|
$txt = '';
|
|
$rc = new SG_iCal_Recurrence( new SG_iCal_Line( 'RRULE:' . $rrule ) );
|
|
switch( $rc->getFreq() ) {
|
|
case 'DAILY':
|
|
$this->_get_interval( $txt, 'daily', $rc->getInterval() );
|
|
$this->_ending_sentence( $txt, $rc );
|
|
break;
|
|
case 'WEEKLY':
|
|
$this->_get_interval( $txt, 'weekly', $rc->getInterval() );
|
|
$this->_get_sentence_by( $txt, 'weekly', $rc );
|
|
$this->_ending_sentence( $txt, $rc );
|
|
break;
|
|
case 'MONTHLY':
|
|
$this->_get_interval( $txt, 'monthly', $rc->getInterval() );
|
|
$this->_get_sentence_by( $txt, 'monthly', $rc );
|
|
$this->_ending_sentence( $txt, $rc );
|
|
break;
|
|
case 'YEARLY':
|
|
$this->_get_interval( $txt, 'yearly', $rc->getInterval() );
|
|
$this->_get_sentence_by( $txt, 'yearly', $rc );
|
|
$this->_ending_sentence( $txt, $rc );
|
|
break;
|
|
default:
|
|
$processed = explode( '=', $rrule );
|
|
if (
|
|
isset( $processed[1] ) &&
|
|
in_array(
|
|
strtoupper( $processed[0] ),
|
|
array( 'RDATE', 'EXDATE' )
|
|
)
|
|
) {
|
|
$txt = $this->exdate_to_text( $processed[1] );
|
|
} else {
|
|
$txt = $rrule;
|
|
}
|
|
}
|
|
return $txt;
|
|
}
|
|
|
|
/**
|
|
* Parse a `recurrence rule' into an array that can be used to calculate
|
|
* recurrence instances.
|
|
*
|
|
* @see http://kigkonsult.se/iCalcreator/docs/using.html#EXRULE
|
|
*
|
|
* @param string $rule
|
|
* @return array
|
|
*/
|
|
public function build_recurrence_rules_array( $rule ) {
|
|
$rules = array();
|
|
$rule_list = explode( ';', $rule );
|
|
foreach ( $rule_list as $single_rule ) {
|
|
if ( false === strpos( $single_rule, '=' ) ) {
|
|
continue;
|
|
}
|
|
list( $key, $val ) = explode( '=', $single_rule );
|
|
$key = strtoupper( $key );
|
|
switch ( $key ) {
|
|
case 'BYDAY':
|
|
$rules['BYDAY'] = array();
|
|
foreach ( explode( ',', $val ) as $day ) {
|
|
$rule_map = $this->create_byday_array( $day );
|
|
$rules['BYDAY'][] = $rule_map;
|
|
if (
|
|
preg_match( '/FREQ=(MONTH|YEAR)LY/i', $rule ) &&
|
|
1 === count( $rule_map )
|
|
) {
|
|
// monthly/yearly "last" recurrences need day name
|
|
$rules['BYDAY']['DAY'] = substr(
|
|
$rule_map['DAY'],
|
|
-2
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'BYMONTHDAY':
|
|
case 'BYMONTH':
|
|
if ( false === strpos( $val, ',' ) ) {
|
|
$rules[$key] = $val;
|
|
} else {
|
|
$rules[$key] = explode( ',', $val );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
$rules[$key] = $val;
|
|
}
|
|
}
|
|
return $rules;
|
|
}
|
|
|
|
/**
|
|
* _merge_exrule method
|
|
*
|
|
* Merge RRULE values to EXRULE, to ensure, that it matches the according
|
|
* repetition values, it is meant to exclude.
|
|
*
|
|
* NOTE: one shall ensure, that RRULE values are placed in between EXRULE
|
|
* keys, so that wording in UI would remain the same after mangling.
|
|
*
|
|
* @param string $exrule Value for EXRULE provided by user
|
|
* @param string $rrule Value for RRULE provided by user
|
|
*
|
|
* @return string Modified value to use for EXRULE
|
|
*/
|
|
public function merge_exrule( $exrule, $rrule ) {
|
|
$list_exrule = explode( ';', $exrule );
|
|
$list_rrule = explode( ';', $rrule );
|
|
$map_exrule = $map_rrule = array();
|
|
foreach ( $list_rrule as $entry ) {
|
|
if ( empty( $entry ) ) {
|
|
continue;
|
|
}
|
|
list( $key, $value ) = explode( '=', $entry );
|
|
$map_rrule[$key] = $value;
|
|
}
|
|
foreach ( $list_exrule as $entry ) {
|
|
if ( empty( $entry ) ) {
|
|
continue;
|
|
}
|
|
list( $key, $value ) = explode( '=', $entry );
|
|
$map_exrule[$key] = $value;
|
|
}
|
|
|
|
$resulting_map = array_merge( $map_rrule, $map_exrule );
|
|
$result_rule = array();
|
|
foreach ( $resulting_map as $key => $value ) {
|
|
$result_rule[] = $key . '=' . $value;
|
|
}
|
|
$result_rule = implode( ';', $result_rule );
|
|
return $result_rule;
|
|
}
|
|
|
|
/**
|
|
* Return given exception dates as text.
|
|
*
|
|
* @param array $exception_dates Dates to translate
|
|
*
|
|
* @return string
|
|
*/
|
|
public function exdate_to_text( $exception_dates ) {
|
|
$dates_to_add = array();
|
|
foreach ( explode( ',', $exception_dates ) as $_exdate ) {
|
|
$date_format = $this->_registry->get( 'model.option' )
|
|
->get( 'date_format', 'l, M j, Y' );
|
|
$dates_to_add[] = $this->_registry->get(
|
|
'date.time',
|
|
vsprintf(
|
|
'%04d-%02d-%02d',
|
|
sscanf(
|
|
$_exdate,
|
|
'%04d%02d%02dT%dZ'
|
|
)
|
|
),
|
|
'sys.default'
|
|
)
|
|
->format_i18n( $date_format );
|
|
}
|
|
$dates_to_add = str_replace( ' ', ' ', $dates_to_add );
|
|
// append dates to the string and return it;
|
|
return implode( '; ', $dates_to_add );
|
|
}
|
|
|
|
/**
|
|
* Filter recurrence / exclusion rule or dates. Avoid throwing exception for old, malformed values.
|
|
*
|
|
* @param string $rule Rule or dates value.
|
|
*
|
|
* @return string Fixed rule or dates value.
|
|
*/
|
|
public function filter_rule( $rule ) {
|
|
$matches = null;
|
|
if (
|
|
empty( $rule ) ||
|
|
! preg_match('/(T[0-9]+)(ZUNTIL=[0-9Z;T]+)/i', $rule, $matches)
|
|
) {
|
|
return $rule;
|
|
}
|
|
return preg_replace('/(T[0-9]+)(ZUNTIL=[0-9Z;T]+)/i', '$1', $rule);
|
|
}
|
|
|
|
/**
|
|
* when using BYday you need an array of arrays.
|
|
* This function create valid arrays that keep into account the presence
|
|
* of a week number beofre the day
|
|
*
|
|
* @param string $val
|
|
* @return array
|
|
*/
|
|
protected function create_byday_array( $val ) {
|
|
$week = substr( $val, 0, 1 );
|
|
if ( is_numeric( $week ) ) {
|
|
return array( $week, 'DAY' => substr( $val, 1 ) );
|
|
}
|
|
return array( 'DAY' => $val );
|
|
}
|
|
|
|
/**
|
|
* _get_sentence_by function
|
|
*
|
|
* @internal
|
|
*
|
|
* @return void
|
|
**/
|
|
protected function _get_sentence_by( &$txt, $freq, $rc ) {
|
|
global $wp_locale;
|
|
|
|
switch( $freq ) {
|
|
case 'weekly':
|
|
if( $rc->getByDay() ) {
|
|
if( count( $rc->getByDay() ) > 1 ) {
|
|
// if there are more than 3 days
|
|
// use days's abbr
|
|
if( count( $rc->getByDay() ) > 2 ) {
|
|
$_days = '';
|
|
foreach( $rc->getByDay() as $d ) {
|
|
$day = $this->get_weekday_by_id( $d, true );
|
|
$_days .= ' ' . $wp_locale->weekday_abbrev[$wp_locale->weekday[$day]] . ',';
|
|
}
|
|
// remove the last ' and'
|
|
$_days = substr( $_days, 0, -1 );
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - weekly tab' ) . $_days;
|
|
} else {
|
|
$_days = '';
|
|
foreach( $rc->getByDay() as $d ) {
|
|
$day = $this->get_weekday_by_id( $d, true );
|
|
$_days .= ' ' . $wp_locale->weekday[$day] . ' ' . Ai1ec_I18n::__( 'and' );
|
|
}
|
|
// remove the last ' and'
|
|
$_days = substr( $_days, 0, -( strlen( Ai1ec_I18n::__( 'and' ) ) + 1 ) );
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - weekly tab' ) . $_days;
|
|
}
|
|
} else {
|
|
$_days = '';
|
|
foreach( $rc->getByDay() as $d ) {
|
|
$day = $this->get_weekday_by_id( $d, true );
|
|
$_days .= ' ' . $wp_locale->weekday[$day];
|
|
}
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - weekly tab' ) . $_days;
|
|
}
|
|
}
|
|
break;
|
|
case 'monthly':
|
|
if( $rc->getByMonthDay() ) {
|
|
// if there are more than 2 days
|
|
if( count( $rc->getByMonthDay() ) > 2 ) {
|
|
$_days = '';
|
|
foreach( $rc->getByMonthDay() as $m_day ) {
|
|
$_days .= ' ' . $this->_ordinal( $m_day ) . ',';
|
|
}
|
|
$_days = substr( $_days, 0, -1 );
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - monthly tab' ) . $_days . ' ' . Ai1ec_I18n::__( 'of the month' );
|
|
} else if( count( $rc->getByMonthDay() ) > 1 ) {
|
|
$_days = '';
|
|
foreach( $rc->getByMonthDay() as $m_day ) {
|
|
$_days .= ' ' . $this->_ordinal( $m_day ) . ' ' . Ai1ec_I18n::__( 'and' );
|
|
}
|
|
$_days = substr( $_days, 0, -4 );
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - monthly tab' ) . $_days . ' ' . Ai1ec_I18n::__( 'of the month' );
|
|
} else {
|
|
$_days = '';
|
|
foreach( $rc->getByMonthDay() as $m_day ) {
|
|
$_days .= ' ' . $this->_ordinal( $m_day );
|
|
}
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - monthly tab' ) . $_days . ' ' . Ai1ec_I18n::__( 'of the month' );
|
|
}
|
|
} elseif( $rc->getByDay() ) {
|
|
$_days = '';
|
|
$dnum = '';
|
|
foreach( $rc->getByDay() as $d ) {
|
|
if ( ! preg_match( '|^((-?)\d+)([A-Z]{2})$|', $d, $matches ) ) {
|
|
continue;
|
|
}
|
|
$_dnum = $matches[1];
|
|
$_day = $matches[3];
|
|
if ( '-' === $matches[2] ) {
|
|
$dnum = ' ' . Ai1ec_I18n::__( 'last' );
|
|
} else {
|
|
$dnum = ' ' . $this->_registry->get(
|
|
'date.time',
|
|
strtotime( $_dnum . '-01-1998 12:00:00' )
|
|
)->format_i18n( 'jS' );
|
|
}
|
|
$day = $this->get_weekday_by_id( $_day, true );
|
|
$_days .= ' ' . $wp_locale->weekday[$day];
|
|
}
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - monthly tab' ) . $dnum . $_days;
|
|
}
|
|
break;
|
|
case 'yearly':
|
|
if( $rc->getByMonth() ) {
|
|
// if there are more than 2 months
|
|
if( count( $rc->getByMonth() ) > 2 ) {
|
|
$_months = '';
|
|
foreach( $rc->getByMonth() as $_m ) {
|
|
$_m = $_m < 10 ? 0 . $_m : $_m;
|
|
$_months .= ' ' . $wp_locale->month_abbrev[$wp_locale->month[$_m]] . ',';
|
|
}
|
|
$_months = substr( $_months, 0, -1 );
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - yearly tab' ) . $_months;
|
|
} else if( count( $rc->getByMonth() ) > 1 ) {
|
|
$_months = '';
|
|
foreach( $rc->getByMonth() as $_m ) {
|
|
$_m = $_m < 10 ? 0 . $_m : $_m;
|
|
$_months .= ' ' . $wp_locale->month[$_m] . ' ' . Ai1ec_I18n::__( 'and' );
|
|
}
|
|
$_months = substr( $_months, 0, -4 );
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - yearly tab' ) . $_months;
|
|
} else {
|
|
$_months = '';
|
|
foreach( $rc->getByMonth() as $_m ) {
|
|
$_m = $_m < 10 ? 0 . $_m : $_m;
|
|
$_months .= ' ' . $wp_locale->month[$_m];
|
|
}
|
|
$txt .= ' ' . Ai1ec_I18n::_x( 'on', 'Recurrence editor - yearly tab' ) . $_months;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* _ordinal function
|
|
*
|
|
* @internal
|
|
*
|
|
* @return void
|
|
**/
|
|
protected function _ordinal( $cdnl ) {
|
|
$locale = explode( '_', get_locale() );
|
|
|
|
if( isset( $locale[0] ) && $locale[0] != 'en' )
|
|
return $cdnl;
|
|
|
|
$test_c = abs($cdnl) % 10;
|
|
$ext = ( ( abs( $cdnl ) % 100 < 21 && abs( $cdnl ) % 100 > 4 ) ? 'th'
|
|
: ( ( $test_c < 4 ) ? ( $test_c < 3 ) ? ( $test_c < 2 ) ? ( $test_c < 1 )
|
|
? 'th' : 'st' : 'nd' : 'rd' : 'th' ) );
|
|
return $cdnl.$ext;
|
|
}
|
|
|
|
/**
|
|
* Returns the textual representation of the given recurrence frequency and
|
|
* interval, with result stored in $txt.
|
|
*
|
|
* @internal
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function _get_interval( &$txt, $freq, $interval ) {
|
|
switch ( $freq ) {
|
|
case 'daily':
|
|
// check if interval is set
|
|
if ( ! $interval || $interval == 1 ) {
|
|
$txt = Ai1ec_I18n::__( 'Daily' );
|
|
} else {
|
|
if ( $interval == 2 ) {
|
|
$txt = Ai1ec_I18n::__( 'Every other day' );
|
|
} else {
|
|
$txt = sprintf(
|
|
Ai1ec_I18n::__( 'Every %d days' ),
|
|
$interval
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
case 'weekly':
|
|
// check if interval is set
|
|
if ( ! $interval || $interval == 1 ) {
|
|
$txt = Ai1ec_I18n::__( 'Weekly' );
|
|
} else {
|
|
if ( $interval == 2 ) {
|
|
$txt = Ai1ec_I18n::__( 'Every other week' );
|
|
} else {
|
|
$txt = sprintf(
|
|
Ai1ec_I18n::__( 'Every %d weeks' ),
|
|
$interval
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
case 'monthly':
|
|
// check if interval is set
|
|
if ( ! $interval || $interval == 1 ) {
|
|
$txt = Ai1ec_I18n::__( 'Monthly' );
|
|
} else {
|
|
if ( $interval == 2 ) {
|
|
$txt = Ai1ec_I18n::__( 'Every other month' );
|
|
} else {
|
|
$txt = sprintf(
|
|
Ai1ec_I18n::__( 'Every %d months' ),
|
|
$interval
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
case 'yearly':
|
|
// check if interval is set
|
|
if ( ! $interval || $interval == 1 ) {
|
|
$txt = Ai1ec_I18n::__( 'Yearly' );
|
|
} else {
|
|
if ( $interval == 2 ) {
|
|
$txt = Ai1ec_I18n::__( 'Every other year' );
|
|
} else {
|
|
$txt = sprintf(
|
|
Ai1ec_I18n::__( 'Every %d years' ),
|
|
$interval
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get_weekday_by_id function
|
|
*
|
|
* Returns weekday name in English
|
|
*
|
|
* @param int $day_id Day ID
|
|
*
|
|
* @return string
|
|
**/
|
|
protected function get_weekday_by_id( $day_id, $by_value = false ) {
|
|
return $this->_registry->get( 'view.admin.get-repeat-box' )
|
|
->get_weekday_by_id( $day_id, $by_value );
|
|
}
|
|
|
|
/**
|
|
* _ending_sentence function
|
|
*
|
|
* Ends rrule to text sentence
|
|
*
|
|
* @internal
|
|
*
|
|
* @return void
|
|
**/
|
|
protected function _ending_sentence( &$txt, &$rc ) {
|
|
if ( $until = $rc->getUntil() ) {
|
|
if ( ! is_int( $until ) ) {
|
|
$until = strtotime( $until );
|
|
}
|
|
$txt .= ' ' . sprintf(
|
|
Ai1ec_I18n::__( 'until %s' ),
|
|
$this->_registry->get(
|
|
'date.time',
|
|
$until
|
|
)->format_i18n(
|
|
$this->_registry->get( 'model.option')->get( 'date_format' )
|
|
)
|
|
);
|
|
} else if ( $count = $rc->getCount() ) {
|
|
$txt .= ' ' . sprintf(
|
|
Ai1ec_I18n::__( 'for %d occurrences' ),
|
|
$count
|
|
);
|
|
} else {
|
|
$txt .= ', ' . Ai1ec_I18n::__( 'forever' );
|
|
}
|
|
}
|
|
|
|
|
|
} |