Skip to content

Commit

Permalink
Merge pull request #659 from phil-davis/backport-20240530
Browse files Browse the repository at this point in the history
Backport from master branch
  • Loading branch information
phil-davis authored May 31, 2024
2 parents a6d53a3 + a997470 commit 2783cc8
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
/.gitattributes export-ignore
/.gitignore export-ignore
/.php_cs.dist export-ignore
/.php-cs-fixer.dist.php export-ignore
/CHANGELOG.md export-ignore
/phpstan.neon export-ignore
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
coverage: ['pcov']
code-analysis: ['no']
include:
Expand All @@ -21,7 +21,7 @@ jobs:
code-analysis: 'yes'
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
Expand All @@ -36,7 +36,7 @@ jobs:
run: echo "::set-output name=dir::$(composer config cache-files-dir)"

- name: Cache composer dependencies
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
Expand All @@ -59,5 +59,5 @@ jobs:
run: vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover clover.xml

- name: Code Coverage
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v4
if: matrix.coverage != 'none'
6 changes: 2 additions & 4 deletions lib/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,8 @@ public function createComponent($name, array $children = null, $defaults = true)
* @param mixed $value
* @param array $parameters
* @param string $valueType Force a specific valuetype, such as URI or TEXT
*
* @return Property
*/
public function createProperty($name, $value = null, array $parameters = null, $valueType = null)
public function createProperty($name, $value = null, array $parameters = null, $valueType = null, int $lineIndex = null, string $lineString = null): Property
{
// If there's a . in the name, it means it's prefixed by a groupname.
if (false !== ($i = strpos($name, '.'))) {
Expand Down Expand Up @@ -223,7 +221,7 @@ public function createProperty($name, $value = null, array $parameters = null, $
$parameters = [];
}

return new $class($this, $name, $value, $parameters, $group);
return new $class($this, $name, $value, $parameters, $group, $lineIndex, $lineString);
}

/**
Expand Down
13 changes: 10 additions & 3 deletions lib/ITip/Broker.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,10 @@ protected function processMessageReply(Message $itipMessage, VCalendar $existing

// Finding all the instances the attendee replied to.
foreach ($itipMessage->message->VEVENT as $vevent) {
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
// Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence.
// The Unix timestamp will be the same for an event, even if the reply from the attendee
// used a different format/timezone to express the event date-time.
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master';
$attendee = $vevent->ATTENDEE;
$instances[$recurId] = $attendee['PARTSTAT']->getValue();
if (isset($vevent->{'REQUEST-STATUS'})) {
Expand All @@ -351,7 +354,8 @@ protected function processMessageReply(Message $itipMessage, VCalendar $existing
// all the instances where we have a reply for.
$masterObject = null;
foreach ($existingObject->VEVENT as $vevent) {
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
// Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence.
$recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master';
if ('master' === $recurId) {
$masterObject = $vevent;
}
Expand Down Expand Up @@ -398,7 +402,10 @@ protected function processMessageReply(Message $itipMessage, VCalendar $existing
$newObject = $recurrenceIterator->getEventObject();
$recurrenceIterator->next();

if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
// Compare the Unix timestamp returned by getTimestamp with the previously calculated timestamp.
// If they are the same, then this is a matching recurrence, even though its date-time may have
// been expressed in a different format/timezone.
if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() === $recurId) {
$found = true;
}
--$iterations;
Expand Down
8 changes: 7 additions & 1 deletion lib/Parser/MimeDir.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public function parse($input = null, $options = 0)
$this->setInput($input);
}

if (!\is_resource($this->input)) {
// Null was passed as input, but there was no existing input buffer
// There is nothing to parse.
throw new ParseException('No input provided to parse');
}

if (0 !== $options) {
$this->options = $options;
}
Expand Down Expand Up @@ -447,7 +453,7 @@ protected function readProperty($line)
}
}

$propObj = $this->root->createProperty($property['name'], null, $namedParameters);
$propObj = $this->root->createProperty($property['name'], null, $namedParameters, null, $this->startLine, $line);

foreach ($namelessParameters as $namelessParameter) {
$propObj->add(null, $namelessParameter);
Expand Down
24 changes: 23 additions & 1 deletion lib/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ abstract class Property extends Node
*/
public $delimiter = ';';

/**
* The line number in the original iCalendar / vCard file
* that corresponds with the current node
* if the node was read from a file.
*/
public $lineIndex;

/**
* The line string from the original iCalendar / vCard file
* that corresponds with the current node
* if the node was read from a file.
*/
public $lineString;

/**
* Creates the generic property.
*
Expand All @@ -67,7 +81,7 @@ abstract class Property extends Node
* @param array $parameters List of parameters
* @param string $group The vcard property group
*/
public function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null)
public function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null, int $lineIndex = null, string $lineString = null)
{
$this->name = $name;
$this->group = $group;
Expand All @@ -81,6 +95,14 @@ public function __construct(Component $root, $name, $value = null, array $parame
if (!is_null($value)) {
$this->setValue($value);
}

if (!is_null($lineIndex)) {
$this->lineIndex = $lineIndex;
}

if (!is_null($lineString)) {
$this->lineString = $lineString;
}
}

/**
Expand Down
6 changes: 5 additions & 1 deletion lib/Recur/RRuleIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,11 @@ protected function nextYearly()
// If we advanced to the next month or year, the first
// occurrence is always correct.
if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
break 2;
// only consider byMonth matches,
// otherwise, we don't follow RRule correctly
if (in_array($currentMonth, $this->byMonth)) {
break 2;
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
parameters:
level: 1
reportUnmatchedIgnoredErrors: false
universalObjectCratesClasses:
- \Sabre\VObject\Component
ignoreErrors:
- '#Call to an undefined static method Sabre\\VObject\\Component\\VCalendarTest::assertObjectHasProperty\(\).#'
49 changes: 49 additions & 0 deletions tests/VObject/Component/VCalendarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,55 @@ public function testCalDAVMETHOD()
);
}

public function testNodeInValidationErrorHasLineIndexAndLineStringProps(): void
{
$defectiveInput = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
PRODID:vobject
BEGIN:VEVENT
UID:foo
CLASS:PUBLIC
DTSTART;VALUE=DATE:19931231
DTSTAMP:20240422T070855Z
CREATED:
LAST-MODIFIED:
DESCRIPTION:bar
END:VEVENT
ICS;

$vcal = VObject\Reader::read($defectiveInput);
$result = $vcal->validate();
$warningMessages = [];
foreach ($result as $error) {
$warningMessages[] = $error['message'];
}
self::assertCount(2, $result, 'We expected exactly 2 validation messages, instead we got '.count($result).' results:'.implode(', ', $warningMessages));
foreach ($result as $idx => $warning) {
self::assertArrayHasKey('node', $warning);
self::assertInstanceOf(VObject\Property\ICalendar\DateTime::class, $warning['node']);
if (method_exists($this, 'assertObjectHasProperty')) {
self::assertObjectHasProperty('lineIndex', $warning['node']);
self::assertObjectHasProperty('lineString', $warning['node']);
} else {
// Fall back to the older method name known to older versions of phpunit.
self::assertObjectHasAttribute('lineIndex', $warning['node']);
self::assertObjectHasAttribute('lineString', $warning['node']);
}
switch ($idx) {
case 0:
self::assertEquals('10', $warning['node']->lineIndex);
self::assertEquals('CREATED:', $warning['node']->lineString);
break;
case 1:
self::assertEquals('11', $warning['node']->lineIndex);
self::assertEquals('LAST-MODIFIED:', $warning['node']->lineString);
break;
}
}
}

public function assertValidate($ics, $options, $expectedLevel, $expectedMessage = null)
{
$vcal = VObject\Reader::read($ics);
Expand Down
Loading

0 comments on commit 2783cc8

Please sign in to comment.