1<?php
2
3namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
4
5use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6use PhpOffice\PhpSpreadsheet\Comment;
7use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
8
9class Comments extends WriterPart
10{
11    /**
12     * Write comments to XML format.
13     *
14     * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet
15     *
16     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
17     *
18     * @return string XML Output
19     */
20    public function writeComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
21    {
22        // Create XML writer
23        $objWriter = null;
24        if ($this->getParentWriter()->getUseDiskCaching()) {
25            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
26        } else {
27            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
28        }
29
30        // XML header
31        $objWriter->startDocument('1.0', 'UTF-8', 'yes');
32
33        // Comments cache
34        $comments = $pWorksheet->getComments();
35
36        // Authors cache
37        $authors = [];
38        $authorId = 0;
39        foreach ($comments as $comment) {
40            if (!isset($authors[$comment->getAuthor()])) {
41                $authors[$comment->getAuthor()] = $authorId++;
42            }
43        }
44
45        // comments
46        $objWriter->startElement('comments');
47        $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
48
49        // Loop through authors
50        $objWriter->startElement('authors');
51        foreach ($authors as $author => $index) {
52            $objWriter->writeElement('author', $author);
53        }
54        $objWriter->endElement();
55
56        // Loop through comments
57        $objWriter->startElement('commentList');
58        foreach ($comments as $key => $value) {
59            $this->writeComment($objWriter, $key, $value, $authors);
60        }
61        $objWriter->endElement();
62
63        $objWriter->endElement();
64
65        // Return
66        return $objWriter->getData();
67    }
68
69    /**
70     * Write comment to XML format.
71     *
72     * @param XMLWriter $objWriter XML Writer
73     * @param string $pCellReference Cell reference
74     * @param Comment $pComment Comment
75     * @param array $pAuthors Array of authors
76     *
77     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
78     */
79    private function writeComment(XMLWriter $objWriter, $pCellReference, Comment $pComment, array $pAuthors)
80    {
81        // comment
82        $objWriter->startElement('comment');
83        $objWriter->writeAttribute('ref', $pCellReference);
84        $objWriter->writeAttribute('authorId', $pAuthors[$pComment->getAuthor()]);
85
86        // text
87        $objWriter->startElement('text');
88        $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $pComment->getText());
89        $objWriter->endElement();
90
91        $objWriter->endElement();
92    }
93
94    /**
95     * Write VML comments to XML format.
96     *
97     * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet
98     *
99     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
100     *
101     * @return string XML Output
102     */
103    public function writeVMLComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
104    {
105        // Create XML writer
106        $objWriter = null;
107        if ($this->getParentWriter()->getUseDiskCaching()) {
108            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
109        } else {
110            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
111        }
112
113        // XML header
114        $objWriter->startDocument('1.0', 'UTF-8', 'yes');
115
116        // Comments cache
117        $comments = $pWorksheet->getComments();
118
119        // xml
120        $objWriter->startElement('xml');
121        $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
122        $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
123        $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
124
125        // o:shapelayout
126        $objWriter->startElement('o:shapelayout');
127        $objWriter->writeAttribute('v:ext', 'edit');
128
129        // o:idmap
130        $objWriter->startElement('o:idmap');
131        $objWriter->writeAttribute('v:ext', 'edit');
132        $objWriter->writeAttribute('data', '1');
133        $objWriter->endElement();
134
135        $objWriter->endElement();
136
137        // v:shapetype
138        $objWriter->startElement('v:shapetype');
139        $objWriter->writeAttribute('id', '_x0000_t202');
140        $objWriter->writeAttribute('coordsize', '21600,21600');
141        $objWriter->writeAttribute('o:spt', '202');
142        $objWriter->writeAttribute('path', 'm,l,21600r21600,l21600,xe');
143
144        // v:stroke
145        $objWriter->startElement('v:stroke');
146        $objWriter->writeAttribute('joinstyle', 'miter');
147        $objWriter->endElement();
148
149        // v:path
150        $objWriter->startElement('v:path');
151        $objWriter->writeAttribute('gradientshapeok', 't');
152        $objWriter->writeAttribute('o:connecttype', 'rect');
153        $objWriter->endElement();
154
155        $objWriter->endElement();
156
157        // Loop through comments
158        foreach ($comments as $key => $value) {
159            $this->writeVMLComment($objWriter, $key, $value);
160        }
161
162        $objWriter->endElement();
163
164        // Return
165        return $objWriter->getData();
166    }
167
168    /**
169     * Write VML comment to XML format.
170     *
171     * @param XMLWriter $objWriter XML Writer
172     * @param string $pCellReference Cell reference, eg: 'A1'
173     * @param Comment $pComment Comment
174     */
175    private function writeVMLComment(XMLWriter $objWriter, $pCellReference, Comment $pComment)
176    {
177        // Metadata
178        list($column, $row) = Coordinate::coordinateFromString($pCellReference);
179        $column = Coordinate::columnIndexFromString($column);
180        $id = 1024 + $column + $row;
181        $id = substr($id, 0, 4);
182
183        // v:shape
184        $objWriter->startElement('v:shape');
185        $objWriter->writeAttribute('id', '_x0000_s' . $id);
186        $objWriter->writeAttribute('type', '#_x0000_t202');
187        $objWriter->writeAttribute('style', 'position:absolute;margin-left:' . $pComment->getMarginLeft() . ';margin-top:' . $pComment->getMarginTop() . ';width:' . $pComment->getWidth() . ';height:' . $pComment->getHeight() . ';z-index:1;visibility:' . ($pComment->getVisible() ? 'visible' : 'hidden'));
188        $objWriter->writeAttribute('fillcolor', '#' . $pComment->getFillColor()->getRGB());
189        $objWriter->writeAttribute('o:insetmode', 'auto');
190
191        // v:fill
192        $objWriter->startElement('v:fill');
193        $objWriter->writeAttribute('color2', '#' . $pComment->getFillColor()->getRGB());
194        $objWriter->endElement();
195
196        // v:shadow
197        $objWriter->startElement('v:shadow');
198        $objWriter->writeAttribute('on', 't');
199        $objWriter->writeAttribute('color', 'black');
200        $objWriter->writeAttribute('obscured', 't');
201        $objWriter->endElement();
202
203        // v:path
204        $objWriter->startElement('v:path');
205        $objWriter->writeAttribute('o:connecttype', 'none');
206        $objWriter->endElement();
207
208        // v:textbox
209        $objWriter->startElement('v:textbox');
210        $objWriter->writeAttribute('style', 'mso-direction-alt:auto');
211
212        // div
213        $objWriter->startElement('div');
214        $objWriter->writeAttribute('style', 'text-align:left');
215        $objWriter->endElement();
216
217        $objWriter->endElement();
218
219        // x:ClientData
220        $objWriter->startElement('x:ClientData');
221        $objWriter->writeAttribute('ObjectType', 'Note');
222
223        // x:MoveWithCells
224        $objWriter->writeElement('x:MoveWithCells', '');
225
226        // x:SizeWithCells
227        $objWriter->writeElement('x:SizeWithCells', '');
228
229        // x:AutoFill
230        $objWriter->writeElement('x:AutoFill', 'False');
231
232        // x:Row
233        $objWriter->writeElement('x:Row', ($row - 1));
234
235        // x:Column
236        $objWriter->writeElement('x:Column', ($column - 1));
237
238        $objWriter->endElement();
239
240        $objWriter->endElement();
241    }
242}
243