diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f8b2da00..f82759dc10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ v0.15.0 (?? ??? 2018) ### Added - Parsing of "align" HTML attribute - @troosan #1231 - Parse formatting inside HTML lists - @troosan @samimussbach #1239 #945 #1215 #508 +- Add support for Track changes @Cip @troosan #354 #1262 ### Fixed - fix reading of docx default style - @troosan #1238 diff --git a/docs/elements.rst b/docs/elements.rst index fe67330489..7df3b16359 100644 --- a/docs/elements.rst +++ b/docs/elements.rst @@ -31,7 +31,7 @@ column shows the containers while the rows lists the elements. +-------+-----------------+-----------+----------+----------+---------+------------+------------+ | 11 | Watermark | - | v | - | - | - | - | +-------+-----------------+-----------+----------+----------+---------+------------+------------+ -| 12 | Object | v | v | v | v | v | v | +| 12 | OLEObject | v | v | v | v | v | v | +-------+-----------------+-----------+----------+----------+---------+------------+------------+ | 13 | TOC | v | - | - | - | - | - | +-------+-----------------+-----------+----------+----------+---------+------------+------------+ @@ -77,6 +77,13 @@ italics, etc) or other elements, e.g. images or links. The syntaxes are as follo For available styling options see :ref:`font-style` and :ref:`paragraph-style`. +If you want to enable track changes on added text you can mark it as INSERTED or DELETED by a specific user at a given time: + +.. code-block:: php + + $text = $section->addText('Hello World!'); + $text->setChanged(\PhpOffice\PhpWord\Element\ChangedElement::TYPE_INSERTED, 'Fred', (new \DateTime())); + Titles ~~~~~~ @@ -276,11 +283,11 @@ Objects ------- You can add OLE embeddings, such as Excel spreadsheets or PowerPoint -presentations to the document by using ``addObject`` method. +presentations to the document by using ``addOLEObject`` method. .. code-block:: php - $section->addObject($src, [$style]); + $section->addOLEObject($src, [$style]); Table of contents ----------------- @@ -309,7 +316,7 @@ Footnotes & endnotes You can create footnotes with ``addFootnote`` and endnotes with ``addEndnote`` in texts or textruns, but it's recommended to use textrun to have better layout. You can use ``addText``, ``addLink``, -``addTextBreak``, ``addImage``, ``addObject`` on footnotes and endnotes. +``addTextBreak``, ``addImage``, ``addOLEObject`` on footnotes and endnotes. On textrun: @@ -465,4 +472,28 @@ The comment can contain formatted text. Once the comment has been added, it can // link the comment to the text you just created $text->setCommentStart($comment); -If no end is set for a comment using the ``setCommentEnd``, the comment will be ended automatically at the end of the element it is started on. \ No newline at end of file +If no end is set for a comment using the ``setCommentEnd``, the comment will be ended automatically at the end of the element it is started on. + +Track Changes +------------- + +Track changes can be set on text elements. There are 2 ways to set the change information on an element. +Either by calling the `setChangeInfo()`, or by setting the `TrackChange` instance on the element with `setTrackChange()`. + +.. code-block:: php + $phpWord = new \PhpOffice\PhpWord\PhpWord(); + + // New portrait section + $section = $phpWord->addSection(); + $textRun = $section->addTextRun(); + + $text = $textRun->addText('Hello World! Time to '); + + $text = $textRun->addText('wake ', array('bold' => true)); + $text->setChangeInfo(TrackChange::INSERTED, 'Fred', time() - 1800); + + $text = $textRun->addText('up'); + $text->setTrackChange(new TrackChange(TrackChange::INSERTED, 'Fred')); + + $text = $textRun->addText('go to sleep'); + $text->setChangeInfo(TrackChange::DELETED, 'Barney', new \DateTime('@' . (time() - 3600))); diff --git a/samples/Sample_16_Object.php b/samples/Sample_16_Object.php index 8b05b9e8ac..c4db7f6106 100644 --- a/samples/Sample_16_Object.php +++ b/samples/Sample_16_Object.php @@ -9,7 +9,7 @@ $section = $phpWord->addSection(); $section->addText('You can open this OLE object by double clicking on the icon:'); $section->addTextBreak(2); -$section->addObject('resources/_sheet.xls'); +$section->addOLEObject('resources/_sheet.xls'); // Save file echo write($phpWord, basename(__FILE__, '.php'), $writers); diff --git a/samples/Sample_39_TrackChanges.php b/samples/Sample_39_TrackChanges.php new file mode 100644 index 0000000000..e6a3066864 --- /dev/null +++ b/samples/Sample_39_TrackChanges.php @@ -0,0 +1,29 @@ +addSection(); +$textRun = $section->addTextRun(); + +$text = $textRun->addText('Hello World! Time to '); + +$text = $textRun->addText('wake ', array('bold' => true)); +$text->setChangeInfo(TrackChange::INSERTED, 'Fred', time() - 1800); + +$text = $textRun->addText('up'); +$text->setTrackChange(new TrackChange(TrackChange::INSERTED, 'Fred')); + +$text = $textRun->addText('go to sleep'); +$text->setChangeInfo(TrackChange::DELETED, 'Barney', new \DateTime('@' . (time() - 3600))); + +// Save file +echo write($phpWord, basename(__FILE__, '.php'), $writers); +if (!CLI) { + include_once 'Sample_Footer.php'; +} diff --git a/samples/resources/Sample_11_ReadWord2007.docx b/samples/resources/Sample_11_ReadWord2007.docx index c9a50f485a..3faf6f1213 100644 Binary files a/samples/resources/Sample_11_ReadWord2007.docx and b/samples/resources/Sample_11_ReadWord2007.docx differ diff --git a/samples/resources/Sample_24_ReadODText.odt b/samples/resources/Sample_24_ReadODText.odt index d37c4e6629..59ac16da03 100644 Binary files a/samples/resources/Sample_24_ReadODText.odt and b/samples/resources/Sample_24_ReadODText.odt differ diff --git a/src/PhpWord/Element/AbstractContainer.php b/src/PhpWord/Element/AbstractContainer.php index 28d45672be..4a5f83f536 100644 --- a/src/PhpWord/Element/AbstractContainer.php +++ b/src/PhpWord/Element/AbstractContainer.php @@ -81,7 +81,7 @@ public function __call($function, $args) { $elements = array( 'Text', 'TextRun', 'Bookmark', 'Link', 'PreserveText', 'TextBreak', - 'ListItem', 'ListItemRun', 'Table', 'Image', 'Object', + 'ListItem', 'ListItemRun', 'Table', 'Image', 'Object', 'OLEObject', 'Footnote', 'Endnote', 'CheckBox', 'TextBox', 'Field', 'Line', 'Shape', 'Title', 'TOC', 'PageBreak', 'Chart', 'FormField', 'SDT', 'Comment', @@ -157,7 +157,7 @@ protected function addElement($elementName) /** * Get all elements * - * @return array + * @return \PhpOffice\PhpWord\Element\AbstractElement[] */ public function getElements() { diff --git a/src/PhpWord/Element/AbstractElement.php b/src/PhpWord/Element/AbstractElement.php index 63892b74ea..36c49af0df 100644 --- a/src/PhpWord/Element/AbstractElement.php +++ b/src/PhpWord/Element/AbstractElement.php @@ -93,6 +93,13 @@ abstract class AbstractElement */ private $nestedLevel = 0; + /** + * changed element info + * + * @var TrackChange + */ + private $trackChange; + /** * Parent container type * @@ -425,6 +432,38 @@ protected function setNewStyle($styleObject, $styleValue = null, $returnObject = return $style; } + /** + * Sets the trackChange information + * + * @param TrackChange $trackChange + */ + public function setTrackChange(TrackChange $trackChange) + { + $this->trackChange = $trackChange; + } + + /** + * Gets the trackChange information + * + * @return TrackChange + */ + public function getTrackChange() + { + return $this->trackChange; + } + + /** + * Set changed + * + * @param string $type INSERTED|DELETED + * @param string $author + * @param null|int|\DateTime $date allways in UTC + */ + public function setChangeInfo($type, $author, $date = null) + { + $this->trackChange = new TrackChange($type, $author, $date); + } + /** * Set enum value * diff --git a/src/PhpWord/Element/Comment.php b/src/PhpWord/Element/Comment.php index 188369292c..205ff598b9 100644 --- a/src/PhpWord/Element/Comment.php +++ b/src/PhpWord/Element/Comment.php @@ -55,12 +55,12 @@ class Comment extends TrackChange * Create a new Comment Element * * @param string $author - * @param \DateTime $date + * @param null|\DateTime $date * @param string $initials */ public function __construct($author, $date = null, $initials = null) { - parent::__construct($author, $date); + parent::__construct(null, $author, $date); $this->initials = $initials; } diff --git a/src/PhpWord/Element/TrackChange.php b/src/PhpWord/Element/TrackChange.php index 44327f263f..dde616cc21 100644 --- a/src/PhpWord/Element/TrackChange.php +++ b/src/PhpWord/Element/TrackChange.php @@ -20,14 +20,25 @@ /** * TrackChange element * @see http://datypic.com/sc/ooxml/t-w_CT_TrackChange.html + * @see http://datypic.com/sc/ooxml/t-w_CT_RunTrackChange.html */ class TrackChange extends AbstractContainer { + const INSERTED = 'INSERTED'; + const DELETED = 'DELETED'; + /** * @var string Container type */ protected $container = 'TrackChange'; + /** + * The type of change, (insert or delete), not applicable for PhpOffice\PhpWord\Element\Comment + * + * @var string + */ + private $changeType; + /** * Author * @@ -45,13 +56,17 @@ class TrackChange extends AbstractContainer /** * Create a new TrackChange Element * + * @param string $changeType * @param string $author - * @param \DateTime $date + * @param null|int|\DateTime $date */ - public function __construct($author, \DateTime $date = null) + public function __construct($changeType = null, $author = null, $date = null) { + $this->changeType = $changeType; $this->author = $author; - $this->date = $date; + if ($date !== null) { + $this->date = ($date instanceof \DateTime) ? $date : new \DateTime('@' . $date); + } } /** @@ -73,4 +88,14 @@ public function getDate() { return $this->date; } + + /** + * Get the Change type + * + * @return string + */ + public function getChangeType() + { + return $this->changeType; + } } diff --git a/src/PhpWord/Reader/ODText/Content.php b/src/PhpWord/Reader/ODText/Content.php index 8843d8a276..7a7a046837 100644 --- a/src/PhpWord/Reader/ODText/Content.php +++ b/src/PhpWord/Reader/ODText/Content.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Reader\ODText; use PhpOffice\Common\XMLReader; +use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; /** @@ -37,6 +38,8 @@ public function read(PhpWord $phpWord) $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); + $trackedChanges = array(); + $nodes = $xmlReader->getElements('office:body/office:text/*'); if ($nodes->length > 0) { $section = $phpWord->addSection(); @@ -48,7 +51,37 @@ public function read(PhpWord $phpWord) $section->addTitle($node->nodeValue, $depth); break; case 'text:p': // Paragraph - $section->addText($node->nodeValue); + $children = $node->childNodes; + foreach ($children as $child) { + switch ($child->nodeName) { + case 'text:change-start': + $changeId = $child->getAttribute('text:change-id'); + if (isset($trackedChanges[$changeId])) { + $changed = $trackedChanges[$changeId]; + } + break; + case 'text:change-end': + unset($changed); + break; + case 'text:change': + $changeId = $child->getAttribute('text:change-id'); + if (isset($trackedChanges[$changeId])) { + $changed = $trackedChanges[$changeId]; + } + break; + } + } + + $element = $section->addText($node->nodeValue); + if (isset($changed) && is_array($changed)) { + $element->setTrackChange($changed['changed']); + if (isset($changed['textNodes'])) { + foreach ($changed['textNodes'] as $changedNode) { + $element = $section->addText($changedNode->nodeValue); + $element->setTrackChange($changed['changed']); + } + } + } break; case 'text:list': // List $listItems = $xmlReader->getElements('text:list-item/text:p', $node); @@ -57,6 +90,21 @@ public function read(PhpWord $phpWord) $section->addListItem($listItem->nodeValue, 0); } break; + case 'text:tracked-changes': + $changedRegions = $xmlReader->getElements('text:changed-region', $node); + foreach ($changedRegions as $changedRegion) { + $type = ($changedRegion->firstChild->nodeName == 'text:insertion') ? TrackChange::INSERTED : TrackChange::DELETED; + $creatorNode = $xmlReader->getElements('office:change-info/dc:creator', $changedRegion->firstChild); + $author = $creatorNode[0]->nodeValue; + $dateNode = $xmlReader->getElements('office:change-info/dc:date', $changedRegion->firstChild); + $date = $dateNode[0]->nodeValue; + $date = preg_replace('/\.\d+$/', '', $date); + $date = \DateTime::createFromFormat('Y-m-d\TH:i:s', $date); + $changed = new TrackChange($type, $author, $date); + $textNodes = $xmlReader->getElements('text:deletion/text:p', $changedRegion); + $trackedChanges[$changedRegion->getAttribute('text:id')] = array('changed' => $changed, 'textNodes'=> $textNodes); + } + break; } } } diff --git a/src/PhpWord/Reader/Word2007/AbstractPart.php b/src/PhpWord/Reader/Word2007/AbstractPart.php index 3d853e8f24..366bde7e24 100644 --- a/src/PhpWord/Reader/Word2007/AbstractPart.php +++ b/src/PhpWord/Reader/Word2007/AbstractPart.php @@ -18,6 +18,7 @@ namespace PhpOffice\PhpWord\Reader\Word2007; use PhpOffice\Common\XMLReader; +use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; /** @@ -156,8 +157,10 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa } else { // Text and TextRun $runCount = $xmlReader->countElements('w:r', $domNode); + $insCount = $xmlReader->countElements('w:ins', $domNode); + $delCount = $xmlReader->countElements('w:del', $domNode); $linkCount = $xmlReader->countElements('w:hyperlink', $domNode); - $runLinkCount = $runCount + $linkCount; + $runLinkCount = $runCount + $insCount + $delCount + $linkCount; if (0 == $runLinkCount) { $parent->addTextBreak(null, $paragraphStyle); } else { @@ -185,6 +188,13 @@ protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $pa */ protected function readRun(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart, $paragraphStyle = null) { + if (in_array($domNode->nodeName, array('w:ins', 'w:del'))) { + $nodes = $xmlReader->getElements('*', $domNode); + foreach ($nodes as $node) { + return $this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle); + } + } + if (!in_array($domNode->nodeName, array('w:r', 'w:hyperlink'))) { return; } @@ -228,8 +238,19 @@ protected function readRun(XMLReader $xmlReader, \DOMElement $domNode, $parent, } } else { // TextRun - $textContent = $xmlReader->getValue('w:t', $domNode); - $parent->addText($textContent, $fontStyle, $paragraphStyle); + if ($domNode->parentNode->nodeName == 'w:del') { + $textContent = $xmlReader->getValue('w:delText', $domNode); + } else { + $textContent = $xmlReader->getValue('w:t', $domNode); + } + /** @var AbstractElement $element */ + $element = $parent->addText($textContent, $fontStyle, $paragraphStyle); + if (in_array($domNode->parentNode->nodeName, array('w:ins', 'w:del'))) { + $type = ($domNode->parentNode->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED; + $author = $domNode->parentNode->getAttribute('w:author'); + $date = \DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $domNode->parentNode->getAttribute('w:date')); + $element->setChangeInfo($type, $author, $date); + } } } } diff --git a/src/PhpWord/Reader/Word2007/Styles.php b/src/PhpWord/Reader/Word2007/Styles.php index c6e64e45f5..8719641ec2 100644 --- a/src/PhpWord/Reader/Word2007/Styles.php +++ b/src/PhpWord/Reader/Word2007/Styles.php @@ -56,7 +56,7 @@ public function read(PhpWord $phpWord) if ($paragraphDefaults !== null) { $paragraphDefaultStyle = $this->readParagraphStyle($xmlReader, $paragraphDefaults); if ($paragraphDefaultStyle != null) { - $phpWord->setDefaultParagraphStyle(); + $phpWord->setDefaultParagraphStyle($paragraphDefaultStyle); } } diff --git a/src/PhpWord/Writer/HTML/Element/Text.php b/src/PhpWord/Writer/HTML/Element/Text.php index 71cb75669a..9f8f7773c5 100644 --- a/src/PhpWord/Writer/HTML/Element/Text.php +++ b/src/PhpWord/Writer/HTML/Element/Text.php @@ -17,6 +17,7 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; +use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; @@ -121,6 +122,9 @@ protected function writeOpening() $content .= "
";
}
+ //open track change tag
+ $content .= $this->writeTrackChangeOpening();
+
return $content;
}
@@ -132,6 +136,10 @@ protected function writeOpening()
protected function writeClosing()
{
$content = '';
+
+ //close track change tag
+ $content .= $this->writeTrackChangeClosing();
+
if (!$this->withoutP) {
if (Settings::isOutputEscapingEnabled()) {
$content .= $this->escaper->escapeHtml($this->closingText);
@@ -145,6 +153,63 @@ protected function writeClosing()
return $content;
}
+ /**
+ * writes the track change opening tag
+ *
+ * @return string the HTML, an empty string if no track change information
+ */
+ private function writeTrackChangeOpening()
+ {
+ $changed = $this->element->getTrackChange();
+ if ($changed == null) {
+ return '';
+ }
+
+ $content = '';
+ if (($changed->getChangeType() == TrackChange::INSERTED)) {
+ $content .= 'getChangeType() == TrackChange::DELETED) {
+ $content .= ' array('author'=> $changed->getAuthor(), 'id' => $this->element->getElementId()));
+ if ($changed->getDate() != null) {
+ $changedProp['changed']['date'] = $changed->getDate()->format('Y-m-d\TH:i:s\Z');
+ }
+ $content .= json_encode($changedProp);
+ $content .= '\' ';
+ $content .= 'title="' . $changed->getAuthor();
+ if ($changed->getDate() != null) {
+ $dateUser = $changed->getDate()->format('Y-m-d H:i:s');
+ $content .= ' - ' . $dateUser;
+ }
+ $content .= '">';
+
+ return $content;
+ }
+
+ /**
+ * writes the track change closing tag
+ *
+ * @return string the HTML, an empty string if no track change information
+ */
+ private function writeTrackChangeClosing()
+ {
+ $changed = $this->element->getTrackChange();
+ if ($changed == null) {
+ return '';
+ }
+
+ $content = '';
+ if (($changed->getChangeType() == TrackChange::INSERTED)) {
+ $content .= '';
+ } elseif ($changed->getChangeType() == TrackChange::DELETED) {
+ $content .= '';
+ }
+
+ return $content;
+ }
+
/**
* Write paragraph style
*
diff --git a/src/PhpWord/Writer/ODText/Element/Text.php b/src/PhpWord/Writer/ODText/Element/Text.php
index 1fc0b80028..f9259fc501 100644
--- a/src/PhpWord/Writer/ODText/Element/Text.php
+++ b/src/PhpWord/Writer/ODText/Element/Text.php
@@ -17,6 +17,7 @@
namespace PhpOffice\PhpWord\Writer\ODText\Element;
+use PhpOffice\PhpWord\Element\TrackChange;
use PhpOffice\PhpWord\Exception\Exception;
/**
@@ -51,29 +52,50 @@ public function write()
if (!$this->withoutP) {
$xmlWriter->startElement('text:p'); // text:p
}
- if (empty($fontStyle)) {
- if (empty($paragraphStyle)) {
- $xmlWriter->writeAttribute('text:style-name', 'P1');
- } elseif (is_string($paragraphStyle)) {
- $xmlWriter->writeAttribute('text:style-name', $paragraphStyle);
- }
- $this->writeText($element->getText());
+ if ($element->getTrackChange() != null && $element->getTrackChange()->getChangeType() == TrackChange::DELETED) {
+ $xmlWriter->startElement('text:change');
+ $xmlWriter->writeAttribute('text:change-id', $element->getTrackChange()->getElementId());
+ $xmlWriter->endElement();
} else {
- if (empty($paragraphStyle)) {
- $xmlWriter->writeAttribute('text:style-name', 'Standard');
- } elseif (is_string($paragraphStyle)) {
- $xmlWriter->writeAttribute('text:style-name', $paragraphStyle);
- }
- // text:span
- $xmlWriter->startElement('text:span');
- if (is_string($fontStyle)) {
- $xmlWriter->writeAttribute('text:style-name', $fontStyle);
+ if (empty($fontStyle)) {
+ if (empty($paragraphStyle)) {
+ $xmlWriter->writeAttribute('text:style-name', 'P1');
+ } elseif (is_string($paragraphStyle)) {
+ $xmlWriter->writeAttribute('text:style-name', $paragraphStyle);
+ }
+ $this->writeChangeInsertion(true, $element->getTrackChange());
+ $this->writeText($element->getText());
+ $this->writeChangeInsertion(false, $element->getTrackChange());
+ } else {
+ if (empty($paragraphStyle)) {
+ $xmlWriter->writeAttribute('text:style-name', 'Standard');
+ } elseif (is_string($paragraphStyle)) {
+ $xmlWriter->writeAttribute('text:style-name', $paragraphStyle);
+ }
+ // text:span
+ $xmlWriter->startElement('text:span');
+ if (is_string($fontStyle)) {
+ $xmlWriter->writeAttribute('text:style-name', $fontStyle);
+ }
+ $this->writeChangeInsertion(true, $element->getTrackChange());
+ $this->writeText($element->getText());
+ $this->writeChangeInsertion(false, $element->getTrackChange());
+ $xmlWriter->endElement();
}
- $this->writeText($element->getText());
- $xmlWriter->endElement();
}
if (!$this->withoutP) {
$xmlWriter->endElement(); // text:p
}
}
+
+ private function writeChangeInsertion($start = true, TrackChange $trackChange = null)
+ {
+ if ($trackChange == null || $trackChange->getChangeType() != TrackChange::INSERTED) {
+ return;
+ }
+ $xmlWriter = $this->getXmlWriter();
+ $xmlWriter->startElement('text:change-' . ($start ? 'start' : 'end'));
+ $xmlWriter->writeAttribute('text:change-id', $trackChange->getElementId());
+ $xmlWriter->endElement();
+ }
}
diff --git a/src/PhpWord/Writer/ODText/Part/Content.php b/src/PhpWord/Writer/ODText/Part/Content.php
index 8ae4dca9c7..19d3e54ab0 100644
--- a/src/PhpWord/Writer/ODText/Part/Content.php
+++ b/src/PhpWord/Writer/ODText/Part/Content.php
@@ -18,10 +18,12 @@
namespace PhpOffice\PhpWord\Writer\ODText\Part;
use PhpOffice\Common\XMLWriter;
+use PhpOffice\PhpWord\Element\AbstractContainer;
use PhpOffice\PhpWord\Element\Image;
use PhpOffice\PhpWord\Element\Table;
use PhpOffice\PhpWord\Element\Text;
use PhpOffice\PhpWord\Element\TextRun;
+use PhpOffice\PhpWord\Element\TrackChange;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Style;
use PhpOffice\PhpWord\Style\Font;
@@ -74,6 +76,40 @@ public function write()
$xmlWriter->startElement('office:body');
$xmlWriter->startElement('office:text');
+ // Tracked changes declarations
+ $trackedChanges = array();
+ $sections = $phpWord->getSections();
+ foreach ($sections as $section) {
+ $this->collectTrackedChanges($section, $trackedChanges);
+ }
+ $xmlWriter->startElement('text:tracked-changes');
+ foreach ($trackedChanges as $trackedElement) {
+ $trackedChange = $trackedElement->getTrackChange();
+ $xmlWriter->startElement('text:changed-region');
+ $trackedChange->setElementId();
+ $xmlWriter->writeAttribute('text:id', $trackedChange->getElementId());
+
+ if (($trackedChange->getChangeType() == TrackChange::INSERTED)) {
+ $xmlWriter->startElement('text:insertion');
+ } elseif ($trackedChange->getChangeType() == TrackChange::DELETED) {
+ $xmlWriter->startElement('text:deletion');
+ }
+
+ $xmlWriter->startElement('office:change-info');
+ $xmlWriter->writeElement('dc:creator', $trackedChange->getAuthor());
+ if ($trackedChange->getDate() != null) {
+ $xmlWriter->writeElement('dc:date', $trackedChange->getDate()->format('Y-m-d\TH:i:s\Z'));
+ }
+ $xmlWriter->endElement(); // office:change-info
+ if ($trackedChange->getChangeType() == TrackChange::DELETED) {
+ $xmlWriter->writeElement('text:p', $trackedElement->getText());
+ }
+
+ $xmlWriter->endElement(); // text:insertion|text:deletion
+ $xmlWriter->endElement(); // text:changed-region
+ }
+ $xmlWriter->endElement(); // text:tracked-changes
+
// Sequence declarations
$sequences = array('Illustration', 'Table', 'Text', 'Drawing');
$xmlWriter->startElement('text:sequence-decls');
@@ -242,4 +278,23 @@ private function getElementStyle(&$element, &$paragraphStyleCount, &$fontStyleCo
$element->setParagraphStyle("P{$paragraphStyleCount}");
}
}
+
+ /**
+ * Finds all tracked changes
+ *
+ * @param AbstractContainer $container
+ * @param \PhpOffice\PhpWord\Element\AbstractElement[] $trackedChanges
+ */
+ private function collectTrackedChanges(AbstractContainer $container, &$trackedChanges = array())
+ {
+ $elements = $container->getElements();
+ foreach ($elements as $element) {
+ if ($element->getTrackChange() != null) {
+ $trackedChanges[] = $element;
+ }
+ if (is_callable(array($element, 'getElements'))) {
+ $this->collectTrackedChanges($element, $trackedChanges);
+ }
+ }
+ }
}
diff --git a/src/PhpWord/Writer/Word2007/Element/Text.php b/src/PhpWord/Writer/Word2007/Element/Text.php
index e714943222..130b912bad 100644
--- a/src/PhpWord/Writer/Word2007/Element/Text.php
+++ b/src/PhpWord/Writer/Word2007/Element/Text.php
@@ -17,6 +17,8 @@
namespace PhpOffice\PhpWord\Writer\Word2007\Element;
+use PhpOffice\PhpWord\Element\TrackChange;
+
/**
* Text element writer
*
@@ -37,16 +39,66 @@ public function write()
$this->startElementP();
+ $this->writeOpeningTrackChange();
+
$xmlWriter->startElement('w:r');
$this->writeFontStyle();
- $xmlWriter->startElement('w:t');
+ $textElement = 'w:t';
+ //'w:delText' in case of deleted text
+ $changed = $element->getTrackChange();
+ if ($changed != null && $changed->getChangeType() == TrackChange::DELETED) {
+ $textElement = 'w:delText';
+ }
+ $xmlWriter->startElement($textElement);
+
$xmlWriter->writeAttribute('xml:space', 'preserve');
$this->writeText($this->getText($element->getText()));
$xmlWriter->endElement();
$xmlWriter->endElement(); // w:r
+ $this->writeClosingTrackChange();
+
$this->endElementP(); // w:p
}
+
+ /**
+ * Write opening of changed element
+ */
+ protected function writeOpeningTrackChange()
+ {
+ $changed = $this->getElement()->getTrackChange();
+ if ($changed == null) {
+ return;
+ }
+
+ $xmlWriter = $this->getXmlWriter();
+
+ if (($changed->getChangeType() == TrackChange::INSERTED)) {
+ $xmlWriter->startElement('w:ins');
+ } elseif ($changed->getChangeType() == TrackChange::DELETED) {
+ $xmlWriter->startElement('w:del');
+ }
+ $xmlWriter->writeAttribute('w:author', $changed->getAuthor());
+ if ($changed->getDate() != null) {
+ $xmlWriter->writeAttribute('w:date', $changed->getDate()->format('Y-m-d\TH:i:s\Z'));
+ }
+ $xmlWriter->writeAttribute('w:id', $this->getElement()->getElementId());
+ }
+
+ /**
+ * Write ending
+ */
+ protected function writeClosingTrackChange()
+ {
+ $changed = $this->getElement()->getTrackChange();
+ if ($changed == null) {
+ return;
+ }
+
+ $xmlWriter = $this->getXmlWriter();
+
+ $xmlWriter->endElement(); // w:ins|w:del
+ }
}
diff --git a/tests/PhpWord/Element/TrackChangeTest.php b/tests/PhpWord/Element/TrackChangeTest.php
new file mode 100644
index 0000000000..3249f10be3
--- /dev/null
+++ b/tests/PhpWord/Element/TrackChangeTest.php
@@ -0,0 +1,44 @@
+setTrackChange($oTrackChange);
+
+ $this->assertInstanceOf('PhpOffice\\PhpWord\\Element\\TrackChange', $oTrackChange);
+ $this->assertEquals($author, $oTrackChange->getAuthor());
+ $this->assertEquals($date, $oTrackChange->getDate());
+ $this->assertEquals(TrackChange::INSERTED, $oTrackChange->getChangeType());
+ }
+}
diff --git a/tests/PhpWord/Shared/HtmlTest.php b/tests/PhpWord/Shared/HtmlTest.php
index 6122924ff0..f2d83a2c66 100644
--- a/tests/PhpWord/Shared/HtmlTest.php
+++ b/tests/PhpWord/Shared/HtmlTest.php
@@ -259,7 +259,7 @@ public function tesOrderedListNumbering()
Html::addHtml($section, $html, false, false);
$doc = TestHelperDOCX::getDocument($phpWord, 'Word2007');
- echo $doc->printXml();
+
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:pPr/w:numPr/w:numId'));
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:t'));
diff --git a/tests/PhpWord/Writer/Word2007/ElementTest.php b/tests/PhpWord/Writer/Word2007/ElementTest.php
index 4f0d50d96a..f91a8479da 100644
--- a/tests/PhpWord/Writer/Word2007/ElementTest.php
+++ b/tests/PhpWord/Writer/Word2007/ElementTest.php
@@ -19,8 +19,8 @@
use PhpOffice\Common\XMLWriter;
use PhpOffice\PhpWord\Element\Comment;
-use PhpOffice\PhpWord\Element\Text;
use PhpOffice\PhpWord\Element\TextRun;
+use PhpOffice\PhpWord\Element\TrackChange;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\TestHelperDOCX;
@@ -415,4 +415,20 @@ public function testCommentWithEndElement()
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:commentRangeEnd'));
$this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:r/w:commentReference'));
}
+
+ public function testTrackChange()
+ {
+ $phpWord = new PhpWord();
+ $section = $phpWord->addSection();
+ $text = $section->addText('my dummy text');
+ $text->setChangeInfo(TrackChange::INSERTED, 'author name');
+ $text2 = $section->addText('my other text');
+ $text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new \DateTime()));
+
+ $doc = TestHelperDOCX::getDocument($phpWord);
+
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:ins/w:r'));
+ $this->assertEquals('author name', $doc->getElementAttribute('/w:document/w:body/w:p/w:ins', 'w:author'));
+ $this->assertTrue($doc->elementExists('/w:document/w:body/w:p/w:del/w:r/w:delText'));
+ }
}