PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/PhpWord/Element/AbstractContainer.php

https://github.com/cyrillkalita/PHPWord
PHP | 341 lines | 194 code | 22 blank | 125 comment | 27 complexity | dcf124a5c94e3105c22beefc1a807080 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-3.0
  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. * @link https://github.com/PHPOffice/PHPWord
  14. * @copyright 2010-2014 PHPWord contributors
  15. * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
  16. */
  17. namespace PhpOffice\PhpWord\Element;
  18. use PhpOffice\PhpWord\Media;
  19. use PhpOffice\PhpWord\PhpWord;
  20. /**
  21. * Container abstract class
  22. *
  23. * @method Text addText($text, $fStyle = null, $pStyle = null)
  24. * @method TextRun addTextRun($pStyle = null)
  25. * @method Link addLink($target, $text = null, $fStyle = null, $pStyle = null)
  26. * @method PreserveText addPreserveText($text, $fStyle = null, $pStyle = null)
  27. * @method void addTextBreak($count = 1, $fStyle = null, $pStyle = null)
  28. * @method ListItem addListItem($text, $depth = 0, $fStyle = null, $listStyle = null, $pStyle = null)
  29. * @method ListItemRun addListItemRun($depth = 0, $listStyle = null, $pStyle = null)
  30. * @method Table addTable($style = null)
  31. * @method Image addImage($source, $style = null, $isWatermark = false)
  32. * @method Object addObject($source, $style = null)
  33. * @method Footnote addFootnote($pStyle = null)
  34. * @method Endnote addEndnote($pStyle = null)
  35. * @method CheckBox addCheckBox($name, $text, $fStyle = null, $pStyle = null)
  36. * @method TextBox addTextBox($style = null)
  37. * @method Field addField($type = null, $properties = array(), $options = array())
  38. * @method Line addLine($lineStyle = null)
  39. *
  40. * @since 0.10.0
  41. */
  42. abstract class AbstractContainer extends AbstractElement
  43. {
  44. /**
  45. * Elements collection
  46. *
  47. * @var array
  48. */
  49. protected $elements = array();
  50. /**
  51. * Container type Section|Header|Footer|Footnote|Endnote|Cell|TextRun|TextBox|ListItemRun
  52. *
  53. * @var string
  54. */
  55. protected $container;
  56. /**
  57. * Magic method to catch all 'addElement' variation
  58. *
  59. * This removes addText, addTextRun, etc. When adding new element, we have to
  60. * add the model in the class docblock with `@method`.
  61. *
  62. * Warning: This makes capitalization matters, e.g. addCheckbox or addcheckbox won't work.
  63. *
  64. * @param mixed $function
  65. * @param mixed $args
  66. * @return \PhpOffice\PhpWord\Element\AbstractElement
  67. */
  68. public function __call($function, $args)
  69. {
  70. $elements = array('Text', 'TextRun', 'Link', 'PreserveText', 'TextBreak',
  71. 'ListItem', 'ListItemRun', 'Table', 'Image', 'Object', 'Footnote',
  72. 'Endnote', 'CheckBox', 'TextBox', 'Field', 'Line');
  73. $functions = array();
  74. for ($i = 0; $i < count($elements); $i++) {
  75. $functions[$i] = 'add' . $elements[$i];
  76. }
  77. // Run valid `add` command
  78. if (in_array($function, $functions)) {
  79. $element = str_replace('add', '', $function);
  80. // Special case for TextBreak
  81. // @todo Remove the `$count` parameter in 1.0.0 to make this element similiar to other elements?
  82. if ($element == 'TextBreak') {
  83. @list($count, $fontStyle, $paragraphStyle) = $args; // Suppress error
  84. if ($count === null) {
  85. $count = 1;
  86. }
  87. for ($i = 1; $i <= $count; $i++) {
  88. $this->addElement($element, $fontStyle, $paragraphStyle);
  89. }
  90. // All other elements
  91. } else {
  92. array_unshift($args, $element); // Prepend element name to the beginning of args array
  93. return call_user_func_array(array($this, 'addElement'), $args);
  94. }
  95. }
  96. return null;
  97. }
  98. /**
  99. * Add element
  100. *
  101. * Each element has different number of parameters passed
  102. *
  103. * @param string $elementName
  104. * @return \PhpOffice\PhpWord\Element\AbstractElement
  105. */
  106. protected function addElement($elementName)
  107. {
  108. $elementClass = __NAMESPACE__ . '\\' . $elementName;
  109. $this->checkValidity($elementName);
  110. // Get arguments
  111. $args = func_get_args();
  112. $withoutP = in_array($this->container, array('TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field'));
  113. if ($withoutP && ($elementName == 'Text' || $elementName == 'PreserveText')) {
  114. $args[3] = null; // Remove paragraph style for texts in textrun
  115. }
  116. $source = '';
  117. if (count($args) > 1) {
  118. $source = $args[1];
  119. }
  120. // Create element using reflection
  121. $reflection = new \ReflectionClass($elementClass);
  122. $elementArgs = $args;
  123. array_shift($elementArgs); // Shift the $elementName off the beginning of array
  124. /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */
  125. $element = $reflection->newInstanceArgs($elementArgs);
  126. // Set nested level and relation Id
  127. $this->setElementNestedLevel($element);
  128. $this->setElementRelationId($element, $elementName, $source);
  129. // Set other properties and add element into collection
  130. $element->setDocPart($this->getDocPart(), $this->getDocPartId());
  131. $element->setElementIndex($this->countElements() + 1);
  132. $element->setElementId();
  133. $element->setPhpWord($this->phpWord);
  134. $this->elements[] = $element;
  135. return $element;
  136. }
  137. /**
  138. * Get all elements
  139. *
  140. * @return array
  141. */
  142. public function getElements()
  143. {
  144. return $this->elements;
  145. }
  146. /**
  147. * Count elements
  148. *
  149. * @return int
  150. */
  151. public function countElements()
  152. {
  153. return count($this->elements);
  154. }
  155. /**
  156. * Set element nested level based on container; add one when it's inside a cell
  157. */
  158. private function setElementNestedLevel(AbstractElement $element)
  159. {
  160. if ($this->container == 'Cell') {
  161. $element->setNestedLevel($this->getNestedLevel() + 1);
  162. } else {
  163. $element->setNestedLevel($this->getNestedLevel());
  164. }
  165. }
  166. /**
  167. * Set relation Id
  168. *
  169. * @param string $elementName
  170. * @param string $source
  171. */
  172. private function setElementRelationId(AbstractElement $element, $elementName, $source)
  173. {
  174. $mediaContainer = $this->getMediaContainer();
  175. $hasMediaRelation = in_array($elementName, array('Link', 'Image', 'Object'));
  176. $hasOtherRelation = in_array($elementName, array('Footnote', 'Endnote', 'Title'));
  177. // Set relation Id for media elements (link, image, object; legacy of OOXML)
  178. // Only Image that needs to be passed to Media class
  179. if ($hasMediaRelation) {
  180. /** @var \PhpOffice\PhpWord\Element\Image $element Type hint */
  181. $image = ($elementName == 'Image') ? $element : null;
  182. $rId = Media::addElement($mediaContainer, strtolower($elementName), $source, $image);
  183. $element->setRelationId($rId);
  184. }
  185. // Set relation Id for icon of object element
  186. if ($elementName == 'Object') {
  187. /** @var \PhpOffice\PhpWord\Element\Object $element Type hint */
  188. $rIdIcon = Media::addElement($mediaContainer, 'image', $element->getIcon(), new Image($element->getIcon()));
  189. $element->setImageRelationId($rIdIcon);
  190. }
  191. // Set relation Id for elements that will be registered in the Collection subnamespaces
  192. if ($hasOtherRelation && $this->phpWord instanceof PhpWord) {
  193. $addMethod = "add{$elementName}";
  194. $rId = $this->phpWord->$addMethod($element);
  195. $element->setRelationId($rId);
  196. }
  197. }
  198. /**
  199. * Check if a method is allowed for the current container
  200. *
  201. * @param string $method
  202. * @return bool
  203. * @throws \BadMethodCallException
  204. */
  205. private function checkValidity($method)
  206. {
  207. // Valid containers for each element
  208. $allContainers = array(
  209. 'Section', 'Header', 'Footer', 'Footnote', 'Endnote',
  210. 'Cell', 'TextRun', 'TextBox', 'ListItemRun',
  211. );
  212. $validContainers = array(
  213. 'Text' => $allContainers,
  214. 'Link' => $allContainers,
  215. 'TextBreak' => $allContainers,
  216. 'Image' => $allContainers,
  217. 'Object' => $allContainers,
  218. 'Field' => $allContainers,
  219. 'Line' => $allContainers,
  220. 'TextRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'),
  221. 'ListItem' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'),
  222. 'ListItemRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'),
  223. 'Table' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'),
  224. 'CheckBox' => array('Section', 'Header', 'Footer', 'Cell'),
  225. 'TextBox' => array('Section', 'Header', 'Footer', 'Cell'),
  226. 'Footnote' => array('Section', 'TextRun', 'Cell'),
  227. 'Endnote' => array('Section', 'TextRun', 'Cell'),
  228. 'PreserveText' => array('Header', 'Footer', 'Cell'),
  229. );
  230. // Special condition, e.g. preservetext can only exists in cell when
  231. // the cell is located in header or footer
  232. $validSubcontainers = array(
  233. 'PreserveText' => array(array('Cell'), array('Header', 'Footer')),
  234. 'Footnote' => array(array('Cell', 'TextRun'), array('Section')),
  235. 'Endnote' => array(array('Cell', 'TextRun'), array('Section')),
  236. );
  237. // Check if a method is valid for current container
  238. if (array_key_exists($method, $validContainers)) {
  239. if (!in_array($this->container, $validContainers[$method])) {
  240. throw new \BadMethodCallException("Cannot add $method in $this->container.");
  241. }
  242. }
  243. // Check if a method is valid for current container, located in other container
  244. if (array_key_exists($method, $validSubcontainers)) {
  245. $rules = $validSubcontainers[$method];
  246. $containers = $rules[0];
  247. $allowedDocParts = $rules[1];
  248. foreach ($containers as $container) {
  249. if ($this->container == $container && !in_array($this->getDocPart(), $allowedDocParts)) {
  250. throw new \BadMethodCallException("Cannot add $method in $this->container.");
  251. }
  252. }
  253. }
  254. return true;
  255. }
  256. /**
  257. * Return media element (image, object, link) container name
  258. *
  259. * @return string section|headerx|footerx|footnote|endnote
  260. */
  261. private function getMediaContainer()
  262. {
  263. $partName = $this->container;
  264. if (in_array($partName, array('Cell', 'TextRun', 'TextBox', 'ListItemRun'))) {
  265. $partName = $this->getDocPart();
  266. }
  267. if ($partName == 'Header' || $partName == 'Footer') {
  268. $partName .= $this->getDocPartId();
  269. }
  270. return strtolower($partName);
  271. }
  272. /**
  273. * Add memory image element
  274. *
  275. * @param string $src
  276. * @param mixed $style
  277. * @return \PhpOffice\PhpWord\Element\Image
  278. * @deprecated 0.9.0
  279. * @codeCoverageIgnore
  280. */
  281. public function addMemoryImage($src, $style = null)
  282. {
  283. return $this->addImage($src, $style);
  284. }
  285. /**
  286. * Create textrun element
  287. *
  288. * @param mixed $paragraphStyle
  289. * @return \PhpOffice\PhpWord\Element\TextRun
  290. * @deprecated 0.10.0
  291. * @codeCoverageIgnore
  292. */
  293. public function createTextRun($paragraphStyle = null)
  294. {
  295. return $this->addTextRun($paragraphStyle);
  296. }
  297. /**
  298. * Create footnote element
  299. *
  300. * @param mixed $paragraphStyle
  301. * @return \PhpOffice\PhpWord\Element\Footnote
  302. * @deprecated 0.10.0
  303. * @codeCoverageIgnore
  304. */
  305. public function createFootnote($paragraphStyle = null)
  306. {
  307. return $this->addFootnote($paragraphStyle);
  308. }
  309. }