Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport from master branch #659

Merged
merged 17 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading