Skip to content

Commit be74207

Browse files
authored
Merge pull request #411 from PHPOffice/issue313pptx
#313 : Support for custom document properties
2 parents a0c665c + a9a4ec7 commit be74207

File tree

12 files changed

+515
-9
lines changed

12 files changed

+515
-9
lines changed

docs/changes/1.0.0.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
- ODPresentation Writer
6161
- PowerPoint2007 Reader
6262
- PowerPoint2007 Writer
63+
- Support for custom document properties - @Progi1984 GH-313
64+
- ODPresentation Reader
65+
- ODPresentation Writer
66+
- PowerPoint2007 Reader
67+
- PowerPoint2007 Writer
6368

6469
## Project Management
6570
- Migrated from Travis CI to Github Actions - @Progi1984 GH-635

docs/usage/presentation.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,45 @@ $properties->setSubject('My subject');
6464
$properties->setKeywords('my, key, word');
6565
```
6666

67+
### Custom Properties
68+
69+
You can add custom properties with the method `setCustomProperty`.
70+
71+
Multiple types are available:
72+
* `DocumentProperties::PROPERTY_TYPE_STRING` for string value,
73+
* `DocumentProperties::PROPERTY_TYPE_BOOLEAN` for boolean value,
74+
* `DocumentProperties::PROPERTY_TYPE_FLOAT` for float value,
75+
* `DocumentProperties::PROPERTY_TYPE_INTEGER` for integer value,
76+
* `DocumentProperties::PROPERTY_TYPE_DATE` for date value,
77+
* `DocumentProperties::PROPERTY_TYPE_UNKNOWN` for unknown type value.
78+
79+
80+
``` php
81+
<?php
82+
83+
use PhpOffice\PhpPresentation\DocumentProperties;
84+
85+
$properties = $presentation->getProperties();
86+
87+
// Set the custom property
88+
$properties->setCustomProperty('propertyName', 'propertyValue', DocumentProperties::PROPERTY_TYPE_STRING);
89+
90+
// Check if a custom property exists
91+
$properties->isCustomPropertySet('unknown'); // return `false`
92+
$properties->isCustomPropertySet('propertyName'); // return `true`
93+
94+
// Return all custom properties
95+
$properties->getCustomProperties(); // return `['propertyName']`
96+
97+
// Return value from a custom property
98+
$properties->getCustomPropertyValue('unknown'); // return `null` if not set
99+
$properties->getCustomPropertyValue('propertyName'); // return `propertyValue`
100+
101+
// Return type from a custom property
102+
$properties->getCustomPropertyType('unknown'); // return `null` if not set
103+
$properties->getCustomPropertyType('propertyName'); // return `DocumentProperties::PROPERTY_TYPE_STRING`
104+
```
105+
67106
## Presentation Properties
68107

69108
You can define some properties which are relative to the presentation, like the zoom or the thumbnail.

src/PhpPresentation/DocumentProperties.php

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@
2323
*/
2424
class DocumentProperties
2525
{
26+
public const PROPERTY_TYPE_BOOLEAN = 'b';
27+
public const PROPERTY_TYPE_INTEGER = 'i';
28+
public const PROPERTY_TYPE_FLOAT = 'f';
29+
public const PROPERTY_TYPE_DATE = 'd';
30+
public const PROPERTY_TYPE_STRING = 's';
31+
public const PROPERTY_TYPE_UNKNOWN = 'u';
32+
2633
/**
2734
* Creator.
2835
*
@@ -94,7 +101,14 @@ class DocumentProperties
94101
private $company;
95102

96103
/**
97-
* Create a new \PhpOffice\PhpPresentation\DocumentProperties.
104+
* Custom Properties.
105+
*
106+
* @var array<string, array<string, mixed>>
107+
*/
108+
private $customProperties = [];
109+
110+
/**
111+
* Create a new \PhpOffice\PhpPresentation\DocumentProperties
98112
*/
99113
public function __construct()
100114
{
@@ -356,4 +370,99 @@ public function setCompany($pValue = '')
356370

357371
return $this;
358372
}
373+
374+
/**
375+
* Get a List of Custom Property Names.
376+
*
377+
* @return array<int, string>
378+
*/
379+
public function getCustomProperties(): array
380+
{
381+
return array_keys($this->customProperties);
382+
}
383+
384+
/**
385+
* Check if a Custom Property is defined.
386+
*
387+
* @param string $propertyName
388+
*
389+
* @return bool
390+
*/
391+
public function isCustomPropertySet(string $propertyName): bool
392+
{
393+
return isset($this->customProperties[$propertyName]);
394+
}
395+
396+
/**
397+
* Get a Custom Property Value.
398+
*
399+
* @param string $propertyName
400+
*
401+
* @return string|null
402+
*/
403+
public function getCustomPropertyValue(string $propertyName): ?string
404+
{
405+
if ($this->isCustomPropertySet($propertyName)) {
406+
return $this->customProperties[$propertyName]['value'];
407+
}
408+
409+
return null;
410+
}
411+
412+
/**
413+
* Get a Custom Property Type.
414+
*
415+
* @param string $propertyName
416+
*
417+
* @return string|null
418+
*/
419+
public function getCustomPropertyType(string $propertyName): ?string
420+
{
421+
if ($this->isCustomPropertySet($propertyName)) {
422+
return $this->customProperties[$propertyName]['type'];
423+
}
424+
425+
return null;
426+
}
427+
428+
/**
429+
* Set a Custom Property.
430+
*
431+
* @param string $propertyName
432+
* @param mixed $propertyValue
433+
* @param string|null $propertyType
434+
* 'i' : Integer
435+
* 'f' : Floating Point
436+
* 's' : String
437+
* 'd' : Date/Time
438+
* 'b' : Boolean
439+
*
440+
* @return self
441+
*/
442+
public function setCustomProperty(string $propertyName, $propertyValue = '', ?string $propertyType = null): self
443+
{
444+
if (!in_array($propertyType, [
445+
self::PROPERTY_TYPE_INTEGER,
446+
self::PROPERTY_TYPE_FLOAT,
447+
self::PROPERTY_TYPE_STRING,
448+
self::PROPERTY_TYPE_DATE,
449+
self::PROPERTY_TYPE_BOOLEAN,
450+
])) {
451+
if (is_float($propertyValue)) {
452+
$propertyType = self::PROPERTY_TYPE_FLOAT;
453+
} elseif (is_int($propertyValue)) {
454+
$propertyType = self::PROPERTY_TYPE_INTEGER;
455+
} elseif (is_bool($propertyValue)) {
456+
$propertyType = self::PROPERTY_TYPE_BOOLEAN;
457+
} else {
458+
$propertyType = self::PROPERTY_TYPE_STRING;
459+
}
460+
}
461+
$this->customProperties[$propertyName] = [
462+
'value' => $propertyValue,
463+
'type' => $propertyType,
464+
];
465+
466+
return $this;
467+
}
359468
}

src/PhpPresentation/Reader/ODPresentation.php

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use DOMElement;
2323
use PhpOffice\Common\Drawing as CommonDrawing;
2424
use PhpOffice\Common\XMLReader;
25+
use PhpOffice\PhpPresentation\DocumentProperties;
2526
use PhpOffice\PhpPresentation\PhpPresentation;
2627
use PhpOffice\PhpPresentation\PresentationProperties;
2728
use PhpOffice\PhpPresentation\Shape\Drawing\Gd;
@@ -169,7 +170,7 @@ protected function loadDocumentProperties(): void
169170
'/office:document-meta/office:meta/meta:creation-date' => 'setCreated',
170171
'/office:document-meta/office:meta/dc:date' => 'setModified',
171172
];
172-
$oProperties = $this->oPhpPresentation->getDocumentProperties();
173+
$properties = $this->oPhpPresentation->getDocumentProperties();
173174
foreach ($arrayProperties as $path => $property) {
174175
$oElement = $this->oXMLReader->getElement($path);
175176
if ($oElement instanceof DOMElement) {
@@ -181,8 +182,36 @@ protected function loadDocumentProperties(): void
181182
}
182183
$value = $dateTime->getTimestamp();
183184
}
184-
$oProperties->{$property}($value);
185-
}
185+
$properties->{$property}($value);
186+
}
187+
}
188+
189+
foreach ($this->oXMLReader->getElements('/office:document-meta/office:meta/meta:user-defined') as $element) {
190+
if (!($element instanceof DOMElement)
191+
|| !$element->hasAttribute('meta:name')) {
192+
continue;
193+
}
194+
$propertyName = $element->getAttribute('meta:name');
195+
$propertyValue = (string) $element->nodeValue;
196+
$propertyType = $element->getAttribute('meta:value-type');
197+
switch ($propertyType) {
198+
case 'boolean':
199+
$propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
200+
break;
201+
case 'float':
202+
$propertyType = filter_var($propertyValue, FILTER_VALIDATE_INT) === false
203+
? DocumentProperties::PROPERTY_TYPE_FLOAT
204+
: DocumentProperties::PROPERTY_TYPE_INTEGER;
205+
break;
206+
case 'date':
207+
$propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
208+
break;
209+
case 'string':
210+
default:
211+
$propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
212+
break;
213+
}
214+
$properties->setCustomProperty($propertyName, $propertyValue, $propertyType);
186215
}
187216
}
188217

src/PhpPresentation/Reader/PowerPoint2007.php

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use PhpOffice\Common\Drawing as CommonDrawing;
2626
use PhpOffice\Common\XMLReader;
2727
use PhpOffice\PhpPresentation\DocumentLayout;
28+
use PhpOffice\PhpPresentation\DocumentProperties;
2829
use PhpOffice\PhpPresentation\PhpPresentation;
2930
use PhpOffice\PhpPresentation\PresentationProperties;
3031
use PhpOffice\PhpPresentation\Shape\Drawing\Gd;
@@ -244,10 +245,41 @@ protected function loadCustomProperties(string $sPart): void
244245
$sPart = str_replace(' xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"', '', $sPart);
245246
/* @phpstan-ignore-next-line */
246247
if ($xmlReader->getDomFromString($sPart)) {
247-
$pathMarkAsFinal = '/Properties/property[@pid="2"][@fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"][@name="_MarkAsFinal"]/vt:bool';
248-
if (is_object($oElement = $xmlReader->getElement($pathMarkAsFinal))) {
249-
if ('true' == $oElement->nodeValue) {
250-
$this->oPhpPresentation->getPresentationProperties()->markAsFinal(true);
248+
foreach ($xmlReader->getElements('/Properties/property[@fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"]') as $element) {
249+
if (!$element->hasAttribute('name')) {
250+
continue;
251+
}
252+
$propertyName = $element->getAttribute('name');
253+
if ($propertyName == '_MarkAsFinal') {
254+
$attributeElement = $xmlReader->getElement('vt:bool', $element);
255+
if ($attributeElement && 'true' == $attributeElement->nodeValue) {
256+
$this->oPhpPresentation->getPresentationProperties()->markAsFinal(true);
257+
}
258+
} else {
259+
$attributeTypeInt = $xmlReader->getElement('vt:i4', $element);
260+
$attributeTypeFloat = $xmlReader->getElement('vt:r8', $element);
261+
$attributeTypeBoolean = $xmlReader->getElement('vt:bool', $element);
262+
$attributeTypeDate = $xmlReader->getElement('vt:filetime', $element);
263+
$attributeTypeString = $xmlReader->getElement('vt:lpwstr', $element);
264+
265+
if ($attributeTypeInt) {
266+
$propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER;
267+
$propertyValue = (int) $attributeTypeInt->nodeValue;
268+
} elseif ($attributeTypeFloat) {
269+
$propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT;
270+
$propertyValue = (float) $attributeTypeFloat->nodeValue;
271+
} elseif ($attributeTypeBoolean) {
272+
$propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
273+
$propertyValue = $attributeTypeBoolean->nodeValue == 'true' ? true : false;
274+
} elseif ($attributeTypeDate) {
275+
$propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
276+
$propertyValue = strtotime($attributeTypeDate->nodeValue);
277+
} else {
278+
$propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
279+
$propertyValue = $attributeTypeString->nodeValue;
280+
}
281+
282+
$this->oPhpPresentation->getDocumentProperties()->setCustomProperty($propertyName, $propertyValue, $propertyType);
251283
}
252284
}
253285
}

src/PhpPresentation/Writer/ODPresentation/Meta.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpPresentation\Writer\ODPresentation;
44

55
use PhpOffice\Common\XMLWriter;
6+
use PhpOffice\PhpPresentation\DocumentProperties;
67

78
class Meta extends AbstractDecoratorWriter
89
{
@@ -52,6 +53,38 @@ public function render()
5253
// meta:keyword
5354
$objWriter->writeElement('meta:keyword', $this->getPresentation()->getDocumentProperties()->getKeywords());
5455

56+
// meta:user-defined
57+
$oDocumentProperties = $this->oPresentation->getDocumentProperties();
58+
foreach ($oDocumentProperties->getCustomProperties() as $customProperty) {
59+
$propertyValue = $oDocumentProperties->getCustomPropertyValue($customProperty);
60+
$propertyType = $oDocumentProperties->getCustomPropertyType($customProperty);
61+
62+
$objWriter->startElement('meta:user-defined');
63+
$objWriter->writeAttribute('meta:name', $customProperty);
64+
switch ($propertyType) {
65+
case DocumentProperties::PROPERTY_TYPE_INTEGER:
66+
case DocumentProperties::PROPERTY_TYPE_FLOAT:
67+
$objWriter->writeAttribute('meta:value-type', 'float');
68+
$objWriter->writeRaw($propertyValue);
69+
break;
70+
case DocumentProperties::PROPERTY_TYPE_BOOLEAN:
71+
$objWriter->writeAttribute('meta:value-type', 'boolean');
72+
$objWriter->writeRaw($propertyValue ? 'true' : 'false');
73+
break;
74+
case DocumentProperties::PROPERTY_TYPE_DATE:
75+
$objWriter->writeAttribute('meta:value-type', 'date');
76+
$objWriter->writeRaw(date(DATE_W3C, (int) $propertyValue));
77+
break;
78+
case DocumentProperties::PROPERTY_TYPE_STRING:
79+
case DocumentProperties::PROPERTY_TYPE_UNKNOWN:
80+
default:
81+
$objWriter->writeAttribute('meta:value-type', 'string');
82+
$objWriter->writeRaw($propertyValue);
83+
break;
84+
}
85+
$objWriter->endElement();
86+
}
87+
5588
// @todo : Where these properties are written ?
5689
// $this->getPresentation()->getDocumentProperties()->getCategory()
5790
// $this->getPresentation()->getDocumentProperties()->getCompany()

0 commit comments

Comments
 (0)