Skip to content

Commit

Permalink
Merge pull request #411 from PHPOffice/issue313pptx
Browse files Browse the repository at this point in the history
#313 : Support for custom document properties
  • Loading branch information
Progi1984 committed Aug 3, 2021
2 parents a0c665c + a9a4ec7 commit be74207
Show file tree
Hide file tree
Showing 12 changed files with 515 additions and 9 deletions.
5 changes: 5 additions & 0 deletions docs/changes/1.0.0.md
Expand Up @@ -60,6 +60,11 @@
- ODPresentation Writer
- PowerPoint2007 Reader
- PowerPoint2007 Writer
- Support for custom document properties - @Progi1984 GH-313
- ODPresentation Reader
- ODPresentation Writer
- PowerPoint2007 Reader
- PowerPoint2007 Writer

## Project Management
- Migrated from Travis CI to Github Actions - @Progi1984 GH-635
Expand Down
39 changes: 39 additions & 0 deletions docs/usage/presentation.md
Expand Up @@ -64,6 +64,45 @@ $properties->setSubject('My subject');
$properties->setKeywords('my, key, word');
```

### Custom Properties

You can add custom properties with the method `setCustomProperty`.

Multiple types are available:
* `DocumentProperties::PROPERTY_TYPE_STRING` for string value,
* `DocumentProperties::PROPERTY_TYPE_BOOLEAN` for boolean value,
* `DocumentProperties::PROPERTY_TYPE_FLOAT` for float value,
* `DocumentProperties::PROPERTY_TYPE_INTEGER` for integer value,
* `DocumentProperties::PROPERTY_TYPE_DATE` for date value,
* `DocumentProperties::PROPERTY_TYPE_UNKNOWN` for unknown type value.


``` php
<?php

use PhpOffice\PhpPresentation\DocumentProperties;

$properties = $presentation->getProperties();

// Set the custom property
$properties->setCustomProperty('propertyName', 'propertyValue', DocumentProperties::PROPERTY_TYPE_STRING);

// Check if a custom property exists
$properties->isCustomPropertySet('unknown'); // return `false`
$properties->isCustomPropertySet('propertyName'); // return `true`

// Return all custom properties
$properties->getCustomProperties(); // return `['propertyName']`

// Return value from a custom property
$properties->getCustomPropertyValue('unknown'); // return `null` if not set
$properties->getCustomPropertyValue('propertyName'); // return `propertyValue`

// Return type from a custom property
$properties->getCustomPropertyType('unknown'); // return `null` if not set
$properties->getCustomPropertyType('propertyName'); // return `DocumentProperties::PROPERTY_TYPE_STRING`
```

## Presentation Properties

You can define some properties which are relative to the presentation, like the zoom or the thumbnail.
Expand Down
111 changes: 110 additions & 1 deletion src/PhpPresentation/DocumentProperties.php
Expand Up @@ -23,6 +23,13 @@
*/
class DocumentProperties
{
public const PROPERTY_TYPE_BOOLEAN = 'b';
public const PROPERTY_TYPE_INTEGER = 'i';
public const PROPERTY_TYPE_FLOAT = 'f';
public const PROPERTY_TYPE_DATE = 'd';
public const PROPERTY_TYPE_STRING = 's';
public const PROPERTY_TYPE_UNKNOWN = 'u';

/**
* Creator.
*
Expand Down Expand Up @@ -94,7 +101,14 @@ class DocumentProperties
private $company;

/**
* Create a new \PhpOffice\PhpPresentation\DocumentProperties.
* Custom Properties.
*
* @var array<string, array<string, mixed>>
*/
private $customProperties = [];

/**
* Create a new \PhpOffice\PhpPresentation\DocumentProperties
*/
public function __construct()
{
Expand Down Expand Up @@ -356,4 +370,99 @@ public function setCompany($pValue = '')

return $this;
}

/**
* Get a List of Custom Property Names.
*
* @return array<int, string>
*/
public function getCustomProperties(): array
{
return array_keys($this->customProperties);
}

/**
* Check if a Custom Property is defined.
*
* @param string $propertyName
*
* @return bool
*/
public function isCustomPropertySet(string $propertyName): bool
{
return isset($this->customProperties[$propertyName]);
}

/**
* Get a Custom Property Value.
*
* @param string $propertyName
*
* @return string|null
*/
public function getCustomPropertyValue(string $propertyName): ?string
{
if ($this->isCustomPropertySet($propertyName)) {
return $this->customProperties[$propertyName]['value'];
}

return null;
}

/**
* Get a Custom Property Type.
*
* @param string $propertyName
*
* @return string|null
*/
public function getCustomPropertyType(string $propertyName): ?string
{
if ($this->isCustomPropertySet($propertyName)) {
return $this->customProperties[$propertyName]['type'];
}

return null;
}

/**
* Set a Custom Property.
*
* @param string $propertyName
* @param mixed $propertyValue
* @param string|null $propertyType
* 'i' : Integer
* 'f' : Floating Point
* 's' : String
* 'd' : Date/Time
* 'b' : Boolean
*
* @return self
*/
public function setCustomProperty(string $propertyName, $propertyValue = '', ?string $propertyType = null): self
{
if (!in_array($propertyType, [
self::PROPERTY_TYPE_INTEGER,
self::PROPERTY_TYPE_FLOAT,
self::PROPERTY_TYPE_STRING,
self::PROPERTY_TYPE_DATE,
self::PROPERTY_TYPE_BOOLEAN,
])) {
if (is_float($propertyValue)) {
$propertyType = self::PROPERTY_TYPE_FLOAT;
} elseif (is_int($propertyValue)) {
$propertyType = self::PROPERTY_TYPE_INTEGER;
} elseif (is_bool($propertyValue)) {
$propertyType = self::PROPERTY_TYPE_BOOLEAN;
} else {
$propertyType = self::PROPERTY_TYPE_STRING;
}
}
$this->customProperties[$propertyName] = [
'value' => $propertyValue,
'type' => $propertyType,
];

return $this;
}
}
35 changes: 32 additions & 3 deletions src/PhpPresentation/Reader/ODPresentation.php
Expand Up @@ -22,6 +22,7 @@
use DOMElement;
use PhpOffice\Common\Drawing as CommonDrawing;
use PhpOffice\Common\XMLReader;
use PhpOffice\PhpPresentation\DocumentProperties;
use PhpOffice\PhpPresentation\PhpPresentation;
use PhpOffice\PhpPresentation\PresentationProperties;
use PhpOffice\PhpPresentation\Shape\Drawing\Gd;
Expand Down Expand Up @@ -169,7 +170,7 @@ protected function loadDocumentProperties(): void
'/office:document-meta/office:meta/meta:creation-date' => 'setCreated',
'/office:document-meta/office:meta/dc:date' => 'setModified',
];
$oProperties = $this->oPhpPresentation->getDocumentProperties();
$properties = $this->oPhpPresentation->getDocumentProperties();
foreach ($arrayProperties as $path => $property) {
$oElement = $this->oXMLReader->getElement($path);
if ($oElement instanceof DOMElement) {
Expand All @@ -181,8 +182,36 @@ protected function loadDocumentProperties(): void
}
$value = $dateTime->getTimestamp();
}
$oProperties->{$property}($value);
}
$properties->{$property}($value);
}
}

foreach ($this->oXMLReader->getElements('/office:document-meta/office:meta/meta:user-defined') as $element) {
if (!($element instanceof DOMElement)
|| !$element->hasAttribute('meta:name')) {
continue;
}
$propertyName = $element->getAttribute('meta:name');
$propertyValue = (string) $element->nodeValue;
$propertyType = $element->getAttribute('meta:value-type');
switch ($propertyType) {
case 'boolean':
$propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
break;
case 'float':
$propertyType = filter_var($propertyValue, FILTER_VALIDATE_INT) === false
? DocumentProperties::PROPERTY_TYPE_FLOAT
: DocumentProperties::PROPERTY_TYPE_INTEGER;
break;
case 'date':
$propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
break;
case 'string':
default:
$propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
break;
}
$properties->setCustomProperty($propertyName, $propertyValue, $propertyType);
}
}

Expand Down
40 changes: 36 additions & 4 deletions src/PhpPresentation/Reader/PowerPoint2007.php
Expand Up @@ -25,6 +25,7 @@
use PhpOffice\Common\Drawing as CommonDrawing;
use PhpOffice\Common\XMLReader;
use PhpOffice\PhpPresentation\DocumentLayout;
use PhpOffice\PhpPresentation\DocumentProperties;
use PhpOffice\PhpPresentation\PhpPresentation;
use PhpOffice\PhpPresentation\PresentationProperties;
use PhpOffice\PhpPresentation\Shape\Drawing\Gd;
Expand Down Expand Up @@ -244,10 +245,41 @@ protected function loadCustomProperties(string $sPart): void
$sPart = str_replace(' xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"', '', $sPart);
/* @phpstan-ignore-next-line */
if ($xmlReader->getDomFromString($sPart)) {
$pathMarkAsFinal = '/Properties/property[@pid="2"][@fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"][@name="_MarkAsFinal"]/vt:bool';
if (is_object($oElement = $xmlReader->getElement($pathMarkAsFinal))) {
if ('true' == $oElement->nodeValue) {
$this->oPhpPresentation->getPresentationProperties()->markAsFinal(true);
foreach ($xmlReader->getElements('/Properties/property[@fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"]') as $element) {
if (!$element->hasAttribute('name')) {
continue;
}
$propertyName = $element->getAttribute('name');
if ($propertyName == '_MarkAsFinal') {
$attributeElement = $xmlReader->getElement('vt:bool', $element);
if ($attributeElement && 'true' == $attributeElement->nodeValue) {
$this->oPhpPresentation->getPresentationProperties()->markAsFinal(true);
}
} else {
$attributeTypeInt = $xmlReader->getElement('vt:i4', $element);
$attributeTypeFloat = $xmlReader->getElement('vt:r8', $element);
$attributeTypeBoolean = $xmlReader->getElement('vt:bool', $element);
$attributeTypeDate = $xmlReader->getElement('vt:filetime', $element);
$attributeTypeString = $xmlReader->getElement('vt:lpwstr', $element);

if ($attributeTypeInt) {
$propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER;
$propertyValue = (int) $attributeTypeInt->nodeValue;
} elseif ($attributeTypeFloat) {
$propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT;
$propertyValue = (float) $attributeTypeFloat->nodeValue;
} elseif ($attributeTypeBoolean) {
$propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
$propertyValue = $attributeTypeBoolean->nodeValue == 'true' ? true : false;
} elseif ($attributeTypeDate) {
$propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
$propertyValue = strtotime($attributeTypeDate->nodeValue);
} else {
$propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
$propertyValue = $attributeTypeString->nodeValue;
}

$this->oPhpPresentation->getDocumentProperties()->setCustomProperty($propertyName, $propertyValue, $propertyType);
}
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/PhpPresentation/Writer/ODPresentation/Meta.php
Expand Up @@ -3,6 +3,7 @@
namespace PhpOffice\PhpPresentation\Writer\ODPresentation;

use PhpOffice\Common\XMLWriter;
use PhpOffice\PhpPresentation\DocumentProperties;

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

// meta:user-defined
$oDocumentProperties = $this->oPresentation->getDocumentProperties();
foreach ($oDocumentProperties->getCustomProperties() as $customProperty) {
$propertyValue = $oDocumentProperties->getCustomPropertyValue($customProperty);
$propertyType = $oDocumentProperties->getCustomPropertyType($customProperty);

$objWriter->startElement('meta:user-defined');
$objWriter->writeAttribute('meta:name', $customProperty);
switch ($propertyType) {
case DocumentProperties::PROPERTY_TYPE_INTEGER:
case DocumentProperties::PROPERTY_TYPE_FLOAT:
$objWriter->writeAttribute('meta:value-type', 'float');
$objWriter->writeRaw($propertyValue);
break;
case DocumentProperties::PROPERTY_TYPE_BOOLEAN:
$objWriter->writeAttribute('meta:value-type', 'boolean');
$objWriter->writeRaw($propertyValue ? 'true' : 'false');
break;
case DocumentProperties::PROPERTY_TYPE_DATE:
$objWriter->writeAttribute('meta:value-type', 'date');
$objWriter->writeRaw(date(DATE_W3C, (int) $propertyValue));
break;
case DocumentProperties::PROPERTY_TYPE_STRING:
case DocumentProperties::PROPERTY_TYPE_UNKNOWN:
default:
$objWriter->writeAttribute('meta:value-type', 'string');
$objWriter->writeRaw($propertyValue);
break;
}
$objWriter->endElement();
}

// @todo : Where these properties are written ?
// $this->getPresentation()->getDocumentProperties()->getCategory()
// $this->getPresentation()->getDocumentProperties()->getCompany()
Expand Down

0 comments on commit be74207

Please sign in to comment.