diff --git a/src/Exceptions/InvalidDataException.php b/src/Exceptions/InvalidDataException.php new file mode 100644 index 0000000..7ed3b11 --- /dev/null +++ b/src/Exceptions/InvalidDataException.php @@ -0,0 +1,13 @@ +resolveClipboardDataReferences($newClipboardData); } + if (in_array(HasGroup::class, class_uses($object))) { + $object->resolveGroupClipboardDataReferences($newClipboardData); + } } return $newClipboardData; diff --git a/src/Objects/MiroGroup.php b/src/Objects/MiroGroup.php index 612fc9c..7f0ef66 100644 --- a/src/Objects/MiroGroup.php +++ b/src/Objects/MiroGroup.php @@ -4,6 +4,7 @@ use MiroClipboard\Enums\ObjectType; use MiroClipboard\MiroClipboardData; +use MiroClipboard\Utility\HasGroup; use MiroClipboard\Utility\Makeable; class MiroGroup extends MiroObject @@ -33,6 +34,10 @@ public function __construct() public function add(int|MiroObject $object): static { if ($object instanceof MiroObject) { + if (in_array(HasGroup::class, class_uses($object))) { + $object->setGroup($this); + } + $this->objects[] = $object; $this->ids[] = $object->id; } else { @@ -50,6 +55,18 @@ public function getObjects(): array return $this->objects; } + public function findObject(int|MiroObject $object): false|MiroObject + { + $searchId = is_int($object) ? $object : $object->getId(); + foreach ($this->objects as $existing) { + if ($existing->getId() === $searchId) { + return $existing; + } + } + + return false; + } + /** * @param MiroClipboardData $clipboardData * diff --git a/src/Objects/MiroLine.php b/src/Objects/MiroLine.php index 14c6c10..a057c1b 100644 --- a/src/Objects/MiroLine.php +++ b/src/Objects/MiroLine.php @@ -7,10 +7,12 @@ use MiroClipboard\MiroClipboardData; use MiroClipboard\MiroWidget; use MiroClipboard\Styles\MiroLineStyle; +use MiroClipboard\Utility\HasGroup; use MiroClipboard\Utility\SetPropertiesFromArray; class MiroLine extends MiroWidget { + use HasGroup; use SetPropertiesFromArray; private MiroLineStyle $lineStyle; diff --git a/src/Objects/MiroShape.php b/src/Objects/MiroShape.php index a6b389b..0e06143 100644 --- a/src/Objects/MiroShape.php +++ b/src/Objects/MiroShape.php @@ -6,10 +6,12 @@ use MiroClipboard\Enums\WidgetType; use MiroClipboard\MiroWidget; use MiroClipboard\Styles\MiroShapeStyle; +use MiroClipboard\Utility\HasGroup; use MiroClipboard\Utility\SetPropertiesFromArray; class MiroShape extends MiroWidget { + use HasGroup; use SetPropertiesFromArray; private MiroShapeStyle $shapeStyle; diff --git a/src/Utility/HasGroup.php b/src/Utility/HasGroup.php new file mode 100644 index 0000000..2b3b147 --- /dev/null +++ b/src/Utility/HasGroup.php @@ -0,0 +1,46 @@ +group; + } + + /** + * @internal + */ + public function setGroup(MiroGroup $group): static + { + $this->group = $group; + + return $this; + } + + /** + * @param MiroClipboardData $clipboardData + * + * @return void + * + * @internal + */ + public function resolveGroupClipboardDataReferences(MiroClipboardData $clipboardData): void + { + foreach ($clipboardData->getObjects() as $object) { + if ($object instanceof MiroGroup) { + if (in_array($this->getId(), $found = $object->toArray()['items'])) { + if (in_array(HasGroup::class, class_uses(static::class))) { + $this->group = $object; + } + } + } + } + } +} diff --git a/src/Utility/MiroUtility.php b/src/Utility/MiroUtility.php index 5285fcd..f88fb20 100644 --- a/src/Utility/MiroUtility.php +++ b/src/Utility/MiroUtility.php @@ -2,6 +2,8 @@ namespace MiroClipboard\MiroUtility; +use MiroClipboard\Exceptions\InvalidDataException; + use function MiroClipboard\Utility\stringToByteArray; function encodeMiroData(array $data, int $encodingOffset = 59): string @@ -21,9 +23,12 @@ function encodeMiroData(array $data, int $encodingOffset = 59): string function decodeMiroDataString(string $text, int $encodingOffset = 59): array { - $isHtml = preg_match('/(?:^.*?\(miro-data-v[0-9]+\))(.*?)(?:\(\/miro-data-v[0-9]+\).*?$)/', $text, $matches); + $isHtml = preg_match('/(?:^.*?\(miro-data-v[0-9]+\))(.*?)(?:\(\/miro-data-v[0-9]+\).*?$)/m', $text, $matches); + if ($isHtml) { $text = $matches[1]; + } else { + throw new InvalidDataException(); } $text = base64_decode($text); diff --git a/src/Utility/SetPropertiesFromArray.php b/src/Utility/SetPropertiesFromArray.php index fa7edb4..4d6ef8b 100644 --- a/src/Utility/SetPropertiesFromArray.php +++ b/src/Utility/SetPropertiesFromArray.php @@ -32,6 +32,8 @@ enum_exists($enumName = $propType->getName()) && ) { /** @phpstan-ignore-next-line */ $this->$key = $enumName::from($val); + } elseif ($propType->allowsNull() && empty($val)) { + $this->$key = null; } else { $this->$key = $val; } diff --git a/tests/Unit/MiroGroupTest.php b/tests/Unit/MiroGroupTest.php index e1d0dfc..b18ee5f 100644 --- a/tests/Unit/MiroGroupTest.php +++ b/tests/Unit/MiroGroupTest.php @@ -8,22 +8,35 @@ test('create group from array', function() { $clipboardData = MiroClipboardData::make() ->addGroup([ - MiroWidget::make()->shape()->text('hello')->position(100, 100), - MiroWidget::make()->shape()->text('world')->position(200, 200), + $shape1 = MiroWidget::make()->shape()->text('hello')->position(100, 100), + $shape2 = MiroWidget::make()->shape()->text('world')->position(200, 200), ]); expect($clipboardData->getObjects())->toHaveCount(3); + + expect($shape1->group())->toEqual($clipboardData->getObjects()[2]); + expect($shape2->group())->toEqual($clipboardData->getObjects()[2]); }); test('create group from object', function() { $group = MiroGroup::make() - ->add(MiroWidget::make()->shape()->text('test1')->position(100, 100)) - ->add(MiroWidget::make()->shape()->text('test2')->position(200, 200)); + ->add($shape1 = MiroWidget::make()->shape()->text('test1')->position(100, 100)) + ->add($shape2 = MiroWidget::make()->shape()->text('test2')->position(200, 200)); $clipboardData = MiroClipboardData::make()->addObject($group); expect($clipboardData->getObjects())->toHaveCount(3); expect(array_map(fn($x) => get_class($x), $clipboardData->getObjects()))->toBe([MiroShape::class, MiroShape::class, MiroGroup::class]); + + expect($shape1->group())->toEqual($group); + expect($shape2->group())->toEqual($group); +}); + +test('can get group of object', function() { + $group = MiroGroup::make() + ->add($shape = MiroWidget::make()->shape()->text('test1')->position(100, 100)); + + expect($shape->group())->toEqual($group); }); test('group to array', function() { @@ -34,3 +47,13 @@ expect($group->toArray()['items'])->toBe([123, 456, 987]); }); + +test('find object in group', function() { + $group = MiroGroup::make(); + + expect($group->findObject(123))->toBeFalse(); + + $group->add(MiroWidget::make()->id(123)->shape()->text('test1')->position(100, 100)); + + expect($group->findObject(123))->toBeInstanceOf(MiroShape::class); +}); diff --git a/tests/Unit/ParserTest.php b/tests/Unit/ParserTest.php index 5f25b92..ab190fb 100644 --- a/tests/Unit/ParserTest.php +++ b/tests/Unit/ParserTest.php @@ -20,6 +20,7 @@ use MiroClipboard\Parsers\ShapeParser; use MiroClipboard\Styles\MiroLineStyle; use MiroClipboard\Styles\MiroShapeStyle; +use MiroClipboard\Utility\SetPropertiesFromArray; test('parse string', function() { $shape = MiroWidget::make()->shape()->position(1.1, 1.1); @@ -142,6 +143,19 @@ expect(invade($parsedData->getObjects()[2])->objects)->toHaveCount(2); }); +test('parsed objects part of a group have a group reference', function() { + $clipboardData = MiroClipboardData::make() + ->addGroup(MiroGroup::make() + ->add(MiroWidget::make()->shape()->text('hello')->position(100, 100)) + ); + + $parsedData = MiroClipboardData::parse($clipboardData->toHTML()); + + expect($shape = $parsedData->getObjects()[0])->toBeInstanceOf(MiroShape::class); + + expect($shape->group())->toEqual($parsedData->getObjects()[1]); +}); + test('line object to and from objects are set', function() { $clipboardData = MiroClipboardData::make() ->addObject(MiroLine::make()->id(1) @@ -159,3 +173,19 @@ expect(invade($line)->fromObject)->toBeInstanceOf(MiroShape::class); expect(invade($line)->toObject)->toBeInstanceOf(MiroShape::class); }); + +test('throws with invalid data', function() { + MiroParser::parse('test'); +})->throws(\MiroClipboard\Exceptions\InvalidDataException::class); + +test('parsing empty string into nullable field', function() { + $instance = new class() + { + public ?string $field; + use SetPropertiesFromArray; + }; + + $instance->setPropertiesFromArray(['field' => '']); + + expect($instance->field)->toBeNull(); +});