Marking documents
A Word document, like any Office Open XML document, carries its marking in its custom properties:
the part docProps/custom.xml, which a program reads without opening the text and Word lists among
the document's properties. Pushery\AiContentMarking\Documents\DocumentMarker writes the marking
there and reads it back.
use Pushery\AiContentMarking\Documents\DocumentMarker;
use Pushery\AiContentMarking\Marking;
$marker = new DocumentMarker;
$marker->write($path, Marking::generated('Support Desk', model: 'example-writer', operator: 'Example Ltd'));
$marking = $marker->read($path); // the Marking, or null
Each value of the marking is a property of its own, written only where the marking has it:
| Property | Holds |
|---|---|
DigitalSourceType | the URI of the IPTC term |
DigitalSourceGenerator | the generator |
DigitalSourceModel | the model |
DigitalSourceOperator | the operator |
DigitalSourceGeneratedAt | the moment, as an ISO 8601 date-time |
In a document write() marked, the part holds them like this:

A document without custom properties gets the part, declared with its content type and its
relationship. A document that has them keeps every property of its own, and a marking written before
is replaced rather than repeated. Every write is read back, and a file that is no Office Open XML
document is refused with an InvalidArgumentException.
read() returns a Marking only where DigitalSourceType names a term of the IPTC vocabulary,
as its IPTC URI or as the matching schema.org member. A moment that is no ISO 8601 date-time is left
out, and the term and the other values still stand.
What it is not
The properties are plain metadata: nothing signs them, and a program that saves the document
without them removes them. In the gap report, describe such an output with
metadata: Metadata::Unsigned.
Writing and reading need the zip and dom extensions of PHP. Without them DocumentMarker
throws a RuntimeException that names both.