Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

@@ -12,7 +12,7 @@
namespace IPS\calendar\Icalendar;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -29,6 +29,7 @@ class _ICSParser
*
* @param string $rrule The ICS RRULE value
* @param string $timezone Timezone to treat recurring end date as
* @param NULL|\IPS\calendar\Date $startDate If supplied, we can use the start date to help convert certain repeat rules to make them simpler
* @return array
* @todo [Future] We do not support HOURLY, MINUTELY or SECONDLY "FREQ" values
* @todo [Future] We do not support the properties BYSETPOS, BYYEARDAY, BYMONTH, BYHOUR, BYMINUTE, BYSECOND, BYWEEKNO, WKST
@@ -37,7 +38,7 @@ class _ICSParser
* @throws \InvalidArgumentException
* @note Some implementations separate with : and some with ; so we have to support both
*/
public static function parseRrule( $rrule, $timezone=NULL )
public static function parseRrule( $rrule, $timezone=NULL, $startDate = NULL )
{
$rule = array_map( 'trim', explode( ';', str_replace( ':', ';', $rrule ) ) );
$dayNames = array();
@@ -65,10 +66,18 @@ class _ICSParser
throw new \InvalidArgumentException( $_ruleData[0] );
break;
case 'WKST':
/* We can safely ignore this when it is set to the default value of MO. We can't make assumptions in other cases. */
if( mb_strtolower( $_ruleData[1] ) != 'mo' )
{
throw new \InvalidArgumentException( $_ruleData[0] );
}
break;
case 'FREQ':
$frequency = mb_strtolower( $_ruleData[1] );
if( in_array( $frequency, array( 'daily', 'weekly', 'monthly', 'yearly' ) ) )
if( \in_array( $frequency, array( 'daily', 'weekly', 'monthly', 'yearly' ) ) )
{
$repeatData['event_repeats'] = $frequency;
$repeatData['event_repeat'] = TRUE;
@@ -110,7 +119,7 @@ class _ICSParser
case 'BYMONTHDAY':
$values = array_map( 'trim', explode( ',', $_ruleData[1] ) );
if( count( $values ) > 1 OR \stripos( $values[0], '-' ) !== FALSE )
if( \count( $values ) > 1 OR \stripos( $values[0], '-' ) !== FALSE )
{
throw new \InvalidArgumentException( 'BYMONTHDAY' );
}
@@ -136,6 +145,36 @@ class _ICSParser
$repeatData['event_repeat_freq'] = 1;
}
/* Try to make the returned rules a little "smarter" for efficiency reasons... */
/* Convert "repeats every 14 days" into "2 weeks" */
if( $repeatData['event_repeats'] == 'daily' AND $repeatData['event_repeat_freq'] % 7 == 0 )
{
$repeatData['event_repeats'] = 'weekly';
$repeatData['event_repeat_freq'] = $repeatData['event_repeat_freq'] / 7;
}
/* Convert "repeats every week on Tuesday" to just "repeats every week" if the start date was Tuesday */
if( $repeatData['event_repeats'] == 'weekly' AND $startDate !== NULL )
{
$_repeatDays = array();
foreach( \IPS\calendar\Date::getDayNames() as $day )
{
if( $repeatData['repeat_freq_on_' . $day['ical'] ] == TRUE )
{
$_repeatDays[] = $day['english'];
}
}
if( ( \count( $_repeatDays ) == 1 AND $startDate->weekday == $_repeatDays[0] ) OR \count( $_repeatDays ) == 7 )
{
foreach( $dayNames as $day )
{
$repeatData['repeat_freq_on_' . $day ] = NULL;
}
}
}
return $repeatData;
}
@@ -169,7 +208,7 @@ class _ICSParser
if( isset( $repeat['repeat_end_date'] ) AND $repeat['repeat_end_date'] )
{
$rrule[] = 'UNTIL=' . \IPS\calendar\Date::createFromForm( $repeat['repeat_end_date'], NULL, $repeat['event_timezone'] )->setTimezone( new \DateTimeZone( 'UTC' ) )->modifiedIso8601();
$rrule[] = 'UNTIL=' . \IPS\calendar\Date::createFromForm( $repeat['repeat_end_date'], NULL, 'UTC' )->modifiedIso8601();
}
/* By-day rule */
@@ -183,7 +222,7 @@ class _ICSParser
}
}
if( count( $days ) )
if( \count( $days ) )
{
$rrule[] = 'BYDAY=' . implode( ',', $days );
}
@@ -225,7 +264,7 @@ class _ICSParser
$_raw = preg_replace( "#(\n\r|\r|\n){1,}#", "\n", $content );
$_raw = explode( "\n", $_raw );
if( !count($_raw) )
if( !\count($_raw) )
{
throw new \UnexpectedValueException( "NO_CONTENT" );
}
@@ -294,7 +333,7 @@ class _ICSParser
}
/* Convert the raw ICS data to GMT now */
if( count($this->_parsedIcsData) )
if( \count($this->_parsedIcsData) )
{
$this->_parsedIcsData = $this->_convertToGmt( $this->_parsedIcsData );
}
@@ -308,7 +347,7 @@ class _ICSParser
// Leave this here - useful for debugging
// print_r($this->_parsedIcsData);exit;
if ( count( $this->_parsedIcsData ) and isset( $this->_parsedIcsData['events'] ) )
if ( \count( $this->_parsedIcsData ) and isset( $this->_parsedIcsData['events'] ) )
{
usort( $this->_parsedIcsData['events'], function( $a, $b ) {
if ( $a['start']['raw_ts'] == $b['start']['raw_ts'] )
@@ -367,15 +406,15 @@ class _ICSParser
if ( ! $event_unix_to AND isset( $event['duration'] ) AND $event['duration'] )
{
preg_match( "#(\d+?)H#is", $event['duration'], $match );
$hour = $match[1] ? $match[1] : 0;
$hour = isset( $match[1] ) ? $match[1] : 0;
preg_match( "#(\d+?)M#is", $event['duration'], $match );
$minute = $match[1] ? $match[1] : 0;
$minute = isset( $match[1] ) ? $match[1] : 0;
preg_match( "#(\d+?)S#is", $event['duration'], $match );
$second = $match[1] ? $match[1] : 0;
$second = isset( $match[1] ) ? $match[1] : 0;
$event_unix_to = $event_unix_from->add( new \DateIntveral( 'PT' . ( $hour * 3600 ) . 'H' . ( $minute * 60 ) . 'M' . $second . 'S' ) );
$event_unix_to = $event_unix_from->add( new \DateInterval( 'PT' . ( $hour * 3600 ) . 'H' . ( $minute * 60 ) . 'M' . $second . 'S' ) );
}
/* If this is an all day event, adjust the timestamps */
@@ -401,7 +440,7 @@ class _ICSParser
try
{
/* If we are importing an ICS file, feed here will be null. */
if ( is_null( $this->feed ) )
if ( \is_null( $this->feed ) )
{
/* Bubble up so we can create a new event */
throw new \UnderflowException;
@@ -451,7 +490,7 @@ class _ICSParser
$newEvent->title = ( isset( $event['summary'] ) AND $event['summary'] ) ? $event['summary'] : mb_substr( strip_tags( $event['description'] ), 0, 100 );
$content = ( isset( $event['description'] ) AND $event['description'] ) ? nl2br( $event['description'] ) : $event['summary'] . ( isset( $event['location'] ) ? '<br>' . $event['location'] : '' );
$newEvent->content = \IPS\Text\Parser::parseStatic( $content, TRUE, NULL, $this->member );
$newEvent->sequence = ( isset( $event['sequence'] ) ? intval( $event['sequence'] ) : 0 );
$newEvent->sequence = ( isset( $event['sequence'] ) ? \intval( $event['sequence'] ) : 0 );
/* Times and dates */
if( isset( $event['last_modified'] ) AND $event['last_modified'] AND $event['last_modified'] < time() )
@@ -469,7 +508,7 @@ class _ICSParser
$newEvent->end_date = $event_unix_to ? $event_unix_to->setTimezone( new \DateTimeZone( 'UTC' ) )->format( 'Y-m-d H:i:s' ) : NULL;
/* Geolocation? */
if( isset( $event['geo'] ) AND is_array( $event['geo'] ) AND count( $event['geo'] ) )
if( isset( $event['geo'] ) AND \is_array( $event['geo'] ) AND \count( $event['geo'] ) )
{
/* If we are updating an event, don't pull geo data if we already have it and it hasn't changed */
$update = TRUE;
@@ -532,7 +571,7 @@ class _ICSParser
}
/* Add any event attendees that are members of our installation */
if( !$eventId AND isset($event['attendee']) AND count($event['attendee']) )
if( !$eventId AND isset($event['attendee']) AND \count($event['attendee']) )
{
foreach( $event['attendee'] as $attendee )
{
@@ -647,7 +686,7 @@ class _ICSParser
/* Get the years that all events span */
$years = array();
if( count( $this->_events ) )
if( \count( $this->_events ) )
{
foreach( $this->_events as $event )
{
@@ -727,7 +766,7 @@ class _ICSParser
$output = '';
/* Loop over the events */
if( count( $this->_events ) )
if( \count( $this->_events ) )
{
foreach( $this->_events as $event )
{
@@ -744,7 +783,7 @@ class _ICSParser
preg_match_all( "/(http.+?attachment\.php\?id=(\d+))/i", $event->content, $matches );
if( is_array($matches) AND count($matches) )
if( \is_array($matches) AND \count($matches) )
{
foreach( $matches[2] as $k => $v )
{
@@ -752,7 +791,7 @@ class _ICSParser
}
}
if( count( $attachments ) )
if( \count( $attachments ) )
{
foreach( \IPS\Db::i()->select( '*', 'core_attachments', 'attach_id IN(' . implode( ',', array_keys( $attachments ) ) . ')' ) as $attachment )
{
@@ -770,7 +809,7 @@ class _ICSParser
if( $event->all_day )
{
$output .= "DTSTART;VALUE=DATE:" . $event->_start_date->modifiedIso8601( FALSE ) . "\r\n";
$output .= "DTEND;VALUE=DATE:" . $event->_end_date->modifiedIso8601( FALSE ) . "\r\n";
$output .= "DTEND;VALUE=DATE:" . $event->_end_date->add( new \DateInterval( 'P1D' ) )->modifiedIso8601( FALSE ) . "\r\n";
}
else
{
@@ -1102,7 +1141,7 @@ class _ICSParser
/* Loop over the lines */
$_recid = null;
for( $i = $start, $j = count( $this->_rawIcsData ); $i < $j; $i++ )
for( $i = $start, $j = \count( $this->_rawIcsData ); $i < $j; $i++ )
{
/* Unparse and get content */
$tmp = $this->_unparseContent( $this->_rawIcsData[$i], $i );
@@ -1115,7 +1154,7 @@ class _ICSParser
$_type = $tmp['type'];
$_data = $tmp['data'];
if( $this->currentlyParsing != 'EVENT' AND !in_array( $_type, array( 'END', 'BEGIN' ) ) )
if( $this->currentlyParsing != 'EVENT' AND !\in_array( $_type, array( 'END', 'BEGIN' ) ) )
{
continue;
}
@@ -1218,7 +1257,7 @@ class _ICSParser
break;
case 'SEQUENCE':
$_event['sequence'] = intval($_data);
$_event['sequence'] = \intval($_data);
break;
case 'RRULE':
@@ -1268,7 +1307,7 @@ class _ICSParser
protected function _processVcalendarObject( $start )
{
/* Loop over the lines */
for( $i = $start, $j = count( $this->_rawIcsData ); $i < $j; $i++ )
for( $i = $start, $j = \count( $this->_rawIcsData ); $i < $j; $i++ )
{
/* Unparse and get the data */
$tmp = $this->_unparseContent( $this->_rawIcsData[$i], $i );
@@ -1342,7 +1381,7 @@ class _ICSParser
protected function _convertToGmt( $data )
{
/* Fix events */
if ( is_array( $data['events'] ) AND count( $data['events'] ) )
if ( \is_array( $data['events'] ) AND \count( $data['events'] ) )
{
foreach( $data['events'] as $id => $event )
{
@@ -1352,16 +1391,15 @@ class _ICSParser
if ( isset( $event[ $method ]['tzid'] ) AND $event[ $method ]['tzid'] )
{
/* Let's test it first... */
if( in_array( $event[ $method ]['tzid'], \IPS\DateTime::getTimezoneIdentifiers() ) )
$timezone = NULL;
if( \in_array( $event[ $method ]['tzid'], \IPS\DateTime::getTimezoneIdentifiers() ) )
{
try
{
$timezone = new \DateTimeZone( $event[ $method ]['tzid'] );
}
catch ( \Exception $e )
{
$timezone = NULL;
}
catch ( \Exception $e ) {}
}
$event[ $method ]['utc_time'] = new \IPS\DateTime( $event[ $method ]['raw'], $timezone );