Word2007.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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. * @copyright 2010-2018 PHPWord contributors
  15. * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
  16. */
  17. namespace PhpOffice\PhpWord\Writer;
  18. use PhpOffice\PhpWord\Element\Section;
  19. use PhpOffice\PhpWord\Media;
  20. use PhpOffice\PhpWord\PhpWord;
  21. use PhpOffice\PhpWord\Shared\ZipArchive;
  22. /**
  23. * Word2007 writer
  24. */
  25. class Word2007 extends AbstractWriter implements WriterInterface
  26. {
  27. /**
  28. * Content types values
  29. *
  30. * @var array
  31. */
  32. private $contentTypes = array('default' => array(), 'override' => array());
  33. /**
  34. * Document relationship
  35. *
  36. * @var array
  37. */
  38. private $relationships = array();
  39. /**
  40. * Create new Word2007 writer
  41. *
  42. * @param \PhpOffice\PhpWord\PhpWord
  43. */
  44. public function __construct(PhpWord $phpWord = null)
  45. {
  46. // Assign PhpWord
  47. $this->setPhpWord($phpWord);
  48. // Create parts
  49. $this->parts = array(
  50. 'ContentTypes' => '[Content_Types].xml',
  51. 'Rels' => '_rels/.rels',
  52. 'DocPropsApp' => 'docProps/app.xml',
  53. 'DocPropsCore' => 'docProps/core.xml',
  54. 'DocPropsCustom' => 'docProps/custom.xml',
  55. 'RelsDocument' => 'word/_rels/document.xml.rels',
  56. 'Document' => 'word/document.xml',
  57. 'Comments' => 'word/comments.xml',
  58. 'Styles' => 'word/styles.xml',
  59. 'Numbering' => 'word/numbering.xml',
  60. 'Settings' => 'word/settings.xml',
  61. 'WebSettings' => 'word/webSettings.xml',
  62. 'FontTable' => 'word/fontTable.xml',
  63. 'Theme' => 'word/theme/theme1.xml',
  64. 'RelsPart' => '',
  65. 'Header' => '',
  66. 'Footer' => '',
  67. 'Footnotes' => '',
  68. 'Endnotes' => '',
  69. 'Chart' => '',
  70. );
  71. foreach (array_keys($this->parts) as $partName) {
  72. $partClass = get_class($this) . '\\Part\\' . $partName;
  73. if (class_exists($partClass)) {
  74. /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $part Type hint */
  75. $part = new $partClass();
  76. $part->setParentWriter($this);
  77. $this->writerParts[strtolower($partName)] = $part;
  78. }
  79. }
  80. // Set package paths
  81. $this->mediaPaths = array('image' => 'word/media/', 'object' => 'word/embeddings/');
  82. }
  83. /**
  84. * Save document by name.
  85. *
  86. * @param string $filename
  87. */
  88. public function save($filename = null)
  89. {
  90. $filename = $this->getTempFile($filename);
  91. $zip = $this->getZipArchive($filename);
  92. $phpWord = $this->getPhpWord();
  93. // Content types
  94. $this->contentTypes['default'] = array(
  95. 'rels' => 'application/vnd.openxmlformats-package.relationships+xml',
  96. 'xml' => 'application/xml',
  97. );
  98. // Add section media files
  99. $sectionMedia = Media::getElements('section');
  100. if (!empty($sectionMedia)) {
  101. $this->addFilesToPackage($zip, $sectionMedia);
  102. $this->registerContentTypes($sectionMedia);
  103. foreach ($sectionMedia as $element) {
  104. $this->relationships[] = $element;
  105. }
  106. }
  107. // Add header/footer media files & relations
  108. $this->addHeaderFooterMedia($zip, 'header');
  109. $this->addHeaderFooterMedia($zip, 'footer');
  110. // Add header/footer contents
  111. $rId = Media::countElements('section') + 6; // @see Rels::writeDocRels for 6 first elements
  112. $sections = $phpWord->getSections();
  113. foreach ($sections as $section) {
  114. $this->addHeaderFooterContent($section, $zip, 'header', $rId);
  115. $this->addHeaderFooterContent($section, $zip, 'footer', $rId);
  116. }
  117. $this->addNotes($zip, $rId, 'footnote');
  118. $this->addNotes($zip, $rId, 'endnote');
  119. $this->addComments($zip, $rId);
  120. $this->addChart($zip, $rId);
  121. // Write parts
  122. foreach ($this->parts as $partName => $fileName) {
  123. if ($fileName != '') {
  124. $zip->addFromString($fileName, $this->getWriterPart($partName)->write());
  125. }
  126. }
  127. // Close zip archive and cleanup temp file
  128. $zip->close();
  129. $this->cleanupTempFile();
  130. }
  131. /**
  132. * Get content types
  133. *
  134. * @return array
  135. */
  136. public function getContentTypes()
  137. {
  138. return $this->contentTypes;
  139. }
  140. /**
  141. * Get content types
  142. *
  143. * @return array
  144. */
  145. public function getRelationships()
  146. {
  147. return $this->relationships;
  148. }
  149. /**
  150. * Add header/footer media files, e.g. footer1.xml.rels.
  151. *
  152. * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip
  153. * @param string $docPart
  154. */
  155. private function addHeaderFooterMedia(ZipArchive $zip, $docPart)
  156. {
  157. $elements = Media::getElements($docPart);
  158. if (!empty($elements)) {
  159. foreach ($elements as $file => $media) {
  160. if (count($media) > 0) {
  161. if (!empty($media)) {
  162. $this->addFilesToPackage($zip, $media);
  163. $this->registerContentTypes($media);
  164. }
  165. /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */
  166. $writerPart = $this->getWriterPart('relspart')->setMedia($media);
  167. $zip->addFromString("word/_rels/{$file}.xml.rels", $writerPart->write());
  168. }
  169. }
  170. }
  171. }
  172. /**
  173. * Add header/footer content.
  174. *
  175. * @param \PhpOffice\PhpWord\Element\Section &$section
  176. * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip
  177. * @param string $elmType header|footer
  178. * @param int &$rId
  179. */
  180. private function addHeaderFooterContent(Section &$section, ZipArchive $zip, $elmType, &$rId)
  181. {
  182. $getFunction = $elmType == 'header' ? 'getHeaders' : 'getFooters';
  183. $elmCount = ($section->getSectionId() - 1) * 3;
  184. $elements = $section->$getFunction();
  185. /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */
  186. foreach ($elements as &$element) {
  187. $elmCount++;
  188. $element->setRelationId(++$rId);
  189. $elmFile = "{$elmType}{$elmCount}.xml"; // e.g. footer1.xml
  190. $this->contentTypes['override']["/word/$elmFile"] = $elmType;
  191. $this->relationships[] = array('target' => $elmFile, 'type' => $elmType, 'rID' => $rId);
  192. /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */
  193. $writerPart = $this->getWriterPart($elmType)->setElement($element);
  194. $zip->addFromString("word/$elmFile", $writerPart->write());
  195. }
  196. }
  197. /**
  198. * Add footnotes/endnotes
  199. *
  200. * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip
  201. * @param int &$rId
  202. * @param string $noteType
  203. */
  204. private function addNotes(ZipArchive $zip, &$rId, $noteType = 'footnote')
  205. {
  206. $phpWord = $this->getPhpWord();
  207. $noteType = ($noteType == 'endnote') ? 'endnote' : 'footnote';
  208. $partName = "{$noteType}s";
  209. $method = 'get' . $partName;
  210. $collection = $phpWord->$method();
  211. // Add footnotes media files, relations, and contents
  212. /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */
  213. if ($collection->countItems() > 0) {
  214. $media = Media::getElements($noteType);
  215. $this->addFilesToPackage($zip, $media);
  216. $this->registerContentTypes($media);
  217. $this->contentTypes['override']["/word/{$partName}.xml"] = $partName;
  218. $this->relationships[] = array('target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId);
  219. // Write relationships file, e.g. word/_rels/footnotes.xml
  220. if (!empty($media)) {
  221. /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */
  222. $writerPart = $this->getWriterPart('relspart')->setMedia($media);
  223. $zip->addFromString("word/_rels/{$partName}.xml.rels", $writerPart->write());
  224. }
  225. // Write content file, e.g. word/footnotes.xml
  226. $writerPart = $this->getWriterPart($partName)->setElements($collection->getItems());
  227. $zip->addFromString("word/{$partName}.xml", $writerPart->write());
  228. }
  229. }
  230. /**
  231. * Add comments
  232. *
  233. * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip
  234. * @param int &$rId
  235. */
  236. private function addComments(ZipArchive $zip, &$rId)
  237. {
  238. $phpWord = $this->getPhpWord();
  239. $collection = $phpWord->getComments();
  240. $partName = 'comments';
  241. // Add comment relations and contents
  242. /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */
  243. if ($collection->countItems() > 0) {
  244. $this->relationships[] = array('target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId);
  245. // Write content file, e.g. word/comments.xml
  246. $writerPart = $this->getWriterPart($partName)->setElements($collection->getItems());
  247. $zip->addFromString("word/{$partName}.xml", $writerPart->write());
  248. }
  249. }
  250. /**
  251. * Add chart.
  252. *
  253. * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip
  254. * @param int &$rId
  255. */
  256. private function addChart(ZipArchive $zip, &$rId)
  257. {
  258. $phpWord = $this->getPhpWord();
  259. $collection = $phpWord->getCharts();
  260. $index = 0;
  261. if ($collection->countItems() > 0) {
  262. /** @var \PhpOffice\PhpWord\Element\Chart $chart */
  263. foreach ($collection->getItems() as $chart) {
  264. $index++;
  265. $rId++;
  266. $filename = "charts/chart{$index}.xml";
  267. // ContentTypes.xml
  268. $this->contentTypes['override']["/word/{$filename}"] = 'chart';
  269. // word/_rels/document.xml.rel
  270. $this->relationships[] = array('target' => $filename, 'type' => 'chart', 'rID' => $rId);
  271. // word/charts/chartN.xml
  272. $chart->setRelationId($rId);
  273. $writerPart = $this->getWriterPart('Chart');
  274. $writerPart->setElement($chart);
  275. $zip->addFromString("word/{$filename}", $writerPart->write());
  276. }
  277. }
  278. }
  279. /**
  280. * Register content types for each media.
  281. *
  282. * @param array $media
  283. */
  284. private function registerContentTypes($media)
  285. {
  286. foreach ($media as $medium) {
  287. $mediumType = $medium['type'];
  288. if ($mediumType == 'image') {
  289. $extension = $medium['imageExtension'];
  290. if (!isset($this->contentTypes['default'][$extension])) {
  291. $this->contentTypes['default'][$extension] = $medium['imageType'];
  292. }
  293. } elseif ($mediumType == 'object') {
  294. if (!isset($this->contentTypes['default']['bin'])) {
  295. $this->contentTypes['default']['bin'] = 'application/vnd.openxmlformats-officedocument.oleObject';
  296. }
  297. }
  298. }
  299. }
  300. }