Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
96 / 96 |
|
100.00% |
7 / 7 |
CRAP | |
100.00% |
1 / 1 |
AbstractContainer | |
100.00% |
96 / 96 |
|
100.00% |
7 / 7 |
28 | |
100.00% |
1 / 1 |
__call | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
7 | |||
addElement | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 | |||
getElements | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getElement | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
removeElement | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
6 | |||
countElements | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
checkValidity | |
100.00% |
46 / 46 |
|
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 BadMethodCallException; |
21 | use PhpOffice\Math\Math; |
22 | use ReflectionClass; |
23 | |
24 | /** |
25 | * Container abstract class. |
26 | * |
27 | * @method Text addText(string $text, mixed $fStyle = null, mixed $pStyle = null) |
28 | * @method TextRun addTextRun(mixed $pStyle = null) |
29 | * @method Bookmark addBookmark(string $name) |
30 | * @method Link addLink(string $target, string $text = null, mixed $fStyle = null, mixed $pStyle = null, boolean $internal = false) |
31 | * @method PreserveText addPreserveText(string $text, mixed $fStyle = null, mixed $pStyle = null) |
32 | * @method void addTextBreak(int $count = 1, mixed $fStyle = null, mixed $pStyle = null) |
33 | * @method ListItem addListItem(string $txt, int $depth = 0, mixed $font = null, mixed $list = null, mixed $para = null) |
34 | * @method ListItemRun addListItemRun(int $depth = 0, mixed $listStyle = null, mixed $pStyle = null) |
35 | * @method Footnote addFootnote(mixed $pStyle = null) |
36 | * @method Endnote addEndnote(mixed $pStyle = null) |
37 | * @method CheckBox addCheckBox(string $name, $text, mixed $fStyle = null, mixed $pStyle = null) |
38 | * @method Title addTitle(mixed $text, int $depth = 1, int $pageNumber = null) |
39 | * @method TOC addTOC(mixed $fontStyle = null, mixed $tocStyle = null, int $minDepth = 1, int $maxDepth = 9) |
40 | * @method PageBreak addPageBreak() |
41 | * @method Table addTable(mixed $style = null) |
42 | * @method Image addImage(string $source, mixed $style = null, bool $isWatermark = false, $name = null) |
43 | * @method OLEObject addOLEObject(string $source, mixed $style = null) |
44 | * @method TextBox addTextBox(mixed $style = null) |
45 | * @method Field addField(string $type = null, array $properties = array(), array $options = array(), mixed $text = null) |
46 | * @method Line addLine(mixed $lineStyle = null) |
47 | * @method Shape addShape(string $type, mixed $style = null) |
48 | * @method Chart addChart(string $type, array $categories, array $values, array $style = null, $seriesName = null) |
49 | * @method FormField addFormField(string $type, mixed $fStyle = null, mixed $pStyle = null) |
50 | * @method SDT addSDT(string $type) |
51 | * @method Formula addFormula(Math $math) |
52 | * @method \PhpOffice\PhpWord\Element\OLEObject addObject(string $source, mixed $style = null) deprecated, use addOLEObject instead |
53 | * |
54 | * @since 0.10.0 |
55 | */ |
56 | abstract class AbstractContainer extends AbstractElement |
57 | { |
58 | /** |
59 | * Elements collection. |
60 | * |
61 | * @var \PhpOffice\PhpWord\Element\AbstractElement[] |
62 | */ |
63 | protected $elements = []; |
64 | |
65 | /** |
66 | * Container type Section|Header|Footer|Footnote|Endnote|Cell|TextRun|TextBox|ListItemRun|TrackChange. |
67 | * |
68 | * @var string |
69 | */ |
70 | protected $container; |
71 | |
72 | /** |
73 | * Magic method to catch all 'addElement' variation. |
74 | * |
75 | * This removes addText, addTextRun, etc. When adding new element, we have to |
76 | * add the model in the class docblock with `@method`. |
77 | * |
78 | * Warning: This makes capitalization matters, e.g. addCheckbox or addcheckbox won't work. |
79 | * |
80 | * @param mixed $function |
81 | * @param mixed $args |
82 | * |
83 | * @return \PhpOffice\PhpWord\Element\AbstractElement |
84 | */ |
85 | public function __call($function, $args) |
86 | { |
87 | $elements = [ |
88 | 'Text', 'TextRun', 'Bookmark', 'Link', 'PreserveText', 'TextBreak', |
89 | 'ListItem', 'ListItemRun', 'Table', 'Image', 'Object', 'OLEObject', |
90 | 'Footnote', 'Endnote', 'CheckBox', 'TextBox', 'Field', |
91 | 'Line', 'Shape', 'Title', 'TOC', 'PageBreak', |
92 | 'Chart', 'FormField', 'SDT', 'Comment', |
93 | 'Formula', |
94 | ]; |
95 | $functions = []; |
96 | foreach ($elements as $element) { |
97 | $functions['add' . strtolower($element)] = $element == 'Object' ? 'OLEObject' : $element; |
98 | } |
99 | |
100 | // Run valid `add` command |
101 | $function = strtolower($function); |
102 | if (isset($functions[$function])) { |
103 | $element = $functions[$function]; |
104 | |
105 | // Special case for TextBreak |
106 | // @todo Remove the `$count` parameter in 1.0.0 to make this element similiar to other elements? |
107 | if ($element == 'TextBreak') { |
108 | [$count, $fontStyle, $paragraphStyle] = array_pad($args, 3, null); |
109 | if ($count === null) { |
110 | $count = 1; |
111 | } |
112 | for ($i = 1; $i <= $count; ++$i) { |
113 | $this->addElement($element, $fontStyle, $paragraphStyle); |
114 | } |
115 | } else { |
116 | // All other elements |
117 | array_unshift($args, $element); // Prepend element name to the beginning of args array |
118 | |
119 | return call_user_func_array([$this, 'addElement'], $args); |
120 | } |
121 | } |
122 | |
123 | return null; |
124 | } |
125 | |
126 | /** |
127 | * Add element. |
128 | * |
129 | * Each element has different number of parameters passed |
130 | * |
131 | * @param string $elementName |
132 | * |
133 | * @return \PhpOffice\PhpWord\Element\AbstractElement |
134 | */ |
135 | protected function addElement($elementName) |
136 | { |
137 | $elementClass = __NAMESPACE__ . '\\' . $elementName; |
138 | $this->checkValidity($elementName); |
139 | |
140 | // Get arguments |
141 | $args = func_get_args(); |
142 | $withoutP = in_array($this->container, ['TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field']); |
143 | if ($withoutP && ($elementName == 'Text' || $elementName == 'PreserveText')) { |
144 | $args[3] = null; // Remove paragraph style for texts in textrun |
145 | } |
146 | |
147 | // Create element using reflection |
148 | $reflection = new ReflectionClass($elementClass); |
149 | $elementArgs = $args; |
150 | array_shift($elementArgs); // Shift the $elementName off the beginning of array |
151 | |
152 | /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */ |
153 | $element = $reflection->newInstanceArgs($elementArgs); |
154 | |
155 | // Set parent container |
156 | $element->setParentContainer($this); |
157 | $element->setElementIndex($this->countElements() + 1); |
158 | $element->setElementId(); |
159 | |
160 | $this->elements[] = $element; |
161 | |
162 | return $element; |
163 | } |
164 | |
165 | /** |
166 | * Get all elements. |
167 | * |
168 | * @return \PhpOffice\PhpWord\Element\AbstractElement[] |
169 | */ |
170 | public function getElements() |
171 | { |
172 | return $this->elements; |
173 | } |
174 | |
175 | /** |
176 | * Returns the element at the requested position. |
177 | * |
178 | * @param int $index |
179 | * |
180 | * @return null|\PhpOffice\PhpWord\Element\AbstractElement |
181 | */ |
182 | public function getElement($index) |
183 | { |
184 | if (array_key_exists($index, $this->elements)) { |
185 | return $this->elements[$index]; |
186 | } |
187 | |
188 | return null; |
189 | } |
190 | |
191 | /** |
192 | * Removes the element at requested index. |
193 | * |
194 | * @param int|\PhpOffice\PhpWord\Element\AbstractElement $toRemove |
195 | */ |
196 | public function removeElement($toRemove): void |
197 | { |
198 | if (is_int($toRemove) && array_key_exists($toRemove, $this->elements)) { |
199 | unset($this->elements[$toRemove]); |
200 | } elseif ($toRemove instanceof \PhpOffice\PhpWord\Element\AbstractElement) { |
201 | foreach ($this->elements as $key => $element) { |
202 | if ($element->getElementId() === $toRemove->getElementId()) { |
203 | unset($this->elements[$key]); |
204 | |
205 | return; |
206 | } |
207 | } |
208 | } |
209 | } |
210 | |
211 | /** |
212 | * Count elements. |
213 | * |
214 | * @return int |
215 | */ |
216 | public function countElements() |
217 | { |
218 | return count($this->elements); |
219 | } |
220 | |
221 | /** |
222 | * Check if a method is allowed for the current container. |
223 | * |
224 | * @param string $method |
225 | * |
226 | * @return bool |
227 | */ |
228 | private function checkValidity($method) |
229 | { |
230 | $generalContainers = [ |
231 | 'Section', 'Header', 'Footer', 'Footnote', 'Endnote', 'Cell', 'TextRun', 'TextBox', 'ListItemRun', 'TrackChange', |
232 | ]; |
233 | |
234 | $validContainers = [ |
235 | 'Text' => $generalContainers, |
236 | 'Bookmark' => $generalContainers, |
237 | 'Link' => $generalContainers, |
238 | 'TextBreak' => $generalContainers, |
239 | 'Image' => $generalContainers, |
240 | 'OLEObject' => $generalContainers, |
241 | 'Field' => $generalContainers, |
242 | 'Line' => $generalContainers, |
243 | 'Shape' => $generalContainers, |
244 | 'FormField' => $generalContainers, |
245 | 'SDT' => $generalContainers, |
246 | 'TrackChange' => $generalContainers, |
247 | 'TextRun' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox', 'TrackChange', 'ListItemRun'], |
248 | 'ListItem' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], |
249 | 'ListItemRun' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], |
250 | 'Table' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], |
251 | 'CheckBox' => ['Section', 'Header', 'Footer', 'Cell', 'TextRun'], |
252 | 'TextBox' => ['Section', 'Header', 'Footer', 'Cell'], |
253 | 'Footnote' => ['Section', 'TextRun', 'Cell', 'ListItemRun'], |
254 | 'Endnote' => ['Section', 'TextRun', 'Cell'], |
255 | 'PreserveText' => ['Section', 'Header', 'Footer', 'Cell'], |
256 | 'Title' => ['Section', 'Cell'], |
257 | 'TOC' => ['Section'], |
258 | 'PageBreak' => ['Section'], |
259 | 'Chart' => ['Section', 'Cell'], |
260 | ]; |
261 | |
262 | // Special condition, e.g. preservetext can only exists in cell when |
263 | // the cell is located in header or footer |
264 | $validSubcontainers = [ |
265 | 'PreserveText' => [['Cell'], ['Header', 'Footer', 'Section']], |
266 | 'Footnote' => [['Cell', 'TextRun'], ['Section']], |
267 | 'Endnote' => [['Cell', 'TextRun'], ['Section']], |
268 | ]; |
269 | |
270 | // Check if a method is valid for current container |
271 | if (isset($validContainers[$method])) { |
272 | if (!in_array($this->container, $validContainers[$method])) { |
273 | throw new BadMethodCallException("Cannot add {$method} in {$this->container}."); |
274 | } |
275 | } |
276 | |
277 | // Check if a method is valid for current container, located in other container |
278 | if (isset($validSubcontainers[$method])) { |
279 | $rules = $validSubcontainers[$method]; |
280 | $containers = $rules[0]; |
281 | $allowedDocParts = $rules[1]; |
282 | foreach ($containers as $container) { |
283 | if ($this->container == $container && !in_array($this->getDocPart(), $allowedDocParts)) { |
284 | throw new BadMethodCallException("Cannot add {$method} in {$this->container}."); |
285 | } |
286 | } |
287 | } |
288 | |
289 | return true; |
290 | } |
291 | } |