diff --git a/docs/changes/1.x/1.3.0.md b/docs/changes/1.x/1.3.0.md index a843c80bb1..a52e16e339 100644 --- a/docs/changes/1.x/1.3.0.md +++ b/docs/changes/1.x/1.3.0.md @@ -12,6 +12,7 @@ - RTF Writer : Support for Table Border Style fixing [#345](https://github.com/PHPOffice/PHPWord/issues/345) by [@Progi1984](https://github.com/Progi1984) in [#2656](https://github.com/PHPOffice/PHPWord/pull/2656) - Word2007 Reader: Support the page break () by [@stanolacko](https://github.com/stanolacko) in [#2662](https://github.com/PHPOffice/PHPWord/pull/2662) - MsDoc Reader: Support for UTF-8 characters by [@Progi1984] fixing [#881](https://github.com/PHPOffice/PHPWord/issues/881), [#1454](https://github.com/PHPOffice/PHPWord/issues/1454), [#1817](https://github.com/PHPOffice/PHPWord/issues/1817), [#1927](https://github.com/PHPOffice/PHPWord/issues/1927), [#2383](https://github.com/PHPOffice/PHPWord/issues/2383), [#2565](https://github.com/PHPOffice/PHPWord/issues/2565) in [#2664](https://github.com/PHPOffice/PHPWord/pull/2664) +- Word2007 Writer: Added support for multiples comment for the same text by [@rodrigoq](https://github.com/rodrigoq) fixing [#2109](https://github.com/PHPOffice/PHPWord/issues/2109) in [#2665](https://github.com/PHPOffice/PHPWord/pull/2665) ### Bug fixes diff --git a/samples/Sample_37_Comments.php b/samples/Sample_37_Comments.php index 79647478e8..8254f35c6d 100644 --- a/samples/Sample_37_Comments.php +++ b/samples/Sample_37_Comments.php @@ -42,7 +42,7 @@ $imageComment->addText('Hey, Mars does look '); $imageComment->addText('red', ['color' => 'FF0000']); $phpWord->addComment($commentOnImage); -$image = $section->addImage('resources/_mars.jpg'); +$image = $section->addImage(__DIR__ . '/resources/_mars.jpg'); $image->setCommentRangeStart($commentOnImage); $section->addTextBreak(2); @@ -56,6 +56,21 @@ $comment1->setEndElement($anotherText); $phpWord->addComment($comment1); +// We can also do things the other way round, link the comment to the element +$lastText = $section->addText('with a last text and two comments'); + +$comment1 = new \PhpOffice\PhpWord\Element\Comment('Authors name', new \DateTime(), 'my_initials'); +$comment1->addText('Comment 1', ['bold' => true]); +$comment1->setStartElement($lastText); +$comment1->setEndElement($lastText); +$phpWord->addComment($comment1); + +$comment2 = new \PhpOffice\PhpWord\Element\Comment('Authors name', new \DateTime(), 'my_initials'); +$comment2->addText('Comment 2', ['bold' => true]); +$comment2->setStartElement($lastText); +$comment2->setEndElement($lastText); +$phpWord->addComment($comment2); + // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); if (!CLI) { diff --git a/src/PhpWord/Collection/AbstractCollection.php b/src/PhpWord/Collection/AbstractCollection.php index 78b5b891b8..646489d886 100644 --- a/src/PhpWord/Collection/AbstractCollection.php +++ b/src/PhpWord/Collection/AbstractCollection.php @@ -21,22 +21,23 @@ * Collection abstract class. * * @since 0.10.0 + * @template T */ abstract class AbstractCollection { /** * Items. * - * @var \PhpOffice\PhpWord\Element\AbstractContainer[] + * @var T[] */ private $items = []; /** * Get items. * - * @return \PhpOffice\PhpWord\Element\AbstractContainer[] + * @return T[] */ - public function getItems() + public function getItems(): array { return $this->items; } @@ -44,11 +45,9 @@ public function getItems() /** * Get item by index. * - * @param int $index - * - * @return ?\PhpOffice\PhpWord\Element\AbstractContainer + * @return ?T */ - public function getItem($index) + public function getItem(int $index) { if (array_key_exists($index, $this->items)) { return $this->items[$index]; @@ -60,10 +59,9 @@ public function getItem($index) /** * Set item. * - * @param int $index - * @param ?\PhpOffice\PhpWord\Element\AbstractContainer $item + * @param ?T $item */ - public function setItem($index, $item): void + public function setItem(int $index, $item): void { if (array_key_exists($index, $this->items)) { $this->items[$index] = $item; @@ -73,11 +71,9 @@ public function setItem($index, $item): void /** * Add new item. * - * @param \PhpOffice\PhpWord\Element\AbstractContainer $item - * - * @return int + * @param T $item */ - public function addItem($item) + public function addItem($item): int { $index = $this->countItems(); $this->items[$index] = $item; @@ -87,10 +83,8 @@ public function addItem($item) /** * Get item count. - * - * @return int */ - public function countItems() + public function countItems(): int { return count($this->items); } diff --git a/src/PhpWord/Collection/Bookmarks.php b/src/PhpWord/Collection/Bookmarks.php index e7d9b4a384..71544c9469 100644 --- a/src/PhpWord/Collection/Bookmarks.php +++ b/src/PhpWord/Collection/Bookmarks.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Bookmark; + /** * Bookmarks collection. * * @since 0.12.0 + * @extends AbstractCollection */ class Bookmarks extends AbstractCollection { diff --git a/src/PhpWord/Collection/Charts.php b/src/PhpWord/Collection/Charts.php index bb63a13962..7c2dfbab94 100644 --- a/src/PhpWord/Collection/Charts.php +++ b/src/PhpWord/Collection/Charts.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Chart; + /** * Charts collection. * * @since 0.12.0 + * @extends AbstractCollection */ class Charts extends AbstractCollection { diff --git a/src/PhpWord/Collection/Comments.php b/src/PhpWord/Collection/Comments.php index 8c6b577d7e..5fa4020a5a 100644 --- a/src/PhpWord/Collection/Comments.php +++ b/src/PhpWord/Collection/Comments.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Comment; + /** * Comments collection. * * @since 0.12.0 + * @extends AbstractCollection */ class Comments extends AbstractCollection { diff --git a/src/PhpWord/Collection/Endnotes.php b/src/PhpWord/Collection/Endnotes.php index 362b25881d..09903b1bf6 100644 --- a/src/PhpWord/Collection/Endnotes.php +++ b/src/PhpWord/Collection/Endnotes.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Endnote; + /** * Endnotes collection. * * @since 0.10.0 + * @extends AbstractCollection */ class Endnotes extends AbstractCollection { diff --git a/src/PhpWord/Collection/Footnotes.php b/src/PhpWord/Collection/Footnotes.php index 76eae3eab3..0387fce3c7 100644 --- a/src/PhpWord/Collection/Footnotes.php +++ b/src/PhpWord/Collection/Footnotes.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Footnote; + /** * Footnotes collection. * * @since 0.10.0 + * @extends AbstractCollection */ class Footnotes extends AbstractCollection { diff --git a/src/PhpWord/Collection/Titles.php b/src/PhpWord/Collection/Titles.php index 7b795771e8..543aabda1d 100644 --- a/src/PhpWord/Collection/Titles.php +++ b/src/PhpWord/Collection/Titles.php @@ -17,10 +17,13 @@ namespace PhpOffice\PhpWord\Collection; +use PhpOffice\PhpWord\Element\Title; + /** * Titles collection. * * @since 0.10.0 + * @extends AbstractCollection */ class Titles extends AbstractCollection { diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 9f9c2e82aa..385e4d3140 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -19,8 +19,10 @@ use DateTime; use InvalidArgumentException; +use PhpOffice\PhpWord\Collection\Comments; use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Style; /** * Element abstract class. @@ -32,7 +34,7 @@ abstract class AbstractElement /** * PhpWord object. * - * @var ?\PhpOffice\PhpWord\PhpWord + * @var ?PhpWord */ protected $phpWord; @@ -131,25 +133,25 @@ abstract class AbstractElement protected $collectionRelation = false; /** - * The start position for the linked comment. + * The start position for the linked comments. * - * @var Comment + * @var Comments */ - protected $commentRangeStart; + protected $commentsRangeStart; /** - * The end position for the linked comment. + * The end position for the linked comments. * - * @var Comment + * @var Comments */ - protected $commentRangeEnd; + protected $commentsRangeEnd; /** * Get PhpWord. * - * @return ?\PhpOffice\PhpWord\PhpWord + * @return ?PhpWord */ - public function getPhpWord() + public function getPhpWord(): ?PhpWord { return $this->phpWord; } @@ -287,14 +289,28 @@ public function getNestedLevel() return $this->nestedLevel; } + /** + * Get comments start. + * + * @return Comments + */ + public function getCommentsRangeStart(): ?Comments + { + return $this->commentsRangeStart; + } + /** * Get comment start. * * @return Comment */ - public function getCommentRangeStart() + public function getCommentRangeStart(): ?Comment { - return $this->commentRangeStart; + if ($this->commentsRangeStart != null) { + return $this->commentsRangeStart->getItem($this->commentsRangeStart->countItems()); + } + + return null; } /** @@ -305,8 +321,30 @@ public function setCommentRangeStart(Comment $value): void if ($this instanceof Comment) { throw new InvalidArgumentException('Cannot set a Comment on a Comment'); } - $this->commentRangeStart = $value; - $this->commentRangeStart->setStartElement($this); + if ($this->commentsRangeStart == null) { + $this->commentsRangeStart = new Comments(); + } + // Set ID early to avoid duplicates. + if ($value->getElementId() == null) { + $value->setElementId(); + } + foreach ($this->commentsRangeStart->getItems() as $comment) { + if ($value->getElementId() == $comment->getElementId()) { + return; + } + } + $idxItem = $this->commentsRangeStart->addItem($value); + $this->commentsRangeStart->getItem($idxItem)->setStartElement($this); + } + + /** + * Get comments end. + * + * @return Comments + */ + public function getCommentsRangeEnd(): ?Comments + { + return $this->commentsRangeEnd; } /** @@ -314,9 +352,13 @@ public function setCommentRangeStart(Comment $value): void * * @return Comment */ - public function getCommentRangeEnd() + public function getCommentRangeEnd(): ?Comment { - return $this->commentRangeEnd; + if ($this->commentsRangeEnd != null) { + return $this->commentsRangeEnd->getItem($this->commentsRangeEnd->countItems()); + } + + return null; } /** @@ -327,8 +369,20 @@ public function setCommentRangeEnd(Comment $value): void if ($this instanceof Comment) { throw new InvalidArgumentException('Cannot set a Comment on a Comment'); } - $this->commentRangeEnd = $value; - $this->commentRangeEnd->setEndElement($this); + if ($this->commentsRangeEnd == null) { + $this->commentsRangeEnd = new Comments(); + } + // Set ID early to avoid duplicates. + if ($value->getElementId() == null) { + $value->setElementId(); + } + foreach ($this->commentsRangeEnd->getItems() as $comment) { + if ($value->getElementId() == $comment->getElementId()) { + return; + } + } + $idxItem = $this->commentsRangeEnd->addItem($value); + $this->commentsRangeEnd->getItem($idxItem)->setEndElement($this); } /** @@ -428,7 +482,7 @@ public function isInSection() * Set new style value. * * @param mixed $styleObject Style object - * @param null|array|\PhpOffice\PhpWord\Style|string $styleValue Style value + * @param null|array|string|Style $styleValue Style value * @param bool $returnObject Always return object * * @return mixed diff --git a/src/PhpWord/Element/Comment.php b/src/PhpWord/Element/Comment.php index 7e7c5241fa..9173c49148 100644 --- a/src/PhpWord/Element/Comment.php +++ b/src/PhpWord/Element/Comment.php @@ -83,9 +83,7 @@ public function getInitials() public function setStartElement(AbstractElement $value): void { $this->startElement = $value; - if ($value->getCommentRangeStart() == null) { - $value->setCommentRangeStart($this); - } + $value->setCommentRangeStart($this); } /** @@ -104,9 +102,7 @@ public function getStartElement() public function setEndElement(AbstractElement $value): void { $this->endElement = $value; - if ($value->getCommentRangeEnd() == null) { - $value->setCommentRangeEnd($this); - } + $value->setCommentRangeEnd($this); } /** diff --git a/src/PhpWord/PhpWord.php b/src/PhpWord/PhpWord.php index a7aa95ce45..cf6f16ae02 100644 --- a/src/PhpWord/PhpWord.php +++ b/src/PhpWord/PhpWord.php @@ -134,7 +134,6 @@ public function __call($function, $args) if (in_array($function, $addCollection)) { $key = ucfirst(str_replace('add', '', $function) . 's'); - /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collectionObject */ $collectionObject = $this->collections[$key]; return $collectionObject->addItem($args[0] ?? null); diff --git a/src/PhpWord/Writer/Word2007.php b/src/PhpWord/Writer/Word2007.php index ab4fd1e3eb..e7801c04c2 100644 --- a/src/PhpWord/Writer/Word2007.php +++ b/src/PhpWord/Writer/Word2007.php @@ -227,7 +227,6 @@ private function addNotes(ZipArchive $zip, &$rId, $noteType = 'footnote'): void $collection = $phpWord->$method(); // Add footnotes media files, relations, and contents - /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */ if ($collection->countItems() > 0) { $media = Media::getElements($noteType); $this->addFilesToPackage($zip, $media); @@ -260,7 +259,6 @@ private function addComments(ZipArchive $zip, &$rId): void $partName = 'comments'; // Add comment relations and contents - /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */ if ($collection->countItems() > 0) { $this->relationships[] = ['target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId]; diff --git a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php index abd1324aad..b677556d62 100644 --- a/src/PhpWord/Writer/Word2007/Element/AbstractElement.php +++ b/src/PhpWord/Writer/Word2007/Element/AbstractElement.php @@ -126,14 +126,10 @@ protected function endElementP(): void */ protected function writeCommentRangeStart(): void { - if ($this->element->getCommentRangeStart() != null) { - $comment = $this->element->getCommentRangeStart(); - //only set the ID if it is not yet set, otherwise it will overwrite it - if ($comment->getElementId() == null) { - $comment->setElementId(); + if ($this->element->getCommentsRangeStart() != null) { + foreach ($this->element->getCommentsRangeStart()->getItems() as $comment) { + $this->xmlWriter->writeElementBlock('w:commentRangeStart', ['w:id' => $comment->getElementId()]); } - - $this->xmlWriter->writeElementBlock('w:commentRangeStart', ['w:id' => $comment->getElementId()]); } } @@ -142,28 +138,23 @@ protected function writeCommentRangeStart(): void */ protected function writeCommentRangeEnd(): void { - if ($this->element->getCommentRangeEnd() != null) { - $comment = $this->element->getCommentRangeEnd(); - //only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen - if ($comment->getElementId() == null) { - $comment->setElementId(); // @codeCoverageIgnore - } // @codeCoverageIgnore - - $this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]); - $this->xmlWriter->startElement('w:r'); - $this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]); - $this->xmlWriter->endElement(); - } elseif ($this->element->getCommentRangeStart() != null && $this->element->getCommentRangeStart()->getEndElement() == null) { - $comment = $this->element->getCommentRangeStart(); - //only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen - if ($comment->getElementId() == null) { - $comment->setElementId(); // @codeCoverageIgnore - } // @codeCoverageIgnore - - $this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]); - $this->xmlWriter->startElement('w:r'); - $this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]); - $this->xmlWriter->endElement(); + if ($this->element->getCommentsRangeEnd() != null) { + foreach ($this->element->getCommentsRangeEnd()->getItems() as $comment) { + $this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]); + $this->xmlWriter->startElement('w:r'); + $this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]); + $this->xmlWriter->endElement(); + } + } + if ($this->element->getCommentsRangeStart() != null) { + foreach ($this->element->getCommentsRangeStart()->getItems() as $comment) { + if ($comment->getEndElement() == null) { + $this->xmlWriter->writeElementBlock('w:commentRangeEnd', ['w:id' => $comment->getElementId()]); + $this->xmlWriter->startElement('w:r'); + $this->xmlWriter->writeElementBlock('w:commentReference', ['w:id' => $comment->getElementId()]); + $this->xmlWriter->endElement(); + } + } } } diff --git a/tests/PhpWordTests/Element/CommentTest.php b/tests/PhpWordTests/Element/CommentTest.php index fe346307c4..b4d9cc6a5d 100644 --- a/tests/PhpWordTests/Element/CommentTest.php +++ b/tests/PhpWordTests/Element/CommentTest.php @@ -20,6 +20,7 @@ use DateTime; use InvalidArgumentException; use PhpOffice\PhpWord\Element\Comment; +use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Element\Text; /** @@ -51,6 +52,39 @@ public function testConstructDefault(): void self::assertEquals($oText, $oComment->getEndElement()); } + /** + * Two comments on same text. + */ + public function testTwoCommentsOnSameText(): void + { + $section = new Section(0); + $text = $section->addText('Text'); + + $comment1 = new Comment('Author1', new DateTime(), 'A1'); + $comment1->addText('Comment1'); + + $comment2 = new Comment('Author2', new DateTime(), 'A2'); + $comment2->addText('Comment2'); + + $comment1->setStartElement($text); + $comment2->setStartElement($text); + + $text->setCommentRangeStart($comment1); + $text->setCommentRangeEnd($comment1); + + $text->setCommentRangeStart($comment2); + $text->setCommentRangeEnd($comment2); + + self::assertEquals(2, $text->getCommentsRangeStart()->countItems()); + self::assertEquals(2, $text->getCommentsRangeEnd()->countItems()); + + self::assertEquals($text->getCommentsRangeStart()->getItem(0)->getElementId(), $comment1->getElementId()); + self::assertEquals($text->getCommentsRangeEnd()->getItem(0)->getElementId(), $comment1->getElementId()); + + self::assertEquals($text->getCommentsRangeStart()->getItem(1)->getElementId(), $comment2->getElementId()); + self::assertEquals($text->getCommentsRangeEnd()->getItem(1)->getElementId(), $comment2->getElementId()); + } + /** * Add text. */