Rels.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  3. use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
  4. use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
  5. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  6. use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
  7. use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
  8. use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
  9. class Rels extends WriterPart
  10. {
  11. /**
  12. * Write relationships to XML format.
  13. *
  14. * @return string XML Output
  15. */
  16. public function writeRelationships(Spreadsheet $spreadsheet)
  17. {
  18. // Create XML writer
  19. $objWriter = null;
  20. if ($this->getParentWriter()->getUseDiskCaching()) {
  21. $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  22. } else {
  23. $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  24. }
  25. // XML header
  26. $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  27. // Relationships
  28. $objWriter->startElement('Relationships');
  29. $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS);
  30. $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
  31. if (!empty($customPropertyList)) {
  32. // Relationship docProps/app.xml
  33. $this->writeRelationship(
  34. $objWriter,
  35. 4,
  36. Namespaces::RELATIONSHIPS_CUSTOM_PROPERTIES,
  37. 'docProps/custom.xml'
  38. );
  39. }
  40. // Relationship docProps/app.xml
  41. $this->writeRelationship(
  42. $objWriter,
  43. 3,
  44. Namespaces::RELATIONSHIPS_EXTENDED_PROPERTIES,
  45. 'docProps/app.xml'
  46. );
  47. // Relationship docProps/core.xml
  48. $this->writeRelationship(
  49. $objWriter,
  50. 2,
  51. Namespaces::CORE_PROPERTIES,
  52. 'docProps/core.xml'
  53. );
  54. // Relationship xl/workbook.xml
  55. $this->writeRelationship(
  56. $objWriter,
  57. 1,
  58. Namespaces::OFFICE_DOCUMENT,
  59. 'xl/workbook.xml'
  60. );
  61. // a custom UI in workbook ?
  62. $target = $spreadsheet->getRibbonXMLData('target');
  63. if ($spreadsheet->hasRibbon()) {
  64. $this->writeRelationShip(
  65. $objWriter,
  66. 5,
  67. Namespaces::EXTENSIBILITY,
  68. is_string($target) ? $target : ''
  69. );
  70. }
  71. $objWriter->endElement();
  72. return $objWriter->getData();
  73. }
  74. /**
  75. * Write workbook relationships to XML format.
  76. *
  77. * @return string XML Output
  78. */
  79. public function writeWorkbookRelationships(Spreadsheet $spreadsheet)
  80. {
  81. // Create XML writer
  82. $objWriter = null;
  83. if ($this->getParentWriter()->getUseDiskCaching()) {
  84. $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  85. } else {
  86. $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  87. }
  88. // XML header
  89. $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  90. // Relationships
  91. $objWriter->startElement('Relationships');
  92. $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS);
  93. // Relationship styles.xml
  94. $this->writeRelationship(
  95. $objWriter,
  96. 1,
  97. Namespaces::STYLES,
  98. 'styles.xml'
  99. );
  100. // Relationship theme/theme1.xml
  101. $this->writeRelationship(
  102. $objWriter,
  103. 2,
  104. Namespaces::THEME2,
  105. 'theme/theme1.xml'
  106. );
  107. // Relationship sharedStrings.xml
  108. $this->writeRelationship(
  109. $objWriter,
  110. 3,
  111. Namespaces::SHARED_STRINGS,
  112. 'sharedStrings.xml'
  113. );
  114. // Relationships with sheets
  115. $sheetCount = $spreadsheet->getSheetCount();
  116. for ($i = 0; $i < $sheetCount; ++$i) {
  117. $this->writeRelationship(
  118. $objWriter,
  119. ($i + 1 + 3),
  120. Namespaces::WORKSHEET,
  121. 'worksheets/sheet' . ($i + 1) . '.xml'
  122. );
  123. }
  124. // Relationships for vbaProject if needed
  125. // id : just after the last sheet
  126. if ($spreadsheet->hasMacros()) {
  127. $this->writeRelationShip(
  128. $objWriter,
  129. ($i + 1 + 3),
  130. Namespaces::VBA,
  131. 'vbaProject.bin'
  132. );
  133. ++$i; //increment i if needed for an another relation
  134. }
  135. $objWriter->endElement();
  136. return $objWriter->getData();
  137. }
  138. /**
  139. * Write worksheet relationships to XML format.
  140. *
  141. * Numbering is as follows:
  142. * rId1 - Drawings
  143. * rId_hyperlink_x - Hyperlinks
  144. *
  145. * @param int $worksheetId
  146. * @param bool $includeCharts Flag indicating if we should write charts
  147. * @param int $tableRef Table ID
  148. *
  149. * @return string XML Output
  150. */
  151. public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1)
  152. {
  153. // Create XML writer
  154. $objWriter = null;
  155. if ($this->getParentWriter()->getUseDiskCaching()) {
  156. $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  157. } else {
  158. $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  159. }
  160. // XML header
  161. $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  162. // Relationships
  163. $objWriter->startElement('Relationships');
  164. $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS);
  165. // Write drawing relationships?
  166. $drawingOriginalIds = [];
  167. $unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
  168. if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'])) {
  169. $drawingOriginalIds = $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'];
  170. }
  171. if ($includeCharts) {
  172. $charts = $worksheet->getChartCollection();
  173. } else {
  174. $charts = [];
  175. }
  176. if (($worksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) {
  177. $rId = 1;
  178. // Use original $relPath to get original $rId.
  179. // Take first. In future can be overwritten.
  180. // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings)
  181. reset($drawingOriginalIds);
  182. $relPath = key($drawingOriginalIds);
  183. if (isset($drawingOriginalIds[$relPath])) {
  184. $rId = (int) (substr($drawingOriginalIds[$relPath], 3));
  185. }
  186. // Generate new $relPath to write drawing relationship
  187. $relPath = '../drawings/drawing' . $worksheetId . '.xml';
  188. $this->writeRelationship(
  189. $objWriter,
  190. $rId,
  191. Namespaces::RELATIONSHIPS_DRAWING,
  192. $relPath
  193. );
  194. }
  195. // Write hyperlink relationships?
  196. $i = 1;
  197. foreach ($worksheet->getHyperlinkCollection() as $hyperlink) {
  198. if (!$hyperlink->isInternal()) {
  199. $this->writeRelationship(
  200. $objWriter,
  201. '_hyperlink_' . $i,
  202. Namespaces::HYPERLINK,
  203. $hyperlink->getUrl(),
  204. 'External'
  205. );
  206. ++$i;
  207. }
  208. }
  209. // Write comments relationship?
  210. $i = 1;
  211. if (count($worksheet->getComments()) > 0 || isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['legacyDrawing'])) {
  212. $this->writeRelationship(
  213. $objWriter,
  214. '_comments_vml' . $i,
  215. Namespaces::VML,
  216. '../drawings/vmlDrawing' . $worksheetId . '.vml'
  217. );
  218. }
  219. if (count($worksheet->getComments()) > 0) {
  220. $this->writeRelationship(
  221. $objWriter,
  222. '_comments' . $i,
  223. Namespaces::COMMENTS,
  224. '../comments' . $worksheetId . '.xml'
  225. );
  226. }
  227. // Write Table
  228. $tableCount = $worksheet->getTableCollection()->count();
  229. for ($i = 1; $i <= $tableCount; ++$i) {
  230. $this->writeRelationship(
  231. $objWriter,
  232. '_table_' . $i,
  233. Namespaces::RELATIONSHIPS_TABLE,
  234. '../tables/table' . $tableRef++ . '.xml'
  235. );
  236. }
  237. // Write header/footer relationship?
  238. $i = 1;
  239. if (count($worksheet->getHeaderFooter()->getImages()) > 0) {
  240. $this->writeRelationship(
  241. $objWriter,
  242. '_headerfooter_vml' . $i,
  243. Namespaces::VML,
  244. '../drawings/vmlDrawingHF' . $worksheetId . '.vml'
  245. );
  246. }
  247. $this->writeUnparsedRelationship($worksheet, $objWriter, 'ctrlProps', Namespaces::RELATIONSHIPS_CTRLPROP);
  248. $this->writeUnparsedRelationship($worksheet, $objWriter, 'vmlDrawings', Namespaces::VML);
  249. $this->writeUnparsedRelationship($worksheet, $objWriter, 'printerSettings', Namespaces::RELATIONSHIPS_PRINTER_SETTINGS);
  250. $objWriter->endElement();
  251. return $objWriter->getData();
  252. }
  253. private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, XMLWriter $objWriter, string $relationship, string $type): void
  254. {
  255. $unparsedLoadedData = $worksheet->getParentOrThrow()->getUnparsedLoadedData();
  256. if (!isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship])) {
  257. return;
  258. }
  259. foreach ($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship] as $rId => $value) {
  260. if (substr($rId, 0, 17) !== '_headerfooter_vml') {
  261. $this->writeRelationship(
  262. $objWriter,
  263. $rId,
  264. $type,
  265. $value['relFilePath']
  266. );
  267. }
  268. }
  269. }
  270. /**
  271. * Write drawing relationships to XML format.
  272. *
  273. * @param int $chartRef Chart ID
  274. * @param bool $includeCharts Flag indicating if we should write charts
  275. *
  276. * @return string XML Output
  277. */
  278. public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, &$chartRef, $includeCharts = false)
  279. {
  280. // Create XML writer
  281. $objWriter = null;
  282. if ($this->getParentWriter()->getUseDiskCaching()) {
  283. $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  284. } else {
  285. $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  286. }
  287. // XML header
  288. $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  289. // Relationships
  290. $objWriter->startElement('Relationships');
  291. $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS);
  292. // Loop through images and write relationships
  293. $i = 1;
  294. $iterator = $worksheet->getDrawingCollection()->getIterator();
  295. while ($iterator->valid()) {
  296. $drawing = $iterator->current();
  297. if (
  298. $drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing
  299. || $drawing instanceof MemoryDrawing
  300. ) {
  301. // Write relationship for image drawing
  302. $this->writeRelationship(
  303. $objWriter,
  304. $i,
  305. Namespaces::IMAGE,
  306. '../media/' . $drawing->getIndexedFilename()
  307. );
  308. $i = $this->writeDrawingHyperLink($objWriter, $drawing, $i);
  309. }
  310. $iterator->next();
  311. ++$i;
  312. }
  313. if ($includeCharts) {
  314. // Loop through charts and write relationships
  315. $chartCount = $worksheet->getChartCount();
  316. if ($chartCount > 0) {
  317. for ($c = 0; $c < $chartCount; ++$c) {
  318. $this->writeRelationship(
  319. $objWriter,
  320. $i++,
  321. Namespaces::RELATIONSHIPS_CHART,
  322. '../charts/chart' . ++$chartRef . '.xml'
  323. );
  324. }
  325. }
  326. }
  327. $objWriter->endElement();
  328. return $objWriter->getData();
  329. }
  330. /**
  331. * Write header/footer drawing relationships to XML format.
  332. *
  333. * @return string XML Output
  334. */
  335. public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet)
  336. {
  337. // Create XML writer
  338. $objWriter = null;
  339. if ($this->getParentWriter()->getUseDiskCaching()) {
  340. $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  341. } else {
  342. $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  343. }
  344. // XML header
  345. $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  346. // Relationships
  347. $objWriter->startElement('Relationships');
  348. $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS);
  349. // Loop through images and write relationships
  350. foreach ($worksheet->getHeaderFooter()->getImages() as $key => $value) {
  351. // Write relationship for image drawing
  352. $this->writeRelationship(
  353. $objWriter,
  354. $key,
  355. Namespaces::IMAGE,
  356. '../media/' . $value->getIndexedFilename()
  357. );
  358. }
  359. $objWriter->endElement();
  360. return $objWriter->getData();
  361. }
  362. public function writeVMLDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet): string
  363. {
  364. // Create XML writer
  365. $objWriter = null;
  366. if ($this->getParentWriter()->getUseDiskCaching()) {
  367. $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  368. } else {
  369. $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  370. }
  371. // XML header
  372. $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  373. // Relationships
  374. $objWriter->startElement('Relationships');
  375. $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS);
  376. // Loop through images and write relationships
  377. foreach ($worksheet->getComments() as $comment) {
  378. if (!$comment->hasBackgroundImage()) {
  379. continue;
  380. }
  381. $bgImage = $comment->getBackgroundImage();
  382. $this->writeRelationship(
  383. $objWriter,
  384. $bgImage->getImageIndex(),
  385. Namespaces::IMAGE,
  386. '../media/' . $bgImage->getMediaFilename()
  387. );
  388. }
  389. $objWriter->endElement();
  390. return $objWriter->getData();
  391. }
  392. /**
  393. * Write Override content type.
  394. *
  395. * @param int|string $id Relationship ID. rId will be prepended!
  396. * @param string $type Relationship type
  397. * @param string $target Relationship target
  398. * @param string $targetMode Relationship target mode
  399. */
  400. private function writeRelationship(XMLWriter $objWriter, $id, $type, $target, $targetMode = ''): void
  401. {
  402. if ($type != '' && $target != '') {
  403. // Write relationship
  404. $objWriter->startElement('Relationship');
  405. $objWriter->writeAttribute('Id', 'rId' . $id);
  406. $objWriter->writeAttribute('Type', $type);
  407. $objWriter->writeAttribute('Target', $target);
  408. if ($targetMode != '') {
  409. $objWriter->writeAttribute('TargetMode', $targetMode);
  410. }
  411. $objWriter->endElement();
  412. } else {
  413. throw new WriterException('Invalid parameters passed.');
  414. }
  415. }
  416. private function writeDrawingHyperLink(XMLWriter $objWriter, BaseDrawing $drawing, int $i): int
  417. {
  418. if ($drawing->getHyperlink() === null) {
  419. return $i;
  420. }
  421. ++$i;
  422. $this->writeRelationship(
  423. $objWriter,
  424. $i,
  425. Namespaces::HYPERLINK,
  426. $drawing->getHyperlink()->getUrl(),
  427. $drawing->getHyperlink()->getTypeHyperlink()
  428. );
  429. return $i;
  430. }
  431. }