diff --git a/.gitignore b/.gitignore index 0b9d0608d0..6918df72e6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ _build /build phpunit.xml composer.phar +composer.lock vendor /report /build diff --git a/composer.json b/composer.json index efebe941e7..48fc16caed 100644 --- a/composer.json +++ b/composer.json @@ -108,13 +108,13 @@ "require": { "php": "^7.1|^8.0", "ext-dom": "*", - "ext-gd": "*", "ext-json": "*", "ext-xml": "*", - "ext-zip": "*", "phpoffice/math": "^0.2" }, "require-dev": { + "ext-zip": "*", + "ext-gd": "*", "ext-libxml": "*", "dompdf/dompdf": "^2.0 || ^3.0", "friendsofphp/php-cs-fixer": "^3.3", diff --git a/src/PhpWord/IOFactory.php b/src/PhpWord/IOFactory.php index 55c374a8b9..50c419cae2 100644 --- a/src/PhpWord/IOFactory.php +++ b/src/PhpWord/IOFactory.php @@ -36,7 +36,7 @@ abstract class IOFactory */ public static function createWriter(PhpWord $phpWord, $name = 'Word2007') { - if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF'], true)) { + if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF', 'EPub3'], true)) { throw new Exception("\"{$name}\" is not a valid writer."); } diff --git a/src/PhpWord/Writer/EPub3.php b/src/PhpWord/Writer/EPub3.php new file mode 100644 index 0000000000..b9eb5a00f2 --- /dev/null +++ b/src/PhpWord/Writer/EPub3.php @@ -0,0 +1,91 @@ +setPhpWord($phpWord); + + // Create parts + $this->parts = [ + 'Mimetype' => 'mimetype', + 'Content' => 'content.opf', + 'Toc' => 'toc.ncx', + 'Styles' => 'styles.css', + 'Manifest' => 'META-INF/container.xml', + ]; + foreach (array_keys($this->parts) as $partName) { + $partClass = static::class . '\\Part\\' . $partName; + if (class_exists($partClass)) { + /** @var AbstractPart $partObject Type hint */ + $partObject = new $partClass(); + $partObject->setParentWriter($this); + $this->writerParts[strtolower($partName)] = $partObject; + } + } + + // Set package paths + $this->mediaPaths = ['image' => 'Images/', 'object' => 'Objects/']; + } + + /** + * Save PhpWord to file. + */ + public function save(string $filename): void + { + $filename = $this->getTempFile($filename); + $zip = $this->getZipArchive($filename); + + // Add section media files + $sectionMedia = Media::getElements('section'); + if (!empty($sectionMedia)) { + $this->addFilesToPackage($zip, $sectionMedia); + } + + // Write parts + foreach ($this->parts as $partName => $fileName) { + if ($fileName === '') { + continue; + } + $part = $this->getWriterPart($partName); + if (!$part instanceof AbstractPart) { + continue; + } + + $zip->addFromString($fileName, $part->write()); + } + + // Close zip archive and cleanup temp file + $zip->close(); + $this->cleanupTempFile(); + } +} diff --git a/src/PhpWord/Writer/EPub3/Part/AbstractPart.php b/src/PhpWord/Writer/EPub3/Part/AbstractPart.php new file mode 100644 index 0000000000..c3b79b2d26 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/AbstractPart.php @@ -0,0 +1,63 @@ +parentWriter = $writer; + + return $this; + } + + /** + * Get parent writer. + * + * @return AbstractWriter + */ + public function getParentWriter() + { + return $this->parentWriter; + } + + /** + * Write part content. + * + * @return string + */ + abstract public function write(); +} diff --git a/src/PhpWord/Writer/EPub3/Part/Content.php b/src/PhpWord/Writer/EPub3/Part/Content.php new file mode 100644 index 0000000000..add42b35c5 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/Content.php @@ -0,0 +1,49 @@ +'; + $content .= ''; + $content .= ''; + $content .= 'Sample EPub3 Document'; + $content .= 'en'; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + + return $content; + } +} diff --git a/src/PhpWord/Writer/EPub3/Part/Manifest.php b/src/PhpWord/Writer/EPub3/Part/Manifest.php new file mode 100644 index 0000000000..9c5143f434 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/Manifest.php @@ -0,0 +1,42 @@ +'; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + + return $content; + } +} diff --git a/src/PhpWord/Writer/EPub3/Part/Meta.php b/src/PhpWord/Writer/EPub3/Part/Meta.php new file mode 100644 index 0000000000..04531f45d2 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/Meta.php @@ -0,0 +1,43 @@ +'; + $content .= ''; + $content .= 'Sample EPub3 Document'; + $content .= 'en'; + $content .= 'urn:uuid:12345'; + $content .= '2023-01-01T00:00:00Z'; + $content .= ''; + + return $content; + } +} diff --git a/src/PhpWord/Writer/EPub3/Part/Mimetype.php b/src/PhpWord/Writer/EPub3/Part/Mimetype.php new file mode 100644 index 0000000000..36dc9168b1 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Part/Mimetype.php @@ -0,0 +1,35 @@ +parentWriter = $writer; + + return $this; + } + + /** + * Get parent writer. + * + * @return AbstractWriter + */ + public function getParentWriter() + { + return $this->parentWriter; + } + + /** + * Write style content. + * + * @return string + */ + abstract public function write(); +} diff --git a/src/PhpWord/Writer/EPub3/Style/Font.php b/src/PhpWord/Writer/EPub3/Style/Font.php new file mode 100644 index 0000000000..3f3ffd1544 --- /dev/null +++ b/src/PhpWord/Writer/EPub3/Style/Font.php @@ -0,0 +1,41 @@ +getPhpWord()); + self::assertEquals('./', $object->getDiskCachingDirectory()); + foreach (['Content', 'Manifest', 'Mimetype'] as $part) { + self::assertInstanceOf( + "PhpOffice\\PhpWord\\Writer\\Epub3\\Part\\{$part}", + $object->getWriterPart($part) + ); + self::assertInstanceOf( + 'PhpOffice\\PhpWord\\Writer\\Epub3', + $object->getWriterPart($part)->getParentWriter() + ); + } + } + + /** + * Test construction with null. + */ + public function testConstructWithNull(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $this->expectExceptionMessage('No PhpWord assigned.'); + $object = new EPub3(); + $object->getPhpWord(); + } + + /** + * Test saving document. + */ + public function testSave(): void + { + $imageSrc = __DIR__ . '/../_files/images/PhpWord.png'; + $file = __DIR__ . '/../_files/temp.epub'; + + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Test 1'); + $section->addTextBreak(); + $section->addText('Test 2', null, ['alignment' => Jc::CENTER]); + $section->addLink('https://github.com/PHPOffice/PHPWord'); + $section->addTitle('Test', 1); + $section->addPageBreak(); + $section->addImage($imageSrc); + $writer = new EPub3($phpWord); + $writer->save($file); + self::assertFileExists($file); + unlink($file); + } + + /** + * Test PHP output. + */ + public function testSavePhpOutput(): void + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $section->addText('Test'); + $writer = new EPub3($phpWord); + ob_start(); + $writer->save('php://output'); + $contents = ob_get_contents(); + self::assertTrue(ob_end_clean()); + self::assertNotEmpty($contents); + } + + /** + * Test disk caching. + */ + public function testSetGetUseDiskCaching(): void + { + $object = new EPub3(); + $object->setUseDiskCaching(true, PHPWORD_TESTS_BASE_DIR); + self::assertTrue($object->isUseDiskCaching()); + self::assertEquals(PHPWORD_TESTS_BASE_DIR, $object->getDiskCachingDirectory()); + } + + /** + * Test disk caching exception. + */ + public function testSetUseDiskCachingException(): void + { + $this->expectException(\PhpOffice\PhpWord\Exception\Exception::class); + $dir = implode(DIRECTORY_SEPARATOR, [PHPWORD_TESTS_BASE_DIR, 'foo']); + + $object = new EPub3(); + $object->setUseDiskCaching(true, $dir); + } +}