@@ -0,0 +1,485 @@
|
||||
<?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' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user