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