649 lines
17 KiB
PHP
649 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* PHPWord
|
|
*
|
|
* @link https://github.com/PHPOffice/PHPWord
|
|
* @copyright 2014 PHPWord
|
|
* @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
|
|
*/
|
|
|
|
namespace PhpOffice\PhpWord\Element;
|
|
|
|
use PhpOffice\PhpWord\Element\Element;
|
|
use PhpOffice\PhpWord\Element\Footnote as FootnoteElement;
|
|
use PhpOffice\PhpWord\Endnotes;
|
|
use PhpOffice\PhpWord\Exception\InvalidObjectException;
|
|
use PhpOffice\PhpWord\Footnotes;
|
|
use PhpOffice\PhpWord\Media;
|
|
use PhpOffice\PhpWord\Shared\String;
|
|
use PhpOffice\PhpWord\Style;
|
|
use PhpOffice\PhpWord\TOC;
|
|
|
|
/**
|
|
* Container abstract class
|
|
*
|
|
* @since 0.10.0
|
|
*/
|
|
abstract class AbstractElement
|
|
{
|
|
/**
|
|
* Container type section|header|footer|cell|textrun|footnote|endnote
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $container;
|
|
|
|
/**
|
|
* Section Id
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $sectionId;
|
|
|
|
/**
|
|
* Document part type: section|header|footer
|
|
*
|
|
* Used by textrun and cell container to determine where the element is
|
|
* located because it will affect the availability of other element,
|
|
* e.g. footnote will not be available when $docPart is header or footer.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $docPart = 'section';
|
|
|
|
/**
|
|
* Document part Id
|
|
*
|
|
* For header and footer, this will be = ($sectionId - 1) * 3 + $index
|
|
* because the max number of header/footer in every page is 3, i.e.
|
|
* AUTO, FIRST, and EVEN (AUTO = ODD)
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $docPartId = 1;
|
|
|
|
/**
|
|
* Elements collection
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $elements = array();
|
|
|
|
/**
|
|
* Index of element in the elements collection (start with 1)
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $elementIndex = 1;
|
|
|
|
/**
|
|
* Unique Id for element
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $elementId;
|
|
|
|
/**
|
|
* Relation Id
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $relationId;
|
|
|
|
/**
|
|
* Add text element
|
|
*
|
|
* @param string $text
|
|
* @param mixed $fontStyle
|
|
* @param mixed $paragraphStyle
|
|
* @return Text
|
|
*/
|
|
public function addText($text, $fontStyle = null, $paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('text');
|
|
|
|
// Reset paragraph style for footnote and textrun. They have their own
|
|
if (in_array($this->container, array('textrun', 'footnote', 'endnote'))) {
|
|
$paragraphStyle = null;
|
|
}
|
|
|
|
$text = String::toUTF8($text);
|
|
$textObject = new Text($text, $fontStyle, $paragraphStyle);
|
|
$textObject->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$this->addElement($textObject);
|
|
|
|
return $textObject;
|
|
}
|
|
|
|
/**
|
|
* Add textrun element
|
|
*
|
|
* @param mixed $paragraphStyle
|
|
* @return TextRun
|
|
*/
|
|
public function addTextRun($paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('textrun');
|
|
|
|
$textRun = new TextRun($paragraphStyle);
|
|
$textRun->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$this->addElement($textRun);
|
|
|
|
return $textRun;
|
|
}
|
|
|
|
/**
|
|
* Add link element
|
|
*
|
|
* @param string $linkSrc
|
|
* @param string $linkName
|
|
* @param mixed $fontStyle
|
|
* @param mixed $paragraphStyle
|
|
* @return Link
|
|
*/
|
|
public function addLink($linkSrc, $linkName = null, $fontStyle = null, $paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('link');
|
|
$elementDocPart = $this->checkElementDocPart();
|
|
|
|
$link = new Link(String::toUTF8($linkSrc), String::toUTF8($linkName), $fontStyle, $paragraphStyle);
|
|
$link->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$rId = Media::addElement($elementDocPart, 'link', $linkSrc);
|
|
$link->setRelationId($rId);
|
|
$this->addElement($link);
|
|
|
|
return $link;
|
|
}
|
|
|
|
/**
|
|
* Add a Title Element
|
|
*
|
|
* @param string $text
|
|
* @param int $depth
|
|
* @return Title
|
|
* @todo Enable title element in other containers
|
|
*/
|
|
public function addTitle($text, $depth = 1)
|
|
{
|
|
$this->checkValidity('title');
|
|
|
|
$styles = Style::getStyles();
|
|
if (array_key_exists('Heading_' . $depth, $styles)) {
|
|
$style = 'Heading' . $depth;
|
|
} else {
|
|
$style = null;
|
|
}
|
|
$text = String::toUTF8($text);
|
|
$title = new Title($text, $depth, $style);
|
|
$title->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$data = TOC::addTitle($text, $depth);
|
|
$anchor = $data[0];
|
|
$bookmarkId = $data[1];
|
|
$title->setAnchor($anchor);
|
|
$title->setBookmarkId($bookmarkId);
|
|
$this->addElement($title);
|
|
|
|
return $title;
|
|
}
|
|
|
|
/**
|
|
* Add preserve text element
|
|
*
|
|
* @param string $text
|
|
* @param mixed $fontStyle
|
|
* @param mixed $paragraphStyle
|
|
* @return PreserveText
|
|
*/
|
|
public function addPreserveText($text, $fontStyle = null, $paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('preservetext');
|
|
|
|
$preserveText = new PreserveText(String::toUTF8($text), $fontStyle, $paragraphStyle);
|
|
$preserveText->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$this->addElement($preserveText);
|
|
|
|
return $preserveText;
|
|
}
|
|
|
|
/**
|
|
* Add text break element
|
|
*
|
|
* @param int $count
|
|
* @param mixed $fontStyle
|
|
* @param mixed $paragraphStyle
|
|
*/
|
|
public function addTextBreak($count = 1, $fontStyle = null, $paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('textbreak');
|
|
|
|
for ($i = 1; $i <= $count; $i++) {
|
|
$textBreak = new TextBreak($fontStyle, $paragraphStyle);
|
|
$textBreak->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$this->addElement($textBreak);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add listitem element
|
|
*
|
|
* @param string $text
|
|
* @param int $depth
|
|
* @param mixed $fontStyle
|
|
* @param mixed $styleList
|
|
* @param mixed $paragraphStyle
|
|
* @return ListItem
|
|
*/
|
|
public function addListItem($text, $depth = 0, $fontStyle = null, $styleList = null, $paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('listitem');
|
|
|
|
$listItem = new ListItem(String::toUTF8($text), $depth, $fontStyle, $styleList, $paragraphStyle);
|
|
$listItem->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$this->addElement($listItem);
|
|
|
|
return $listItem;
|
|
}
|
|
|
|
/**
|
|
* Add table element
|
|
*
|
|
* @param mixed $style
|
|
* @return Table
|
|
*/
|
|
public function addTable($style = null)
|
|
{
|
|
$this->checkValidity('table');
|
|
|
|
$table = new Table($this->getDocPart(), $this->getDocPartId(), $style);
|
|
$this->addElement($table);
|
|
|
|
return $table;
|
|
}
|
|
|
|
/**
|
|
* Add image element
|
|
*
|
|
* @param string $src
|
|
* @param mixed $style Image style
|
|
* @param boolean $isWatermark
|
|
* @return Image
|
|
*/
|
|
public function addImage($src, $style = null, $isWatermark = false)
|
|
{
|
|
$this->checkValidity('image');
|
|
$elementDocPart = $this->checkElementDocPart();
|
|
|
|
$image = new Image($src, $style, $isWatermark);
|
|
$image->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$rId = Media::addElement($elementDocPart, 'image', $src, $image);
|
|
$image->setRelationId($rId);
|
|
$this->addElement($image);
|
|
|
|
return $image;
|
|
}
|
|
|
|
/**
|
|
* Add OLE-object element
|
|
*
|
|
* All exceptions should be handled by \PhpOffice\PhpWord\Element\Object
|
|
*
|
|
* @param string $src
|
|
* @param mixed $style
|
|
* @return Object
|
|
* @throws \PhpOffice\PhpWord\Exception\Exception
|
|
* @todo Enable OLE object element in header and footer
|
|
*/
|
|
public function addObject($src, $style = null)
|
|
{
|
|
$this->checkValidity('object');
|
|
$elementDocPart = $this->checkElementDocPart();
|
|
|
|
$object = new Object($src, $style);
|
|
$object->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
if (!is_null($object->getSource())) {
|
|
$inf = pathinfo($src);
|
|
$ext = $inf['extension'];
|
|
if (strlen($ext) == 4 && strtolower(substr($ext, -1)) == 'x') {
|
|
$ext = substr($ext, 0, -1);
|
|
}
|
|
$icon = realpath(__DIR__ . "/../_staticDocParts/_{$ext}.png");
|
|
$rId = Media::addElement($elementDocPart, 'object', $src);
|
|
$object->setRelationId($rId);
|
|
$rIdimg = Media::addElement($elementDocPart, 'image', $icon, new Image($icon));
|
|
$object->setImageRelationId($rIdimg);
|
|
$this->addElement($object);
|
|
|
|
return $object;
|
|
} else {
|
|
throw new InvalidObjectException();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add footnote element
|
|
*
|
|
* @param mixed $paragraphStyle
|
|
* @return FootnoteElement
|
|
*/
|
|
public function addFootnote($paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('footnote');
|
|
|
|
$footnote = new FootnoteElement($paragraphStyle);
|
|
$rId = Footnotes::addElement($footnote);
|
|
|
|
$footnote->setDocPart('footnote', $this->getDocPartId());
|
|
$footnote->setRelationId($rId);
|
|
$this->addElement($footnote);
|
|
|
|
return $footnote;
|
|
}
|
|
|
|
/**
|
|
* Add endnote element
|
|
*
|
|
* @param mixed $paragraphStyle
|
|
* @return Endnote
|
|
*/
|
|
public function addEndnote($paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('endnote');
|
|
|
|
$endnote = new Endnote($paragraphStyle);
|
|
$rId = Endnotes::addElement($endnote);
|
|
|
|
$endnote->setDocPart('endnote', $this->getDocPartId());
|
|
$endnote->setRelationId($rId);
|
|
$this->addElement($endnote);
|
|
|
|
return $endnote;
|
|
}
|
|
|
|
/**
|
|
* Add a CheckBox Element
|
|
*
|
|
* @param string $name
|
|
* @param string $text
|
|
* @param mixed $fontStyle
|
|
* @param mixed $paragraphStyle
|
|
* @return CheckBox
|
|
*/
|
|
public function addCheckBox($name, $text, $fontStyle = null, $paragraphStyle = null)
|
|
{
|
|
$this->checkValidity('checkbox');
|
|
|
|
$checkBox = new CheckBox(String::toUTF8($name), String::toUTF8($text), $fontStyle, $paragraphStyle);
|
|
$checkBox->setDocPart($this->getDocPart(), $this->getDocPartId());
|
|
$this->addElement($checkBox);
|
|
|
|
return $checkBox;
|
|
}
|
|
|
|
/**
|
|
* Get section number
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getSectionId()
|
|
{
|
|
return $this->sectionId;
|
|
}
|
|
|
|
/**
|
|
* Set doc part
|
|
*
|
|
* @param string $docPart
|
|
* @param integer $docPartId
|
|
*/
|
|
public function setDocPart($docPart, $docPartId = 1)
|
|
{
|
|
$this->docPart = $docPart;
|
|
$this->docPartId = $docPartId;
|
|
}
|
|
|
|
/**
|
|
* Get doc part
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getDocPart()
|
|
{
|
|
return $this->docPart;
|
|
}
|
|
|
|
/**
|
|
* Get doc part Id
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getDocPartId()
|
|
{
|
|
return $this->docPartId;
|
|
}
|
|
|
|
/**
|
|
* Set element index and unique id, and add element into elements collection
|
|
*/
|
|
protected function addElement(AbstractElement $element)
|
|
{
|
|
$element->setElementIndex($this->countElements() + 1);
|
|
$element->setElementId();
|
|
$this->elements[] = $element;
|
|
}
|
|
|
|
/**
|
|
* Get all elements
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getElements()
|
|
{
|
|
return $this->elements;
|
|
}
|
|
|
|
/**
|
|
* Count elements
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function countElements()
|
|
{
|
|
return count($this->elements);
|
|
}
|
|
|
|
/**
|
|
* Get element index
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getElementIndex()
|
|
{
|
|
return $this->elementIndex;
|
|
}
|
|
|
|
/**
|
|
* Set element index
|
|
*
|
|
* @param int $value
|
|
*/
|
|
public function setElementIndex($value)
|
|
{
|
|
$this->elementIndex = $value;
|
|
}
|
|
|
|
/**
|
|
* Get element unique ID
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getElementId()
|
|
{
|
|
return $this->elementId;
|
|
}
|
|
|
|
/**
|
|
* Set element unique ID from 6 first digit of md5
|
|
*/
|
|
public function setElementId()
|
|
{
|
|
$this->elementId = substr(md5(rand()), 0, 6);
|
|
}
|
|
|
|
/**
|
|
* Get relation Id
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getRelationId()
|
|
{
|
|
$this->checkValidity('relationid');
|
|
return $this->relationId;
|
|
}
|
|
|
|
/**
|
|
* Set relation Id
|
|
*
|
|
* @param int $rId
|
|
*/
|
|
public function setRelationId($rId)
|
|
{
|
|
$this->checkValidity('relationid');
|
|
$this->relationId = $rId;
|
|
}
|
|
|
|
/**
|
|
* Check if element is located in section doc part (as opposed to header/footer)
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function isInSection()
|
|
{
|
|
return ($this->docPart == 'section');
|
|
}
|
|
|
|
/**
|
|
* Set style value
|
|
*
|
|
* @param mixed $styleObject Style object
|
|
* @param mixed $styleValue Style value
|
|
* @param boolean $returnObject Always return object
|
|
*/
|
|
protected function setStyle($styleObject, $styleValue = null, $returnObject = false)
|
|
{
|
|
if (!is_null($styleValue) && is_array($styleValue)) {
|
|
foreach ($styleValue as $key => $value) {
|
|
$styleObject->setStyleValue($key, $value);
|
|
}
|
|
$style = $styleObject;
|
|
} else {
|
|
$style = $returnObject ? $styleObject : $styleValue;
|
|
}
|
|
|
|
return $style;
|
|
}
|
|
|
|
/**
|
|
* Check if a method is allowed for the current container
|
|
*
|
|
* @param string $method
|
|
* @return boolean
|
|
*/
|
|
private function checkValidity($method)
|
|
{
|
|
// Valid containers for each element
|
|
$allContainers = array('section', 'header', 'footer', 'cell', 'textrun', 'footnote', 'endnote');
|
|
$validContainers = array(
|
|
'text' => $allContainers,
|
|
'link' => $allContainers,
|
|
'textbreak' => $allContainers,
|
|
'image' => $allContainers,
|
|
'object' => $allContainers,
|
|
'textrun' => array('section', 'header', 'footer', 'cell'),
|
|
'listitem' => array('section', 'header', 'footer', 'cell'),
|
|
'checkbox' => array('section', 'header', 'footer', 'cell'),
|
|
'table' => array('section', 'header', 'footer'),
|
|
'footnote' => array('section', 'textrun', 'cell'),
|
|
'endnote' => array('section', 'textrun', 'cell'),
|
|
'preservetext' => array('header', 'footer', 'cell'),
|
|
'title' => array('section'),
|
|
);
|
|
// Special condition, e.g. preservetext can only exists in cell when
|
|
// the cell is located in header or footer
|
|
$validContainerInContainers = array(
|
|
'preservetext' => array(array('cell'), array('header', 'footer')),
|
|
'footnote' => array(array('cell', 'textrun'), array('section')),
|
|
'endnote' => array(array('cell', 'textrun'), array('section')),
|
|
);
|
|
|
|
// Check if a method is valid for current container
|
|
if (array_key_exists($method, $validContainers)) {
|
|
if (!in_array($this->container, $validContainers[$method])) {
|
|
throw new \BadMethodCallException();
|
|
}
|
|
}
|
|
// Check if a method is valid for current container, located in other container
|
|
if (array_key_exists($method, $validContainerInContainers)) {
|
|
$rules = $validContainerInContainers[$method];
|
|
$containers = $rules[0];
|
|
$allowedDocParts = $rules[1];
|
|
foreach ($containers as $container) {
|
|
if ($this->container == $container && !in_array($this->getDocPart(), $allowedDocParts)) {
|
|
throw new \BadMethodCallException();
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Return element location in document: section, headerx, or footerx
|
|
*/
|
|
private function checkElementDocPart()
|
|
{
|
|
$isCellTextrun = in_array($this->container, array('cell', 'textrun'));
|
|
$docPart = $isCellTextrun ? $this->getDocPart() : $this->container;
|
|
$docPartId = $isCellTextrun ? $this->getDocPartId() : $this->sectionId;
|
|
$inHeaderFooter = ($docPart == 'header' || $docPart == 'footer');
|
|
|
|
return $inHeaderFooter ? $docPart . $docPartId : $docPart;
|
|
}
|
|
|
|
/**
|
|
* Add memory image element
|
|
*
|
|
* @param string $src
|
|
* @param mixed $style
|
|
* @deprecated 0.9.0
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function addMemoryImage($src, $style = null)
|
|
{
|
|
return $this->addImage($src, $style);
|
|
}
|
|
|
|
/**
|
|
* Create textrun element
|
|
*
|
|
* @param mixed $paragraphStyle
|
|
* @deprecated 0.10.0
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function createTextRun($paragraphStyle = null)
|
|
{
|
|
return $this->addTextRun($paragraphStyle);
|
|
}
|
|
|
|
/**
|
|
* Create footnote element
|
|
*
|
|
* @param mixed $paragraphStyle
|
|
* @deprecated 0.10.0
|
|
* @codeCoverageIgnore
|
|
*/
|
|
public function createFootnote($paragraphStyle = null)
|
|
{
|
|
return $this->addFootnote($paragraphStyle);
|
|
}
|
|
}
|