forked from Grandt/PHPePub
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEPub.php
1520 lines (1356 loc) · 49.9 KB
/
EPub.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
/**
* Create an ePub compatible book file.
*
* Please note, once finalized a book can no longer have chapters of data added or changed.
*
* License: GNU LGPL, Attribution required for commercial implementations, requested for everything else.
*
* Thanks to: Adam Schmalhofer and Kirstyn Fox for invaluable input and for "nudging" me in the right direction :)
*
* @author A. Grandt <php@grandt.com>
* @copyright 2009-2012 A. Grandt
* @license GNU LGPL, Attribution required for commercial implementations, requested for everything else.
* @version 2.08
* @link http://www.phpclasses.org/package/6115
* @link /~https://github.com/Grandt/PHPePub
* @uses Zip.php version 1.35; http://www.phpclasses.org/browse/package/6110.html or /~https://github.com/Grandt/PHPZip
*/
class EPub {
const VERSION = 2.08;
const REQ_ZIP_VERSION = 1.35;
const IDENTIFIER_UUID = 'UUID';
const IDENTIFIER_URI = 'URI';
const IDENTIFIER_ISBN = 'ISBN';
/** Ignore all external references, and do not process the file for these */
const EXTERNAL_REF_IGNORE = 0;
/** Process the file for external references and add them to the book */
const EXTERNAL_REF_ADD = 1;
/** Process the file for external references and add them to the book, but remove images, and img tags */
const EXTERNAL_REF_REMOVE_IMAGES = 2;
/** Process the file for external references and add them to the book, but replace images, and img tags with [image] */
const EXTERNAL_REF_REPLACE_IMAGES = 3;
public $maxImageWidth = 768;
public $maxImageHeight = 1024;
public $splitDefaultSize = 250000;
private $zip;
private $title = "";
private $language = "en";
private $identifier = "";
private $identifierType = "";
private $description = "";
private $author = "";
private $authorSortKey = "";
private $publisherName = "";
private $publisherURL = "";
private $date = 0;
private $rights = "";
private $subject = "";
private $coverage = "";
private $relation = "";
private $sourceURL = "";
private $chapterCount = 0;
private $opf_manifest = "";
private $opf_spine = "";
private $ncx_navmap = "";
private $opf = "";
private $ncx = "";
private $isFinalized = FALSE;
private $isCoverImageSet = FALSE;
private $fileList = array();
private $dateformat = 'Y-m-d\TH:i:s.000000P'; // ISO 8601 long
private $dateformatShort = 'Y-m-d'; // short date format to placate ePubChecker.
private $headerDateFormat = "D, d M Y H:i:s T";
protected $isGdInstalled;
private $docRoot = NULL;
private $EPubMark = TRUE;
private $generator = "";
private $contentHeader = "";
private $contentFooter = "";
/**
* Class constructor.
*
* @return void
*/
function __construct() {
include_once "Zip.php";
if (!defined("Zip::VERSION") || Zip::VERSION < self::REQ_ZIP_VERSION) {
die("<p>EPub requires Zip.php at version " . self::REQ_ZIP_VERSION . " or higher.<br />You can obtain the latest version from <a href=\"http://www.phpclasses.org/browse/package/6110.html\">http://www.phpclasses.org/browse/package/6110.html</a>.</p>");
}
include_once "EPubChapterSplitter.php";
$this->docRoot = $_SERVER["DOCUMENT_ROOT"] . "/";
$this->zip = new Zip();
$this->zip->setExtraField(FALSE);
$this->zip->addFile("application/epub+zip", "mimetype");
$this->zip->setExtraField(TRUE);
$this->zip->addDirectory("META-INF");
$this->content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n\t<rootfiles>\n\t\t<rootfile full-path=\"book.opf\" media-type=\"application/oebps-package+xml\" />\n\t</rootfiles>\n</container>\n";
$this->zip->addFile($this->content, "META-INF/container.xml");
$this->content = NULL;
$this->opf_manifest = "\t\t<item id=\"ncx\" href=\"book.ncx\" media-type=\"application/x-dtbncx+xml\" />\n";
$this->chapterCount = 0;
$this->isGdInstalled = extension_loaded('gd') && function_exists('gd_info');
// introduced variable {{BOOKTITLE}} for str_replace the BookTitle in all head sections
$this->contentHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
. "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n"
. " \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
. "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
. "<head>"
. "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"
. "<link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\" />\n"
. "<title>{{BOOKTITLE}}</title>\n"
. "</head>\n"
. "<body>\n";
$this->contentFooter = "</body>\n</html>\n";
}
/**
* Class destructor
*
* @return void
* @TODO make sure elements in the destructor match the current class elements
*/
function __destruct() {
$this->zip = NULL;
$this->title = "";
$this->author = "";
$this->publisherName = "";
$this->publisherURL = "";
$this->date = 0;
$this->identifier = "";
$this->opf_manifest = "";
$this->opf_spine = "";
$this->ncx_navmap = "";
$this->opf = "";
$this->ncx = "";
$this->chapterCount = 0;
$this->subject = "";
$this->coverage = "";
$this->relation = "";
$this->generator = "";
}
/**
*
* @param String $fileName Filename to use for the file, must be unique for the book.
* @param String $fileId Unique identifier for the file.
* @param String $fileData File data
* @param String $mimetype file mime type
* @return bool $success
*/
function addFile($fileName, $fileId, $fileData, $mimetype) {
if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) {
return FALSE;
}
$fileName = preg_replace('#\\\#i', "/", $fileName);
$fileName = preg_replace('#^[/\.]+#i', "", $fileName);
$this->zip->addFile($fileData, $fileName);
$this->fileList[$fileName] = $fileName;
$this->opf_manifest .= "\t\t<item id=\"" . $fileId . "\" href=\"" . $fileName . "\" media-type=\"" . $mimetype . "\" />\n";
return TRUE;
}
/**
* Add a CSS file to the book.
*
* @param String $fileName Filename to use for the CSS file, must be unique for the book.
* @param String $fileId Unique identifier for the file.
* @param String $fileData CSS data
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? See documentation for <code>processCSSExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE.
* @param String $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
*
* @return bool $success
*/
function addCSSFile($fileName, $fileId, $fileData, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") {
if ($this->isFinalized || array_key_exists($fileName, $this->fileList)) {
return FALSE;
}
$fileName = preg_replace('#\\\#i', "/", $fileName);
$fileName = preg_replace('#^[/\.]+#i', "", $fileName);
$cssDir = pathinfo($fileName);
$cssDir = preg_replace('#^[/\.]+#i', "", $cssDir["dirname"] . "/");
if (!empty($cssDir)) {
$cssDir = preg_replace('#[^/]+/#i', "../", $cssDir);
}
if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
$this->processCSSExternalReferences($fileData, $externalReferences, $baseDir, $cssDir);
}
$this->zip->addFile($fileData, $fileName);
$this->fileList[$fileName] = $fileName;
$this->opf_manifest .= "\t\t<item id=\"css_" . $fileId . "\" href=\"" . $fileName . "\" media-type=\"text/css\" />\n";
return TRUE;
}
/**
* Add a chapter to the book, as a chapter should not exceed 250kB, you can parse an array with multiple parts as $chapterData.
* These will still only show up as a single chapter in the book TOC.
*
* @param String $chapterName Name of the chapter, will be use din the TOC
* @param String $fileName Filename to use for the chapter, must be unique for the book.
* @param String $chapter Chapter text in XHTML or array $chapterData valid XHTML data for the chapter. File should NOT exceed 250kB.
* @param Bool $autoSplit Should the chapter be split if it exceeds the default split size? Default=FALSE, only used if $chapterData is a String.
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? See documentation for <code>processChapterExternalReferences</code> for explanation. Default is EPub::EXTERNAL_REF_IGNORE.
* @param String $baseDir Default is "", meaning it is pointing to the document root. NOT used if $externalReferences is set to EPub::EXTERNAL_REF_IGNORE.
* @return bool $success
*/
function addChapter($chapterName, $fileName, $chapterData, $autoSplit = FALSE, $externalReferences = EPub::EXTERNAL_REF_IGNORE, $baseDir = "") {
if ($this->isFinalized) {
return FALSE;
}
$fileName = preg_replace('#\\\#i', "/", $fileName);
$fileName = preg_replace('#^[/\.]+#i', "", $fileName);
$htmlDir = pathinfo($fileName);
$htmlDir = preg_replace('#^[/\.]+#i', "", $htmlDir["dirname"] . "/");
// Add Header and Footer to Chapter HTML Data
$chapter = $this->wrapChapter($chapterData);
if ($autoSplit && is_string($chapterData) && mb_strlen($chapterData) > $this->splitDefaultSize) {
$splitter = new EPubChapterSplitter();
$chapterArray = $splitter->splitChapter($chapterData);
if (count($chapterArray) > 1) {
$chapter = $chapterArray;
}
}
if (!empty($chapter) && is_string($chapter)) {
if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
$this->processChapterExternalReferences($chapter, $externalReferences, $baseDir, $htmlDir);
}
$this->zip->addFile($chapter, $fileName);
$this->fileList[$fileName] = $fileName;
$this->chapterCount++;
$this->opf_manifest .= "\t\t<item id=\"chapter" . $this->chapterCount . "\" href=\"" . $fileName . "\" media-type=\"application/xhtml+xml\" />\n";
$this->opf_spine .= "\t\t<itemref idref=\"chapter" . $this->chapterCount . "\" />\n";
$this->ncx_navmap .= "\n\t\t<navPoint id=\"chapter" . $this->chapterCount . "\" playOrder=\"" . $this->chapterCount . "\">\n\t\t\t<navLabel><text>" . $chapterName . "</text></navLabel>\n\t\t\t<content src=\"" . $fileName . "\" />\n\t\t</navPoint>\n";
} else if (is_array($chapter)) {
$fileNameParts = pathinfo($fileName);
$extension = $fileNameParts['extension'];
$name = $fileNameParts['filename'];
$partCount = 0;
$this->chapterCount++;
$oneChapter = each($chapter);
while ($oneChapter) {
list($k, $v) = $oneChapter;
$c = $v;
if ($externalReferences !== EPub::EXTERNAL_REF_IGNORE) {
$this->processChapterExternalReferences($c, $externalReferences, $baseDir);
}
$partCount++;
$partName = $partName = $name . "-" . $partCount . "." . $extension;;
$this->zip->addFile($c, $partName);
$this->fileList[$partName] = $partName;
$this->opf_manifest .= "\t\t<item id=\"chapter" . $this->chapterCount . "-" . $partCount . "\" href=\"" . $partName . "\" media-type=\"application/xhtml+xml\" />\n";
$this->opf_spine .= "\t\t<itemref idref=\"chapter" . $this->chapterCount . "-" . $partCount . "\" />\n";
$oneChapter = each($chapter);
}
$this->ncx_navmap .= "\n\t\t<navPoint id=\"chapter" . $this->chapterCount . "-1\" playOrder=\"" . $this->chapterCount . "\">\n\t\t\t<navLabel><text>" . $chapterName . "</text></navLabel>\n\t\t\t<content src=\"" . $name . "-1." . $extension . "\" />\n\t\t</navPoint>\n";
}
return TRUE;
}
/**
* Wrap ChapterContent with Head and Footer
*
* @param $content
* @return string $content
*/
private function wrapChapter($content) {
return $this->contentHeader . "\n" . $content . "\n" . $this->contentFooter;
}
/**
* Process external references from a HTML to the book. The chapter itself is not stored.
* the HTML is scanned for <link>, <style> and <img> tags.
* Embedded CSS styles and links will also be processed.
* Script tags are not processed, as scripting should be avoided in e-books.
*
* EPub keeps track of added files, and duplicate files referenced across multiple
* chapters, are only added once.
*
* If the $doc is a string, it is assumed to be the content of an HTML file,
* else is it assumes to be a DOMDocument.
*
* Basedir is the root dir the HTML is supposed to "live" in, used to resolve
* relative references such as <code><img src="../images/image.png"/></code>
*
* $externalReferences determines how the function will handle external references.
*
* @param mixed &$doc (referenced)
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
* @param String $baseDir Default is "", meaning it is pointing to the document root.
* @param String $htmlDir The path to the parent HTML file's directory from the root of the archive.
*
* @return Bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
*/
protected function processChapterExternalReferences(&$doc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") {
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
return FALSE;
}
$backPath = preg_replace('#[^/]+/#i', "../", $htmlDir);
$isDocAString = is_string($doc);
$xmlDoc = NULL;
if ($isDocAString) {
$xmlDoc = new DOMDocument();
@$xmlDoc->loadHTML($doc);
} else {
$xmlDoc = $doc;
}
$this->processChapterStyles($xmlDoc, $externalReferences, $baseDir, $htmlDir);
$this->processChapterLinks($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
$this->processChapterImages($xmlDoc, $externalReferences, $baseDir, $htmlDir, $backPath);
if ($isDocAString) {
$html = $xmlDoc->saveXML();
$head = $xmlDoc->getElementsByTagName("head");
$body = $xmlDoc->getElementsByTagName("body");
$xml = new DOMDocument('1.0', "utf-8");
$xml->lookupPrefix("http://www.w3.org/1999/xhtml");
$xml->preserveWhiteSpace = FALSE;
$xml->formatOutput = TRUE;
$xml2Doc = new DOMDocument('1.0', "utf-8");
$xml2Doc->lookupPrefix("http://www.w3.org/1999/xhtml");
$xml2Doc->loadXML("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n</html>\n");
$html = $xml2Doc->getElementsByTagName("html")->item(0);
$html->appendChild($xml2Doc->importNode($head->item(0), TRUE));
$html->appendChild($xml2Doc->importNode($body->item(0), TRUE));
// force pretty printing and correct formatting, should not be needed, but it is.
$xml->loadXML($xml2Doc->saveXML());
$doc = $xml->saveXML();
}
return TRUE;
}
/**
* Process images referenced from an CSS file to the book.
*
* $externalReferences determines how the function will handle external references.
*
* @param String &$cssFile (referenced)
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
* @param String $baseDir Default is "", meaning it is pointing to the document root.
* @param String $cssDir The of the CSS file's directory from the root of the archive.
*
* @return Bool FALSE if unsuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
*/
protected function processCSSExternalReferences(&$cssFile, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $cssDir = "") {
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
return FALSE;
}
$backPath = preg_replace('#[^/]+/#i', "../", $cssDir);
$imgs = null;
preg_match_all('#url\s*\([\'\"\s]*(.+?)[\'\"\s]*\)#im', $cssFile, $imgs, PREG_SET_ORDER);
$itemCount = count($imgs);
for ($idx = 0; $idx < $itemCount; $idx++) {
$img = $imgs[$idx];
if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES || $externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) {
$cssFile = str_replace($img[0], "", $cssFile);
} else {
$source = $img[1];
$pathData = pathinfo($source);
$internalSrc = $pathData['basename'];
$internalPath = "";
$isSourceExternal = FALSE;
if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $cssDir, $backPath)) {
$cssFile = str_replace($img[0], "url('" . $backPath . $internalPath . "')", $cssFile);
} else if ($isSourceExternal) {
$cssFile = str_replace($img[0], "", $cssFile); // External image is missing
} // else do nothing, if the image is local, and missing, assume it's been generated.
}
}
return TRUE;
}
/**
* Process style tags in a DOMDocument. Styles will be passed as CSS files and reinserted into the document.
*
* @param DOMDocument &$xmlDoc (referenced)
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
* @param String $baseDir Default is "", meaning it is pointing to the document root.
* @param String $htmlDir The path to the parent HTML file's directory from the root of the archive.
*
* @return Bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
*/
protected function processChapterStyles(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "") {
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
return FALSE;
}
// process inlined CSS styles in style tags.
$styles = $xmlDoc->getElementsByTagName("style");
$styleCount = $styles->length;
for ($styleIdx = 0; $styleIdx < $styleCount; $styleIdx++) {
$style = $styles->item($styleIdx);
$styleData = $style->nodeValue;
$styleData = preg_replace('#[/\*\s]*\<\!\[CDATA\[[\s\*/]*#im', "", $styleData);
$styleData = preg_replace('#[/\*\s]*\]\]\>[\s\*/]*#im', "", $styleData);
$this->processCSSExternalReferences($styleData, $externalReferences, $baseDir, $htmlDir);
$style->nodeValue = "\n" . trim($styleData) . "\n";
}
return TRUE;
}
/**
* Process link tags in a DOMDocument. Linked files will be loaded into the archive, and the link src will be rewritten to point to that location.
* Link types text/css will be passed as CSS files.
*
* @param DOMDocument &$xmlDoc (referenced)
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
* @param String $baseDir Default is "", meaning it is pointing to the document root.
* @param String $htmlDir The path to the parent HTML file's directory from the root of the archive.
* @param String $backPath The path to get back to the root of the archive from $htmlDir.
*
* @return Bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
*/
protected function processChapterLinks(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") {
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
return FALSE;
}
// process link tags.
$links = $xmlDoc->getElementsByTagName("link");
$linkCount = $links->length;
for ($linkIdx = 0; $linkIdx < $linkCount; $linkIdx++) {
$link = $links->item($linkIdx);
$source = $link->attributes->getNamedItem("href")->nodeValue;
$sourceData = NULL;
$pathData = pathinfo($source);
$internalSrc = $pathData['basename'];
if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
$urlinfo = parse_url($source);
if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
$internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1);
}
@$sourceData = file_get_contents($source);
} else if (strpos($source, "/") === 0) {
@$sourceData = file_get_contents($this->docRoot . $source);
} else {
@$sourceData = file_get_contents($this->docRoot . $baseDir . "/" . $source);
}
if (!empty($sourceData)) {
if (!array_key_exists($internalSrc, $this->fileList)) {
$mime = $link->attributes->getNamedItem("type")->nodeValue;
if (empty($mime)) {
$mime = "text/plain";
}
if ($mime == "text/css") {
$this->processCSSExternalReferences($sourceData, $externalReferences, $baseDir, $htmlDir);
$this->addCSSFile($internalSrc, $internalSrc, $sourceData, EPub::EXTERNAL_REF_IGNORE, $baseDir);
$link->setAttribute("href", $backPath . $internalSrc);
} else {
$this->addFile($internalSrc, $internalSrc, $sourceData, $mime);
}
$this->fileList[$internalSrc] = $source;
} else {
$link->setAttribute("href", $backPath . $internalSrc);
}
} // else do nothing, if the link is local, and missing, assume it's been generated.
}
return TRUE;
}
/**
* Process img tags in a DOMDocument.
* $externalReferences will determine what will happen to these images, and the img src will be rewritten accordingly.
*
* @param DOMDocument &$xmlDoc (referenced)
* @param int $externalReferences How to handle external references, EPub::EXTERNAL_REF_IGNORE, EPub::EXTERNAL_REF_ADD or EPub::EXTERNAL_REF_REMOVE_IMAGES? Default is EPub::EXTERNAL_REF_ADD.
* @param String $baseDir Default is "", meaning it is pointing to the document root.
* @param String $htmlDir The path to the parent HTML file's directory from the root of the archive.
* @param String $backPath The path to get back to the root of the archive from $htmlDir.
*
* @return Bool FALSE if uncuccessful (book is finalized or $externalReferences == EXTERNAL_REF_IGNORE).
*/
protected function processChapterImages(&$xmlDoc, $externalReferences = EPub::EXTERNAL_REF_ADD, $baseDir = "", $htmlDir = "", $backPath = "") {
if ($this->isFinalized || $externalReferences === EPub::EXTERNAL_REF_IGNORE) {
return FALSE;
}
// process img tags.
$postProcDomElememts = array();
$images = $xmlDoc->getElementsByTagName("img");
$itemCount = $images->length;
for ($idx = 0; $idx < $itemCount; $idx++) {
$img = $images->item($idx);
if ($externalReferences === EPub::EXTERNAL_REF_REMOVE_IMAGES) {
$postProcDomElememts[] = $img;
} else if ($externalReferences === EPub::EXTERNAL_REF_REPLACE_IMAGES) {
$postProcDomElememts[] = array($img, $this->createDomFragment($xmlDoc, "<em>[image]</em>"));
} else {
$source = $img->attributes->getNamedItem("src")->nodeValue;
$pathData = pathinfo($source);
$internalSrc = $pathData['basename'];
$internalPath = "";
$isSourceExternal = FALSE;
if ($this->resolveImage($source, $internalPath, $internalSrc, $isSourceExternal, $baseDir, $htmlDir, $backPath)) {
$img->setAttribute("src", $backPath . $internalPath);
} else if ($isSourceExternal) {
$postProcDomElememts[] = $img; // External image is missing
} // else do nothing, if the image is local, and missing, assume it's been generated.
}
}
foreach ($postProcDomElememts as $target) {
if (is_array($target)) {
$target[0]->parentNode->replaceChild($target[1], $target[0]);
} else {
$target->parentNode->removeChild($target);
}
}
return TRUE;
}
/**
* Resolve an image src and determine it's target location and add it to the book.
*
* @param String $source Image Source link.
* @param String &$internalPath (referenced) Return value, will be set to the target path and name in the book.
* @param String &$internalSrc (referenced) Return value, will be set to the target name in the book.
* @param String &$isSourceExternal (referenced) Return value, will be set to TRUE if the image originated from a full URL.
* @param String $baseDir Default is "", meaning it is pointing to the document root.
* @param String $htmlDir The path to the parent HTML file's directory from the root of the archive.
* @param String $backPath The path to get back to the root of the archive from $htmlDir.
*/
protected function resolveImage($source, &$internalPath, &$internalSrc, &$isSourceExternal, $baseDir = "", $htmlDir = "", $backPath = "") {
if ($this->isFinalized) {
return FALSE;
}
$imageData = NULL;
if (preg_match('#^(http|ftp)s?://#i', $source) == 1) {
$urlinfo = parse_url($source);
if (strpos($urlinfo['path'], $baseDir."/") !== FALSE) {
$internalSrc = substr($urlinfo['path'], strpos($urlinfo['path'], $baseDir."/") + strlen($baseDir) + 1);
}
$internalPath = $urlinfo["scheme"] . "/" . $urlinfo["host"] . "/" . pathinfo($urlinfo["path"], PATHINFO_DIRNAME);
$isSourceExternal = TRUE;
$imageData = $this->getImage($source);
} else if (strpos($source, "/") === 0) {
$internalPath = pathinfo($source, PATHINFO_DIRNAME);
$imageData = $this->getImage($this->docRoot . $source);
} else {
$internalPath = $htmlDir . "/" . preg_replace('#^[/\.]+#', '', pathinfo($source, PATHINFO_DIRNAME));
$imageData = $this->getImage($this->docRoot . $baseDir . "/" . $source);
}
if ($imageData !== FALSE) {
$internalPath = Zip::getRelativePath("images/" . $internalPath . "/" . $internalSrc);
if (!array_key_exists($internalPath, $this->fileList)) {
$this->addFile($internalPath, "i_" . $internalSrc, $imageData['image'], $imageData['mime']);
$this->fileList[$internalPath] = $source;
}
return TRUE;
}
return FALSE;
}
/**
* Add a cover image to the book.
*
* The styling and structure of the generated XHTML is heavily inspired by the XHTML generated by Calibre.
*
* @param String $fileName Filename to use for the image, must be unique for the book.
* @param String $imageData Binary image data
* @param String $mimetype Image mimetype, such as "image/jpeg" or "image/png".
* @return bool $success
*/
function setCoverImage($fileName, $imageData = NULL, $mimetype = NULL) {
if ($this->isFinalized || $this->isCoverImageSet || array_key_exists("CoverPage.html", $this->fileList)) {
return FALSE;
}
if ($imageData == NULL) { // assume $fileName is the valid file path.
$image = $this->getImage($this->docRoot . $fileName);
$imageData = $image['image'];
$mimetype = $image['mime'];
}
$path = pathinfo($this->docRoot . $fileName);
$imgPath = "images/" . $path["basename"];
$coverPage = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n\t<head>\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n\t\t<title>Cover Image</title>\n\t\t<style type=\"text/css\" title=\"css\">\n\t\t\t@page, body, div, img {\n\t\t\t\tpadding: 0pt;\n\t\t\t\tmargin:0pt;\n\t\t\t}\n\t\t\tbody {\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div>\n\t\t\t<img src=\""
. $imgPath . "\" alt=\"Cover image\" style=\"height: 100%\"/>\n\t\t</div>\n\t</body>\n</html>\n";
$this->zip->addFile($coverPage, "CoverPage.html");
$this->zip->addFile($imageData, $imgPath);
$this->fileList["CoverPage.html"] = "CoverPage.html";
$this->fileList[$imgPath] = $fileName;
$this->opf_manifest = "\t\t<item id=\"coverImage\" href=\"" . $imgPath . "\" media-type=\"" . $mimetype . "\" />\n" . $this->opf_manifest;
$this->opf_manifest = "\t\t<item id=\"coverPage\" href=\"CoverPage.html\" media-type=\"application/xhtml+xml\" />\n" . $this->opf_manifest;
$this->opf_spine = "\t\t<itemref idref=\"coverPage\" linear=\"no\" />\n" . $this->opf_spine;
$this->opf_guide .= "\t\t<reference href=\"CoverPage.html\" type=\"cover\" title=\"coverPage\"/>\n";
$this->ncx_navmap = "\n\t\t<navPoint id=\"\" playOrder=\"0\">\n\t\t\t<navLabel><text>Cover</text></navLabel>\n\t\t\t<content src=\"CoverPage.html\" />\n\t\t</navPoint>\n" . $this->ncx_navmap;
$this->isCoverImageSet = TRUE;
return TRUE;
}
/**
* Get Book Chapter count.
*
* @access public
* @return number of chapters
*/
function getChapterCount() {
return $this->chapterCount;
}
/**
* Book title, mandatory.
*
* Used for the dc:title metadata parameter in the OPF file as well as the DocTitle attribute in the NCX file.
*
* @param string $title
* @access public
* @return bool $success
*/
function setTitle($title) {
if ($this->isFinalized) {
return FALSE;
}
$this->title = $title;
// Set Booktitle as Title Tag in all xHTML Documents
$this->contentHeader = str_replace("{{BOOKTITLE}}", $title, $this->contentHeader);
return TRUE;
}
/**
* Get Book title.
*
* @access public
* @return $title
*/
function getTitle() {
return $this->title;
}
/**
* Book language, mandatory
*
* Use the RFC3066 Language codes, such as "en", "da", "fr" etc.
* Defaults to "en".
*
* Used for the dc:language metadata parameter in the OPF file.
*
* @param string $language
* @access public
* @return bool $success
*/
function setLanguage($language) {
if ($this->isFinalized || mb_strlen($language) != 2) {
return FALSE;
}
$this->language = $language;
return TRUE;
}
/**
* Get Book language.
*
* @access public
* @return $language
*/
function getLanguage() {
return $this->language;
}
/**
* Unique book identifier, mandatory.
* Use the URI, or ISBN if available.
*
* An unambiguous reference to the resource within a given context.
*
* Recommended best practice is to identify the resource by means of a
* string conforming to a formal identification system.
*
* Used for the dc:identifier metadata parameter in the OPF file, as well
* as dtb:uid in the NCX file.
*
* Identifier type should only be:
* EPub::IDENTIFIER_URI
* EPub::IDENTIFIER_ISBN
* EPub::IDENTIFIER_UUID
*
* @param string $identifier
* @param string $identifierType
* @access public
* @return bool $success
*/
function setIdentifier($identifier, $identifierType) {
if ($this->isFinalized || ($identifierType !== EPub::IDENTIFIER_URI && $identifierType !== EPub::IDENTIFIER_ISBN && $identifierType !== EPub::IDENTIFIER_UUID)) {
return FALSE;
}
$this->identifier = $identifier;
$this->identifierType = $identifierType;
return TRUE;
}
/**
* Get Book identifier.
*
* @access public
* @return $identifier
*/
function getIdentifier() {
return $this->identifier;
}
/**
* Get Book identifierType.
*
* @access public
* @return $identifierType
*/
function getIdentifierType() {
return $this->identifierType;
}
/**
* Book description, optional.
*
* An account of the resource.
*
* Description may include but is not limited to: an abstract, a table of
* contents, a graphical representation, or a free-text account of the
* resource.
*
* Used for the dc:source metadata parameter in the OPF file
*
* @param string $description
* @access public
* @return bool $success
*/
function setDescription($description) {
if ($this->isFinalized) {
return FALSE;
}
$this->description = $description;
return TRUE;
}
/**
* Get Book description.
*
* @access public
* @return $description
*/
function getDescription() {
return $this->description;
}
/**
* Book author or creator, optional.
* The $authorSortKey is basically how the name is to be sorted, usually
* it's "Lastname, First names" where the $author is the straight
* "Firstnames Lastname"
*
* An entity primarily responsible for making the resource.
*
* Examples of a Creator include a person, an organization, or a service.
* Typically, the name of a Creator should be used to indicate the entity.
*
* Used for the dc:creator metadata parameter in the OPF file and the
* docAuthor attribure in the NCX file.
* The sort key is used for the opf:file-as attribute in dc:creator.
*
* @param string $author
* @param string $authorSortKey
* @access public
* @return bool $success
*/
function setAuthor($author, $authorSortKey) {
if ($this->isFinalized) {
return FALSE;
}
$this->author = $author;
$this->authorSortKey = $authorSortKey;
return TRUE;
}
/**
* Get Book author.
*
* @access public
* @return $author
*/
function getAuthor() {
return $this->author;
}
/**
* Publisher Information, optional.
*
* An entity responsible for making the resource available.
*
* Examples of a Publisher include a person, an organization, or a service.
* Typically, the name of a Publisher should be used to indicate the entity.
*
* Used for the dc:publisher and dc:relation metadata parameters in the OPF file.
*
* @param string $publisherName
* @param string $publisherURL
* @access public
* @return bool $success
*/
function setPublisher($publisherName, $publisherURL) {
if ($this->isFinalized) {
return FALSE;
}
$this->publisherName = $publisherName;
$this->publisherURL = $publisherURL;
return TRUE;
}
/**
* Get Book publisherName.
*
* @access public
* @return $publisherName
*/
function getPublisherName() {
return $this->publisherName;
}
/**
* Get Book publisherURL.
*
* @access public
* @return $publisherURL
*/
function getPublisherURL() {
return $this->publisherURL;
}
/**
* Release date, optional. If left blank, the time of the finalization will
* be used.
*
* A point or period of time associated with an event in the lifecycle of
* the resource.
*
* Date may be used to express temporal information at any level of
* granularity. Recommended best practice is to use an encoding scheme,
* such as the W3CDTF profile of ISO 8601 [W3CDTF].
*
* Used for the dc:date metadata parameter in the OPF file
*
* @param long $timestamp
* @access public
* @return bool $success
*/
function setDate($timestamp) {
if ($this->isFinalized) {
return FALSE;
}
$this->date = $timestamp;
return TRUE;
}
/**
* Get Book date.
*
* @access public
* @return $date
*/
function getDate() {
return $this->date;
}
/**
* Book (copy)rights, optional.
*
* Information about rights held in and over the resource.
*
* Typically, rights information includes a statement about various
* property rights associated with the resource, including intellectual
* property rights.
*
* Used for the dc:rights metadata parameter in the OPF file
*
* @param string $rightsText
* @access public
* @return bool $success
*/
function setRights($rightsText) {
if ($this->isFinalized) {
return FALSE;
}
$this->rights = $rightsText;
return TRUE;
}
/**
* Get Book rights.
*
* @access public
* @return $rights
*/
function getRights() {
return $this->rights;
}
/**
* Set book Subject.
*
* The topic of the resource.
*
* Typically, the subject will be represented using keywords, key phrases,
* or classification codes. Recommended best practice is to use a
* controlled vocabulary. To describe the spatial or temporal topic of the
* resource, use the Coverage element.
*
* @param String $subject
*/
function setSubject($subject) {
if ($this->isFinalized) {
return;
}
$this->subject = $subject;
}
/**
* Get the book subject.
*
* @return String The Subject.
*/
function getSubject() {
return $this->subject;
}
/**
* Book source URL, optional.
*
* A related resource from which the described resource is derived.
*
* The described resource may be derived from the related resource in whole
* or in part. Recommended best practice is to identify the related
* resource by means of a string conforming to a formal identification system.
*
* Used for the dc:source metadata parameter in the OPF file
*
* @param string $sourceURL
* @access public
* @return bool $success
*/
function setSourceURL($sourceURL) {
if ($this->isFinalized) {
return FALSE;
}
$this->sourceURL = $sourceURL;
return TRUE;
}
/**
* Get Book sourceURL.
*
* @access public
* @return $sourceURL
*/
function getSourceURL() {
return $this->sourceURL;