Skip to content

Commit 604e60c

Browse files
authored
Add support for Track changes (PHPOffice#1262)
* add changed information to HTML writer * add missing writeFontStyle * refactor track changes * set the style * update documentation and release note * Update the changelog and doc * fix scrutinizer issues
1 parent fd127ef commit 604e60c

File tree

20 files changed

+487
-39
lines changed

20 files changed

+487
-39
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ v0.15.0 (?? ??? 2018)
88
### Added
99
- Parsing of "align" HTML attribute - @troosan #1231
1010
- Parse formatting inside HTML lists - @troosan @samimussbach #1239 #945 #1215 #508
11+
- Add support for Track changes @Cip @troosan #354 #1262
1112

1213
### Fixed
1314
- fix reading of docx default style - @troosan #1238

docs/elements.rst

+36-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ column shows the containers while the rows lists the elements.
3131
+-------+-----------------+-----------+----------+----------+---------+------------+------------+
3232
| 11 | Watermark | - | v | - | - | - | - |
3333
+-------+-----------------+-----------+----------+----------+---------+------------+------------+
34-
| 12 | Object | v | v | v | v | v | v |
34+
| 12 | OLEObject | v | v | v | v | v | v |
3535
+-------+-----------------+-----------+----------+----------+---------+------------+------------+
3636
| 13 | TOC | v | - | - | - | - | - |
3737
+-------+-----------------+-----------+----------+----------+---------+------------+------------+
@@ -77,6 +77,13 @@ italics, etc) or other elements, e.g. images or links. The syntaxes are as follo
7777

7878
For available styling options see :ref:`font-style` and :ref:`paragraph-style`.
7979

80+
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:
81+
82+
.. code-block:: php
83+
84+
$text = $section->addText('Hello World!');
85+
$text->setChanged(\PhpOffice\PhpWord\Element\ChangedElement::TYPE_INSERTED, 'Fred', (new \DateTime()));
86+
8087
Titles
8188
~~~~~~
8289

@@ -276,11 +283,11 @@ Objects
276283
-------
277284

278285
You can add OLE embeddings, such as Excel spreadsheets or PowerPoint
279-
presentations to the document by using ``addObject`` method.
286+
presentations to the document by using ``addOLEObject`` method.
280287

281288
.. code-block:: php
282289
283-
$section->addObject($src, [$style]);
290+
$section->addOLEObject($src, [$style]);
284291
285292
Table of contents
286293
-----------------
@@ -309,7 +316,7 @@ Footnotes & endnotes
309316
You can create footnotes with ``addFootnote`` and endnotes with
310317
``addEndnote`` in texts or textruns, but it's recommended to use textrun
311318
to have better layout. You can use ``addText``, ``addLink``,
312-
``addTextBreak``, ``addImage``, ``addObject`` on footnotes and endnotes.
319+
``addTextBreak``, ``addImage``, ``addOLEObject`` on footnotes and endnotes.
313320

314321
On textrun:
315322

@@ -465,4 +472,28 @@ The comment can contain formatted text. Once the comment has been added, it can
465472
// link the comment to the text you just created
466473
$text->setCommentStart($comment);
467474
468-
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.
475+
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.
476+
477+
Track Changes
478+
-------------
479+
480+
Track changes can be set on text elements. There are 2 ways to set the change information on an element.
481+
Either by calling the `setChangeInfo()`, or by setting the `TrackChange` instance on the element with `setTrackChange()`.
482+
483+
.. code-block:: php
484+
$phpWord = new \PhpOffice\PhpWord\PhpWord();
485+
486+
// New portrait section
487+
$section = $phpWord->addSection();
488+
$textRun = $section->addTextRun();
489+
490+
$text = $textRun->addText('Hello World! Time to ');
491+
492+
$text = $textRun->addText('wake ', array('bold' => true));
493+
$text->setChangeInfo(TrackChange::INSERTED, 'Fred', time() - 1800);
494+
495+
$text = $textRun->addText('up');
496+
$text->setTrackChange(new TrackChange(TrackChange::INSERTED, 'Fred'));
497+
498+
$text = $textRun->addText('go to sleep');
499+
$text->setChangeInfo(TrackChange::DELETED, 'Barney', new \DateTime('@' . (time() - 3600)));

samples/Sample_16_Object.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
$section = $phpWord->addSection();
1010
$section->addText('You can open this OLE object by double clicking on the icon:');
1111
$section->addTextBreak(2);
12-
$section->addObject('resources/_sheet.xls');
12+
$section->addOLEObject('resources/_sheet.xls');
1313

1414
// Save file
1515
echo write($phpWord, basename(__FILE__, '.php'), $writers);

samples/Sample_39_TrackChanges.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
use PhpOffice\PhpWord\Element\TrackChange;
3+
4+
include_once 'Sample_Header.php';
5+
6+
// New Word Document
7+
echo date('H:i:s') , ' Create new PhpWord object' , EOL;
8+
$phpWord = new \PhpOffice\PhpWord\PhpWord();
9+
10+
// New portrait section
11+
$section = $phpWord->addSection();
12+
$textRun = $section->addTextRun();
13+
14+
$text = $textRun->addText('Hello World! Time to ');
15+
16+
$text = $textRun->addText('wake ', array('bold' => true));
17+
$text->setChangeInfo(TrackChange::INSERTED, 'Fred', time() - 1800);
18+
19+
$text = $textRun->addText('up');
20+
$text->setTrackChange(new TrackChange(TrackChange::INSERTED, 'Fred'));
21+
22+
$text = $textRun->addText('go to sleep');
23+
$text->setChangeInfo(TrackChange::DELETED, 'Barney', new \DateTime('@' . (time() - 3600)));
24+
25+
// Save file
26+
echo write($phpWord, basename(__FILE__, '.php'), $writers);
27+
if (!CLI) {
28+
include_once 'Sample_Footer.php';
29+
}
-4.45 KB
Binary file not shown.
-412 Bytes
Binary file not shown.

src/PhpWord/Element/AbstractContainer.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public function __call($function, $args)
8181
{
8282
$elements = array(
8383
'Text', 'TextRun', 'Bookmark', 'Link', 'PreserveText', 'TextBreak',
84-
'ListItem', 'ListItemRun', 'Table', 'Image', 'Object',
84+
'ListItem', 'ListItemRun', 'Table', 'Image', 'Object', 'OLEObject',
8585
'Footnote', 'Endnote', 'CheckBox', 'TextBox', 'Field',
8686
'Line', 'Shape', 'Title', 'TOC', 'PageBreak',
8787
'Chart', 'FormField', 'SDT', 'Comment',
@@ -157,7 +157,7 @@ protected function addElement($elementName)
157157
/**
158158
* Get all elements
159159
*
160-
* @return array
160+
* @return \PhpOffice\PhpWord\Element\AbstractElement[]
161161
*/
162162
public function getElements()
163163
{

src/PhpWord/Element/AbstractElement.php

+39
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ abstract class AbstractElement
9393
*/
9494
private $nestedLevel = 0;
9595

96+
/**
97+
* changed element info
98+
*
99+
* @var TrackChange
100+
*/
101+
private $trackChange;
102+
96103
/**
97104
* Parent container type
98105
*
@@ -425,6 +432,38 @@ protected function setNewStyle($styleObject, $styleValue = null, $returnObject =
425432
return $style;
426433
}
427434

435+
/**
436+
* Sets the trackChange information
437+
*
438+
* @param TrackChange $trackChange
439+
*/
440+
public function setTrackChange(TrackChange $trackChange)
441+
{
442+
$this->trackChange = $trackChange;
443+
}
444+
445+
/**
446+
* Gets the trackChange information
447+
*
448+
* @return TrackChange
449+
*/
450+
public function getTrackChange()
451+
{
452+
return $this->trackChange;
453+
}
454+
455+
/**
456+
* Set changed
457+
*
458+
* @param string $type INSERTED|DELETED
459+
* @param string $author
460+
* @param null|int|\DateTime $date allways in UTC
461+
*/
462+
public function setChangeInfo($type, $author, $date = null)
463+
{
464+
$this->trackChange = new TrackChange($type, $author, $date);
465+
}
466+
428467
/**
429468
* Set enum value
430469
*

src/PhpWord/Element/Comment.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ class Comment extends TrackChange
5555
* Create a new Comment Element
5656
*
5757
* @param string $author
58-
* @param \DateTime $date
58+
* @param null|\DateTime $date
5959
* @param string $initials
6060
*/
6161
public function __construct($author, $date = null, $initials = null)
6262
{
63-
parent::__construct($author, $date);
63+
parent::__construct(null, $author, $date);
6464
$this->initials = $initials;
6565
}
6666

src/PhpWord/Element/TrackChange.php

+28-3
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,25 @@
2020
/**
2121
* TrackChange element
2222
* @see http://datypic.com/sc/ooxml/t-w_CT_TrackChange.html
23+
* @see http://datypic.com/sc/ooxml/t-w_CT_RunTrackChange.html
2324
*/
2425
class TrackChange extends AbstractContainer
2526
{
27+
const INSERTED = 'INSERTED';
28+
const DELETED = 'DELETED';
29+
2630
/**
2731
* @var string Container type
2832
*/
2933
protected $container = 'TrackChange';
3034

35+
/**
36+
* The type of change, (insert or delete), not applicable for PhpOffice\PhpWord\Element\Comment
37+
*
38+
* @var string
39+
*/
40+
private $changeType;
41+
3142
/**
3243
* Author
3344
*
@@ -45,13 +56,17 @@ class TrackChange extends AbstractContainer
4556
/**
4657
* Create a new TrackChange Element
4758
*
59+
* @param string $changeType
4860
* @param string $author
49-
* @param \DateTime $date
61+
* @param null|int|\DateTime $date
5062
*/
51-
public function __construct($author, \DateTime $date = null)
63+
public function __construct($changeType = null, $author = null, $date = null)
5264
{
65+
$this->changeType = $changeType;
5366
$this->author = $author;
54-
$this->date = $date;
67+
if ($date !== null) {
68+
$this->date = ($date instanceof \DateTime) ? $date : new \DateTime('@' . $date);
69+
}
5570
}
5671

5772
/**
@@ -73,4 +88,14 @@ public function getDate()
7388
{
7489
return $this->date;
7590
}
91+
92+
/**
93+
* Get the Change type
94+
*
95+
* @return string
96+
*/
97+
public function getChangeType()
98+
{
99+
return $this->changeType;
100+
}
76101
}

src/PhpWord/Reader/ODText/Content.php

+49-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
namespace PhpOffice\PhpWord\Reader\ODText;
1919

2020
use PhpOffice\Common\XMLReader;
21+
use PhpOffice\PhpWord\Element\TrackChange;
2122
use PhpOffice\PhpWord\PhpWord;
2223

2324
/**
@@ -37,6 +38,8 @@ public function read(PhpWord $phpWord)
3738
$xmlReader = new XMLReader();
3839
$xmlReader->getDomFromZip($this->docFile, $this->xmlFile);
3940

41+
$trackedChanges = array();
42+
4043
$nodes = $xmlReader->getElements('office:body/office:text/*');
4144
if ($nodes->length > 0) {
4245
$section = $phpWord->addSection();
@@ -48,7 +51,37 @@ public function read(PhpWord $phpWord)
4851
$section->addTitle($node->nodeValue, $depth);
4952
break;
5053
case 'text:p': // Paragraph
51-
$section->addText($node->nodeValue);
54+
$children = $node->childNodes;
55+
foreach ($children as $child) {
56+
switch ($child->nodeName) {
57+
case 'text:change-start':
58+
$changeId = $child->getAttribute('text:change-id');
59+
if (isset($trackedChanges[$changeId])) {
60+
$changed = $trackedChanges[$changeId];
61+
}
62+
break;
63+
case 'text:change-end':
64+
unset($changed);
65+
break;
66+
case 'text:change':
67+
$changeId = $child->getAttribute('text:change-id');
68+
if (isset($trackedChanges[$changeId])) {
69+
$changed = $trackedChanges[$changeId];
70+
}
71+
break;
72+
}
73+
}
74+
75+
$element = $section->addText($node->nodeValue);
76+
if (isset($changed) && is_array($changed)) {
77+
$element->setTrackChange($changed['changed']);
78+
if (isset($changed['textNodes'])) {
79+
foreach ($changed['textNodes'] as $changedNode) {
80+
$element = $section->addText($changedNode->nodeValue);
81+
$element->setTrackChange($changed['changed']);
82+
}
83+
}
84+
}
5285
break;
5386
case 'text:list': // List
5487
$listItems = $xmlReader->getElements('text:list-item/text:p', $node);
@@ -57,6 +90,21 @@ public function read(PhpWord $phpWord)
5790
$section->addListItem($listItem->nodeValue, 0);
5891
}
5992
break;
93+
case 'text:tracked-changes':
94+
$changedRegions = $xmlReader->getElements('text:changed-region', $node);
95+
foreach ($changedRegions as $changedRegion) {
96+
$type = ($changedRegion->firstChild->nodeName == 'text:insertion') ? TrackChange::INSERTED : TrackChange::DELETED;
97+
$creatorNode = $xmlReader->getElements('office:change-info/dc:creator', $changedRegion->firstChild);
98+
$author = $creatorNode[0]->nodeValue;
99+
$dateNode = $xmlReader->getElements('office:change-info/dc:date', $changedRegion->firstChild);
100+
$date = $dateNode[0]->nodeValue;
101+
$date = preg_replace('/\.\d+$/', '', $date);
102+
$date = \DateTime::createFromFormat('Y-m-d\TH:i:s', $date);
103+
$changed = new TrackChange($type, $author, $date);
104+
$textNodes = $xmlReader->getElements('text:deletion/text:p', $changedRegion);
105+
$trackedChanges[$changedRegion->getAttribute('text:id')] = array('changed' => $changed, 'textNodes'=> $textNodes);
106+
}
107+
break;
60108
}
61109
}
62110
}

0 commit comments

Comments
 (0)