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