Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
92.55% |
87 / 94 |
|
90.00% |
27 / 30 |
CRAP | |
0.00% |
0 / 1 |
AbstractElement | |
92.55% |
87 / 94 |
|
90.00% |
27 / 30 |
64.64 | |
0.00% |
0 / 1 |
getPhpWord | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setPhpWord | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSectionId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setDocPart | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getDocPart | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDocPartId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMediaPart | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
getElementIndex | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setElementIndex | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getElementId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setElementId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRelationId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setRelationId | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getNestedLevel | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCommentsRangeStart | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCommentRangeStart | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setCommentRangeStart | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
getCommentsRangeEnd | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCommentRangeEnd | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setCommentRangeEnd | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
6.03 | |||
getParent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setParentContainer | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
setMediaRelation | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
7 | |||
setCollectionRelation | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
isInSection | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setNewStyle | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
setTrackChange | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTrackChange | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setChangeInfo | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setEnumVal | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
7 |
1 | <?php |
2 | /** |
3 | * This file is part of PHPWord - A pure PHP library for reading and writing |
4 | * word processing documents. |
5 | * |
6 | * PHPWord is free software distributed under the terms of the GNU Lesser |
7 | * General Public License version 3 as published by the Free Software Foundation. |
8 | * |
9 | * For the full copyright and license information, please read the LICENSE |
10 | * file that was distributed with this source code. For the full list of |
11 | * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. |
12 | * |
13 | * @see https://github.com/PHPOffice/PHPWord |
14 | * |
15 | * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 |
16 | */ |
17 | |
18 | namespace PhpOffice\PhpWord\Element; |
19 | |
20 | use DateTime; |
21 | use InvalidArgumentException; |
22 | use PhpOffice\PhpWord\Collection\Comments; |
23 | use PhpOffice\PhpWord\Media; |
24 | use PhpOffice\PhpWord\PhpWord; |
25 | use PhpOffice\PhpWord\Style; |
26 | |
27 | /** |
28 | * Element abstract class. |
29 | * |
30 | * @since 0.10.0 |
31 | */ |
32 | abstract class AbstractElement |
33 | { |
34 | /** |
35 | * PhpWord object. |
36 | * |
37 | * @var ?PhpWord |
38 | */ |
39 | protected $phpWord; |
40 | |
41 | /** |
42 | * Section Id. |
43 | * |
44 | * @var int |
45 | */ |
46 | protected $sectionId; |
47 | |
48 | /** |
49 | * Document part type: Section|Header|Footer|Footnote|Endnote. |
50 | * |
51 | * Used by textrun and cell container to determine where the element is |
52 | * located because it will affect the availability of other element, |
53 | * e.g. footnote will not be available when $docPart is header or footer. |
54 | * |
55 | * @var string |
56 | */ |
57 | protected $docPart = 'Section'; |
58 | |
59 | /** |
60 | * Document part Id. |
61 | * |
62 | * For header and footer, this will be = ($sectionId - 1) * 3 + $index |
63 | * because the max number of header/footer in every page is 3, i.e. |
64 | * AUTO, FIRST, and EVEN (AUTO = ODD) |
65 | * |
66 | * @var int |
67 | */ |
68 | protected $docPartId = 1; |
69 | |
70 | /** |
71 | * Index of element in the elements collection (start with 1). |
72 | * |
73 | * @var int |
74 | */ |
75 | protected $elementIndex = 1; |
76 | |
77 | /** |
78 | * Unique Id for element. |
79 | * |
80 | * @var string |
81 | */ |
82 | protected $elementId; |
83 | |
84 | /** |
85 | * Relation Id. |
86 | * |
87 | * @var int |
88 | */ |
89 | protected $relationId; |
90 | |
91 | /** |
92 | * Depth of table container nested level; Primarily used for RTF writer/reader. |
93 | * |
94 | * 0 = Not in a table; 1 = in a table; 2 = in a table inside another table, etc. |
95 | * |
96 | * @var int |
97 | */ |
98 | private $nestedLevel = 0; |
99 | |
100 | /** |
101 | * A reference to the parent. |
102 | * |
103 | * @var null|AbstractElement |
104 | */ |
105 | private $parent; |
106 | |
107 | /** |
108 | * changed element info. |
109 | * |
110 | * @var TrackChange |
111 | */ |
112 | private $trackChange; |
113 | |
114 | /** |
115 | * Parent container type. |
116 | * |
117 | * @var string |
118 | */ |
119 | private $parentContainer; |
120 | |
121 | /** |
122 | * Has media relation flag; true for Link, Image, and Object. |
123 | * |
124 | * @var bool |
125 | */ |
126 | protected $mediaRelation = false; |
127 | |
128 | /** |
129 | * Is part of collection; true for Title, Footnote, Endnote, Chart, and Comment. |
130 | * |
131 | * @var bool |
132 | */ |
133 | protected $collectionRelation = false; |
134 | |
135 | /** |
136 | * The start position for the linked comments. |
137 | * |
138 | * @var Comments |
139 | */ |
140 | protected $commentsRangeStart; |
141 | |
142 | /** |
143 | * The end position for the linked comments. |
144 | * |
145 | * @var Comments |
146 | */ |
147 | protected $commentsRangeEnd; |
148 | |
149 | /** |
150 | * Get PhpWord. |
151 | * |
152 | * @return ?PhpWord |
153 | */ |
154 | public function getPhpWord(): ?PhpWord |
155 | { |
156 | return $this->phpWord; |
157 | } |
158 | |
159 | /** |
160 | * Set PhpWord as reference. |
161 | */ |
162 | public function setPhpWord(?PhpWord $phpWord = null): void |
163 | { |
164 | $this->phpWord = $phpWord; |
165 | } |
166 | |
167 | /** |
168 | * Get section number. |
169 | * |
170 | * @return int |
171 | */ |
172 | public function getSectionId() |
173 | { |
174 | return $this->sectionId; |
175 | } |
176 | |
177 | /** |
178 | * Set doc part. |
179 | * |
180 | * @param string $docPart |
181 | * @param int $docPartId |
182 | */ |
183 | public function setDocPart($docPart, $docPartId = 1): void |
184 | { |
185 | $this->docPart = $docPart; |
186 | $this->docPartId = $docPartId; |
187 | } |
188 | |
189 | /** |
190 | * Get doc part. |
191 | * |
192 | * @return string |
193 | */ |
194 | public function getDocPart() |
195 | { |
196 | return $this->docPart; |
197 | } |
198 | |
199 | /** |
200 | * Get doc part Id. |
201 | * |
202 | * @return int |
203 | */ |
204 | public function getDocPartId() |
205 | { |
206 | return $this->docPartId; |
207 | } |
208 | |
209 | /** |
210 | * Return media element (image, object, link) container name. |
211 | * |
212 | * @return string section|headerx|footerx|footnote|endnote |
213 | */ |
214 | private function getMediaPart() |
215 | { |
216 | $mediaPart = $this->docPart; |
217 | if ($mediaPart == 'Header' || $mediaPart == 'Footer') { |
218 | $mediaPart .= $this->docPartId; |
219 | } |
220 | |
221 | return strtolower($mediaPart); |
222 | } |
223 | |
224 | /** |
225 | * Get element index. |
226 | * |
227 | * @return int |
228 | */ |
229 | public function getElementIndex() |
230 | { |
231 | return $this->elementIndex; |
232 | } |
233 | |
234 | /** |
235 | * Set element index. |
236 | * |
237 | * @param int $value |
238 | */ |
239 | public function setElementIndex($value): void |
240 | { |
241 | $this->elementIndex = $value; |
242 | } |
243 | |
244 | /** |
245 | * Get element unique ID. |
246 | * |
247 | * @return string |
248 | */ |
249 | public function getElementId() |
250 | { |
251 | return $this->elementId; |
252 | } |
253 | |
254 | /** |
255 | * Set element unique ID from 6 first digit of md5. |
256 | */ |
257 | public function setElementId(): void |
258 | { |
259 | $this->elementId = substr(md5(mt_rand()), 0, 6); |
260 | } |
261 | |
262 | /** |
263 | * Get relation Id. |
264 | * |
265 | * @return int |
266 | */ |
267 | public function getRelationId() |
268 | { |
269 | return $this->relationId; |
270 | } |
271 | |
272 | /** |
273 | * Set relation Id. |
274 | * |
275 | * @param int $value |
276 | */ |
277 | public function setRelationId($value): void |
278 | { |
279 | $this->relationId = $value; |
280 | } |
281 | |
282 | /** |
283 | * Get nested level. |
284 | * |
285 | * @return int |
286 | */ |
287 | public function getNestedLevel() |
288 | { |
289 | return $this->nestedLevel; |
290 | } |
291 | |
292 | /** |
293 | * Get comments start. |
294 | * |
295 | * @return Comments |
296 | */ |
297 | public function getCommentsRangeStart(): ?Comments |
298 | { |
299 | return $this->commentsRangeStart; |
300 | } |
301 | |
302 | /** |
303 | * Get comment start. |
304 | * |
305 | * @return Comment |
306 | */ |
307 | public function getCommentRangeStart(): ?Comment |
308 | { |
309 | if ($this->commentsRangeStart != null) { |
310 | return $this->commentsRangeStart->getItem($this->commentsRangeStart->countItems()); |
311 | } |
312 | |
313 | return null; |
314 | } |
315 | |
316 | /** |
317 | * Set comment start. |
318 | */ |
319 | public function setCommentRangeStart(Comment $value): void |
320 | { |
321 | if ($this instanceof Comment) { |
322 | throw new InvalidArgumentException('Cannot set a Comment on a Comment'); |
323 | } |
324 | if ($this->commentsRangeStart == null) { |
325 | $this->commentsRangeStart = new Comments(); |
326 | } |
327 | // Set ID early to avoid duplicates. |
328 | if ($value->getElementId() == null) { |
329 | $value->setElementId(); |
330 | } |
331 | foreach ($this->commentsRangeStart->getItems() as $comment) { |
332 | if ($value->getElementId() == $comment->getElementId()) { |
333 | return; |
334 | } |
335 | } |
336 | $idxItem = $this->commentsRangeStart->addItem($value); |
337 | $this->commentsRangeStart->getItem($idxItem)->setStartElement($this); |
338 | } |
339 | |
340 | /** |
341 | * Get comments end. |
342 | * |
343 | * @return Comments |
344 | */ |
345 | public function getCommentsRangeEnd(): ?Comments |
346 | { |
347 | return $this->commentsRangeEnd; |
348 | } |
349 | |
350 | /** |
351 | * Get comment end. |
352 | * |
353 | * @return Comment |
354 | */ |
355 | public function getCommentRangeEnd(): ?Comment |
356 | { |
357 | if ($this->commentsRangeEnd != null) { |
358 | return $this->commentsRangeEnd->getItem($this->commentsRangeEnd->countItems()); |
359 | } |
360 | |
361 | return null; |
362 | } |
363 | |
364 | /** |
365 | * Set comment end. |
366 | */ |
367 | public function setCommentRangeEnd(Comment $value): void |
368 | { |
369 | if ($this instanceof Comment) { |
370 | throw new InvalidArgumentException('Cannot set a Comment on a Comment'); |
371 | } |
372 | if ($this->commentsRangeEnd == null) { |
373 | $this->commentsRangeEnd = new Comments(); |
374 | } |
375 | // Set ID early to avoid duplicates. |
376 | if ($value->getElementId() == null) { |
377 | $value->setElementId(); |
378 | } |
379 | foreach ($this->commentsRangeEnd->getItems() as $comment) { |
380 | if ($value->getElementId() == $comment->getElementId()) { |
381 | return; |
382 | } |
383 | } |
384 | $idxItem = $this->commentsRangeEnd->addItem($value); |
385 | $this->commentsRangeEnd->getItem($idxItem)->setEndElement($this); |
386 | } |
387 | |
388 | /** |
389 | * Get parent element. |
390 | * |
391 | * @return null|AbstractElement |
392 | */ |
393 | public function getParent() |
394 | { |
395 | return $this->parent; |
396 | } |
397 | |
398 | /** |
399 | * Set parent container. |
400 | * |
401 | * Passed parameter should be a container, except for Table (contain Row) and Row (contain Cell) |
402 | */ |
403 | public function setParentContainer(self $container): void |
404 | { |
405 | $this->parentContainer = substr(get_class($container), strrpos(get_class($container), '\\') + 1); |
406 | $this->parent = $container; |
407 | |
408 | // Set nested level |
409 | $this->nestedLevel = $container->getNestedLevel(); |
410 | if ($this->parentContainer == 'Cell') { |
411 | ++$this->nestedLevel; |
412 | } |
413 | |
414 | // Set phpword |
415 | $this->setPhpWord($container->getPhpWord()); |
416 | |
417 | // Set doc part |
418 | if (!$this instanceof Footnote) { |
419 | $this->setDocPart($container->getDocPart(), $container->getDocPartId()); |
420 | } |
421 | |
422 | $this->setMediaRelation(); |
423 | $this->setCollectionRelation(); |
424 | } |
425 | |
426 | /** |
427 | * Set relation Id for media elements (link, image, object; legacy of OOXML). |
428 | * |
429 | * - Image element needs to be passed to Media object |
430 | * - Icon needs to be set for Object element |
431 | */ |
432 | private function setMediaRelation(): void |
433 | { |
434 | if (!$this instanceof Link && !$this instanceof Image && !$this instanceof OLEObject) { |
435 | return; |
436 | } |
437 | |
438 | $elementName = substr(static::class, strrpos(static::class, '\\') + 1); |
439 | if ($elementName == 'OLEObject') { |
440 | $elementName = 'Object'; |
441 | } |
442 | $mediaPart = $this->getMediaPart(); |
443 | $source = $this->getSource(); |
444 | $image = null; |
445 | if ($this instanceof Image) { |
446 | $image = $this; |
447 | } |
448 | $rId = Media::addElement($mediaPart, strtolower($elementName), $source, $image); |
449 | $this->setRelationId($rId); |
450 | |
451 | if ($this instanceof OLEObject) { |
452 | $icon = $this->getIcon(); |
453 | $rId = Media::addElement($mediaPart, 'image', $icon, new Image($icon)); |
454 | $this->setImageRelationId($rId); |
455 | } |
456 | } |
457 | |
458 | /** |
459 | * Set relation Id for elements that will be registered in the Collection subnamespaces. |
460 | */ |
461 | private function setCollectionRelation(): void |
462 | { |
463 | if ($this->collectionRelation === true && $this->phpWord instanceof PhpWord) { |
464 | $elementName = substr(static::class, strrpos(static::class, '\\') + 1); |
465 | $addMethod = "add{$elementName}"; |
466 | $rId = $this->phpWord->$addMethod($this); |
467 | $this->setRelationId($rId); |
468 | } |
469 | } |
470 | |
471 | /** |
472 | * Check if element is located in Section doc part (as opposed to Header/Footer). |
473 | * |
474 | * @return bool |
475 | */ |
476 | public function isInSection() |
477 | { |
478 | return $this->docPart == 'Section'; |
479 | } |
480 | |
481 | /** |
482 | * Set new style value. |
483 | * |
484 | * @param mixed $styleObject Style object |
485 | * @param null|array|string|Style $styleValue Style value |
486 | * @param bool $returnObject Always return object |
487 | * |
488 | * @return mixed |
489 | */ |
490 | protected function setNewStyle($styleObject, $styleValue = null, $returnObject = false) |
491 | { |
492 | if (null !== $styleValue && is_array($styleValue)) { |
493 | $styleObject->setStyleByArray($styleValue); |
494 | $style = $styleObject; |
495 | } else { |
496 | $style = $returnObject ? $styleObject : $styleValue; |
497 | } |
498 | |
499 | return $style; |
500 | } |
501 | |
502 | /** |
503 | * Sets the trackChange information. |
504 | */ |
505 | public function setTrackChange(TrackChange $trackChange): void |
506 | { |
507 | $this->trackChange = $trackChange; |
508 | } |
509 | |
510 | /** |
511 | * Gets the trackChange information. |
512 | * |
513 | * @return TrackChange |
514 | */ |
515 | public function getTrackChange() |
516 | { |
517 | return $this->trackChange; |
518 | } |
519 | |
520 | /** |
521 | * Set changed. |
522 | * |
523 | * @param string $type INSERTED|DELETED |
524 | * @param string $author |
525 | * @param null|DateTime|int $date allways in UTC |
526 | */ |
527 | public function setChangeInfo($type, $author, $date = null): void |
528 | { |
529 | $this->trackChange = new TrackChange($type, $author, $date); |
530 | } |
531 | |
532 | /** |
533 | * Set enum value. |
534 | * |
535 | * @param null|string $value |
536 | * @param string[] $enum |
537 | * @param null|string $default |
538 | * |
539 | * @return null|string |
540 | * |
541 | * @todo Merge with the same method in AbstractStyle |
542 | */ |
543 | protected function setEnumVal($value = null, $enum = [], $default = null) |
544 | { |
545 | if ($value !== null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { |
546 | throw new InvalidArgumentException("Invalid style value: {$value}"); |
547 | } elseif ($value === null || trim($value) == '') { |
548 | $value = $default; |
549 | } |
550 | |
551 | return $value; |
552 | } |
553 | } |