Skip to content

Commit 17d4a54

Browse files
tezrikPowerKiKi
authored andcommitted
Read and write hyperlink for drawing image
Fixes #490
1 parent b05d07a commit 17d4a54

File tree

9 files changed

+253
-5
lines changed

9 files changed

+253
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99

1010
### Added
1111

12-
- Add excel function EXACT(value1, value2) support
12+
- Add excel function EXACT(value1, value2) support - [595](https://github.com/PHPOffice/PhpSpreadsheet/pull/595)
1313
- Support workbook view attributes for Xlsx format - [#523](https://github.com/PHPOffice/PhpSpreadsheet/issues/523)
14+
- Read and write hyperlink for drawing image - [#490](https://github.com/PHPOffice/PhpSpreadsheet/pull/490)
1415

1516
### Fixed
1617

docs/references/features-cross-reference.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,24 @@
750750
<td></td>
751751
<td></td>
752752
</tr>
753+
<tr>
754+
<td style="padding-left: 1em;">Drawing hyperlink</td>
755+
<td></td>
756+
<td style="text-align: center; color: green;">✔</td>
757+
<td></td>
758+
<td></td>
759+
<td></td>
760+
<td></td>
761+
<td></td>
762+
<td></td>
763+
<td style="text-align: center; color: green;">✔</td>
764+
<td></td>
765+
<td></td>
766+
<td></td>
767+
<td></td>
768+
<td>$drawing->getHyperlink()->getUrl()</td>
769+
<td>$drawing->setHyperlink()->setUrl($url)</td>
770+
</tr>
753771
<tr>
754772
<td><strong>Cell Formatting</strong></td>
755773
<td></td>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
4+
5+
require __DIR__ . '/../Header.php';
6+
$inputFileType = 'Xlsx';
7+
8+
$helper->log('Start');
9+
10+
$spreadsheet = new Spreadsheet();
11+
12+
$aSheet = $spreadsheet->getActiveSheet();
13+
14+
$gdImage = @imagecreatetruecolor(120, 20);
15+
$textColor = imagecolorallocate($gdImage, 255, 255, 255);
16+
imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor);
17+
18+
$baseUrl = 'https://phpspreadsheet.readthedocs.io/';
19+
20+
$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing();
21+
$drawing->setName('In-Memory image 1');
22+
$drawing->setDescription('In-Memory image 1');
23+
$drawing->setCoordinates('A1');
24+
$drawing->setImageResource($gdImage);
25+
$drawing->setRenderingFunction(
26+
\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::RENDERING_JPEG
27+
);
28+
$drawing->setMimeType(\PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::MIMETYPE_DEFAULT);
29+
$drawing->setHeight(36);
30+
$helper->log('Write image');
31+
32+
$hyperLink = new \PhpOffice\PhpSpreadsheet\Cell\Hyperlink($baseUrl, 'test image');
33+
$drawing->setHyperlink($hyperLink);
34+
$helper->log('Write link: ' . $baseUrl);
35+
36+
$drawing->setWorksheet($aSheet);
37+
38+
$filename = tempnam(\PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir(), 'phpspreadsheet-test');
39+
40+
$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, $inputFileType);
41+
$writer->save($filename);
42+
43+
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
44+
45+
$reloadedSpreadsheet = $reader->load($filename);
46+
unlink($filename);
47+
48+
$helper->log('reloaded Spreadsheet');
49+
50+
foreach ($reloadedSpreadsheet->getActiveSheet()->getDrawingCollection() as $pDrawing) {
51+
$helper->log('Read link: ' . $pDrawing->getHyperlink()->getUrl());
52+
}
53+
54+
$helper->log('end');

src/PhpSpreadsheet/Cell/Hyperlink.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ public function isInternal()
8989
return strpos($this->url, 'sheet://') !== false;
9090
}
9191

92+
/**
93+
* @return string
94+
*/
95+
public function getTypeHyperlink()
96+
{
97+
return $this->isInternal() ? '' : 'External';
98+
}
99+
92100
/**
93101
* Get hash code.
94102
*

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Reader;
44

55
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6+
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
67
use PhpOffice\PhpSpreadsheet\Document\Properties;
78
use PhpOffice\PhpSpreadsheet\NamedRange;
89
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart;
@@ -1669,9 +1670,12 @@ public function load($pFilename)
16691670
Settings::getLibXmlLoaderOptions()
16701671
);
16711672
$images = [];
1672-
1673+
$hyperlinks = [];
16731674
if ($relsDrawing && $relsDrawing->Relationship) {
16741675
foreach ($relsDrawing->Relationship as $ele) {
1676+
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') {
1677+
$hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
1678+
}
16751679
if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
16761680
$images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
16771681
} elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') {
@@ -1699,6 +1703,9 @@ public function load($pFilename)
16991703
$xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
17001704
/** @var SimpleXMLElement $outerShdw */
17011705
$outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
1706+
/** @var \SimpleXMLElement $hlinkClick */
1707+
$hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
1708+
17021709
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
17031710
$objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
17041711
$objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
@@ -1729,6 +1736,9 @@ public function load($pFilename)
17291736
$shadow->getColor()->setRGB(self::getArrayItem($outerShdw->srgbClr->attributes(), 'val'));
17301737
$shadow->setAlpha(self::getArrayItem($outerShdw->srgbClr->alpha->attributes(), 'val') / 1000);
17311738
}
1739+
1740+
$this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
1741+
17321742
$objDrawing->setWorksheet($docSheet);
17331743
} else {
17341744
// ? Can charts be positioned with a oneCellAnchor ?
@@ -1746,6 +1756,7 @@ public function load($pFilename)
17461756
$blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip;
17471757
$xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm;
17481758
$outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw;
1759+
$hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
17491760
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
17501761
$objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name'));
17511762
$objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr'));
@@ -1777,6 +1788,9 @@ public function load($pFilename)
17771788
$shadow->getColor()->setRGB(self::getArrayItem($outerShdw->srgbClr->attributes(), 'val'));
17781789
$shadow->setAlpha(self::getArrayItem($outerShdw->srgbClr->alpha->attributes(), 'val') / 1000);
17791790
}
1791+
1792+
$this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks);
1793+
17801794
$objDrawing->setWorksheet($docSheet);
17811795
} elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) {
17821796
$fromCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);
@@ -2427,6 +2441,27 @@ private static function boolean($value)
24272441
return $value === 'true' || $value === 'TRUE';
24282442
}
24292443

2444+
/**
2445+
* @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing
2446+
* @param \SimpleXMLElement $cellAnchor
2447+
* @param array $hyperlinks
2448+
*/
2449+
private function readHyperLinkDrawing($objDrawing, $cellAnchor, $hyperlinks)
2450+
{
2451+
$hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick;
2452+
2453+
if ($hlinkClick->count() === 0) {
2454+
return;
2455+
}
2456+
2457+
$hlinkId = (string) $hlinkClick->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships')['id'];
2458+
$hyperlink = new Hyperlink(
2459+
$hyperlinks[$hlinkId],
2460+
(string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')
2461+
);
2462+
$objDrawing->setHyperlink($hyperlink);
2463+
}
2464+
24302465
private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook)
24312466
{
24322467
if (!$xmlWorkbook->workbookProtection) {

src/PhpSpreadsheet/Worksheet/BaseDrawing.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpOffice\PhpSpreadsheet\Worksheet;
44

5+
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
56
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
67
use PhpOffice\PhpSpreadsheet\IComparable;
78

@@ -98,6 +99,13 @@ class BaseDrawing implements IComparable
9899
*/
99100
protected $shadow;
100101

102+
/**
103+
* Image hyperlink.
104+
*
105+
* @var null|Hyperlink
106+
*/
107+
private $hyperlink;
108+
101109
/**
102110
* Create a new BaseDrawing.
103111
*/
@@ -508,4 +516,20 @@ public function __clone()
508516
}
509517
}
510518
}
519+
520+
/**
521+
* @param null|Hyperlink $pHyperlink
522+
*/
523+
public function setHyperlink(Hyperlink $pHyperlink = null)
524+
{
525+
$this->hyperlink = $pHyperlink;
526+
}
527+
528+
/**
529+
* @return null|Hyperlink
530+
*/
531+
public function getHyperlink()
532+
{
533+
return $this->hyperlink;
534+
}
511535
}

src/PhpSpreadsheet/Writer/Xlsx/Drawing.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWo
4343
$i = 1;
4444
$iterator = $pWorksheet->getDrawingCollection()->getIterator();
4545
while ($iterator->valid()) {
46-
$this->writeDrawing($objWriter, $iterator->current(), $i);
46+
/** @var BaseDrawing $pDrawing */
47+
$pDrawing = $iterator->current();
48+
$pRelationId = $i;
49+
$hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i;
50+
51+
$this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
4752

4853
$iterator->next();
4954
++$i;
@@ -150,10 +155,11 @@ public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart
150155
* @param XMLWriter $objWriter XML Writer
151156
* @param BaseDrawing $pDrawing
152157
* @param int $pRelationId
158+
* @param null|int $hlinkClickId
153159
*
154160
* @throws WriterException
155161
*/
156-
public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1)
162+
public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1, $hlinkClickId = null)
157163
{
158164
if ($pRelationId >= 0) {
159165
// xdr:oneCellAnchor
@@ -187,6 +193,10 @@ public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRela
187193
$objWriter->writeAttribute('id', $pRelationId);
188194
$objWriter->writeAttribute('name', $pDrawing->getName());
189195
$objWriter->writeAttribute('descr', $pDrawing->getDescription());
196+
197+
//a:hlinkClick
198+
$this->writeHyperLinkDrawing($objWriter, $hlinkClickId);
199+
190200
$objWriter->endElement();
191201

192202
// xdr:cNvPicPr
@@ -490,4 +500,20 @@ public function allDrawings(Spreadsheet $spreadsheet)
490500

491501
return $aDrawings;
492502
}
503+
504+
/**
505+
* @param XMLWriter $objWriter
506+
* @param null|int $hlinkClickId
507+
*/
508+
private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId)
509+
{
510+
if ($hlinkClickId === null) {
511+
return;
512+
}
513+
514+
$objWriter->startElement('a:hlinkClick');
515+
$objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
516+
$objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId);
517+
$objWriter->endElement();
518+
}
493519
}

src/PhpSpreadsheet/Writer/Xlsx/Rels.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,12 +329,16 @@ public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Wo
329329
if ($iterator->current() instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing
330330
|| $iterator->current() instanceof MemoryDrawing) {
331331
// Write relationship for image drawing
332+
/** @var \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing */
333+
$drawing = $iterator->current();
332334
$this->writeRelationship(
333335
$objWriter,
334336
$i,
335337
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
336-
'../media/' . str_replace(' ', '', $iterator->current()->getIndexedFilename())
338+
'../media/' . str_replace(' ', '', $drawing->getIndexedFilename())
337339
);
340+
341+
$i = $this->writeDrawingHyperLink($objWriter, $drawing, $i);
338342
}
339343

340344
$iterator->next();
@@ -432,4 +436,31 @@ private function writeRelationship(XMLWriter $objWriter, $pId, $pType, $pTarget,
432436
throw new WriterException('Invalid parameters passed.');
433437
}
434438
}
439+
440+
/**
441+
* @param $objWriter
442+
* @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing
443+
* @param $i
444+
*
445+
* @throws WriterException
446+
*
447+
* @return int
448+
*/
449+
private function writeDrawingHyperLink($objWriter, $drawing, $i)
450+
{
451+
if ($drawing->getHyperlink() === null) {
452+
return $i;
453+
}
454+
455+
++$i;
456+
$this->writeRelationship(
457+
$objWriter,
458+
$i,
459+
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
460+
$drawing->getHyperlink()->getUrl(),
461+
$drawing->getHyperlink()->getTypeHyperlink()
462+
);
463+
464+
return $i;
465+
}
435466
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: yuzhakov
5+
* Date: 08.05.18
6+
* Time: 12:00.
7+
*/
8+
9+
namespace PhpOffice\PhpSpreadsheetTests\Functional;
10+
11+
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
12+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
13+
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
14+
15+
class DrawingImageHyperlinkTest extends AbstractFunctional
16+
{
17+
/**
18+
* @throws \PhpOffice\PhpSpreadsheet\Exception
19+
*/
20+
public function testDrawingImageHyperlinkTest()
21+
{
22+
$baseUrl = 'https://github.com/PHPOffice/PhpSpreadsheet';
23+
$spreadsheet = new Spreadsheet();
24+
25+
$aSheet = $spreadsheet->getActiveSheet();
26+
27+
$gdImage = @imagecreatetruecolor(120, 20);
28+
$textColor = imagecolorallocate($gdImage, 255, 255, 255);
29+
imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor);
30+
31+
$drawing = new MemoryDrawing();
32+
$drawing->setName('In-Memory image 1');
33+
$drawing->setDescription('In-Memory image 1');
34+
$drawing->setCoordinates('A1');
35+
$drawing->setImageResource($gdImage);
36+
$drawing->setRenderingFunction(
37+
MemoryDrawing::RENDERING_JPEG
38+
);
39+
$drawing->setMimeType(MemoryDrawing::MIMETYPE_DEFAULT);
40+
$drawing->setHeight(36);
41+
$hyperLink = new Hyperlink($baseUrl, 'test image');
42+
$drawing->setHyperlink($hyperLink);
43+
$drawing->setWorksheet($aSheet);
44+
45+
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
46+
47+
foreach ($reloadedSpreadsheet->getActiveSheet()->getDrawingCollection() as $pDrawing) {
48+
self::assertEquals('https://github.com/PHPOffice/PhpSpreadsheet', $pDrawing->getHyperlink()->getUrl(), 'functional test drawing hyperlink');
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)