• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPOffice / PHPWord / 13025401639

29 Jan 2025 05:48AM UTC coverage: 96.825% (-0.4%) from 97.217%
13025401639

Pull #2562

github

web-flow
Merge ebe3e5dac into 2d2759585
Pull Request #2562: TemplateProcessor SetComplexBlock/Value and Sections

6 of 7 new or added lines in 1 file covered. (85.71%)

245 existing lines in 40 files now uncovered.

12227 of 12628 relevant lines covered (96.82%)

34.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.55
/src/PhpWord/Element/AbstractElement.php
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;
370✔
156
    }
157

158
    /**
159
     * Set PhpWord as reference.
160
     */
161
    public function setPhpWord(?PhpWord $phpWord = null): void
162
    {
163
        $this->phpWord = $phpWord;
379✔
164
    }
165

166
    /**
167
     * Get section number.
168
     *
169
     * @return int
170
     */
171
    public function getSectionId()
172
    {
173
        return $this->sectionId;
208✔
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;
397✔
185
        $this->docPartId = $docPartId;
397✔
186
    }
187

188
    /**
189
     * Get doc part.
190
     *
191
     * @return string
192
     */
193
    public function getDocPart()
194
    {
195
        return $this->docPart;
370✔
196
    }
197

198
    /**
199
     * Get doc part Id.
200
     *
201
     * @return int
202
     */
203
    public function getDocPartId()
204
    {
205
        return $this->docPartId;
368✔
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;
55✔
216
        if ($mediaPart == 'Header' || $mediaPart == 'Footer') {
55✔
217
            $mediaPart .= $this->docPartId;
9✔
218
        }
219

220
        return strtolower($mediaPart);
55✔
221
    }
222

223
    /**
224
     * Get element index.
225
     *
226
     * @return int
227
     */
228
    public function getElementIndex()
229
    {
230
        return $this->elementIndex;
3✔
231
    }
232

233
    /**
234
     * Set element index.
235
     *
236
     * @param int $value
237
     */
238
    public function setElementIndex($value): void
239
    {
240
        $this->elementIndex = $value;
366✔
241
    }
242

243
    /**
244
     * Get element unique ID.
245
     *
246
     * @return string
247
     */
248
    public function getElementId()
249
    {
250
        return $this->elementId;
20✔
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);
367✔
259
    }
260

261
    /**
262
     * Get relation Id.
263
     *
264
     * @return int
265
     */
266
    public function getRelationId()
267
    {
268
        return $this->relationId;
52✔
269
    }
270

271
    /**
272
     * Set relation Id.
273
     *
274
     * @param int $value
275
     */
276
    public function setRelationId($value): void
277
    {
278
        $this->relationId = $value;
94✔
279
    }
280

281
    /**
282
     * Get nested level.
283
     *
284
     * @return int
285
     */
286
    public function getNestedLevel()
287
    {
288
        return $this->nestedLevel;
381✔
289
    }
290

291
    /**
292
     * Get comments start.
293
     */
294
    public function getCommentsRangeStart(): ?Comments
295
    {
296
        return $this->commentsRangeStart;
137✔
297
    }
298

299
    /**
300
     * Get comment start.
301
     */
302
    public function getCommentRangeStart(): ?Comment
303
    {
UNCOV
304
        if ($this->commentsRangeStart != null) {
×
UNCOV
305
            return $this->commentsRangeStart->getItem($this->commentsRangeStart->countItems());
×
306
        }
307

UNCOV
308
        return null;
×
309
    }
310

311
    /**
312
     * Set comment start.
313
     */
314
    public function setCommentRangeStart(Comment $value): void
315
    {
316
        if ($this instanceof Comment) {
6✔
317
            throw new InvalidArgumentException('Cannot set a Comment on a Comment');
1✔
318
        }
319
        if ($this->commentsRangeStart == null) {
5✔
320
            $this->commentsRangeStart = new Comments();
5✔
321
        }
322
        // Set ID early to avoid duplicates.
323
        if ($value->getElementId() == null) {
5✔
324
            $value->setElementId();
5✔
325
        }
326
        foreach ($this->commentsRangeStart->getItems() as $comment) {
5✔
327
            if ($value->getElementId() == $comment->getElementId()) {
5✔
328
                return;
5✔
329
            }
330
        }
331
        $idxItem = $this->commentsRangeStart->addItem($value);
5✔
332
        $this->commentsRangeStart->getItem($idxItem)->setStartElement($this);
5✔
333
    }
334

335
    /**
336
     * Get comments end.
337
     */
338
    public function getCommentsRangeEnd(): ?Comments
339
    {
340
        return $this->commentsRangeEnd;
137✔
341
    }
342

343
    /**
344
     * Get comment end.
345
     */
346
    public function getCommentRangeEnd(): ?Comment
347
    {
UNCOV
348
        if ($this->commentsRangeEnd != null) {
×
UNCOV
349
            return $this->commentsRangeEnd->getItem($this->commentsRangeEnd->countItems());
×
350
        }
351

UNCOV
352
        return null;
×
353
    }
354

355
    /**
356
     * Set comment end.
357
     */
358
    public function setCommentRangeEnd(Comment $value): void
359
    {
360
        if ($this instanceof Comment) {
5✔
361
            throw new InvalidArgumentException('Cannot set a Comment on a Comment');
1✔
362
        }
363
        if ($this->commentsRangeEnd == null) {
4✔
364
            $this->commentsRangeEnd = new Comments();
4✔
365
        }
366
        // Set ID early to avoid duplicates.
367
        if ($value->getElementId() == null) {
4✔
UNCOV
368
            $value->setElementId();
×
369
        }
370
        foreach ($this->commentsRangeEnd->getItems() as $comment) {
4✔
371
            if ($value->getElementId() == $comment->getElementId()) {
4✔
372
                return;
4✔
373
            }
374
        }
375
        $idxItem = $this->commentsRangeEnd->addItem($value);
4✔
376
        $this->commentsRangeEnd->getItem($idxItem)->setEndElement($this);
4✔
377
    }
378

379
    /**
380
     * Get parent element.
381
     *
382
     * @return null|AbstractElement
383
     */
384
    public function getParent()
385
    {
386
        return $this->parent;
6✔
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);
369✔
397
        $this->parent = $container;
369✔
398

399
        // Set nested level
400
        $this->nestedLevel = $container->getNestedLevel();
369✔
401
        if ($this->parentContainer == 'Cell') {
369✔
402
            ++$this->nestedLevel;
59✔
403
        }
404

405
        // Set phpword
406
        $this->setPhpWord($container->getPhpWord());
369✔
407

408
        // Set doc part
409
        if (!$this instanceof Footnote) {
369✔
410
            $this->setDocPart($container->getDocPart(), $container->getDocPartId());
368✔
411
        }
412

413
        $this->setMediaRelation();
369✔
414
        $this->setCollectionRelation();
369✔
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) {
369✔
426
            return;
341✔
427
        }
428

429
        $elementName = substr(static::class, strrpos(static::class, '\\') + 1);
55✔
430
        if ($elementName == 'OLEObject') {
55✔
431
            $elementName = 'Object';
8✔
432
        }
433
        $mediaPart = $this->getMediaPart();
55✔
434
        $source = $this->getSource();
55✔
435
        $image = null;
55✔
436
        if ($this instanceof Image) {
55✔
437
            $image = $this;
40✔
438
        }
439
        $rId = Media::addElement($mediaPart, strtolower($elementName), $source, $image);
55✔
440
        $this->setRelationId($rId);
55✔
441

442
        if ($this instanceof OLEObject) {
55✔
443
            $icon = $this->getIcon();
8✔
444
            $rId = Media::addElement($mediaPart, 'image', $icon, new Image($icon));
8✔
445
            $this->setImageRelationId($rId);
8✔
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) {
369✔
455
            $elementName = substr(static::class, strrpos(static::class, '\\') + 1);
43✔
456
            $addMethod = "add{$elementName}";
43✔
457
            $rId = $this->phpWord->$addMethod($this);
43✔
458
            $this->setRelationId($rId);
43✔
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';
26✔
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)) {
419✔
484
            $styleObject->setStyleByArray($styleValue);
93✔
485
            $style = $styleObject;
93✔
486
        } else {
487
            $style = $returnObject ? $styleObject : $styleValue;
396✔
488
        }
489

490
        return $style;
419✔
491
    }
492

493
    /**
494
     * Sets the trackChange information.
495
     */
496
    public function setTrackChange(TrackChange $trackChange): void
497
    {
498
        $this->trackChange = $trackChange;
5✔
499
    }
500

501
    /**
502
     * Gets the trackChange information.
503
     *
504
     * @return TrackChange
505
     */
506
    public function getTrackChange()
507
    {
508
        return $this->trackChange;
186✔
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);
6✔
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)) {
15✔
537
            throw new InvalidArgumentException("Invalid style value: {$value}");
1✔
538
        } elseif ($value === null || trim($value) == '') {
15✔
539
            $value = $default;
1✔
540
        }
541

542
        return $value;
15✔
543
    }
544
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc