From dca863d888c8e041b03d3e1a408b42b84b8d7fb7 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Wed, 28 Oct 2020 22:16:38 +0100 Subject: [PATCH] add more phpoffice libs --- .../Common/Adapter/Zip/PclZipAdapter.php | 45 + .../Common/Adapter/Zip/ZipArchiveAdapter.php | 49 + PhpOffice/Common/Adapter/Zip/ZipInterface.php | 30 + PhpOffice/Common/Autoloader.php | 54 + PhpOffice/Common/Drawing.php | 237 + PhpOffice/Common/File.php | 112 + PhpOffice/Common/Font.php | 100 + PhpOffice/Common/Microsoft/OLERead.php | 320 + .../Common/Microsoft/PasswordEncoder.php | 234 + PhpOffice/Common/Text.php | 234 + PhpOffice/Common/XMLReader.php | 210 + PhpOffice/Common/XMLWriter.php | 183 + PhpOffice/PhpPresentation/AbstractShape.php | 469 ++ PhpOffice/PhpPresentation/Autoloader.php | 55 + .../PhpPresentation/ComparableInterface.php | 51 + PhpOffice/PhpPresentation/DocumentLayout.php | 252 + .../PhpPresentation/DocumentProperties.php | 348 ++ .../PhpPresentation/GeometryCalculator.php | 96 + PhpOffice/PhpPresentation/HashTable.php | 198 + PhpOffice/PhpPresentation/IOFactory.php | 115 + PhpOffice/PhpPresentation/PhpPresentation.php | 447 ++ .../PresentationProperties.php | 201 + .../PhpPresentation/Reader/ODPresentation.php | 576 ++ .../PhpPresentation/Reader/PowerPoint2007.php | 1314 ++++ .../PhpPresentation/Reader/PowerPoint97.php | 3472 +++++++++++ .../Reader/ReaderInterface.php | 41 + .../PhpPresentation/Reader/Serialized.php | 113 + .../PhpPresentation/Shape/AbstractGraphic.php | 247 + PhpOffice/PhpPresentation/Shape/Chart.php | 172 + .../PhpPresentation/Shape/Chart/Axis.php | 416 ++ .../PhpPresentation/Shape/Chart/Gridlines.php | 36 + .../PhpPresentation/Shape/Chart/Legend.php | 384 ++ .../PhpPresentation/Shape/Chart/Marker.php | 94 + .../PhpPresentation/Shape/Chart/PlotArea.php | 277 + .../PhpPresentation/Shape/Chart/Series.php | 598 ++ .../PhpPresentation/Shape/Chart/Title.php | 325 + .../Shape/Chart/Type/AbstractType.php | 171 + .../Shape/Chart/Type/AbstractTypeBar.php | 140 + .../Shape/Chart/Type/AbstractTypePie.php | 76 + .../PhpPresentation/Shape/Chart/Type/Area.php | 40 + .../PhpPresentation/Shape/Chart/Type/Bar.php | 36 + .../Shape/Chart/Type/Bar3D.php | 36 + .../Shape/Chart/Type/Doughnut.php | 67 + .../PhpPresentation/Shape/Chart/Type/Line.php | 40 + .../PhpPresentation/Shape/Chart/Type/Pie.php | 36 + .../Shape/Chart/Type/Pie3D.php | 36 + .../Shape/Chart/Type/Scatter.php | 40 + .../PhpPresentation/Shape/Chart/View3D.php | 258 + PhpOffice/PhpPresentation/Shape/Comment.php | 145 + .../PhpPresentation/Shape/Comment/Author.php | 85 + PhpOffice/PhpPresentation/Shape/Drawing.php | 28 + .../Shape/Drawing/AbstractDrawingAdapter.php | 28 + .../PhpPresentation/Shape/Drawing/Base64.php | 103 + .../PhpPresentation/Shape/Drawing/File.php | 91 + .../PhpPresentation/Shape/Drawing/Gd.php | 163 + .../PhpPresentation/Shape/Drawing/ZipFile.php | 109 + PhpOffice/PhpPresentation/Shape/Group.php | 268 + PhpOffice/PhpPresentation/Shape/Hyperlink.php | 189 + PhpOffice/PhpPresentation/Shape/Line.php | 57 + PhpOffice/PhpPresentation/Shape/Media.php | 49 + .../PhpPresentation/Shape/MemoryDrawing.php | 28 + .../PhpPresentation/Shape/Placeholder.php | 95 + PhpOffice/PhpPresentation/Shape/RichText.php | 710 +++ .../Shape/RichText/BreakElement.php | 93 + .../Shape/RichText/Paragraph.php | 327 + .../PhpPresentation/Shape/RichText/Run.php | 79 + .../Shape/RichText/TextElement.php | 158 + .../Shape/RichText/TextElementInterface.php | 64 + PhpOffice/PhpPresentation/Shape/Table.php | 135 + .../PhpPresentation/Shape/Table/Cell.php | 443 ++ PhpOffice/PhpPresentation/Shape/Table/Row.php | 212 + .../ShapeContainerInterface.php | 67 + PhpOffice/PhpPresentation/Slide.php | 246 + .../Slide/AbstractBackground.php | 8 + .../PhpPresentation/Slide/AbstractSlide.php | 390 ++ PhpOffice/PhpPresentation/Slide/Animation.php | 40 + .../Slide/Background/Color.php | 32 + .../Slide/Background/Image.php | 97 + .../Slide/Background/SchemeColor.php | 32 + PhpOffice/PhpPresentation/Slide/Iterator.php | 108 + PhpOffice/PhpPresentation/Slide/Layout.php | 37 + PhpOffice/PhpPresentation/Slide/Note.php | 261 + .../PhpPresentation/Slide/SlideLayout.php | 99 + .../PhpPresentation/Slide/SlideMaster.php | 170 + .../PhpPresentation/Slide/Transition.php | 166 + PhpOffice/PhpPresentation/Style/Alignment.php | 394 ++ PhpOffice/PhpPresentation/Style/Border.php | 235 + PhpOffice/PhpPresentation/Style/Borders.php | 195 + PhpOffice/PhpPresentation/Style/Bullet.php | 317 + PhpOffice/PhpPresentation/Style/Color.php | 197 + PhpOffice/PhpPresentation/Style/ColorMap.php | 103 + PhpOffice/PhpPresentation/Style/Fill.php | 236 + PhpOffice/PhpPresentation/Style/Font.php | 450 ++ PhpOffice/PhpPresentation/Style/Outline.php | 79 + .../PhpPresentation/Style/SchemeColor.php | 39 + PhpOffice/PhpPresentation/Style/Shadow.php | 309 + PhpOffice/PhpPresentation/Style/TextStyle.php | 186 + .../Writer/AbstractDecoratorWriter.php | 84 + .../PhpPresentation/Writer/AbstractWriter.php | 142 + .../PhpPresentation/Writer/ODPresentation.php | 186 + .../AbstractDecoratorWriter.php | 31 + .../Writer/ODPresentation/Content.php | 1388 +++++ .../Writer/ODPresentation/Meta.php | 65 + .../Writer/ODPresentation/MetaInfManifest.php | 109 + .../Writer/ODPresentation/Mimetype.php | 18 + .../Writer/ODPresentation/ObjectsChart.php | 979 +++ .../Writer/ODPresentation/Pictures.php | 38 + .../Writer/ODPresentation/Styles.php | 327 + .../ODPresentation/ThumbnailsThumbnail.php | 42 + .../PhpPresentation/Writer/PowerPoint2007.php | 211 + .../AbstractDecoratorWriter.php | 311 + .../Writer/PowerPoint2007/AbstractSlide.php | 1611 +++++ .../Writer/PowerPoint2007/CommentAuthors.php | 76 + .../Writer/PowerPoint2007/ContentTypes.php | 182 + .../Writer/PowerPoint2007/DocPropsApp.php | 103 + .../Writer/PowerPoint2007/DocPropsCore.php | 74 + .../Writer/PowerPoint2007/DocPropsCustom.php | 48 + .../PowerPoint2007/DocPropsThumbnail.php | 32 + .../LayoutPack/AbstractLayoutPack.php | 225 + .../PowerPoint2007/LayoutPack/PackDefault.php | 3281 ++++++++++ .../LayoutPack/TemplateBased.php | 203 + .../Writer/PowerPoint2007/PptCharts.php | 2358 +++++++ .../Writer/PowerPoint2007/PptComments.php | 85 + .../Writer/PowerPoint2007/PptMedia.php | 25 + .../Writer/PowerPoint2007/PptPresProps.php | 72 + .../Writer/PowerPoint2007/PptPresentation.php | 183 + .../Writer/PowerPoint2007/PptSlideLayouts.php | 179 + .../Writer/PowerPoint2007/PptSlideMasters.php | 238 + .../Writer/PowerPoint2007/PptSlides.php | 872 +++ .../Writer/PowerPoint2007/PptTableProps.php | 30 + .../Writer/PowerPoint2007/PptTheme.php | 857 +++ .../Writer/PowerPoint2007/PptViewProps.php | 74 + .../Writer/PowerPoint2007/Relationships.php | 129 + .../PhpPresentation/Writer/Serialized.php | 131 + .../Writer/WriterInterface.php | 32 + .../PhpWord/Collection/AbstractCollection.php | 95 + PhpOffice/PhpWord/Collection/Bookmarks.php | 27 + PhpOffice/PhpWord/Collection/Charts.php | 27 + PhpOffice/PhpWord/Collection/Comments.php | 27 + PhpOffice/PhpWord/Collection/Endnotes.php | 27 + PhpOffice/PhpWord/Collection/Footnotes.php | 27 + PhpOffice/PhpWord/Collection/Titles.php | 27 + .../ComplexType/FootnoteProperties.php | 181 + PhpOffice/PhpWord/ComplexType/ProofState.php | 106 + PhpOffice/PhpWord/ComplexType/TblWidth.php | 59 + .../PhpWord/ComplexType/TrackChangesView.php | 166 + .../PhpWord/Element/AbstractContainer.php | 315 + PhpOffice/PhpWord/Element/AbstractElement.php | 507 ++ PhpOffice/PhpWord/Element/Bookmark.php | 60 + PhpOffice/PhpWord/Element/Cell.php | 77 + PhpOffice/PhpWord/Element/Chart.php | 129 + PhpOffice/PhpWord/Element/CheckBox.php | 72 + PhpOffice/PhpWord/Element/Comment.php | 122 + PhpOffice/PhpWord/Element/Endnote.php | 41 + PhpOffice/PhpWord/Element/Field.php | 299 + PhpOffice/PhpWord/Element/Footer.php | 116 + PhpOffice/PhpWord/Element/Footnote.php | 89 + PhpOffice/PhpWord/Element/FormField.php | 195 + PhpOffice/PhpWord/Element/Header.php | 41 + PhpOffice/PhpWord/Element/Image.php | 602 ++ PhpOffice/PhpWord/Element/Line.php | 53 + PhpOffice/PhpWord/Element/Link.php | 180 + PhpOffice/PhpWord/Element/ListItem.php | 111 + PhpOffice/PhpWord/Element/ListItemRun.php | 85 + PhpOffice/PhpWord/Element/OLEObject.php | 169 + PhpOffice/PhpWord/Element/PageBreak.php | 31 + PhpOffice/PhpWord/Element/PreserveText.php | 98 + PhpOffice/PhpWord/Element/Row.php | 107 + PhpOffice/PhpWord/Element/SDT.php | 190 + PhpOffice/PhpWord/Element/Section.php | 305 + PhpOffice/PhpWord/Element/Shape.php | 88 + PhpOffice/PhpWord/Element/TOC.php | 169 + PhpOffice/PhpWord/Element/Table.php | 174 + PhpOffice/PhpWord/Element/Text.php | 153 + PhpOffice/PhpWord/Element/TextBox.php | 60 + PhpOffice/PhpWord/Element/TextBreak.php | 130 + PhpOffice/PhpWord/Element/TextRun.php | 80 + PhpOffice/PhpWord/Element/Title.php | 108 + PhpOffice/PhpWord/Element/TrackChange.php | 101 + PhpOffice/PhpWord/Escaper/AbstractEscaper.php | 46 + .../PhpWord/Escaper/EscaperInterface.php | 33 + PhpOffice/PhpWord/Escaper/RegExp.php | 33 + PhpOffice/PhpWord/Escaper/Rtf.php | 97 + PhpOffice/PhpWord/Escaper/Xml.php | 32 + .../PhpWord/Exception/CopyFileException.php | 39 + .../CreateTemporaryFileException.php | 37 + PhpOffice/PhpWord/Exception/Exception.php | 25 + .../Exception/InvalidImageException.php | 25 + .../Exception/InvalidObjectException.php | 25 + .../Exception/InvalidStyleException.php | 27 + .../UnsupportedImageTypeException.php | 25 + PhpOffice/PhpWord/IOFactory.php | 108 + PhpOffice/PhpWord/Media.php | 365 ++ PhpOffice/PhpWord/Metadata/Compatibility.php | 62 + PhpOffice/PhpWord/Metadata/DocInfo.php | 581 ++ PhpOffice/PhpWord/Metadata/Protection.php | 199 + PhpOffice/PhpWord/Metadata/Settings.php | 480 ++ PhpOffice/PhpWord/PhpWord.php | 425 ++ PhpOffice/PhpWord/Reader/AbstractReader.php | 124 + PhpOffice/PhpWord/Reader/HTML.php | 52 + PhpOffice/PhpWord/Reader/MsDoc.php | 2356 +++++++ PhpOffice/PhpWord/Reader/ODText.php | 94 + .../PhpWord/Reader/ODText/AbstractPart.php | 30 + PhpOffice/PhpWord/Reader/ODText/Content.php | 112 + PhpOffice/PhpWord/Reader/ODText/Meta.php | 78 + PhpOffice/PhpWord/Reader/RTF.php | 53 + PhpOffice/PhpWord/Reader/RTF/Document.php | 394 ++ PhpOffice/PhpWord/Reader/ReaderInterface.php | 41 + PhpOffice/PhpWord/Reader/Word2007.php | 172 + .../PhpWord/Reader/Word2007/AbstractPart.php | 733 +++ .../PhpWord/Reader/Word2007/DocPropsApp.php | 40 + .../PhpWord/Reader/Word2007/DocPropsCore.php | 83 + .../Reader/Word2007/DocPropsCustom.php | 55 + .../PhpWord/Reader/Word2007/Document.php | 183 + .../PhpWord/Reader/Word2007/Endnotes.php | 40 + .../PhpWord/Reader/Word2007/Footnotes.php | 97 + .../PhpWord/Reader/Word2007/Numbering.php | 122 + .../PhpWord/Reader/Word2007/Settings.php | 201 + PhpOffice/PhpWord/Reader/Word2007/Styles.php | 106 + PhpOffice/PhpWord/Settings.php | 446 ++ PhpOffice/PhpWord/Shared/AbstractEnum.php | 75 + PhpOffice/PhpWord/Shared/Converter.php | 426 ++ PhpOffice/PhpWord/Shared/Html.php | 811 +++ PhpOffice/PhpWord/Shared/OLERead.php | 323 + .../PhpWord/Shared/PCLZip/pclzip.lib.php | 5415 +++++++++++++++++ PhpOffice/PhpWord/Shared/ZipArchive.php | 399 ++ PhpOffice/PhpWord/SimpleType/DocProtect.php | 55 + PhpOffice/PhpWord/SimpleType/Jc.php | 67 + PhpOffice/PhpWord/SimpleType/JcTable.php | 34 + .../PhpWord/SimpleType/LineSpacingRule.php | 45 + PhpOffice/PhpWord/SimpleType/NumberFormat.php | 151 + PhpOffice/PhpWord/SimpleType/TblWidth.php | 42 + .../PhpWord/SimpleType/TextAlignment.php | 45 + PhpOffice/PhpWord/SimpleType/VerticalJc.php | 36 + PhpOffice/PhpWord/SimpleType/Zoom.php | 42 + PhpOffice/PhpWord/Style.php | 206 + PhpOffice/PhpWord/Style/AbstractStyle.php | 354 ++ PhpOffice/PhpWord/Style/Border.php | 489 ++ PhpOffice/PhpWord/Style/Cell.php | 340 ++ PhpOffice/PhpWord/Style/Chart.php | 492 ++ PhpOffice/PhpWord/Style/Extrusion.php | 106 + PhpOffice/PhpWord/Style/Fill.php | 69 + PhpOffice/PhpWord/Style/Font.php | 995 +++ PhpOffice/PhpWord/Style/Frame.php | 692 +++ PhpOffice/PhpWord/Style/Image.php | 278 + PhpOffice/PhpWord/Style/Indentation.php | 157 + PhpOffice/PhpWord/Style/Language.php | 241 + PhpOffice/PhpWord/Style/Line.php | 281 + PhpOffice/PhpWord/Style/LineNumbering.php | 164 + PhpOffice/PhpWord/Style/ListItem.php | 280 + PhpOffice/PhpWord/Style/Numbering.php | 131 + PhpOffice/PhpWord/Style/NumberingLevel.php | 458 ++ PhpOffice/PhpWord/Style/Outline.php | 308 + PhpOffice/PhpWord/Style/Paper.php | 194 + PhpOffice/PhpWord/Style/Paragraph.php | 874 +++ PhpOffice/PhpWord/Style/Row.php | 159 + PhpOffice/PhpWord/Style/Section.php | 636 ++ PhpOffice/PhpWord/Style/Shading.php | 146 + PhpOffice/PhpWord/Style/Shadow.php | 97 + PhpOffice/PhpWord/Style/Shape.php | 255 + PhpOffice/PhpWord/Style/Spacing.php | 187 + PhpOffice/PhpWord/Style/TOC.php | 116 + PhpOffice/PhpWord/Style/Tab.php | 173 + PhpOffice/PhpWord/Style/Table.php | 810 +++ PhpOffice/PhpWord/Style/TablePosition.php | 410 ++ PhpOffice/PhpWord/Style/TextBox.php | 230 + PhpOffice/PhpWord/Template.php | 27 + PhpOffice/PhpWord/TemplateProcessor.php | 1252 ++++ PhpOffice/PhpWord/Writer/AbstractWriter.php | 436 ++ PhpOffice/PhpWord/Writer/HTML.php | 139 + .../Writer/HTML/Element/AbstractElement.php | 86 + .../PhpWord/Writer/HTML/Element/Bookmark.php | 45 + .../PhpWord/Writer/HTML/Element/Container.php | 64 + .../PhpWord/Writer/HTML/Element/Endnote.php | 33 + .../PhpWord/Writer/HTML/Element/Footnote.php | 55 + .../PhpWord/Writer/HTML/Element/Image.php | 54 + .../PhpWord/Writer/HTML/Element/Link.php | 51 + .../PhpWord/Writer/HTML/Element/ListItem.php | 48 + .../Writer/HTML/Element/ListItemRun.php | 43 + .../PhpWord/Writer/HTML/Element/PageBreak.php | 44 + .../PhpWord/Writer/HTML/Element/Table.php | 141 + .../PhpWord/Writer/HTML/Element/Text.php | 265 + .../PhpWord/Writer/HTML/Element/TextBreak.php | 42 + .../PhpWord/Writer/HTML/Element/TextRun.php | 43 + .../PhpWord/Writer/HTML/Element/Title.php | 56 + .../PhpWord/Writer/HTML/Part/AbstractPart.php | 69 + PhpOffice/PhpWord/Writer/HTML/Part/Body.php | 89 + PhpOffice/PhpWord/Writer/HTML/Part/Head.php | 138 + .../Writer/HTML/Style/AbstractStyle.php | 125 + PhpOffice/PhpWord/Writer/HTML/Style/Font.php | 70 + .../PhpWord/Writer/HTML/Style/Generic.php | 43 + PhpOffice/PhpWord/Writer/HTML/Style/Image.php | 47 + .../PhpWord/Writer/HTML/Style/Paragraph.php | 85 + PhpOffice/PhpWord/Writer/ODText.php | 89 + .../Writer/ODText/Element/AbstractElement.php | 29 + .../Writer/ODText/Element/Container.php | 35 + .../PhpWord/Writer/ODText/Element/Image.php | 68 + .../PhpWord/Writer/ODText/Element/Link.php | 52 + .../Writer/ODText/Element/PageBreak.php | 36 + .../PhpWord/Writer/ODText/Element/Table.php | 98 + .../PhpWord/Writer/ODText/Element/Text.php | 101 + .../Writer/ODText/Element/TextBreak.php | 38 + .../PhpWord/Writer/ODText/Element/TextRun.php | 42 + .../PhpWord/Writer/ODText/Element/Title.php | 49 + .../Writer/ODText/Part/AbstractPart.php | 109 + .../PhpWord/Writer/ODText/Part/Content.php | 302 + .../PhpWord/Writer/ODText/Part/Manifest.php | 71 + PhpOffice/PhpWord/Writer/ODText/Part/Meta.php | 105 + .../PhpWord/Writer/ODText/Part/Mimetype.php | 34 + .../PhpWord/Writer/ODText/Part/Styles.php | 203 + .../Writer/ODText/Style/AbstractStyle.php | 29 + .../PhpWord/Writer/ODText/Style/Font.php | 92 + .../PhpWord/Writer/ODText/Style/Image.php | 49 + .../PhpWord/Writer/ODText/Style/Paragraph.php | 65 + .../PhpWord/Writer/ODText/Style/Section.php | 51 + .../PhpWord/Writer/ODText/Style/Table.php | 64 + PhpOffice/PhpWord/Writer/PDF.php | 79 + .../PhpWord/Writer/PDF/AbstractRenderer.php | 206 + PhpOffice/PhpWord/Writer/PDF/DomPDF.php | 62 + PhpOffice/PhpWord/Writer/PDF/MPDF.php | 99 + PhpOffice/PhpWord/Writer/PDF/TCPDF.php | 75 + PhpOffice/PhpWord/Writer/RTF.php | 126 + .../Writer/RTF/Element/AbstractElement.php | 180 + .../PhpWord/Writer/RTF/Element/Container.php | 35 + .../PhpWord/Writer/RTF/Element/Field.php | 80 + .../PhpWord/Writer/RTF/Element/Image.php | 57 + PhpOffice/PhpWord/Writer/RTF/Element/Link.php | 50 + .../PhpWord/Writer/RTF/Element/ListItem.php | 27 + .../PhpWord/Writer/RTF/Element/PageBreak.php | 36 + .../PhpWord/Writer/RTF/Element/Table.php | 146 + PhpOffice/PhpWord/Writer/RTF/Element/Text.php | 53 + .../PhpWord/Writer/RTF/Element/TextBreak.php | 40 + .../PhpWord/Writer/RTF/Element/TextRun.php | 46 + .../PhpWord/Writer/RTF/Element/Title.php | 92 + .../PhpWord/Writer/RTF/Part/AbstractPart.php | 68 + .../PhpWord/Writer/RTF/Part/Document.php | 231 + PhpOffice/PhpWord/Writer/RTF/Part/Header.php | 255 + .../Writer/RTF/Style/AbstractStyle.php | 29 + PhpOffice/PhpWord/Writer/RTF/Style/Border.php | 122 + PhpOffice/PhpWord/Writer/RTF/Style/Font.php | 89 + .../PhpWord/Writer/RTF/Style/Indentation.php | 45 + .../PhpWord/Writer/RTF/Style/Paragraph.php | 130 + .../PhpWord/Writer/RTF/Style/Section.php | 70 + PhpOffice/PhpWord/Writer/RTF/Style/Tab.php | 49 + PhpOffice/PhpWord/Writer/Word2007.php | 334 + .../Word2007/Element/AbstractElement.php | 227 + .../Writer/Word2007/Element/Bookmark.php | 49 + .../PhpWord/Writer/Word2007/Element/Chart.php | 76 + .../Writer/Word2007/Element/CheckBox.php | 90 + .../Writer/Word2007/Element/Container.php | 92 + .../Writer/Word2007/Element/Endnote.php | 33 + .../PhpWord/Writer/Word2007/Element/Field.php | 215 + .../Writer/Word2007/Element/Footnote.php | 60 + .../Writer/Word2007/Element/FormField.php | 170 + .../PhpWord/Writer/Word2007/Element/Image.php | 127 + .../PhpWord/Writer/Word2007/Element/Line.php | 88 + .../PhpWord/Writer/Word2007/Element/Link.php | 62 + .../Writer/Word2007/Element/ListItem.php | 67 + .../Writer/Word2007/Element/ListItemRun.php | 87 + .../Writer/Word2007/Element/OLEObject.php | 88 + .../Writer/Word2007/Element/PageBreak.php | 44 + .../Word2007/Element/ParagraphAlignment.php | 60 + .../Writer/Word2007/Element/PreserveText.php | 91 + .../PhpWord/Writer/Word2007/Element/SDT.php | 137 + .../PhpWord/Writer/Word2007/Element/Shape.php | 169 + .../PhpWord/Writer/Word2007/Element/TOC.php | 208 + .../PhpWord/Writer/Word2007/Element/Table.php | 143 + .../Word2007/Element/TableAlignment.php | 60 + .../PhpWord/Writer/Word2007/Element/Text.php | 104 + .../Writer/Word2007/Element/TextBox.php | 73 + .../Writer/Word2007/Element/TextBreak.php | 53 + .../Writer/Word2007/Element/TextRun.php | 42 + .../PhpWord/Writer/Word2007/Element/Title.php | 83 + .../Writer/Word2007/Part/AbstractPart.php | 107 + .../PhpWord/Writer/Word2007/Part/Chart.php | 444 ++ .../PhpWord/Writer/Word2007/Part/Comments.php | 104 + .../Writer/Word2007/Part/ContentTypes.php | 98 + .../Writer/Word2007/Part/DocPropsApp.php | 51 + .../Writer/Word2007/Part/DocPropsCore.php | 70 + .../Writer/Word2007/Part/DocPropsCustom.php | 80 + .../PhpWord/Writer/Word2007/Part/Document.php | 161 + .../PhpWord/Writer/Word2007/Part/Endnotes.php | 52 + .../Writer/Word2007/Part/FontTable.php | 108 + .../PhpWord/Writer/Word2007/Part/Footer.php | 83 + .../Writer/Word2007/Part/Footnotes.php | 176 + .../PhpWord/Writer/Word2007/Part/Header.php | 31 + .../Writer/Word2007/Part/Numbering.php | 201 + .../PhpWord/Writer/Word2007/Part/Rels.php | 130 + .../Writer/Word2007/Part/RelsDocument.php | 50 + .../PhpWord/Writer/Word2007/Part/RelsPart.php | 59 + .../PhpWord/Writer/Word2007/Part/Settings.php | 328 + .../PhpWord/Writer/Word2007/Part/Styles.php | 286 + .../PhpWord/Writer/Word2007/Part/Theme.php | 422 ++ .../Writer/Word2007/Part/WebSettings.php | 51 + .../Writer/Word2007/Style/AbstractStyle.php | 156 + .../PhpWord/Writer/Word2007/Style/Cell.php | 104 + .../Writer/Word2007/Style/Extrusion.php | 44 + .../PhpWord/Writer/Word2007/Style/Fill.php | 41 + .../PhpWord/Writer/Word2007/Style/Font.php | 176 + .../PhpWord/Writer/Word2007/Style/Frame.php | 171 + .../PhpWord/Writer/Word2007/Style/Image.php | 27 + .../Writer/Word2007/Style/Indentation.php | 51 + .../PhpWord/Writer/Word2007/Style/Line.php | 68 + .../Writer/Word2007/Style/LineNumbering.php | 46 + .../Writer/Word2007/Style/MarginBorder.php | 150 + .../PhpWord/Writer/Word2007/Style/Outline.php | 49 + .../Writer/Word2007/Style/Paragraph.php | 212 + .../PhpWord/Writer/Word2007/Style/Row.php | 66 + .../PhpWord/Writer/Word2007/Style/Section.php | 101 + .../PhpWord/Writer/Word2007/Style/Shading.php | 44 + .../PhpWord/Writer/Word2007/Style/Shadow.php | 44 + .../PhpWord/Writer/Word2007/Style/Shape.php | 45 + .../PhpWord/Writer/Word2007/Style/Spacing.php | 57 + .../PhpWord/Writer/Word2007/Style/Tab.php | 44 + .../PhpWord/Writer/Word2007/Style/Table.php | 238 + .../Writer/Word2007/Style/TablePosition.php | 65 + .../PhpWord/Writer/Word2007/Style/TextBox.php | 61 + PhpOffice/PhpWord/Writer/WriterInterface.php | 31 + PhpOffice/PhpWord/resources/doc.png | Bin 0 -> 1157 bytes PhpOffice/PhpWord/resources/ppt.png | Bin 0 -> 1886 bytes PhpOffice/PhpWord/resources/xls.png | Bin 0 -> 1924 bytes 421 files changed, 84953 insertions(+) create mode 100644 PhpOffice/Common/Adapter/Zip/PclZipAdapter.php create mode 100644 PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php create mode 100644 PhpOffice/Common/Adapter/Zip/ZipInterface.php create mode 100644 PhpOffice/Common/Autoloader.php create mode 100644 PhpOffice/Common/Drawing.php create mode 100644 PhpOffice/Common/File.php create mode 100644 PhpOffice/Common/Font.php create mode 100644 PhpOffice/Common/Microsoft/OLERead.php create mode 100644 PhpOffice/Common/Microsoft/PasswordEncoder.php create mode 100644 PhpOffice/Common/Text.php create mode 100644 PhpOffice/Common/XMLReader.php create mode 100644 PhpOffice/Common/XMLWriter.php create mode 100644 PhpOffice/PhpPresentation/AbstractShape.php create mode 100644 PhpOffice/PhpPresentation/Autoloader.php create mode 100644 PhpOffice/PhpPresentation/ComparableInterface.php create mode 100644 PhpOffice/PhpPresentation/DocumentLayout.php create mode 100644 PhpOffice/PhpPresentation/DocumentProperties.php create mode 100644 PhpOffice/PhpPresentation/GeometryCalculator.php create mode 100644 PhpOffice/PhpPresentation/HashTable.php create mode 100644 PhpOffice/PhpPresentation/IOFactory.php create mode 100644 PhpOffice/PhpPresentation/PhpPresentation.php create mode 100644 PhpOffice/PhpPresentation/PresentationProperties.php create mode 100644 PhpOffice/PhpPresentation/Reader/ODPresentation.php create mode 100644 PhpOffice/PhpPresentation/Reader/PowerPoint2007.php create mode 100644 PhpOffice/PhpPresentation/Reader/PowerPoint97.php create mode 100644 PhpOffice/PhpPresentation/Reader/ReaderInterface.php create mode 100644 PhpOffice/PhpPresentation/Reader/Serialized.php create mode 100644 PhpOffice/PhpPresentation/Shape/AbstractGraphic.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Axis.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Legend.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Marker.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Series.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Title.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Bar3D.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Doughnut.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Pie3D.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Scatter.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/View3D.php create mode 100644 PhpOffice/PhpPresentation/Shape/Comment.php create mode 100644 PhpOffice/PhpPresentation/Shape/Comment/Author.php create mode 100644 PhpOffice/PhpPresentation/Shape/Drawing.php create mode 100644 PhpOffice/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php create mode 100644 PhpOffice/PhpPresentation/Shape/Drawing/Base64.php create mode 100644 PhpOffice/PhpPresentation/Shape/Drawing/File.php create mode 100644 PhpOffice/PhpPresentation/Shape/Drawing/Gd.php create mode 100644 PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php create mode 100644 PhpOffice/PhpPresentation/Shape/Group.php create mode 100644 PhpOffice/PhpPresentation/Shape/Hyperlink.php create mode 100644 PhpOffice/PhpPresentation/Shape/Line.php create mode 100644 PhpOffice/PhpPresentation/Shape/Media.php create mode 100644 PhpOffice/PhpPresentation/Shape/MemoryDrawing.php create mode 100644 PhpOffice/PhpPresentation/Shape/Placeholder.php create mode 100644 PhpOffice/PhpPresentation/Shape/RichText.php create mode 100644 PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php create mode 100644 PhpOffice/PhpPresentation/Shape/RichText/Paragraph.php create mode 100644 PhpOffice/PhpPresentation/Shape/RichText/Run.php create mode 100644 PhpOffice/PhpPresentation/Shape/RichText/TextElement.php create mode 100644 PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php create mode 100644 PhpOffice/PhpPresentation/Shape/Table.php create mode 100644 PhpOffice/PhpPresentation/Shape/Table/Cell.php create mode 100644 PhpOffice/PhpPresentation/Shape/Table/Row.php create mode 100644 PhpOffice/PhpPresentation/ShapeContainerInterface.php create mode 100644 PhpOffice/PhpPresentation/Slide.php create mode 100644 PhpOffice/PhpPresentation/Slide/AbstractBackground.php create mode 100644 PhpOffice/PhpPresentation/Slide/AbstractSlide.php create mode 100644 PhpOffice/PhpPresentation/Slide/Animation.php create mode 100644 PhpOffice/PhpPresentation/Slide/Background/Color.php create mode 100644 PhpOffice/PhpPresentation/Slide/Background/Image.php create mode 100644 PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php create mode 100644 PhpOffice/PhpPresentation/Slide/Iterator.php create mode 100644 PhpOffice/PhpPresentation/Slide/Layout.php create mode 100644 PhpOffice/PhpPresentation/Slide/Note.php create mode 100644 PhpOffice/PhpPresentation/Slide/SlideLayout.php create mode 100644 PhpOffice/PhpPresentation/Slide/SlideMaster.php create mode 100644 PhpOffice/PhpPresentation/Slide/Transition.php create mode 100644 PhpOffice/PhpPresentation/Style/Alignment.php create mode 100644 PhpOffice/PhpPresentation/Style/Border.php create mode 100644 PhpOffice/PhpPresentation/Style/Borders.php create mode 100644 PhpOffice/PhpPresentation/Style/Bullet.php create mode 100644 PhpOffice/PhpPresentation/Style/Color.php create mode 100644 PhpOffice/PhpPresentation/Style/ColorMap.php create mode 100644 PhpOffice/PhpPresentation/Style/Fill.php create mode 100644 PhpOffice/PhpPresentation/Style/Font.php create mode 100644 PhpOffice/PhpPresentation/Style/Outline.php create mode 100644 PhpOffice/PhpPresentation/Style/SchemeColor.php create mode 100644 PhpOffice/PhpPresentation/Style/Shadow.php create mode 100644 PhpOffice/PhpPresentation/Style/TextStyle.php create mode 100644 PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php create mode 100644 PhpOffice/PhpPresentation/Writer/AbstractWriter.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php create mode 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCustom.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/AbstractLayoutPack.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/PackDefault.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/TemplateBased.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTheme.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php create mode 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php create mode 100644 PhpOffice/PhpPresentation/Writer/Serialized.php create mode 100644 PhpOffice/PhpPresentation/Writer/WriterInterface.php create mode 100644 PhpOffice/PhpWord/Collection/AbstractCollection.php create mode 100644 PhpOffice/PhpWord/Collection/Bookmarks.php create mode 100644 PhpOffice/PhpWord/Collection/Charts.php create mode 100644 PhpOffice/PhpWord/Collection/Comments.php create mode 100644 PhpOffice/PhpWord/Collection/Endnotes.php create mode 100644 PhpOffice/PhpWord/Collection/Footnotes.php create mode 100644 PhpOffice/PhpWord/Collection/Titles.php create mode 100644 PhpOffice/PhpWord/ComplexType/FootnoteProperties.php create mode 100644 PhpOffice/PhpWord/ComplexType/ProofState.php create mode 100644 PhpOffice/PhpWord/ComplexType/TblWidth.php create mode 100644 PhpOffice/PhpWord/ComplexType/TrackChangesView.php create mode 100644 PhpOffice/PhpWord/Element/AbstractContainer.php create mode 100644 PhpOffice/PhpWord/Element/AbstractElement.php create mode 100644 PhpOffice/PhpWord/Element/Bookmark.php create mode 100644 PhpOffice/PhpWord/Element/Cell.php create mode 100644 PhpOffice/PhpWord/Element/Chart.php create mode 100644 PhpOffice/PhpWord/Element/CheckBox.php create mode 100644 PhpOffice/PhpWord/Element/Comment.php create mode 100644 PhpOffice/PhpWord/Element/Endnote.php create mode 100644 PhpOffice/PhpWord/Element/Field.php create mode 100644 PhpOffice/PhpWord/Element/Footer.php create mode 100644 PhpOffice/PhpWord/Element/Footnote.php create mode 100644 PhpOffice/PhpWord/Element/FormField.php create mode 100644 PhpOffice/PhpWord/Element/Header.php create mode 100644 PhpOffice/PhpWord/Element/Image.php create mode 100644 PhpOffice/PhpWord/Element/Line.php create mode 100644 PhpOffice/PhpWord/Element/Link.php create mode 100644 PhpOffice/PhpWord/Element/ListItem.php create mode 100644 PhpOffice/PhpWord/Element/ListItemRun.php create mode 100644 PhpOffice/PhpWord/Element/OLEObject.php create mode 100644 PhpOffice/PhpWord/Element/PageBreak.php create mode 100644 PhpOffice/PhpWord/Element/PreserveText.php create mode 100644 PhpOffice/PhpWord/Element/Row.php create mode 100644 PhpOffice/PhpWord/Element/SDT.php create mode 100644 PhpOffice/PhpWord/Element/Section.php create mode 100644 PhpOffice/PhpWord/Element/Shape.php create mode 100644 PhpOffice/PhpWord/Element/TOC.php create mode 100644 PhpOffice/PhpWord/Element/Table.php create mode 100644 PhpOffice/PhpWord/Element/Text.php create mode 100644 PhpOffice/PhpWord/Element/TextBox.php create mode 100644 PhpOffice/PhpWord/Element/TextBreak.php create mode 100644 PhpOffice/PhpWord/Element/TextRun.php create mode 100644 PhpOffice/PhpWord/Element/Title.php create mode 100644 PhpOffice/PhpWord/Element/TrackChange.php create mode 100644 PhpOffice/PhpWord/Escaper/AbstractEscaper.php create mode 100644 PhpOffice/PhpWord/Escaper/EscaperInterface.php create mode 100644 PhpOffice/PhpWord/Escaper/RegExp.php create mode 100644 PhpOffice/PhpWord/Escaper/Rtf.php create mode 100644 PhpOffice/PhpWord/Escaper/Xml.php create mode 100644 PhpOffice/PhpWord/Exception/CopyFileException.php create mode 100644 PhpOffice/PhpWord/Exception/CreateTemporaryFileException.php create mode 100644 PhpOffice/PhpWord/Exception/Exception.php create mode 100644 PhpOffice/PhpWord/Exception/InvalidImageException.php create mode 100644 PhpOffice/PhpWord/Exception/InvalidObjectException.php create mode 100644 PhpOffice/PhpWord/Exception/InvalidStyleException.php create mode 100644 PhpOffice/PhpWord/Exception/UnsupportedImageTypeException.php create mode 100644 PhpOffice/PhpWord/IOFactory.php create mode 100644 PhpOffice/PhpWord/Media.php create mode 100644 PhpOffice/PhpWord/Metadata/Compatibility.php create mode 100644 PhpOffice/PhpWord/Metadata/DocInfo.php create mode 100644 PhpOffice/PhpWord/Metadata/Protection.php create mode 100644 PhpOffice/PhpWord/Metadata/Settings.php create mode 100644 PhpOffice/PhpWord/PhpWord.php create mode 100644 PhpOffice/PhpWord/Reader/AbstractReader.php create mode 100644 PhpOffice/PhpWord/Reader/HTML.php create mode 100644 PhpOffice/PhpWord/Reader/MsDoc.php create mode 100644 PhpOffice/PhpWord/Reader/ODText.php create mode 100644 PhpOffice/PhpWord/Reader/ODText/AbstractPart.php create mode 100644 PhpOffice/PhpWord/Reader/ODText/Content.php create mode 100644 PhpOffice/PhpWord/Reader/ODText/Meta.php create mode 100644 PhpOffice/PhpWord/Reader/RTF.php create mode 100644 PhpOffice/PhpWord/Reader/RTF/Document.php create mode 100644 PhpOffice/PhpWord/Reader/ReaderInterface.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/Document.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/Endnotes.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/Footnotes.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/Numbering.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/Settings.php create mode 100644 PhpOffice/PhpWord/Reader/Word2007/Styles.php create mode 100644 PhpOffice/PhpWord/Settings.php create mode 100644 PhpOffice/PhpWord/Shared/AbstractEnum.php create mode 100644 PhpOffice/PhpWord/Shared/Converter.php create mode 100644 PhpOffice/PhpWord/Shared/Html.php create mode 100644 PhpOffice/PhpWord/Shared/OLERead.php create mode 100644 PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php create mode 100644 PhpOffice/PhpWord/Shared/ZipArchive.php create mode 100644 PhpOffice/PhpWord/SimpleType/DocProtect.php create mode 100644 PhpOffice/PhpWord/SimpleType/Jc.php create mode 100644 PhpOffice/PhpWord/SimpleType/JcTable.php create mode 100644 PhpOffice/PhpWord/SimpleType/LineSpacingRule.php create mode 100644 PhpOffice/PhpWord/SimpleType/NumberFormat.php create mode 100644 PhpOffice/PhpWord/SimpleType/TblWidth.php create mode 100644 PhpOffice/PhpWord/SimpleType/TextAlignment.php create mode 100644 PhpOffice/PhpWord/SimpleType/VerticalJc.php create mode 100644 PhpOffice/PhpWord/SimpleType/Zoom.php create mode 100644 PhpOffice/PhpWord/Style.php create mode 100644 PhpOffice/PhpWord/Style/AbstractStyle.php create mode 100644 PhpOffice/PhpWord/Style/Border.php create mode 100644 PhpOffice/PhpWord/Style/Cell.php create mode 100644 PhpOffice/PhpWord/Style/Chart.php create mode 100644 PhpOffice/PhpWord/Style/Extrusion.php create mode 100644 PhpOffice/PhpWord/Style/Fill.php create mode 100644 PhpOffice/PhpWord/Style/Font.php create mode 100644 PhpOffice/PhpWord/Style/Frame.php create mode 100644 PhpOffice/PhpWord/Style/Image.php create mode 100644 PhpOffice/PhpWord/Style/Indentation.php create mode 100644 PhpOffice/PhpWord/Style/Language.php create mode 100644 PhpOffice/PhpWord/Style/Line.php create mode 100644 PhpOffice/PhpWord/Style/LineNumbering.php create mode 100644 PhpOffice/PhpWord/Style/ListItem.php create mode 100644 PhpOffice/PhpWord/Style/Numbering.php create mode 100644 PhpOffice/PhpWord/Style/NumberingLevel.php create mode 100644 PhpOffice/PhpWord/Style/Outline.php create mode 100644 PhpOffice/PhpWord/Style/Paper.php create mode 100644 PhpOffice/PhpWord/Style/Paragraph.php create mode 100644 PhpOffice/PhpWord/Style/Row.php create mode 100644 PhpOffice/PhpWord/Style/Section.php create mode 100644 PhpOffice/PhpWord/Style/Shading.php create mode 100644 PhpOffice/PhpWord/Style/Shadow.php create mode 100644 PhpOffice/PhpWord/Style/Shape.php create mode 100644 PhpOffice/PhpWord/Style/Spacing.php create mode 100644 PhpOffice/PhpWord/Style/TOC.php create mode 100644 PhpOffice/PhpWord/Style/Tab.php create mode 100644 PhpOffice/PhpWord/Style/Table.php create mode 100644 PhpOffice/PhpWord/Style/TablePosition.php create mode 100644 PhpOffice/PhpWord/Style/TextBox.php create mode 100644 PhpOffice/PhpWord/Template.php create mode 100644 PhpOffice/PhpWord/TemplateProcessor.php create mode 100644 PhpOffice/PhpWord/Writer/AbstractWriter.php create mode 100644 PhpOffice/PhpWord/Writer/HTML.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Container.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Footnote.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Image.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Link.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Table.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Text.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Element/Title.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Part/Body.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Part/Head.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Style/AbstractStyle.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Style/Font.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Style/Generic.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Style/Image.php create mode 100644 PhpOffice/PhpWord/Writer/HTML/Style/Paragraph.php create mode 100644 PhpOffice/PhpWord/Writer/ODText.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/AbstractElement.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/Container.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/Image.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/Link.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/PageBreak.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/Table.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/Text.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/TextBreak.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/TextRun.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/Title.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Part/AbstractPart.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Part/Content.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Part/Manifest.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Part/Meta.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Part/Mimetype.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Part/Styles.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Style/AbstractStyle.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Style/Font.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Style/Image.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Style/Paragraph.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Style/Section.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Style/Table.php create mode 100644 PhpOffice/PhpWord/Writer/PDF.php create mode 100644 PhpOffice/PhpWord/Writer/PDF/AbstractRenderer.php create mode 100644 PhpOffice/PhpWord/Writer/PDF/DomPDF.php create mode 100644 PhpOffice/PhpWord/Writer/PDF/MPDF.php create mode 100644 PhpOffice/PhpWord/Writer/PDF/TCPDF.php create mode 100644 PhpOffice/PhpWord/Writer/RTF.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/AbstractElement.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/Container.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/Field.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/Image.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/Link.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/ListItem.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/PageBreak.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/Table.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/Text.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/TextBreak.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/TextRun.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Element/Title.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Part/AbstractPart.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Part/Document.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Part/Header.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Style/AbstractStyle.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Style/Border.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Style/Font.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Style/Indentation.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Style/Paragraph.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Style/Section.php create mode 100644 PhpOffice/PhpWord/Writer/RTF/Style/Tab.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/AbstractElement.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Bookmark.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Chart.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/CheckBox.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Container.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Endnote.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Field.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Footnote.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/FormField.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Image.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Line.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Link.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/ListItem.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/ListItemRun.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/OLEObject.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/PageBreak.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/PreserveText.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/SDT.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Shape.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TOC.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Table.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TableAlignment.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Text.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TextBox.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TextBreak.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TextRun.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Title.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/AbstractPart.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Chart.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Comments.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/ContentTypes.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsApp.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCore.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCustom.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Document.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Endnotes.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/FontTable.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Footer.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Footnotes.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Header.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Numbering.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Rels.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/RelsDocument.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/RelsPart.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Settings.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Styles.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Theme.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Part/WebSettings.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/AbstractStyle.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Cell.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Extrusion.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Fill.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Font.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Frame.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Image.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Indentation.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Line.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/LineNumbering.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/MarginBorder.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Outline.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Paragraph.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Row.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Section.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Shading.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Shadow.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Shape.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Spacing.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Tab.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Table.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/TablePosition.php create mode 100644 PhpOffice/PhpWord/Writer/Word2007/Style/TextBox.php create mode 100644 PhpOffice/PhpWord/Writer/WriterInterface.php create mode 100644 PhpOffice/PhpWord/resources/doc.png create mode 100644 PhpOffice/PhpWord/resources/ppt.png create mode 100644 PhpOffice/PhpWord/resources/xls.png diff --git a/PhpOffice/Common/Adapter/Zip/PclZipAdapter.php b/PhpOffice/Common/Adapter/Zip/PclZipAdapter.php new file mode 100644 index 0000000..053531f --- /dev/null +++ b/PhpOffice/Common/Adapter/Zip/PclZipAdapter.php @@ -0,0 +1,45 @@ +oPclZip = new PclZip($filename); + $this->tmpDir = sys_get_temp_dir(); + return $this; + } + + public function close() + { + return $this; + } + + public function addFromString($localname, $contents) + { + $pathData = pathinfo($localname); + + $hFile = fopen($this->tmpDir.'/'.$pathData['basename'], "wb"); + fwrite($hFile, $contents); + fclose($hFile); + + $res = $this->oPclZip->add($this->tmpDir.'/'.$pathData['basename'], PCLZIP_OPT_REMOVE_PATH, $this->tmpDir, PCLZIP_OPT_ADD_PATH, $pathData['dirname']); + if ($res == 0) { + throw new \Exception("Error zipping files : " . $this->oPclZip->errorInfo(true)); + } + unlink($this->tmpDir.'/'.$pathData['basename']); + return $this; + } +} diff --git a/PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php b/PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php new file mode 100644 index 0000000..da2c346 --- /dev/null +++ b/PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php @@ -0,0 +1,49 @@ +filename = $filename; + $this->oZipArchive = new ZipArchive(); + + if ($this->oZipArchive->open($this->filename, ZipArchive::OVERWRITE) === true) { + return $this; + } + if ($this->oZipArchive->open($this->filename, ZipArchive::CREATE) === true) { + return $this; + } + throw new \Exception("Could not open $this->filename for writing."); + } + + public function close() + { + if ($this->oZipArchive->close() === false) { + throw new \Exception("Could not close zip file $this->filename."); + } + return $this; + } + + public function addFromString($localname, $contents) + { + if ($this->oZipArchive->addFromString($localname, $contents) === false) { + throw new \Exception("Error zipping files : " . $localname); + } + + return $this; + } +} diff --git a/PhpOffice/Common/Adapter/Zip/ZipInterface.php b/PhpOffice/Common/Adapter/Zip/ZipInterface.php new file mode 100644 index 0000000..e8dbc8c --- /dev/null +++ b/PhpOffice/Common/Adapter/Zip/ZipInterface.php @@ -0,0 +1,30 @@ +open($zipFile) === true) { + $returnValue = ($zip->getFromName($archiveFile) !== false); + $zip->close(); + + return $returnValue; + } + + return false; + } + + // Regular file_exists + return file_exists($pFilename); + } + /** + * Returns the content of a file + * + * @param string $pFilename Filename + * @return string + */ + public static function fileGetContents($pFilename) + { + if (!self::fileExists($pFilename)) { + return false; + } + if (strtolower(substr($pFilename, 0, 3)) == 'zip') { + // Open ZIP file and verify if the file exists + $zipFile = substr($pFilename, 6, strpos($pFilename, '#') - 6); + $archiveFile = substr($pFilename, strpos($pFilename, '#') + 1); + + $zip = new \ZipArchive(); + if ($zip->open($zipFile) === true) { + $returnValue = $zip->getFromName($archiveFile); + $zip->close(); + return $returnValue; + } + return false; + } + // Regular file contents + return file_get_contents($pFilename); + } + + /** + * Returns canonicalized absolute pathname, also for ZIP archives + * + * @param string $pFilename + * @return string + */ + public static function realpath($pFilename) + { + // Try using realpath() + $returnValue = realpath($pFilename); + + // Found something? + if ($returnValue == '' || is_null($returnValue)) { + $pathArray = explode('/', $pFilename); + while (in_array('..', $pathArray) && $pathArray[0] != '..') { + $numPathArray = count($pathArray); + for ($i = 0; $i < $numPathArray; ++$i) { + if ($pathArray[$i] == '..' && $i > 0) { + unset($pathArray[$i]); + unset($pathArray[$i - 1]); + break; + } + } + } + $returnValue = implode('/', $pathArray); + } + + // Return + return $returnValue; + } +} diff --git a/PhpOffice/Common/Font.php b/PhpOffice/Common/Font.php new file mode 100644 index 0000000..03a8e73 --- /dev/null +++ b/PhpOffice/Common/Font.php @@ -0,0 +1,100 @@ +data = file_get_contents($sFileName, false, null, 0, 8); + + // Check OLE identifier + if ($this->data != self::IDENTIFIER_OLE) { + throw new \Exception('The filename ' . $sFileName . ' is not recognised as an OLE file'); + } + + // Get the file data + $this->data = file_get_contents($sFileName); + + // Total number of sectors used for the SAT + $numBigBlkDepotBlks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); + + // SecID of the first sector of the directory stream + $rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS); + + // SecID of the first sector of the SSAT (or -2 if not extant) + $sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS); + + // SecID of the first sector of the MSAT (or -2 if no additional sectors are used) + $extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS); + + // Total number of sectors used by MSAT + $numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS); + + $bigBlockDepotBlocks = array(); + $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS; + + $bbdBlocks = $numBigBlkDepotBlks; + + if ($numExtensionBlocks != 0) { + $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4; + } + + for ($i = 0; $i < $bbdBlocks; ++$i) { + $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); + $pos += 4; + } + + for ($j = 0; $j < $numExtensionBlocks; ++$j) { + $pos = ($extensionBlock + 1) * self::BIG_BLOCK_SIZE; + $blocksToRead = min($numBigBlkDepotBlks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1); + + for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) { + $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); + $pos += 4; + } + + $bbdBlocks += $blocksToRead; + if ($bbdBlocks < $numBigBlkDepotBlks) { + $extensionBlock = self::getInt4d($this->data, $pos); + } + } + + $this->bigBlockChain = ''; + $bbs = self::BIG_BLOCK_SIZE / 4; + for ($i = 0; $i < $numBigBlkDepotBlks; ++$i) { + $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE; + + $this->bigBlockChain .= substr($this->data, $pos, 4*$bbs); + $pos += 4*$bbs; + } + + $sbdBlock = $sbdStartBlock; + $this->smallBlockChain = ''; + while ($sbdBlock != -2) { + $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE; + + $this->smallBlockChain .= substr($this->data, $pos, 4*$bbs); + $pos += 4*$bbs; + + $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock*4); + } + + // read the directory stream + $block = $rootStartBlock; + $this->entry = $this->readData($block); + + $this->readPropertySets(); + } + + /** + * Extract binary stream data + * + * @return string + */ + public function getStream($stream) + { + if ($stream === null) { + return null; + } + + $streamData = ''; + + if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) { + $rootdata = $this->readData($this->props[$this->rootEntry]['startBlock']); + + $block = $this->props[$stream]['startBlock']; + + while ($block != -2) { + $pos = $block * self::SMALL_BLOCK_SIZE; + $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE); + + $block = self::getInt4d($this->smallBlockChain, $block*4); + } + + return $streamData; + } + + $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE; + if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) { + ++$numBlocks; + } + + if ($numBlocks == 0) { + return ''; + } + + $block = $this->props[$stream]['startBlock']; + + while ($block != -2) { + $pos = ($block + 1) * self::BIG_BLOCK_SIZE; + $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); + $block = self::getInt4d($this->bigBlockChain, $block*4); + } + + return $streamData; + } + + /** + * Read a standard stream (by joining sectors using information from SAT) + * + * @param int $blID Sector ID where the stream starts + * @return string Data for standard stream + */ + private function readData($blID) + { + $block = $blID; + $data = ''; + + while ($block != -2) { + $pos = ($block + 1) * self::BIG_BLOCK_SIZE; + $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); + $block = self::getInt4d($this->bigBlockChain, $block*4); + } + return $data; + } + + /** + * Read entries in the directory stream. + */ + private function readPropertySets() + { + $offset = 0; + + // loop through entires, each entry is 128 bytes + $entryLen = strlen($this->entry); + while ($offset < $entryLen) { + // entry data (128 bytes) + $data = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE); + + // size in bytes of name + $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS+1]) << 8); + + // type of entry + $type = ord($data[self::TYPE_POS]); + + // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook) + // sectorID of first sector of the short-stream container stream, if this entry is root entry + $startBlock = self::getInt4d($data, self::START_BLOCK_POS); + + $size = self::getInt4d($data, self::SIZE_POS); + + $name = str_replace("\x00", "", substr($data, 0, $nameSize)); + if ($size > 0) { + $this->props[] = array ( + 'name' => $name, + 'type' => $type, + 'startBlock' => $startBlock, + 'size' => $size); + + // tmp helper to simplify checks + $upName = strtoupper($name); + + switch ($upName) { + case 'ROOT ENTRY': + case 'R': + $this->rootEntry = count($this->props) - 1; + break; + case chr(1).'COMPOBJ': + break; + case chr(1).'OLE': + break; + case chr(5).'SUMMARYINFORMATION': + $this->summaryInformation = count($this->props) - 1; + break; + case chr(5).'DOCUMENTSUMMARYINFORMATION': + $this->docSummaryInfos = count($this->props) - 1; + break; + case 'CURRENT USER': + $this->currentUser = count($this->props) - 1; + break; + case 'PICTURES': + $this->pictures = count($this->props) - 1; + break; + case 'POWERPOINT DOCUMENT': + $this->powerpointDocument = count($this->props) - 1; + break; + default: + throw new \Exception('OLE Block Not defined: $upName : '.$upName. ' - $name : "'.$name.'"'); + } + } + + $offset += self::PROPERTY_STORAGE_BLOCK_SIZE; + } + } + + /** + * Read 4 bytes of data at specified position + * + * @param string $data + * @param int $pos + * @return int + */ + private static function getInt4d($data, $pos) + { + // FIX: represent numbers correctly on 64-bit system + // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 + // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems + $or24 = ord($data[$pos + 3]); + if ($or24 >= 128) { + // negative number + $ord24 = -abs((256 - $or24) << 24); + } else { + $ord24 = ($or24 & 127) << 24; + } + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24; + } +} diff --git a/PhpOffice/Common/Microsoft/PasswordEncoder.php b/PhpOffice/Common/Microsoft/PasswordEncoder.php new file mode 100644 index 0000000..f620f4c --- /dev/null +++ b/PhpOffice/Common/Microsoft/PasswordEncoder.php @@ -0,0 +1,234 @@ + array(1, 'md2'), + self::ALGORITHM_MD4 => array(2, 'md4'), + self::ALGORITHM_MD5 => array(3, 'md5'), + self::ALGORITHM_SHA_1 => array(4, 'sha1'), + self::ALGORITHM_MAC => array(5, ''), // 'mac' -> not possible with hash() + self::ALGORITHM_RIPEMD => array(6, 'ripemd'), + self::ALGORITHM_RIPEMD_160 => array(7, 'ripemd160'), + self::ALGORITHM_HMAC => array(9, ''), //'hmac' -> not possible with hash() + self::ALGORITHM_SHA_256 => array(12, 'sha256'), + self::ALGORITHM_SHA_384 => array(13, 'sha384'), + self::ALGORITHM_SHA_512 => array(14, 'sha512'), + ); + + private static $initialCodeArray = array( + 0xE1F0, + 0x1D0F, + 0xCC9C, + 0x84C0, + 0x110C, + 0x0E10, + 0xF1CE, + 0x313E, + 0x1872, + 0xE139, + 0xD40F, + 0x84F9, + 0x280C, + 0xA96A, + 0x4EC3, + ); + + private static $encryptionMatrix = array( + array(0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09), + array(0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF), + array(0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0), + array(0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40), + array(0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5), + array(0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A), + array(0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9), + array(0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0), + array(0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC), + array(0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10), + array(0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168), + array(0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C), + array(0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD), + array(0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC), + array(0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4), + ); + + private static $passwordMaxLength = 15; + + /** + * Create a hashed password that MS Word will be able to work with + * @see https://blogs.msdn.microsoft.com/vsod/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0/ + * + * @param string $password + * @param string $algorithmName + * @param string $salt + * @param int $spinCount + * @return string + */ + public static function hashPassword($password, $algorithmName = self::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000) + { + $origEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + + $password = mb_substr($password, 0, min(self::$passwordMaxLength, mb_strlen($password))); + + // Get the single-byte values by iterating through the Unicode characters of the truncated password. + // For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte. + $passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); + $byteChars = array(); + + for ($i = 0; $i < mb_strlen($password); $i++) { + $byteChars[$i] = ord(substr($passUtf8, $i * 2, 1)); + + if ($byteChars[$i] == 0) { + $byteChars[$i] = ord(substr($passUtf8, $i * 2 + 1, 1)); + } + } + + // build low-order word and hig-order word and combine them + $combinedKey = self::buildCombinedKey($byteChars); + // build reversed hexadecimal string + $hex = str_pad(strtoupper(dechex($combinedKey & 0xFFFFFFFF)), 8, '0', \STR_PAD_LEFT); + $reversedHex = $hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1]; + + $generatedKey = mb_convert_encoding($reversedHex, 'UCS-2LE', 'UTF-8'); + + // Implementation Notes List: + // Word requires that the initial hash of the password with the salt not be considered in the count. + // The initial hash of salt + key is not included in the iteration count. + $algorithm = self::getAlgorithm($algorithmName); + $generatedKey = hash($algorithm, $salt . $generatedKey, true); + + for ($i = 0; $i < $spinCount; $i++) { + $generatedKey = hash($algorithm, $generatedKey . pack('CCCC', $i, $i >> 8, $i >> 16, $i >> 24), true); + } + $generatedKey = base64_encode($generatedKey); + + mb_internal_encoding($origEncoding); + + return $generatedKey; + } + + /** + * Get algorithm from self::$algorithmMapping + * + * @param string $algorithmName + * @return string + */ + private static function getAlgorithm($algorithmName) + { + $algorithm = self::$algorithmMapping[$algorithmName][1]; + if ($algorithm == '') { + $algorithm = 'sha1'; + } + + return $algorithm; + } + + /** + * Returns the algorithm ID + * + * @param string $algorithmName + * @return int + */ + public static function getAlgorithmId($algorithmName) + { + return self::$algorithmMapping[$algorithmName][0]; + } + + /** + * Build combined key from low-order word and high-order word + * + * @param array $byteChars byte array representation of password + * @return int + */ + private static function buildCombinedKey($byteChars) + { + $byteCharsLength = count($byteChars); + // Compute the high-order word + // Initialize from the initial code array (see above), depending on the passwords length. + $highOrderWord = self::$initialCodeArray[$byteCharsLength - 1]; + + // For each character in the password: + // For every bit in the character, starting with the least significant and progressing to (but excluding) + // the most significant, if the bit is set, XOR the key’s high-order word with the corresponding word from + // the Encryption Matrix + for ($i = 0; $i < $byteCharsLength; $i++) { + $tmp = self::$passwordMaxLength - $byteCharsLength + $i; + $matrixRow = self::$encryptionMatrix[$tmp]; + for ($intBit = 0; $intBit < 7; $intBit++) { + if (($byteChars[$i] & (0x0001 << $intBit)) != 0) { + $highOrderWord = ($highOrderWord ^ $matrixRow[$intBit]); + } + } + } + + // Compute low-order word + // Initialize with 0 + $lowOrderWord = 0; + // For each character in the password, going backwards + for ($i = $byteCharsLength - 1; $i >= 0; $i--) { + // low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character + $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteChars[$i]); + } + // Lastly, low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B. + $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteCharsLength ^ 0xCE4B); + + // Combine the Low and High Order Word + return self::int32(($highOrderWord << 16) + $lowOrderWord); + } + + /** + * Simulate behaviour of (signed) int32 + * + * @codeCoverageIgnore + * @param int $value + * @return int + */ + private static function int32($value) + { + $value = ($value & 0xFFFFFFFF); + + if ($value & 0x80000000) { + $value = -((~$value & 0xFFFFFFFF) + 1); + } + + return $value; + } +} diff --git a/PhpOffice/Common/Text.php b/PhpOffice/Common/Text.php new file mode 100644 index 0000000..969b6c1 --- /dev/null +++ b/PhpOffice/Common/Text.php @@ -0,0 +1,234 @@ +) + * element or in the shared string element. + * + * @param string $value Value to escape + * @return string + */ + public static function controlCharacterPHP2OOXML($value = '') + { + if (empty(self::$controlCharacters)) { + self::buildControlCharacters(); + } + + return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value); + } + + /** + * Return a number formatted for being integrated in xml files + * @param float $number + * @param integer $decimals + * @return string + */ + public static function numberFormat($number, $decimals) + { + return number_format($number, $decimals, '.', ''); + } + + /** + * @param int $dec + * @link http://stackoverflow.com/a/7153133/2235790 + * @author velcrow + * @return string + */ + public static function chr($dec) + { + if ($dec<=0x7F) { + return chr($dec); + } + if ($dec<=0x7FF) { + return chr(($dec>>6)+192).chr(($dec&63)+128); + } + if ($dec<=0xFFFF) { + return chr(($dec>>12)+224).chr((($dec>>6)&63)+128).chr(($dec&63)+128); + } + if ($dec<=0x1FFFFF) { + return chr(($dec>>18)+240).chr((($dec>>12)&63)+128).chr((($dec>>6)&63)+128).chr(($dec&63)+128); + } + return ''; + } + + /** + * Convert from OpenXML escaped control character to PHP control character + * + * @param string $value Value to unescape + * @return string + */ + public static function controlCharacterOOXML2PHP($value = '') + { + if (empty(self::$controlCharacters)) { + self::buildControlCharacters(); + } + + return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value); + } + + /** + * Check if a string contains UTF-8 data + * + * @param string $value + * @return boolean + */ + public static function isUTF8($value = '') + { + return is_string($value) && ($value === '' || preg_match('/^./su', $value) == 1); + } + + /** + * Return UTF8 encoded value + * + * @param string $value + * @return string + */ + public static function toUTF8($value = '') + { + if (!is_null($value) && !self::isUTF8($value)) { + $value = utf8_encode($value); + } + + return $value; + } + + /** + * Returns unicode from UTF8 text + * + * The function is splitted to reduce cyclomatic complexity + * + * @param string $text UTF8 text + * @return string Unicode text + * @since 0.11.0 + */ + public static function toUnicode($text) + { + return self::unicodeToEntities(self::utf8ToUnicode($text)); + } + + /** + * Returns unicode array from UTF8 text + * + * @param string $text UTF8 text + * @return array + * @since 0.11.0 + * @link http://www.randomchaos.com/documents/?source=php_and_unicode + */ + public static function utf8ToUnicode($text) + { + $unicode = array(); + $values = array(); + $lookingFor = 1; + + // Gets unicode for each character + for ($i = 0; $i < strlen($text); $i++) { + $thisValue = ord($text[$i]); + if ($thisValue < 128) { + $unicode[] = $thisValue; + } else { + if (count($values) == 0) { + $lookingFor = $thisValue < 224 ? 2 : 3; + } + $values[] = $thisValue; + if (count($values) == $lookingFor) { + if ($lookingFor == 3) { + $number = (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64); + } else { + $number = (($values[0] % 32) * 64) + ($values[1] % 64); + } + $unicode[] = $number; + $values = array(); + $lookingFor = 1; + } + } + } + + return $unicode; + } + + /** + * Returns entites from unicode array + * + * @param array $unicode + * @return string + * @since 0.11.0 + * @link http://www.randomchaos.com/documents/?source=php_and_unicode + */ + private static function unicodeToEntities($unicode) + { + $entities = ''; + + foreach ($unicode as $value) { + if ($value != 65279) { + $entities .= $value > 127 ? '\uc0{\u' . $value . '}' : chr($value); + } + } + + return $entities; + } + + /** + * Return name without underscore for < 0.10.0 variable name compatibility + * + * @param string $value + * @return string + */ + public static function removeUnderscorePrefix($value) + { + if (!is_null($value)) { + if (substr($value, 0, 1) == '_') { + $value = substr($value, 1); + } + } + + return $value; + } +} diff --git a/PhpOffice/Common/XMLReader.php b/PhpOffice/Common/XMLReader.php new file mode 100644 index 0000000..6c720ae --- /dev/null +++ b/PhpOffice/Common/XMLReader.php @@ -0,0 +1,210 @@ +open($zipFile); + $content = $zip->getFromName($xmlFile); + $zip->close(); + + if ($content === false) { + return false; + } + + return $this->getDomFromString($content); + } + + /** + * Get DOMDocument from content string + * + * @param string $content + * @return \DOMDocument + */ + public function getDomFromString($content) + { + $originalLibXMLEntityValue = libxml_disable_entity_loader(true); + $this->dom = new \DOMDocument(); + $this->dom->loadXML($content); + libxml_disable_entity_loader($originalLibXMLEntityValue); + + return $this->dom; + } + + /** + * Get elements + * + * @param string $path + * @param \DOMElement $contextNode + * @return \DOMNodeList + */ + public function getElements($path, \DOMElement $contextNode = null) + { + if ($this->dom === null) { + return array(); + } + if ($this->xpath === null) { + $this->xpath = new \DOMXpath($this->dom); + } + + if (is_null($contextNode)) { + return $this->xpath->query($path); + } + + return $this->xpath->query($path, $contextNode); + } + + /** + * Registers the namespace with the DOMXPath object + * + * @param string $prefix The prefix + * @param string $namespaceURI The URI of the namespace + * @return bool true on success or false on failure + * @throws \InvalidArgumentException If called before having loaded the DOM document + */ + public function registerNamespace($prefix, $namespaceURI) + { + if ($this->dom === null) { + throw new \InvalidArgumentException('Dom needs to be loaded before registering a namespace'); + } + if ($this->xpath === null) { + $this->xpath = new \DOMXpath($this->dom); + } + return $this->xpath->registerNamespace($prefix, $namespaceURI); + } + + /** + * Get element + * + * @param string $path + * @param \DOMElement $contextNode + * @return \DOMElement|null + */ + public function getElement($path, \DOMElement $contextNode = null) + { + $elements = $this->getElements($path, $contextNode); + if ($elements->length > 0) { + return $elements->item(0); + } + + return null; + } + + /** + * Get element attribute + * + * @param string $attribute + * @param \DOMElement $contextNode + * @param string $path + * @return string|null + */ + public function getAttribute($attribute, \DOMElement $contextNode = null, $path = null) + { + $return = null; + if ($path !== null) { + $elements = $this->getElements($path, $contextNode); + if ($elements->length > 0) { + /** @var \DOMElement $node Type hint */ + $node = $elements->item(0); + $return = $node->getAttribute($attribute); + } + } else { + if ($contextNode !== null) { + $return = $contextNode->getAttribute($attribute); + } + } + + return ($return == '') ? null : $return; + } + + /** + * Get element value + * + * @param string $path + * @param \DOMElement $contextNode + * @return string|null + */ + public function getValue($path, \DOMElement $contextNode = null) + { + $elements = $this->getElements($path, $contextNode); + if ($elements->length > 0) { + return $elements->item(0)->nodeValue; + } + + return null; + } + + /** + * Count elements + * + * @param string $path + * @param \DOMElement $contextNode + * @return integer + */ + public function countElements($path, \DOMElement $contextNode = null) + { + $elements = $this->getElements($path, $contextNode); + + return $elements->length; + } + + /** + * Element exists + * + * @param string $path + * @param \DOMElement $contextNode + * @return boolean + */ + public function elementExists($path, \DOMElement $contextNode = null) + { + return $this->getElements($path, $contextNode)->length > 0; + } +} diff --git a/PhpOffice/Common/XMLWriter.php b/PhpOffice/Common/XMLWriter.php new file mode 100644 index 0000000..4440d65 --- /dev/null +++ b/PhpOffice/Common/XMLWriter.php @@ -0,0 +1,183 @@ +openMemory(); + } else { + if (!is_dir($pTemporaryStorageDir)) { + $pTemporaryStorageDir = sys_get_temp_dir(); + } + // Create temporary filename + $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); + + // Open storage + $this->openUri($this->tempFileName); + } + + if ($compatibility) { + $this->setIndent(false); + $this->setIndentString(''); + } else { + $this->setIndent(true); + $this->setIndentString(' '); + } + } + + /** + * Destructor + */ + public function __destruct() + { + // Unlink temporary files + if (empty($this->tempFileName)) { + return; + } + if (PHP_OS != 'WINNT' && @unlink($this->tempFileName) === false) { + throw new \Exception('The file '.$this->tempFileName.' could not be deleted.'); + } + } + + /** + * Get written data + * + * @return string + */ + public function getData() + { + if ($this->tempFileName == '') { + return $this->outputMemory(true); + } + + $this->flush(); + return file_get_contents($this->tempFileName); + } + + + /** + * Write simple element and attribute(s) block + * + * There are two options: + * 1. If the `$attributes` is an array, then it's an associative array of attributes + * 2. If not, then it's a simple attribute-value pair + * + * @param string $element + * @param string|array $attributes + * @param string $value + * @return void + */ + public function writeElementBlock($element, $attributes, $value = null) + { + $this->startElement($element); + if (!is_array($attributes)) { + $attributes = array($attributes => $value); + } + foreach ($attributes as $attribute => $value) { + $this->writeAttribute($attribute, $value); + } + $this->endElement(); + } + + /** + * Write element if ... + * + * @param bool $condition + * @param string $element + * @param string $attribute + * @param mixed $value + * @return void + */ + public function writeElementIf($condition, $element, $attribute = null, $value = null) + { + if ($condition == true) { + if (is_null($attribute)) { + $this->writeElement($element, $value); + } else { + $this->startElement($element); + $this->writeAttribute($attribute, $value); + $this->endElement(); + } + } + } + + /** + * Write attribute if ... + * + * @param bool $condition + * @param string $attribute + * @param mixed $value + * @return void + */ + public function writeAttributeIf($condition, $attribute, $value) + { + if ($condition == true) { + $this->writeAttribute($attribute, $value); + } + } + + /** + * @param string $name + * @param mixed $value + * @return bool + */ + public function writeAttribute($name, $value) + { + if (is_float($value)) { + $value = json_encode($value); + } + return parent::writeAttribute($name, $value); + } +} diff --git a/PhpOffice/PhpPresentation/AbstractShape.php b/PhpOffice/PhpPresentation/AbstractShape.php new file mode 100644 index 0000000..9c588df --- /dev/null +++ b/PhpOffice/PhpPresentation/AbstractShape.php @@ -0,0 +1,469 @@ +container = null; + $this->offsetX = 0; + $this->offsetY = 0; + $this->width = 0; + $this->height = 0; + $this->rotation = 0; + $this->fill = new Style\Fill(); + $this->border = new Style\Border(); + $this->shadow = new Style\Shadow(); + + $this->border->setLineStyle(Style\Border::LINE_NONE); + } + + /** + * Magic Method : clone + */ + public function __clone() + { + $this->container = null; + $this->fill = clone $this->fill; + $this->border = clone $this->border; + $this->shadow = clone $this->shadow; + } + + /** + * Get Container, Slide or Group + * + * @return \PhpOffice\PhpPresentation\ShapeContainerInterface + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set Container, Slide or Group + * + * @param \PhpOffice\PhpPresentation\ShapeContainerInterface $pValue + * @param bool $pOverrideOld If a Slide has already been assigned, overwrite it and remove image from old Slide? + * @throws \Exception + * @return $this + */ + public function setContainer(ShapeContainerInterface $pValue = null, $pOverrideOld = false) + { + if (is_null($this->container)) { + // Add drawing to \PhpOffice\PhpPresentation\ShapeContainerInterface + $this->container = $pValue; + if (!is_null($this->container)) { + $this->container->getShapeCollection()->append($this); + } + } else { + if ($pOverrideOld) { + // Remove drawing from old \PhpOffice\PhpPresentation\ShapeContainerInterface + $iterator = $this->container->getShapeCollection()->getIterator(); + + while ($iterator->valid()) { + if ($iterator->current()->getHashCode() == $this->getHashCode()) { + $this->container->getShapeCollection()->offsetUnset($iterator->key()); + $this->container = null; + break; + } + $iterator->next(); + } + + // Set new \PhpOffice\PhpPresentation\Slide + $this->setContainer($pValue); + } else { + throw new \Exception("A \PhpOffice\PhpPresentation\ShapeContainerInterface has already been assigned. Shapes can only exist on one \PhpOffice\PhpPresentation\ShapeContainerInterface."); + } + } + + return $this; + } + + /** + * Get OffsetX + * + * @return int + */ + public function getOffsetX() + { + return $this->offsetX; + } + + /** + * Set OffsetX + * + * @param int $pValue + * @return $this + */ + public function setOffsetX($pValue = 0) + { + $this->offsetX = $pValue; + + return $this; + } + + /** + * Get OffsetY + * + * @return int + */ + public function getOffsetY() + { + return $this->offsetY; + } + + /** + * Set OffsetY + * + * @param int $pValue + * @return $this + */ + public function setOffsetY($pValue = 0) + { + $this->offsetY = $pValue; + + return $this; + } + + /** + * Get Width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set Width + * + * @param int $pValue + * @return $this + */ + public function setWidth($pValue = 0) + { + $this->width = $pValue; + return $this; + } + + /** + * Get Height + * + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set Height + * + * @param int $pValue + * @return $this + */ + public function setHeight($pValue = 0) + { + $this->height = $pValue; + return $this; + } + + /** + * Set width and height with proportional resize + * + * @param int $width + * @param int $height + * @example $objDrawing->setWidthAndHeight(160,120); + * @return $this + */ + public function setWidthAndHeight($width = 0, $height = 0) + { + $this->width = $width; + $this->height = $height; + return $this; + } + + /** + * Get Rotation + * + * @return int + */ + public function getRotation() + { + return $this->rotation; + } + + /** + * Set Rotation + * + * @param int $pValue + * @return $this + */ + public function setRotation($pValue = 0) + { + $this->rotation = $pValue; + return $this; + } + + /** + * Get Fill + * + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function getFill() + { + return $this->fill; + } + + /** + * Set Fill + * @param \PhpOffice\PhpPresentation\Style\Fill $pValue + * @return \PhpOffice\PhpPresentation\AbstractShape + */ + public function setFill(Fill $pValue = null) + { + $this->fill = $pValue; + return $this; + } + + /** + * Get Border + * + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function getBorder() + { + return $this->border; + } + + /** + * Get Shadow + * + * @return \PhpOffice\PhpPresentation\Style\Shadow + */ + public function getShadow() + { + return $this->shadow; + } + + /** + * Set Shadow + * + * @param \PhpOffice\PhpPresentation\Style\Shadow $pValue + * @throws \Exception + * @return $this + */ + public function setShadow(Shadow $pValue = null) + { + $this->shadow = $pValue; + return $this; + } + + /** + * Has Hyperlink? + * + * @return boolean + */ + public function hasHyperlink() + { + return !is_null($this->hyperlink); + } + + /** + * Get Hyperlink + * + * @return \PhpOffice\PhpPresentation\Shape\Hyperlink + * @throws \Exception + */ + public function getHyperlink() + { + if (is_null($this->hyperlink)) { + $this->hyperlink = new Hyperlink(); + } + return $this->hyperlink; + } + + /** + * Set Hyperlink + * + * @param \PhpOffice\PhpPresentation\Shape\Hyperlink $pHyperlink + * @throws \Exception + * @return $this + */ + public function setHyperlink(Hyperlink $pHyperlink = null) + { + $this->hyperlink = $pHyperlink; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5((is_object($this->container) ? $this->container->getHashCode() : '') . $this->offsetX . $this->offsetY . $this->width . $this->height . $this->rotation . (is_null($this->getFill()) ? '' : $this->getFill()->getHashCode()) . (is_null($this->shadow) ? '' : $this->shadow->getHashCode()) . (is_null($this->hyperlink) ? '' : $this->hyperlink->getHashCode()) . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } + + public function isPlaceholder() + { + return !is_null($this->placeholder); + } + + public function getPlaceholder() + { + if (!$this->isPlaceholder()) { + return null; + } + return $this->placeholder; + } + + /** + * @param \PhpOffice\PhpPresentation\Shape\Placeholder $placeholder + * @return $this + */ + public function setPlaceHolder(Placeholder $placeholder) + { + $this->placeholder = $placeholder; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Autoloader.php b/PhpOffice/PhpPresentation/Autoloader.php new file mode 100644 index 0000000..605981b --- /dev/null +++ b/PhpOffice/PhpPresentation/Autoloader.php @@ -0,0 +1,55 @@ + array('cx' => 9144000, 'cy' => 6858000), + self::LAYOUT_SCREEN_16X10 => array('cx' => 9144000, 'cy' => 5715000), + self::LAYOUT_SCREEN_16X9 => array('cx' => 9144000, 'cy' => 5143500), + self::LAYOUT_35MM => array('cx' => 10287000, 'cy' => 6858000), + self::LAYOUT_A3 => array('cx' => 15120000, 'cy' => 10692000), + self::LAYOUT_A4 => array('cx' => 10692000, 'cy' => 7560000), + self::LAYOUT_B4ISO => array('cx' => 10826750, 'cy' => 8120063), + self::LAYOUT_B5ISO => array('cx' => 7169150, 'cy' => 5376863), + self::LAYOUT_BANNER => array('cx' => 7315200, 'cy' => 914400), + self::LAYOUT_LETTER => array('cx' => 9144000, 'cy' => 6858000), + self::LAYOUT_OVERHEAD => array('cx' => 9144000, 'cy' => 6858000), + ); + + /** + * Layout name + * + * @var string + */ + private $layout; + + /** + * Layout X dimension + * @var float + */ + private $dimensionX; + + /** + * Layout Y dimension + * @var float + */ + private $dimensionY; + + /** + * Create a new \PhpOffice\PhpPresentation\DocumentLayout + */ + public function __construct() + { + $this->setDocumentLayout(self::LAYOUT_SCREEN_4X3); + } + + /** + * Get Document Layout + * + * @return string + */ + public function getDocumentLayout() + { + return $this->layout; + } + + /** + * Set Document Layout + * + * @param array|string $pValue + * @param boolean $isLandscape + * @return \PhpOffice\PhpPresentation\DocumentLayout + */ + public function setDocumentLayout($pValue = self::LAYOUT_SCREEN_4X3, $isLandscape = true) + { + switch ($pValue) { + case self::LAYOUT_SCREEN_4X3: + case self::LAYOUT_SCREEN_16X10: + case self::LAYOUT_SCREEN_16X9: + case self::LAYOUT_35MM: + case self::LAYOUT_A3: + case self::LAYOUT_A4: + case self::LAYOUT_B4ISO: + case self::LAYOUT_B5ISO: + case self::LAYOUT_BANNER: + case self::LAYOUT_LETTER: + case self::LAYOUT_OVERHEAD: + $this->layout = $pValue; + $this->dimensionX = $this->dimension[$this->layout]['cx']; + $this->dimensionY = $this->dimension[$this->layout]['cy']; + break; + case self::LAYOUT_CUSTOM: + default: + $this->layout = self::LAYOUT_CUSTOM; + $this->dimensionX = $pValue['cx']; + $this->dimensionY = $pValue['cy']; + break; + } + + if (!$isLandscape) { + $tmp = $this->dimensionX; + $this->dimensionX = $this->dimensionY; + $this->dimensionY = $tmp; + } + + return $this; + } + + /** + * Get Document Layout cx + * + * @param string $unit + * @return integer + */ + public function getCX($unit = self::UNIT_EMU) + { + return $this->convertUnit($this->dimensionX, self::UNIT_EMU, $unit); + } + + /** + * Get Document Layout cy + * + * @param string $unit + * @return integer + */ + public function getCY($unit = self::UNIT_EMU) + { + return $this->convertUnit($this->dimensionY, self::UNIT_EMU, $unit); + } + + /** + * Get Document Layout cx + * + * @param float $value + * @param string $unit + * @return DocumentLayout + */ + public function setCX($value, $unit = self::UNIT_EMU) + { + $this->layout = self::LAYOUT_CUSTOM; + $this->dimensionX = $this->convertUnit($value, $unit, self::UNIT_EMU); + return $this; + } + + /** + * Get Document Layout cy + * + * @param float $value + * @param string $unit + * @return DocumentLayout + */ + public function setCY($value, $unit = self::UNIT_EMU) + { + $this->layout = self::LAYOUT_CUSTOM; + $this->dimensionY = $this->convertUnit($value, $unit, self::UNIT_EMU); + return $this; + } + + /** + * Convert EMUs to differents units + * @param float $value + * @param string $fromUnit + * @param string $toUnit + * @return float + */ + protected function convertUnit($value, $fromUnit, $toUnit) + { + // Convert from $fromUnit to EMU + switch ($fromUnit) { + case self::UNIT_MILLIMETER: + $value *= 36000; + break; + case self::UNIT_CENTIMETER: + $value *= 360000; + break; + case self::UNIT_INCH: + $value *= 914400; + break; + case self::UNIT_PIXEL: + $value = Drawing::pixelsToEmu($value); + break; + case self::UNIT_POINT: + $value *= 12700; + break; + case self::UNIT_EMU: + default: + // no changes + } + + // Convert from EMU to $toUnit + switch ($toUnit) { + case self::UNIT_MILLIMETER: + $value /= 36000; + break; + case self::UNIT_CENTIMETER: + $value /= 360000; + break; + case self::UNIT_INCH: + $value /= 914400; + break; + case self::UNIT_PIXEL: + $value = Drawing::emuToPixels($value); + break; + case self::UNIT_POINT: + $value /= 12700; + break; + case self::UNIT_EMU: + default: + // no changes + } + return $value; + } +} diff --git a/PhpOffice/PhpPresentation/DocumentProperties.php b/PhpOffice/PhpPresentation/DocumentProperties.php new file mode 100644 index 0000000..c9344fe --- /dev/null +++ b/PhpOffice/PhpPresentation/DocumentProperties.php @@ -0,0 +1,348 @@ +creator = 'Unknown Creator'; + $this->lastModifiedBy = $this->creator; + $this->created = time(); + $this->modified = time(); + $this->title = "Untitled Presentation"; + $this->subject = ''; + $this->description = ''; + $this->keywords = ''; + $this->category = ''; + $this->company = 'Microsoft Corporation'; + } + + /** + * Get Creator + * + * @return string + */ + public function getCreator() + { + return $this->creator; + } + + /** + * Set Creator + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setCreator($pValue = '') + { + $this->creator = $pValue; + + return $this; + } + + /** + * Get Last Modified By + * + * @return string + */ + public function getLastModifiedBy() + { + return $this->lastModifiedBy; + } + + /** + * Set Last Modified By + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setLastModifiedBy($pValue = '') + { + $this->lastModifiedBy = $pValue; + + return $this; + } + + /** + * Get Created + * + * @return int + */ + public function getCreated() + { + return $this->created; + } + + /** + * Set Created + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setCreated($pValue = null) + { + if (is_null($pValue)) { + $pValue = time(); + } + $this->created = $pValue; + + return $this; + } + + /** + * Get Modified + * + * @return int + */ + public function getModified() + { + return $this->modified; + } + + /** + * Set Modified + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setModified($pValue = null) + { + if (is_null($pValue)) { + $pValue = time(); + } + $this->modified = $pValue; + + return $this; + } + + /** + * Get Title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set Title + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setTitle($pValue = '') + { + $this->title = $pValue; + + return $this; + } + + /** + * Get Description + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set Description + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setDescription($pValue = '') + { + $this->description = $pValue; + + return $this; + } + + /** + * Get Subject + * + * @return string + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Set Subject + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setSubject($pValue = '') + { + $this->subject = $pValue; + + return $this; + } + + /** + * Get Keywords + * + * @return string + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * Set Keywords + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setKeywords($pValue = '') + { + $this->keywords = $pValue; + + return $this; + } + + /** + * Get Category + * + * @return string + */ + public function getCategory() + { + return $this->category; + } + + /** + * Set Category + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setCategory($pValue = '') + { + $this->category = $pValue; + + return $this; + } + + /** + * Get Company + * + * @return string + */ + public function getCompany() + { + return $this->company; + } + + /** + * Set Company + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function setCompany($pValue = '') + { + $this->company = $pValue; + + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/GeometryCalculator.php b/PhpOffice/PhpPresentation/GeometryCalculator.php new file mode 100644 index 0000000..987a7ba --- /dev/null +++ b/PhpOffice/PhpPresentation/GeometryCalculator.php @@ -0,0 +1,96 @@ + 0, self::Y => 0); + + if ($container !== null && count($container->getShapeCollection()) != 0) { + $shapes = $container->getShapeCollection(); + if ($shapes[0] !== null) { + $offsets[self::X] = $shapes[0]->getOffsetX(); + $offsets[self::Y] = $shapes[0]->getOffsetY(); + } + + foreach ($shapes as $shape) { + if ($shape !== null) { + if ($shape->getOffsetX() < $offsets[self::X]) { + $offsets[self::X] = $shape->getOffsetX(); + } + + if ($shape->getOffsetY() < $offsets[self::Y]) { + $offsets[self::Y] = $shape->getOffsetY(); + } + } + } + } + + return $offsets; + } + + /** + * Calculate X and Y extents for a set of shapes within a container such as a slide or group. + * + * @param \PhpOffice\PhpPresentation\ShapeContainerInterface $container + * @return array + */ + public static function calculateExtents(ShapeContainerInterface $container) + { + $extents = array(self::X => 0, self::Y => 0); + + if ($container !== null && count($container->getShapeCollection()) != 0) { + $shapes = $container->getShapeCollection(); + if ($shapes[0] !== null) { + $extents[self::X] = $shapes[0]->getOffsetX() + $shapes[0]->getWidth(); + $extents[self::Y] = $shapes[0]->getOffsetY() + $shapes[0]->getHeight(); + } + + foreach ($shapes as $shape) { + if ($shape !== null) { + $extentX = $shape->getOffsetX() + $shape->getWidth(); + $extentY = $shape->getOffsetY() + $shape->getHeight(); + + if ($extentX > $extents[self::X]) { + $extents[self::X] = $extentX; + } + + if ($extentY > $extents[self::Y]) { + $extents[self::Y] = $extentY; + } + } + } + } + + return $extents; + } +} diff --git a/PhpOffice/PhpPresentation/HashTable.php b/PhpOffice/PhpPresentation/HashTable.php new file mode 100644 index 0000000..54eec91 --- /dev/null +++ b/PhpOffice/PhpPresentation/HashTable.php @@ -0,0 +1,198 @@ +addFromSource($pSource); + } + } + + /** + * Add HashTable items from source + * + * @param \PhpOffice\PhpPresentation\ComparableInterface[] $pSource Source array to create HashTable from + * @throws \Exception + */ + public function addFromSource($pSource = null) + { + // Check if an array was passed + if ($pSource == null) { + return; + } elseif (!is_array($pSource)) { + throw new \Exception('Invalid array parameter passed.'); + } + + foreach ($pSource as $item) { + $this->add($item); + } + } + + /** + * Add HashTable item + * + * @param \PhpOffice\PhpPresentation\ComparableInterface $pSource Item to add + */ + public function add(ComparableInterface $pSource) + { + // Determine hashcode + $hashIndex = $pSource->getHashIndex(); + $hashCode = $pSource->getHashCode(); + + if (is_null($hashIndex)) { + $hashCode = $pSource->getHashCode(); + } elseif (isset($this->keyMap[$hashIndex])) { + $hashCode = $this->keyMap[$hashIndex]; + } + + // Add value + if (!isset($this->items[$hashCode])) { + $this->items[$hashCode] = $pSource; + $index = count($this->items) - 1; + $this->keyMap[$index] = $hashCode; + $pSource->setHashIndex($index); + } else { + $pSource->setHashIndex($this->items[$hashCode]->getHashIndex()); + } + } + + /** + * Remove HashTable item + * + * @param \PhpOffice\PhpPresentation\ComparableInterface $pSource Item to remove + * @throws \Exception + */ + public function remove(ComparableInterface $pSource) + { + if (isset($this->items[$pSource->getHashCode()])) { + unset($this->items[$pSource->getHashCode()]); + + $deleteKey = -1; + foreach ($this->keyMap as $key => $value) { + if ($deleteKey >= 0) { + $this->keyMap[$key - 1] = $value; + } + + if ($value == $pSource->getHashCode()) { + $deleteKey = $key; + } + } + unset($this->keyMap[count($this->keyMap) - 1]); + } + } + + /** + * Clear HashTable + * + */ + public function clear() + { + $this->items = array(); + $this->keyMap = array(); + } + + /** + * Count + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Get index for hash code + * + * @param string $pHashCode + * @return int Index + */ + public function getIndexForHashCode($pHashCode = '') + { + return array_search($pHashCode, $this->keyMap); + } + + /** + * Get by index + * + * @param int $pIndex + * @return \PhpOffice\PhpPresentation\ComparableInterface + * + */ + public function getByIndex($pIndex = 0) + { + if (isset($this->keyMap[$pIndex])) { + return $this->getByHashCode($this->keyMap[$pIndex]); + } + + return null; + } + + /** + * Get by hashcode + * + * @param string $pHashCode + * @return \PhpOffice\PhpPresentation\ComparableInterface + * + */ + public function getByHashCode($pHashCode = '') + { + if (isset($this->items[$pHashCode])) { + return $this->items[$pHashCode]; + } + + return null; + } + + /** + * HashTable to array + * + * @return \PhpOffice\PhpPresentation\ComparableInterface[] + */ + public function toArray() + { + return $this->items; + } +} diff --git a/PhpOffice/PhpPresentation/IOFactory.php b/PhpOffice/PhpPresentation/IOFactory.php new file mode 100644 index 0000000..84cb375 --- /dev/null +++ b/PhpOffice/PhpPresentation/IOFactory.php @@ -0,0 +1,115 @@ +canRead($pFilename)) { + return $reader->load($pFilename); + } + } + + throw new \Exception("Could not automatically determine \PhpOffice\PhpPresentation\Reader\ReaderInterface for file."); + } + + /** + * Load class + * + * @param string $class + * @param string $name + * @param string $type + * @param \PhpOffice\PhpPresentation\PhpPresentation $phpPresentation + * @return mixed + * @throws \ReflectionException + */ + private static function loadClass($class, $name, $type, PhpPresentation $phpPresentation = null) + { + if (class_exists($class) && self::isConcreteClass($class)) { + if (is_null($phpPresentation)) { + return new $class(); + } else { + return new $class($phpPresentation); + } + } else { + throw new \Exception('"'.$name.'" is not a valid '.$type.'.'); + } + } + + /** + * Is it a concrete class? + * + * @param string $class + * @return bool + * @throws \ReflectionException + */ + private static function isConcreteClass($class) + { + $reflection = new \ReflectionClass($class); + + return !$reflection->isAbstract() && !$reflection->isInterface(); + } +} diff --git a/PhpOffice/PhpPresentation/PhpPresentation.php b/PhpOffice/PhpPresentation/PhpPresentation.php new file mode 100644 index 0000000..172babe --- /dev/null +++ b/PhpOffice/PhpPresentation/PhpPresentation.php @@ -0,0 +1,447 @@ +createMasterSlide()->createSlideLayout(); + + // Initialise slide collection and add one slide + $this->createSlide(); + $this->setActiveSlideIndex(); + + // Set initial document properties & layout + $this->setDocumentProperties(new DocumentProperties()); + $this->setPresentationProperties(new PresentationProperties()); + $this->setLayout(new DocumentLayout()); + } + + /** + * Get properties + * + * @return \PhpOffice\PhpPresentation\DocumentProperties + * @deprecated for getDocumentProperties + */ + public function getProperties() + { + return $this->getDocumentProperties(); + } + + /** + * Set properties + * + * @param \PhpOffice\PhpPresentation\DocumentProperties $value + * @deprecated for setDocumentProperties + * @return PhpPresentation + */ + public function setProperties(DocumentProperties $value) + { + return $this->setDocumentProperties($value); + } + + /** + * Get properties + * + * @return \PhpOffice\PhpPresentation\DocumentProperties + */ + public function getDocumentProperties() + { + return $this->documentProperties; + } + + /** + * Set properties + * + * @param \PhpOffice\PhpPresentation\DocumentProperties $value + * @return PhpPresentation + */ + public function setDocumentProperties(DocumentProperties $value) + { + $this->documentProperties = $value; + + return $this; + } + + /** + * Get presentation properties + * + * @return \PhpOffice\PhpPresentation\PresentationProperties + */ + public function getPresentationProperties() + { + return $this->presentationProps; + } + + /** + * Set presentation properties + * + * @param \PhpOffice\PhpPresentation\PresentationProperties $value + * @return PhpPresentation + */ + public function setPresentationProperties(PresentationProperties $value) + { + $this->presentationProps = $value; + return $this; + } + + /** + * Get layout + * + * @return \PhpOffice\PhpPresentation\DocumentLayout + */ + public function getLayout() + { + return $this->layout; + } + + /** + * Set layout + * + * @param \PhpOffice\PhpPresentation\DocumentLayout $value + * @return PhpPresentation + */ + public function setLayout(DocumentLayout $value) + { + $this->layout = $value; + + return $this; + } + + /** + * Get active slide + * + * @return \PhpOffice\PhpPresentation\Slide + */ + public function getActiveSlide() + { + return $this->slideCollection[$this->activeSlideIndex]; + } + + /** + * Create slide and add it to this presentation + * + * @return \PhpOffice\PhpPresentation\Slide + * @throws \Exception + */ + public function createSlide() + { + $newSlide = new Slide($this); + $this->addSlide($newSlide); + return $newSlide; + } + + /** + * Add slide + * + * @param \PhpOffice\PhpPresentation\Slide $slide + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Slide + */ + public function addSlide(Slide $slide = null) + { + $this->slideCollection[] = $slide; + + return $slide; + } + + /** + * Remove slide by index + * + * @param int $index Slide index + * @throws \Exception + * @return PhpPresentation + */ + public function removeSlideByIndex($index = 0) + { + if ($index > count($this->slideCollection) - 1) { + throw new \Exception("Slide index is out of bounds."); + } + array_splice($this->slideCollection, $index, 1); + + return $this; + } + + /** + * Get slide by index + * + * @param int $index Slide index + * @return \PhpOffice\PhpPresentation\Slide + * @throws \Exception + */ + public function getSlide($index = 0) + { + if ($index > count($this->slideCollection) - 1) { + throw new \Exception("Slide index is out of bounds."); + } + return $this->slideCollection[$index]; + } + + /** + * Get all slides + * + * @return \PhpOffice\PhpPresentation\Slide[] + */ + public function getAllSlides() + { + return $this->slideCollection; + } + + /** + * Get index for slide + * + * @param \PhpOffice\PhpPresentation\Slide\AbstractSlide $slide + * @return int + * @throws \Exception + */ + public function getIndex(Slide\AbstractSlide $slide) + { + $index = null; + foreach ($this->slideCollection as $key => $value) { + if ($value->getHashCode() == $slide->getHashCode()) { + $index = $key; + break; + } + } + return $index; + } + + /** + * Get slide count + * + * @return int + */ + public function getSlideCount() + { + return count($this->slideCollection); + } + + /** + * Get active slide index + * + * @return int Active slide index + */ + public function getActiveSlideIndex() + { + return $this->activeSlideIndex; + } + + /** + * Set active slide index + * + * @param int $index Active slide index + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Slide + */ + public function setActiveSlideIndex($index = 0) + { + if ($index > count($this->slideCollection) - 1) { + throw new \Exception("Active slide index is out of bounds."); + } + $this->activeSlideIndex = $index; + + return $this->getActiveSlide(); + } + + /** + * Add external slide + * + * @param \PhpOffice\PhpPresentation\Slide $slide External slide to add + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Slide + */ + public function addExternalSlide(Slide $slide) + { + $slide->rebindParent($this); + + $this->addMasterSlide($slide->getSlideLayout()->getSlideMaster()); + + return $this->addSlide($slide); + } + + /** + * Get slide iterator + * + * @return \PhpOffice\PhpPresentation\Slide\Iterator + */ + public function getSlideIterator() + { + return new Iterator($this); + } + + /** + * Create a masterslide and add it to this presentation + * + * @return \PhpOffice\PhpPresentation\Slide\SlideMaster + * @throws \Exception + */ + public function createMasterSlide() + { + $newMasterSlide = new SlideMaster($this); + $this->addMasterSlide($newMasterSlide); + return $newMasterSlide; + } + + /** + * Add masterslide + * + * @param \PhpOffice\PhpPresentation\Slide\SlideMaster $slide + * @return \PhpOffice\PhpPresentation\Slide\SlideMaster + * @throws \Exception + */ + public function addMasterSlide(SlideMaster $slide = null) + { + $this->slideMasters[] = $slide; + + return $slide; + } + + /** + * Copy presentation (!= clone!) + * + * @return PhpPresentation + * @throws \Exception + */ + public function copy() + { + $copied = clone $this; + + $slideCount = count($this->slideCollection); + for ($i = 0; $i < $slideCount; ++$i) { + $this->slideCollection[$i] = $this->slideCollection[$i]->copy(); + $this->slideCollection[$i]->rebindParent($this); + } + + return $copied; + } + + /** + * Mark a document as final + * @param bool $state + * @return PresentationProperties + * @deprecated for getPresentationProperties()->markAsFinal() + */ + public function markAsFinal($state = true) + { + return $this->getPresentationProperties()->markAsFinal($state); + } + + /** + * Return if this document is marked as final + * @return bool + * @deprecated for getPresentationProperties()->isMarkedAsFinal() + */ + public function isMarkedAsFinal() + { + return $this->getPresentationProperties()->isMarkedAsFinal(); + } + + /** + * Set the zoom of the document (in percentage) + * @param float $zoom + * @return PresentationProperties + * @deprecated for getPresentationProperties()->setZoom() + */ + public function setZoom($zoom = 1.0) + { + return $this->getPresentationProperties()->setZoom($zoom); + } + + /** + * Return the zoom (in percentage) + * @return float + * @deprecated for getPresentationProperties()->getZoom() + */ + public function getZoom() + { + return $this->getPresentationProperties()->getZoom(); + } + + /** + * @return \ArrayObject|Slide\SlideMaster[] + */ + public function getAllMasterSlides() + { + return $this->slideMasters; + } + + /** + * @param \ArrayObject|Slide\SlideMaster[] $slideMasters + * @return $this + */ + public function setAllMasterSlides($slideMasters = array()) + { + if ($slideMasters instanceof \ArrayObject || is_array($slideMasters)) { + $this->slideMasters = $slideMasters; + } + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/PresentationProperties.php b/PhpOffice/PhpPresentation/PresentationProperties.php new file mode 100644 index 0000000..05b6dbf --- /dev/null +++ b/PhpOffice/PhpPresentation/PresentationProperties.php @@ -0,0 +1,201 @@ +isLoopUntilEsc; + } + + /** + * @param bool $value + * @return \PhpOffice\PhpPresentation\PresentationProperties + */ + public function setLoopContinuouslyUntilEsc($value = false) + { + if (is_bool($value)) { + $this->isLoopUntilEsc = $value; + } + return $this; + } + + /** + * Return the thumbnail file path + * @return string + */ + public function getThumbnailPath() + { + return $this->thumbnail; + } + + /** + * Define the path for the thumbnail file / preview picture + * @param string $path + * @return \PhpOffice\PhpPresentation\PresentationProperties + */ + public function setThumbnailPath($path = '') + { + if (file_exists($path)) { + $this->thumbnail = $path; + } + return $this; + } + + /** + * Mark a document as final + * @param bool $state + * @return PresentationProperties + */ + public function markAsFinal($state = true) + { + if (is_bool($state)) { + $this->markAsFinal = $state; + } + return $this; + } + + /** + * Return if this document is marked as final + * @return bool + */ + public function isMarkedAsFinal() + { + return $this->markAsFinal; + } + + /** + * Set the zoom of the document (in percentage) + * @param float $zoom + * @return PresentationProperties + */ + public function setZoom($zoom = 1.0) + { + if (is_numeric($zoom)) { + $this->zoom = (float)$zoom; + } + return $this; + } + + /** + * Return the zoom (in percentage) + * @return float + */ + public function getZoom() + { + return $this->zoom; + } + + /** + * @param string $value + * @return $this + */ + public function setLastView($value = self::VIEW_SLIDE) + { + if (in_array($value, $this->arrayView)) { + $this->lastView = $value; + } + return $this; + } + + /** + * @return string + */ + public function getLastView() + { + return $this->lastView; + } + + /** + * @param bool $value + * @return $this + */ + public function setCommentVisible($value = false) + { + if (is_bool($value)) { + $this->isCommentVisible = $value; + } + return $this; + } + + /** + * @return string + */ + public function isCommentVisible() + { + return $this->isCommentVisible; + } +} diff --git a/PhpOffice/PhpPresentation/Reader/ODPresentation.php b/PhpOffice/PhpPresentation/Reader/ODPresentation.php new file mode 100644 index 0000000..d658322 --- /dev/null +++ b/PhpOffice/PhpPresentation/Reader/ODPresentation.php @@ -0,0 +1,576 @@ +fileSupportsUnserializePhpPresentation($pFilename); + } + + /** + * Does a file support UnserializePhpPresentation ? + * + * @param string $pFilename + * @throws \Exception + * @return boolean + */ + public function fileSupportsUnserializePhpPresentation($pFilename = '') + { + // Check if file exists + if (!file_exists($pFilename)) { + throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + } + + $oZip = new ZipArchive(); + // Is it a zip ? + if ($oZip->open($pFilename) === true) { + // Is it an OpenXML Document ? + // Is it a Presentation ? + if (is_array($oZip->statName('META-INF/manifest.xml')) && is_array($oZip->statName('mimetype')) && $oZip->getFromName('mimetype') == 'application/vnd.oasis.opendocument.presentation') { + return true; + } + } + return false; + } + + /** + * Loads PhpPresentation Serialized file + * + * @param string $pFilename + * @return \PhpOffice\PhpPresentation\PhpPresentation + * @throws \Exception + */ + public function load($pFilename) + { + // Unserialize... First make sure the file supports it! + if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { + throw new \Exception("Invalid file format for PhpOffice\PhpPresentation\Reader\ODPresentation: " . $pFilename . "."); + } + + return $this->loadFile($pFilename); + } + + /** + * Load PhpPresentation Serialized file + * + * @param string $pFilename + * @return \PhpOffice\PhpPresentation\PhpPresentation + * @throws \Exception + */ + protected function loadFile($pFilename) + { + $this->oPhpPresentation = new PhpPresentation(); + $this->oPhpPresentation->removeSlideByIndex(); + + $this->oZip = new ZipArchive(); + $this->oZip->open($pFilename); + + $this->oXMLReader = new XMLReader(); + if ($this->oXMLReader->getDomFromZip($pFilename, 'meta.xml') !== false) { + $this->loadDocumentProperties(); + } + $this->oXMLReader = new XMLReader(); + if ($this->oXMLReader->getDomFromZip($pFilename, 'styles.xml') !== false) { + $this->loadStylesFile(); + } + $this->oXMLReader = new XMLReader(); + if ($this->oXMLReader->getDomFromZip($pFilename, 'content.xml') !== false) { + $this->loadSlides(); + } + + return $this->oPhpPresentation; + } + + /** + * Read Document Properties + */ + protected function loadDocumentProperties() + { + $arrayProperties = array( + '/office:document-meta/office:meta/meta:initial-creator' => 'setCreator', + '/office:document-meta/office:meta/dc:creator' => 'setLastModifiedBy', + '/office:document-meta/office:meta/dc:title' => 'setTitle', + '/office:document-meta/office:meta/dc:description' => 'setDescription', + '/office:document-meta/office:meta/dc:subject' => 'setSubject', + '/office:document-meta/office:meta/meta:keyword' => 'setKeywords', + '/office:document-meta/office:meta/meta:creation-date' => 'setCreated', + '/office:document-meta/office:meta/dc:date' => 'setModified', + ); + $oProperties = $this->oPhpPresentation->getDocumentProperties(); + foreach ($arrayProperties as $path => $property) { + $oElement = $this->oXMLReader->getElement($path); + if ($oElement instanceof \DOMElement) { + if (in_array($property, array('setCreated', 'setModified'))) { + $oDateTime = new \DateTime(); + $oDateTime->createFromFormat(\DateTime::W3C, $oElement->nodeValue); + $oProperties->{$property}($oDateTime->getTimestamp()); + } else { + $oProperties->{$property}($oElement->nodeValue); + } + } + } + } + + /** + * Extract all slides + */ + protected function loadSlides() + { + foreach ($this->oXMLReader->getElements('/office:document-content/office:automatic-styles/*') as $oElement) { + if ($oElement->hasAttribute('style:name')) { + $this->loadStyle($oElement); + } + } + foreach ($this->oXMLReader->getElements('/office:document-content/office:body/office:presentation/draw:page') as $oElement) { + if ($oElement->nodeName == 'draw:page') { + $this->loadSlide($oElement); + } + } + } + + /** + * Extract style + * @param \DOMElement $nodeStyle + * @return bool + * @throws \Exception + */ + protected function loadStyle(\DOMElement $nodeStyle) + { + $keyStyle = $nodeStyle->getAttribute('style:name'); + + $nodeDrawingPageProps = $this->oXMLReader->getElement('style:drawing-page-properties', $nodeStyle); + if ($nodeDrawingPageProps instanceof \DOMElement) { + // Read Background Color + if ($nodeDrawingPageProps->hasAttribute('draw:fill-color') && $nodeDrawingPageProps->getAttribute('draw:fill') == 'solid') { + $oBackground = new \PhpOffice\PhpPresentation\Slide\Background\Color(); + $oColor = new Color(); + $oColor->setRGB(substr($nodeDrawingPageProps->getAttribute('draw:fill-color'), -6)); + $oBackground->setColor($oColor); + } + // Read Background Image + if ($nodeDrawingPageProps->getAttribute('draw:fill') == 'bitmap' && $nodeDrawingPageProps->hasAttribute('draw:fill-image-name')) { + $nameStyle = $nodeDrawingPageProps->getAttribute('draw:fill-image-name'); + if (!empty($this->arrayCommonStyles[$nameStyle]) && $this->arrayCommonStyles[$nameStyle]['type'] == 'image' && !empty($this->arrayCommonStyles[$nameStyle]['path'])) { + $tmpBkgImg = tempnam(sys_get_temp_dir(), 'PhpPresentationReaderODPBkg'); + $contentImg = $this->oZip->getFromName($this->arrayCommonStyles[$nameStyle]['path']); + file_put_contents($tmpBkgImg, $contentImg); + + $oBackground = new Image(); + $oBackground->setPath($tmpBkgImg); + } + } + } + + $nodeGraphicProps = $this->oXMLReader->getElement('style:graphic-properties', $nodeStyle); + if ($nodeGraphicProps instanceof \DOMElement) { + // Read Shadow + if ($nodeGraphicProps->hasAttribute('draw:shadow') && $nodeGraphicProps->getAttribute('draw:shadow') == 'visible') { + $oShadow = new Shadow(); + $oShadow->setVisible(true); + if ($nodeGraphicProps->hasAttribute('draw:shadow-color')) { + $oShadow->getColor()->setRGB(substr($nodeGraphicProps->getAttribute('draw:shadow-color'), -6)); + } + if ($nodeGraphicProps->hasAttribute('draw:shadow-opacity')) { + $oShadow->setAlpha(100 - (int)substr($nodeGraphicProps->getAttribute('draw:shadow-opacity'), 0, -1)); + } + if ($nodeGraphicProps->hasAttribute('draw:shadow-offset-x') && $nodeGraphicProps->hasAttribute('draw:shadow-offset-y')) { + $offsetX = substr($nodeGraphicProps->getAttribute('draw:shadow-offset-x'), 0, -2); + $offsetY = substr($nodeGraphicProps->getAttribute('draw:shadow-offset-y'), 0, -2); + $distance = 0; + if ($offsetX != 0) { + $distance = ($offsetX < 0 ? $offsetX * -1 : $offsetX); + } elseif ($offsetY != 0) { + $distance = ($offsetY < 0 ? $offsetY * -1 : $offsetY); + } + $oShadow->setDirection(rad2deg(atan2($offsetY, $offsetX))); + $oShadow->setDistance(CommonDrawing::centimetersToPixels($distance)); + } + } + // Read Fill + if ($nodeGraphicProps->hasAttribute('draw:fill')) { + $value = $nodeGraphicProps->getAttribute('draw:fill'); + + switch ($value) { + case 'none': + $oFill = new Fill(); + $oFill->setFillType(Fill::FILL_NONE); + break; + case 'solid': + $oFill = new Fill(); + $oFill->setFillType(Fill::FILL_SOLID); + if ($nodeGraphicProps->hasAttribute('draw:fill-color')) { + $oColor = new Color(); + $oColor->setRGB(substr($nodeGraphicProps->getAttribute('draw:fill-color'), 1)); + $oFill->setStartColor($oColor); + } + break; + } + } + } + + $nodeTextProperties = $this->oXMLReader->getElement('style:text-properties', $nodeStyle); + if ($nodeTextProperties instanceof \DOMElement) { + $oFont = new Font(); + if ($nodeTextProperties->hasAttribute('fo:color')) { + $oFont->getColor()->setRGB(substr($nodeTextProperties->getAttribute('fo:color'), -6)); + } + if ($nodeTextProperties->hasAttribute('fo:font-family')) { + $oFont->setName($nodeTextProperties->getAttribute('fo:font-family')); + } + if ($nodeTextProperties->hasAttribute('fo:font-weight') && $nodeTextProperties->getAttribute('fo:font-weight') == 'bold') { + $oFont->setBold(true); + } + if ($nodeTextProperties->hasAttribute('fo:font-size')) { + $oFont->setSize(substr($nodeTextProperties->getAttribute('fo:font-size'), 0, -2)); + } + } + + $nodeParagraphProps = $this->oXMLReader->getElement('style:paragraph-properties', $nodeStyle); + if ($nodeParagraphProps instanceof \DOMElement) { + $oAlignment = new Alignment(); + if ($nodeParagraphProps->hasAttribute('fo:text-align')) { + $oAlignment->setHorizontal($nodeParagraphProps->getAttribute('fo:text-align')); + } + } + + if ($nodeStyle->nodeName == 'text:list-style') { + $arrayListStyle = array(); + foreach ($this->oXMLReader->getElements('text:list-level-style-bullet', $nodeStyle) as $oNodeListLevel) { + $oAlignment = new Alignment(); + $oBullet = new Bullet(); + $oBullet->setBulletType(Bullet::TYPE_NONE); + if ($oNodeListLevel->hasAttribute('text:level')) { + $oAlignment->setLevel((int) $oNodeListLevel->getAttribute('text:level') - 1); + } + if ($oNodeListLevel->hasAttribute('text:bullet-char')) { + $oBullet->setBulletChar($oNodeListLevel->getAttribute('text:bullet-char')); + $oBullet->setBulletType(Bullet::TYPE_BULLET); + } + + $oNodeListProperties = $this->oXMLReader->getElement('style:list-level-properties', $oNodeListLevel); + if ($oNodeListProperties instanceof \DOMElement) { + if ($oNodeListProperties->hasAttribute('text:min-label-width')) { + $oAlignment->setIndent((int)round(CommonDrawing::centimetersToPixels(substr($oNodeListProperties->getAttribute('text:min-label-width'), 0, -2)))); + } + if ($oNodeListProperties->hasAttribute('text:space-before')) { + $iSpaceBefore = CommonDrawing::centimetersToPixels(substr($oNodeListProperties->getAttribute('text:space-before'), 0, -2)); + $iMarginLeft = $iSpaceBefore + $oAlignment->getIndent(); + $oAlignment->setMarginLeft($iMarginLeft); + } + } + $oNodeTextProperties = $this->oXMLReader->getElement('style:text-properties', $oNodeListLevel); + if ($oNodeTextProperties instanceof \DOMElement) { + if ($oNodeTextProperties->hasAttribute('fo:font-family')) { + $oBullet->setBulletFont($oNodeTextProperties->getAttribute('fo:font-family')); + } + } + + $arrayListStyle[$oAlignment->getLevel()] = array( + 'alignment' => $oAlignment, + 'bullet' => $oBullet, + ); + } + } + + $this->arrayStyles[$keyStyle] = array( + 'alignment' => isset($oAlignment) ? $oAlignment : null, + 'background' => isset($oBackground) ? $oBackground : null, + 'fill' => isset($oFill) ? $oFill : null, + 'font' => isset($oFont) ? $oFont : null, + 'shadow' => isset($oShadow) ? $oShadow : null, + 'listStyle' => isset($arrayListStyle) ? $arrayListStyle : null, + ); + + return true; + } + + /** + * Read Slide + * + * @param \DOMElement $nodeSlide + * @return bool + * @throws \Exception + */ + protected function loadSlide(\DOMElement $nodeSlide) + { + // Core + $this->oPhpPresentation->createSlide(); + $this->oPhpPresentation->setActiveSlideIndex($this->oPhpPresentation->getSlideCount() - 1); + if ($nodeSlide->hasAttribute('draw:name')) { + $this->oPhpPresentation->getActiveSlide()->setName($nodeSlide->getAttribute('draw:name')); + } + if ($nodeSlide->hasAttribute('draw:style-name')) { + $keyStyle = $nodeSlide->getAttribute('draw:style-name'); + if (isset($this->arrayStyles[$keyStyle])) { + $this->oPhpPresentation->getActiveSlide()->setBackground($this->arrayStyles[$keyStyle]['background']); + } + } + foreach ($this->oXMLReader->getElements('draw:frame', $nodeSlide) as $oNodeFrame) { + if ($this->oXMLReader->getElement('draw:image', $oNodeFrame)) { + $this->loadShapeDrawing($oNodeFrame); + continue; + } + if ($this->oXMLReader->getElement('draw:text-box', $oNodeFrame)) { + $this->loadShapeRichText($oNodeFrame); + continue; + } + } + return true; + } + + /** + * Read Shape Drawing + * + * @param \DOMElement $oNodeFrame + * @throws \Exception + */ + protected function loadShapeDrawing(\DOMElement $oNodeFrame) + { + // Core + $oShape = new Gd(); + $oShape->getShadow()->setVisible(false); + + $oNodeImage = $this->oXMLReader->getElement('draw:image', $oNodeFrame); + if ($oNodeImage instanceof \DOMElement) { + if ($oNodeImage->hasAttribute('xlink:href')) { + $sFilename = $oNodeImage->getAttribute('xlink:href'); + // svm = StarView Metafile + if (pathinfo($sFilename, PATHINFO_EXTENSION) == 'svm') { + return; + } + $imageFile = $this->oZip->getFromName($sFilename); + if (!empty($imageFile)) { + $oShape->setImageResource(imagecreatefromstring($imageFile)); + } + } + } + + $oShape->setName($oNodeFrame->hasAttribute('draw:name') ? $oNodeFrame->getAttribute('draw:name') : ''); + $oShape->setDescription($oNodeFrame->hasAttribute('draw:name') ? $oNodeFrame->getAttribute('draw:name') : ''); + $oShape->setResizeProportional(false); + $oShape->setWidth($oNodeFrame->hasAttribute('svg:width') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:width'), 0, -2))) : ''); + $oShape->setHeight($oNodeFrame->hasAttribute('svg:height') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:height'), 0, -2))) : ''); + $oShape->setResizeProportional(true); + $oShape->setOffsetX($oNodeFrame->hasAttribute('svg:x') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:x'), 0, -2))) : ''); + $oShape->setOffsetY($oNodeFrame->hasAttribute('svg:y') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:y'), 0, -2))) : ''); + + if ($oNodeFrame->hasAttribute('draw:style-name')) { + $keyStyle = $oNodeFrame->getAttribute('draw:style-name'); + if (isset($this->arrayStyles[$keyStyle])) { + $oShape->setShadow($this->arrayStyles[$keyStyle]['shadow']); + $oShape->setFill($this->arrayStyles[$keyStyle]['fill']); + } + } + + $this->oPhpPresentation->getActiveSlide()->addShape($oShape); + } + + /** + * Read Shape RichText + * + * @param \DOMElement $oNodeFrame + * @throws \Exception + */ + protected function loadShapeRichText(\DOMElement $oNodeFrame) + { + // Core + $oShape = $this->oPhpPresentation->getActiveSlide()->createRichTextShape(); + $oShape->setParagraphs(array()); + + $oShape->setWidth($oNodeFrame->hasAttribute('svg:width') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:width'), 0, -2))) : ''); + $oShape->setHeight($oNodeFrame->hasAttribute('svg:height') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:height'), 0, -2))) : ''); + $oShape->setOffsetX($oNodeFrame->hasAttribute('svg:x') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:x'), 0, -2))) : ''); + $oShape->setOffsetY($oNodeFrame->hasAttribute('svg:y') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:y'), 0, -2))) : ''); + + foreach ($this->oXMLReader->getElements('draw:text-box/*', $oNodeFrame) as $oNodeParagraph) { + $this->levelParagraph = 0; + if ($oNodeParagraph->nodeName == 'text:p') { + $this->readParagraph($oShape, $oNodeParagraph); + } + if ($oNodeParagraph->nodeName == 'text:list') { + $this->readList($oShape, $oNodeParagraph); + } + } + + if (count($oShape->getParagraphs()) > 0) { + $oShape->setActiveParagraph(0); + } + } + + protected $levelParagraph = 0; + + /** + * Read Paragraph + * @param RichText $oShape + * @param \DOMElement $oNodeParent + * @throws \Exception + */ + protected function readParagraph(RichText $oShape, \DOMElement $oNodeParent) + { + $oParagraph = $oShape->createParagraph(); + $oDomList = $this->oXMLReader->getElements('text:span', $oNodeParent); + $oDomTextNodes = $this->oXMLReader->getElements('text()', $oNodeParent); + foreach ($oDomTextNodes as $oDomTextNode) { + if (trim($oDomTextNode->nodeValue) != '') { + $oTextRun = $oParagraph->createTextRun(); + $oTextRun->setText(trim($oDomTextNode->nodeValue)); + } + } + foreach ($oDomList as $oNodeRichTextElement) { + $this->readParagraphItem($oParagraph, $oNodeRichTextElement); + } + } + + /** + * Read Paragraph Item + * @param Paragraph $oParagraph + * @param \DOMElement $oNodeParent + * @throws \Exception + */ + protected function readParagraphItem(Paragraph $oParagraph, \DOMElement $oNodeParent) + { + if ($this->oXMLReader->elementExists('text:line-break', $oNodeParent)) { + $oParagraph->createBreak(); + } else { + $oTextRun = $oParagraph->createTextRun(); + if ($oNodeParent->hasAttribute('text:style-name')) { + $keyStyle = $oNodeParent->getAttribute('text:style-name'); + if (isset($this->arrayStyles[$keyStyle])) { + $oTextRun->setFont($this->arrayStyles[$keyStyle]['font']); + } + } + $oTextRunLink = $this->oXMLReader->getElement('text:a', $oNodeParent); + if ($oTextRunLink instanceof \DOMElement) { + $oTextRun->setText($oTextRunLink->nodeValue); + if ($oTextRunLink->hasAttribute('xlink:href')) { + $oTextRun->getHyperlink()->setUrl($oTextRunLink->getAttribute('xlink:href')); + } + } else { + $oTextRun->setText($oNodeParent->nodeValue); + } + } + } + + /** + * Read List + * + * @param RichText $oShape + * @param \DOMElement $oNodeParent + * @throws \Exception + */ + protected function readList(RichText $oShape, \DOMElement $oNodeParent) + { + foreach ($this->oXMLReader->getElements('text:list-item/*', $oNodeParent) as $oNodeListItem) { + if ($oNodeListItem->nodeName == 'text:p') { + $this->readListItem($oShape, $oNodeListItem, $oNodeParent); + } + if ($oNodeListItem->nodeName == 'text:list') { + $this->levelParagraph++; + $this->readList($oShape, $oNodeListItem); + $this->levelParagraph--; + } + } + } + + /** + * Read List Item + * @param RichText $oShape + * @param \DOMElement $oNodeParent + * @param \DOMElement $oNodeParagraph + * @throws \Exception + */ + protected function readListItem(RichText $oShape, \DOMElement $oNodeParent, \DOMElement $oNodeParagraph) + { + $oParagraph = $oShape->createParagraph(); + if ($oNodeParagraph->hasAttribute('text:style-name')) { + $keyStyle = $oNodeParagraph->getAttribute('text:style-name'); + if (isset($this->arrayStyles[$keyStyle]) && !empty($this->arrayStyles[$keyStyle]['listStyle'])) { + $oParagraph->setAlignment($this->arrayStyles[$keyStyle]['listStyle'][$this->levelParagraph]['alignment']); + $oParagraph->setBulletStyle($this->arrayStyles[$keyStyle]['listStyle'][$this->levelParagraph]['bullet']); + } + } + foreach ($this->oXMLReader->getElements('text:span', $oNodeParent) as $oNodeRichTextElement) { + $this->readParagraphItem($oParagraph, $oNodeRichTextElement); + } + } + + /** + * Load file 'styles.xml' + */ + protected function loadStylesFile() + { + foreach ($this->oXMLReader->getElements('/office:document-styles/office:styles/*') as $oElement) { + if ($oElement->nodeName == 'draw:fill-image') { + $this->arrayCommonStyles[$oElement->getAttribute('draw:name')] = array( + 'type' => 'image', + 'path' => $oElement->hasAttribute('xlink:href') ? $oElement->getAttribute('xlink:href') : null + ); + } + } + } +} diff --git a/PhpOffice/PhpPresentation/Reader/PowerPoint2007.php b/PhpOffice/PhpPresentation/Reader/PowerPoint2007.php new file mode 100644 index 0000000..aa3b847 --- /dev/null +++ b/PhpOffice/PhpPresentation/Reader/PowerPoint2007.php @@ -0,0 +1,1314 @@ +fileSupportsUnserializePhpPresentation($pFilename); + } + + /** + * Does a file support UnserializePhpPresentation ? + * + * @param string $pFilename + * @throws \Exception + * @return boolean + */ + public function fileSupportsUnserializePhpPresentation($pFilename = '') + { + // Check if file exists + if (!file_exists($pFilename)) { + throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + } + + $oZip = new ZipArchive(); + // Is it a zip ? + if ($oZip->open($pFilename) === true) { + // Is it an OpenXML Document ? + // Is it a Presentation ? + if (is_array($oZip->statName('[Content_Types].xml')) && is_array($oZip->statName('ppt/presentation.xml'))) { + return true; + } + } + return false; + } + + /** + * Loads PhpPresentation Serialized file + * + * @param string $pFilename + * @return \PhpOffice\PhpPresentation\PhpPresentation + * @throws \Exception + */ + public function load($pFilename) + { + // Unserialize... First make sure the file supports it! + if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { + throw new \Exception("Invalid file format for PhpOffice\PhpPresentation\Reader\PowerPoint2007: " . $pFilename . "."); + } + + return $this->loadFile($pFilename); + } + + /** + * Load PhpPresentation Serialized file + * + * @param string $pFilename + * @return \PhpOffice\PhpPresentation\PhpPresentation + * @throws \Exception + */ + protected function loadFile($pFilename) + { + $this->oPhpPresentation = new PhpPresentation(); + $this->oPhpPresentation->removeSlideByIndex(); + $this->oPhpPresentation->setAllMasterSlides(array()); + $this->filename = $pFilename; + + $this->oZip = new ZipArchive(); + $this->oZip->open($this->filename); + $docPropsCore = $this->oZip->getFromName('docProps/core.xml'); + if ($docPropsCore !== false) { + $this->loadDocumentProperties($docPropsCore); + } + + $docPropsCustom = $this->oZip->getFromName('docProps/custom.xml'); + if ($docPropsCustom !== false) { + $this->loadCustomProperties($docPropsCustom); + } + + $pptViewProps = $this->oZip->getFromName('ppt/viewProps.xml'); + if ($pptViewProps !== false) { + $this->loadViewProperties($pptViewProps); + } + + $pptPresentation = $this->oZip->getFromName('ppt/presentation.xml'); + if ($pptPresentation !== false) { + $this->loadDocumentLayout($pptPresentation); + $this->loadSlides($pptPresentation); + } + + return $this->oPhpPresentation; + } + + /** + * Read Document Layout + * @param $sPart + */ + protected function loadDocumentLayout($sPart) + { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + foreach ($xmlReader->getElements('/p:presentation/p:sldSz') as $oElement) { + if (!($oElement instanceof \DOMElement)) { + continue; + } + $type = $oElement->getAttribute('type'); + $oLayout = $this->oPhpPresentation->getLayout(); + if ($type == DocumentLayout::LAYOUT_CUSTOM) { + $oLayout->setCX($oElement->getAttribute('cx')); + $oLayout->setCY($oElement->getAttribute('cy')); + } else { + $oLayout->setDocumentLayout($type, true); + if ($oElement->getAttribute('cx') < $oElement->getAttribute('cy')) { + $oLayout->setDocumentLayout($type, false); + } + } + } + } + } + + /** + * Read Document Properties + * @param string $sPart + */ + protected function loadDocumentProperties($sPart) + { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + $arrayProperties = array( + '/cp:coreProperties/dc:creator' => 'setCreator', + '/cp:coreProperties/cp:lastModifiedBy' => 'setLastModifiedBy', + '/cp:coreProperties/dc:title' => 'setTitle', + '/cp:coreProperties/dc:description' => 'setDescription', + '/cp:coreProperties/dc:subject' => 'setSubject', + '/cp:coreProperties/cp:keywords' => 'setKeywords', + '/cp:coreProperties/cp:category' => 'setCategory', + '/cp:coreProperties/dcterms:created' => 'setCreated', + '/cp:coreProperties/dcterms:modified' => 'setModified', + ); + $oProperties = $this->oPhpPresentation->getDocumentProperties(); + foreach ($arrayProperties as $path => $property) { + $oElement = $xmlReader->getElement($path); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('xsi:type') && $oElement->getAttribute('xsi:type') == 'dcterms:W3CDTF') { + $oDateTime = new \DateTime(); + $oDateTime->createFromFormat(\DateTime::W3C, $oElement->nodeValue); + $oProperties->{$property}($oDateTime->getTimestamp()); + } else { + $oProperties->{$property}($oElement->nodeValue); + } + } + } + } + } + + /** + * Read Custom Properties + * @param string $sPart + */ + protected function loadCustomProperties($sPart) + { + $xmlReader = new XMLReader(); + $sPart = str_replace(' xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"', '', $sPart); + 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 ($oElement->nodeValue == 'true') { + $this->oPhpPresentation->getPresentationProperties()->markAsFinal(true); + } + } + } + } + + /** + * Read View Properties + * @param string $sPart + */ + protected function loadViewProperties($sPart) + { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + $pathZoom = '/p:viewPr/p:slideViewPr/p:cSldViewPr/p:cViewPr/p:scale/a:sx'; + $oElement = $xmlReader->getElement($pathZoom); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('d') && $oElement->hasAttribute('n')) { + $this->oPhpPresentation->getPresentationProperties()->setZoom($oElement->getAttribute('n') / $oElement->getAttribute('d')); + } + } + } + } + + /** + * Extract all slides + * @param $sPart + * @throws \Exception + */ + protected function loadSlides($sPart) + { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + $fileRels = 'ppt/_rels/presentation.xml.rels'; + $this->loadRels($fileRels); + // Load the Masterslides + $this->loadMasterSlides($xmlReader, $fileRels); + // Continue with loading the slides + foreach ($xmlReader->getElements('/p:presentation/p:sldIdLst/p:sldId') as $oElement) { + if (!($oElement instanceof \DOMElement)) { + continue; + } + $rId = $oElement->getAttribute('r:id'); + $pathSlide = isset($this->arrayRels[$fileRels][$rId]) ? $this->arrayRels[$fileRels][$rId]['Target'] : ''; + if (!empty($pathSlide)) { + $pptSlide = $this->oZip->getFromName('ppt/' . $pathSlide); + if ($pptSlide !== false) { + $slideRels = 'ppt/slides/_rels/' . basename($pathSlide) . '.rels'; + $this->loadRels($slideRels); + $this->loadSlide($pptSlide, basename($pathSlide)); + foreach ($this->arrayRels[$slideRels] as $rel) { + if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide') { + $this->loadSlideNote(basename($rel['Target']), $this->oPhpPresentation->getActiveSlide()); + } + } + } + } + } + } + } + + /** + * Extract all MasterSlides + * @param XMLReader $xmlReader + * @param string $fileRels + * @throws \Exception + */ + protected function loadMasterSlides(XMLReader $xmlReader, $fileRels) + { + // Get all the MasterSlide Id's from the presentation.xml file + foreach ($xmlReader->getElements('/p:presentation/p:sldMasterIdLst/p:sldMasterId') as $oElement) { + if (!($oElement instanceof \DOMElement)) { + continue; + } + $rId = $oElement->getAttribute('r:id'); + // Get the path to the masterslide from the array with _rels files + $pathMasterSlide = isset($this->arrayRels[$fileRels][$rId]) ? + $this->arrayRels[$fileRels][$rId]['Target'] : ''; + if (!empty($pathMasterSlide)) { + $pptMasterSlide = $this->oZip->getFromName('ppt/' . $pathMasterSlide); + if ($pptMasterSlide !== false) { + $this->loadRels('ppt/slideMasters/_rels/' . basename($pathMasterSlide) . '.rels'); + $this->loadMasterSlide($pptMasterSlide, basename($pathMasterSlide)); + } + } + } + } + + /** + * Extract data from slide + * @param string $sPart + * @param string $baseFile + * @throws \Exception + */ + protected function loadSlide($sPart, $baseFile) + { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + // Core + $oSlide = $this->oPhpPresentation->createSlide(); + $this->oPhpPresentation->setActiveSlideIndex($this->oPhpPresentation->getSlideCount() - 1); + $oSlide->setRelsIndex('ppt/slides/_rels/' . $baseFile . '.rels'); + + // Background + $oElement = $xmlReader->getElement('/p:sld/p:cSld/p:bg/p:bgPr'); + if ($oElement instanceof \DOMElement) { + $oElementColor = $xmlReader->getElement('a:solidFill/a:srgbClr', $oElement); + if ($oElementColor instanceof \DOMElement) { + // Color + $oColor = new Color(); + $oColor->setRGB($oElementColor->hasAttribute('val') ? $oElementColor->getAttribute('val') : null); + // Background + $oBackground = new Slide\Background\Color(); + $oBackground->setColor($oColor); + // Slide Background + $oSlide = $this->oPhpPresentation->getActiveSlide(); + $oSlide->setBackground($oBackground); + } + $oElementColor = $xmlReader->getElement('a:solidFill/a:schemeClr', $oElement); + if ($oElementColor instanceof \DOMElement) { + // Color + $oColor = new SchemeColor(); + $oColor->setValue($oElementColor->hasAttribute('val') ? $oElementColor->getAttribute('val') : null); + // Background + $oBackground = new Slide\Background\SchemeColor(); + $oBackground->setSchemeColor($oColor); + // Slide Background + $oSlide = $this->oPhpPresentation->getActiveSlide(); + $oSlide->setBackground($oBackground); + } + $oElementImage = $xmlReader->getElement('a:blipFill/a:blip', $oElement); + if ($oElementImage instanceof \DOMElement) { + $relImg = $this->arrayRels['ppt/slides/_rels/' . $baseFile . '.rels'][$oElementImage->getAttribute('r:embed')]; + if (is_array($relImg)) { + // File + $pathImage = 'ppt/slides/' . $relImg['Target']; + $pathImage = explode('/', $pathImage); + foreach ($pathImage as $key => $partPath) { + if ($partPath == '..') { + unset($pathImage[$key - 1]); + unset($pathImage[$key]); + } + } + $pathImage = implode('/', $pathImage); + $contentImg = $this->oZip->getFromName($pathImage); + + $tmpBkgImg = tempnam(sys_get_temp_dir(), 'PhpPresentationReaderPpt2007Bkg'); + file_put_contents($tmpBkgImg, $contentImg); + // Background + $oBackground = new Slide\Background\Image(); + $oBackground->setPath($tmpBkgImg); + // Slide Background + $oSlide = $this->oPhpPresentation->getActiveSlide(); + $oSlide->setBackground($oBackground); + } + } + } + + // Shapes + $arrayElements = $xmlReader->getElements('/p:sld/p:cSld/p:spTree/*'); + if ($arrayElements) { + $this->loadSlideShapes($oSlide, $arrayElements, $xmlReader); + } + + // Layout + $oSlide = $this->oPhpPresentation->getActiveSlide(); + foreach ($this->arrayRels['ppt/slides/_rels/' . $baseFile . '.rels'] as $valueRel) { + if ($valueRel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout') { + $layoutBasename = basename($valueRel['Target']); + if (array_key_exists($layoutBasename, $this->arraySlideLayouts)) { + $oSlide->setSlideLayout($this->arraySlideLayouts[$layoutBasename]); + } + break; + } + } + } + } + + /** + * @param string $sPart + * @param string $baseFile + * @throws \Exception + */ + protected function loadMasterSlide($sPart, $baseFile) + { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + // Core + $oSlideMaster = $this->oPhpPresentation->createMasterSlide(); + $oSlideMaster->setTextStyles(new TextStyle(false)); + $oSlideMaster->setRelsIndex('ppt/slideMasters/_rels/' . $baseFile . '.rels'); + + // Background + $oElement = $xmlReader->getElement('/p:sldMaster/p:cSld/p:bg'); + if ($oElement instanceof \DOMElement) { + $this->loadSlideBackground($xmlReader, $oElement, $oSlideMaster); + } + + // Shapes + $arrayElements = $xmlReader->getElements('/p:sldMaster/p:cSld/p:spTree/*'); + if ($arrayElements) { + $this->loadSlideShapes($oSlideMaster, $arrayElements, $xmlReader); + } + // Header & Footer + + // ColorMapping + $colorMap = array(); + $oElement = $xmlReader->getElement('/p:sldMaster/p:clrMap'); + if ($oElement->hasAttributes()) { + foreach ($oElement->attributes as $attr) { + $colorMap[$attr->nodeName] = $attr->nodeValue; + } + $oSlideMaster->colorMap->setMapping($colorMap); + } + + // TextStyles + $arrayElementTxStyles = $xmlReader->getElements('/p:sldMaster/p:txStyles/*'); + if ($arrayElementTxStyles) { + foreach ($arrayElementTxStyles as $oElementTxStyle) { + $arrayElementsLvl = $xmlReader->getElements('/p:sldMaster/p:txStyles/' . $oElementTxStyle->nodeName . '/*'); + foreach ($arrayElementsLvl as $oElementLvl) { + if (!($oElementLvl instanceof \DOMElement) || $oElementLvl->nodeName == 'a:extLst') { + continue; + } + $oRTParagraph = new Paragraph(); + + if ($oElementLvl->nodeName == 'a:defPPr') { + $level = 0; + } else { + $level = str_replace('a:lvl', '', $oElementLvl->nodeName); + $level = str_replace('pPr', '', $level); + } + + if ($oElementLvl->hasAttribute('algn')) { + $oRTParagraph->getAlignment()->setHorizontal($oElementLvl->getAttribute('algn')); + } + if ($oElementLvl->hasAttribute('marL')) { + $val = $oElementLvl->getAttribute('marL'); + $val = CommonDrawing::emuToPixels($val); + $oRTParagraph->getAlignment()->setMarginLeft($val); + } + if ($oElementLvl->hasAttribute('marR')) { + $val = $oElementLvl->getAttribute('marR'); + $val = CommonDrawing::emuToPixels($val); + $oRTParagraph->getAlignment()->setMarginRight($val); + } + if ($oElementLvl->hasAttribute('indent')) { + $val = $oElementLvl->getAttribute('indent'); + $val = CommonDrawing::emuToPixels($val); + $oRTParagraph->getAlignment()->setIndent($val); + } + $oElementLvlDefRPR = $xmlReader->getElement('a:defRPr', $oElementLvl); + if ($oElementLvlDefRPR instanceof \DOMElement) { + if ($oElementLvlDefRPR->hasAttribute('sz')) { + $oRTParagraph->getFont()->setSize($oElementLvlDefRPR->getAttribute('sz') / 100); + } + if ($oElementLvlDefRPR->hasAttribute('b') && $oElementLvlDefRPR->getAttribute('b') == 1) { + $oRTParagraph->getFont()->setBold(true); + } + if ($oElementLvlDefRPR->hasAttribute('i') && $oElementLvlDefRPR->getAttribute('i') == 1) { + $oRTParagraph->getFont()->setItalic(true); + } + } + $oElementSchemeColor = $xmlReader->getElement('a:defRPr/a:solidFill/a:schemeClr', $oElementLvl); + if ($oElementSchemeColor instanceof \DOMElement) { + if ($oElementSchemeColor->hasAttribute('val')) { + $oSchemeColor = new SchemeColor(); + $oSchemeColor->setValue($oElementSchemeColor->getAttribute('val')); + $oRTParagraph->getFont()->setColor($oSchemeColor); + } + } + + switch ($oElementTxStyle->nodeName) { + case 'p:bodyStyle': + $oSlideMaster->getTextStyles()->setBodyStyleAtLvl($oRTParagraph, $level); + break; + case 'p:otherStyle': + $oSlideMaster->getTextStyles()->setOtherStyleAtLvl($oRTParagraph, $level); + break; + case 'p:titleStyle': + $oSlideMaster->getTextStyles()->setTitleStyleAtLvl($oRTParagraph, $level); + break; + } + } + } + } + + // Load the theme + foreach ($this->arrayRels[$oSlideMaster->getRelsIndex()] as $arrayRel) { + if ($arrayRel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') { + $pptTheme = $this->oZip->getFromName('ppt/' . substr($arrayRel['Target'], strrpos($arrayRel['Target'], '../') + 3)); + if ($pptTheme !== false) { + $this->loadTheme($pptTheme, $oSlideMaster); + } + break; + } + } + + // Load the Layoutslide + foreach ($xmlReader->getElements('/p:sldMaster/p:sldLayoutIdLst/p:sldLayoutId') as $oElement) { + if (!($oElement instanceof \DOMElement)) { + continue; + } + $rId = $oElement->getAttribute('r:id'); + // Get the path to the masterslide from the array with _rels files + $pathLayoutSlide = isset($this->arrayRels[$oSlideMaster->getRelsIndex()][$rId]) ? + $this->arrayRels[$oSlideMaster->getRelsIndex()][$rId]['Target'] : ''; + if (!empty($pathLayoutSlide)) { + $pptLayoutSlide = $this->oZip->getFromName('ppt/' . substr($pathLayoutSlide, strrpos($pathLayoutSlide, '../') + 3)); + if ($pptLayoutSlide !== false) { + $this->loadRels('ppt/slideLayouts/_rels/' . basename($pathLayoutSlide) . '.rels'); + $oSlideMaster->addSlideLayout( + $this->loadLayoutSlide($pptLayoutSlide, basename($pathLayoutSlide), $oSlideMaster) + ); + } + } + } + } + } + + /** + * @param string $sPart + * @param string $baseFile + * @param SlideMaster $oSlideMaster + * @return SlideLayout|null + * @throws \Exception + */ + protected function loadLayoutSlide($sPart, $baseFile, SlideMaster $oSlideMaster) + { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + // Core + $oSlideLayout = new SlideLayout($oSlideMaster); + $oSlideLayout->setRelsIndex('ppt/slideLayouts/_rels/' . $baseFile . '.rels'); + + // Name + $oElement = $xmlReader->getElement('/p:sldLayout/p:cSld'); + if ($oElement instanceof \DOMElement && $oElement->hasAttribute('name')) { + $oSlideLayout->setLayoutName($oElement->getAttribute('name')); + } + + // Background + $oElement = $xmlReader->getElement('/p:sldLayout/p:cSld/p:bg'); + if ($oElement instanceof \DOMElement) { + $this->loadSlideBackground($xmlReader, $oElement, $oSlideLayout); + } + + // ColorMapping + $oElement = $xmlReader->getElement('/p:sldLayout/p:clrMapOvr/a:overrideClrMapping'); + if ($oElement instanceof \DOMElement && $oElement->hasAttributes()) { + $colorMap = array(); + foreach ($oElement->attributes as $attr) { + $colorMap[$attr->nodeName] = $attr->nodeValue; + } + $oSlideLayout->colorMap->setMapping($colorMap); + } + + // Shapes + $oElements = $xmlReader->getElements('/p:sldLayout/p:cSld/p:spTree/*'); + if ($oElements) { + $this->loadSlideShapes($oSlideLayout, $oElements, $xmlReader); + } + $this->arraySlideLayouts[$baseFile] = &$oSlideLayout; + return $oSlideLayout; + } + return null; + } + + /** + * @param string $sPart + * @param SlideMaster $oSlideMaster + */ + protected function loadTheme($sPart, SlideMaster $oSlideMaster) + { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + $oElements = $xmlReader->getElements('/a:theme/a:themeElements/a:clrScheme/*'); + if ($oElements) { + foreach ($oElements as $oElement) { + $oSchemeColor = new SchemeColor(); + $oSchemeColor->setValue(str_replace('a:', '', $oElement->tagName)); + $colorElement = $xmlReader->getElement('*', $oElement); + if ($colorElement instanceof \DOMElement) { + if ($colorElement->hasAttribute('lastClr')) { + $oSchemeColor->setRGB($colorElement->getAttribute('lastClr')); + } elseif ($colorElement->hasAttribute('val')) { + $oSchemeColor->setRGB($colorElement->getAttribute('val')); + } + } + $oSlideMaster->addSchemeColor($oSchemeColor); + } + } + } + } + + /** + * @param XMLReader $xmlReader + * @param \DOMElement $oElement + * @param AbstractSlide $oSlide + * @throws \Exception + */ + protected function loadSlideBackground(XMLReader $xmlReader, \DOMElement $oElement, AbstractSlide $oSlide) + { + // Background color + $oElementColor = $xmlReader->getElement('p:bgPr/a:solidFill/a:srgbClr', $oElement); + if ($oElementColor instanceof \DOMElement) { + // Color + $oColor = new Color(); + $oColor->setRGB($oElementColor->hasAttribute('val') ? $oElementColor->getAttribute('val') : null); + // Background + $oBackground = new Slide\Background\Color(); + $oBackground->setColor($oColor); + // Slide Background + $oSlide->setBackground($oBackground); + } + + // Background scheme color + $oElementSchemeColor = $xmlReader->getElement('p:bgRef/a:schemeClr', $oElement); + if ($oElementSchemeColor instanceof \DOMElement) { + // Color + $oColor = new SchemeColor(); + $oColor->setValue($oElementSchemeColor->hasAttribute('val') ? $oElementSchemeColor->getAttribute('val') : null); + // Background + $oBackground = new Slide\Background\SchemeColor(); + $oBackground->setSchemeColor($oColor); + // Slide Background + $oSlide->setBackground($oBackground); + } + + // Background image + $oElementImage = $xmlReader->getElement('p:bgPr/a:blipFill/a:blip', $oElement); + if ($oElementImage instanceof \DOMElement) { + $relImg = $this->arrayRels[$oSlide->getRelsIndex()][$oElementImage->getAttribute('r:embed')]; + if (is_array($relImg)) { + // File + $pathImage = 'ppt/slides/' . $relImg['Target']; + $pathImage = explode('/', $pathImage); + foreach ($pathImage as $key => $partPath) { + if ($partPath == '..') { + unset($pathImage[$key - 1]); + unset($pathImage[$key]); + } + } + $pathImage = implode('/', $pathImage); + $contentImg = $this->oZip->getFromName($pathImage); + + $tmpBkgImg = tempnam(sys_get_temp_dir(), 'PhpPresentationReaderPpt2007Bkg'); + file_put_contents($tmpBkgImg, $contentImg); + // Background + $oBackground = new Slide\Background\Image(); + $oBackground->setPath($tmpBkgImg); + // Slide Background + $oSlide->setBackground($oBackground); + } + } + } + + /** + * @param string $baseFile + * @param Slide $oSlide + * @throws \Exception + */ + protected function loadSlideNote($baseFile, Slide $oSlide) + { + $sPart = $this->oZip->getFromName('ppt/notesSlides/' . $baseFile); + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + $oNote = $oSlide->getNote(); + + $arrayElements = $xmlReader->getElements('/p:notes/p:cSld/p:spTree/*'); + if ($arrayElements) { + $this->loadSlideShapes($oNote, $arrayElements, $xmlReader); + } + } + } + + /** + * @param XMLReader $document + * @param \DOMElement $node + * @param AbstractSlide $oSlide + * @throws \Exception + */ + protected function loadShapeDrawing(XMLReader $document, \DOMElement $node, AbstractSlide $oSlide) + { + // Core + $oShape = new Gd(); + $oShape->getShadow()->setVisible(false); + // Variables + $fileRels = $oSlide->getRelsIndex(); + + $oElement = $document->getElement('p:nvPicPr/p:cNvPr', $node); + if ($oElement instanceof \DOMElement) { + $oShape->setName($oElement->hasAttribute('name') ? $oElement->getAttribute('name') : ''); + $oShape->setDescription($oElement->hasAttribute('descr') ? $oElement->getAttribute('descr') : ''); + + // Hyperlink + $oElementHlinkClick = $document->getElement('a:hlinkClick', $oElement); + if (is_object($oElementHlinkClick)) { + if ($oElementHlinkClick->hasAttribute('tooltip')) { + $oShape->getHyperlink()->setTooltip($oElementHlinkClick->getAttribute('tooltip')); + } + if ($oElementHlinkClick->hasAttribute('r:id') && isset($this->arrayRels[$fileRels][$oElementHlinkClick->getAttribute('r:id')]['Target'])) { + $oShape->getHyperlink()->setUrl($this->arrayRels[$fileRels][$oElementHlinkClick->getAttribute('r:id')]['Target']); + } + } + } + + $oElement = $document->getElement('p:blipFill/a:blip', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('r:embed') && isset($this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target'])) { + $pathImage = 'ppt/slides/' . $this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target']; + $pathImage = explode('/', $pathImage); + foreach ($pathImage as $key => $partPath) { + if ($partPath == '..') { + unset($pathImage[$key - 1]); + unset($pathImage[$key]); + } + } + $pathImage = implode('/', $pathImage); + $imageFile = $this->oZip->getFromName($pathImage); + if (!empty($imageFile)) { + $info = getimagesizefromstring($imageFile); + $oShape->setMimeType($info['mime']); + $oShape->setRenderingFunction(str_replace('/', '', $info['mime'])); + $oShape->setImageResource(imagecreatefromstring($imageFile)); + } + } + } + + $oElement = $document->getElement('p:spPr', $node); + if ($oElement instanceof \DOMElement) { + $oFill = $this->loadStyleFill($document, $oElement); + $oShape->setFill($oFill); + } + + $oElement = $document->getElement('p:spPr/a:xfrm', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('rot')) { + $oShape->setRotation(CommonDrawing::angleToDegrees($oElement->getAttribute('rot'))); + } + } + + $oElement = $document->getElement('p:spPr/a:xfrm/a:off', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('x')) { + $oShape->setOffsetX(CommonDrawing::emuToPixels($oElement->getAttribute('x'))); + } + if ($oElement->hasAttribute('y')) { + $oShape->setOffsetY(CommonDrawing::emuToPixels($oElement->getAttribute('y'))); + } + } + + $oElement = $document->getElement('p:spPr/a:xfrm/a:ext', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('cx')) { + $oShape->setWidth(CommonDrawing::emuToPixels($oElement->getAttribute('cx'))); + } + if ($oElement->hasAttribute('cy')) { + $oShape->setHeight(CommonDrawing::emuToPixels($oElement->getAttribute('cy'))); + } + } + + $oElement = $document->getElement('p:spPr/a:effectLst', $node); + if ($oElement instanceof \DOMElement) { + $oShape->getShadow()->setVisible(true); + + $oSubElement = $document->getElement('a:outerShdw', $oElement); + if ($oSubElement instanceof \DOMElement) { + if ($oSubElement->hasAttribute('blurRad')) { + $oShape->getShadow()->setBlurRadius(CommonDrawing::emuToPixels($oSubElement->getAttribute('blurRad'))); + } + if ($oSubElement->hasAttribute('dist')) { + $oShape->getShadow()->setDistance(CommonDrawing::emuToPixels($oSubElement->getAttribute('dist'))); + } + if ($oSubElement->hasAttribute('dir')) { + $oShape->getShadow()->setDirection(CommonDrawing::angleToDegrees($oSubElement->getAttribute('dir'))); + } + if ($oSubElement->hasAttribute('algn')) { + $oShape->getShadow()->setAlignment($oSubElement->getAttribute('algn')); + } + } + + $oSubElement = $document->getElement('a:outerShdw/a:srgbClr', $oElement); + if ($oSubElement instanceof \DOMElement) { + if ($oSubElement->hasAttribute('val')) { + $oColor = new Color(); + $oColor->setRGB($oSubElement->getAttribute('val')); + $oShape->getShadow()->setColor($oColor); + } + } + + $oSubElement = $document->getElement('a:outerShdw/a:srgbClr/a:alpha', $oElement); + if ($oSubElement instanceof \DOMElement) { + if ($oSubElement->hasAttribute('val')) { + $oShape->getShadow()->setAlpha((int)$oSubElement->getAttribute('val') / 1000); + } + } + } + + $oSlide->addShape($oShape); + } + + /** + * @param XMLReader $document + * @param \DOMElement $node + * @param AbstractSlide $oSlide + * @throws \Exception + */ + protected function loadShapeRichText(XMLReader $document, \DOMElement $node, $oSlide) + { + if (!$document->elementExists('p:txBody/a:p/a:r', $node)) { + return; + } + // Core + $oShape = $oSlide->createRichTextShape(); + $oShape->setParagraphs(array()); + // Variables + if ($oSlide instanceof AbstractSlide) { + $this->fileRels = $oSlide->getRelsIndex(); + } + + $oElement = $document->getElement('p:spPr/a:xfrm', $node); + if ($oElement instanceof \DOMElement && $oElement->hasAttribute('rot')) { + $oShape->setRotation(CommonDrawing::angleToDegrees($oElement->getAttribute('rot'))); + } + + $oElement = $document->getElement('p:spPr/a:xfrm/a:off', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('x')) { + $oShape->setOffsetX(CommonDrawing::emuToPixels($oElement->getAttribute('x'))); + } + if ($oElement->hasAttribute('y')) { + $oShape->setOffsetY(CommonDrawing::emuToPixels($oElement->getAttribute('y'))); + } + } + + $oElement = $document->getElement('p:spPr/a:xfrm/a:ext', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('cx')) { + $oShape->setWidth(CommonDrawing::emuToPixels($oElement->getAttribute('cx'))); + } + if ($oElement->hasAttribute('cy')) { + $oShape->setHeight(CommonDrawing::emuToPixels($oElement->getAttribute('cy'))); + } + } + + $oElement = $document->getElement('p:nvSpPr/p:nvPr/p:ph', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('type')) { + $placeholder = new Placeholder($oElement->getAttribute('type')); + $oShape->setPlaceHolder($placeholder); + } + } + + $arrayElements = $document->getElements('p:txBody/a:p', $node); + foreach ($arrayElements as $oElement) { + $this->loadParagraph($document, $oElement, $oShape); + } + + if (count($oShape->getParagraphs()) > 0) { + $oShape->setActiveParagraph(0); + } + } + + /** + * @param XMLReader $document + * @param \DOMElement $node + * @param AbstractSlide $oSlide + * @throws \Exception + */ + protected function loadShapeTable(XMLReader $document, \DOMElement $node, AbstractSlide $oSlide) + { + $this->fileRels = $oSlide->getRelsIndex(); + + $oShape = $oSlide->createTableShape(); + + $oElement = $document->getElement('p:cNvPr', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('name')) { + $oShape->setName($oElement->getAttribute('name')); + } + if ($oElement->hasAttribute('descr')) { + $oShape->setDescription($oElement->getAttribute('descr')); + } + } + + $oElement = $document->getElement('p:xfrm/a:off', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('x')) { + $oShape->setOffsetX(CommonDrawing::emuToPixels($oElement->getAttribute('x'))); + } + if ($oElement->hasAttribute('y')) { + $oShape->setOffsetY(CommonDrawing::emuToPixels($oElement->getAttribute('y'))); + } + } + + $oElement = $document->getElement('p:xfrm/a:ext', $node); + if ($oElement instanceof \DOMElement) { + if ($oElement->hasAttribute('cx')) { + $oShape->setWidth(CommonDrawing::emuToPixels($oElement->getAttribute('cx'))); + } + if ($oElement->hasAttribute('cy')) { + $oShape->setHeight(CommonDrawing::emuToPixels($oElement->getAttribute('cy'))); + } + } + + $arrayElements = $document->getElements('a:graphic/a:graphicData/a:tbl/a:tblGrid/a:gridCol', $node); + $oShape->setNumColumns($arrayElements->length); + $oShape->createRow(); + foreach ($arrayElements as $key => $oElement) { + if ($oElement instanceof \DOMElement && $oElement->getAttribute('w')) { + $oShape->getRow(0)->getCell($key)->setWidth(CommonDrawing::emuToPixels($oElement->getAttribute('w'))); + } + } + + $arrayElements = $document->getElements('a:graphic/a:graphicData/a:tbl/a:tr', $node); + foreach ($arrayElements as $keyRow => $oElementRow) { + if (!($oElementRow instanceof \DOMElement)) { + continue; + } + $oRow = $oShape->getRow($keyRow, true); + if (is_null($oRow)) { + $oRow = $oShape->createRow(); + } + if ($oElementRow->hasAttribute('h')) { + $oRow->setHeight(CommonDrawing::emuToPixels($oElementRow->getAttribute('h'))); + } + $arrayElementsCell = $document->getElements('a:tc', $oElementRow); + foreach ($arrayElementsCell as $keyCell => $oElementCell) { + if (!($oElementCell instanceof \DOMElement)) { + continue; + } + $oCell = $oRow->getCell($keyCell); + $oCell->setParagraphs(array()); + if ($oElementCell->hasAttribute('gridSpan')) { + $oCell->setColSpan($oElementCell->getAttribute('gridSpan')); + } + if ($oElementCell->hasAttribute('rowSpan')) { + $oCell->setRowSpan($oElementCell->getAttribute('rowSpan')); + } + + foreach ($document->getElements('a:txBody/a:p', $oElementCell) as $oElementPara) { + $this->loadParagraph($document, $oElementPara, $oCell); + } + + $oElementTcPr = $document->getElement('a:tcPr', $oElementCell); + if ($oElementTcPr instanceof \DOMElement) { + $numParagraphs = count($oCell->getParagraphs()); + if ($numParagraphs > 0) { + if ($oElementTcPr->hasAttribute('vert')) { + $oCell->getParagraph(0)->getAlignment()->setTextDirection($oElementTcPr->getAttribute('vert')); + } + if ($oElementTcPr->hasAttribute('anchor')) { + $oCell->getParagraph(0)->getAlignment()->setVertical($oElementTcPr->getAttribute('anchor')); + } + if ($oElementTcPr->hasAttribute('marB')) { + $oCell->getParagraph(0)->getAlignment()->setMarginBottom($oElementTcPr->getAttribute('marB')); + } + if ($oElementTcPr->hasAttribute('marL')) { + $oCell->getParagraph(0)->getAlignment()->setMarginLeft($oElementTcPr->getAttribute('marL')); + } + if ($oElementTcPr->hasAttribute('marR')) { + $oCell->getParagraph(0)->getAlignment()->setMarginRight($oElementTcPr->getAttribute('marR')); + } + if ($oElementTcPr->hasAttribute('marT')) { + $oCell->getParagraph(0)->getAlignment()->setMarginTop($oElementTcPr->getAttribute('marT')); + } + } + + $oFill = $this->loadStyleFill($document, $oElementTcPr); + if ($oFill instanceof Fill) { + $oCell->setFill($oFill); + } + + $oBorders = new Borders(); + $oElementBorderL = $document->getElement('a:lnL', $oElementTcPr); + if ($oElementBorderL instanceof \DOMElement) { + $this->loadStyleBorder($document, $oElementBorderL, $oBorders->getLeft()); + } + $oElementBorderR = $document->getElement('a:lnR', $oElementTcPr); + if ($oElementBorderR instanceof \DOMElement) { + $this->loadStyleBorder($document, $oElementBorderR, $oBorders->getRight()); + } + $oElementBorderT = $document->getElement('a:lnT', $oElementTcPr); + if ($oElementBorderT instanceof \DOMElement) { + $this->loadStyleBorder($document, $oElementBorderT, $oBorders->getTop()); + } + $oElementBorderB = $document->getElement('a:lnB', $oElementTcPr); + if ($oElementBorderB instanceof \DOMElement) { + $this->loadStyleBorder($document, $oElementBorderB, $oBorders->getBottom()); + } + $oElementBorderDiagDown = $document->getElement('a:lnTlToBr', $oElementTcPr); + if ($oElementBorderDiagDown instanceof \DOMElement) { + $this->loadStyleBorder($document, $oElementBorderDiagDown, $oBorders->getDiagonalDown()); + } + $oElementBorderDiagUp = $document->getElement('a:lnBlToTr', $oElementTcPr); + if ($oElementBorderDiagUp instanceof \DOMElement) { + $this->loadStyleBorder($document, $oElementBorderDiagUp, $oBorders->getDiagonalUp()); + } + $oCell->setBorders($oBorders); + } + } + } + } + + /** + * @param XMLReader $document + * @param \DOMElement $oElement + * @param Cell|RichText $oShape + * @throws \Exception + */ + protected function loadParagraph(XMLReader $document, \DOMElement $oElement, $oShape) + { + // Core + $oParagraph = $oShape->createParagraph(); + $oParagraph->setRichTextElements(array()); + + $oSubElement = $document->getElement('a:pPr', $oElement); + if ($oSubElement instanceof \DOMElement) { + if ($oSubElement->hasAttribute('algn')) { + $oParagraph->getAlignment()->setHorizontal($oSubElement->getAttribute('algn')); + } + if ($oSubElement->hasAttribute('fontAlgn')) { + $oParagraph->getAlignment()->setVertical($oSubElement->getAttribute('fontAlgn')); + } + if ($oSubElement->hasAttribute('marL')) { + $oParagraph->getAlignment()->setMarginLeft(CommonDrawing::emuToPixels($oSubElement->getAttribute('marL'))); + } + if ($oSubElement->hasAttribute('marR')) { + $oParagraph->getAlignment()->setMarginRight(CommonDrawing::emuToPixels($oSubElement->getAttribute('marR'))); + } + if ($oSubElement->hasAttribute('indent')) { + $oParagraph->getAlignment()->setIndent(CommonDrawing::emuToPixels($oSubElement->getAttribute('indent'))); + } + if ($oSubElement->hasAttribute('lvl')) { + $oParagraph->getAlignment()->setLevel($oSubElement->getAttribute('lvl')); + } + + $oParagraph->getBulletStyle()->setBulletType(Bullet::TYPE_NONE); + + $oElementBuFont = $document->getElement('a:buFont', $oSubElement); + if ($oElementBuFont instanceof \DOMElement) { + if ($oElementBuFont->hasAttribute('typeface')) { + $oParagraph->getBulletStyle()->setBulletFont($oElementBuFont->getAttribute('typeface')); + } + } + $oElementBuChar = $document->getElement('a:buChar', $oSubElement); + if ($oElementBuChar instanceof \DOMElement) { + $oParagraph->getBulletStyle()->setBulletType(Bullet::TYPE_BULLET); + if ($oElementBuChar->hasAttribute('char')) { + $oParagraph->getBulletStyle()->setBulletChar($oElementBuChar->getAttribute('char')); + } + } + $oElementBuAutoNum = $document->getElement('a:buAutoNum', $oSubElement); + if ($oElementBuAutoNum instanceof \DOMElement) { + $oParagraph->getBulletStyle()->setBulletType(Bullet::TYPE_NUMERIC); + if ($oElementBuAutoNum->hasAttribute('type')) { + $oParagraph->getBulletStyle()->setBulletNumericStyle($oElementBuAutoNum->getAttribute('type')); + } + if ($oElementBuAutoNum->hasAttribute('startAt') && $oElementBuAutoNum->getAttribute('startAt') != 1) { + $oParagraph->getBulletStyle()->setBulletNumericStartAt($oElementBuAutoNum->getAttribute('startAt')); + } + } + $oElementBuClr = $document->getElement('a:buClr', $oSubElement); + if ($oElementBuClr instanceof \DOMElement) { + $oColor = new Color(); + /** + * @todo Create protected for reading Color + */ + $oElementColor = $document->getElement('a:srgbClr', $oElementBuClr); + if ($oElementColor instanceof \DOMElement) { + $oColor->setRGB($oElementColor->hasAttribute('val') ? $oElementColor->getAttribute('val') : null); + } + $oParagraph->getBulletStyle()->setBulletColor($oColor); + } + } + $arraySubElements = $document->getElements('(a:r|a:br)', $oElement); + foreach ($arraySubElements as $oSubElement) { + if ($oSubElement->tagName == 'a:br') { + $oParagraph->createBreak(); + } + if ($oSubElement->tagName == 'a:r') { + $oElementrPr = $document->getElement('a:rPr', $oSubElement); + if (is_object($oElementrPr)) { + $oText = $oParagraph->createTextRun(); + + if ($oElementrPr->hasAttribute('b')) { + $att = $oElementrPr->getAttribute('b'); + $oText->getFont()->setBold($att == 'true' || $att == '1' ? true : false); + } + if ($oElementrPr->hasAttribute('i')) { + $att = $oElementrPr->getAttribute('i'); + $oText->getFont()->setItalic($att == 'true' || $att == '1' ? true : false); + } + if ($oElementrPr->hasAttribute('strike')) { + $oText->getFont()->setStrikethrough($oElementrPr->getAttribute('strike') == 'noStrike' ? false : true); + } + if ($oElementrPr->hasAttribute('sz')) { + $oText->getFont()->setSize((int)($oElementrPr->getAttribute('sz') / 100)); + } + if ($oElementrPr->hasAttribute('u')) { + $oText->getFont()->setUnderline($oElementrPr->getAttribute('u')); + } + // Color + $oElementSrgbClr = $document->getElement('a:solidFill/a:srgbClr', $oElementrPr); + if (is_object($oElementSrgbClr) && $oElementSrgbClr->hasAttribute('val')) { + $oColor = new Color(); + $oColor->setRGB($oElementSrgbClr->getAttribute('val')); + $oText->getFont()->setColor($oColor); + } + // Hyperlink + $oElementHlinkClick = $document->getElement('a:hlinkClick', $oElementrPr); + if (is_object($oElementHlinkClick)) { + if ($oElementHlinkClick->hasAttribute('tooltip')) { + $oText->getHyperlink()->setTooltip($oElementHlinkClick->getAttribute('tooltip')); + } + if ($oElementHlinkClick->hasAttribute('r:id') && isset($this->arrayRels[$this->fileRels][$oElementHlinkClick->getAttribute('r:id')]['Target'])) { + $oText->getHyperlink()->setUrl($this->arrayRels[$this->fileRels][$oElementHlinkClick->getAttribute('r:id')]['Target']); + } + } + //} else { + // $oText = $oParagraph->createText(); + + $oSubSubElement = $document->getElement('a:t', $oSubElement); + $oText->setText($oSubSubElement->nodeValue); + } + } + } + } + + /** + * @param XMLReader $xmlReader + * @param \DOMElement $oElement + * @param Border $oBorder + * @throws \Exception + */ + protected function loadStyleBorder(XMLReader $xmlReader, \DOMElement $oElement, Border $oBorder) + { + if ($oElement->hasAttribute('w')) { + $oBorder->setLineWidth($oElement->getAttribute('w') / 12700); + } + if ($oElement->hasAttribute('cmpd')) { + $oBorder->setLineStyle($oElement->getAttribute('cmpd')); + } + + $oElementNoFill = $xmlReader->getElement('a:noFill', $oElement); + if ($oElementNoFill instanceof \DOMElement && $oBorder->getLineStyle() == Border::LINE_SINGLE) { + $oBorder->setLineStyle(Border::LINE_NONE); + } + + $oElementColor = $xmlReader->getElement('a:solidFill/a:srgbClr', $oElement); + if ($oElementColor instanceof \DOMElement) { + $oBorder->setColor($this->loadStyleColor($xmlReader, $oElementColor)); + } + + $oElementDashStyle = $xmlReader->getElement('a:prstDash', $oElement); + if ($oElementDashStyle instanceof \DOMElement && $oElementDashStyle->hasAttribute('val')) { + $oBorder->setDashStyle($oElementDashStyle->getAttribute('val')); + } + } + + /** + * @param XMLReader $xmlReader + * @param \DOMElement $oElement + * @return Color + */ + protected function loadStyleColor(XMLReader $xmlReader, \DOMElement $oElement) + { + $oColor = new Color(); + $oColor->setRGB($oElement->getAttribute('val')); + $oElementAlpha = $xmlReader->getElement('a:alpha', $oElement); + if ($oElementAlpha instanceof \DOMElement && $oElementAlpha->hasAttribute('val')) { + $alpha = strtoupper(dechex((($oElementAlpha->getAttribute('val') / 1000) / 100) * 255)); + $oColor->setRGB($oElement->getAttribute('val'), $alpha); + } + return $oColor; + } + + /** + * @param XMLReader $xmlReader + * @param \DOMElement $oElement + * @return null|Fill + * @throws \Exception + */ + protected function loadStyleFill(XMLReader $xmlReader, \DOMElement $oElement) + { + // Gradient fill + $oElementFill = $xmlReader->getElement('a:gradFill', $oElement); + if ($oElementFill instanceof \DOMElement) { + $oFill = new Fill(); + $oFill->setFillType(Fill::FILL_GRADIENT_LINEAR); + + $oElementColor = $xmlReader->getElement('a:gsLst/a:gs[@pos="0"]/a:srgbClr', $oElementFill); + if ($oElementColor instanceof \DOMElement && $oElementColor->hasAttribute('val')) { + $oFill->setStartColor($this->loadStyleColor($xmlReader, $oElementColor)); + } + + $oElementColor = $xmlReader->getElement('a:gsLst/a:gs[@pos="100000"]/a:srgbClr', $oElementFill); + if ($oElementColor instanceof \DOMElement && $oElementColor->hasAttribute('val')) { + $oFill->setEndColor($this->loadStyleColor($xmlReader, $oElementColor)); + } + + $oRotation = $xmlReader->getElement('a:lin', $oElementFill); + if ($oRotation instanceof \DOMElement && $oRotation->hasAttribute('ang')) { + $oFill->setRotation(CommonDrawing::angleToDegrees($oRotation->getAttribute('ang'))); + } + return $oFill; + } + + // Solid fill + $oElementFill = $xmlReader->getElement('a:solidFill', $oElement); + if ($oElementFill instanceof \DOMElement) { + $oFill = new Fill(); + $oFill->setFillType(Fill::FILL_SOLID); + + $oElementColor = $xmlReader->getElement('a:srgbClr', $oElementFill); + if ($oElementColor instanceof \DOMElement) { + $oFill->setStartColor($this->loadStyleColor($xmlReader, $oElementColor)); + } + return $oFill; + } + return null; + } + + /** + * @param string $fileRels + */ + protected function loadRels($fileRels) + { + $sPart = $this->oZip->getFromName($fileRels); + if ($sPart !== false) { + $xmlReader = new XMLReader(); + if ($xmlReader->getDomFromString($sPart)) { + foreach ($xmlReader->getElements('*') as $oNode) { + if (!($oNode instanceof \DOMElement)) { + continue; + } + $this->arrayRels[$fileRels][$oNode->getAttribute('Id')] = array( + 'Target' => $oNode->getAttribute('Target'), + 'Type' => $oNode->getAttribute('Type'), + ); + } + } + } + } + + /** + * @param $oSlide + * @param \DOMNodeList $oElements + * @param XMLReader $xmlReader + * @throws \Exception + * @internal param $baseFile + */ + protected function loadSlideShapes($oSlide, $oElements, $xmlReader) + { + foreach ($oElements as $oNode) { + switch ($oNode->tagName) { + case 'p:graphicFrame': + $this->loadShapeTable($xmlReader, $oNode, $oSlide); + break; + case 'p:pic': + $this->loadShapeDrawing($xmlReader, $oNode, $oSlide); + break; + case 'p:sp': + $this->loadShapeRichText($xmlReader, $oNode, $oSlide); + break; + default: + //var_export($oNode->tagName); + } + } + } +} diff --git a/PhpOffice/PhpPresentation/Reader/PowerPoint97.php b/PhpOffice/PhpPresentation/Reader/PowerPoint97.php new file mode 100644 index 0000000..385638c --- /dev/null +++ b/PhpOffice/PhpPresentation/Reader/PowerPoint97.php @@ -0,0 +1,3472 @@ +fileSupportsUnserializePhpPresentation($pFilename); + } + + /** + * Does a file support UnserializePhpPresentation ? + * + * @param string $pFilename + * @throws \Exception + * @return boolean + */ + public function fileSupportsUnserializePhpPresentation($pFilename = '') + { + // Check if file exists + if (!file_exists($pFilename)) { + throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + } + + try { + // Use ParseXL for the hard work. + $ole = new OLERead(); + // get excel data + $ole->read($pFilename); + return true; + } catch (\Exception $e) { + return false; + } + } + + /** + * Loads PhpPresentation Serialized file + * + * @param string $pFilename + * @return \PhpOffice\PhpPresentation\PhpPresentation + * @throws \Exception + */ + public function load($pFilename) + { + // Unserialize... First make sure the file supports it! + if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { + throw new \Exception("Invalid file format for PhpOffice\PhpPresentation\Reader\PowerPoint97: " . $pFilename . "."); + } + + return $this->loadFile($pFilename); + } + + /** + * Load PhpPresentation Serialized file + * + * @param string $pFilename + * @return \PhpOffice\PhpPresentation\PhpPresentation + * @throws \Exception + */ + private function loadFile($pFilename) + { + $this->oPhpPresentation = new PhpPresentation(); + $this->oPhpPresentation->removeSlideByIndex(); + + // Read OLE Blocks + $this->loadOLE($pFilename); + // Read pictures in the Pictures Stream + $this->loadPicturesStream(); + // Read information in the Current User Stream + $this->loadCurrentUserStream(); + // Read information in the PowerPoint Document Stream + $this->loadPowerpointDocumentStream(); + + return $this->oPhpPresentation; + } + + /** + * Read OLE Part + * @param string $pFilename + * @throws \Exception + */ + private function loadOLE($pFilename) + { + // OLE reader + $oOLE = new OLERead(); + $oOLE->read($pFilename); + + // PowerPoint Document Stream + $this->streamPowerpointDocument = $oOLE->getStream($oOLE->powerpointDocument); + + // Current User Stream + $this->streamCurrentUser = $oOLE->getStream($oOLE->currentUser); + + // Get summary information data + $this->streamSummaryInformation = $oOLE->getStream($oOLE->summaryInformation); + + // Get additional document summary information data + $this->streamDocumentSummaryInformation = $oOLE->getStream($oOLE->docSummaryInfos); + + // Get pictures data + $this->streamPictures = $oOLE->getStream($oOLE->pictures); + } + + /** + * Stream Pictures + * @link http://msdn.microsoft.com/en-us/library/dd920746(v=office.12).aspx + */ + private function loadPicturesStream() + { + $stream = $this->streamPictures; + + $pos = 0; + do { + $arrayRH = $this->loadRecordHeader($stream, $pos); + $pos += 8; + $readSuccess = false; + if ($arrayRH['recVer'] == 0x00 && ($arrayRH['recType'] == 0xF007 || ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117))) { + //@link : http://msdn.microsoft.com/en-us/library/dd950560(v=office.12).aspx + if ($arrayRH['recType'] == 0xF007) { + // OfficeArtFBSE + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + if ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117) { + $arrayRecord = $this->readRecordOfficeArtBlip($stream, $pos - 8); + if ($arrayRecord['length'] > 0) { + $pos += $arrayRecord['length']; + $this->arrayPictures[] = $arrayRecord['picture']; + } + } + $readSuccess = true; + } + } while ($readSuccess === true); + } + + /** + * Stream Current User + * @link http://msdn.microsoft.com/en-us/library/dd908567(v=office.12).aspx + */ + private function loadCurrentUserStream() + { + $pos = 0; + + /** + * CurrentUserAtom : http://msdn.microsoft.com/en-us/library/dd948895(v=office.12).aspx + */ + // RecordHeader : http://msdn.microsoft.com/en-us/library/dd926377(v=office.12).aspx + $rHeader = $this->loadRecordHeader($this->streamCurrentUser, $pos); + $pos += 8; + if ($rHeader['recVer'] != 0x0 || $rHeader['recInstance'] != 0x000 || $rHeader['recType'] != self::RT_CURRENTUSERATOM) { + throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > RecordHeader).'); + } + + // Size + $size = self::getInt4d($this->streamCurrentUser, $pos); + $pos += 4; + if ($size != 0x00000014) { + throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > Size).'); + } + + // headerToken + $headerToken = self::getInt4d($this->streamCurrentUser, $pos); + $pos += 4; + if ($headerToken == 0xF3D1C4DF && $headerToken != 0xE391C05F) { + throw new \Exception('Feature not implemented (l.'.__LINE__.') : Encrypted file'); + } + + // offsetToCurrentEdit + $this->offsetToCurrentEdit = self::getInt4d($this->streamCurrentUser, $pos); + $pos += 4; + + // lenUserName + $lenUserName = self::getInt2d($this->streamCurrentUser, $pos); + $pos += 2; + if ($lenUserName > 255) { + throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > lenUserName).'); + } + + // docFileVersion + $docFileVersion = self::getInt2d($this->streamCurrentUser, $pos); + $pos += 2; + if ($docFileVersion != 0x03F4) { + throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > docFileVersion).'); + } + + // majorVersion + $majorVersion = self::getInt1d($this->streamCurrentUser, $pos); + $pos += 1; + if ($majorVersion != 0x03) { + throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > majorVersion).'); + } + + // minorVersion + $minorVersion = self::getInt1d($this->streamCurrentUser, $pos); + $pos += 1; + if ($minorVersion != 0x00) { + throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > minorVersion).'); + } + + // unused + $pos += 2; + + // ansiUserName + $ansiUserName = ''; + do { + $char = self::getInt1d($this->streamCurrentUser, $pos); + if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) { + $char = false; + } else { + $ansiUserName .= chr($char); + $pos += 1; + } + } while ($char !== false); + + // relVersion + $relVersion = self::getInt4d($this->streamCurrentUser, $pos); + $pos += 4; + if ($relVersion != 0x00000008 && $relVersion != 0x00000009) { + throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > relVersion).'); + } + + // unicodeUserName + $unicodeUserName = ''; + for ($inc = 0; $inc < $lenUserName; $inc++) { + $char = self::getInt2d($this->streamCurrentUser, $pos); + if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) { + break; + } + $unicodeUserName .= chr($char); + $pos += 2; + } + } + + /** + * Stream Powerpoint Document + * @link http://msdn.microsoft.com/en-us/library/dd921564(v=office.12).aspx + */ + private function loadPowerpointDocumentStream() + { + $this->readRecordUserEditAtom($this->streamPowerpointDocument, $this->offsetToCurrentEdit); + + $this->readRecordPersistDirectoryAtom($this->streamPowerpointDocument, $this->offsetPersistDirectory); + + foreach ($this->rgPersistDirEntry as $offsetDir) { + $pos = $offsetDir; + + $rh = $this->loadRecordHeader($this->streamPowerpointDocument, $pos); + $pos += 8; + $this->inMainType = $rh['recType']; + $this->currentNote = null; + switch ($rh['recType']) { + case self::RT_DOCUMENT: + $this->readRecordDocumentContainer($this->streamPowerpointDocument, $pos); + break; + case self::RT_NOTES: + $this->readRecordNotesContainer($this->streamPowerpointDocument, $pos); + break; + case self::RT_SLIDE: + $this->readRecordSlideContainer($this->streamPowerpointDocument, $pos); + break; + default: + // throw new \Exception('Feature not implemented : l.'.__LINE__.'('.dechex($rh['recType']).')'); + break; + } + } + } + + /** + * Read a record header + * @param string $stream + * @param integer $pos + * @return array + */ + private function loadRecordHeader($stream, $pos) + { + $rec = self::getInt2d($stream, $pos); + $recType = self::getInt2d($stream, $pos + 2); + $recLen = self::getInt4d($stream, $pos + 4); + return array( + 'recVer' => ($rec >> 0) & bindec('1111'), + 'recInstance' => ($rec >> 4) & bindec('111111111111'), + 'recType' => $recType, + 'recLen' => $recLen, + ); + } + + /** + * Read 8-bit unsigned integer + * + * @param string $data + * @param int $pos + * @return int + */ + public static function getInt1d($data, $pos) + { + return ord($data[$pos]); + } + + /** + * Read 16-bit unsigned integer + * + * @param string $data + * @param int $pos + * @return int + */ + public static function getInt2d($data, $pos) + { + return ord($data[$pos]) | (ord($data[$pos+1]) << 8); + } + + /** + * Read 32-bit signed integer + * + * @param string $data + * @param int $pos + * @return int + */ + public static function getInt4d($data, $pos) + { + // FIX: represent numbers correctly on 64-bit system + // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 + // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems + $or24 = ord($data[$pos + 3]); + + $ord24 = ($or24 & 127) << 24; + if ($or24 >= 128) { + // negative number + $ord24 = -abs((256 - $or24) << 24); + } + return ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | $ord24; + } + + /** + * A container record that specifies the animation and sound information for a shape. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd772900(v=office.12).aspx + */ + private function readRecordAnimationInfoContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_ANIMATIONINFO) { + // Record Header + $arrayReturn['length'] += 8; + // animationAtom + // animationSound + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + + return $arrayReturn; + } + + /** + * A container record that specifies information about the document. + * @param string $stream + * @param integer $pos + * @throws \Exception + * @link http://msdn.microsoft.com/en-us/library/dd947357(v=office.12).aspx + */ + private function readRecordDocumentContainer($stream, $pos) + { + $documentAtom = $this->loadRecordHeader($stream, $pos); + $pos += 8; + if ($documentAtom['recVer'] != 0x1 || $documentAtom['recInstance'] != 0x000 || $documentAtom['recType'] != self::RT_DOCUMENTATOM) { + throw new \Exception('File PowerPoint 97 in error (Location : RTDocument > DocumentAtom).'); + } + $pos += $documentAtom['recLen']; + + $exObjList = $this->loadRecordHeader($stream, $pos); + if ($exObjList['recVer'] == 0xF && $exObjList['recInstance'] == 0x000 && $exObjList['recType'] == self::RT_EXTERNALOBJECTLIST) { + $pos += 8; + // exObjListAtom > rh + $exObjListAtom = $this->loadRecordHeader($stream, $pos); + if ($exObjListAtom['recVer'] != 0x0 || $exObjListAtom['recInstance'] != 0x000 || $exObjListAtom['recType'] != self::RT_EXTERNALOBJECTLISTATOM || $exObjListAtom['recLen'] != 0x00000004) { + throw new \Exception('File PowerPoint 97 in error (Location : RTDocument > DocumentAtom > exObjList > exObjListAtom).'); + } + $pos += 8; + // exObjListAtom > exObjIdSeed + $pos += 4; + // rgChildRec + $exObjList['recLen'] -= 12; + do { + $childRec = $this->loadRecordHeader($stream, $pos); + $pos += 8; + $exObjList['recLen'] -= 8; + switch ($childRec['recType']) { + case self::RT_EXTERNALHYPERLINK: + //@link : http://msdn.microsoft.com/en-us/library/dd944995(v=office.12).aspx + // exHyperlinkAtom > rh + $exHyperlinkAtom = $this->loadRecordHeader($stream, $pos); + if ($exHyperlinkAtom['recVer'] != 0x0 || $exHyperlinkAtom['recInstance'] != 0x000 || $exHyperlinkAtom['recType'] != self::RT_EXTERNALHYPERLINKATOM || $exObjListAtom['recLen'] != 0x00000004) { + throw new \Exception('File PowerPoint 97 in error (Location : RTDocument > DocumentAtom > exObjList > rgChildRec > RT_ExternalHyperlink).'); + } + $pos += 8; + $exObjList['recLen'] -= 8; + // exHyperlinkAtom > exHyperlinkId + $exHyperlinkId = self::getInt4d($stream, $pos); + $pos += 4; + $exObjList['recLen'] -= 4; + + $this->arrayHyperlinks[$exHyperlinkId] = array(); + // friendlyNameAtom + $friendlyNameAtom = $this->loadRecordHeader($stream, $pos); + if ($friendlyNameAtom['recVer'] == 0x0 && $friendlyNameAtom['recInstance'] == 0x000 && $friendlyNameAtom['recType'] == self::RT_CSTRING && $friendlyNameAtom['recLen'] % 2 == 0) { + $pos += 8; + $exObjList['recLen'] -= 8; + $this->arrayHyperlinks[$exHyperlinkId]['text'] = ''; + for ($inc = 0; $inc < ($friendlyNameAtom['recLen'] / 2); $inc++) { + $char = self::getInt2d($stream, $pos); + $pos += 2; + $exObjList['recLen'] -= 2; + $this->arrayHyperlinks[$exHyperlinkId]['text'] .= chr($char); + } + } + // targetAtom + $targetAtom = $this->loadRecordHeader($stream, $pos); + if ($targetAtom['recVer'] == 0x0 && $targetAtom['recInstance'] == 0x001 && $targetAtom['recType'] == self::RT_CSTRING && $targetAtom['recLen'] % 2 == 0) { + $pos += 8; + $exObjList['recLen'] -= 8; + $this->arrayHyperlinks[$exHyperlinkId]['url'] = ''; + for ($inc = 0; $inc < ($targetAtom['recLen'] / 2); $inc++) { + $char = self::getInt2d($stream, $pos); + $pos += 2; + $exObjList['recLen'] -= 2; + $this->arrayHyperlinks[$exHyperlinkId]['url'] .= chr($char); + } + } + // locationAtom + $locationAtom = $this->loadRecordHeader($stream, $pos); + if ($locationAtom['recVer'] == 0x0 && $locationAtom['recInstance'] == 0x003 && $locationAtom['recType'] == self::RT_CSTRING && $locationAtom['recLen'] % 2 == 0) { + $pos += 8; + $exObjList['recLen'] -= 8; + $string = ''; + for ($inc = 0; $inc < ($locationAtom['recLen'] / 2); $inc++) { + $char = self::getInt2d($stream, $pos); + $pos += 2; + $exObjList['recLen'] -= 2; + $string .= chr($char); + } + } + break; + default: + throw new \Exception('Feature not implemented (l.'.__LINE__.' : '.dechex($childRec['recType'].')')); + } + } while ($exObjList['recLen'] > 0); + } + + //@link : http://msdn.microsoft.com/en-us/library/dd907813(v=office.12).aspx + $documentTextInfo = $this->loadRecordHeader($stream, $pos); + if ($documentTextInfo['recVer'] == 0xF && $documentTextInfo['recInstance'] == 0x000 && $documentTextInfo['recType'] == self::RT_ENVIRONMENT) { + $pos += 8; + //@link : http://msdn.microsoft.com/en-us/library/dd952717(v=office.12).aspx + $kinsoku = $this->loadRecordHeader($stream, $pos); + if ($kinsoku['recVer'] == 0xF && $kinsoku['recInstance'] == 0x002 && $kinsoku['recType'] == self::RT_KINSOKU) { + $pos += 8; + $pos += $kinsoku['recLen']; + } + + //@link : http://msdn.microsoft.com/en-us/library/dd948152(v=office.12).aspx + $fontCollection = $this->loadRecordHeader($stream, $pos); + if ($fontCollection['recVer'] == 0xF && $fontCollection['recInstance'] == 0x000 && $fontCollection['recType'] == self::RT_FONTCOLLECTION) { + $pos += 8; + do { + $fontEntityAtom = $this->loadRecordHeader($stream, $pos); + $pos += 8; + $fontCollection['recLen'] -= 8; + if ($fontEntityAtom['recVer'] != 0x0 || $fontEntityAtom['recInstance'] > 128 || $fontEntityAtom['recType'] != self::RT_FONTENTITYATOM) { + throw new \Exception('File PowerPoint 97 in error (Location : RTDocument > RT_Environment > RT_FontCollection > RT_FontEntityAtom).'); + } + $string = ''; + for ($inc = 0; $inc < 32; $inc++) { + $char = self::getInt2d($stream, $pos); + $pos += 2; + $fontCollection['recLen'] -= 2; + $string .= chr($char); + } + $this->arrayFonts[] = $string; + + // lfCharSet (1 byte) + $pos += 1; + $fontCollection['recLen'] -= 1; + + // fEmbedSubsetted (1 bit) + // unused (7 bits) + $pos += 1; + $fontCollection['recLen'] -= 1; + + // rasterFontType (1 bit) + // deviceFontType (1 bit) + // truetypeFontType (1 bit) + // fNoFontSubstitution (1 bit) + // reserved (4 bits) + $pos += 1; + $fontCollection['recLen'] -= 1; + + // lfPitchAndFamily (1 byte) + $pos += 1; + $fontCollection['recLen'] -= 1; + + $fontEmbedData1 = $this->loadRecordHeader($stream, $pos); + if ($fontEmbedData1['recVer'] == 0x0 && $fontEmbedData1['recInstance'] >= 0x000 && $fontEmbedData1['recInstance'] <= 0x003 && $fontEmbedData1['recType'] == self::RT_FONTEMBEDDATABLOB) { + $pos += 8; + $fontCollection['recLen'] -= 8; + $pos += $fontEmbedData1['recLen']; + $fontCollection['recLen'] -= $fontEmbedData1['recLen']; + } + + $fontEmbedData2 = $this->loadRecordHeader($stream, $pos); + if ($fontEmbedData2['recVer'] == 0x0 && $fontEmbedData2['recInstance'] >= 0x000 && $fontEmbedData2['recInstance'] <= 0x003 && $fontEmbedData2['recType'] == self::RT_FONTEMBEDDATABLOB) { + $pos += 8; + $fontCollection['recLen'] -= 8; + $pos += $fontEmbedData2['recLen']; + $fontCollection['recLen'] -= $fontEmbedData2['recLen']; + } + + $fontEmbedData3 = $this->loadRecordHeader($stream, $pos); + if ($fontEmbedData3['recVer'] == 0x0 && $fontEmbedData3['recInstance'] >= 0x000 && $fontEmbedData3['recInstance'] <= 0x003 && $fontEmbedData3['recType'] == self::RT_FONTEMBEDDATABLOB) { + $pos += 8; + $fontCollection['recLen'] -= 8; + $pos += $fontEmbedData3['recLen']; + $fontCollection['recLen'] -= $fontEmbedData3['recLen']; + } + + $fontEmbedData4 = $this->loadRecordHeader($stream, $pos); + if ($fontEmbedData4['recVer'] == 0x0 && $fontEmbedData4['recInstance'] >= 0x000 && $fontEmbedData4['recInstance'] <= 0x003 && $fontEmbedData4['recType'] == self::RT_FONTEMBEDDATABLOB) { + $pos += 8; + $fontCollection['recLen'] -= 8; + $pos += $fontEmbedData4['recLen']; + $fontCollection['recLen'] -= $fontEmbedData4['recLen']; + } + } while ($fontCollection['recLen'] > 0); + } + + $textCFDefaultsAtom = $this->loadRecordHeader($stream, $pos); + if ($textCFDefaultsAtom['recVer'] == 0x0 && $textCFDefaultsAtom['recInstance'] == 0x000 && $textCFDefaultsAtom['recType'] == self::RT_TEXTCHARFORMATEXCEPTIONATOM) { + $pos += 8; + $pos += $textCFDefaultsAtom['recLen']; + } + + $textPFDefaultsAtom = $this->loadRecordHeader($stream, $pos); + if ($textPFDefaultsAtom['recVer'] == 0x0 && $textPFDefaultsAtom['recInstance'] == 0x000 && $textPFDefaultsAtom['recType'] == self::RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM) { + $pos += 8; + $pos += $textPFDefaultsAtom['recLen']; + } + + $defaultRulerAtom = $this->loadRecordHeader($stream, $pos); + if ($defaultRulerAtom['recVer'] == 0x0 && $defaultRulerAtom['recInstance'] == 0x000 && $defaultRulerAtom['recType'] == self::RT_DEFAULTRULERATOM) { + $pos += 8; + $pos += $defaultRulerAtom['recLen']; + } + + $textSIDefaultsAtom = $this->loadRecordHeader($stream, $pos); + if ($textSIDefaultsAtom['recVer'] == 0x0 && $textSIDefaultsAtom['recInstance'] == 0x000 && $textSIDefaultsAtom['recType'] == self::RT_TEXTSPECIALINFODEFAULTATOM) { + $pos += 8; + $pos += $textSIDefaultsAtom['recLen']; + } + + $textMasterStyleAtom = $this->loadRecordHeader($stream, $pos); + if ($textMasterStyleAtom['recVer'] == 0x0 && $textMasterStyleAtom['recType'] == self::RT_TEXTMASTERSTYLEATOM) { + $pos += 8; + $pos += $textMasterStyleAtom['recLen']; + } + } + + $soundCollection = $this->loadRecordHeader($stream, $pos); + if ($soundCollection['recVer'] == 0xF && $soundCollection['recInstance'] == 0x005 && $soundCollection['recType'] == self::RT_SOUNDCOLLECTION) { + $pos += 8; + $pos += $soundCollection['recLen']; + } + + $drawingGroup = $this->loadRecordHeader($stream, $pos); + if ($drawingGroup['recVer'] == 0xF && $drawingGroup['recInstance'] == 0x000 && $drawingGroup['recType'] == self::RT_DRAWINGGROUP) { + $drawing = $this->readRecordDrawingGroupContainer($stream, $pos); + $pos += 8; + $pos += $drawing['length']; + } + + $masterList = $this->loadRecordHeader($stream, $pos); + if ($masterList['recVer'] == 0xF && $masterList['recInstance'] == 0x001 && $masterList['recType'] == self::RT_SLIDELISTWITHTEXT) { + $pos += 8; + $pos += $masterList['recLen']; + } + + $docInfoList = $this->loadRecordHeader($stream, $pos); + if ($docInfoList['recVer'] == 0xF && $docInfoList['recInstance'] == 0x000 && $docInfoList['recType'] == self::RT_LIST) { + $pos += 8; + $pos += $docInfoList['recLen']; + } + + $slideHF = $this->loadRecordHeader($stream, $pos); + if ($slideHF['recVer'] == 0xF && $slideHF['recInstance'] == 0x003 && $slideHF['recType'] == self::RT_HEADERSFOOTERS) { + $pos += 8; + $pos += $slideHF['recLen']; + } + + $notesHF = $this->loadRecordHeader($stream, $pos); + if ($notesHF['recVer'] == 0xF && $notesHF['recInstance'] == 0x004 && $notesHF['recType'] == self::RT_HEADERSFOOTERS) { + $pos += 8; + $pos += $notesHF['recLen']; + } + + // SlideListWithTextContainer + $slideList = $this->loadRecordHeader($stream, $pos); + if ($slideList['recVer'] == 0xF && $slideList['recInstance'] == 0x000 && $slideList['recType'] == self::RT_SLIDELISTWITHTEXT) { + $pos += 8; + do { + // SlideListWithTextSubContainerOrAtom + $rhSlideList = $this->loadRecordHeader($stream, $pos); + if ($rhSlideList['recVer'] == 0x0 && $rhSlideList['recInstance'] == 0x000 && $rhSlideList['recType'] == self::RT_SLIDEPERSISTATOM && $rhSlideList['recLen'] == 0x00000014) { + $pos += 8; + $slideList['recLen'] -= 8; + // persistIdRef + $pos += 4; + $slideList['recLen'] -= 4; + // reserved1 - fShouldCollapse - fNonOutlineData - reserved2 + $pos += 4; + $slideList['recLen'] -= 4; + // cTexts + $pos += 4; + $slideList['recLen'] -= 4; + // slideId + $slideId = self::getInt4d($stream, $pos); + if ($slideId == -2147483648) { + $slideId = 0; + } + if ($slideId > 0) { + $this->arrayNotes[$this->oPhpPresentation->getActiveSlideIndex()] = $slideId; + } + $pos += 4; + $slideList['recLen'] -= 4; + // reserved3 + $pos += 4; + $slideList['recLen'] -= 4; + } + } while ($slideList['recLen'] > 0); + } + } + + /** + * An atom record that specifies information about a slide. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx + */ + private function readRecordDrawingContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_DRAWING) { + // Record Header + $arrayReturn['length'] += 8; + + $officeArtDg = $this->readRecordOfficeArtDgContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $officeArtDg['length']; + } + return $arrayReturn; + } + + private function readRecordDrawingGroupContainer($stream, $pos) + { + + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_DRAWINGGROUP) { + // Record Header + $arrayReturn['length'] += 8; + $arrayReturn['length'] += $data['recLen']; + } + return $arrayReturn; + } + + /** + * An atom record that specifies a reference to an external object. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd910388(v=office.12).aspx + */ + private function readRecordExObjRefAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_EXTERNALOBJECTREFATOM && $data['recLen'] == 0x00000004) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies a type of action to be performed. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd953300(v=office.12).aspx + */ + private function readRecordInteractiveInfoAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_INTERACTIVEINFOATOM && $data['recLen'] == 0x00000010) { + // Record Header + $arrayReturn['length'] += 8; + // soundIdRef + $arrayReturn['length'] += 4; + // exHyperlinkIdRef + $arrayReturn['exHyperlinkIdRef'] = self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + // action + $arrayReturn['length'] += 1; + // oleVerb + $arrayReturn['length'] += 1; + // jump + $arrayReturn['length'] += 1; + // fAnimated (1 bit) + // fStopSound (1 bit) + // fCustomShowReturn (1 bit) + // fVisited (1 bit) + // reserved (4 bits) + $arrayReturn['length'] += 1; + // hyperlinkType + $arrayReturn['length'] += 1; + // unused + $arrayReturn['length'] += 3; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies the name of a macro, a file name, or a named show. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd925121(v=office.12).aspx + */ + private function readRecordMacroNameAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x002 && $data['recType'] == self::RT_CSTRING && $data['recLen'] % 2 == 0) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * A container record that specifies what actions to perform when interacting with an object by means of a mouse click. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd952348(v=office.12).aspx + */ + private function readRecordMouseClickInteractiveInfoContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_INTERACTIVEINFO) { + // Record Header + $arrayReturn['length'] += 8; + // interactiveInfoAtom + $interactiveInfoAtom = $this->readRecordInteractiveInfoAtom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $interactiveInfoAtom['length']; + if ($interactiveInfoAtom['length'] > 0) { + $arrayReturn['exHyperlinkIdRef'] = $interactiveInfoAtom['exHyperlinkIdRef']; + } + // macroNameAtom + $macroNameAtom = $this->readRecordMacroNameAtom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $macroNameAtom['length']; + } + + return $arrayReturn; + } + + /** + * A container record that specifies what actions to perform when interacting with an object by moving the mouse cursor over it. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd925811(v=office.12).aspx + */ + private function readRecordMouseOverInteractiveInfoContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x001 && $data['recType'] == self::RT_INTERACTIVEINFO) { + // Record Header + $arrayReturn['length'] += 8; + // interactiveInfoAtom + // macroNameAtom + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + + return $arrayReturn; + } + + /** + * The OfficeArtBlip record specifies BLIP file data. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd910081(v=office.12).aspx + */ + private function readRecordOfficeArtBlip($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + 'picture' => null + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && ($data['recType'] >= 0xF018 && $data['recType'] <= 0xF117)) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + switch ($data['recType']) { + case self::OFFICEARTBLIPJPG: + case self::OFFICEARTBLIPPNG: + // rgbUid1 + $arrayReturn['length'] += 16; + $data['recLen'] -= 16; + if ($data['recInstance'] == 0x6E1) { + // rgbUid2 + $arrayReturn['length'] += 16; + $data['recLen'] -= 16; + } + // tag + $arrayReturn['length'] += 1; + $data['recLen'] -= 1; + // BLIPFileData + $arrayReturn['picture'] = substr($this->streamPictures, $pos + $arrayReturn['length'], $data['recLen']); + $arrayReturn['length'] += $data['recLen']; + break; + default: + throw new \Exception('Feature not implemented (l.'.__LINE__.' : '.dechex($data['recType'].')')); + } + } + + return $arrayReturn; + } + + /** + * The OfficeArtChildAnchor record specifies four signed integers that specify the anchor for the shape that contains this record. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd922720(v=office.12).aspx + */ + private function readRecordOfficeArtChildAnchor($stream, $pos) + { + $arrayReturn = array( + 'length' => 0 + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == 0xF00F && $data['recLen'] == 0x00000010) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['left'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + $arrayReturn['top'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + $arrayReturn['width'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']) - $arrayReturn['left']; + $arrayReturn['length'] += 4; + $arrayReturn['height'] = (int) self::getInt4d($stream, $pos + $arrayReturn['length']) - $arrayReturn['top']; + $arrayReturn['length'] += 4; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies the location of a shape. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd922797(v=office.12).aspx + */ + private function readRecordOfficeArtClientAnchor($stream, $pos) + { + $arrayReturn = array( + 'length' => 0 + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == 0xF010 && ($data['recLen'] == 0x00000008 || $data['recLen'] == 0x00000010)) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + switch ($data['recLen']) { + case 0x00000008: + $arrayReturn['top'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6); + $arrayReturn['length'] += 2; + $arrayReturn['left'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6); + $arrayReturn['length'] += 2; + $arrayReturn['width'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6) - $arrayReturn['left']; + $arrayReturn['length'] += 2; + $arrayReturn['height'] = (int) (self::getInt2d($stream, $pos + $arrayReturn['length']) / 6) - $arrayReturn['left']; + $arrayReturn['length'] += 2; + $pos += 8; + break; + case 0x00000010: + throw new \Exception('PowerPoint97 Reader : record OfficeArtClientAnchor (0x00000010)'); + } + } + + return $arrayReturn; + } + + /** + * A container record that specifies text related data for a shape. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx + */ + private function readRecordOfficeArtClientTextbox($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + 'text' => '', + 'numParts' => 0, + 'numTexts' => 0, + 'hyperlink' => array(), + ); + + $data = $this->loadRecordHeader($stream, $pos); + // recVer 0xF + // Doc : 0x0 https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx + // Sample : 0xF https://msdn.microsoft.com/en-us/library/dd953497(v=office.12).aspx + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF00D) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $strLen = 0; + do { + $rhChild = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); + /** + * @link : https://msdn.microsoft.com/en-us/library/dd947039(v=office.12).aspx + */ + // echo dechex($rhChild['recType']).'-'.$rhChild['recType'].EOL; + switch ($rhChild['recType']) { + case self::RT_INTERACTIVEINFO: + //@link : http://msdn.microsoft.com/en-us/library/dd948623(v=office.12).aspx + if ($rhChild['recInstance'] == 0x0000) { + $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $mouseClickInfo['length']; + $arrayReturn['hyperlink'][]['id'] = $mouseClickInfo['exHyperlinkIdRef']; + } + if ($rhChild['recInstance'] == 0x0001) { + $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $mouseOverInfo['length']; + } + break; + case self::RT_STYLETEXTPROPATOM: + $arrayReturn['length'] += 8; + // @link : http://msdn.microsoft.com/en-us/library/dd950647(v=office.12).aspx + // rgTextPFRun + $strLenRT = $strLen + 1; + do { + $strucTextPFRun = $this->readStructureTextPFRun($stream, $pos + $arrayReturn['length'], $strLenRT); + $arrayReturn['numTexts']++; + $arrayReturn['text'.$arrayReturn['numTexts']] = $strucTextPFRun; + if (isset($strucTextPFRun['alignH'])) { + $arrayReturn['alignH'] = $strucTextPFRun['alignH']; + } + $strLenRT = $strucTextPFRun['strLenRT']; + $arrayReturn['length'] += $strucTextPFRun['length']; + } while ($strLenRT > 0); + // rgTextCFRun + $strLenRT = $strLen + 1; + do { + $strucTextCFRun = $this->readStructureTextCFRun($stream, $pos + $arrayReturn['length'], $strLenRT); + $arrayReturn['numParts']++; + $arrayReturn['part'.$arrayReturn['numParts']] = $strucTextCFRun; + $strLenRT = $strucTextCFRun['strLenRT']; + $arrayReturn['length'] += $strucTextCFRun['length']; + } while ($strLenRT > 0); + break; + case self::RT_TEXTBYTESATOM: + $arrayReturn['length'] += 8; + // @link : https://msdn.microsoft.com/en-us/library/dd947905(v=office.12).aspx + $strLen = (int)$rhChild['recLen']; + for ($inc = 0; $inc < $strLen; $inc++) { + $char = self::getInt1d($stream, $pos + $arrayReturn['length']); + if ($char == 0x0B) { + $char = 0x20; + } + $arrayReturn['text'] .= Text::chr($char); + $arrayReturn['length'] += 1; + } + break; + case self::RT_TEXTCHARSATOM: + $arrayReturn['length'] += 8; + // @link : http://msdn.microsoft.com/en-us/library/dd772921(v=office.12).aspx + $strLen = (int)($rhChild['recLen']/2); + for ($inc = 0; $inc < $strLen; $inc++) { + $char = self::getInt2d($stream, $pos + $arrayReturn['length']); + if ($char == 0x0B) { + $char = 0x20; + } + $arrayReturn['text'] .= Text::chr($char); + $arrayReturn['length'] += 2; + } + break; + case self::RT_TEXTHEADERATOM: + $arrayReturn['length'] += 8; + // @link : http://msdn.microsoft.com/en-us/library/dd905272(v=office.12).aspx + // textType + $arrayReturn['length'] += 4; + break; + case self::RT_TEXTINTERACTIVEINFOATOM: + $arrayReturn['length'] += 8; + //@link : http://msdn.microsoft.com/en-us/library/dd947973(v=office.12).aspx + if ($rhChild['recInstance'] == 0x0000) { + //@todo : MouseClickTextInteractiveInfoAtom + $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['start'] = self::getInt4d($stream, $pos + + $arrayReturn['length']); + $arrayReturn['length'] += 4; + + $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['end'] = self::getInt4d($stream, $pos + + $arrayReturn['length']); + $arrayReturn['length'] += 4; + } + if ($rhChild['recInstance'] == 0x0001) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + break; + case self::RT_TEXTSPECIALINFOATOM: + $arrayReturn['length'] += 8; + // @link : http://msdn.microsoft.com/en-us/library/dd945296(v=office.12).aspx + $strLenRT = $strLen + 1; + do { + $structTextSIRun = $this->readStructureTextSIRun($stream, $pos + $arrayReturn['length'], $strLenRT); + $strLenRT = $structTextSIRun['strLenRT']; + $arrayReturn['length'] += $structTextSIRun['length']; + } while ($strLenRT > 0); + break; + case self::RT_TEXTRULERATOM: + $arrayReturn['length'] += 8; + // @link : http://msdn.microsoft.com/en-us/library/dd953212(v=office.12).aspx + $structRuler = $this->readStructureTextRuler($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $structRuler['length']; + break; + case self::RT_SLIDENUMBERMETACHARATOM: + $datasRecord = $this->readRecordSlideNumberMCAtom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $datasRecord['length']; + break; + default: + $arrayReturn['length'] += 8; + $arrayReturn['length'] += $rhChild['recLen']; + // throw new \Exception('Feature not implemented (l.'.__LINE__.' : 0x'.dechex($rhChild['recType']).')'); + } + } while (($data['recLen'] - $arrayReturn['length']) > 0); + } + return $arrayReturn; + } + + /** + * The OfficeArtSpContainer record specifies a shape container. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd943794(v=office.12).aspx + */ + private function readRecordOfficeArtSpContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + 'shape' => null, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF004) { + // Record Header + $arrayReturn['length'] += 8; + // shapeGroup + $shapeGroup = $this->readRecordOfficeArtFSPGR($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $shapeGroup['length']; + + // shapeProp + $shapeProp = $this->readRecordOfficeArtFSP($stream, $pos + $arrayReturn['length']); + if ($shapeProp['length'] == 0) { + throw new \Exception('PowerPoint97 Reader : record OfficeArtFSP'); + } + $arrayReturn['length'] += $shapeProp['length']; + + if ($shapeProp['fDeleted'] == 0x1 && $shapeProp['fChild'] == 0x0) { + // deletedShape + $deletedShape = $this->readRecordOfficeArtFPSPL($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $deletedShape['length']; + } + + // shapePrimaryOptions + $shpPrimaryOptions = $this->readRecordOfficeArtFOPT($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $shpPrimaryOptions['length']; + + // shapeSecondaryOptions1 + $shpSecondaryOptions1 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $shpSecondaryOptions1['length']; + + // shapeTertiaryOptions1 + $shpTertiaryOptions1 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $shpTertiaryOptions1['length']; + + // childAnchor + $childAnchor = $this->readRecordOfficeArtChildAnchor($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $childAnchor['length']; + + // clientAnchor + $clientAnchor = $this->readRecordOfficeArtClientAnchor($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $clientAnchor['length']; + + // clientData + $clientData = $this->readRecordOfficeArtClientData($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $clientData['length']; + + // clientTextbox + $clientTextbox = $this->readRecordOfficeArtClientTextbox($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $clientTextbox['length']; + + // shapeSecondaryOptions2 + if ($shpSecondaryOptions1['length'] == 0) { + $shpSecondaryOptions2 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $shpSecondaryOptions2['length']; + } + + // shapeTertiaryOptions2 + if ($shpTertiaryOptions1['length'] == 0) { + $shpTertiaryOptions2 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $shpTertiaryOptions2['length']; + } + + // Core : Shape + // Informations about group are not defined + $arrayDimensions = array(); + $bIsGroup = false; + if (is_object($this->oCurrentGroup)) { + if (!$this->bFirstShapeGroup) { + if ($clientAnchor['length'] > 0) { + // $this->oCurrentGroup->setOffsetX($clientAnchor['left']); + // $this->oCurrentGroup->setOffsetY($clientAnchor['top']); + // $this->oCurrentGroup->setHeight($clientAnchor['height']); + // $this->oCurrentGroup->setWidth($clientAnchor['width']); + } + $bIsGroup = true; + $this->bFirstShapeGroup = true; + } else { + if ($childAnchor['length'] > 0) { + $arrayDimensions = $childAnchor; + } + } + } else { + if ($clientAnchor['length'] > 0) { + $arrayDimensions = $clientAnchor; + } + } + if (!$bIsGroup) { + // *** Shape *** + if (isset($shpPrimaryOptions['pib'])) { + // isDrawing + $drawingPib = $shpPrimaryOptions['pib']; + if (isset($this->arrayPictures[$drawingPib - 1])) { + $gdImage = imagecreatefromstring($this->arrayPictures[$drawingPib - 1]); + $arrayReturn['shape'] = new Drawing\Gd(); + $arrayReturn['shape']->setImageResource($gdImage); + } + } elseif (isset($shpPrimaryOptions['line']) && $shpPrimaryOptions['line']) { + // isLine + $arrayReturn['shape'] = new Line(0, 0, 0, 0); + } elseif ($clientTextbox['length'] > 0) { + $arrayReturn['shape'] = new RichText(); + if (isset($clientTextbox['alignH'])) { + $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setHorizontal($clientTextbox['alignH']); + } + + $start = 0; + $lastLevel = -1; + $lastMarginLeft = 0; + for ($inc = 1; $inc <= $clientTextbox['numParts']; $inc++) { + if ($clientTextbox['numParts'] == $clientTextbox['numTexts'] && isset($clientTextbox['text'.$inc])) { + if (isset($clientTextbox['text'.$inc]['bulletChar'])) { + $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletType(Bullet::TYPE_BULLET); + $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletChar($clientTextbox['text'.$inc]['bulletChar']); + } + // Indent + $indent = 0; + if (isset($clientTextbox['text'.$inc]['indent'])) { + $indent = $clientTextbox['text'.$inc]['indent']; + } + if (isset($clientTextbox['text'.$inc]['leftMargin'])) { + if ($lastMarginLeft > $clientTextbox['text'.$inc]['leftMargin']) { + $lastLevel--; + } + if ($lastMarginLeft < $clientTextbox['text'.$inc]['leftMargin']) { + $lastLevel++; + } + $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setLevel($lastLevel); + $lastMarginLeft = $clientTextbox['text'.$inc]['leftMargin']; + + $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setMarginLeft($clientTextbox['text'.$inc]['leftMargin']); + $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setIndent($indent - $clientTextbox['text'.$inc]['leftMargin']); + } + } + // Texte + $sText = substr(isset($clientTextbox['text']) ? $clientTextbox['text'] : '', $start, $clientTextbox['part'.$inc]['partLength']); + $sHyperlinkURL = ''; + if (empty($sText)) { + // Is there a hyperlink ? + if (isset($clientTextbox['hyperlink']) && is_array($clientTextbox['hyperlink']) && !empty($clientTextbox['hyperlink'])) { + foreach ($clientTextbox['hyperlink'] as $itmHyperlink) { + if ($itmHyperlink['start'] == $start && ($itmHyperlink['end'] - $itmHyperlink['start']) == $clientTextbox['part'.$inc]['partLength']) { + $sText = $this->arrayHyperlinks[$itmHyperlink['id']]['text']; + $sHyperlinkURL = $this->arrayHyperlinks[$itmHyperlink['id']]['url']; + break; + } + } + } + } + // New paragraph + $bCreateParagraph = false; + if (strpos($sText, "\r") !== false) { + $bCreateParagraph = true; + $sText = str_replace("\r", '', $sText); + } + // TextRun + $txtRun = $arrayReturn['shape']->createTextRun($sText); + if (isset($clientTextbox['part'.$inc]['bold'])) { + $txtRun->getFont()->setBold($clientTextbox['part'.$inc]['bold']); + } + if (isset($clientTextbox['part'.$inc]['italic'])) { + $txtRun->getFont()->setItalic($clientTextbox['part'.$inc]['italic']); + } + if (isset($clientTextbox['part'.$inc]['underline'])) { + $txtRun->getFont()->setUnderline($clientTextbox['part'.$inc]['underline']); + } + if (isset($clientTextbox['part'.$inc]['fontName'])) { + $txtRun->getFont()->setName($clientTextbox['part'.$inc]['fontName']); + } + if (isset($clientTextbox['part'.$inc]['fontSize'])) { + $txtRun->getFont()->setSize($clientTextbox['part'.$inc]['fontSize']); + } + if (isset($clientTextbox['part'.$inc]['color'])) { + $txtRun->getFont()->setColor($clientTextbox['part'.$inc]['color']); + } + // Hyperlink + if (!empty($sHyperlinkURL)) { + $txtRun->setHyperlink(new Hyperlink($sHyperlinkURL)); + } + + $start += $clientTextbox['part'.$inc]['partLength']; + if ($bCreateParagraph) { + $arrayReturn['shape']->createParagraph(); + } + } + } + + // *** Properties *** + // Dimensions + if ($arrayReturn['shape'] instanceof AbstractShape) { + if (!empty($arrayDimensions)) { + $arrayReturn['shape']->setOffsetX($arrayDimensions['left']); + $arrayReturn['shape']->setOffsetY($arrayDimensions['top']); + $arrayReturn['shape']->setHeight($arrayDimensions['height']); + $arrayReturn['shape']->setWidth($arrayDimensions['width']); + } + // Rotation + if (isset($shpPrimaryOptions['rotation'])) { + $rotation = $shpPrimaryOptions['rotation']; + $arrayReturn['shape']->setRotation($rotation); + } + // Shadow + if (isset($shpPrimaryOptions['shadowOffsetX']) && isset($shpPrimaryOptions['shadowOffsetY'])) { + $shadowOffsetX = $shpPrimaryOptions['shadowOffsetX']; + $shadowOffsetY = $shpPrimaryOptions['shadowOffsetY']; + if ($shadowOffsetX != 0 && $shadowOffsetX != 0) { + $arrayReturn['shape']->getShadow()->setVisible(true); + if ($shadowOffsetX > 0 && $shadowOffsetX == $shadowOffsetY) { + $arrayReturn['shape']->getShadow()->setDistance($shadowOffsetX)->setDirection(45); + } + } + } + // Specific Line + if ($arrayReturn['shape'] instanceof Line) { + if (isset($shpPrimaryOptions['lineColor'])) { + $arrayReturn['shape']->getBorder()->getColor()->setARGB('FF'.$shpPrimaryOptions['lineColor']); + } + if (isset($shpPrimaryOptions['lineWidth'])) { + $arrayReturn['shape']->setHeight($shpPrimaryOptions['lineWidth']); + } + } + // Specific RichText + if ($arrayReturn['shape'] instanceof RichText) { + if (isset($shpPrimaryOptions['insetBottom'])) { + $arrayReturn['shape']->setInsetBottom($shpPrimaryOptions['insetBottom']); + } + if (isset($shpPrimaryOptions['insetLeft'])) { + $arrayReturn['shape']->setInsetLeft($shpPrimaryOptions['insetLeft']); + } + if (isset($shpPrimaryOptions['insetRight'])) { + $arrayReturn['shape']->setInsetRight($shpPrimaryOptions['insetRight']); + } + if (isset($shpPrimaryOptions['insetTop'])) { + $arrayReturn['shape']->setInsetTop($shpPrimaryOptions['insetTop']); + } + } + } + } else { + // Rotation + if (isset($shpPrimaryOptions['rotation'])) { + $rotation = $shpPrimaryOptions['rotation']; + $this->oCurrentGroup->setRotation($rotation); + } + } + } + + return $arrayReturn; + } + + /** + * The OfficeArtSpgrContainer record specifies a container for groups of shapes. + * @param string $stream + * @param integer $pos + * @param boolean $bInGroup + * @return array + * @throws \Exception + * @link : https://msdn.microsoft.com/en-us/library/dd910416(v=office.12).aspx + */ + private function readRecordOfficeArtSpgrContainer($stream, $pos, $bInGroup = false) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF003) { + $arrayReturn['length'] += 8; + + do { + $rhFileBlock = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); + if (!($rhFileBlock['recVer'] == 0xF && $rhFileBlock['recInstance'] == 0x0000 && ($rhFileBlock['recType'] == 0xF003 || $rhFileBlock['recType'] == 0xF004))) { + throw new \Exception('PowerPoint97 Reader : readRecordOfficeArtSpgrContainer.'); + } + + switch ($rhFileBlock['recType']) { + case 0xF003: + // Core + $this->oCurrentGroup = $this->oPhpPresentation->getActiveSlide()->createGroup(); + $this->bFirstShapeGroup = false; + // OfficeArtSpgrContainer + $fileBlock = $this->readRecordOfficeArtSpgrContainer($stream, $pos + $arrayReturn['length'], true); + $arrayReturn['length'] += $fileBlock['length']; + $data['recLen'] -= $fileBlock['length']; + break; + case 0xF004: + // Core + if (!$bInGroup) { + $this->oCurrentGroup = null; + } + // OfficeArtSpContainer + $fileBlock = $this->readRecordOfficeArtSpContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $fileBlock['length']; + $data['recLen'] -= $fileBlock['length']; + // Core + //@todo + if (!is_null($fileBlock['shape'])) { + switch ($this->inMainType) { + case self::RT_NOTES: + $arrayIdxSlide = array_flip($this->arrayNotes); + if ($this->currentNote > 0 && isset($arrayIdxSlide[$this->currentNote])) { + $oSlide = $this->oPhpPresentation->getSlide($arrayIdxSlide[$this->currentNote]); + if ($oSlide->getNote()->getShapeCollection()->count() == 0) { + $oSlide->getNote()->addShape($fileBlock['shape']); + } + } + break; + case self::RT_SLIDE: + if ($bInGroup) { + $this->oCurrentGroup->addShape($fileBlock['shape']); + } else { + $this->oPhpPresentation->getActiveSlide()->addShape($fileBlock['shape']); + } + break; + } + } + + break; + } + } while ($data['recLen'] > 0); + } + return $arrayReturn; + } + + /** + * The OfficeArtTertiaryFOPT record specifies a table of OfficeArtRGFOPTE records,. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd950206(v=office.12).aspx + */ + private function readRecordOfficeArtTertiaryFOPT($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x3 && $data['recType'] == 0xF122) { + // Record Header + $arrayReturn['length'] += 8; + + $officeArtFOPTE = array(); + for ($inc = 0; $inc < $data['recInstance']; $inc++) { + $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + $officeArtFOPTE[] = array( + 'opid' => ($opid >> 0) & bindec('11111111111111'), + 'fBid' => ($opid >> 14) & bindec('1'), + 'fComplex' => ($opid >> 15) & bindec('1'), + 'op' => $optOp, + ); + } + //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID + foreach ($officeArtFOPTE as $opt) { + switch ($opt['opid']) { + case 0x039F: + // Table properties + //@link : https://msdn.microsoft.com/en-us/library/dd922773(v=office.12).aspx + break; + case 0x03A0: + // Table Row Properties + //@link : https://msdn.microsoft.com/en-us/library/dd923419(v=office.12).aspx + if ($opt['fComplex'] == 0x1) { + $arrayReturn['length'] += $opt['op']; + } + break; + case 0x03A9: + // GroupShape : metroBlob + //@link : https://msdn.microsoft.com/en-us/library/dd943388(v=office.12).aspx + if ($opt['fComplex'] == 0x1) { + $arrayReturn['length'] += $opt['op']; + } + break; + case 0x01FF: + // Line Style Boolean + //@link : https://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx + break; + default: + throw new \Exception('Feature not implemented (l.'.__LINE__.' : 0x'.dechex($opt['opid']).')'); + } + } + } + return $arrayReturn; + } + + /** + * The OfficeArtDgContainer record specifies the container for all the file records for the objects in a drawing. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link : https://msdn.microsoft.com/en-us/library/dd924455(v=office.12).aspx + */ + private function readRecordOfficeArtDgContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF002) { + // Record Header + $arrayReturn['length'] += 8; + // drawingData + $drawingData = $this->readRecordOfficeArtFDG($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $drawingData['length']; + // regroupItems + //@todo + // groupShape + $groupShape = $this->readRecordOfficeArtSpgrContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $groupShape['length']; + // shape + $shape = $this->readRecordOfficeArtSpContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $shape['length']; + // solvers1 + //@todo + // deletedShapes + //@todo + // solvers1 + //@todo + } + + return $arrayReturn; + } + + /** + * The OfficeArtFDG record specifies the number of shapes, the drawing identifier, and the shape identifier of the last shape in a drawing. + * @param string $stream + * @param integer $pos + * @return array + * @link : https://msdn.microsoft.com/en-us/library/dd946757(v=office.12).aspx + */ + private function readRecordOfficeArtFDG($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] <= 0xFFE && $data['recType'] == 0xF008 && $data['recLen'] == 0x00000008) { + // Record Header + $arrayReturn['length'] += 8; + // Length + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * The OfficeArtFOPT record specifies a table of OfficeArtRGFOPTE records. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd943404(v=office.12).aspx + */ + private function readRecordOfficeArtFOPT($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x3 && $data['recType'] == 0xF00B) { + // Record Header + $arrayReturn['length'] += 8; + + //@link : http://msdn.microsoft.com/en-us/library/dd906086(v=office.12).aspx + $officeArtFOPTE = array(); + for ($inc = 0; $inc < $data['recInstance']; $inc++) { + $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $data['recLen'] -= 2; + $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + $data['recLen'] -= 4; + $officeArtFOPTE[] = array( + 'opid' => ($opid >> 0) & bindec('11111111111111'), + 'fBid' => ($opid >> 14) & bindec('1'), + 'fComplex' => ($opid >> 15) & bindec('1'), + 'op' => $optOp, + ); + } + //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID + foreach ($officeArtFOPTE as $opt) { + // echo $opt['opid'].'-0x'.dechex($opt['opid']).EOL; + switch ($opt['opid']) { + case 0x0004: + // Transform : rotation + //@link : https://msdn.microsoft.com/en-us/library/dd949750(v=office.12).aspx + $arrayReturn['rotation'] = $opt['op']; + break; + case 0x007F: + // Transform : Protection Boolean Properties + //@link : http://msdn.microsoft.com/en-us/library/dd909131(v=office.12).aspx + break; + case 0x0080: + // Text : ltxid + //@link : http://msdn.microsoft.com/en-us/library/dd947446(v=office.12).aspx + break; + case 0x0081: + // Text : dxTextLeft + //@link : http://msdn.microsoft.com/en-us/library/dd953234(v=office.12).aspx + $arrayReturn['insetLeft'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + break; + case 0x0082: + // Text : dyTextTop + //@link : http://msdn.microsoft.com/en-us/library/dd925068(v=office.12).aspx + $arrayReturn['insetTop'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + break; + case 0x0083: + // Text : dxTextRight + //@link : http://msdn.microsoft.com/en-us/library/dd906782(v=office.12).aspx + $arrayReturn['insetRight'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + break; + case 0x0084: + // Text : dyTextBottom + //@link : http://msdn.microsoft.com/en-us/library/dd772858(v=office.12).aspx + $arrayReturn['insetBottom'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + break; + case 0x0085: + // Text : WrapText + //@link : http://msdn.microsoft.com/en-us/library/dd924770(v=office.12).aspx + break; + case 0x0087: + // Text : anchorText + //@link : http://msdn.microsoft.com/en-us/library/dd948575(v=office.12).aspx + break; + case 0x00BF: + // Text : Text Boolean Properties + //@link : http://msdn.microsoft.com/en-us/library/dd950905(v=office.12).aspx + break; + case 0x0104: + // Blip : pib + //@link : http://msdn.microsoft.com/en-us/library/dd772837(v=office.12).aspx + if ($opt['fComplex'] == 0) { + $arrayReturn['pib'] = $opt['op']; + $data['recLen'] -= $opt['op']; + } else { + // pib Complex + } + break; + case 0x13F: + // Blip Boolean Properties + //@link : https://msdn.microsoft.com/en-us/library/dd944215(v=office.12).aspx + break; + case 0x140: + // Geometry : geoLeft + //@link : http://msdn.microsoft.com/en-us/library/dd947489(v=office.12).aspx + // print_r('geoLeft : '.$opt['op'].EOL); + break; + case 0x141: + // Geometry : geoTop + //@link : http://msdn.microsoft.com/en-us/library/dd949459(v=office.12).aspx + // print_r('geoTop : '.$opt['op'].EOL); + break; + case 0x142: + // Geometry : geoRight + //@link : http://msdn.microsoft.com/en-us/library/dd947117(v=office.12).aspx + // print_r('geoRight : '.$opt['op'].EOL); + break; + case 0x143: + // Geometry : geoBottom + //@link : http://msdn.microsoft.com/en-us/library/dd948602(v=office.12).aspx + // print_r('geoBottom : '.$opt['op'].EOL); + break; + case 0x144: + // Geometry : shapePath + //@link : http://msdn.microsoft.com/en-us/library/dd945249(v=office.12).aspx + $arrayReturn['line'] = true; + break; + case 0x145: + // Geometry : pVertices + //@link : http://msdn.microsoft.com/en-us/library/dd949814(v=office.12).aspx + if ($opt['fComplex'] == 1) { + $arrayReturn['length'] += $opt['op']; + $data['recLen'] -= $opt['op']; + } + break; + case 0x146: + // Geometry : pSegmentInfo + //@link : http://msdn.microsoft.com/en-us/library/dd905742(v=office.12).aspx + if ($opt['fComplex'] == 1) { + $arrayReturn['length'] += $opt['op']; + $data['recLen'] -= $opt['op']; + } + break; + case 0x155: + // Geometry : pAdjustHandles + //@link : http://msdn.microsoft.com/en-us/library/dd905890(v=office.12).aspx + if ($opt['fComplex'] == 1) { + $arrayReturn['length'] += $opt['op']; + $data['recLen'] -= $opt['op']; + } + break; + case 0x156: + // Geometry : pGuides + //@link : http://msdn.microsoft.com/en-us/library/dd910801(v=office.12).aspx + if ($opt['fComplex'] == 1) { + $arrayReturn['length'] += $opt['op']; + $data['recLen'] -= $opt['op']; + } + break; + case 0x157: + // Geometry : pInscribe + //@link : http://msdn.microsoft.com/en-us/library/dd904889(v=office.12).aspx + if ($opt['fComplex'] == 1) { + $arrayReturn['length'] += $opt['op']; + $data['recLen'] -= $opt['op']; + } + break; + case 0x17F: + // Geometry Boolean Properties + //@link : http://msdn.microsoft.com/en-us/library/dd944968(v=office.12).aspx + break; + case 0x0180: + // Fill : fillType + //@link : http://msdn.microsoft.com/en-us/library/dd947909(v=office.12).aspx + break; + case 0x0181: + // Fill : fillColor + //@link : http://msdn.microsoft.com/en-us/library/dd921332(v=office.12).aspx + $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + // echo 'fillColor : '.$strColor.EOL; + break; + case 0x0183: + // Fill : fillBackColor + //@link : http://msdn.microsoft.com/en-us/library/dd950634(v=office.12).aspx + $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + // echo 'fillBackColor : '.$strColor.EOL; + break; + case 0x0193: + // Fill : fillRectRight + //@link : http://msdn.microsoft.com/en-us/library/dd951294(v=office.12).aspx + // echo 'fillRectRight : '.\PhpOffice\Common\Drawing::emuToPixels($opt['op']).EOL; + break; + case 0x0194: + // Fill : fillRectBottom + //@link : http://msdn.microsoft.com/en-us/library/dd910194(v=office.12).aspx + // echo 'fillRectBottom : '.\PhpOffice\Common\Drawing::emuToPixels($opt['op']).EOL; + break; + case 0x01BF: + // Fill : Fill Style Boolean Properties + //@link : http://msdn.microsoft.com/en-us/library/dd909380(v=office.12).aspx + break; + case 0x01C0: + // Line Style : lineColor + //@link : http://msdn.microsoft.com/en-us/library/dd920397(v=office.12).aspx + $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $arrayReturn['lineColor'] = $strColor; + break; + case 0x01C1: + // Line Style : lineOpacity + //@link : http://msdn.microsoft.com/en-us/library/dd923433(v=office.12).aspx + // echo 'lineOpacity : '.dechex($opt['op']).EOL; + break; + case 0x01C2: + // Line Style : lineBackColor + //@link : http://msdn.microsoft.com/en-us/library/dd947669(v=office.12).aspx + break; + case 0x01CB: + // Line Style : lineWidth + //@link : http://msdn.microsoft.com/en-us/library/dd926964(v=office.12).aspx + $arrayReturn['lineWidth'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + break; + case 0x01D6: + // Line Style : lineJoinStyle + //@link : http://msdn.microsoft.com/en-us/library/dd909643(v=office.12).aspx + break; + case 0x01D7: + // Line Style : lineEndCapStyle + //@link : http://msdn.microsoft.com/en-us/library/dd925071(v=office.12).aspx + break; + case 0x01FF: + // Line Style : Line Style Boolean Properties + //@link : http://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx + break; + case 0x0201: + // Shadow Style : shadowColor + //@link : http://msdn.microsoft.com/en-us/library/dd923454(v=office.12).aspx + break; + case 0x0204: + // Shadow Style : shadowOpacity + //@link : http://msdn.microsoft.com/en-us/library/dd920720(v=office.12).aspx + break; + case 0x0205: + // Shadow Style : shadowOffsetX + //@link : http://msdn.microsoft.com/en-us/library/dd945280(v=office.12).aspx + $arrayReturn['shadowOffsetX'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + break; + case 0x0206: + // Shadow Style : shadowOffsetY + //@link : http://msdn.microsoft.com/en-us/library/dd907855(v=office.12).aspx + $arrayReturn['shadowOffsetY'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + break; + case 0x023F: + // Shadow Style : Shadow Style Boolean Properties + //@link : http://msdn.microsoft.com/en-us/library/dd947887(v=office.12).aspx + break; + case 0x0304: + // Shape : bWMode + //@link : http://msdn.microsoft.com/en-us/library/dd947659(v=office.12).aspx + break; + case 0x033F: + // Shape Boolean Properties + //@link : http://msdn.microsoft.com/en-us/library/dd951345(v=office.12).aspx + break; + case 0x0380: + // Group Shape Property Set : wzName + //@link : http://msdn.microsoft.com/en-us/library/dd950681(v=office.12).aspx + if ($opt['fComplex'] == 1) { + $arrayReturn['length'] += $opt['op']; + $data['recLen'] -= $opt['op']; + } + break; + case 0x03BF: + // Group Shape Property Set : Group Shape Boolean Properties + //@link : http://msdn.microsoft.com/en-us/library/dd949807(v=office.12).aspx + break; + default: + // throw new \Exception('Feature not implemented (l.'.__LINE__.' : 0x'.dechex($opt['opid']).')'); + } + } + if ($data['recLen'] > 0) { + $arrayReturn['length'] += $data['recLen']; + } + } + + return $arrayReturn; + } + + /** + * The OfficeArtFPSPL record specifies the former hierarchical position of the containing object that is either a shape or a group of shapes. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd947479(v=office.12).aspx + */ + private function readRecordOfficeArtFPSPL($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == 0xF11D && $data['recLen'] == 0x00000004) { + $arrayReturn['length'] += 8; + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * The OfficeArtFSP record specifies an instance of a shape. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd925898(v=office.12).aspx + */ + private function readRecordOfficeArtFSP($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x2 && $data['recType'] == 0xF00A && $data['recLen'] == 0x00000008) { + $arrayReturn['length'] += 8; + // spid + $arrayReturn['length'] += 4; + // data + $data = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + $arrayReturn['fGroup'] = ($data >> 0) & bindec('1'); + $arrayReturn['fChild'] = ($data >> 1) & bindec('1'); + $arrayReturn['fPatriarch'] = ($data >> 2) & bindec('1'); + $arrayReturn['fDeleted'] = ($data >> 3) & bindec('1'); + } + + return $arrayReturn; + } + + /** + * The OfficeArtFSPGR record specifies the coordinate system of the group shape that the anchors of the child shape are expressed in. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd925381(v=office.12).aspx + */ + private function readRecordOfficeArtFSPGR($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x1 && $data['recInstance'] == 0x000 && $data['recType'] == 0xF009 && $data['recLen'] == 0x00000010) { + $arrayReturn['length'] += 8; + //$arrShapeGroup['xLeft'] = self::getInt4d($this->streamPowerpointDocument, $pos); + $arrayReturn['length'] += 4; + //$arrShapeGroup['yTop'] = self::getInt4d($this->streamPowerpointDocument, $pos); + $arrayReturn['length'] += 4; + //$arrShapeGroup['xRight'] = self::getInt4d($this->streamPowerpointDocument, $pos); + $arrayReturn['length'] += 4; + //$arrShapeGroup['yBottom'] = self::getInt4d($this->streamPowerpointDocument, $pos); + $arrayReturn['length'] += 4; + } + + return $arrayReturn; + } + + /** + * The OfficeArtSecondaryFOPT record specifies a table of OfficeArtRGFOPTE records. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd950259(v=office.12).aspx + */ + private function readRecordOfficeArtSecondaryFOPT($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x3 && $data['recType'] == 0xF121) { + // Record Header + $arrayReturn['length'] += 8; + // Length + $arrayReturn['length'] += $data['recLen']; + } + return $arrayReturn; + } + + /** + * A container record that specifies information about a shape. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link : https://msdn.microsoft.com/en-us/library/dd950927(v=office.12).aspx + */ + private function readRecordOfficeArtClientData($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF011) { + $arrayReturn['length'] += 8; + // shapeFlagsAtom (9 bytes) + $dataShapeFlagsAtom = $this->readRecordShapeFlagsAtom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataShapeFlagsAtom['length']; + + // shapeFlags10Atom (9 bytes) + $dataShapeFlags10Atom = $this->readRecordShapeFlags10Atom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataShapeFlags10Atom['length']; + + // exObjRefAtom (12 bytes) + $dataExObjRefAtom = $this->readRecordExObjRefAtom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataExObjRefAtom['length']; + + // animationInfo (variable) + $dataAnimationInfo = $this->readRecordAnimationInfoContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataAnimationInfo['length']; + + // mouseClickInteractiveInfo (variable) + $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $mouseClickInfo['length']; + + // mouseOverInteractiveInfo (variable) + $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $mouseOverInfo['length']; + + // placeholderAtom (16 bytes) + $dataPlaceholderAtom = $this->readRecordPlaceholderAtom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataPlaceholderAtom['length']; + + // recolorInfoAtom (variable) + $dataRecolorInfo = $this->readRecordRecolorInfoAtom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataRecolorInfo['length']; + + // rgShapeClientRoundtripData (variable) + $array = array( + self::RT_PROGTAGS, + self::RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM, + self::RT_ROUNDTRIPSHAPEID12ATOM, + self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM, + self::RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM, + ); + do { + $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); + if (in_array($dataHeaderRG['recType'], $array)) { + switch ($dataHeaderRG['recType']) { + case self::RT_PROGTAGS: + $dataRG = $this->readRecordShapeProgTagsContainer($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataRG['length']; + break; + case self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM: + $dataRG = $this->readRecordRoundTripHFPlaceholder12Atom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataRG['length']; + break; + case self::RT_ROUNDTRIPSHAPEID12ATOM: + $dataRG = $this->readRecordRoundTripShapeId12Atom($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += $dataRG['length']; + break; + default: + throw new \Exception('Feature not implemented (l.'.__LINE__.' : 0x'.dechex($dataHeaderRG['recType']).')'); + } + } + } while (in_array($dataHeaderRG['recType'], $array)); + } + + return $arrayReturn; + } + + /** + * An atom record that specifies a persist object directory. Each persist object identifier specified MUST be unique in that persist object directory. + * @link http://msdn.microsoft.com/en-us/library/dd952680(v=office.12).aspx + * @param string $stream + * @param integer $pos + * @throws \Exception + */ + private function readRecordPersistDirectoryAtom($stream, $pos) + { + $rHeader = $this->loadRecordHeader($stream, $pos); + $pos += 8; + if ($rHeader['recVer'] != 0x0 || $rHeader['recInstance'] != 0x000 || $rHeader['recType'] != self::RT_PERSISTDIRECTORYATOM) { + throw new \Exception('File PowerPoint 97 in error (Location : PersistDirectoryAtom > RecordHeader).'); + } + // rgPersistDirEntry + // @link : http://msdn.microsoft.com/en-us/library/dd947347(v=office.12).aspx + do { + $data = self::getInt4d($stream, $pos); + $pos += 4; + $rHeader['recLen'] -= 4; + //$persistId = ($data >> 0) & bindec('11111111111111111111'); + $cPersist = ($data >> 20) & bindec('111111111111'); + + $rgPersistOffset = array(); + for ($inc = 0; $inc < $cPersist; $inc++) { + $rgPersistOffset[] = self::getInt4d($stream, $pos); + $pos += 4; + $rHeader['recLen'] -= 4; + } + } while ($rHeader['recLen'] > 0); + $this->rgPersistDirEntry = $rgPersistOffset; + } + + /** + * A container record that specifies information about the headers (1) and footers within a slide. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd904856(v=office.12).aspx + * @return array + */ + private function readRecordPerSlideHeadersFootersContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_HEADERSFOOTERS) { + // Record Header + $arrayReturn['length'] += 8; + // Length + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies whether a shape is a placeholder shape. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd923930(v=office.12).aspx + * @return array + */ + private function readRecordPlaceholderAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_PLACEHOLDERATOM && $data['recLen'] == 0x00000008) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies a collection of re-color mappings for a metafile ([MS-WMF]). + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd904899(v=office.12).aspx + * @return array + */ + private function readRecordRecolorInfoAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_RECOLORINFOATOM) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies that a shape is a header or footerplaceholder shape. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd910800(v=office.12).aspx + * @return array + */ + private function readRecordRoundTripHFPlaceholder12Atom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM && $data['recLen'] == 0x00000001) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies a shape identifier. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd772926(v=office.12).aspx + * @return array + */ + private function readRecordRoundTripShapeId12Atom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_ROUNDTRIPSHAPEID12ATOM && $data['recLen'] == 0x00000004) { + // Record Header + $arrayReturn['length'] += 8; + // Length + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * A container record that specifies information about a slide that synchronizes to a slide in a slide library. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx + * @return array + */ + private function readRecordRoundTripSlideSyncInfo12Container($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_ROUNDTRIPSLIDESYNCINFO12) { + // Record Header + $arrayReturn['length'] += 8; + // Length + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies shape-level Boolean flags. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd908949(v=office.12).aspx + * @return array + */ + private function readRecordShapeFlags10Atom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SHAPEFLAGS10ATOM && $data['recLen'] == 0x00000001) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies shape-level Boolean flags. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd925824(v=office.12).aspx + * @return array + */ + private function readRecordShapeFlagsAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SHAPEATOM && $data['recLen'] == 0x00000001) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * A container record that specifies programmable tags with additional binary shape data. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd911033(v=office.12).aspx + * @return array + */ + private function readRecordShapeProgBinaryTagContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_PROGBINARYTAG) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * A container record that specifies programmable tags with additional shape data. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd911266(v=office.12).aspx + */ + private function readRecordShapeProgTagsContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_PROGTAGS) { + // Record Header + $arrayReturn['length'] += 8; + + $length = 0; + do { + $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length'] + $length); + switch ($dataHeaderRG['recType']) { + case self::RT_PROGBINARYTAG: + $dataRG = $this->readRecordShapeProgBinaryTagContainer($stream, $pos + $arrayReturn['length'] + $length); + $length += $dataRG['length']; + break; + //case self::RT_PROGSTRINGTAG: + default: + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + } while ($length < $data['recLen']); + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * An atom record that specifies information about a slide. + * @param string $stream + * @param integer $pos + * @return array + * @link https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx + */ + private function readRecordSlideAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x2 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SLIDEATOM) { + // Record Header + $arrayReturn['length'] += 8; + // slideAtom > geom + $arrayReturn['length'] += 4; + // slideAtom > rgPlaceholderTypes + $rgPlaceholderTypes = array(); + for ($inc = 0; $inc < 8; $inc++) { + $rgPlaceholderTypes[] = self::getInt1d($this->streamPowerpointDocument, $pos); + $arrayReturn['length'] += 1; + } + + // slideAtom > masterIdRef + $arrayReturn['length'] += 4; + // slideAtom > notesIdRef + $arrayReturn['length'] += 4; + // slideAtom > slideFlags + $arrayReturn['length'] += 2; + // slideAtom > unused; + $arrayReturn['length'] += 2; + } + + return $arrayReturn; + } + + /** + * A container record that specifies a presentation slide or title master slide. + * @param string $stream + * @param int $pos + * @throws \Exception + * @link http://msdn.microsoft.com/en-us/library/dd946323(v=office.12).aspx + */ + private function readRecordSlideContainer($stream, $pos) + { + // Core + $this->oPhpPresentation->createSlide(); + $this->oPhpPresentation->setActiveSlideIndex($this->oPhpPresentation->getSlideCount() - 1); + + // *** slideAtom (32 bytes) + $slideAtom = $this->readRecordSlideAtom($stream, $pos); + if ($slideAtom['length'] == 0) { + throw new \Exception('PowerPoint97 Reader : record SlideAtom'); + } + $pos += $slideAtom['length']; + + // *** slideShowSlideInfoAtom (24 bytes) + $slideShowInfoAtom = $this->readRecordSlideShowSlideInfoAtom($stream, $pos); + $pos += $slideShowInfoAtom['length']; + + // *** perSlideHFContainer (variable) : optional + $perSlideHFContainer = $this->readRecordPerSlideHeadersFootersContainer($stream, $pos); + $pos += $perSlideHFContainer['length']; + + // *** rtSlideSyncInfo12 (variable) : optional + $rtSlideSyncInfo12 = $this->readRecordRoundTripSlideSyncInfo12Container($stream, $pos); + $pos += $rtSlideSyncInfo12['length']; + + // *** drawing (variable) + $drawing = $this->readRecordDrawingContainer($stream, $pos); + $pos += $drawing['length']; + + // *** slideSchemeColorSchemeAtom (40 bytes) + $slideSchemeColorAtom = $this->readRecordSlideSchemeColorSchemeAtom($stream, $pos); + if ($slideSchemeColorAtom['length'] == 0) { + throw new \Exception('PowerPoint97 Reader : record SlideSchemeColorSchemeAtom'); + } + $pos += $slideSchemeColorAtom['length']; + + // *** slideNameAtom (variable) + $slideNameAtom = $this->readRecordSlideNameAtom($stream, $pos); + $pos += $slideNameAtom['length']; + + // *** slideProgTagsContainer (variable). + $slideProgTags = $this->readRecordSlideProgTagsContainer($stream, $pos); + $pos += $slideProgTags['length']; + + // *** rgRoundTripSlide (variable) + } + + /** + * An atom record that specifies the name of a slide. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd906297(v=office.12).aspx + * @return array + */ + private function readRecordSlideNameAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + 'slideName' => '', + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x003 && $data['recType'] == self::RT_CSTRING && $data['recLen'] % 2 == 0) { + // Record Header + $arrayReturn['length'] += 8; + // Length + $strLen = ($data['recLen'] / 2); + for ($inc = 0; $inc < $strLen; $inc++) { + $char = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $arrayReturn['slideName'] .= Text::chr($char); + } + } + + return $arrayReturn; + } + + /** + * An atom record that specifies a slide number metacharacter. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd945703(v=office.12).aspx + * @return array + */ + private function readRecordSlideNumberMCAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SLIDENUMBERMETACHARATOM && $data['recLen'] == 0x00000004) { + // Record Header + $arrayReturn['length'] += 8; + // Datas + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * A container record that specifies programmable tags with additional slide data. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd951946(v=office.12).aspx + * @return array + */ + private function readRecordSlideProgTagsContainer($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_PROGTAGS) { + // Record Header + $arrayReturn['length'] += 8; + // Length + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * A container record that specifies the color scheme used by a slide. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd949420(v=office.12).aspx + * @return array + */ + private function readRecordSlideSchemeColorSchemeAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x001 && $data['recType'] == self::RT_COLORSCHEMEATOM && $data['recLen'] == 0x00000020) { + // Record Header + $arrayReturn['length'] += 8; + // Length + $rgSchemeColor = array(); + for ($inc = 0; $inc <= 7; $inc++) { + $rgSchemeColor[] = array( + 'red' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4), + 'green' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 1), + 'blue' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 2), + ); + } + $arrayReturn['length'] += (8 * 4); + } + + return $arrayReturn; + } + + /** + * An atom record that specifies what transition effect to perform during a slide show, and how to advance to the next presentation slide. + * @param string $stream + * @param integer $pos + * @link https://msdn.microsoft.com/en-us/library/dd943408(v=office.12).aspx + * @return array + */ + private function readRecordSlideShowSlideInfoAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SLIDESHOWSLIDEINFOATOM && $data['recLen'] == 0x00000010) { + // Record Header + $arrayReturn['length'] += 8; + // Length; + $arrayReturn['length'] += $data['recLen']; + } + + return $arrayReturn; + } + + /** + * UserEditAtom + * @link http://msdn.microsoft.com/en-us/library/dd945746(v=office.12).aspx + * @param string $stream + * @param integer $pos + * @throws \Exception + */ + private function readRecordUserEditAtom($stream, $pos) + { + $rHeader = $this->loadRecordHeader($stream, $pos); + $pos += 8; + if ($rHeader['recVer'] != 0x0 || $rHeader['recInstance'] != 0x000 || $rHeader['recType'] != self::RT_USEREDITATOM || ($rHeader['recLen'] != 0x0000001C && $rHeader['recLen'] != 0x00000020)) { + throw new \Exception('File PowerPoint 97 in error (Location : UserEditAtom > RecordHeader).'); + } + + // lastSlideIdRef + $pos += 4; + // version + $pos += 2; + + // minorVersion + $minorVersion = self::getInt1d($stream, $pos); + $pos += 1; + if ($minorVersion != 0x00) { + throw new \Exception('File PowerPoint 97 in error (Location : UserEditAtom > minorVersion).'); + } + + // majorVersion + $majorVersion = self::getInt1d($stream, $pos); + $pos += 1; + if ($majorVersion != 0x03) { + throw new \Exception('File PowerPoint 97 in error (Location : UserEditAtom > majorVersion).'); + } + + // offsetLastEdit + $pos += 4; + // offsetPersistDirectory + $this->offsetPersistDirectory = self::getInt4d($stream, $pos); + $pos += 4; + + // docPersistIdRef + $docPersistIdRef = self::getInt4d($stream, $pos); + $pos += 4; + if ($docPersistIdRef != 0x00000001) { + throw new \Exception('File PowerPoint 97 in error (Location : UserEditAtom > docPersistIdRef).'); + } + + // persistIdSeed + $pos += 4; + // lastView + $pos += 2; + // unused + $pos += 2; + } + + /** + * A structure that specifies the character-level formatting of a run of text. + * @param string $stream + * @param int $pos + * @param int $strLenRT + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd945870(v=office.12).aspx + */ + private function readStructureTextCFRun($stream, $pos, $strLenRT) + { + $arrayReturn = array( + 'length' => 0, + 'strLenRT' => $strLenRT, + ); + + // rgTextCFRun + $countRgTextCFRun = self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['strLenRT'] -= $countRgTextCFRun; + $arrayReturn['length'] += 4; + $arrayReturn['partLength'] = $countRgTextCFRun; + + $masks = self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + + $masksData = array(); + $masksData['bold'] = ($masks >> 0) & bindec('1'); + $masksData['italic'] = ($masks >> 1) & bindec('1'); + $masksData['underline'] = ($masks >> 2) & bindec('1'); + $masksData['unused1'] = ($masks >> 3) & bindec('1'); + $masksData['shadow'] = ($masks >> 4) & bindec('1'); + $masksData['fehint'] = ($masks >> 5) & bindec('1'); + $masksData['unused2'] = ($masks >> 6) & bindec('1'); + $masksData['kumi'] = ($masks >> 7) & bindec('1'); + $masksData['unused3'] = ($masks >> 8) & bindec('1'); + $masksData['emboss'] = ($masks >> 9) & bindec('1'); + $masksData['fHasStyle'] = ($masks >> 10) & bindec('1111'); + $masksData['unused4'] = ($masks >> 14) & bindec('11'); + $masksData['typeface'] = ($masks >> 16) & bindec('1'); + $masksData['size'] = ($masks >> 17) & bindec('1'); + $masksData['color'] = ($masks >> 18) & bindec('1'); + $masksData['position'] = ($masks >> 19) & bindec('1'); + $masksData['pp10ext'] = ($masks >> 20) & bindec('1'); + $masksData['oldEATypeface'] = ($masks >> 21) & bindec('1'); + $masksData['ansiTypeface'] = ($masks >> 22) & bindec('1'); + $masksData['symbolTypeface'] = ($masks >> 23) & bindec('1'); + $masksData['newEATypeface'] = ($masks >> 24) & bindec('1'); + $masksData['csTypeface'] = ($masks >> 25) & bindec('1'); + $masksData['pp11ext'] = ($masks >> 26) & bindec('1'); + if ($masksData['bold'] == 1 || $masksData['italic'] == 1 || $masksData['underline'] == 1 || $masksData['shadow'] == 1 || $masksData['fehint'] == 1 || $masksData['kumi'] == 1 || $masksData['emboss'] == 1 || $masksData['fHasStyle'] == 1) { + $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + + $fontStyleFlags = array(); + $fontStyleFlags['bold'] = ($data >> 0) & bindec('1'); + $fontStyleFlags['italic'] = ($data >> 1) & bindec('1'); + $fontStyleFlags['underline'] = ($data >> 2) & bindec('1'); + $fontStyleFlags['unused1'] = ($data >> 3) & bindec('1'); + $fontStyleFlags['shadow'] = ($data >> 4) & bindec('1'); + $fontStyleFlags['fehint'] = ($data >> 5) & bindec('1'); + $fontStyleFlags['unused2'] = ($data >> 6) & bindec('1'); + $fontStyleFlags['kumi'] = ($data >> 7) & bindec('1'); + $fontStyleFlags['unused3'] = ($data >> 8) & bindec('1'); + $fontStyleFlags['emboss'] = ($data >> 9) & bindec('1'); + $fontStyleFlags['pp9rt'] = ($data >> 10) & bindec('1111'); + $fontStyleFlags['unused4'] = ($data >> 14) & bindec('11'); + + $arrayReturn['bold'] = ($fontStyleFlags['bold'] == 1) ? true : false; + $arrayReturn['italic'] = ($fontStyleFlags['italic'] == 1) ? true : false; + $arrayReturn['underline'] = ($fontStyleFlags['underline'] == 1) ? true : false; + } + if ($masksData['typeface'] == 1) { + $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $arrayReturn['fontName'] = isset($this->arrayFonts[$data]) ? $this->arrayFonts[$data] : ''; + } + if ($masksData['oldEATypeface'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['ansiTypeface'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['symbolTypeface'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['size'] == 1) { + $arrayReturn['fontSize'] = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['color'] == 1) { + $red = self::getInt1d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 1; + $green = self::getInt1d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 1; + $blue = self::getInt1d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 1; + $index = self::getInt1d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 1; + + if ($index == 0xFE) { + $strColor = str_pad(dechex($red), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex($green), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex($blue), 2, STR_PAD_LEFT, '0'); + + $arrayReturn['color'] = new Color('FF'.$strColor); + } + } + if ($masksData['position'] == 1) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + + return $arrayReturn; + } + + /** + * A structure that specifies the paragraph-level formatting of a run of text. + * @param string $stream + * @param integer $pos + * @param integer $strLenRT + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd923535(v=office.12).aspx + */ + private function readStructureTextPFRun($stream, $pos, $strLenRT) + { + $arrayReturn = array( + 'length' => 0, + 'strLenRT' => $strLenRT, + ); + + // rgTextPFRun + $countRgTextPFRun = self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['strLenRT'] -= $countRgTextPFRun; + $arrayReturn['length'] += 4; + + // indent + $arrayReturn['length'] += 2; + + $masks = self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + + $masksData = array(); + $masksData['hasBullet'] = ($masks >> 0) & bindec('1'); + $masksData['bulletHasFont'] = ($masks >> 1) & bindec('1'); + $masksData['bulletHasColor'] = ($masks >> 2) & bindec('1'); + $masksData['bulletHasSize'] = ($masks >> 3) & bindec('1'); + $masksData['bulletFont'] = ($masks >> 4) & bindec('1'); + $masksData['bulletColor'] = ($masks >> 5) & bindec('1'); + $masksData['bulletSize'] = ($masks >> 6) & bindec('1'); + $masksData['bulletChar'] = ($masks >> 7) & bindec('1'); + $masksData['leftMargin'] = ($masks >> 8) & bindec('1'); + $masksData['unused'] = ($masks >> 9) & bindec('1'); + $masksData['indent'] = ($masks >> 10) & bindec('1'); + $masksData['align'] = ($masks >> 11) & bindec('1'); + $masksData['lineSpacing'] = ($masks >> 12) & bindec('1'); + $masksData['spaceBefore'] = ($masks >> 13) & bindec('1'); + $masksData['spaceAfter'] = ($masks >> 14) & bindec('1'); + $masksData['defaultTabSize'] = ($masks >> 15) & bindec('1'); + $masksData['fontAlign'] = ($masks >> 16) & bindec('1'); + $masksData['charWrap'] = ($masks >> 17) & bindec('1'); + $masksData['wordWrap'] = ($masks >> 18) & bindec('1'); + $masksData['overflow'] = ($masks >> 19) & bindec('1'); + $masksData['tabStops'] = ($masks >> 20) & bindec('1'); + $masksData['textDirection'] = ($masks >> 21) & bindec('1'); + $masksData['reserved1'] = ($masks >> 22) & bindec('1'); + $masksData['bulletBlip'] = ($masks >> 23) & bindec('1'); + $masksData['bulletScheme'] = ($masks >> 24) & bindec('1'); + $masksData['bulletHasScheme'] = ($masks >> 25) & bindec('1'); + + $bulletFlags = array(); + if ($masksData['hasBullet'] == 1 || $masksData['bulletHasFont'] == 1 || $masksData['bulletHasColor'] == 1 || $masksData['bulletHasSize'] == 1) { + $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + + $bulletFlags['fHasBullet'] = ($data >> 0) & bindec('1'); + $bulletFlags['fBulletHasFont'] = ($data >> 1) & bindec('1'); + $bulletFlags['fBulletHasColor'] = ($data >> 2) & bindec('1'); + $bulletFlags['fBulletHasSize'] = ($data >> 3) & bindec('1'); + } + if ($masksData['bulletChar'] == 1) { + $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $arrayReturn['bulletChar'] = chr($data); + } + if ($masksData['bulletFont'] == 1) { + // $data = self::getInt2d($stream, $pos); + $arrayReturn['length'] += 2; + } + if ($masksData['bulletSize'] == 1) { + // $data = self::getInt2d($stream, $pos); + $arrayReturn['length'] += 2; + } + if ($masksData['bulletColor'] == 1) { + $red = self::getInt1d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 1; + $green = self::getInt1d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 1; + $blue = self::getInt1d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 1; + $index = self::getInt1d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 1; + + if ($index == 0xFE) { + $strColor = str_pad(dechex($red), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex($green), 2, STR_PAD_LEFT, '0'); + $strColor .= str_pad(dechex($blue), 2, STR_PAD_LEFT, '0'); + } + } + if ($masksData['align'] == 1) { + $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + switch ($data) { + case 0x0000: + $arrayReturn['alignH'] = Alignment::HORIZONTAL_LEFT; + break; + case 0x0001: + $arrayReturn['alignH'] = Alignment::HORIZONTAL_CENTER; + break; + case 0x0002: + $arrayReturn['alignH'] = Alignment::HORIZONTAL_RIGHT; + break; + case 0x0003: + $arrayReturn['alignH'] = Alignment::HORIZONTAL_JUSTIFY; + break; + case 0x0004: + $arrayReturn['alignH'] = Alignment::HORIZONTAL_DISTRIBUTED; + break; + case 0x0005: + $arrayReturn['alignH'] = Alignment::HORIZONTAL_DISTRIBUTED; + break; + case 0x0006: + $arrayReturn['alignH'] = Alignment::HORIZONTAL_JUSTIFY; + break; + default: + break; + } + } + if ($masksData['lineSpacing'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['spaceBefore'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['spaceAfter'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['leftMargin'] == 1) { + $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $arrayReturn['leftMargin'] = (int)round($data/6); + } + if ($masksData['indent'] == 1) { + $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $arrayReturn['indent'] = (int)round($data/6); + } + if ($masksData['defaultTabSize'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['tabStops'] == 1) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + if ($masksData['fontAlign'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['charWrap'] == 1 || $masksData['wordWrap'] == 1 || $masksData['overflow'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['textDirection'] == 1) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + + return $arrayReturn; + } + + /** + * A structure that specifies language and spelling information for a run of text. + * @param string $stream + * @param integer $pos + * @param string $strLenRT + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd909603(v=office.12).aspx + */ + private function readStructureTextSIRun($stream, $pos, $strLenRT) + { + $arrayReturn = array( + 'length' => 0, + 'strLenRT' => $strLenRT, + ); + + $arrayReturn['strLenRT'] -= self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + + $data = self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + $masksData = array(); + $masksData['spell'] = ($data >> 0) & bindec('1'); + $masksData['lang'] = ($data >> 1) & bindec('1'); + $masksData['altLang'] = ($data >> 2) & bindec('1'); + $masksData['unused1'] = ($data >> 3) & bindec('1'); + $masksData['unused2'] = ($data >> 4) & bindec('1'); + $masksData['fPp10ext'] = ($data >> 5) & bindec('1'); + $masksData['fBidi'] = ($data >> 6) & bindec('1'); + $masksData['unused3'] = ($data >> 7) & bindec('1'); + $masksData['reserved1'] = ($data >> 8) & bindec('1'); + $masksData['smartTag'] = ($data >> 9) & bindec('1'); + + if ($masksData['spell'] == 1) { + $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $masksSpell = array(); + $masksSpell['error'] = ($data >> 0) & bindec('1'); + $masksSpell['clean'] = ($data >> 1) & bindec('1'); + $masksSpell['grammar'] = ($data >> 2) & bindec('1'); + } + if ($masksData['lang'] == 1) { + // $data = self::getInt2d($stream, $pos); + $arrayReturn['length'] += 2; + } + if ($masksData['altLang'] == 1) { + // $data = self::getInt2d($stream, $pos); + $arrayReturn['length'] += 2; + } + if ($masksData['fBidi'] == 1) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + if ($masksData['fPp10ext'] == 1) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + if ($masksData['smartTag'] == 1) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + + return $arrayReturn; + } + + /** + * A structure that specifies tabbing, margins, and indentation for text. + * @param string $stream + * @param integer $pos + * @return array + * @throws \Exception + * @link https://msdn.microsoft.com/en-us/library/dd922749(v=office.12).aspx + */ + private function readStructureTextRuler($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = self::getInt4d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 4; + + $masksData = array(); + $masksData['fDefaultTabSize'] = ($data >> 0) & bindec('1'); + $masksData['fCLevels'] = ($data >> 1) & bindec('1'); + $masksData['fTabStops'] = ($data >> 2) & bindec('1'); + $masksData['fLeftMargin1'] = ($data >> 3) & bindec('1'); + $masksData['fLeftMargin2'] = ($data >> 4) & bindec('1'); + $masksData['fLeftMargin3'] = ($data >> 5) & bindec('1'); + $masksData['fLeftMargin4'] = ($data >> 6) & bindec('1'); + $masksData['fLeftMargin5'] = ($data >> 7) & bindec('1'); + $masksData['fIndent1'] = ($data >> 8) & bindec('1'); + $masksData['fIndent2'] = ($data >> 9) & bindec('1'); + $masksData['fIndent3'] = ($data >> 10) & bindec('1'); + $masksData['fIndent4'] = ($data >> 11) & bindec('1'); + $masksData['fIndent5'] = ($data >> 12) & bindec('1'); + + if ($masksData['fCLevels'] == 1) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + if ($masksData['fDefaultTabSize'] == 1) { + throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + } + if ($masksData['fTabStops'] == 1) { + $count = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $arrayTabStops = array(); + for ($inc = 0; $inc < $count; $inc++) { + $position = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $type = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + $arrayTabStops[] = array( + 'position' => $position, + 'type' => $type, + ); + } + } + if ($masksData['fLeftMargin1'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fIndent1'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fLeftMargin2'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fIndent2'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fLeftMargin3'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fIndent3'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fLeftMargin4'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fIndent4'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fLeftMargin5'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + if ($masksData['fIndent5'] == 1) { + // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); + $arrayReturn['length'] += 2; + } + + return $arrayReturn; + } + + /** + * @param $stream + * @param int $pos + * @throws \Exception + */ + private function readRecordNotesContainer($stream, $pos) + { + // notesAtom + $notesAtom = $this->readRecordNotesAtom($stream, $pos); + $pos += $notesAtom['length']; + + // drawing + $drawing = $this->readRecordDrawingContainer($stream, $pos); + $pos += $drawing['length']; + + // slideSchemeColorSchemeAtom + // slideNameAtom + // slideProgTagsContainer + // rgNotesRoundTripAtom + } + + /** + * @param $stream + * @param int $pos + * @return array + * @throws \Exception + */ + private function readRecordNotesAtom($stream, $pos) + { + $arrayReturn = array( + 'length' => 0, + ); + + $data = $this->loadRecordHeader($stream, $pos); + if ($data['recVer'] != 0x1 || $data['recInstance'] != 0x000 || $data['recType'] != self::RT_NOTESATOM || $data['recLen'] != 0x00000008) { + throw new \Exception('File PowerPoint 97 in error (Location : NotesAtom > RecordHeader)'); + } + // Record Header + $arrayReturn['length'] += 8; + // NotesAtom > slideIdRef + $notesIdRef = self::getInt4d($stream, $pos + $arrayReturn['length']); + if ($notesIdRef == -2147483648) { + $notesIdRef = 0; + } + $this->currentNote = $notesIdRef; + $arrayReturn['length'] += 4; + + // NotesAtom > slideFlags + $arrayReturn['length'] += 2; + // NotesAtom > unused + $arrayReturn['length'] += 2; + + return $arrayReturn; + } +} diff --git a/PhpOffice/PhpPresentation/Reader/ReaderInterface.php b/PhpOffice/PhpPresentation/Reader/ReaderInterface.php new file mode 100644 index 0000000..c5149e9 --- /dev/null +++ b/PhpOffice/PhpPresentation/Reader/ReaderInterface.php @@ -0,0 +1,41 @@ +fileSupportsUnserializePhpPresentation($pFilename); + } + + /** + * Does a file support UnserializePhpPresentation ? + * + * @param string $pFilename + * @throws \Exception + * @return boolean + */ + public function fileSupportsUnserializePhpPresentation($pFilename = '') + { + // Check if file exists + if (!file_exists($pFilename)) { + throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + } + + // File exists, does it contain PhpPresentation.xml? + return File::fileExists("zip://$pFilename#PhpPresentation.xml"); + } + + /** + * Loads PhpPresentation Serialized file + * + * @param string $pFilename + * @return \PhpOffice\PhpPresentation\PhpPresentation + * @throws \Exception + */ + public function load($pFilename) + { + // Check if file exists + if (!file_exists($pFilename)) { + throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + } + + // Unserialize... First make sure the file supports it! + if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { + throw new \Exception("Invalid file format for PhpOffice\PhpPresentation\Reader\Serialized: " . $pFilename . "."); + } + + return $this->loadSerialized($pFilename); + } + + /** + * Load PhpPresentation Serialized file + * + * @param string $pFilename + * @return \PhpOffice\PhpPresentation\PhpPresentation + */ + private function loadSerialized($pFilename) + { + $oArchive = new \ZipArchive(); + if ($oArchive->open($pFilename) === true) { + $xmlContent = $oArchive->getFromName('PhpPresentation.xml'); + + if (!empty($xmlContent)) { + $xmlData = simplexml_load_string($xmlContent); + $file = unserialize(base64_decode((string) $xmlData->data)); + + // Update media links + for ($i = 0; $i < $file->getSlideCount(); ++$i) { + for ($j = 0; $j < $file->getSlide($i)->getShapeCollection()->count(); ++$j) { + if ($file->getSlide($i)->getShapeCollection()->offsetGet($j) instanceof AbstractDrawingAdapter) { + $imgTemp = $file->getSlide($i)->getShapeCollection()->offsetGet($j); + $imgTemp->setPath('zip://' . $pFilename . '#media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME), false); + } + } + } + + $oArchive->close(); + return $file; + } + } + + return null; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/AbstractGraphic.php b/PhpOffice/PhpPresentation/Shape/AbstractGraphic.php new file mode 100644 index 0000000..982a08b --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/AbstractGraphic.php @@ -0,0 +1,247 @@ +name = ''; + $this->description = ''; + $this->resizeProportional = true; + + // Set image index + self::$imageCounter++; + $this->imageIndex = self::$imageCounter; + + // Initialize parent + parent::__construct(); + } + + public function __clone() + { + parent::__clone(); + + self::$imageCounter++; + $this->imageIndex = self::$imageCounter; + } + + /** + * Get image index + * + * @return int + */ + public function getImageIndex() + { + return $this->imageIndex; + } + + /** + * Get Name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set Name + * + * @param string $pValue + * @return $this + */ + public function setName($pValue = '') + { + $this->name = $pValue; + return $this; + } + + /** + * Get Description + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set Description + * + * @param string $pValue + * @return $this + */ + public function setDescription($pValue = '') + { + $this->description = $pValue; + + return $this; + } + + /** + * Set Width + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Shape\AbstractGraphic + */ + public function setWidth($pValue = 0) + { + // Resize proportional? + if ($this->resizeProportional && $pValue != 0 && $this->width != 0) { + $ratio = $this->height / $this->width; + $this->height = (int) round($ratio * $pValue); + } + + // Set width + $this->width = $pValue; + + return $this; + } + + /** + * Set Height + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Shape\AbstractGraphic + */ + public function setHeight($pValue = 0) + { + // Resize proportional? + if ($this->resizeProportional && $pValue != 0 && $this->height != 0) { + $ratio = $this->width / $this->height; + $this->width = (int) round($ratio * $pValue); + } + + // Set height + $this->height = $pValue; + + return $this; + } + + /** + * Set width and height with proportional resize + * @author Vincent@luo MSN:kele_100@hotmail.com + * @param int $width + * @param int $height + * @return \PhpOffice\PhpPresentation\Shape\AbstractGraphic + */ + public function setWidthAndHeight($width = 0, $height = 0) + { + $xratio = $width / $this->width; + $yratio = $height / $this->height; + if ($this->resizeProportional && !($width == 0 || $height == 0)) { + if (($xratio * $this->height) < $height) { + $this->height = (int) ceil($xratio * $this->height); + $this->width = $width; + } else { + $this->width = (int) ceil($yratio * $this->width); + $this->height = $height; + } + } + + return $this; + } + + /** + * Get ResizeProportional + * + * @return boolean + */ + public function isResizeProportional() + { + return $this->resizeProportional; + } + + /** + * Set ResizeProportional + * + * @param boolean $pValue + * @return \PhpOffice\PhpPresentation\Shape\AbstractGraphic + */ + public function setResizeProportional($pValue = true) + { + $this->resizeProportional = $pValue; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->name . $this->description . parent::getHashCode() . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart.php b/PhpOffice/PhpPresentation/Shape/Chart.php new file mode 100644 index 0000000..a851801 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart.php @@ -0,0 +1,172 @@ +title = new Title(); + $this->legend = new Legend(); + $this->plotArea = new PlotArea(); + $this->view3D = new View3D(); + + // Initialize parent + parent::__construct(); + } + + public function __clone() + { + parent::__clone(); + + $this->title = clone $this->title; + $this->legend = clone $this->legend; + $this->plotArea = clone $this->plotArea; + $this->view3D = clone $this->view3D; + } + + /** + * Get Title + * + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function getTitle() + { + return $this->title; + } + + /** + * Get Legend + * + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function getLegend() + { + return $this->legend; + } + + /** + * Get PlotArea + * + * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + */ + public function getPlotArea() + { + return $this->plotArea; + } + + /** + * Get View3D + * + * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D + */ + public function getView3D() + { + return $this->view3D; + } + + /** + * Include spreadsheet for editing data? Requires PHPExcel in the same folder as PhpPresentation + * + * @return boolean + */ + public function hasIncludedSpreadsheet() + { + return $this->includeSpreadsheet; + } + + /** + * Include spreadsheet for editing data? Requires PHPExcel in the same folder as PhpPresentation + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart + */ + public function setIncludeSpreadsheet($value = false) + { + $this->includeSpreadsheet = $value; + return $this; + } + + /** + * Get indexed filename (using image index) + * + * @return string + */ + public function getIndexedFilename() + { + return 'chart' . $this->getImageIndex() . '.xml'; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5(parent::getHashCode() . $this->title->getHashCode() . $this->legend->getHashCode() . $this->plotArea->getHashCode() . $this->view3D->getHashCode() . ($this->includeSpreadsheet ? 1 : 0) . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Axis.php b/PhpOffice/PhpPresentation/Shape/Chart/Axis.php new file mode 100644 index 0000000..e9433da --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Axis.php @@ -0,0 +1,416 @@ +title = $title; + $this->outline = new Outline(); + $this->font = new Font(); + } + + /** + * Get Title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set Title + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + */ + public function setTitle($value = 'Axis Title') + { + $this->title = $value; + + return $this; + } + + /** + * Get font + * + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font + * + * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + */ + public function setFont(Font $pFont = null) + { + $this->font = $pFont; + return $this; + } + + /** + * Get Format Code + * + * @return string + */ + public function getFormatCode() + { + return $this->formatCode; + } + + /** + * Set Format Code + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + */ + public function setFormatCode($value = '') + { + $this->formatCode = $value; + + return $this; + } + + /** + * @return int|null + */ + public function getMinBounds() + { + return $this->minBounds; + } + + /** + * @param int|null $minBounds + * @return Axis + */ + public function setMinBounds($minBounds = null) + { + $this->minBounds = is_null($minBounds) ? null : (int)$minBounds; + return $this; + } + + /** + * @return int|null + */ + public function getMaxBounds() + { + return $this->maxBounds; + } + + /** + * @param int|null $maxBounds + * @return Axis + */ + public function setMaxBounds($maxBounds = null) + { + $this->maxBounds = is_null($maxBounds) ? null : (int)$maxBounds; + return $this; + } + + /** + * @return Gridlines + */ + public function getMajorGridlines() + { + return $this->majorGridlines; + } + + /** + * @param Gridlines $majorGridlines + * @return Axis + */ + public function setMajorGridlines(Gridlines $majorGridlines) + { + $this->majorGridlines = $majorGridlines; + return $this; + } + + /** + * @return Gridlines + */ + public function getMinorGridlines() + { + return $this->minorGridlines; + } + + /** + * @param Gridlines $minorGridlines + * @return Axis + */ + public function setMinorGridlines(Gridlines $minorGridlines) + { + $this->minorGridlines = $minorGridlines; + return $this; + } + + /** + * @return string + */ + public function getMinorTickMark() + { + return $this->minorTickMark; + } + + /** + * @param string $pTickMark + * @return Axis + */ + public function setMinorTickMark($pTickMark = self::TICK_MARK_NONE) + { + $this->minorTickMark = $pTickMark; + return $this; + } + + /** + * @return string + */ + public function getMajorTickMark() + { + return $this->majorTickMark; + } + + /** + * @param string $pTickMark + * @return Axis + */ + public function setMajorTickMark($pTickMark = self::TICK_MARK_NONE) + { + $this->majorTickMark = $pTickMark; + return $this; + } + + /** + * @return float + */ + public function getMinorUnit() + { + return $this->minorUnit; + } + + /** + * @param float $pUnit + * @return Axis + */ + public function setMinorUnit($pUnit = null) + { + $this->minorUnit = $pUnit; + return $this; + } + + /** + * @return float + */ + public function getMajorUnit() + { + return $this->majorUnit; + } + + /** + * @param float $pUnit + * @return Axis + */ + public function setMajorUnit($pUnit = null) + { + $this->majorUnit = $pUnit; + return $this; + } + + /** + * @return Outline + */ + public function getOutline() + { + return $this->outline; + } + + /** + * @param Outline $outline + * @return Axis + */ + public function setOutline(Outline $outline) + { + $this->outline = $outline; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->title . $this->formatCode . __CLASS__); + } + + /** + * Hash index + * + * @var string + */ + private $hashIndex; + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + * @return $this + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + return $this; + } + + /** + * Axis is hidden ? + * @return boolean + */ + public function isVisible() + { + return $this->isVisible; + } + + /** + * Hide an axis + * + * @param boolean $value delete + * @return $this + */ + public function setIsVisible($value) + { + $this->isVisible = (bool)$value; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php b/PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php new file mode 100644 index 0000000..4040a5a --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php @@ -0,0 +1,36 @@ +outline = new Outline(); + } + + /** + * @return Outline + */ + public function getOutline() + { + return $this->outline; + } + + /** + * @param Outline $outline + * @return Gridlines + */ + public function setOutline(Outline $outline) + { + $this->outline = $outline; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Legend.php b/PhpOffice/PhpPresentation/Shape/Chart/Legend.php new file mode 100644 index 0000000..989d932 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Legend.php @@ -0,0 +1,384 @@ +font = new Font(); + $this->border = new Border(); + $this->fill = new Fill(); + $this->alignment = new Alignment(); + } + + /** + * Get Visible + * + * @return boolean + */ + public function isVisible() + { + return $this->visible; + } + + /** + * Set Visible + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setVisible($value = true) + { + $this->visible = $value; + return $this; + } + + /** + * Get Position + * + * @return string + */ + public function getPosition() + { + return $this->position; + } + + /** + * Set Position + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setPosition($value = self::POSITION_RIGHT) + { + $this->position = $value; + return $this; + } + + /** + * Get OffsetX (as a fraction of the chart) + * + * @return float + */ + public function getOffsetX() + { + return $this->offsetX; + } + + /** + * Set OffsetX (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setOffsetX($value = 0) + { + $this->offsetX = (double)$value; + return $this; + } + + /** + * Get OffsetY (as a fraction of the chart) + * + * @return float + */ + public function getOffsetY() + { + return $this->offsetY; + } + + /** + * Set OffsetY (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setOffsetY($value = 0) + { + $this->offsetY = (double)$value; + return $this; + } + + /** + * Get Width (as a fraction of the chart) + * + * @return float + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set Width (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setWidth($value = 0) + { + $this->width = (double)$value; + return $this; + } + + /** + * Get Height (as a fraction of the chart) + * + * @return float + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set Height (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setHeight($value = 0) + { + $this->height = (double)$value; + return $this; + } + + /** + * Get font + * + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font + * + * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setFont(Font $pFont = null) + { + $this->font = $pFont; + return $this; + } + + /** + * Get Border + * + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function getBorder() + { + return $this->border; + } + + /** + * Set Border + * + * @param \PhpOffice\PhpPresentation\Style\Border $border + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setBorder(Border $border) + { + $this->border = $border; + return $this; + } + + /** + * Get Fill + * + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function getFill() + { + return $this->fill; + } + + /** + * Set Fill + * + * @param \PhpOffice\PhpPresentation\Style\Fill $fill + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setFill(Fill $fill) + { + $this->fill = $fill; + return $this; + } + + /** + * Get alignment + * + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * Set alignment + * + * @param \PhpOffice\PhpPresentation\Style\Alignment $alignment + * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + */ + public function setAlignment(Alignment $alignment) + { + $this->alignment = $alignment; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->position . $this->offsetX . $this->offsetY . $this->width . $this->height . $this->font->getHashCode() . $this->border->getHashCode() . $this->fill->getHashCode() . $this->alignment->getHashCode() . ($this->visible ? 't' : 'f') . __CLASS__); + } + + /** + * Hash index + * + * @var string + */ + private $hashIndex; + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + * @return Legend + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Marker.php b/PhpOffice/PhpPresentation/Shape/Chart/Marker.php new file mode 100644 index 0000000..c1ca912 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Marker.php @@ -0,0 +1,94 @@ +symbol; + } + + /** + * @param string $symbol + * @return $this + */ + public function setSymbol($symbol = self::SYMBOL_NONE) + { + $this->symbol = $symbol; + return $this; + } + + /** + * @return int + */ + public function getSize() + { + return $this->size; + } + + /** + * @param int $size + * @return $this + */ + public function setSize($size = 5) + { + $this->size = $size; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php b/PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php new file mode 100644 index 0000000..a46a3b2 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php @@ -0,0 +1,277 @@ +type = null; + $this->axisX = new Axis(); + $this->axisY = new Axis(); + } + + public function __clone() + { + $this->axisX = clone $this->axisX; + $this->axisY = clone $this->axisY; + } + + /** + * Get type + * + * @return AbstractType + * @throws \Exception + */ + public function getType() + { + if (is_null($this->type)) { + throw new \Exception('Chart type has not been set.'); + } + + return $this->type; + } + + /** + * Set type + * + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractType $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + */ + public function setType(Type\AbstractType $value) + { + $this->type = $value; + + return $this; + } + + /** + * Get Axis X + * + * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + */ + public function getAxisX() + { + return $this->axisX; + } + + /** + * Get Axis Y + * + * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + */ + public function getAxisY() + { + return $this->axisY; + } + + /** + * Get OffsetX (as a fraction of the chart) + * + * @return float + */ + public function getOffsetX() + { + return $this->offsetX; + } + + /** + * Set OffsetX (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + */ + public function setOffsetX($value = 0) + { + $this->offsetX = (double)$value; + + return $this; + } + + /** + * Get OffsetY (as a fraction of the chart) + * + * @return float + */ + public function getOffsetY() + { + return $this->offsetY; + } + + /** + * Set OffsetY (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + */ + public function setOffsetY($value = 0) + { + $this->offsetY = (double)$value; + + return $this; + } + + /** + * Get Width (as a fraction of the chart) + * + * @return float + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set Width (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + */ + public function setWidth($value = 0) + { + $this->width = (double)$value; + + return $this; + } + + /** + * Get Height (as a fraction of the chart) + * + * @return float + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set Height (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + */ + public function setHeight($value = 0) + { + $this->height = (double)$value; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5((is_null($this->type) ? 'null' : $this->type->getHashCode()) . $this->axisX->getHashCode() . $this->axisY->getHashCode() . $this->offsetX . $this->offsetY . $this->width . $this->height . __CLASS__); + } + + /** + * Hash index + * + * @var string + */ + private $hashIndex; + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + * @return PlotArea + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Series.php b/PhpOffice/PhpPresentation/Shape/Chart/Series.php new file mode 100644 index 0000000..73cd03d --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Series.php @@ -0,0 +1,598 @@ +fill = new Fill(); + $this->font = new Font(); + $this->font->setName('Calibri'); + $this->font->setSize(9); + $this->title = $title; + $this->values = $values; + $this->marker = new Marker(); + } + + /** + * Get Title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set Title + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setTitle($value = 'Series Title') + { + $this->title = $value; + + return $this; + } + + /** + * Get Data Label NumFormat + * + * @return string + */ + public function getDlblNumFormat() + { + return $this->DlblNumFormat; + } + + /** + * Has Data Label NumFormat + * + * @return string + */ + public function hasDlblNumFormat() + { + return !empty($this->DlblNumFormat); + } + + /** + * Set Data Label NumFormat + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setDlblNumFormat($value = '') + { + $this->DlblNumFormat = $value; + return $this; + } + + /** + * Get Fill + * + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function getFill() + { + return $this->fill; + } + + /** + * Set Fill + * + * @param \PhpOffice\PhpPresentation\Style\Fill $fill + * @return Series + */ + public function setFill(Fill $fill = null) + { + $this->fill = $fill; + return $this; + } + + /** + * Get DataPointFill + * + * @param int $dataPointIndex Data point index. + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function getDataPointFill($dataPointIndex) + { + if (!isset($this->dataPointFills[$dataPointIndex])) { + $this->dataPointFills[$dataPointIndex] = new Fill(); + } + + return $this->dataPointFills[$dataPointIndex]; + } + + /** + * Get DataPointFills + * + * @return Fill[] + */ + public function getDataPointFills() + { + return $this->dataPointFills; + } + + /** + * Get Values + * + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * Set Values + * + * @param array $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setValues($value = array()) + { + $this->values = $value; + + return $this; + } + + /** + * Add Value + * + * @param mixed $key + * @param mixed $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function addValue($key, $value) + { + $this->values[$key] = $value; + + return $this; + } + + /** + * Get ShowSeriesName + * + * @return boolean + */ + public function hasShowSeriesName() + { + return $this->showSeriesName; + } + + /** + * Set ShowSeriesName + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setShowSeriesName($value) + { + $this->showSeriesName = $value; + + return $this; + } + + /** + * Get ShowCategoryName + * + * @return boolean + */ + public function hasShowCategoryName() + { + return $this->showCategoryName; + } + + /** + * Set ShowCategoryName + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setShowCategoryName($value) + { + $this->showCategoryName = $value; + + return $this; + } + + /** + * Get ShowValue + * + * @return boolean + */ + public function hasShowLegendKey() + { + return $this->showLegendKey; + } + + /** + * Set ShowValue + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setShowLegendKey($value) + { + $this->showLegendKey = (bool)$value; + + return $this; + } + + /** + * Get ShowValue + * + * @return boolean + */ + public function hasShowValue() + { + return $this->showValue; + } + + /** + * Set ShowValue + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setShowValue($value) + { + $this->showValue = $value; + + return $this; + } + + /** + * Get ShowPercentage + * + * @return boolean + */ + public function hasShowPercentage() + { + return $this->showPercentage; + } + + /** + * Set ShowPercentage + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setShowPercentage($value) + { + $this->showPercentage = $value; + + return $this; + } + + /** + * Get ShowLeaderLines + * + * @return boolean + */ + public function hasShowSeparator() + { + return !is_null($this->separator); + } + + /** + * Set Separator + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setSeparator($pValue) + { + $this->separator = $pValue; + return $this; + } + + /** + * Get Separator + * @return string + */ + public function getSeparator() + { + return $this->separator; + } + + /** + * Get ShowLeaderLines + * + * @return boolean + */ + public function hasShowLeaderLines() + { + return $this->showLeaderLines; + } + + /** + * Set ShowLeaderLines + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setShowLeaderLines($value) + { + $this->showLeaderLines = $value; + + return $this; + } + + /** + * Get font + * + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font + * + * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setFont(Font $pFont = null) + { + $this->font = $pFont; + + return $this; + } + + /** + * Get label position + * + * @return string + */ + public function getLabelPosition() + { + return $this->labelPosition; + } + + /** + * Set label position + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setLabelPosition($value) + { + $this->labelPosition = $value; + + return $this; + } + + /** + * @return Marker + */ + public function getMarker() + { + return $this->marker; + } + + /** + * @param Marker $marker + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setMarker(Marker $marker) + { + $this->marker = $marker; + return $this; + } + + /** + * @return Outline + */ + public function getOutline() + { + return $this->outline; + } + + /** + * @param Outline $outline + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setOutline(Outline $outline) + { + $this->outline = $outline; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5((is_null($this->fill) ? 'null' : $this->fill->getHashCode()) . (is_null($this->font) ? 'null' : $this->font->getHashCode()) . var_export($this->values, true) . var_export($this, true) . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + return $this; + } + + + /** + * @link http://php.net/manual/en/language.oop5.cloning.php + */ + public function __clone() + { + $this->font = clone $this->font; + $this->marker = clone $this->marker; + if (is_object($this->outline)) { + $this->outline = clone $this->outline; + } + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Title.php b/PhpOffice/PhpPresentation/Shape/Chart/Title.php new file mode 100644 index 0000000..5643a6d --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Title.php @@ -0,0 +1,325 @@ +alignment = new Alignment(); + $this->font = new Font(); + $this->font->setName('Calibri'); + $this->font->setSize(18); + } + + /** + * Get Visible + * + * @return boolean + */ + public function isVisible() + { + return $this->visible; + } + + /** + * Set Visible + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function setVisible($value = true) + { + $this->visible = $value; + + return $this; + } + + /** + * Get Text + * + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * Set Text + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function setText($value = null) + { + $this->text = $value; + + return $this; + } + + /** + * Get OffsetX (as a fraction of the chart) + * + * @return float + */ + public function getOffsetX() + { + return $this->offsetX; + } + + /** + * Set OffsetX (as a fraction of the chart) + * + * @param float $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function setOffsetX($value = 0.01) + { + $this->offsetX = $value; + + return $this; + } + + /** + * Get OffsetY (as a fraction of the chart) + * + * @return float + */ + public function getOffsetY() + { + return $this->offsetY; + } + + /** + * Set OffsetY (as a fraction of the chart) + * + * @param float $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function setOffsetY($value = 0.01) + { + $this->offsetY = $value; + + return $this; + } + + /** + * Get Width (as a fraction of the chart) + * + * @return float + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set Width (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function setWidth($value = 0) + { + $this->width = (double)$value; + + return $this; + } + + /** + * Get Height (as a fraction of the chart) + * + * @return float + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set Height (as a fraction of the chart) + * + * @param float|int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function setHeight($value = 0) + { + $this->height = (double)$value; + + return $this; + } + + /** + * Get font + * + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font + * + * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function setFont(Font $pFont = null) + { + $this->font = $pFont; + + return $this; + } + + /** + * Get alignment + * + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * Set alignment + * + * @param \PhpOffice\PhpPresentation\Style\Alignment $alignment + * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + */ + public function setAlignment(Alignment $alignment) + { + $this->alignment = $alignment; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->text . $this->offsetX . $this->offsetY . $this->width . $this->height . $this->font->getHashCode() . $this->alignment->getHashCode() . ($this->visible ? 't' : 'f') . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + * @return Title + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php new file mode 100644 index 0000000..57c74db --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php @@ -0,0 +1,171 @@ +hasAxisX; + } + + /** + * Has Axis Y? + * + * @return boolean + */ + public function hasAxisY() + { + return $this->hasAxisY; + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + * @return AbstractType + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + return $this; + } + + /** + * Add Series + * + * @param \PhpOffice\PhpPresentation\Shape\Chart\Series $value + * @return $this + */ + public function addSeries(Series $value) + { + $this->data[] = $value; + return $this; + } + + /** + * Get Series + * + * @return \PhpOffice\PhpPresentation\Shape\Chart\Series[] + */ + public function getSeries() + { + return $this->data; + } + + /** + * Set Series + * + * @param array $value Array of \PhpOffice\PhpPresentation\Shape\Chart\Series + * @return $this + */ + public function setSeries($value = array()) + { + $this->data = $value; + return $this; + } + + /** + * Get Data + * + * @deprecated getSeries + */ + public function getData() + { + return $this->getSeries(); + } + + /** + * Set Data + * + * @deprecated setSeries + * @param array $value + * @return AbstractType + */ + public function setData($value = array()) + { + return $this->setSeries($value); + } + + /** + * @link http://php.net/manual/en/language.oop5.cloning.php + */ + public function __clone() + { + $arrayClone = array(); + foreach ($this->data as $itemSeries) { + $arrayClone[] = clone $itemSeries; + } + $this->data = $arrayClone; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php new file mode 100644 index 0000000..f0a8c31 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php @@ -0,0 +1,140 @@ +barDirection = $value; + return $this; + } + + /** + * Get orientation + * + * @return string + */ + public function getBarDirection() + { + return $this->barDirection; + } + + /** + * Set bar grouping (stack or expanded style bar) + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractTypeBar + */ + public function setBarGrouping($value = self::GROUPING_CLUSTERED) + { + $this->barGrouping = $value; + return $this; + } + + /** + * Get grouping (stack or expanded style bar) + * + * @return string + */ + public function getBarGrouping() + { + return $this->barGrouping; + } + + /** + * @return int + */ + public function getGapWidthPercent() + { + return $this->gapWidthPercent; + } + + /** + * @param int $gapWidthPercent + * @return $this + */ + public function setGapWidthPercent($gapWidthPercent) + { + if ($gapWidthPercent < 0) { + $gapWidthPercent = 0; + } + if ($gapWidthPercent > 500) { + $gapWidthPercent = 500; + } + $this->gapWidthPercent = $gapWidthPercent; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + $hash = ''; + foreach ($this->getSeries() as $series) { + $hash .= $series->getHashCode(); + } + return $hash; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php new file mode 100644 index 0000000..f055fe3 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php @@ -0,0 +1,76 @@ +hasAxisX = false; + $this->hasAxisY = false; + } + + /** + * Explosion of the Pie + * + * @var integer + */ + protected $explosion = 0; + + /** + * Set explosion + * + * @param integer $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractTypePie + */ + public function setExplosion($value = 0) + { + $this->explosion = $value; + return $this; + } + + /** + * Get orientation + * + * @return string + */ + public function getExplosion() + { + return $this->explosion; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + $hash = ''; + foreach ($this->getSeries() as $series) { + $hash .= $series->getHashCode(); + } + return $hash; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php new file mode 100644 index 0000000..fdfc5aa --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php @@ -0,0 +1,40 @@ +getSeries() as $series) { + $hash .= $series->getHashCode(); + } + return md5($hash . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php new file mode 100644 index 0000000..da44e04 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php @@ -0,0 +1,36 @@ +holeSize; + } + + /** + * @param int $holeSize + * @return Doughnut + * @link https://msdn.microsoft.com/en-us/library/documentformat.openxml.drawing.charts.holesize(v=office.14).aspx + */ + public function setHoleSize($holeSize = 50) + { + if ($holeSize < 10) { + $holeSize = 10; + } + if ($holeSize > 90) { + $holeSize = 90; + } + $this->holeSize = $holeSize; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5(parent::getHashCode() . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php new file mode 100644 index 0000000..9be7a78 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php @@ -0,0 +1,40 @@ +getSeries() as $series) { + $hash .= $series->getHashCode(); + } + return md5($hash . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php new file mode 100644 index 0000000..a1c0d57 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php @@ -0,0 +1,36 @@ +getSeries() as $series) { + $hash .= $series->getHashCode(); + } + return md5($hash . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/View3D.php b/PhpOffice/PhpPresentation/Shape/Chart/View3D.php new file mode 100644 index 0000000..80ffea6 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/View3D.php @@ -0,0 +1,258 @@ +rotationX; + } + + /** + * Set Rotation X (-90 to 90) + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D + */ + public function setRotationX($pValue = 0) + { + $this->rotationX = $pValue; + + return $this; + } + + /** + * Get Rotation Y + * + * @return int + */ + public function getRotationY() + { + return $this->rotationY; + } + + /** + * Set Rotation Y (-90 to 90) + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D + */ + public function setRotationY($pValue = 0) + { + $this->rotationY = $pValue; + + return $this; + } + + /** + * Get RightAngleAxes + * + * @return boolean + */ + public function hasRightAngleAxes() + { + return $this->rightAngleAxes; + } + + /** + * Set RightAngleAxes + * + * @param boolean $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D + */ + public function setRightAngleAxes($value = true) + { + $this->rightAngleAxes = $value; + + return $this; + } + + /** + * Get Perspective + * + * @return int + */ + public function getPerspective() + { + return $this->perspective; + } + + /** + * Set Perspective (0 to 100) + * + * @param int $value + * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D + */ + public function setPerspective($value = 30) + { + $this->perspective = $value; + + return $this; + } + + /** + * Get HeightPercent + * + * @return int + */ + public function getHeightPercent() + { + return $this->heightPercent; + } + + /** + * Set HeightPercent (5 to 500) + * + * @param int $value + * @return $this + */ + public function setHeightPercent($value = 100) + { + $this->heightPercent = $value; + + return $this; + } + + /** + * Get DepthPercent + * + * @return int + */ + public function getDepthPercent() + { + return $this->depthPercent; + } + + /** + * Set DepthPercent (20 to 2000) + * + * @param int $value + * @return $this + */ + public function setDepthPercent($value = 100) + { + $this->depthPercent = $value; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->rotationX . $this->rotationY . ($this->rightAngleAxes ? 't' : 'f') . $this->perspective . $this->heightPercent . $this->depthPercent . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + * @return View3D + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Comment.php b/PhpOffice/PhpPresentation/Shape/Comment.php new file mode 100644 index 0000000..9dfe73f --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Comment.php @@ -0,0 +1,145 @@ +setDate(time()); + } + + /** + * @return Author + */ + public function getAuthor() + { + return $this->author; + } + + /** + * @param Author $author + * @return Comment + */ + public function setAuthor(Author $author) + { + $this->author = $author; + return $this; + } + + /** + * @return int + */ + public function getDate() + { + return $this->dtComment; + } + + /** + * @param int $dtComment timestamp of the comment + * @return Comment + */ + public function setDate($dtComment) + { + $this->dtComment = (int)$dtComment; + return $this; + } + + /** + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * @param string $text + * @return Comment + */ + public function setText($text = '') + { + $this->text = $text; + return $this; + } + + /** + * Comment has not height + * + * @return null + */ + public function getHeight() + { + return null; + } + + /** + * Set Height + * + * @param int $pValue + * @return $this + */ + public function setHeight($pValue = 0) + { + return $this; + } + + /** + * Comment has not width + * + * @return null + */ + public function getWidth() + { + return null; + } + + /** + * Set Width + * + * @param int $pValue + * @return $this + */ + public function setWidth($pValue = 0) + { + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Comment/Author.php b/PhpOffice/PhpPresentation/Shape/Comment/Author.php new file mode 100644 index 0000000..3c0ba2a --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Comment/Author.php @@ -0,0 +1,85 @@ +idxAuthor; + } + + /** + * @param int $idxAuthor + * @return Author + */ + public function setIndex($idxAuthor) + { + $this->idxAuthor = (int) $idxAuthor; + return $this; + } + + /** + * @return mixed + */ + public function getInitials() + { + return $this->initials; + } + + /** + * @param mixed $initials + * @return Author + */ + public function setInitials($initials) + { + $this->initials = $initials; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + * @return Author + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->getInitials() . $this->getName() . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Drawing.php b/PhpOffice/PhpPresentation/Shape/Drawing.php new file mode 100644 index 0000000..30a05b5 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Drawing.php @@ -0,0 +1,28 @@ + 'jpg', + 'image/png' => 'png', + 'image/gif' => 'gif', + ); + + /** + * Base64 constructor. + */ + public function __construct() + { + parent::__construct(); + $this->uniqueName = md5(rand(0, 9999) . time() . rand(0, 9999)); + } + + /** + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * @param mixed $data + * @return Base64 + */ + public function setData($data) + { + $this->data = $data; + return $this; + } + + /** + * @return string + */ + public function getContents() + { + list(, $imageContents) = explode(';', $this->getData()); + list(, $imageContents) = explode(',', $imageContents); + return base64_decode($imageContents); + } + + /** + * @return string + * @throws \Exception + */ + public function getExtension() + { + list($data, ) = explode(';', $this->getData()); + list(, $mime) = explode(':', $data); + + if (!array_key_exists($mime, $this->arrayMimeExtension)) { + throw new \Exception('Type Mime not found : "'.$mime.'"'); + } + return $this->arrayMimeExtension[$mime]; + } + + /** + * @return string + * @throws \Exception + */ + public function getIndexedFilename() + { + return $this->uniqueName . $this->getImageIndex() . '.' . $this->getExtension(); + } + + /** + * @return string + */ + public function getMimeType() + { + $sImage = $this->getContents(); + if (!function_exists('getimagesizefromstring')) { + $uri = 'data://application/octet-stream;base64,' . base64_encode($sImage); + $image = getimagesize($uri); + } else { + $image = getimagesizefromstring($sImage); + } + return image_type_to_mime_type($image[2]); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Drawing/File.php b/PhpOffice/PhpPresentation/Shape/Drawing/File.php new file mode 100644 index 0000000..e920988 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Drawing/File.php @@ -0,0 +1,91 @@ +path; + } + + /** + * Set Path + * + * @param string $pValue File path + * @param boolean $pVerifyFile Verify file + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Drawing\File + */ + public function setPath($pValue = '', $pVerifyFile = true) + { + if ($pVerifyFile) { + if (!file_exists($pValue)) { + throw new \Exception("File $pValue not found!"); + } + } + $this->path = $pValue; + + if ($pVerifyFile) { + if ($this->width == 0 && $this->height == 0) { + list($this->width, $this->height) = getimagesize($this->getPath()); + } + } + + return $this; + } + + /** + * @return string + */ + public function getContents() + { + return CommonFile::fileGetContents($this->getPath()); + } + + + /** + * @return string + */ + public function getExtension() + { + return pathinfo($this->getPath(), PATHINFO_EXTENSION); + } + + /** + * @throws \Exception + * @return string + */ + public function getMimeType() + { + if (!CommonFile::fileExists($this->getPath())) { + throw new \Exception('File '.$this->getPath().' does not exist'); + } + $image = getimagesizefromstring(CommonFile::fileGetContents($this->getPath())); + return image_type_to_mime_type($image[2]); + } + + /** + * @return string + */ + public function getIndexedFilename() + { + $output = str_replace('.' . $this->getExtension(), '', pathinfo($this->getPath(), PATHINFO_FILENAME)); + $output .= $this->getImageIndex(); + $output .= '.'.$this->getExtension(); + $output = str_replace(' ', '_', $output); + return $output; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Drawing/Gd.php b/PhpOffice/PhpPresentation/Shape/Drawing/Gd.php new file mode 100644 index 0000000..d4509d9 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Drawing/Gd.php @@ -0,0 +1,163 @@ +uniqueName = md5(rand(0, 9999) . time() . rand(0, 9999)); + } + + /** + * Get image resource + * + * @return resource + */ + public function getImageResource() + { + return $this->imageResource; + } + + /** + * Set image resource + * + * @param $value resource + * @return $this + */ + public function setImageResource($value = null) + { + $this->imageResource = $value; + + if (!is_null($this->imageResource)) { + // Get width/height + $this->width = imagesx($this->imageResource); + $this->height = imagesy($this->imageResource); + } + + return $this; + } + + /** + * Get rendering function + * + * @return string + */ + public function getRenderingFunction() + { + return $this->renderingFunction; + } + + /** + * Set rendering function + * + * @param string $value + * @return $this + */ + public function setRenderingFunction($value = self::RENDERING_DEFAULT) + { + $this->renderingFunction = $value; + return $this; + } + + /** + * Get mime type + * + * @return string + */ + public function getMimeType() + { + return $this->mimeType; + } + + /** + * Set mime type + * + * @param string $value + * @return $this + */ + public function setMimeType($value = self::MIMETYPE_DEFAULT) + { + $this->mimeType = $value; + return $this; + } + + /** + * @return string + */ + public function getContents() + { + ob_start(); + if ($this->getMimeType() === self::MIMETYPE_DEFAULT) { + imagealphablending($this->getImageResource(), false); + imagesavealpha($this->getImageResource(), true); + } + call_user_func($this->getRenderingFunction(), $this->getImageResource()); + $imageContents = ob_get_contents(); + ob_end_clean(); + return $imageContents; + } + + /** + * @return string + */ + public function getExtension() + { + $extension = strtolower($this->getMimeType()); + $extension = explode('/', $extension); + $extension = $extension[1]; + return $extension; + } + + /** + * @return string + */ + public function getIndexedFilename() + { + return $this->uniqueName . $this->getImageIndex() . '.' . $this->getExtension(); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php b/PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php new file mode 100644 index 0000000..769f1d9 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php @@ -0,0 +1,109 @@ +path; + } + + /** + * Set Path + * + * @param string $pValue File path + * @return \PhpOffice\PhpPresentation\Shape\Drawing\ZipFile + */ + public function setPath($pValue = '') + { + $this->path = $pValue; + return $this; + } + + /** + * @return string + * @throws \Exception + */ + public function getContents() + { + if (!CommonFile::fileExists($this->getZipFileOut())) { + throw new \Exception('File '.$this->getZipFileOut().' does not exist'); + } + + $imageZip = new \ZipArchive(); + $imageZip->open($this->getZipFileOut()); + $imageContents = $imageZip->getFromName($this->getZipFileIn()); + $imageZip->close(); + unset($imageZip); + return $imageContents; + } + + + /** + * @return string + */ + public function getExtension() + { + return pathinfo($this->getZipFileIn(), PATHINFO_EXTENSION); + } + + /** + * @return string + * @throws \Exception + */ + public function getMimeType() + { + if (!CommonFile::fileExists($this->getZipFileOut())) { + throw new \Exception('File '.$this->getZipFileOut().' does not exist'); + } + $oArchive = new \ZipArchive(); + $oArchive->open($this->getZipFileOut()); + if (!function_exists('getimagesizefromstring')) { + $uri = 'data://application/octet-stream;base64,' . base64_encode($oArchive->getFromName($this->getZipFileIn())); + $image = getimagesize($uri); + } else { + $image = getimagesizefromstring($oArchive->getFromName($this->getZipFileIn())); + } + return image_type_to_mime_type($image[2]); + } + + /** + * @return string + */ + public function getIndexedFilename() + { + $output = pathinfo($this->getZipFileIn(), PATHINFO_FILENAME); + $output = str_replace('.' . $this->getExtension(), '', $output); + $output .= $this->getImageIndex(); + $output .= '.'.$this->getExtension(); + $output = str_replace(' ', '_', $output); + return $output; + } + + protected function getZipFileOut() + { + $path = str_replace('zip://', '', $this->getPath()); + $path = explode('#', $path); + return empty($path[0]) ? '' : $path[0]; + } + + protected function getZipFileIn() + { + $path = str_replace('zip://', '', $this->getPath()); + $path = explode('#', $path); + return empty($path[1]) ? '' : $path[1]; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Group.php b/PhpOffice/PhpPresentation/Shape/Group.php new file mode 100644 index 0000000..5a4faf9 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Group.php @@ -0,0 +1,268 @@ +offsetX = null; + $this->offsetY = null; + + // Shape collection + $this->shapeCollection = new \ArrayObject(); + } + + /** + * Get collection of shapes + * + * @return \ArrayObject|AbstractShape[] + */ + public function getShapeCollection() + { + return $this->shapeCollection; + } + + /** + * Add shape to slide + * + * @param \PhpOffice\PhpPresentation\AbstractShape $shape + * @return \PhpOffice\PhpPresentation\AbstractShape + * @throws \Exception + */ + public function addShape(AbstractShape $shape) + { + $shape->setContainer($this); + + return $shape; + } + + /** + * Get X Offset + * + * @return int + */ + public function getOffsetX() + { + if ($this->offsetX === null) { + $offsets = GeometryCalculator::calculateOffsets($this); + $this->offsetX = $offsets[GeometryCalculator::X]; + $this->offsetY = $offsets[GeometryCalculator::Y]; + } + + return $this->offsetX; + } + + /** + * Ignores setting the X Offset, preserving the default behavior. + * + * @param int $pValue + * @return $this + */ + public function setOffsetX($pValue = 0) + { + return $this; + } + + /** + * Get Y Offset + * + * @return int + */ + public function getOffsetY() + { + if ($this->offsetY === null) { + $offsets = GeometryCalculator::calculateOffsets($this); + $this->offsetX = $offsets[GeometryCalculator::X]; + $this->offsetY = $offsets[GeometryCalculator::Y]; + } + + return $this->offsetY; + } + + /** + * Ignores setting the Y Offset, preserving the default behavior. + * + * @param int $pValue + * @return $this + */ + public function setOffsetY($pValue = 0) + { + return $this; + } + + /** + * Get X Extent + * + * @return int + */ + public function getExtentX() + { + if ($this->extentX === null) { + $extents = GeometryCalculator::calculateExtents($this); + $this->extentX = $extents[GeometryCalculator::X] - $this->getOffsetX(); + $this->extentY = $extents[GeometryCalculator::Y] - $this->getOffsetY(); + } + + return $this->extentX; + } + + /** + * Get Y Extent + * + * @return int + */ + public function getExtentY() + { + if ($this->extentY === null) { + $extents = GeometryCalculator::calculateExtents($this); + $this->extentX = $extents[GeometryCalculator::X] - $this->getOffsetX(); + $this->extentY = $extents[GeometryCalculator::Y] - $this->getOffsetY(); + } + + return $this->extentY; + } + + /** + * Ignores setting the width, preserving the default behavior. + * + * @param int $pValue + * @return $this + */ + public function setWidth($pValue = 0) + { + return $this; + } + + /** + * Ignores setting the height, preserving the default behavior. + * + * @param int $pValue + * @return $this + */ + public function setHeight($pValue = 0) + { + return $this; + } + + /** + * Create rich text shape + * + * @return \PhpOffice\PhpPresentation\Shape\RichText + * @throws \Exception + */ + public function createRichTextShape() + { + $shape = new RichText(); + $this->addShape($shape); + + return $shape; + } + + /** + * Create line shape + * + * @param int $fromX Starting point x offset + * @param int $fromY Starting point y offset + * @param int $toX Ending point x offset + * @param int $toY Ending point y offset + * @return \PhpOffice\PhpPresentation\Shape\Line + * @throws \Exception + */ + public function createLineShape($fromX, $fromY, $toX, $toY) + { + $shape = new Line($fromX, $fromY, $toX, $toY); + $this->addShape($shape); + + return $shape; + } + + /** + * Create chart shape + * + * @return \PhpOffice\PhpPresentation\Shape\Chart + * @throws \Exception + */ + public function createChartShape() + { + $shape = new Chart(); + $this->addShape($shape); + + return $shape; + } + + /** + * Create drawing shape + * + * @return \PhpOffice\PhpPresentation\Shape\Drawing\File + * @throws \Exception + */ + public function createDrawingShape() + { + $shape = new Drawing\File(); + $this->addShape($shape); + + return $shape; + } + + /** + * Create table shape + * + * @param int $columns Number of columns + * @return \PhpOffice\PhpPresentation\Shape\Table + * @throws \Exception + */ + public function createTableShape($columns = 1) + { + $shape = new Table($columns); + $this->addShape($shape); + + return $shape; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Hyperlink.php b/PhpOffice/PhpPresentation/Shape/Hyperlink.php new file mode 100644 index 0000000..0a95507 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Hyperlink.php @@ -0,0 +1,189 @@ +setUrl($pUrl); + $this->setTooltip($pTooltip); + } + + /** + * Get URL + * + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * Set URL + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Hyperlink + */ + public function setUrl($value = '') + { + $this->url = $value; + + return $this; + } + + /** + * Get tooltip + * + * @return string + */ + public function getTooltip() + { + return $this->tooltip; + } + + /** + * Set tooltip + * + * @param string $value + * @return \PhpOffice\PhpPresentation\Shape\Hyperlink + */ + public function setTooltip($value = '') + { + $this->tooltip = $value; + + return $this; + } + + /** + * Get slide number + * + * @return int + */ + public function getSlideNumber() + { + return $this->slideNumber; + } + + /** + * Set slide number + * + * @param int $value + * @return \PhpOffice\PhpPresentation\Shape\Hyperlink + */ + public function setSlideNumber($value = 1) + { + $this->url = 'ppaction://hlinksldjump'; + $this->slideNumber = $value; + + return $this; + } + + /** + * Is this hyperlink internal? (to another slide) + * + * @return boolean + */ + public function isInternal() + { + return strpos($this->url, 'ppaction://') !== false; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->url . $this->tooltip . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Line.php b/PhpOffice/PhpPresentation/Shape/Line.php new file mode 100644 index 0000000..e9a7a07 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Line.php @@ -0,0 +1,57 @@ +getBorder()->setLineStyle(Border::LINE_SINGLE); + + $this->setOffsetX($fromX); + $this->setOffsetY($fromY); + $this->setWidth($toX - $fromX); + $this->setHeight($toY - $fromY); + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->getBorder()->getLineStyle() . parent::getHashCode() . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Media.php b/PhpOffice/PhpPresentation/Shape/Media.php new file mode 100644 index 0000000..64a98b7 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Media.php @@ -0,0 +1,49 @@ +getExtension())) { + case 'mp4': + $mimetype = 'video/mp4'; + break; + case 'ogv': + $mimetype = 'video/ogg'; + break; + case 'wmv': + $mimetype = 'video/x-ms-wmv'; + break; + default: + $mimetype = 'application/octet-stream'; + } + return $mimetype; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/MemoryDrawing.php b/PhpOffice/PhpPresentation/Shape/MemoryDrawing.php new file mode 100644 index 0000000..e4b5c7a --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/MemoryDrawing.php @@ -0,0 +1,28 @@ +type = $type; + } + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * @param mixed $type + * @return Placeholder + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * @return int + */ + public function getIdx() + { + return $this->idx; + } + + /** + * @param int $idx + * @return Placeholder + */ + public function setIdx($idx) + { + $this->idx = $idx; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/RichText.php b/PhpOffice/PhpPresentation/Shape/RichText.php new file mode 100644 index 0000000..d7cc341 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/RichText.php @@ -0,0 +1,710 @@ +richTextParagraphs = array( + new Paragraph() + ); + $this->activeParagraph = 0; + + // Initialize parent + parent::__construct(); + } + + /** + * Get active paragraph index + * + * @return int + */ + public function getActiveParagraphIndex() + { + return $this->activeParagraph; + } + + /** + * Get active paragraph + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function getActiveParagraph() + { + return $this->richTextParagraphs[$this->activeParagraph]; + } + + /** + * Set active paragraph + * + * @param int $index + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function setActiveParagraph($index = 0) + { + if ($index >= count($this->richTextParagraphs)) { + throw new \Exception("Invalid paragraph count."); + } + + $this->activeParagraph = $index; + + return $this->getActiveParagraph(); + } + + /** + * Get paragraph + * + * @param int $index + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function getParagraph($index = 0) + { + if ($index >= count($this->richTextParagraphs)) { + throw new \Exception("Invalid paragraph count."); + } + + return $this->richTextParagraphs[$index]; + } + + /** + * Create paragraph + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @throws \Exception + */ + public function createParagraph() + { + $numParagraphs = count($this->richTextParagraphs); + if ($numParagraphs > 0) { + $alignment = clone $this->getActiveParagraph()->getAlignment(); + $font = clone $this->getActiveParagraph()->getFont(); + $bulletStyle = clone $this->getActiveParagraph()->getBulletStyle(); + } + + $this->richTextParagraphs[] = new Paragraph(); + $this->activeParagraph = count($this->richTextParagraphs) - 1; + + if (isset($alignment)) { + $this->getActiveParagraph()->setAlignment($alignment); + } + if (isset($font)) { + $this->getActiveParagraph()->setFont($font); + } + if (isset($bulletStyle)) { + $this->getActiveParagraph()->setBulletStyle($bulletStyle); + } + return $this->getActiveParagraph(); + } + + /** + * Add text + * + * @param \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface $pText Rich text element + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function addText(TextElementInterface $pText = null) + { + $this->richTextParagraphs[$this->activeParagraph]->addText($pText); + + return $this; + } + + /** + * Create text (can not be formatted !) + * + * @param string $pText Text + * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElement + * @throws \Exception + */ + public function createText($pText = '') + { + return $this->richTextParagraphs[$this->activeParagraph]->createText($pText); + } + + /** + * Create break + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\BreakElement + * @throws \Exception + */ + public function createBreak() + { + return $this->richTextParagraphs[$this->activeParagraph]->createBreak(); + } + + /** + * Create text run (can be formatted) + * + * @param string $pText Text + * @return \PhpOffice\PhpPresentation\Shape\RichText\Run + * @throws \Exception + */ + public function createTextRun($pText = '') + { + return $this->richTextParagraphs[$this->activeParagraph]->createTextRun($pText); + } + + /** + * Get plain text + * + * @return string + */ + public function getPlainText() + { + // Return value + $returnValue = ''; + + // Loop trough all \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + foreach ($this->richTextParagraphs as $p) { + $returnValue .= $p->getPlainText(); + } + + // Return + return $returnValue; + } + + /** + * Convert to string + * + * @return string + */ + public function __toString() + { + return $this->getPlainText(); + } + + /** + * Get paragraphs + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] + */ + public function getParagraphs() + { + return $this->richTextParagraphs; + } + + /** + * Set paragraphs + * + * @param \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] $paragraphs Array of paragraphs + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setParagraphs($paragraphs = null) + { + if (!is_array($paragraphs)) { + throw new \Exception("Invalid \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] array passed."); + } + + $this->richTextParagraphs = $paragraphs; + $this->activeParagraph = count($this->richTextParagraphs) - 1; + return $this; + } + + /** + * Get text wrapping + * + * @return string + */ + public function getWrap() + { + return $this->wrap; + } + + /** + * Set text wrapping + * + * @param $value string + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setWrap($value = self::WRAP_SQUARE) + { + $this->wrap = $value; + + return $this; + } + + /** + * Get autofit + * + * @return string + */ + public function getAutoFit() + { + return $this->autoFit; + } + + /** + * Get pourcentage of fontScale + * + * @return float + */ + public function getFontScale() + { + return $this->fontScale; + } + + /** + * Get pourcentage of the line space reduction + * + * @return float + */ + public function getLineSpaceReduction() + { + return $this->lnSpcReduction; + } + + /** + * Set autofit + * + * @param $value string + * @param $fontScale float + * @param $lnSpcReduction float + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setAutoFit($value = self::AUTOFIT_DEFAULT, $fontScale = null, $lnSpcReduction = null) + { + $this->autoFit = $value; + + if (!is_null($fontScale)) { + $this->fontScale = $fontScale; + } + + if (!is_null($lnSpcReduction)) { + $this->lnSpcReduction = $lnSpcReduction; + } + + return $this; + } + + /** + * Get horizontal overflow + * + * @return string + */ + public function getHorizontalOverflow() + { + return $this->horizontalOverflow; + } + + /** + * Set horizontal overflow + * + * @param $value string + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setHorizontalOverflow($value = self::OVERFLOW_OVERFLOW) + { + $this->horizontalOverflow = $value; + + return $this; + } + + /** + * Get vertical overflow + * + * @return string + */ + public function getVerticalOverflow() + { + return $this->verticalOverflow; + } + + /** + * Set vertical overflow + * + * @param $value string + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setVerticalOverflow($value = self::OVERFLOW_OVERFLOW) + { + $this->verticalOverflow = $value; + + return $this; + } + + /** + * Get upright + * + * @return boolean + */ + public function isUpright() + { + return $this->upright; + } + + /** + * Set vertical + * + * @param $value boolean + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setUpright($value = false) + { + $this->upright = $value; + + return $this; + } + + /** + * Get vertical + * + * @return boolean + */ + public function isVertical() + { + return $this->vertical; + } + + /** + * Set vertical + * + * @param $value boolean + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setVertical($value = false) + { + $this->vertical = $value; + + return $this; + } + + /** + * Get columns + * + * @return int + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Set columns + * + * @param $value int + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setColumns($value = 1) + { + if ($value > 16 || $value < 1) { + throw new \Exception('Number of columns should be 1-16'); + } + + $this->columns = $value; + + return $this; + } + + /** + * Get bottom inset + * + * @return float + */ + public function getInsetBottom() + { + return $this->bottomInset; + } + + /** + * Set bottom inset + * + * @param $value float + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setInsetBottom($value = 4.8) + { + $this->bottomInset = $value; + + return $this; + } + + /** + * Get left inset + * + * @return float + */ + public function getInsetLeft() + { + return $this->leftInset; + } + + /** + * Set left inset + * + * @param $value float + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setInsetLeft($value = 9.6) + { + $this->leftInset = $value; + + return $this; + } + + /** + * Get right inset + * + * @return float + */ + public function getInsetRight() + { + return $this->rightInset; + } + + /** + * Set left inset + * + * @param $value float + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setInsetRight($value = 9.6) + { + $this->rightInset = $value; + + return $this; + } + + /** + * Get top inset + * + * @return float + */ + public function getInsetTop() + { + return $this->topInset; + } + + /** + * Set top inset + * + * @param $value float + * @return \PhpOffice\PhpPresentation\Shape\RichText + */ + public function setInsetTop($value = 4.8) + { + $this->topInset = $value; + + return $this; + } + + /** + * Set horizontal auto shrink + * @param bool $value + * @return RichText + */ + public function setAutoShrinkHorizontal($value = null) + { + if (is_bool($value)) { + $this->autoShrinkHorizontal = $value; + } + return $this; + } + + /** + * Get horizontal auto shrink + * @return bool + */ + public function hasAutoShrinkHorizontal() + { + return $this->autoShrinkHorizontal; + } + + /** + * Set vertical auto shrink + * @param bool $value + * @return RichText + */ + public function setAutoShrinkVertical($value = null) + { + if (is_bool($value)) { + $this->autoShrinkVertical = $value; + } + return $this; + } + + /** + * Set vertical auto shrink + * @return bool + */ + public function hasAutoShrinkVertical() + { + return $this->autoShrinkVertical; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + $hashElements = ''; + foreach ($this->richTextParagraphs as $element) { + $hashElements .= $element->getHashCode(); + } + + return md5($hashElements . $this->wrap . $this->autoFit . $this->horizontalOverflow . $this->verticalOverflow . ($this->upright ? '1' : '0') . ($this->vertical ? '1' : '0') . $this->columns . $this->bottomInset . $this->leftInset . $this->rightInset . $this->topInset . parent::getHashCode() . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php b/PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php new file mode 100644 index 0000000..dbd5bb7 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php @@ -0,0 +1,93 @@ +richTextElements = array(); + $this->alignment = new Alignment(); + $this->font = new Font(); + $this->bulletStyle = new Bullet(); + } + + /** + * Get alignment + * + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * Set alignment + * + * @param \PhpOffice\PhpPresentation\Style\Alignment $alignment + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function setAlignment(Alignment $alignment) + { + $this->alignment = $alignment; + + return $this; + } + + /** + * Get font + * + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font + * + * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function setFont(Font $pFont = null) + { + $this->font = $pFont; + + return $this; + } + + /** + * Get bullet style + * + * @return \PhpOffice\PhpPresentation\Style\Bullet + */ + public function getBulletStyle() + { + return $this->bulletStyle; + } + + /** + * Set bullet style + * + * @param \PhpOffice\PhpPresentation\Style\Bullet $style + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function setBulletStyle(Bullet $style = null) + { + $this->bulletStyle = $style; + + return $this; + } + + /** + * Create text (can not be formatted !) + * + * @param string $pText Text + * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElement + * @throws \Exception + */ + public function createText($pText = '') + { + $objText = new TextElement($pText); + $this->addText($objText); + + return $objText; + } + + /** + * Add text + * + * @param \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface $pText Rich text element + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function addText(TextElementInterface $pText = null) + { + $this->richTextElements[] = $pText; + + return $this; + } + + /** + * Create break + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\BreakElement + * @throws \Exception + */ + public function createBreak() + { + $objText = new BreakElement(); + $this->addText($objText); + + return $objText; + } + + /** + * Create text run (can be formatted) + * + * @param string $pText Text + * @return \PhpOffice\PhpPresentation\Shape\RichText\Run + * @throws \Exception + */ + public function createTextRun($pText = '') + { + $objText = new Run($pText); + $objText->setFont(clone $this->font); + $this->addText($objText); + + return $objText; + } + + /** + * Convert to string + * + * @return string + */ + public function __toString() + { + return $this->getPlainText(); + } + + /** + * Get plain text + * + * @return string + */ + public function getPlainText() + { + // Return value + $returnValue = ''; + + // Loop trough all \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface + foreach ($this->richTextElements as $text) { + if ($text instanceof TextElementInterface) { + $returnValue .= $text->getText(); + } + } + + // Return + return $returnValue; + } + + /** + * Get Rich Text elements + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface[] + */ + public function getRichTextElements() + { + return $this->richTextElements; + } + + /** + * Set Rich Text elements + * + * @param \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface[] $pElements Array of elements + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function setRichTextElements($pElements = null) + { + if (!is_array($pElements)) { + throw new \Exception("Invalid \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface[] array passed."); + } + $this->richTextElements = $pElements; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + $hashElements = ''; + foreach ($this->richTextElements as $element) { + $hashElements .= $element->getHashCode(); + } + + return md5($hashElements . $this->font->getHashCode() . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } + + /** + * @return int + */ + public function getLineSpacing() + { + return $this->lineSpacing; + } + + /** + * @param int $lineSpacing + * @return Paragraph + */ + public function setLineSpacing($lineSpacing) + { + $this->lineSpacing = $lineSpacing; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/RichText/Run.php b/PhpOffice/PhpPresentation/Shape/RichText/Run.php new file mode 100644 index 0000000..97455c0 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/RichText/Run.php @@ -0,0 +1,79 @@ +setText($pText); + $this->font = new Font(); + } + + /** + * Get font + * + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font + * + * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface + */ + public function setFont(Font $pFont = null) + { + $this->font = $pFont; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->getText() . $this->font->getHashCode() . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/RichText/TextElement.php b/PhpOffice/PhpPresentation/Shape/RichText/TextElement.php new file mode 100644 index 0000000..e19b9b8 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/RichText/TextElement.php @@ -0,0 +1,158 @@ +text = $pText; + } + + /** + * Get text + * + * @return string Text + */ + public function getText() + { + return $this->text; + } + + /** + * Set text + * + * @param $pText string Text + * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface + */ + public function setText($pText = '') + { + $this->text = $pText; + + return $this; + } + + /** + * Get font + * + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function getFont() + { + return null; + } + + /** + * Has Hyperlink? + * + * @return boolean + */ + public function hasHyperlink() + { + return !is_null($this->hyperlink); + } + + /** + * Get Hyperlink + * + * @return \PhpOffice\PhpPresentation\Shape\Hyperlink + * @throws \Exception + */ + public function getHyperlink() + { + if (is_null($this->hyperlink)) { + $this->hyperlink = new Hyperlink(); + } + + return $this->hyperlink; + } + + /** + * Set Hyperlink + * + * @param \PhpOffice\PhpPresentation\Shape\Hyperlink $pHyperlink + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElement + */ + public function setHyperlink(Hyperlink $pHyperlink = null) + { + $this->hyperlink = $pHyperlink; + + return $this; + } + + /** + * Get language + * @return string + */ + public function getLanguage() + { + return $this->language; + } + + /** + * Set language + * @param string $language + * @return TextElement + */ + public function setLanguage($language) + { + $this->language = $language; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->text . (is_null($this->hyperlink) ? '' : $this->hyperlink->getHashCode()) . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php b/PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php new file mode 100644 index 0000000..9830244 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php @@ -0,0 +1,64 @@ +rows = array(); + $this->columnCount = $columns; + + // Initialize parent + parent::__construct(); + + // No resize proportional + $this->resizeProportional = false; + } + + /** + * Get row + * + * @param int $row Row number + * @param boolean $exceptionAsNull Return a null value instead of an exception? + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Table\Row + */ + public function getRow($row = 0, $exceptionAsNull = false) + { + if (!isset($this->rows[$row])) { + if ($exceptionAsNull) { + return null; + } + throw new \Exception('Row number out of bounds.'); + } + + return $this->rows[$row]; + } + + /** + * Get rows + * + * @return \PhpOffice\PhpPresentation\Shape\Table\Row[] + */ + public function getRows() + { + return $this->rows; + } + + /** + * Create row + * + * @return \PhpOffice\PhpPresentation\Shape\Table\Row + */ + public function createRow() + { + $row = new Row($this->columnCount); + $this->rows[] = $row; + + return $row; + } + + /** + * @return int + */ + public function getNumColumns() + { + return $this->columnCount; + } + + /** + * @param int $numColumn + * @return Table + */ + public function setNumColumns($numColumn) + { + $this->columnCount = $numColumn; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + $hashElements = ''; + foreach ($this->rows as $row) { + $hashElements .= $row->getHashCode(); + } + + return md5($hashElements . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Table/Cell.php b/PhpOffice/PhpPresentation/Shape/Table/Cell.php new file mode 100644 index 0000000..0a184d2 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Table/Cell.php @@ -0,0 +1,443 @@ +richTextParagraphs = array( + new Paragraph() + ); + $this->activeParagraph = 0; + + // Set fill + $this->fill = new Fill(); + + // Set borders + $this->borders = new Borders(); + } + + /** + * Get active paragraph index + * + * @return int + */ + public function getActiveParagraphIndex() + { + return $this->activeParagraph; + } + + /** + * Get active paragraph + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function getActiveParagraph() + { + return $this->richTextParagraphs[$this->activeParagraph]; + } + + /** + * Set active paragraph + * + * @param int $index + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function setActiveParagraph($index = 0) + { + if ($index >= count($this->richTextParagraphs)) { + throw new \Exception("Invalid paragraph count."); + } + + $this->activeParagraph = $index; + + return $this->getActiveParagraph(); + } + + /** + * Get paragraph + * + * @param int $index + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + */ + public function getParagraph($index = 0) + { + if ($index >= count($this->richTextParagraphs)) { + throw new \Exception("Invalid paragraph count."); + } + + return $this->richTextParagraphs[$index]; + } + + /** + * Create paragraph + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @throws \Exception + */ + public function createParagraph() + { + $this->richTextParagraphs[] = new Paragraph(); + $totalRichTextParagraphs = count($this->richTextParagraphs); + $this->activeParagraph = $totalRichTextParagraphs - 1; + + if ($totalRichTextParagraphs > 1) { + $alignment = clone $this->getActiveParagraph()->getAlignment(); + $font = clone $this->getActiveParagraph()->getFont(); + $bulletStyle = clone $this->getActiveParagraph()->getBulletStyle(); + + $this->getActiveParagraph()->setAlignment($alignment); + $this->getActiveParagraph()->setFont($font); + $this->getActiveParagraph()->setBulletStyle($bulletStyle); + } + return $this->getActiveParagraph(); + } + + /** + * Add text + * + * @param \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface $pText Rich text element + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + */ + public function addText(TextElementInterface $pText = null) + { + $this->richTextParagraphs[$this->activeParagraph]->addText($pText); + + return $this; + } + + /** + * Create text (can not be formatted !) + * + * @param string $pText Text + * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElement + * @throws \Exception + */ + public function createText($pText = '') + { + return $this->richTextParagraphs[$this->activeParagraph]->createText($pText); + } + + /** + * Create break + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\BreakElement + * @throws \Exception + */ + public function createBreak() + { + return $this->richTextParagraphs[$this->activeParagraph]->createBreak(); + } + + /** + * Create text run (can be formatted) + * + * @param string $pText Text + * @return \PhpOffice\PhpPresentation\Shape\RichText\Run + * @throws \Exception + */ + public function createTextRun($pText = '') + { + return $this->richTextParagraphs[$this->activeParagraph]->createTextRun($pText); + } + + /** + * Get plain text + * + * @return string + */ + public function getPlainText() + { + // Return value + $returnValue = ''; + + // Loop trough all \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + foreach ($this->richTextParagraphs as $p) { + $returnValue .= $p->getPlainText(); + } + + // Return + return $returnValue; + } + + /** + * Convert to string + * + * @return string + */ + public function __toString() + { + return $this->getPlainText(); + } + + /** + * Get paragraphs + * + * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] + */ + public function getParagraphs() + { + return $this->richTextParagraphs; + } + + /** + * Set paragraphs + * + * @param \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] $paragraphs Array of paragraphs + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + */ + public function setParagraphs($paragraphs = null) + { + if (!is_array($paragraphs)) { + throw new \Exception("Invalid \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] array passed."); + } + $this->richTextParagraphs = $paragraphs; + $this->activeParagraph = count($this->richTextParagraphs) - 1; + return $this; + } + + /** + * Get fill + * + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function getFill() + { + return $this->fill; + } + + /** + * Set fill + * + * @param \PhpOffice\PhpPresentation\Style\Fill $fill + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + */ + public function setFill(Fill $fill) + { + $this->fill = $fill; + + return $this; + } + + /** + * Get borders + * + * @return \PhpOffice\PhpPresentation\Style\Borders + */ + public function getBorders() + { + return $this->borders; + } + + /** + * Set borders + * + * @param \PhpOffice\PhpPresentation\Style\Borders $borders + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + */ + public function setBorders(Borders $borders) + { + $this->borders = $borders; + + return $this; + } + + /** + * Get width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set width + * + * @param int $value + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + */ + public function setWidth($value = 0) + { + $this->width = $value; + + return $this; + } + + /** + * Get colSpan + * + * @return int + */ + public function getColSpan() + { + return $this->colSpan; + } + + /** + * Set colSpan + * + * @param int $value + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + */ + public function setColSpan($value = 0) + { + $this->colSpan = $value; + + return $this; + } + + /** + * Get rowSpan + * + * @return int + */ + public function getRowSpan() + { + return $this->rowSpan; + } + + /** + * Set rowSpan + * + * @param int $value + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + */ + public function setRowSpan($value = 0) + { + $this->rowSpan = $value; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + $hashElements = ''; + foreach ($this->richTextParagraphs as $element) { + $hashElements .= $element->getHashCode(); + } + + return md5($hashElements . $this->fill->getHashCode() . $this->borders->getHashCode() . $this->width . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Table/Row.php b/PhpOffice/PhpPresentation/Shape/Table/Row.php new file mode 100644 index 0000000..18d4475 --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Table/Row.php @@ -0,0 +1,212 @@ +cells = array(); + for ($i = 0; $i < $columns; $i++) { + $this->cells[] = new Cell(); + } + + // Set fill + $this->fill = new Fill(); + } + + /** + * Get cell + * + * @param int $cell Cell number + * @param boolean $exceptionAsNull Return a null value instead of an exception? + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + */ + public function getCell($cell = 0, $exceptionAsNull = false) + { + if (!isset($this->cells[$cell])) { + if ($exceptionAsNull) { + return null; + } + throw new \Exception('Cell number out of bounds.'); + } + + return $this->cells[$cell]; + } + + /** + * Get cells + * + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell[] + */ + public function getCells() + { + return $this->cells; + } + + /** + * Next cell (moves one cell to the right) + * + * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + * @throws \Exception + */ + public function nextCell() + { + $this->activeCellIndex++; + if (isset($this->cells[$this->activeCellIndex])) { + $this->cells[$this->activeCellIndex]->setFill(clone $this->getFill()); + return $this->cells[$this->activeCellIndex]; + } + throw new \Exception("Cell count out of bounds."); + } + + /** + * Get fill + * + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function getFill() + { + return $this->fill; + } + + /** + * Set fill + * + * @param \PhpOffice\PhpPresentation\Style\Fill $fill + * @return \PhpOffice\PhpPresentation\Shape\Table\Row + */ + public function setFill(Fill $fill) + { + $this->fill = $fill; + + return $this; + } + + /** + * Get height + * + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set height + * + * @param int $value + * @return \PhpOffice\PhpPresentation\Shape\Table\Row + */ + public function setHeight($value = 0) + { + $this->height = $value; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + $hashElements = ''; + foreach ($this->cells as $cell) { + $hashElements .= $cell->getHashCode(); + } + + return md5($hashElements . $this->fill->getHashCode() . $this->height . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/ShapeContainerInterface.php b/PhpOffice/PhpPresentation/ShapeContainerInterface.php new file mode 100644 index 0000000..4575c74 --- /dev/null +++ b/PhpOffice/PhpPresentation/ShapeContainerInterface.php @@ -0,0 +1,67 @@ +parent = $pParent; + // Shape collection + $this->shapeCollection = new \ArrayObject(); + // Set identifier + $this->identifier = md5(rand(0, 9999) . time()); + // Set Slide Layout + if ($this->parent instanceof PhpPresentation) { + $arrayMasterSlides = $this->parent->getAllMasterSlides(); + $oMasterSlide = reset($arrayMasterSlides); + $arraySlideLayouts = $oMasterSlide->getAllSlideLayouts(); + $oSlideLayout = reset($arraySlideLayouts); + $this->setSlideLayout($oSlideLayout); + } + } + + /** + * Get slide layout + * + * @return SlideLayout + */ + public function getSlideLayout() + { + return $this->slideLayout; + } + + /** + * Set slide layout + * + * @param SlideLayout $layout + * @return \PhpOffice\PhpPresentation\Slide + */ + public function setSlideLayout(SlideLayout $layout) + { + $this->slideLayout = $layout; + return $this; + } + + /** + * Get slide master id + * + * @return int + */ + public function getSlideMasterId() + { + return $this->slideMasterId; + } + + /** + * Set slide master id + * + * @param int $masterId + * @return \PhpOffice\PhpPresentation\Slide + */ + public function setSlideMasterId($masterId = 1) + { + $this->slideMasterId = $masterId; + + return $this; + } + + /** + * Copy slide (!= clone!) + * + * @return \PhpOffice\PhpPresentation\Slide + */ + public function copy() + { + $copied = clone $this; + + return $copied; + } + + /** + * + * @return \PhpOffice\PhpPresentation\Slide\Note + */ + public function getNote() + { + if (is_null($this->slideNote)) { + $this->setNote(); + } + return $this->slideNote; + } + + /** + * + * @param \PhpOffice\PhpPresentation\Slide\Note $note + * @return \PhpOffice\PhpPresentation\Slide + */ + public function setNote(Note $note = null) + { + $this->slideNote = (is_null($note) ? new Note() : $note); + $this->slideNote->setParent($this); + + return $this; + } + + /** + * Get the name of the slide + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set the name of the slide + * @param string $name + * @return $this + */ + public function setName($name = null) + { + $this->name = $name; + return $this; + } + + /** + * @return boolean + */ + public function isVisible() + { + return $this->isVisible; + } + + /** + * @param boolean $value + * @return Slide + */ + public function setIsVisible($value = true) + { + $this->isVisible = (bool)$value; + return $this; + } + + /** + * Add an animation to the slide + * + * @param \PhpOffice\PhpPresentation\Slide\Animation + * @return Slide + */ + public function addAnimation($animation) + { + $this->animations[] = $animation; + return $this; + } + + /** + * Get collection of animations + * + * @return \PhpOffice\PhpPresentation\Slide\Animation[] + */ + public function getAnimations() + { + return $this->animations; + } + + /** + * Set collection of animations + * @param \PhpOffice\PhpPresentation\Slide\Animation[] $array + * @return Slide + */ + public function setAnimations(array $array = array()) + { + $this->animations = $array; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Slide/AbstractBackground.php b/PhpOffice/PhpPresentation/Slide/AbstractBackground.php new file mode 100644 index 0000000..72edbdf --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/AbstractBackground.php @@ -0,0 +1,8 @@ +shapeCollection; + } + + /** + * Get collection of shapes + * + * @param array $shapeCollection + * @return AbstractSlide + */ + public function setShapeCollection($shapeCollection = array()) + { + $this->shapeCollection = $shapeCollection; + return $this; + } + + /** + * Add shape to slide + * + * @param \PhpOffice\PhpPresentation\AbstractShape $shape + * @return \PhpOffice\PhpPresentation\AbstractShape + * @throws \Exception + */ + public function addShape(AbstractShape $shape) + { + $shape->setContainer($this); + return $shape; + } + + /** + * Get X Offset + * + * @return int + */ + public function getOffsetX() + { + if ($this->offsetX === null) { + $offsets = GeometryCalculator::calculateOffsets($this); + $this->offsetX = $offsets[GeometryCalculator::X]; + $this->offsetY = $offsets[GeometryCalculator::Y]; + } + return $this->offsetX; + } + + /** + * Get Y Offset + * + * @return int + */ + public function getOffsetY() + { + if ($this->offsetY === null) { + $offsets = GeometryCalculator::calculateOffsets($this); + $this->offsetX = $offsets[GeometryCalculator::X]; + $this->offsetY = $offsets[GeometryCalculator::Y]; + } + return $this->offsetY; + } + + /** + * Get X Extent + * + * @return int + */ + public function getExtentX() + { + if ($this->extentX === null) { + $extents = GeometryCalculator::calculateExtents($this); + $this->extentX = $extents[GeometryCalculator::X]; + $this->extentY = $extents[GeometryCalculator::Y]; + } + return $this->extentX; + } + + /** + * Get Y Extent + * + * @return int + */ + public function getExtentY() + { + if ($this->extentY === null) { + $extents = GeometryCalculator::calculateExtents($this); + $this->extentX = $extents[GeometryCalculator::X]; + $this->extentY = $extents[GeometryCalculator::Y]; + } + return $this->extentY; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->identifier . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } + + /** + * Create rich text shape + * + * @return \PhpOffice\PhpPresentation\Shape\RichText + * @throws \Exception + */ + public function createRichTextShape() + { + $shape = new RichText(); + $this->addShape($shape); + return $shape; + } + + /** + * Create line shape + * + * @param int $fromX Starting point x offset + * @param int $fromY Starting point y offset + * @param int $toX Ending point x offset + * @param int $toY Ending point y offset + * @return \PhpOffice\PhpPresentation\Shape\Line + * @throws \Exception + */ + public function createLineShape($fromX, $fromY, $toX, $toY) + { + $shape = new Line($fromX, $fromY, $toX, $toY); + $this->addShape($shape); + return $shape; + } + + /** + * Create chart shape + * + * @return \PhpOffice\PhpPresentation\Shape\Chart + * @throws \Exception + */ + public function createChartShape() + { + $shape = new Chart(); + $this->addShape($shape); + return $shape; + } + + /** + * Create drawing shape + * + * @return \PhpOffice\PhpPresentation\Shape\Drawing\File + * @throws \Exception + */ + public function createDrawingShape() + { + $shape = new File(); + $this->addShape($shape); + return $shape; + } + + /** + * Create table shape + * + * @param int $columns Number of columns + * @return \PhpOffice\PhpPresentation\Shape\Table + * @throws \Exception + */ + public function createTableShape($columns = 1) + { + $shape = new Table($columns); + $this->addShape($shape); + return $shape; + } + + /** + * Creates a group within this slide + * + * @return \PhpOffice\PhpPresentation\Shape\Group + * @throws \Exception + */ + public function createGroup() + { + $shape = new Group(); + $this->addShape($shape); + return $shape; + } + + /** + * Get parent + * + * @return PhpPresentation + */ + public function getParent() + { + return $this->parent; + } + + /** + * Re-bind parent + * + * @param \PhpOffice\PhpPresentation\PhpPresentation $parent + * @return \PhpOffice\PhpPresentation\Slide\AbstractSlide + * @throws \Exception + */ + public function rebindParent(PhpPresentation $parent) + { + $this->parent->removeSlideByIndex($this->parent->getIndex($this)); + $this->parent = $parent; + return $this; + } + + /** + * @return AbstractBackground + */ + public function getBackground() + { + return $this->background; + } + + /** + * @param AbstractBackground $background + * @return \PhpOffice\PhpPresentation\Slide\AbstractSlide + */ + public function setBackground(AbstractBackground $background = null) + { + $this->background = $background; + return $this; + } + + /** + * + * @return \PhpOffice\PhpPresentation\Slide\Transition + */ + public function getTransition() + { + return $this->slideTransition; + } + + /** + * + * @param \PhpOffice\PhpPresentation\Slide\Transition $transition + * @return \PhpOffice\PhpPresentation\Slide\AbstractSlide + */ + public function setTransition(Transition $transition = null) + { + $this->slideTransition = $transition; + return $this; + } + + /** + * @return string + */ + public function getRelsIndex() + { + return $this->relsIndex; + } + + /** + * @param string $indexName + */ + public function setRelsIndex($indexName) + { + $this->relsIndex = $indexName; + } +} diff --git a/PhpOffice/PhpPresentation/Slide/Animation.php b/PhpOffice/PhpPresentation/Slide/Animation.php new file mode 100644 index 0000000..54f77da --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/Animation.php @@ -0,0 +1,40 @@ +shapeCollection[] = $shape; + return $this; + } + + /** + * @return array + */ + public function getShapeCollection() + { + return $this->shapeCollection; + } + + /** + * @param array $array + * @return Animation + */ + public function setShapeCollection(array $array = array()) + { + $this->shapeCollection = $array; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Slide/Background/Color.php b/PhpOffice/PhpPresentation/Slide/Background/Color.php new file mode 100644 index 0000000..fb975f6 --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/Background/Color.php @@ -0,0 +1,32 @@ +color = $color; + return $this; + } + + /** + * @return StyleColor + */ + public function getColor() + { + return $this->color; + } +} diff --git a/PhpOffice/PhpPresentation/Slide/Background/Image.php b/PhpOffice/PhpPresentation/Slide/Background/Image.php new file mode 100644 index 0000000..ca6eb54 --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/Background/Image.php @@ -0,0 +1,97 @@ +path; + } + + /** + * Set Path + * + * @param string $pValue File path + * @param boolean $pVerifyFile Verify file + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Slide\Background\Image + */ + public function setPath($pValue = '', $pVerifyFile = true) + { + if ($pVerifyFile) { + if (!file_exists($pValue)) { + throw new \Exception("File not found : $pValue"); + } + + if ($this->width == 0 && $this->height == 0) { + // Get width/height + list($this->width, $this->height) = getimagesize($pValue); + } + } + $this->path = $pValue; + return $this; + } + + /** + * Get Filename + * + * @return string + */ + public function getFilename() + { + return basename($this->path); + } + + /** + * Get Extension + * + * @return string + */ + public function getExtension() + { + $exploded = explode('.', basename($this->path)); + + return $exploded[count($exploded) - 1]; + } + + /** + * Get indexed filename (using image index) + * + * @param integer $numSlide + * @return string + */ + public function getIndexedFilename($numSlide) + { + return 'background_' . $numSlide . '.' . $this->getExtension(); + } +} diff --git a/PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php b/PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php new file mode 100644 index 0000000..51bba87 --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php @@ -0,0 +1,32 @@ +schemeColor = $color; + return $this; + } + + /** + * @return StyleSchemeColor + */ + public function getSchemeColor() + { + return $this->schemeColor; + } +} diff --git a/PhpOffice/PhpPresentation/Slide/Iterator.php b/PhpOffice/PhpPresentation/Slide/Iterator.php new file mode 100644 index 0000000..66d159d --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/Iterator.php @@ -0,0 +1,108 @@ +subject = $subject; + } + + /** + * Destructor + */ + public function __destruct() + { + unset($this->subject); + } + + /** + * Rewind iterator + */ + public function rewind() + { + $this->position = 0; + } + + /** + * Current \PhpOffice\PhpPresentation\Slide + * + * @return \PhpOffice\PhpPresentation\Slide + * @throws \Exception + */ + public function current() + { + return $this->subject->getSlide($this->position); + } + + /** + * Current key + * + * @return int + */ + public function key() + { + return $this->position; + } + + /** + * Next value + */ + public function next() + { + ++$this->position; + } + + /** + * More \PhpOffice\PhpPresentation\Slide instances available? + * + * @return boolean + */ + public function valid() + { + return $this->position < $this->subject->getSlideCount(); + } +} diff --git a/PhpOffice/PhpPresentation/Slide/Layout.php b/PhpOffice/PhpPresentation/Slide/Layout.php new file mode 100644 index 0000000..b4fc027 --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/Layout.php @@ -0,0 +1,37 @@ +parent = $pParent; + + // Shape collection + $this->shapeCollection = new \ArrayObject(); + + // Set identifier + $this->identifier = md5(rand(0, 9999) . time()); + } + + /** + * Get collection of shapes + * + * @return \ArrayObject|\PhpOffice\PhpPresentation\AbstractShape[] + */ + public function getShapeCollection() + { + return $this->shapeCollection; + } + + /** + * Add shape to slide + * + * @param \PhpOffice\PhpPresentation\AbstractShape $shape + * @return \PhpOffice\PhpPresentation\AbstractShape + * @throws \Exception + */ + public function addShape(AbstractShape $shape) + { + $shape->setContainer($this); + + return $shape; + } + + /** + * Create rich text shape + * + * @return \PhpOffice\PhpPresentation\Shape\RichText + * @throws \Exception + */ + public function createRichTextShape() + { + $shape = new RichText(); + $this->addShape($shape); + + return $shape; + } + + /** + * Get parent + * + * @return Slide + */ + public function getParent() + { + return $this->parent; + } + + /** + * Set parent + * + * @param Slide $parent + * @return Note + */ + public function setParent(Slide $parent) + { + $this->parent = $parent; + return $this; + } + + + /** + * Get X Offset + * + * @return int + */ + public function getOffsetX() + { + if ($this->offsetX === null) { + $offsets = GeometryCalculator::calculateOffsets($this); + $this->offsetX = $offsets[GeometryCalculator::X]; + $this->offsetY = $offsets[GeometryCalculator::Y]; + } + return $this->offsetX; + } + + /** + * Get Y Offset + * + * @return int + */ + public function getOffsetY() + { + if ($this->offsetY === null) { + $offsets = GeometryCalculator::calculateOffsets($this); + $this->offsetX = $offsets[GeometryCalculator::X]; + $this->offsetY = $offsets[GeometryCalculator::Y]; + } + return $this->offsetY; + } + + /** + * Get X Extent + * + * @return int + */ + public function getExtentX() + { + if ($this->extentX === null) { + $extents = GeometryCalculator::calculateExtents($this); + $this->extentX = $extents[GeometryCalculator::X]; + $this->extentY = $extents[GeometryCalculator::Y]; + } + return $this->extentX; + } + + /** + * Get Y Extent + * + * @return int + */ + public function getExtentY() + { + if ($this->extentY === null) { + $extents = GeometryCalculator::calculateExtents($this); + $this->extentX = $extents[GeometryCalculator::X]; + $this->extentY = $extents[GeometryCalculator::Y]; + } + return $this->extentY; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->identifier . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Slide/SlideLayout.php b/PhpOffice/PhpPresentation/Slide/SlideLayout.php new file mode 100644 index 0000000..72f42b8 --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/SlideLayout.php @@ -0,0 +1,99 @@ +slideMaster = $pSlideMaster; + // Shape collection + $this->shapeCollection = new \ArrayObject(); + // Set identifier + $this->identifier = md5(rand(0, 9999) . time()); + // Set a basic colorMap + $this->colorMap = new ColorMap(); + } + + /** + * @return int + */ + public function getLayoutName() + { + return $this->layoutName; + } + + /** + * @param int $layoutName + * @return SlideLayout + */ + public function setLayoutName($layoutName) + { + $this->layoutName = $layoutName; + return $this; + } + + /** + * @return SlideMaster + */ + public function getSlideMaster() + { + return $this->slideMaster; + } +} diff --git a/PhpOffice/PhpPresentation/Slide/SlideMaster.php b/PhpOffice/PhpPresentation/Slide/SlideMaster.php new file mode 100644 index 0000000..f06f740 --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/SlideMaster.php @@ -0,0 +1,170 @@ + '000000', + 'lt1' => 'FFFFFF', + 'dk2' => '1F497D', + 'lt2' => 'EEECE1', + 'accent1' => '4F81BD', + 'accent2' => 'C0504D', + 'accent3' => '9BBB59', + 'accent4' => '8064A2', + 'accent5' => '4BACC6', + 'accent6' => 'F79646', + 'hlink' => '0000FF', + 'folHlink' => '800080', + ); + + /** + * Create a new slideMaster + * + * @param PhpPresentation $pParent + * @throws \Exception + */ + public function __construct(PhpPresentation $pParent = null) + { + // Set parent + $this->parent = $pParent; + // Shape collection + $this->shapeCollection = new \ArrayObject(); + // Set identifier + $this->identifier = md5(rand(0, 9999) . time()); + // Set a basic colorMap + $this->colorMap = new ColorMap(); + // Set a white background + $this->background = new BackgroundColor(); + $this->background->setColor(new Color(Color::COLOR_WHITE)); + // Set basic textStyles + $this->textStyles = new TextStyle(true); + // Set basic scheme colors + foreach ($this->defaultSchemeColor as $key => $value) { + $oSchemeColor = new SchemeColor(); + $oSchemeColor->setValue($key); + $oSchemeColor->setRGB($value); + $this->addSchemeColor($oSchemeColor); + } + } + + /** + * Create a slideLayout and add it to this presentation + * + * @return \PhpOffice\PhpPresentation\Slide\SlideLayout + * @throws \Exception + */ + public function createSlideLayout() + { + $newSlideLayout = new SlideLayout($this); + $this->addSlideLayout($newSlideLayout); + return $newSlideLayout; + } + + /** + * Add slideLayout + * + * @param \PhpOffice\PhpPresentation\Slide\SlideLayout $slideLayout + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Slide\SlideLayout + */ + public function addSlideLayout(SlideLayout $slideLayout = null) + { + $this->slideLayouts[] = $slideLayout; + return $slideLayout; + } + + /** + * @return SlideLayout[] + */ + public function getAllSlideLayouts() + { + return $this->slideLayouts; + } + + /** + * @return TextStyle + */ + public function getTextStyles() + { + return $this->textStyles; + } + + /** + * @param TextStyle $textStyle + * @return $this + */ + public function setTextStyles(TextStyle $textStyle) + { + $this->textStyles = $textStyle; + return $this; + } + + /** + * @param SchemeColor $schemeColor + * @return $this + */ + public function addSchemeColor(SchemeColor $schemeColor) + { + $this->arraySchemeColor[$schemeColor->getValue()] = $schemeColor; + return $this; + } + + /** + * @return \PhpOffice\PhpPresentation\Style\SchemeColor[] + */ + public function getAllSchemeColors() + { + return $this->arraySchemeColor; + } +} diff --git a/PhpOffice/PhpPresentation/Slide/Transition.php b/PhpOffice/PhpPresentation/Slide/Transition.php new file mode 100644 index 0000000..b2e6a29 --- /dev/null +++ b/PhpOffice/PhpPresentation/Slide/Transition.php @@ -0,0 +1,166 @@ +speed = $speed; + } else { + $this->speed = null; + } + + return $this; + } + + public function getSpeed() + { + return $this->speed; + } + + public function setManualTrigger($value = false) + { + if (is_bool($value)) { + $this->hasManualTrigger = $value; + } + return $this; + } + + public function hasManualTrigger() + { + return $this->hasManualTrigger; + } + + public function setTimeTrigger($value = false, $advanceTime = 1000) + { + if (is_bool($value)) { + $this->hasTimeTrigger = $value; + } + $this->advanceTimeTrigger = null; + if ($this->hasTimeTrigger === true) { + $this->advanceTimeTrigger = (int) $advanceTime; + } + return $this; + } + + public function hasTimeTrigger() + { + return $this->hasTimeTrigger; + } + + public function getAdvanceTimeTrigger() + { + return $this->advanceTimeTrigger; + } + + public function setTransitionType($type = null) + { + $this->transitionType = $type; + return $this; + } + + public function getTransitionType() + { + return $this->transitionType; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Alignment.php b/PhpOffice/PhpPresentation/Style/Alignment.php new file mode 100644 index 0000000..89cde6a --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Alignment.php @@ -0,0 +1,394 @@ +horizontal = self::HORIZONTAL_LEFT; + $this->vertical = self::VERTICAL_BASE; + } + + /** + * Get Horizontal + * + * @return string + */ + public function getHorizontal() + { + return $this->horizontal; + } + + /** + * Set Horizontal + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function setHorizontal($pValue = self::HORIZONTAL_LEFT) + { + if ($pValue == '') { + $pValue = self::HORIZONTAL_LEFT; + } + $this->horizontal = $pValue; + + return $this; + } + + /** + * Get Vertical + * + * @return string + */ + public function getVertical() + { + return $this->vertical; + } + + /** + * Set Vertical + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function setVertical($pValue = self::VERTICAL_BASE) + { + if ($pValue == '') { + $pValue = self::VERTICAL_BASE; + } + $this->vertical = $pValue; + + return $this; + } + + /** + * Get Level + * + * @return int + */ + public function getLevel() + { + return $this->level; + } + + /** + * Set Level + * + * @param int $pValue Ranging 0 - 8 + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function setLevel($pValue = 0) + { + if ($pValue < 0) { + throw new \Exception("Invalid value should be more than 0."); + } + $this->level = $pValue; + + return $this; + } + + /** + * Get indent + * + * @return int + */ + public function getIndent() + { + return $this->indent; + } + + /** + * Set indent + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function setIndent($pValue = 0) + { + if ($pValue > 0 && !in_array($this->getHorizontal(), $this->supportedStyles)) { + $pValue = 0; // indent not supported + } + + $this->indent = $pValue; + + return $this; + } + + /** + * Get margin left + * + * @return int + */ + public function getMarginLeft() + { + return $this->marginLeft; + } + + /** + * Set margin left + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function setMarginLeft($pValue = 0) + { + if ($pValue > 0 && !in_array($this->getHorizontal(), $this->supportedStyles)) { + $pValue = 0; // margin left not supported + } + + $this->marginLeft = $pValue; + + return $this; + } + + /** + * Get margin right + * + * @return int + */ + public function getMarginRight() + { + return $this->marginRight; + } + + /** + * Set margin ight + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function setMarginRight($pValue = 0) + { + if ($pValue > 0 && !in_array($this->getHorizontal(), $this->supportedStyles)) { + $pValue = 0; // margin right not supported + } + + $this->marginRight = $pValue; + + return $this; + } + + /** + * Get margin top + * + * @return int + */ + public function getMarginTop() + { + return $this->marginTop; + } + + /** + * Set margin top + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function setMarginTop($pValue = 0) + { + $this->marginTop = $pValue; + + return $this; + } + + /** + * Get margin bottom + * + * @return int + */ + public function getMarginBottom() + { + return $this->marginBottom; + } + + /** + * Set margin bottom + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Style\Alignment + */ + public function setMarginBottom($pValue = 0) + { + $this->marginBottom = $pValue; + + return $this; + } + + /** + * @return string + */ + public function getTextDirection() + { + return $this->textDirection; + } + + /** + * @param string $pValue + * @return Alignment + */ + public function setTextDirection($pValue = self::TEXT_DIRECTION_HORIZONTAL) + { + if (empty($pValue)) { + $pValue = self::TEXT_DIRECTION_HORIZONTAL; + } + $this->textDirection = $pValue; + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->horizontal + . $this->vertical + . $this->level + . $this->indent + . $this->marginLeft + . $this->marginRight + . __CLASS__ + ); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Border.php b/PhpOffice/PhpPresentation/Style/Border.php new file mode 100644 index 0000000..f8dddf8 --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Border.php @@ -0,0 +1,235 @@ +lineWidth = 1; + $this->lineStyle = self::LINE_SINGLE; + $this->dashStyle = self::DASH_SOLID; + $this->color = new Color(Color::COLOR_BLACK); + } + + /** + * Get line width (in points) + * + * @return int + */ + public function getLineWidth() + { + return $this->lineWidth; + } + + /** + * Set line width (in points) + * + * @param int $pValue + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function setLineWidth($pValue = 1) + { + $this->lineWidth = $pValue; + + return $this; + } + + /** + * Get line style + * + * @return string + */ + public function getLineStyle() + { + return $this->lineStyle; + } + + /** + * Set line style + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function setLineStyle($pValue = self::LINE_SINGLE) + { + if ($pValue == '') { + $pValue = self::LINE_SINGLE; + } + $this->lineStyle = $pValue; + + return $this; + } + + /** + * Get dash style + * + * @return string + */ + public function getDashStyle() + { + return $this->dashStyle; + } + + /** + * Set dash style + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function setDashStyle($pValue = self::DASH_SOLID) + { + if ($pValue == '') { + $pValue = self::DASH_SOLID; + } + $this->dashStyle = $pValue; + + return $this; + } + + /** + * Get Border Color + * + * @return \PhpOffice\PhpPresentation\Style\Color + */ + public function getColor() + { + return $this->color; + } + + /** + * Set Border Color + * + * @param \PhpOffice\PhpPresentation\Style\Color $color + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function setColor(Color $color = null) + { + $this->color = $color; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->lineStyle + . $this->lineWidth + . $this->dashStyle + . $this->color->getHashCode() + . __CLASS__ + ); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Borders.php b/PhpOffice/PhpPresentation/Style/Borders.php new file mode 100644 index 0000000..3d01568 --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Borders.php @@ -0,0 +1,195 @@ +left = new Border(); + $this->right = new Border(); + $this->top = new Border(); + $this->bottom = new Border(); + $this->diagonalUp = new Border(); + $this->diagonalUp->setLineStyle(Border::LINE_NONE); + $this->diagonalDown = new Border(); + $this->diagonalDown->setLineStyle(Border::LINE_NONE); + } + + /** + * Get Left + * + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function getLeft() + { + return $this->left; + } + + /** + * Get Right + * + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function getRight() + { + return $this->right; + } + + /** + * Get Top + * + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function getTop() + { + return $this->top; + } + + /** + * Get Bottom + * + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function getBottom() + { + return $this->bottom; + } + + /** + * Get Diagonal Up + * + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function getDiagonalUp() + { + return $this->diagonalUp; + } + + /** + * Get Diagonal Down + * + * @return \PhpOffice\PhpPresentation\Style\Border + */ + public function getDiagonalDown() + { + return $this->diagonalDown; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->getLeft()->getHashCode() + . $this->getRight()->getHashCode() + . $this->getTop()->getHashCode() + . $this->getBottom()->getHashCode() + . $this->getDiagonalUp()->getHashCode() + . $this->getDiagonalDown()->getHashCode() + . __CLASS__ + ); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Bullet.php b/PhpOffice/PhpPresentation/Style/Bullet.php new file mode 100644 index 0000000..de078a1 --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Bullet.php @@ -0,0 +1,317 @@ +bulletType = self::TYPE_NONE; + $this->bulletFont = 'Calibri'; + $this->bulletChar = '-'; + $this->bulletColor = new Color(); + $this->bulletNumericStyle = self::NUMERIC_DEFAULT; + $this->bulletNumericStartAt = 1; + } + + /** + * Get bullet type + * + * @return string + */ + public function getBulletType() + { + return $this->bulletType; + } + + /** + * Set bullet type + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Bullet + */ + public function setBulletType($pValue = self::TYPE_NONE) + { + $this->bulletType = $pValue; + + return $this; + } + + /** + * Get bullet font + * + * @return string + */ + public function getBulletFont() + { + return $this->bulletFont; + } + + /** + * Set bullet font + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Bullet + */ + public function setBulletFont($pValue = 'Calibri') + { + if ($pValue == '') { + $pValue = 'Calibri'; + } + $this->bulletFont = $pValue; + + return $this; + } + + /** + * Get bullet char + * + * @return string + */ + public function getBulletChar() + { + return $this->bulletChar; + } + + /** + * Set bullet char + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Bullet + */ + public function setBulletChar($pValue = '-') + { + $this->bulletChar = $pValue; + + return $this; + } + + /** + * Get bullet numeric style + * + * @return string + */ + public function getBulletNumericStyle() + { + return $this->bulletNumericStyle; + } + + /** + * Set bullet numeric style + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Bullet + */ + public function setBulletNumericStyle($pValue = self::NUMERIC_DEFAULT) + { + $this->bulletNumericStyle = $pValue; + + return $this; + } + + /** + * Get bullet numeric start at + * + * @return string + */ + public function getBulletNumericStartAt() + { + return $this->bulletNumericStartAt; + } + + /** + * Set bullet numeric start at + * + * @param int|string $pValue + * @return \PhpOffice\PhpPresentation\Style\Bullet + */ + public function setBulletNumericStartAt($pValue = 1) + { + $this->bulletNumericStartAt = $pValue; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->bulletType + . $this->bulletFont + . $this->bulletChar + . $this->bulletNumericStyle + . $this->bulletNumericStartAt + . __CLASS__ + ); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } + + /** + * @return Color + */ + public function getBulletColor() + { + return $this->bulletColor; + } + + /** + * @param Color $bulletColor + * @return Bullet + */ + public function setBulletColor(Color $bulletColor) + { + $this->bulletColor = $bulletColor; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Color.php b/PhpOffice/PhpPresentation/Style/Color.php new file mode 100644 index 0000000..76fddd8 --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Color.php @@ -0,0 +1,197 @@ +argb = $pARGB; + } + + /** + * Get ARGB + * + * @return string + */ + public function getARGB() + { + return $this->argb; + } + + /** + * Set ARGB + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Color + */ + public function setARGB($pValue = self::COLOR_BLACK) + { + if ($pValue == '') { + $pValue = self::COLOR_BLACK; + } + $this->argb = $pValue; + + return $this; + } + + /** + * Get the alpha % of the ARGB + * Will return 100 if no ARGB + * @return integer + */ + public function getAlpha() + { + $alpha = 100; + if (strlen($this->argb) >= 6) { + $dec = hexdec(substr($this->argb, 0, 2)); + $alpha = number_format(($dec/255) * 100, 2); + } + return $alpha; + } + + /** + * Set the alpha % of the ARGB + * @param int $alpha + * @return $this + */ + public function setAlpha($alpha = 100) + { + if ($alpha < 0) { + $alpha = 0; + } + if ($alpha > 100) { + $alpha = 100; + } + $alpha = round(($alpha / 100) * 255); + $alpha = dechex($alpha); + $alpha = str_pad($alpha, 2, '0', STR_PAD_LEFT); + $this->argb = $alpha . substr($this->argb, 2); + return $this; + } + + /** + * Get RGB + * + * @return string + */ + public function getRGB() + { + if (strlen($this->argb) == 6) { + return $this->argb; + } else { + return substr($this->argb, 2); + } + } + + /** + * Set RGB + * + * @param string $pValue + * @param string $pAlpha + * @return \PhpOffice\PhpPresentation\Style\Color + */ + public function setRGB($pValue = '000000', $pAlpha = 'FF') + { + if ($pValue == '') { + $pValue = '000000'; + } + if ($pAlpha == '') { + $pAlpha = 'FF'; + } + $this->argb = $pAlpha . $pValue; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->argb + . __CLASS__ + ); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Style/ColorMap.php b/PhpOffice/PhpPresentation/Style/ColorMap.php new file mode 100644 index 0000000..77f66b9 --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/ColorMap.php @@ -0,0 +1,103 @@ + 'lt1', + self::COLOR_TX1 => 'dk1', + self::COLOR_BG2 => 'lt2', + self::COLOR_TX2 => 'dk2', + self::COLOR_ACCENT1 => 'accent1', + self::COLOR_ACCENT2 => 'accent2', + self::COLOR_ACCENT3 => 'accent3', + self::COLOR_ACCENT4 => 'accent4', + self::COLOR_ACCENT5 => 'accent5', + self::COLOR_ACCENT6 => 'accent6', + self::COLOR_HLINK => 'hlink', + self::COLOR_FOLHLINK => 'folHlink' + ); + + /** + * ColorMap constructor. + * Create a new ColorMap with standard values + */ + public function __construct() + { + $this->mapping = self::$mappingDefault; + } + + /** + * Change the color of one of the elements in the map + * + * @param string $item + * @param string $newThemeColor + * @return ColorMap + */ + public function changeColor($item, $newThemeColor) + { + $this->mapping[$item] = $newThemeColor; + return $this; + } + + /** + * Store a new map. For use with the reader + * + * @param array $arrayMapping + * @return ColorMap + */ + public function setMapping(array $arrayMapping = array()) + { + $this->mapping = $arrayMapping; + return $this; + } + + /** + * Get the whole mapping as an array + * + * @return array + */ + public function getMapping() + { + return $this->mapping; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Fill.php b/PhpOffice/PhpPresentation/Style/Fill.php new file mode 100644 index 0000000..b252762 --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Fill.php @@ -0,0 +1,236 @@ +fillType = self::FILL_NONE; + $this->rotation = (double)0; + $this->startColor = new Color(Color::COLOR_WHITE); + $this->endColor = new Color(Color::COLOR_BLACK); + } + + /** + * Get Fill Type + * + * @return string + */ + public function getFillType() + { + return $this->fillType; + } + + /** + * Set Fill Type + * + * @param string $pValue \PhpOffice\PhpPresentation\Style\Fill fill type + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function setFillType($pValue = self::FILL_NONE) + { + $this->fillType = $pValue; + + return $this; + } + + /** + * Get Rotation + * + * @return double + */ + public function getRotation() + { + return $this->rotation; + } + + /** + * Set Rotation + * + * @param float|int $pValue + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function setRotation($pValue = 0) + { + $this->rotation = (double)$pValue; + + return $this; + } + + /** + * Get Start Color + * + * @return \PhpOffice\PhpPresentation\Style\Color + */ + public function getStartColor() + { + // It's a get but it may lead to a modified color which we won't detect but in which case we must bind. + // So bind as an assurance. + return $this->startColor; + } + + /** + * Set Start Color + * + * @param \PhpOffice\PhpPresentation\Style\Color $pValue + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function setStartColor(Color $pValue = null) + { + $this->startColor = $pValue; + + return $this; + } + + /** + * Get End Color + * + * @return \PhpOffice\PhpPresentation\Style\Color + */ + public function getEndColor() + { + // It's a get but it may lead to a modified color which we won't detect but in which case we must bind. + // So bind as an assurance. + return $this->endColor; + } + + /** + * Set End Color + * + * @param \PhpOffice\PhpPresentation\Style\Color $pValue + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Style\Fill + */ + public function setEndColor(Color $pValue = null) + { + $this->endColor = $pValue; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5( + $this->getFillType() + . $this->getRotation() + . $this->getStartColor()->getHashCode() + . $this->getEndColor()->getHashCode() + . __CLASS__ + ); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Font.php b/PhpOffice/PhpPresentation/Style/Font.php new file mode 100644 index 0000000..e4b49d0 --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Font.php @@ -0,0 +1,450 @@ +name = 'Calibri'; + $this->size = 10; + $this->characterSpacing = 0; + $this->bold = false; + $this->italic = false; + $this->superScript = false; + $this->subScript = false; + $this->underline = self::UNDERLINE_NONE; + $this->strikethrough = false; + $this->color = new Color(Color::COLOR_BLACK); + } + + /** + * Get Name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set Name + * + * @param string $pValue + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setName($pValue = 'Calibri') + { + if ($pValue == '') { + $pValue = 'Calibri'; + } + $this->name = $pValue; + + return $this; + } + + /** + * Get Character Spacing + * + * @return double + */ + public function getCharacterSpacing() + { + return $this->characterSpacing; + } + + /** + * Set Character Spacing + * Value in pt + * @param float|int $pValue + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setCharacterSpacing($pValue = 0) + { + if ($pValue == '') { + $pValue = 0; + } + $this->characterSpacing = $pValue * 100; + + return $this; + } + + /** + * Get Size + * + * @return double + */ + public function getSize() + { + return $this->size; + } + + /** + * Set Size + * + * @param float|int $pValue + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setSize($pValue = 10) + { + if ($pValue == '') { + $pValue = 10; + } + $this->size = $pValue; + + return $this; + } + + /** + * Get Bold + * + * @return boolean + */ + public function isBold() + { + return $this->bold; + } + + /** + * Set Bold + * + * @param boolean $pValue + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setBold($pValue = false) + { + if ($pValue == '') { + $pValue = false; + } + $this->bold = $pValue; + + return $this; + } + + /** + * Get Italic + * + * @return boolean + */ + public function isItalic() + { + return $this->italic; + } + + /** + * Set Italic + * + * @param boolean $pValue + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setItalic($pValue = false) + { + if ($pValue == '') { + $pValue = false; + } + $this->italic = $pValue; + + return $this; + } + + /** + * Get SuperScript + * + * @return boolean + */ + public function isSuperScript() + { + return $this->superScript; + } + + /** + * Set SuperScript + * + * @param boolean $pValue + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setSuperScript($pValue = false) + { + if ($pValue == '') { + $pValue = false; + } + + $this->superScript = $pValue; + + // Set SubScript at false only if SuperScript is true + if ($pValue === true) { + $this->subScript = false; + } + + return $this; + } + + /** + * Get SubScript + * + * @return boolean + */ + public function isSubScript() + { + return $this->subScript; + } + + /** + * Set SubScript + * + * @param boolean $pValue + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setSubScript($pValue = false) + { + if ($pValue == '') { + $pValue = false; + } + + $this->subScript = $pValue; + + // Set SuperScript at false only if SubScript is true + if ($pValue === true) { + $this->superScript = false; + } + + return $this; + } + + /** + * Get Underline + * + * @return string + */ + public function getUnderline() + { + return $this->underline; + } + + /** + * Set Underline + * + * @param string $pValue \PhpOffice\PhpPresentation\Style\Font underline type + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setUnderline($pValue = self::UNDERLINE_NONE) + { + if ($pValue == '') { + $pValue = self::UNDERLINE_NONE; + } + $this->underline = $pValue; + + return $this; + } + + /** + * Get Strikethrough + * + * @return boolean + */ + public function isStrikethrough() + { + return $this->strikethrough; + } + + /** + * Set Strikethrough + * + * @param boolean $pValue + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setStrikethrough($pValue = false) + { + if ($pValue == '') { + $pValue = false; + } + $this->strikethrough = $pValue; + + return $this; + } + + /** + * Get Color + * + * @return \PhpOffice\PhpPresentation\Style\Color|\PhpOffice\PhpPresentation\Style\SchemeColor + */ + public function getColor() + { + return $this->color; + } + + /** + * Set Color + * + * @param \PhpOffice\PhpPresentation\Style\Color|\PhpOffice\PhpPresentation\Style\SchemeColor $pValue + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Style\Font + */ + public function setColor($pValue = null) + { + if (!$pValue instanceof Color) { + throw new \Exception('$pValue must be an instance of \PhpOffice\PhpPresentation\Style\Color'); + } + $this->color = $pValue; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5($this->name . $this->size . ($this->bold ? 't' : 'f') . ($this->italic ? 't' : 'f') . ($this->superScript ? 't' : 'f') . ($this->subScript ? 't' : 'f') . $this->underline . ($this->strikethrough ? 't' : 'f') . $this->color->getHashCode() . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Outline.php b/PhpOffice/PhpPresentation/Style/Outline.php new file mode 100644 index 0000000..03c7d21 --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Outline.php @@ -0,0 +1,79 @@ +fill = new Fill(); + } + + /** + * @return Fill + */ + public function getFill() + { + return $this->fill; + } + + /** + * @param Fill $fill + * @return Outline + */ + public function setFill(Fill $fill) + { + $this->fill = $fill; + return $this; + } + + /** + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Value in points + * @param int $width + * @return Outline + */ + public function setWidth($width) + { + $this->width = intval($width); + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Style/SchemeColor.php b/PhpOffice/PhpPresentation/Style/SchemeColor.php new file mode 100644 index 0000000..7b8c7fb --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/SchemeColor.php @@ -0,0 +1,39 @@ +value; + } + + /** + * @param string $value + */ + public function setValue($value) + { + $this->value = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Style/Shadow.php b/PhpOffice/PhpPresentation/Style/Shadow.php new file mode 100644 index 0000000..acf672e --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/Shadow.php @@ -0,0 +1,309 @@ +visible = false; + $this->blurRadius = 6; + $this->distance = 2; + $this->direction = 0; + $this->alignment = self::SHADOW_BOTTOM_RIGHT; + $this->color = new Color(Color::COLOR_BLACK); + $this->alpha = 50; + } + + /** + * Get Visible + * + * @return boolean + */ + public function isVisible() + { + return $this->visible; + } + + /** + * Set Visible + * + * @param boolean $pValue + * @return $this + */ + public function setVisible($pValue = false) + { + $this->visible = $pValue; + + return $this; + } + + /** + * Get Blur radius + * + * @return int + */ + public function getBlurRadius() + { + return $this->blurRadius; + } + + /** + * Set Blur radius + * + * @param int $pValue + * @return $this + */ + public function setBlurRadius($pValue = 6) + { + $this->blurRadius = $pValue; + + return $this; + } + + /** + * Get Shadow distance + * + * @return int + */ + public function getDistance() + { + return $this->distance; + } + + /** + * Set Shadow distance + * + * @param int $pValue + * @return $this + */ + public function setDistance($pValue = 2) + { + $this->distance = $pValue; + + return $this; + } + + /** + * Get Shadow direction (in degrees) + * + * @return int + */ + public function getDirection() + { + return $this->direction; + } + + /** + * Set Shadow direction (in degrees) + * + * @param int $pValue + * @return $this + */ + public function setDirection($pValue = 0) + { + $this->direction = $pValue; + + return $this; + } + + /** + * Get Shadow alignment + * + * @return int + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * Set Shadow alignment + * + * @param string $pValue + * @return $this + */ + public function setAlignment($pValue = self::SHADOW_BOTTOM_RIGHT) + { + $this->alignment = $pValue; + + return $this; + } + + /** + * Get Color + * + * @return \PhpOffice\PhpPresentation\Style\Color + */ + public function getColor() + { + return $this->color; + } + + /** + * Set Color + * + * @param \PhpOffice\PhpPresentation\Style\Color $pValue + * @throws \Exception + * @return $this + */ + public function setColor(Color $pValue = null) + { + $this->color = $pValue; + + return $this; + } + + /** + * Get Alpha + * + * @return int + */ + public function getAlpha() + { + return $this->alpha; + } + + /** + * Set Alpha + * + * @param int $pValue + * @return $this + */ + public function setAlpha($pValue = 0) + { + $this->alpha = $pValue; + + return $this; + } + + /** + * Get hash code + * + * @return string Hash code + */ + public function getHashCode() + { + return md5(($this->visible ? 't' : 'f') . $this->blurRadius . $this->distance . $this->direction . $this->alignment . $this->color->getHashCode() . $this->alpha . __CLASS__); + } + + /** + * Get hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @return string Hash index + */ + public function getHashIndex() + { + return $this->hashIndex; + } + + /** + * Set hash index + * + * Note that this index may vary during script execution! Only reliable moment is + * while doing a write of a workbook and when changes are not allowed. + * + * @param string $value Hash index + */ + public function setHashIndex($value) + { + $this->hashIndex = $value; + } +} diff --git a/PhpOffice/PhpPresentation/Style/TextStyle.php b/PhpOffice/PhpPresentation/Style/TextStyle.php new file mode 100644 index 0000000..2f41c7a --- /dev/null +++ b/PhpOffice/PhpPresentation/Style/TextStyle.php @@ -0,0 +1,186 @@ +setValue('lt1'); + $oColorTX1 = new SchemeColor(); + $oColorTX1->setValue('tx1'); + + $oRTParagraphBody = new RichTextParagraph(); + $oRTParagraphBody->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_CENTER) + ->setIndent(-324900 / 9525) + ->setMarginLeft(342900 / 9525); + $oRTParagraphBody->getFont()->setSize(32)->setColor($oColorTX1); + $this->bodyStyle[1] = $oRTParagraphBody; + + $oRTParagraphOther = new RichTextParagraph(); + $oRTParagraphOther->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + $oRTParagraphOther->getFont()->setSize(10)->setColor($oColorTX1); + $this->otherStyle[0] = $oRTParagraphOther; + + $oRTParagraphTitle = new RichTextParagraph(); + $oRTParagraphTitle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); + $oRTParagraphTitle->getFont()->setSize(44)->setColor($oColorLT1); + $this->titleStyle[1] = $oRTParagraphTitle; + } + } + + /** + * @param $lvl + * @return bool + */ + private function checkLvl($lvl) + { + if (!is_int($lvl)) { + return false; + } + if ($lvl > 9) { + return false; + } + return true; + } + + /** + * @param RichTextParagraph $style + * @param $lvl + * @return TextStyle + */ + public function setBodyStyleAtLvl(RichTextParagraph $style, $lvl) + { + if ($this->checkLvl($lvl)) { + $this->bodyStyle[$lvl] = $style; + } + return $this; + } + + /** + * @param RichTextParagraph $style + * @param $lvl + * @return TextStyle + */ + public function setTitleStyleAtLvl(RichTextParagraph $style, $lvl) + { + if ($this->checkLvl($lvl)) { + $this->titleStyle[$lvl] = $style; + } + return $this; + } + + /** + * @param RichTextParagraph $style + * @param $lvl + * @return TextStyle + */ + public function setOtherStyleAtLvl(RichTextParagraph $style, $lvl) + { + if ($this->checkLvl($lvl)) { + $this->otherStyle[$lvl] = $style; + } + return $this; + } + + /** + * @param $lvl + * @return mixed + */ + public function getBodyStyleAtLvl($lvl) + { + if ($this->checkLvl($lvl) && !empty($this->bodyStyle[$lvl])) { + return $this->bodyStyle[$lvl]; + } + return null; + } + + /** + * @param $lvl + * @return mixed + */ + public function getTitleStyleAtLvl($lvl) + { + if ($this->checkLvl($lvl) && !empty($this->titleStyle[$lvl])) { + return $this->titleStyle[$lvl]; + } + return null; + } + + /** + * @param $lvl + * @return mixed + */ + public function getOtherStyleAtLvl($lvl) + { + if ($this->checkLvl($lvl) && !empty($this->otherStyle[$lvl])) { + return $this->otherStyle[$lvl]; + } + return null; + } + + /** + * @return array + */ + public function getBodyStyle() + { + return $this->bodyStyle; + } + + /** + * @return array + */ + public function getTitleStyle() + { + return $this->titleStyle; + } + + /** + * @return array + */ + public function getOtherStyle() + { + return $this->otherStyle; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php b/PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php new file mode 100644 index 0000000..1d17404 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php @@ -0,0 +1,84 @@ +oHashTable = $hashTable; + return $this; + } + + /** + * @return HashTable + */ + public function getDrawingHashTable() + { + return $this->oHashTable; + } + + /** + * @param PhpPresentation $oPresentation + * @return $this + */ + public function setPresentation(PhpPresentation $oPresentation) + { + $this->oPresentation = $oPresentation; + return $this; + } + + /** + * @return PhpPresentation + */ + public function getPresentation() + { + return $this->oPresentation; + } + + /** + * @param ZipInterface $oZip + * @return $this + */ + public function setZip(ZipInterface $oZip) + { + $this->oZip = $oZip; + return $this; + } + + /** + * @return ZipInterface + */ + public function getZip() + { + return $this->oZip; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/AbstractWriter.php b/PhpOffice/PhpPresentation/Writer/AbstractWriter.php new file mode 100644 index 0000000..b17dcc1 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/AbstractWriter.php @@ -0,0 +1,142 @@ +oDrawingHashTable; + } + + /** + * Get PhpPresentation object + * + * @return PhpPresentation + * @throws \Exception + */ + public function getPhpPresentation() + { + if (empty($this->oPresentation)) { + throw new \Exception("No PhpPresentation assigned."); + } + return $this->oPresentation; + } + + /** + * Get PhpPresentation object + * + * @param PhpPresentation $pPhpPresentation PhpPresentation object + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Writer\AbstractWriter + */ + public function setPhpPresentation(PhpPresentation $pPhpPresentation = null) + { + $this->oPresentation = $pPhpPresentation; + return $this; + } + + + /** + * @param ZipInterface $oZipAdapter + * @return $this + */ + public function setZipAdapter(ZipInterface $oZipAdapter) + { + $this->oZipAdapter = $oZipAdapter; + return $this; + } + + /** + * @return ZipInterface + */ + public function getZipAdapter() + { + return $this->oZipAdapter; + } + + /** + * Get an array of all drawings + * + * @return \PhpOffice\PhpPresentation\Shape\AbstractDrawing[] All drawings in PhpPresentation + * @throws \Exception + */ + protected function allDrawings() + { + // Get an array of all drawings + $aDrawings = array(); + + // Get an array of all master slides + $aSlideMasters = $this->getPhpPresentation()->getAllMasterSlides(); + + $aSlideMasterLayouts = array_map(function ($oSlideMaster) { + return $oSlideMaster->getAllSlideLayouts(); + }, $aSlideMasters); + + // Get an array of all slide layouts + $aSlideLayouts = array(); + array_walk_recursive($aSlideMasterLayouts, function ($oSlideLayout) use (&$aSlideLayouts) { + $aSlideLayouts[] = $oSlideLayout; + }); + + // Loop through PhpPresentation + foreach (array_merge($this->getPhpPresentation()->getAllSlides(), $aSlideMasters, $aSlideLayouts) as $oSlide) { + $arrayReturn = $this->iterateCollection($oSlide->getShapeCollection()->getIterator()); + $aDrawings = array_merge($aDrawings, $arrayReturn); + } + + return $aDrawings; + } + + private function iterateCollection(\ArrayIterator $oIterator) + { + $arrayReturn = array(); + if ($oIterator->count() <= 0) { + return $arrayReturn; + } + + while ($oIterator->valid()) { + $oShape = $oIterator->current(); + if ($oShape instanceof AbstractDrawingAdapter) { + $arrayReturn[] = $oShape; + } elseif ($oShape instanceof Chart) { + $arrayReturn[] = $oShape; + } elseif ($oShape instanceof Group) { + $arrayGroup = $this->iterateCollection($oShape->getShapeCollection()->getIterator()); + $arrayReturn = array_merge($arrayReturn, $arrayGroup); + } + $oIterator->next(); + } + return $arrayReturn; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation.php b/PhpOffice/PhpPresentation/Writer/ODPresentation.php new file mode 100644 index 0000000..027afdf --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation.php @@ -0,0 +1,186 @@ +setPhpPresentation($pPhpPresentation); + + // Set up disk caching location + $this->diskCachingDirectory = './'; + + // Set HashTable variables + $this->oDrawingHashTable = new HashTable(); + + $this->setZipAdapter(new ZipArchiveAdapter()); + } + + /** + * Save PhpPresentation to file + * + * @param string $pFilename + * @throws \Exception + */ + public function save($pFilename) + { + if (empty($pFilename)) { + throw new \Exception("Filename is empty"); + } + // If $pFilename is php://output or php://stdout, make it a temporary file... + $originalFilename = $pFilename; + if (strtolower($pFilename) == 'php://output' || strtolower($pFilename) == 'php://stdout') { + $pFilename = @tempnam('./', 'phppttmp'); + if ($pFilename == '') { + $pFilename = $originalFilename; + } + } + + // Initialize HashTable + $this->getDrawingHashTable()->addFromSource($this->allDrawings()); + + // Initialize Zip + $oZip = $this->getZipAdapter(); + $oZip->open($pFilename); + + // Variables + $oPresentation = $this->getPhpPresentation(); + $arrayChart = array(); + + $arrayFiles = array(); + $oDir = new DirectoryIterator(dirname(__FILE__).DIRECTORY_SEPARATOR.'ODPresentation'); + foreach ($oDir as $oFile) { + if (!$oFile->isFile()) { + continue; + } + + $class = __NAMESPACE__ . '\\ODPresentation\\' . $oFile->getBasename('.php'); + $o = new \ReflectionClass($class); + + if ($o->isAbstract() || !$o->isSubclassOf('PhpOffice\PhpPresentation\Writer\ODPresentation\AbstractDecoratorWriter')) { + continue; + } + $arrayFiles[$oFile->getBasename('.php')] = $o; + } + + ksort($arrayFiles); + + foreach ($arrayFiles as $o) { + $oService = $o->newInstance(); + $oService->setZip($oZip); + $oService->setPresentation($oPresentation); + $oService->setDrawingHashTable($this->getDrawingHashTable()); + $oService->setArrayChart($arrayChart); + $oZip = $oService->render(); + $arrayChart = $oService->getArrayChart(); + unset($oService); + } + + // Close file + $oZip->close(); + + // If a temporary file was used, copy it to the correct file stream + if ($originalFilename != $pFilename) { + if (copy($pFilename, $originalFilename) === false) { + throw new \Exception("Could not copy temporary zip file $pFilename to $originalFilename."); + } + if (@unlink($pFilename) === false) { + throw new \Exception('The file ' . $pFilename . ' could not be removed.'); + } + } + } + + /** + * Get use disk caching where possible? + * + * @return boolean + */ + public function hasDiskCaching() + { + return $this->useDiskCaching; + } + + /** + * Set use disk caching where possible? + * + * @param boolean $pValue + * @param string $pDirectory Disk caching directory + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Writer\ODPresentation + */ + public function setUseDiskCaching($pValue = false, $pDirectory = null) + { + $this->useDiskCaching = $pValue; + + if (!is_null($pDirectory)) { + if (!is_dir($pDirectory)) { + throw new \Exception("Directory does not exist: $pDirectory"); + } + $this->diskCachingDirectory = $pDirectory; + } + + return $this; + } + + /** + * Get disk caching directory + * + * @return string + */ + public function getDiskCachingDirectory() + { + return $this->diskCachingDirectory; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php new file mode 100644 index 0000000..c5e390e --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php @@ -0,0 +1,31 @@ +arrayChart; + } + + /** + * @param \PhpOffice\PhpPresentation\Shape\Chart[] $arrayChart + * @return AbstractDecoratorWriter + */ + public function setArrayChart($arrayChart) + { + $this->arrayChart = $arrayChart; + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php new file mode 100644 index 0000000..cb399ab --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php @@ -0,0 +1,1388 @@ +getZip()->addFromString('content.xml', $this->writeContent()); + return $this->getZip(); + } + + + + /** + * Write content file to XML format + * + * @return string XML Output + * @throws \Exception + */ + public function writeContent() + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $objWriter->startDocument('1.0', 'UTF-8'); + + // office:document-content + $objWriter->startElement('office:document-content'); + $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'); + $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'); + $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'); + $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'); + $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'); + $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'); + $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0'); + $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'); + $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'); + $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'); + $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML'); + $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'); + $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'); + $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer'); + $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc'); + $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events'); + $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms'); + $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'); + $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $objWriter->writeAttribute('xmlns:smil', 'urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0'); + $objWriter->writeAttribute('xmlns:anim', 'urn:oasis:names:tc:opendocument:xmlns:animation:1.0'); + $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report'); + $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2'); + $objWriter->writeAttribute('xmlns:rdfa', 'http://docs.oasis-open.org/opendocument/meta/rdfa#'); + $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0'); + $objWriter->writeAttribute('xmlns:officeooo', 'http://openoffice.org/2009/office'); + $objWriter->writeAttribute('office:version', '1.2'); + + // office:automatic-styles + $objWriter->startElement('office:automatic-styles'); + + $this->shapeId = 0; + $incSlide = 0; + foreach ($this->getPresentation()->getAllSlides() as $pSlide) { + // Slides + $this->writeStyleSlide($objWriter, $pSlide, $incSlide); + + // Images + $shapes = $pSlide->getShapeCollection(); + foreach ($shapes as $shape) { + // Increment $this->shapeId + ++$this->shapeId; + + // Check type + if ($shape instanceof RichText) { + $this->writeTxtStyle($objWriter, $shape); + } + if ($shape instanceof AbstractDrawingAdapter) { + $this->writeDrawingStyle($objWriter, $shape); + } + if ($shape instanceof Line) { + $this->writeLineStyle($objWriter, $shape); + } + if ($shape instanceof Table) { + $this->writeTableStyle($objWriter, $shape); + } + if ($shape instanceof Group) { + $this->writeGroupStyle($objWriter, $shape); + } + } + + $incSlide++; + } + // Style : Bullet + if (!empty($this->arrStyleBullet)) { + foreach ($this->arrStyleBullet as $key => $item) { + $oStyle = $item['oStyle']; + $arrLevel = explode(';', $item['level']); + // style:style + $objWriter->startElement('text:list-style'); + $objWriter->writeAttribute('style:name', 'L_' . $key); + foreach ($arrLevel as $level) { + if ($level != '') { + $oAlign = $item['oAlign_' . $level]; + // text:list-level-style-bullet + $objWriter->startElement('text:list-level-style-bullet'); + $objWriter->writeAttribute('text:level', $level + 1); + $objWriter->writeAttribute('text:bullet-char', $oStyle->getBulletChar()); + // style:list-level-properties + $objWriter->startElement('style:list-level-properties'); + if ($oAlign->getIndent() < 0) { + $objWriter->writeAttribute('text:space-before', CommonDrawing::pixelsToCentimeters($oAlign->getMarginLeft() - (-1 * $oAlign->getIndent())) . 'cm'); + $objWriter->writeAttribute('text:min-label-width', CommonDrawing::pixelsToCentimeters(-1 * $oAlign->getIndent()) . 'cm'); + } else { + $objWriter->writeAttribute('text:space-before', (CommonDrawing::pixelsToCentimeters($oAlign->getMarginLeft() - $oAlign->getIndent())) . 'cm'); + $objWriter->writeAttribute('text:min-label-width', CommonDrawing::pixelsToCentimeters($oAlign->getIndent()) . 'cm'); + } + + $objWriter->endElement(); + // style:text-properties + $objWriter->startElement('style:text-properties'); + $objWriter->writeAttribute('fo:font-family', $oStyle->getBulletFont()); + $objWriter->writeAttribute('style:font-family-generic', 'swiss'); + $objWriter->writeAttribute('style:use-window-font-color', 'true'); + $objWriter->writeAttribute('fo:font-size', '100%'); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + $objWriter->endElement(); + } + } + // Style : Paragraph + if (!empty($this->arrStyleParagraph)) { + foreach ($this->arrStyleParagraph as $key => $item) { + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', 'P_' . $key); + $objWriter->writeAttribute('style:family', 'paragraph'); + // style:paragraph-properties + $objWriter->startElement('style:paragraph-properties'); + switch ($item->getAlignment()->getHorizontal()) { + case Alignment::HORIZONTAL_LEFT: + $objWriter->writeAttribute('fo:text-align', 'left'); + break; + case Alignment::HORIZONTAL_RIGHT: + $objWriter->writeAttribute('fo:text-align', 'right'); + break; + case Alignment::HORIZONTAL_CENTER: + $objWriter->writeAttribute('fo:text-align', 'center'); + break; + case Alignment::HORIZONTAL_JUSTIFY: + $objWriter->writeAttribute('fo:text-align', 'justify'); + break; + case Alignment::HORIZONTAL_DISTRIBUTED: + $objWriter->writeAttribute('fo:text-align', 'justify'); + break; + default: + $objWriter->writeAttribute('fo:text-align', 'left'); + break; + } + $objWriter->endElement(); + $objWriter->endElement(); + } + } + // Style : Text : Font + if (!empty($this->arrStyleTextFont)) { + foreach ($this->arrStyleTextFont as $key => $item) { + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', 'T_' . $key); + $objWriter->writeAttribute('style:family', 'text'); + // style:text-properties + $objWriter->startElement('style:text-properties'); + $objWriter->writeAttribute('fo:color', '#' . $item->getFont()->getColor()->getRGB()); + $objWriter->writeAttribute('fo:font-family', $item->getFont()->getName()); + $objWriter->writeAttribute('fo:font-size', $item->getFont()->getSize() . 'pt'); + // @todo : fo:font-style + if ($item->getFont()->isBold()) { + $objWriter->writeAttribute('fo:font-weight', 'bold'); + } + $objWriter->writeAttribute('fo:language', ($item->getLanguage() ? $item->getLanguage() : 'en')); + + // @todo : style:text-underline-style + $objWriter->endElement(); + $objWriter->endElement(); + } + } + $objWriter->endElement(); + + //=============================================== + // Body + //=============================================== + // office:body + $objWriter->startElement('office:body'); + // office:presentation + $objWriter->startElement('office:presentation'); + + // Write slides + $slideCount = $this->getPresentation()->getSlideCount(); + $this->shapeId = 0; + for ($i = 0; $i < $slideCount; ++$i) { + $pSlide = $this->getPresentation()->getSlide($i); + $objWriter->startElement('draw:page'); + $name = $pSlide->getName(); + if (!is_null($name)) { + $objWriter->writeAttribute('draw:name', $name); + } + $objWriter->writeAttribute('draw:master-page-name', 'Standard'); + $objWriter->writeAttribute('draw:style-name', 'stylePage' . $i); + // Images + $shapes = $pSlide->getShapeCollection(); + foreach ($shapes as $shape) { + // Increment $this->shapeId + ++$this->shapeId; + + // Check type + if ($shape instanceof RichText) { + $this->writeShapeTxt($objWriter, $shape); + } elseif ($shape instanceof Table) { + $this->writeShapeTable($objWriter, $shape); + } elseif ($shape instanceof Line) { + $this->writeShapeLine($objWriter, $shape); + } elseif ($shape instanceof Chart) { + $this->writeShapeChart($objWriter, $shape); + } elseif ($shape instanceof Media) { + $this->writeShapeMedia($objWriter, $shape); + } elseif ($shape instanceof AbstractDrawingAdapter) { + $this->writeShapeDrawing($objWriter, $shape); + } elseif ($shape instanceof Group) { + $this->writeShapeGroup($objWriter, $shape); + } elseif ($shape instanceof Comment) { + $this->writeShapeComment($objWriter, $shape); + } + } + // Slide Note + if ($pSlide->getNote() instanceof Note) { + $this->writeSlideNote($objWriter, $pSlide->getNote()); + } + + $objWriter->endElement(); + } + + if ($this->getPresentation()->getPresentationProperties()->isLoopContinuouslyUntilEsc()) { + $objWriter->startElement('presentation:settings'); + $objWriter->writeAttribute('presentation:endless', 'true'); + $objWriter->writeAttribute('presentation:pause', 'P0s'); + $objWriter->writeAttribute('presentation:mouse-visible', 'false'); + $objWriter->endElement(); + } + // > office:presentation + $objWriter->endElement(); + // > office:body + $objWriter->endElement(); + // > office:document-content + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write picture + * + * @param \PhpOffice\Common\XMLWriter $objWriter + * @param \PhpOffice\PhpPresentation\Shape\Media $shape + */ + public function writeShapeMedia(XMLWriter $objWriter, Media $shape) + { + // draw:frame + $objWriter->startElement('draw:frame'); + $objWriter->writeAttribute('draw:name', $shape->getName()); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId); + // draw:frame > draw:plugin + $objWriter->startElement('draw:plugin'); + $objWriter->writeAttribute('xlink:href', 'Pictures/' . $shape->getIndexedFilename()); + $objWriter->writeAttribute('xlink:type', 'simple'); + $objWriter->writeAttribute('xlink:show', 'embed'); + $objWriter->writeAttribute('xlink:actuate', 'onLoad'); + $objWriter->writeAttribute('draw:mime-type', 'application/vnd.sun.star.media'); + + $objWriter->startElement('draw:param'); + $objWriter->writeAttribute('draw:name', 'Loop'); + $objWriter->writeAttribute('draw:value', 'false'); + $objWriter->endElement(); + $objWriter->startElement('draw:param'); + $objWriter->writeAttribute('draw:name', 'Mute'); + $objWriter->writeAttribute('draw:value', 'false'); + $objWriter->endElement(); + $objWriter->startElement('draw:param'); + $objWriter->writeAttribute('draw:name', 'VolumeDB'); + $objWriter->writeAttribute('draw:value', 0); + $objWriter->endElement(); + $objWriter->startElement('draw:param'); + $objWriter->writeAttribute('draw:name', 'Zoom'); + $objWriter->writeAttribute('draw:value', 'fit'); + $objWriter->endElement(); + + // draw:frame > ## draw:plugin + $objWriter->endElement(); + + // ## draw:frame + $objWriter->endElement(); + } + + /** + * Write picture + * + * @param \PhpOffice\Common\XMLWriter $objWriter + * @param AbstractDrawingAdapter $shape + * @throws \Exception + */ + public function writeShapeDrawing(XMLWriter $objWriter, ShapeDrawing\AbstractDrawingAdapter $shape) + { + // draw:frame + $objWriter->startElement('draw:frame'); + $objWriter->writeAttribute('draw:name', $shape->getName()); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId); + // draw:image + $objWriter->startElement('draw:image'); + if ($shape instanceof AbstractDrawingAdapter) { + $objWriter->writeAttribute('xlink:href', 'Pictures/' . $shape->getIndexedFilename()); + } + $objWriter->writeAttribute('xlink:type', 'simple'); + $objWriter->writeAttribute('xlink:show', 'embed'); + $objWriter->writeAttribute('xlink:actuate', 'onLoad'); + $objWriter->writeElement('text:p'); + $objWriter->endElement(); + + if ($shape->hasHyperlink()) { + // office:event-listeners + $objWriter->startElement('office:event-listeners'); + // presentation:event-listener + $objWriter->startElement('presentation:event-listener'); + $objWriter->writeAttribute('script:event-name', 'dom:click'); + $objWriter->writeAttribute('presentation:action', 'show'); + $objWriter->writeAttribute('xlink:href', $shape->getHyperlink()->getUrl()); + $objWriter->writeAttribute('xlink:type', 'simple'); + $objWriter->writeAttribute('xlink:show', 'embed'); + $objWriter->writeAttribute('xlink:actuate', 'onRequest'); + // > presentation:event-listener + $objWriter->endElement(); + // > office:event-listeners + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + /** + * Write text + * + * @param \PhpOffice\Common\XMLWriter $objWriter + * @param \PhpOffice\PhpPresentation\Shape\RichText $shape + * @throws \Exception + */ + public function writeShapeTxt(XMLWriter $objWriter, RichText $shape) + { + // draw:frame + $objWriter->startElement('draw:frame'); + $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + // draw:text-box + $objWriter->startElement('draw:text-box'); + + $paragraphs = $shape->getParagraphs(); + $paragraphId = 0; + $sCstShpLastBullet = ''; + $iCstShpLastBulletLvl = 0; + $bCstShpHasBullet = false; + + foreach ($paragraphs as $paragraph) { + // Close the bullet list + if ($sCstShpLastBullet != 'bullet' && $bCstShpHasBullet === true) { + for ($iInc = $iCstShpLastBulletLvl; $iInc >= 0; $iInc--) { + // text:list-item + $objWriter->endElement(); + // text:list + $objWriter->endElement(); + } + } + //=============================================== + // Paragraph + //=============================================== + if ($paragraph->getBulletStyle()->getBulletType() == 'none') { + ++$paragraphId; + // text:p + $objWriter->startElement('text:p'); + $objWriter->writeAttribute('text:style-name', 'P_' . $paragraph->getHashCode()); + + // Loop trough rich text elements + $richtexts = $paragraph->getRichTextElements(); + $richtextId = 0; + foreach ($richtexts as $richtext) { + ++$richtextId; + if ($richtext instanceof TextElement || $richtext instanceof Run) { + // text:span + $objWriter->startElement('text:span'); + if ($richtext instanceof Run) { + $objWriter->writeAttribute('text:style-name', 'T_' . $richtext->getHashCode()); + } + if ($richtext->hasHyperlink() === true && $richtext->getHyperlink()->getUrl() != '') { + // text:a + $objWriter->startElement('text:a'); + $objWriter->writeAttribute('xlink:type', 'simple'); + $objWriter->writeAttribute('xlink:href', $richtext->getHyperlink()->getUrl()); + $objWriter->text($richtext->getText()); + $objWriter->endElement(); + } else { + $objWriter->text($richtext->getText()); + } + $objWriter->endElement(); + } elseif ($richtext instanceof BreakElement) { + // text:span + $objWriter->startElement('text:span'); + // text:line-break + $objWriter->startElement('text:line-break'); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + $objWriter->endElement(); + //=============================================== + // Bullet list + //=============================================== + } elseif ($paragraph->getBulletStyle()->getBulletType() == 'bullet') { + $bCstShpHasBullet = true; + // Open the bullet list + if ($sCstShpLastBullet != 'bullet' || ($sCstShpLastBullet == $paragraph->getBulletStyle()->getBulletType() && $iCstShpLastBulletLvl < $paragraph->getAlignment()->getLevel())) { + // text:list + $objWriter->startElement('text:list'); + $objWriter->writeAttribute('text:style-name', 'L_' . $paragraph->getBulletStyle()->getHashCode()); + } + if ($sCstShpLastBullet == 'bullet') { + if ($iCstShpLastBulletLvl == $paragraph->getAlignment()->getLevel()) { + // text:list-item + $objWriter->endElement(); + } elseif ($iCstShpLastBulletLvl > $paragraph->getAlignment()->getLevel()) { + // text:list-item + $objWriter->endElement(); + // text:list + $objWriter->endElement(); + // text:list-item + $objWriter->endElement(); + } + } + + // text:list-item + $objWriter->startElement('text:list-item'); + ++$paragraphId; + // text:p + $objWriter->startElement('text:p'); + $objWriter->writeAttribute('text:style-name', 'P_' . $paragraph->getHashCode()); + + // Loop trough rich text elements + $richtexts = $paragraph->getRichTextElements(); + $richtextId = 0; + foreach ($richtexts as $richtext) { + ++$richtextId; + if ($richtext instanceof TextElement || $richtext instanceof Run) { + // text:span + $objWriter->startElement('text:span'); + if ($richtext instanceof Run) { + $objWriter->writeAttribute('text:style-name', 'T_' . $richtext->getHashCode()); + } + if ($richtext->hasHyperlink() === true && $richtext->getHyperlink()->getUrl() != '') { + // text:a + $objWriter->startElement('text:a'); + $objWriter->writeAttribute('xlink:type', 'simple'); + $objWriter->writeAttribute('xlink:href', $richtext->getHyperlink()->getUrl()); + $objWriter->text($richtext->getText()); + $objWriter->endElement(); + } else { + $objWriter->text($richtext->getText()); + } + $objWriter->endElement(); + } elseif ($richtext instanceof BreakElement) { + // text:span + $objWriter->startElement('text:span'); + // text:line-break + $objWriter->startElement('text:line-break'); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + $objWriter->endElement(); + } + $sCstShpLastBullet = $paragraph->getBulletStyle()->getBulletType(); + $iCstShpLastBulletLvl = $paragraph->getAlignment()->getLevel(); + } + + // Close the bullet list + if ($sCstShpLastBullet == 'bullet' && $bCstShpHasBullet === true) { + for ($iInc = $iCstShpLastBulletLvl; $iInc >= 0; $iInc--) { + // text:list-item + $objWriter->endElement(); + // text:list + $objWriter->endElement(); + } + } + + // > draw:text-box + $objWriter->endElement(); + // > draw:frame + $objWriter->endElement(); + } + /** + * Write Comment + * @param XMLWriter $objWriter + * @param Comment $oShape + */ + public function writeShapeComment(XMLWriter $objWriter, Comment $oShape) + { + /** + * Note : This element is not valid in the Schema 1.2 + */ + // officeooo:annotation + $objWriter->startElement('officeooo:annotation'); + $objWriter->writeAttribute('svg:x', number_format(CommonDrawing::pixelsToCentimeters($oShape->getOffsetX()), 2, '.', '').'cm'); + $objWriter->writeAttribute('svg:y', number_format(CommonDrawing::pixelsToCentimeters($oShape->getOffsetY()), 2, '.', '').'cm'); + + if ($oShape->getAuthor() instanceof Comment\Author) { + $objWriter->writeElement('dc:creator', $oShape->getAuthor()->getName()); + } + $objWriter->writeElement('dc:date', date('Y-m-d\TH:i:s', $oShape->getDate())); + $objWriter->writeElement('text:p', $oShape->getText()); + + // ## officeooo:annotation + $objWriter->endElement(); + } + + /** + * @param XMLWriter $objWriter + * @param Line $shape + */ + public function writeShapeLine(XMLWriter $objWriter, Line $shape) + { + // draw:line + $objWriter->startElement('draw:line'); + $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId); + $objWriter->writeAttribute('svg:x1', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y1', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x2', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()+$shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y2', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()+$shape->getHeight()), 3) . 'cm'); + + // text:p + $objWriter->writeElement('text:p'); + + $objWriter->endElement(); + } + + /** + * Write table Shape + * @param XMLWriter $objWriter + * @param Table $shape + * @throws \Exception + */ + public function writeShapeTable(XMLWriter $objWriter, Table $shape) + { + // draw:frame + $objWriter->startElement('draw:frame'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); + + $arrayRows = $shape->getRows(); + if (!empty($arrayRows)) { + $firstRow = reset($arrayRows); + $arrayCells = $firstRow->getCells(); + // table:table + $objWriter->startElement('table:table'); + foreach ($arrayCells as $shapeCell) { + $objWriter->startElement('table:table-column'); + $objWriter->endElement(); + } + foreach ($arrayRows as $keyRow => $shapeRow) { + // table:table-row + $objWriter->startElement('table:table-row'); + $objWriter->writeAttribute('table:style-name', 'gr'.$this->shapeId.'r'.$keyRow); + //@todo getFill + + $numColspan = 0; + foreach ($shapeRow->getCells() as $keyCell => $shapeCell) { + if ($numColspan == 0) { + // table:table-cell + $objWriter->startElement('table:table-cell'); + $objWriter->writeAttribute('table:style-name', 'gr' . $this->shapeId.'r'.$keyRow.'c'.$keyCell); + if ($shapeCell->getColspan() > 1) { + $objWriter->writeAttribute('table:number-columns-spanned', $shapeCell->getColspan()); + $numColspan = $shapeCell->getColspan() - 1; + } + + // text:p + $objWriter->startElement('text:p'); + + // text:span + foreach ($shapeCell->getParagraphs() as $shapeParagraph) { + foreach ($shapeParagraph->getRichTextElements() as $shapeRichText) { + if ($shapeRichText instanceof TextElement) { + // text:span + $objWriter->startElement('text:span'); + if ($shapeRichText instanceof Run) { + $objWriter->writeAttribute('text:style-name', 'T_' . $shapeRichText->getHashCode()); + } + if ($shapeRichText->hasHyperlink() === true && $shapeRichText->getHyperlink()->getUrl() !== '') { + // text:a + $objWriter->startElement('text:a'); + $objWriter->writeAttribute('xlink:type', 'simple'); + $objWriter->writeAttribute('xlink:href', $shapeRichText->getHyperlink()->getUrl()); + $objWriter->text($shapeRichText->getText()); + $objWriter->endElement(); + } else { + $objWriter->text($shapeRichText->getText()); + } + $objWriter->endElement(); + } elseif ($shapeRichText instanceof BreakElement) { + // text:span + $objWriter->startElement('text:span'); + // text:line-break + $objWriter->startElement('text:line-break'); + $objWriter->endElement(); + $objWriter->endElement(); + } + } + } + + // > text:p + $objWriter->endElement(); + + // > table:table-cell + $objWriter->endElement(); + } else { + // table:covered-table-cell + $objWriter->writeElement('table:covered-table-cell'); + $numColspan--; + } + } + // > table:table-row + $objWriter->endElement(); + } + // > table:table + $objWriter->endElement(); + } + // > draw:frame + $objWriter->endElement(); + } + + /** + * Write table Chart + * @param XMLWriter $objWriter + * @param Chart $shape + * @throws \Exception + */ + public function writeShapeChart(XMLWriter $objWriter, Chart $shape) + { + $arrayChart = $this->getArrayChart(); + $arrayChart[$this->shapeId] = $shape; + $this->setArrayChart($arrayChart); + + // draw:frame + $objWriter->startElement('draw:frame'); + $objWriter->writeAttribute('draw:name', $shape->getTitle()->getText()); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); + + // draw:object + $objWriter->startElement('draw:object'); + $objWriter->writeAttribute('xlink:href', './Object '.$this->shapeId); + $objWriter->writeAttribute('xlink:type', 'simple'); + $objWriter->writeAttribute('xlink:show', 'embed'); + + // > draw:object + $objWriter->endElement(); + // > draw:frame + $objWriter->endElement(); + } + + /** + * Writes a group of shapes + * + * @param XMLWriter $objWriter + * @param Group $group + * @throws \Exception + */ + public function writeShapeGroup(XMLWriter $objWriter, Group $group) + { + // draw:g + $objWriter->startElement('draw:g'); + + $shapes = $group->getShapeCollection(); + foreach ($shapes as $shape) { + // Increment $this->shapeId + ++$this->shapeId; + + // Check type + if ($shape instanceof RichText) { + $this->writeShapeTxt($objWriter, $shape); + } elseif ($shape instanceof Table) { + $this->writeShapeTable($objWriter, $shape); + } elseif ($shape instanceof Line) { + $this->writeShapeLine($objWriter, $shape); + } elseif ($shape instanceof Chart) { + $this->writeShapeChart($objWriter, $shape); + } elseif ($shape instanceof AbstractDrawingAdapter) { + $this->writeShapeDrawing($objWriter, $shape); + } elseif ($shape instanceof Group) { + $this->writeShapeGroup($objWriter, $shape); + } + } + + $objWriter->endElement(); // draw:g + } + + /** + * Writes the style information for a group of shapes + * + * @param XMLWriter $objWriter + * @param Group $group + */ + public function writeGroupStyle(XMLWriter $objWriter, Group $group) + { + $shapes = $group->getShapeCollection(); + foreach ($shapes as $shape) { + // Increment $this->shapeId + ++$this->shapeId; + + // Check type + if ($shape instanceof RichText) { + $this->writeTxtStyle($objWriter, $shape); + } + if ($shape instanceof AbstractDrawingAdapter) { + $this->writeDrawingStyle($objWriter, $shape); + } + if ($shape instanceof Line) { + $this->writeLineStyle($objWriter, $shape); + } + if ($shape instanceof Table) { + $this->writeTableStyle($objWriter, $shape); + } + } + } + + /** + * Write the default style information for a RichText shape + * + * @param \PhpOffice\Common\XMLWriter $objWriter + * @param \PhpOffice\PhpPresentation\Shape\RichText $shape + */ + public function writeTxtStyle(XMLWriter $objWriter, RichText $shape) + { + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId); + $objWriter->writeAttribute('style:family', 'graphic'); + $objWriter->writeAttribute('style:parent-style-name', 'standard'); + // style:graphic-properties + $objWriter->startElement('style:graphic-properties'); + $objWriter->writeAttribute('style:mirror', 'none'); + $this->writeStylePartShadow($objWriter, $shape->getShadow()); + if (is_bool($shape->hasAutoShrinkVertical())) { + $objWriter->writeAttribute('draw:auto-grow-height', var_export($shape->hasAutoShrinkVertical(), true)); + } + if (is_bool($shape->hasAutoShrinkHorizontal())) { + $objWriter->writeAttribute('draw:auto-grow-width', var_export($shape->hasAutoShrinkHorizontal(), true)); + } + // Fill + switch ($shape->getFill()->getFillType()) { + case Fill::FILL_GRADIENT_LINEAR: + case Fill::FILL_GRADIENT_PATH: + $objWriter->writeAttribute('draw:fill', 'gradient'); + $objWriter->writeAttribute('draw:fill-gradient-name', 'gradient_'.$shape->getFill()->getHashCode()); + break; + case Fill::FILL_SOLID: + $objWriter->writeAttribute('draw:fill', 'solid'); + $objWriter->writeAttribute('draw:fill-color', '#'.$shape->getFill()->getStartColor()->getRGB()); + break; + case Fill::FILL_NONE: + default: + $objWriter->writeAttribute('draw:fill', 'none'); + $objWriter->writeAttribute('draw:fill-color', '#'.$shape->getFill()->getStartColor()->getRGB()); + break; + } + // Border + if ($shape->getBorder()->getLineStyle() == Border::LINE_NONE) { + $objWriter->writeAttribute('draw:stroke', 'none'); + } else { + $objWriter->writeAttribute('svg:stroke-color', '#'.$shape->getBorder()->getColor()->getRGB()); + $objWriter->writeAttribute('svg:stroke-width', number_format(CommonDrawing::pointsToCentimeters($shape->getBorder()->getLineWidth()), 3, '.', '').'cm'); + switch ($shape->getBorder()->getDashStyle()) { + case Border::DASH_SOLID: + $objWriter->writeAttribute('draw:stroke', 'solid'); + break; + case Border::DASH_DASH: + case Border::DASH_DASHDOT: + case Border::DASH_DOT: + case Border::DASH_LARGEDASH: + case Border::DASH_LARGEDASHDOT: + case Border::DASH_LARGEDASHDOTDOT: + case Border::DASH_SYSDASH: + case Border::DASH_SYSDASHDOT: + case Border::DASH_SYSDASHDOTDOT: + case Border::DASH_SYSDOT: + $objWriter->writeAttribute('draw:stroke', 'dash'); + $objWriter->writeAttribute('draw:stroke-dash', 'strokeDash_'.$shape->getBorder()->getDashStyle()); + break; + default: + $objWriter->writeAttribute('draw:stroke', 'none'); + break; + } + } + + $objWriter->writeAttribute('fo:wrap-option', 'wrap'); + // > style:graphic-properties + $objWriter->endElement(); + // > style:style + $objWriter->endElement(); + + $paragraphs = $shape->getParagraphs(); + $paragraphId = 0; + foreach ($paragraphs as $paragraph) { + ++$paragraphId; + + // Style des paragraphes + if (!isset($this->arrStyleParagraph[$paragraph->getHashCode()])) { + $this->arrStyleParagraph[$paragraph->getHashCode()] = $paragraph; + } + + // Style des listes + $bulletStyleHashCode = $paragraph->getBulletStyle()->getHashCode(); + if (!isset($this->arrStyleBullet[$bulletStyleHashCode])) { + $this->arrStyleBullet[$bulletStyleHashCode]['oStyle'] = $paragraph->getBulletStyle(); + $this->arrStyleBullet[$bulletStyleHashCode]['level'] = ''; + } + if (strpos($this->arrStyleBullet[$bulletStyleHashCode]['level'], ';' . $paragraph->getAlignment()->getLevel()) === false) { + $this->arrStyleBullet[$bulletStyleHashCode]['level'] .= ';' . $paragraph->getAlignment()->getLevel(); + $this->arrStyleBullet[$bulletStyleHashCode]['oAlign_' . $paragraph->getAlignment()->getLevel()] = $paragraph->getAlignment(); + } + + $richtexts = $paragraph->getRichTextElements(); + $richtextId = 0; + foreach ($richtexts as $richtext) { + ++$richtextId; + // Not a line break + if ($richtext instanceof Run) { + // Style des font text + if (!isset($this->arrStyleTextFont[$richtext->getHashCode()])) { + $this->arrStyleTextFont[$richtext->getHashCode()] = $richtext; + } + } + } + } + } + + /** + * Write the default style information for an AbstractDrawingAdapter + * + * @param \PhpOffice\Common\XMLWriter $objWriter + * @param AbstractDrawingAdapter $shape + */ + public function writeDrawingStyle(XMLWriter $objWriter, AbstractDrawingAdapter $shape) + { + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId); + $objWriter->writeAttribute('style:family', 'graphic'); + $objWriter->writeAttribute('style:parent-style-name', 'standard'); + + // style:graphic-properties + $objWriter->startElement('style:graphic-properties'); + $objWriter->writeAttribute('draw:stroke', 'none'); + $objWriter->writeAttribute('style:mirror', 'none'); + $this->writeStylePartFill($objWriter, $shape->getFill()); + $this->writeStylePartShadow($objWriter, $shape->getShadow()); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write the default style information for a Line shape. + * + * @param XMLWriter $objWriter + * @param Line $shape + */ + public function writeLineStyle(XMLWriter $objWriter, Line $shape) + { + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId); + $objWriter->writeAttribute('style:family', 'graphic'); + $objWriter->writeAttribute('style:parent-style-name', 'standard'); + + // style:graphic-properties + $objWriter->startElement('style:graphic-properties'); + $objWriter->writeAttribute('draw:fill', 'none'); + switch ($shape->getBorder()->getLineStyle()) { + case Border::LINE_NONE: + $objWriter->writeAttribute('draw:stroke', 'none'); + break; + case Border::LINE_SINGLE: + $objWriter->writeAttribute('draw:stroke', 'solid'); + break; + default: + $objWriter->writeAttribute('draw:stroke', 'none'); + break; + } + $objWriter->writeAttribute('svg:stroke-color', '#'.$shape->getBorder()->getColor()->getRGB()); + $objWriter->writeAttribute('svg:stroke-width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((CommonDrawing::pointsToPixels($shape->getBorder()->getLineWidth()))), 3).'cm'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write the default style information for a Table shape + * + * @param XMLWriter $objWriter + * @param Table $shape + */ + public function writeTableStyle(XMLWriter $objWriter, Table $shape) + { + foreach ($shape->getRows() as $keyRow => $shapeRow) { + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId.'r'.$keyRow); + $objWriter->writeAttribute('style:family', 'table-row'); + + // style:table-row-properties + $objWriter->startElement('style:table-row-properties'); + $objWriter->writeAttribute('style:row-height', Text::numberFormat(CommonDrawing::pixelsToCentimeters(CommonDrawing::pointsToPixels($shapeRow->getHeight())), 3).'cm'); + $objWriter->endElement(); + + $objWriter->endElement(); + + foreach ($shapeRow->getCells() as $keyCell => $shapeCell) { + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId.'r'.$keyRow.'c'.$keyCell); + $objWriter->writeAttribute('style:family', 'table-cell'); + + /** + * Note : This element is not valid in the Schema 1.2 + */ + // style:graphic-properties + if ($shapeCell->getFill()->getFillType() != Fill::FILL_NONE) { + $objWriter->startElement('style:graphic-properties'); + if ($shapeCell->getFill()->getFillType() == Fill::FILL_SOLID) { + $objWriter->writeAttribute('draw:fill', 'solid'); + $objWriter->writeAttribute('draw:fill-color', '#'.$shapeCell->getFill()->getStartColor()->getRGB()); + } + if ($shapeCell->getFill()->getFillType() == Fill::FILL_GRADIENT_LINEAR) { + $objWriter->writeAttribute('draw:fill', 'gradient'); + $objWriter->writeAttribute('draw:fill-gradient-name', 'gradient_'.$shapeCell->getFill()->getHashCode()); + } + $objWriter->endElement(); + } + // >style:graphic-properties + + // style:paragraph-properties + $objWriter->startElement('style:paragraph-properties'); + $cellBorders = $shapeCell->getBorders(); + $cellBordersBottomHashCode = $cellBorders->getBottom()->getHashCode(); + if ($cellBordersBottomHashCode == $cellBorders->getTop()->getHashCode() + && $cellBordersBottomHashCode == $cellBorders->getLeft()->getHashCode() + && $cellBordersBottomHashCode == $cellBorders->getRight()->getHashCode()) { + $lineStyle = 'none'; + $lineWidth = Text::numberFormat($cellBorders->getBottom()->getLineWidth() / 1.75, 2); + $lineColor = $cellBorders->getBottom()->getColor()->getRGB(); + switch ($cellBorders->getBottom()->getLineStyle()) { + case Border::LINE_SINGLE: + $lineStyle = 'solid'; + } + $objWriter->writeAttribute('fo:border', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + } else { + $lineStyle = 'none'; + $lineWidth = Text::numberFormat($cellBorders->getBottom()->getLineWidth() / 1.75, 2); + $lineColor = $cellBorders->getBottom()->getColor()->getRGB(); + switch ($cellBorders->getBottom()->getLineStyle()) { + case Border::LINE_SINGLE: + $lineStyle = 'solid'; + } + $objWriter->writeAttribute('fo:border-bottom', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + // TOP + $lineStyle = 'none'; + $lineWidth = Text::numberFormat($cellBorders->getTop()->getLineWidth() / 1.75, 2); + $lineColor = $cellBorders->getTop()->getColor()->getRGB(); + switch ($cellBorders->getTop()->getLineStyle()) { + case Border::LINE_SINGLE: + $lineStyle = 'solid'; + } + $objWriter->writeAttribute('fo:border-top', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + // RIGHT + $lineStyle = 'none'; + $lineWidth = Text::numberFormat($cellBorders->getRight()->getLineWidth() / 1.75, 2); + $lineColor = $cellBorders->getRight()->getColor()->getRGB(); + switch ($cellBorders->getRight()->getLineStyle()) { + case Border::LINE_SINGLE: + $lineStyle = 'solid'; + } + $objWriter->writeAttribute('fo:border-right', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + // LEFT + $lineStyle = 'none'; + $lineWidth = Text::numberFormat($cellBorders->getLeft()->getLineWidth() / 1.75, 2); + $lineColor = $cellBorders->getLeft()->getColor()->getRGB(); + switch ($cellBorders->getLeft()->getLineStyle()) { + case Border::LINE_SINGLE: + $lineStyle = 'solid'; + } + $objWriter->writeAttribute('fo:border-left', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + } + // >style:paragraph-properties + $objWriter->endElement(); + // >style:style + $objWriter->endElement(); + + foreach ($shapeCell->getParagraphs() as $shapeParagraph) { + foreach ($shapeParagraph->getRichTextElements() as $shapeRichText) { + if ($shapeRichText instanceof Run) { + // Style des font text + if (!isset($this->arrStyleTextFont[$shapeRichText->getHashCode()])) { + $this->arrStyleTextFont[$shapeRichText->getHashCode()] = $shapeRichText; + } + } + } + } + } + } + } + + /** + * Write the slide note + * @param XMLWriter $objWriter + * @param \PhpOffice\PhpPresentation\Slide\Note $note + * @throws \Exception + */ + public function writeSlideNote(XMLWriter $objWriter, Note $note) + { + $shapesNote = $note->getShapeCollection(); + if (count($shapesNote) > 0) { + $objWriter->startElement('presentation:notes'); + + foreach ($shapesNote as $shape) { + // Increment $this->shapeId + ++$this->shapeId; + + if ($shape instanceof RichText) { + $this->writeShapeTxt($objWriter, $shape); + } + } + + $objWriter->endElement(); + } + } + + /** + * Write style of a slide + * @param XMLWriter $objWriter + * @param Slide $slide + * @param int $incPage + */ + public function writeStyleSlide(XMLWriter $objWriter, Slide $slide, $incPage) + { + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:family', 'drawing-page'); + $objWriter->writeAttribute('style:name', 'stylePage'.$incPage); + // style:style/style:drawing-page-properties + $objWriter->startElement('style:drawing-page-properties'); + $objWriter->writeAttributeIf(!$slide->isVisible(), 'presentation:visibility', 'hidden'); + if (!is_null($oTransition = $slide->getTransition())) { + $objWriter->writeAttribute('presentation:duration', 'PT'.number_format($oTransition->getAdvanceTimeTrigger() / 1000, 6, '.', '').'S'); + $objWriter->writeAttributeIf($oTransition->hasManualTrigger(), 'presentation:transition-type', 'manual'); + $objWriter->writeAttributeIf($oTransition->hasTimeTrigger(), 'presentation:transition-type', 'automatic'); + switch ($oTransition->getSpeed()) { + case Transition::SPEED_FAST: + $objWriter->writeAttribute('presentation:transition-speed', 'fast'); + break; + case Transition::SPEED_MEDIUM: + $objWriter->writeAttribute('presentation:transition-speed', 'medium'); + break; + case Transition::SPEED_SLOW: + $objWriter->writeAttribute('presentation:transition-speed', 'slow'); + break; + } + + /** + * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#property-presentation_transition-style + */ + switch ($oTransition->getTransitionType()) { + case Transition::TRANSITION_BLINDS_HORIZONTAL: + $objWriter->writeAttribute('presentation:transition-style', 'horizontal-stripes'); + break; + case Transition::TRANSITION_BLINDS_VERTICAL: + $objWriter->writeAttribute('presentation:transition-style', 'vertical-stripes'); + break; + case Transition::TRANSITION_CHECKER_HORIZONTAL: + $objWriter->writeAttribute('presentation:transition-style', 'horizontal-checkerboard'); + break; + case Transition::TRANSITION_CHECKER_VERTICAL: + $objWriter->writeAttribute('presentation:transition-style', 'vertical-checkerboard'); + break; + case Transition::TRANSITION_CIRCLE: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_COMB_HORIZONTAL: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_COMB_VERTICAL: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_COVER_DOWN: + $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-bottom'); + break; + case Transition::TRANSITION_COVER_LEFT: + $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-left'); + break; + case Transition::TRANSITION_COVER_LEFT_DOWN: + $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-lowerleft'); + break; + case Transition::TRANSITION_COVER_LEFT_UP: + $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-upperleft'); + break; + case Transition::TRANSITION_COVER_RIGHT: + $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-right'); + break; + case Transition::TRANSITION_COVER_RIGHT_DOWN: + $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-lowerright'); + break; + case Transition::TRANSITION_COVER_RIGHT_UP: + $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-upperright'); + break; + case Transition::TRANSITION_COVER_UP: + $objWriter->writeAttribute('presentation:transition-style', 'uncover-to-top'); + break; + case Transition::TRANSITION_CUT: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_DIAMOND: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_DISSOLVE: + $objWriter->writeAttribute('presentation:transition-style', 'dissolve'); + break; + case Transition::TRANSITION_FADE: + $objWriter->writeAttribute('presentation:transition-style', 'fade-from-center'); + break; + case Transition::TRANSITION_NEWSFLASH: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_PLUS: + $objWriter->writeAttribute('presentation:transition-style', 'close'); + break; + case Transition::TRANSITION_PULL_DOWN: + $objWriter->writeAttribute('presentation:transition-style', 'stretch-from-bottom'); + break; + case Transition::TRANSITION_PULL_LEFT: + $objWriter->writeAttribute('presentation:transition-style', 'stretch-from-left'); + break; + case Transition::TRANSITION_PULL_RIGHT: + $objWriter->writeAttribute('presentation:transition-style', 'stretch-from-right'); + break; + case Transition::TRANSITION_PULL_UP: + $objWriter->writeAttribute('presentation:transition-style', 'stretch-from-top'); + break; + case Transition::TRANSITION_PUSH_DOWN: + $objWriter->writeAttribute('presentation:transition-style', 'roll-from-bottom'); + break; + case Transition::TRANSITION_PUSH_LEFT: + $objWriter->writeAttribute('presentation:transition-style', 'roll-from-left'); + break; + case Transition::TRANSITION_PUSH_RIGHT: + $objWriter->writeAttribute('presentation:transition-style', 'roll-from-right'); + break; + case Transition::TRANSITION_PUSH_UP: + $objWriter->writeAttribute('presentation:transition-style', 'roll-from-top'); + break; + case Transition::TRANSITION_RANDOM: + $objWriter->writeAttribute('presentation:transition-style', 'random'); + break; + case Transition::TRANSITION_RANDOMBAR_HORIZONTAL: + $objWriter->writeAttribute('presentation:transition-style', 'horizontal-lines'); + break; + case Transition::TRANSITION_RANDOMBAR_VERTICAL: + $objWriter->writeAttribute('presentation:transition-style', 'vertical-lines'); + break; + case Transition::TRANSITION_SPLIT_IN_HORIZONTAL: + $objWriter->writeAttribute('presentation:transition-style', 'close-horizontal'); + break; + case Transition::TRANSITION_SPLIT_OUT_HORIZONTAL: + $objWriter->writeAttribute('presentation:transition-style', 'open-horizontal'); + break; + case Transition::TRANSITION_SPLIT_IN_VERTICAL: + $objWriter->writeAttribute('presentation:transition-style', 'close-vertical'); + break; + case Transition::TRANSITION_SPLIT_OUT_VERTICAL: + $objWriter->writeAttribute('presentation:transition-style', 'open-vertical'); + break; + case Transition::TRANSITION_STRIPS_LEFT_DOWN: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_STRIPS_LEFT_UP: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_STRIPS_RIGHT_DOWN: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_STRIPS_RIGHT_UP: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_WEDGE: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_WIPE_DOWN: + $objWriter->writeAttribute('presentation:transition-style', 'fade-from-bottom'); + break; + case Transition::TRANSITION_WIPE_LEFT: + $objWriter->writeAttribute('presentation:transition-style', 'fade-from-left'); + break; + case Transition::TRANSITION_WIPE_RIGHT: + $objWriter->writeAttribute('presentation:transition-style', 'fade-from-right'); + break; + case Transition::TRANSITION_WIPE_UP: + $objWriter->writeAttribute('presentation:transition-style', 'fade-from-top'); + break; + case Transition::TRANSITION_ZOOM_IN: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + case Transition::TRANSITION_ZOOM_OUT: + $objWriter->writeAttribute('presentation:transition-style', 'none'); + break; + } + } + $oBackground = $slide->getBackground(); + if ($oBackground instanceof Slide\AbstractBackground) { + $objWriter->writeAttribute('presentation:background-visible', 'true'); + if ($oBackground instanceof Slide\Background\Color) { + $objWriter->writeAttribute('draw:fill', 'solid'); + $objWriter->writeAttribute('draw:fill-color', '#' . $oBackground->getColor()->getRGB()); + } + if ($oBackground instanceof Slide\Background\Image) { + $objWriter->writeAttribute('draw:fill', 'bitmap'); + $objWriter->writeAttribute('draw:fill-image-name', 'background_'.$incPage); + $objWriter->writeAttribute('style:repeat', 'stretch'); + } + } + $objWriter->endElement(); + // > style:style + $objWriter->endElement(); + } + + + /** + * @param XMLWriter $objWriter + * @param Fill $oFill + */ + protected function writeStylePartFill(XMLWriter $objWriter, $oFill) + { + if (!($oFill instanceof Fill)) { + return; + } + switch ($oFill->getFillType()) { + case Fill::FILL_SOLID: + $objWriter->writeAttribute('draw:fill', 'solid'); + $objWriter->writeAttribute('draw:fill-color', '#' . $oFill->getStartColor()->getRGB()); + break; + case Fill::FILL_NONE: + default: + $objWriter->writeAttribute('draw:fill', 'none'); + break; + } + } + + + /** + * @param XMLWriter $objWriter + * @param Shadow $oShadow + * @todo Improve for supporting any direction (https://sinepost.wordpress.com/2012/02/16/theyve-got-atan-you-want-atan2/) + */ + protected function writeStylePartShadow(XMLWriter $objWriter, Shadow $oShadow) + { + if (!$oShadow->isVisible()) { + return; + } + $objWriter->writeAttribute('draw:shadow', 'visible'); + $objWriter->writeAttribute('draw:shadow-color', '#' . $oShadow->getColor()->getRGB()); + + $distanceCms = CommonDrawing::pixelsToCentimeters($oShadow->getDistance()); + if ($oShadow->getDirection() == 0 || $oShadow->getDirection() == 360) { + $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm'); + $objWriter->writeAttribute('draw:shadow-offset-y', '0cm'); + } elseif ($oShadow->getDirection() == 45) { + $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm'); + $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm'); + } elseif ($oShadow->getDirection() == 90) { + $objWriter->writeAttribute('draw:shadow-offset-x', '0cm'); + $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm'); + } elseif ($oShadow->getDirection() == 135) { + $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm'); + $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm'); + } elseif ($oShadow->getDirection() == 180) { + $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm'); + $objWriter->writeAttribute('draw:shadow-offset-y', '0cm'); + } elseif ($oShadow->getDirection() == 225) { + $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm'); + $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm'); + } elseif ($oShadow->getDirection() == 270) { + $objWriter->writeAttribute('draw:shadow-offset-x', '0cm'); + $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm'); + } elseif ($oShadow->getDirection() == 315) { + $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm'); + $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm'); + } + $objWriter->writeAttribute('draw:shadow-opacity', (100 - $oShadow->getAlpha()) . '%'); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php new file mode 100644 index 0000000..751a420 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php @@ -0,0 +1,65 @@ +startDocument('1.0', 'UTF-8'); + + // office:document-meta + $objWriter->startElement('office:document-meta'); + $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0'); + $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $objWriter->writeAttribute('xmlns:smil', 'urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0'); + $objWriter->writeAttribute('xmlns:anim', 'urn:oasis:names:tc:opendocument:xmlns:animation:1.0'); + $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#'); + $objWriter->writeAttribute('xmlns:officeooo', 'http://openoffice.org/2009/office'); + $objWriter->writeAttribute('xmlns:drawooo', 'http://openoffice.org/2010/draw'); + $objWriter->writeAttribute('office:version', '1.2'); + + // office:meta + $objWriter->startElement('office:meta'); + + // dc:creator + $objWriter->writeElement('dc:creator', $this->getPresentation()->getDocumentProperties()->getLastModifiedBy()); + // dc:date + $objWriter->writeElement('dc:date', gmdate('Y-m-d\TH:i:s.000', $this->getPresentation()->getDocumentProperties()->getModified())); + // dc:description + $objWriter->writeElement('dc:description', $this->getPresentation()->getDocumentProperties()->getDescription()); + // dc:subject + $objWriter->writeElement('dc:subject', $this->getPresentation()->getDocumentProperties()->getSubject()); + // dc:title + $objWriter->writeElement('dc:title', $this->getPresentation()->getDocumentProperties()->getTitle()); + // meta:creation-date + $objWriter->writeElement('meta:creation-date', gmdate('Y-m-d\TH:i:s.000', $this->getPresentation()->getDocumentProperties()->getCreated())); + // meta:initial-creator + $objWriter->writeElement('meta:initial-creator', $this->getPresentation()->getDocumentProperties()->getCreator()); + // meta:keyword + $objWriter->writeElement('meta:keyword', $this->getPresentation()->getDocumentProperties()->getKeywords()); + + // @todo : Where these properties are written ? + // $this->getPresentation()->getDocumentProperties()->getCategory() + // $this->getPresentation()->getDocumentProperties()->getCompany() + + $objWriter->endElement(); + + $objWriter->endElement(); + + $this->getZip()->addFromString('meta.xml', $objWriter->getData()); + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php new file mode 100644 index 0000000..9a263db --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php @@ -0,0 +1,109 @@ +startDocument('1.0', 'UTF-8'); + + // manifest:manifest + $objWriter->startElement('manifest:manifest'); + $objWriter->writeAttribute('xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'); + $objWriter->writeAttribute('manifest:version', '1.2'); + + // manifest:file-entry + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.presentation'); + $objWriter->writeAttribute('manifest:full-path', '/'); + $objWriter->writeAttribute('manifest:version', '1.2'); + $objWriter->endElement(); + // manifest:file-entry + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', 'text/xml'); + $objWriter->writeAttribute('manifest:full-path', 'content.xml'); + $objWriter->endElement(); + // manifest:file-entry + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', 'text/xml'); + $objWriter->writeAttribute('manifest:full-path', 'meta.xml'); + $objWriter->endElement(); + // manifest:file-entry + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', 'text/xml'); + $objWriter->writeAttribute('manifest:full-path', 'styles.xml'); + $objWriter->endElement(); + + // Charts + foreach ($this->getArrayChart() as $key => $shape) { + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.chart'); + $objWriter->writeAttribute('manifest:full-path', 'Object '.$key.'/'); + $objWriter->endElement(); + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', 'text/xml'); + $objWriter->writeAttribute('manifest:full-path', 'Object '.$key.'/content.xml'); + $objWriter->endElement(); + } + + $arrMedia = array(); + for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) { + $shape = $this->getDrawingHashTable()->getByIndex($i); + if (! ($shape instanceof ShapeDrawing\AbstractDrawingAdapter)) { + continue; + } + $arrMedia[] = $shape->getIndexedFilename(); + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', $shape->getMimeType()); + $objWriter->writeAttribute('manifest:full-path', 'Pictures/' . $shape->getIndexedFilename()); + $objWriter->endElement(); + } + + foreach ($this->getPresentation()->getAllSlides() as $numSlide => $oSlide) { + $oBkgImage = $oSlide->getBackground(); + if ($oBkgImage instanceof Image) { + $arrayImage = getimagesize($oBkgImage->getPath()); + $mimeType = image_type_to_mime_type($arrayImage[2]); + + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', $mimeType); + $objWriter->writeAttribute('manifest:full-path', 'Pictures/' . str_replace(' ', '_', $oBkgImage->getIndexedFilename($numSlide))); + $objWriter->endElement(); + } + } + + if ($this->getPresentation()->getPresentationProperties()->getThumbnailPath()) { + $pathThumbnail = $this->getPresentation()->getPresentationProperties()->getThumbnailPath(); + // Size : 128x128 pixel + // PNG : 8bit, non-interlaced with full alpha transparency + $gdImage = imagecreatefromstring(file_get_contents($pathThumbnail)); + if ($gdImage) { + imagedestroy($gdImage); + $objWriter->startElement('manifest:file-entry'); + $objWriter->writeAttribute('manifest:media-type', 'image/png'); + $objWriter->writeAttribute('manifest:full-path', 'Thumbnails/thumbnail.png'); + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + + $this->getZip()->addFromString('META-INF/manifest.xml', $objWriter->getData()); + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php new file mode 100644 index 0000000..1efd13e --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php @@ -0,0 +1,18 @@ +getZip()->addFromString('mimetype', 'application/vnd.oasis.opendocument.presentation'); + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php new file mode 100644 index 0000000..2b827ba --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php @@ -0,0 +1,979 @@ +getArrayChart() as $keyChart => $shapeChart) { + $content = $this->writeContentPart($shapeChart); + + if (!empty($content)) { + $this->getZip()->addFromString('Object '.$keyChart.'/content.xml', $content); + } + } + + return $this->getZip(); + } + + /** + * @param Chart $chart + * @return string + * @throws \Exception + */ + protected function writeContentPart(Chart $chart) + { + $this->xmlContent = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + $chartType = $chart->getPlotArea()->getType(); + + // Data + $this->arrayData = array(); + $this->arrayTitle = array(); + $this->numData = 0; + foreach ($chartType->getSeries() as $series) { + $inc = 0; + $this->arrayTitle[] = $series->getTitle(); + foreach ($series->getValues() as $key => $value) { + if (!isset($this->arrayData[$inc])) { + $this->arrayData[$inc] = array(); + } + if (empty($this->arrayData[$inc])) { + $this->arrayData[$inc][] = $key; + } + $this->arrayData[$inc][] = $value; + $inc++; + } + if ($inc > $this->numData) { + $this->numData = $inc; + } + } + + // office:document-content + $this->xmlContent->startElement('office:document-content'); + $this->xmlContent->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $this->xmlContent->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'); + $this->xmlContent->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'); + $this->xmlContent->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'); + $this->xmlContent->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'); + $this->xmlContent->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'); + $this->xmlContent->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $this->xmlContent->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $this->xmlContent->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $this->xmlContent->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'); + $this->xmlContent->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'); + $this->xmlContent->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'); + $this->xmlContent->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'); + $this->xmlContent->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML'); + $this->xmlContent->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'); + $this->xmlContent->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'); + $this->xmlContent->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $this->xmlContent->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer'); + $this->xmlContent->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc'); + $this->xmlContent->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events'); + $this->xmlContent->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms'); + $this->xmlContent->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'); + $this->xmlContent->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $this->xmlContent->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report'); + $this->xmlContent->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2'); + $this->xmlContent->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + $this->xmlContent->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#'); + $this->xmlContent->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table'); + $this->xmlContent->writeAttribute('xmlns:chartooo', 'http://openoffice.org/2010/chart'); + $this->xmlContent->writeAttribute('xmlns:drawooo', 'http://openoffice.org/2010/draw'); + $this->xmlContent->writeAttribute('xmlns:calcext', 'urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0'); + $this->xmlContent->writeAttribute('xmlns:loext', 'urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0'); + $this->xmlContent->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0'); + $this->xmlContent->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0'); + $this->xmlContent->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/'); + $this->xmlContent->writeAttribute('office:version', '1.2'); + + // office:automatic-styles + $this->xmlContent->startElement('office:automatic-styles'); + + // Styles + $this->writeChartStyle($chart); + $this->writeAxisStyle($chart); + $this->numSeries = 0; + foreach ($chartType->getSeries() as $series) { + $this->writeSeriesStyle($chart, $series); + $this->numSeries++; + } + $this->writeFloorStyle(); + $this->writeLegendStyle($chart); + $this->writePlotAreaStyle($chart); + $this->writeTitleStyle($chart->getTitle()); + $this->writeWallStyle($chart); + + // > office:automatic-styles + $this->xmlContent->endElement(); + + // office:body + $this->xmlContent->startElement('office:body'); + // office:chart + $this->xmlContent->startElement('office:chart'); + // office:chart + $this->xmlContent->startElement('chart:chart'); + $this->xmlContent->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($chart->getWidth()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($chart->getHeight()), 3) . 'cm'); + $this->xmlContent->writeAttribute('xlink:href', '.'); + $this->xmlContent->writeAttribute('xlink:type', 'simple'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleChart'); + $this->xmlContent->writeAttributeIf($chartType instanceof Area, 'chart:class', 'chart:area'); + $this->xmlContent->writeAttributeIf($chartType instanceof AbstractTypeBar, 'chart:class', 'chart:bar'); + if (!($chartType instanceof Doughnut)) { + $this->xmlContent->writeAttributeIf($chartType instanceof AbstractTypePie, 'chart:class', 'chart:circle'); + } + $this->xmlContent->writeAttributeIf($chartType instanceof Doughnut, 'chart:class', 'chart:ring'); + $this->xmlContent->writeAttributeIf($chartType instanceof Line, 'chart:class', 'chart:line'); + $this->xmlContent->writeAttributeIf($chartType instanceof Scatter, 'chart:class', 'chart:scatter'); + + $this->writeTitle($chart->getTitle()); + $this->writeLegend($chart); + $this->writePlotArea($chart); + $this->writeTable(); + + // > chart:chart + $this->xmlContent->endElement(); + // > office:chart + $this->xmlContent->endElement(); + // > office:body + $this->xmlContent->endElement(); + // > office:document-content + $this->xmlContent->endElement(); + + return $this->xmlContent->getData(); + } + + /** + * @param Chart $chart + * @throws \Exception + */ + private function writeAxis(Chart $chart) + { + $chartType = $chart->getPlotArea()->getType(); + + // chart:axis + $this->xmlContent->startElement('chart:axis'); + $this->xmlContent->writeAttribute('chart:dimension', 'x'); + $this->xmlContent->writeAttribute('chart:name', 'primary-x'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleAxisX'); + // chart:axis > chart:categories + $this->xmlContent->startElement('chart:categories'); + $this->xmlContent->writeAttribute('table:cell-range-address', 'table-local.$A$2:.$A$'.($this->numData+1)); + $this->xmlContent->endElement(); + // chart:axis > chart:grid + $this->writeGridline($chart->getPlotArea()->getAxisX()->getMajorGridlines(), 'styleAxisXGridlinesMajor', 'major'); + // chart:axis > chart:grid + $this->writeGridline($chart->getPlotArea()->getAxisX()->getMinorGridlines(), 'styleAxisXGridlinesMinor', 'minor'); + // ##chart:axis + $this->xmlContent->endElement(); + + // chart:axis + $this->xmlContent->startElement('chart:axis'); + $this->xmlContent->writeAttribute('chart:dimension', 'y'); + $this->xmlContent->writeAttribute('chart:name', 'primary-y'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleAxisY'); + // chart:axis > chart:grid + $this->writeGridline($chart->getPlotArea()->getAxisY()->getMajorGridlines(), 'styleAxisYGridlinesMajor', 'major'); + // chart:axis > chart:grid + $this->writeGridline($chart->getPlotArea()->getAxisY()->getMinorGridlines(), 'styleAxisYGridlinesMinor', 'minor'); + // ##chart:axis + $this->xmlContent->endElement(); + + if ($chartType instanceof Bar3D || $chartType instanceof Pie3D) { + // chart:axis + $this->xmlContent->startElement('chart:axis'); + $this->xmlContent->writeAttribute('chart:dimension', 'z'); + $this->xmlContent->writeAttribute('chart:name', 'primary-z'); + // > chart:axis + $this->xmlContent->endElement(); + } + } + + protected function writeGridline($oGridlines, $styleName, $chartClass) + { + if (!($oGridlines instanceof Chart\Gridlines)) { + return ; + } + + $this->xmlContent->startElement('chart:grid'); + $this->xmlContent->writeAttribute('chart:style-name', $styleName); + $this->xmlContent->writeAttribute('chart:class', $chartClass); + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + * @throws \Exception + * @todo Set function in \PhpPresentation\Shape\Chart\Axis for defining width and color of the axis + */ + protected function writeAxisStyle(Chart $chart) + { + $chartType = $chart->getPlotArea()->getType(); + + // AxisX + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleAxisX'); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:style > style:chart-properties + $this->xmlContent->startElement('style:chart-properties'); + $this->xmlContent->writeAttribute('chart:display-label', 'true'); + $this->xmlContent->writeAttribute('chart:tick-marks-major-inner', 'false'); + $this->xmlContent->writeAttribute('chart:tick-marks-major-outer', 'false'); + if ($chartType instanceof AbstractTypePie) { + $this->xmlContent->writeAttribute('chart:reverse-direction', 'true'); + } + if ($chart->getPlotArea()->getAxisX()->getMinBounds() != null) { + $this->xmlContent->writeAttribute('chart:minimum', $chart->getPlotArea()->getAxisX()->getMinBounds()); + } + if ($chart->getPlotArea()->getAxisX()->getMaxBounds() != null) { + $this->xmlContent->writeAttribute('chart:maximum', $chart->getPlotArea()->getAxisX()->getMaxBounds()); + } + $this->xmlContent->endElement(); + // style:style > style:graphic-properties + $this->xmlContent->startElement('style:graphic-properties'); + $this->xmlContent->writeAttribute('draw:stroke', 'solid'); + $this->xmlContent->writeAttribute('svg:stroke-width', '0.026cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#878787'); + $this->xmlContent->endElement(); + // style:style > style:text-properties + $oFont = $chart->getPlotArea()->getAxisX()->getFont(); + $this->xmlContent->startElement('style:text-properties'); + $this->xmlContent->writeAttribute('fo:color', '#'.$oFont->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:font-family', $oFont->getName()); + $this->xmlContent->writeAttribute('fo:font-size', $oFont->getSize().'pt'); + $this->xmlContent->writeAttribute('fo:font-style', $oFont->isItalic() ? 'italic' : 'normal'); + $this->xmlContent->endElement(); + // ##style:style + $this->xmlContent->endElement(); + + // AxisX GridLines Major + $this->writeGridlineStyle($chart->getPlotArea()->getAxisX()->getMajorGridlines(), 'styleAxisXGridlinesMajor'); + + // AxisX GridLines Minor + $this->writeGridlineStyle($chart->getPlotArea()->getAxisX()->getMinorGridlines(), 'styleAxisXGridlinesMinor'); + + // AxisY + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleAxisY'); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:style > style:chart-properties + $this->xmlContent->startElement('style:chart-properties'); + $this->xmlContent->writeAttribute('chart:display-label', 'true'); + $this->xmlContent->writeAttribute('chart:tick-marks-major-inner', 'false'); + $this->xmlContent->writeAttribute('chart:tick-marks-major-outer', 'false'); + if ($chartType instanceof AbstractTypePie) { + $this->xmlContent->writeAttribute('chart:reverse-direction', 'true'); + } + if ($chart->getPlotArea()->getAxisY()->getMinBounds() !== null) { + $this->xmlContent->writeAttribute('chart:minimum', $chart->getPlotArea()->getAxisY()->getMinBounds()); + } + if ($chart->getPlotArea()->getAxisY()->getMaxBounds() !== null) { + $this->xmlContent->writeAttribute('chart:maximum', $chart->getPlotArea()->getAxisY()->getMaxBounds()); + } + $this->xmlContent->endElement(); + // style:graphic-properties + $this->xmlContent->startElement('style:graphic-properties'); + $this->xmlContent->writeAttribute('draw:stroke', 'solid'); + $this->xmlContent->writeAttribute('svg:stroke-width', '0.026cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#878787'); + $this->xmlContent->endElement(); + // style:style > style:text-properties + $oFont = $chart->getPlotArea()->getAxisY()->getFont(); + $this->xmlContent->startElement('style:text-properties'); + $this->xmlContent->writeAttribute('fo:color', '#'.$oFont->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:font-family', $oFont->getName()); + $this->xmlContent->writeAttribute('fo:font-size', $oFont->getSize().'pt'); + $this->xmlContent->writeAttribute('fo:font-style', $oFont->isItalic() ? 'italic' : 'normal'); + $this->xmlContent->endElement(); + // ## style:style + $this->xmlContent->endElement(); + + // AxisY GridLines Major + $this->writeGridlineStyle($chart->getPlotArea()->getAxisY()->getMajorGridlines(), 'styleAxisYGridlinesMajor'); + + // AxisY GridLines Minor + $this->writeGridlineStyle($chart->getPlotArea()->getAxisY()->getMinorGridlines(), 'styleAxisYGridlinesMinor'); + } + + /** + * @param Chart\Gridlines $oGridlines + * @param string $styleName + */ + protected function writeGridlineStyle($oGridlines, $styleName) + { + if (!($oGridlines instanceof Chart\Gridlines)) { + return; + } + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', $styleName); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:style > style:graphic-properties + $this->xmlContent->startElement('style:graphic-properties'); + $this->xmlContent->writeAttribute('svg:stroke-width', number_format(CommonDrawing::pointsToCentimeters($oGridlines->getOutline()->getWidth()), 2, '.', '').'cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#'.$oGridlines->getOutline()->getFill()->getStartColor()->getRGB()); + $this->xmlContent->endElement(); + // ##style:style + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + */ + private function writeChartStyle(Chart $chart) + { + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleChart'); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:graphic-properties + $this->xmlContent->startElement('style:graphic-properties'); + $this->xmlContent->writeAttribute('draw:stroke', $chart->getFill()->getFillType()); + $this->xmlContent->writeAttribute('draw:fill-color', '#'.$chart->getFill()->getStartColor()->getRGB()); + // > style:graphic-properties + $this->xmlContent->endElement(); + // > style:style + $this->xmlContent->endElement(); + } + + private function writeFloor() + { + // chart:floor + $this->xmlContent->startElement('chart:floor'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleFloor'); + // > chart:floor + $this->xmlContent->endElement(); + } + + private function writeFloorStyle() + { + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleFloor'); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:chart-properties + $this->xmlContent->startElement('style:graphic-properties'); + $this->xmlContent->writeAttribute('draw:fill', 'none'); + //@todo : Permit edit color and size border of floor + $this->xmlContent->writeAttribute('draw:stroke', 'solid'); + $this->xmlContent->writeAttribute('svg:stroke-width', '0.026cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#878787'); + // > style:chart-properties + $this->xmlContent->endElement(); + // > style:style + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + */ + private function writeLegend(Chart $chart) + { + // chart:legend + $this->xmlContent->startElement('chart:legend'); + switch ($chart->getLegend()->getPosition()) { + case Chart\Legend::POSITION_BOTTOM: + $position = 'bottom'; + break; + case Chart\Legend::POSITION_LEFT: + $position = 'start'; + break; + case Chart\Legend::POSITION_TOP: + $position = 'top'; + break; + case Chart\Legend::POSITION_TOPRIGHT: + $position = 'top-end'; + break; + case Chart\Legend::POSITION_RIGHT: + default: + $position = 'end'; + break; + } + $this->xmlContent->writeAttribute('chart:legend-position', $position); + $this->xmlContent->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($chart->getLegend()->getOffsetX()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($chart->getLegend()->getOffsetY()), 3) . 'cm'); + $this->xmlContent->writeAttribute('style:legend-expansion', 'high'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleLegend'); + // > chart:legend + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + */ + private function writeLegendStyle(Chart $chart) + { + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleLegend'); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:chart-properties + $this->xmlContent->startElement('style:chart-properties'); + $this->xmlContent->writeAttribute('chart:auto-position', 'true'); + // > style:chart-properties + $this->xmlContent->endElement(); + // style:text-properties + $this->xmlContent->startElement('style:text-properties'); + $this->xmlContent->writeAttribute('fo:color', '#'.$chart->getLegend()->getFont()->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:font-family', $chart->getLegend()->getFont()->getName()); + $this->xmlContent->writeAttribute('fo:font-size', $chart->getLegend()->getFont()->getSize().'pt'); + $this->xmlContent->writeAttribute('fo:font-style', $chart->getLegend()->getFont()->isItalic() ? 'italic' : 'normal'); + // > style:text-properties + $this->xmlContent->endElement(); + // > style:style + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + * @throws \Exception + */ + private function writePlotArea(Chart $chart) + { + $chartType = $chart->getPlotArea()->getType(); + + // chart:plot-area + $this->xmlContent->startElement('chart:plot-area'); + $this->xmlContent->writeAttribute('chart:style-name', 'stylePlotArea'); + if ($chartType instanceof Bar3D || $chartType instanceof Pie3D) { + $this->xmlContent->writeAttribute('dr3d:ambient-color', '#cccccc'); + $this->xmlContent->writeAttribute('dr3d:lighting-mode', 'true'); + } + if ($chartType instanceof Bar3D || $chartType instanceof Pie3D) { + // dr3d:light + $arrayLight = array( + array('#808080', '(0 0 1)', 'false', 'true'), + array('#666666', '(0.2 0.4 1)', 'true', 'false'), + array('#808080', '(0 0 1)', 'false', 'false'), + array('#808080', '(0 0 1)', 'false', 'false'), + array('#808080', '(0 0 1)', 'false', 'false'), + array('#808080', '(0 0 1)', 'false', 'false'), + array('#808080', '(0 0 1)', 'false', 'false'), + ); + foreach ($arrayLight as $light) { + $this->xmlContent->startElement('dr3d:light'); + $this->xmlContent->writeAttribute('dr3d:diffuse-color', $light[0]); + $this->xmlContent->writeAttribute('dr3d:direction', $light[1]); + $this->xmlContent->writeAttribute('dr3d:enabled', $light[2]); + $this->xmlContent->writeAttribute('dr3d:specular', $light[3]); + $this->xmlContent->endElement(); + } + } + + //**** Axis **** + $this->writeAxis($chart); + + //**** Series **** + $this->rangeCol = 'B'; + $this->numSeries = 0; + foreach ($chartType->getSeries() as $series) { + $this->writeSeries($chart, $series); + $this->rangeCol++; + $this->numSeries++; + } + + //**** Wall **** + $this->writeWall(); + //**** Floor **** + $this->writeFloor(); + // > chart:plot-area + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + * @throws \Exception + * @link : http://books.evc-cit.info/odbook/ch08.html#chart-plot-area-section + */ + private function writePlotAreaStyle(Chart $chart) + { + $chartType = $chart->getPlotArea()->getType(); + + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'stylePlotArea'); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:text-properties + $this->xmlContent->startElement('style:chart-properties'); + if ($chartType instanceof Bar3D) { + $this->xmlContent->writeAttribute('chart:three-dimensional', 'true'); + $this->xmlContent->writeAttribute('chart:right-angled-axes', 'true'); + } elseif ($chartType instanceof Pie3D) { + $this->xmlContent->writeAttribute('chart:three-dimensional', 'true'); + $this->xmlContent->writeAttribute('chart:right-angled-axes', 'true'); + } + if ($chartType instanceof AbstractTypeBar) { + $chartVertical = 'false'; + if ($chartType->getBarDirection() == AbstractTypeBar::DIRECTION_HORIZONTAL) { + $chartVertical = 'true'; + } + $this->xmlContent->writeAttribute('chart:vertical', $chartVertical); + if ($chartType->getBarGrouping() == Bar::GROUPING_CLUSTERED) { + $this->xmlContent->writeAttribute('chart:stacked', 'false'); + $this->xmlContent->writeAttribute('chart:overlap', '0'); + } elseif ($chartType->getBarGrouping() == Bar::GROUPING_STACKED) { + $this->xmlContent->writeAttribute('chart:stacked', 'true'); + $this->xmlContent->writeAttribute('chart:overlap', '100'); + } elseif ($chartType->getBarGrouping() == Bar::GROUPING_PERCENTSTACKED) { + $this->xmlContent->writeAttribute('chart:stacked', 'true'); + $this->xmlContent->writeAttribute('chart:overlap', '100'); + $this->xmlContent->writeAttribute('chart:percentage', 'true'); + } + } + $labelFormat = 'value'; + if ($chartType instanceof AbstractTypeBar) { + if ($chartType->getBarGrouping() == Bar::GROUPING_PERCENTSTACKED) { + $labelFormat = 'percentage'; + } + } + $this->xmlContent->writeAttribute('chart:data-label-number', $labelFormat); + + // > style:text-properties + $this->xmlContent->endElement(); + // > style:style + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + * @param Chart\Series $series + * @throws \Exception + */ + private function writeSeries(Chart $chart, Chart\Series $series) + { + $chartType = $chart->getPlotArea()->getType(); + + $numRange = count($series->getValues()); + // chart:series + $this->xmlContent->startElement('chart:series'); + $this->xmlContent->writeAttribute('chart:values-cell-range-address', 'table-local.$'.$this->rangeCol.'$2:.$'.$this->rangeCol.'$'.($numRange+1)); + $this->xmlContent->writeAttribute('chart:label-cell-address', 'table-local.$'.$this->rangeCol.'$1'); + // if ($chartType instanceof Area) { + // $this->xmlContent->writeAttribute('chart:class', 'chart:area'); + // } elseif ($chartType instanceof AbstractTypeBar) { + // $this->xmlContent->writeAttribute('chart:class', 'chart:bar'); + // } elseif ($chartType instanceof Line) { + // $this->xmlContent->writeAttribute('chart:class', 'chart:line'); + // } elseif ($chartType instanceof AbstractTypePie) { + // $this->xmlContent->writeAttribute('chart:class', 'chart:circle'); + // } elseif ($chartType instanceof Scatter) { + // $this->xmlContent->writeAttribute('chart:class', 'chart:scatter'); + // } + $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries'.$this->numSeries); + if ($chartType instanceof Area || $chartType instanceof AbstractTypeBar || $chartType instanceof Line || $chartType instanceof Scatter) { + $dataPointFills = $series->getDataPointFills(); + + $incRepeat = $numRange; + if (!empty($dataPointFills)) { + $inc = 0; + $incRepeat = 1; + $newFill = new Fill(); + do { + if ($series->getDataPointFill($inc)->getHashCode() !== $newFill->getHashCode()) { + // chart:data-point + $this->xmlContent->startElement('chart:data-point'); + $this->xmlContent->writeAttribute('chart:repeated', $incRepeat); + // > chart:data-point + $this->xmlContent->endElement(); + $incRepeat = 1; + + // chart:data-point + $this->xmlContent->startElement('chart:data-point'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries'.$this->numSeries.'_'.$inc); + // > chart:data-point + $this->xmlContent->endElement(); + } + $inc++; + $incRepeat++; + } while ($inc < $numRange); + $incRepeat--; + } + // chart:data-point + $this->xmlContent->startElement('chart:data-point'); + $this->xmlContent->writeAttribute('chart:repeated', $incRepeat); + // > chart:data-point + $this->xmlContent->endElement(); + } elseif ($chartType instanceof AbstractTypePie) { + $count = count($series->getDataPointFills()); + for ($inc = 0; $inc < $count; $inc++) { + // chart:data-point + $this->xmlContent->startElement('chart:data-point'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries'.$this->numSeries.'_'.$inc); + // > chart:data-point + $this->xmlContent->endElement(); + } + } + + // > chart:series + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + * @param Chart\Series $series + * @throws \Exception + */ + private function writeSeriesStyle(Chart $chart, Chart\Series $series) + { + $chartType = $chart->getPlotArea()->getType(); + + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleSeries'.$this->numSeries); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:chart-properties + $this->xmlContent->startElement('style:chart-properties'); + if ($series->hasShowValue()) { + if ($series->hasShowPercentage()) { + $this->xmlContent->writeAttribute('chart:data-label-number', 'value-and-percentage'); + } else { + $this->xmlContent->writeAttribute('chart:data-label-number', 'value'); + } + } else { + if ($series->hasShowPercentage()) { + $this->xmlContent->writeAttribute('chart:data-label-number', 'percentage'); + } + } + if ($series->hasShowCategoryName()) { + $this->xmlContent->writeAttribute('chart:data-label-text', 'true'); + } + $this->xmlContent->writeAttribute('chart:label-position', 'center'); + if ($chartType instanceof AbstractTypePie) { + $this->xmlContent->writeAttribute('chart:pie-offset', $chartType->getExplosion()); + } + if ($chartType instanceof Line || $chartType instanceof Scatter) { + $oMarker = $series->getMarker(); + /** + * @link : http://www.datypic.com/sc/odf/a-chart_symbol-type.html + */ + $this->xmlContent->writeAttributeIf($oMarker->getSymbol() == Chart\Marker::SYMBOL_NONE, 'chart:symbol-type', 'none'); + /** + * @link : http://www.datypic.com/sc/odf/a-chart_symbol-name.html + */ + $this->xmlContent->writeAttributeIf($oMarker->getSymbol() != Chart\Marker::SYMBOL_NONE, 'chart:symbol-type', 'named-symbol'); + if ($oMarker->getSymbol() != Chart\Marker::SYMBOL_NONE) { + switch ($oMarker->getSymbol()) { + case Chart\Marker::SYMBOL_DASH: + $symbolName = 'horizontal-bar'; + break; + case Chart\Marker::SYMBOL_DOT: + $symbolName = 'circle'; + break; + case Chart\Marker::SYMBOL_TRIANGLE: + $symbolName = 'arrow-up'; + break; + default: + $symbolName = $oMarker->getSymbol(); + break; + } + $this->xmlContent->writeAttribute('chart:symbol-name', $symbolName); + $symbolSize = number_format(CommonDrawing::pointsToCentimeters($oMarker->getSize()), 2, '.', ''); + $this->xmlContent->writeAttribute('chart:symbol-width', $symbolSize.'cm'); + $this->xmlContent->writeAttribute('chart:symbol-height', $symbolSize.'cm'); + } + } + + $separator = $series->getSeparator(); + if (!empty($separator)) { + // style:chart-properties/chart:label-separator + $this->xmlContent->startElement('chart:label-separator'); + if ($separator == PHP_EOL) { + $this->xmlContent->writeRaw(''); + } else { + $this->xmlContent->writeElement('text:p', $separator); + } + $this->xmlContent->endElement(); + } + + // > style:chart-properties + $this->xmlContent->endElement(); + // style:graphic-properties + $this->xmlContent->startElement('style:graphic-properties'); + if ($chartType instanceof Line || $chartType instanceof Scatter) { + $outlineWidth = ''; + $outlineColor = ''; + + $oOutline = $series->getOutline(); + if ($oOutline instanceof Outline) { + $outlineWidth = $oOutline->getWidth(); + if (!empty($outlineWidth)) { + $outlineWidth = number_format(CommonDrawing::pointsToCentimeters($outlineWidth), 3, '.', ''); + } + $outlineColor = $oOutline->getFill()->getStartColor()->getRGB(); + } + if (empty($outlineWidth)) { + $outlineWidth = '0.079'; + } + if (empty($outlineColor)) { + $outlineColor = '4a7ebb'; + } + $this->xmlContent->writeAttribute('svg:stroke-width', $outlineWidth.'cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#'.$outlineColor); + } else { + $this->xmlContent->writeAttribute('draw:stroke', 'none'); + if (!($chartType instanceof Area)) { + $this->xmlContent->writeAttribute('draw:fill', $series->getFill()->getFillType()); + } + } + $this->xmlContent->writeAttribute('draw:fill-color', '#'.$series->getFill()->getStartColor()->getRGB()); + // > style:graphic-properties + $this->xmlContent->endElement(); + // style:text-properties + $this->xmlContent->startElement('style:text-properties'); + $this->xmlContent->writeAttribute('fo:color', '#'.$series->getFont()->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:font-family', $series->getFont()->getName()); + $this->xmlContent->writeAttribute('fo:font-size', $series->getFont()->getSize().'pt'); + // > style:text-properties + $this->xmlContent->endElement(); + + // > style:style + $this->xmlContent->endElement(); + + foreach ($series->getDataPointFills() as $idx => $oFill) { + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleSeries'.$this->numSeries.'_'.$idx); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:graphic-properties + $this->xmlContent->startElement('style:graphic-properties'); + $this->xmlContent->writeAttribute('draw:fill', $oFill->getFillType()); + $this->xmlContent->writeAttribute('draw:fill-color', '#'.$oFill->getStartColor()->getRGB()); + // > style:graphic-properties + $this->xmlContent->endElement(); + // > style:style + $this->xmlContent->endElement(); + } + } + + /** + */ + private function writeTable() + { + // table:table + $this->xmlContent->startElement('table:table'); + $this->xmlContent->writeAttribute('table:name', 'table-local'); + + // table:table-columns + $this->xmlContent->startElement('table:table-columns'); + // table:table-column + $this->xmlContent->startElement('table:table-column'); + if (!empty($this->arrayData)) { + $rowFirst = reset($this->arrayData); + $this->xmlContent->writeAttribute('table:number-columns-repeated', count($rowFirst) - 1); + } + // > table:table-column + $this->xmlContent->endElement(); + // > table:table-columns + $this->xmlContent->endElement(); + + // table:table-header-columns + $this->xmlContent->startElement('table:table-header-columns'); + // table:table-column + $this->xmlContent->writeElement('table:table-column'); + // > table:table-header-columns + $this->xmlContent->endElement(); + + // table:table-rows + $this->xmlContent->startElement('table:table-rows'); + if (empty($this->arrayData)) { + $this->xmlContent->startElement('table:table-row'); + $this->xmlContent->startElement('table:table-cell'); + $this->xmlContent->endElement(); + $this->xmlContent->endElement(); + } else { + foreach ($this->arrayData as $row) { + // table:table-row + $this->xmlContent->startElement('table:table-row'); + foreach ($row as $cell) { + // table:table-cell + $this->xmlContent->startElement('table:table-cell'); + + $cellNumeric = is_numeric($cell); + $this->xmlContent->writeAttributeIf(!$cellNumeric, 'office:value-type', 'string'); + $this->xmlContent->writeAttributeIf($cellNumeric, 'office:value-type', 'float'); + $this->xmlContent->writeAttributeIf($cellNumeric, 'office:value', $cell); + // text:p + $this->xmlContent->startElement('text:p'); + $this->xmlContent->text($cell); + // > text:p + $this->xmlContent->endElement(); + // > table:table-cell + $this->xmlContent->endElement(); + } + // > table:table-row + $this->xmlContent->endElement(); + } + } + // > table:table-rows + $this->xmlContent->endElement(); + + // table:table-header-rows + $this->xmlContent->startElement('table:table-header-rows'); + // table:table-row + $this->xmlContent->startElement('table:table-row'); + if (empty($this->arrayData)) { + $this->xmlContent->startElement('table:table-cell'); + $this->xmlContent->endElement(); + } else { + $rowFirst = reset($this->arrayData); + foreach ($rowFirst as $key => $cell) { + // table:table-cell + $this->xmlContent->startElement('table:table-cell'); + if (isset($this->arrayTitle[$key - 1])) { + $this->xmlContent->writeAttribute('office:value-type', 'string'); + } + // text:p + $this->xmlContent->startElement('text:p'); + if (isset($this->arrayTitle[$key - 1])) { + $this->xmlContent->text($this->arrayTitle[$key - 1]); + } + // > text:p + $this->xmlContent->endElement(); + // > table:table-cell + $this->xmlContent->endElement(); + } + } + // > table:table-row + $this->xmlContent->endElement(); + // > table:table-header-rows + $this->xmlContent->endElement(); + + // > table:table + $this->xmlContent->endElement(); + } + + /** + * @param Title $oTitle + */ + private function writeTitle(Title $oTitle) + { + if (!$oTitle->isVisible()) { + return; + } + // chart:title + $this->xmlContent->startElement('chart:title'); + $this->xmlContent->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($oTitle->getOffsetX()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($oTitle->getOffsetY()), 3) . 'cm'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleTitle'); + // > text:p + $this->xmlContent->startElement('text:p'); + $this->xmlContent->text($oTitle->getText()); + $this->xmlContent->endElement(); + // > chart:title + $this->xmlContent->endElement(); + } + + /** + * @param Title $oTitle + */ + private function writeTitleStyle(Title $oTitle) + { + if (!$oTitle->isVisible()) { + return; + } + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleTitle'); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:text-properties + $this->xmlContent->startElement('style:text-properties'); + $this->xmlContent->writeAttribute('fo:color', '#'.$oTitle->getFont()->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:font-family', $oTitle->getFont()->getName()); + $this->xmlContent->writeAttribute('fo:font-size', $oTitle->getFont()->getSize().'pt'); + $this->xmlContent->writeAttribute('fo:font-style', $oTitle->getFont()->isItalic() ? 'italic' : 'normal'); + // > style:text-properties + $this->xmlContent->endElement(); + // > style:style + $this->xmlContent->endElement(); + } + + private function writeWall() + { + // chart:wall + $this->xmlContent->startElement('chart:wall'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleWall'); + $this->xmlContent->endElement(); + } + + /** + * @param Chart $chart + * @throws \Exception + */ + private function writeWallStyle(Chart $chart) + { + $chartType = $chart->getPlotArea()->getType(); + + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', 'styleWall'); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:chart-properties + $this->xmlContent->startElement('style:graphic-properties'); + //@todo : Permit edit color and size border of wall + if ($chartType instanceof Line || $chartType instanceof Scatter) { + $this->xmlContent->writeAttribute('draw:fill', 'solid'); + $this->xmlContent->writeAttribute('draw:fill-color', '#FFFFFF'); + } else { + $this->xmlContent->writeAttribute('draw:fill', 'none'); + $this->xmlContent->writeAttribute('draw:stroke', 'solid'); + $this->xmlContent->writeAttribute('svg:stroke-width', '0.026cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#878787'); + } + // > style:chart-properties + $this->xmlContent->endElement(); + // > style:style + $this->xmlContent->endElement(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php new file mode 100644 index 0000000..6ec4e54 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php @@ -0,0 +1,38 @@ +getDrawingHashTable()->count(); ++$i) { + $shape = $this->getDrawingHashTable()->getByIndex($i); + if (!($shape instanceof Drawing\AbstractDrawingAdapter)) { + continue; + } + $arrMedia[] = $shape->getIndexedFilename(); + $this->getZip()->addFromString('Pictures/' . $shape->getIndexedFilename(), $shape->getContents()); + } + + foreach ($this->getPresentation()->getAllSlides() as $keySlide => $oSlide) { + // Add background image slide + $oBkgImage = $oSlide->getBackground(); + if ($oBkgImage instanceof Image) { + $this->getZip()->addFromString('Pictures/'.$oBkgImage->getIndexedFilename($keySlide), file_get_contents($oBkgImage->getPath())); + } + } + + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php new file mode 100644 index 0000000..3c7fd72 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php @@ -0,0 +1,327 @@ +getZip()->addFromString('styles.xml', $this->writePart()); + return $this->getZip(); + } + + /** + * Write Meta file to XML format + * + * @return string XML Output + * @throws \Exception + */ + protected function writePart() + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $objWriter->startDocument('1.0', 'UTF-8'); + + // office:document-meta + $objWriter->startElement('office:document-styles'); + $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'); + $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'); + $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'); + $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'); + $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'); + $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'); + $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0'); + $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'); + $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'); + $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'); + $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML'); + $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'); + $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'); + $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer'); + $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc'); + $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events'); + $objWriter->writeAttribute('xmlns:smil', 'urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0'); + $objWriter->writeAttribute('xmlns:anim', 'urn:oasis:names:tc:opendocument:xmlns:animation:1.0'); + $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report'); + $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2'); + $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#'); + $objWriter->writeAttribute('xmlns:officeooo', 'http://openoffice.org/2009/office'); + $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table'); + $objWriter->writeAttribute('xmlns:drawooo', 'http://openoffice.org/2010/draw'); + $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/'); + $objWriter->writeAttribute('office:version', '1.2'); + + // Variables + $stylePageLayout = $this->getPresentation()->getLayout()->getDocumentLayout(); + if (empty($stylePageLayout)) { + $stylePageLayout = 'sPL0'; + } + + // office:styles + $objWriter->startElement('office:styles'); + // style:style + $objWriter->startElement('style:style'); + $objWriter->writeAttribute('style:name', 'sPres0'); + $objWriter->writeAttribute('style:display-name', 'sPres0'); + $objWriter->writeAttribute('style:family', 'presentation'); + // style:graphic-properties + $objWriter->startElement('style:graphic-properties'); + $objWriter->writeAttribute('draw:fill-color', '#ffffff'); + // > style:graphic-properties + $objWriter->endElement(); + // > style:style + $objWriter->endElement(); + + foreach ($this->getPresentation()->getAllSlides() as $keySlide => $oSlide) { + foreach ($oSlide->getShapeCollection() as $shape) { + if ($shape instanceof Table) { + $this->writeTableStyle($objWriter, $shape); + } elseif ($shape instanceof Group) { + $this->writeGroupStyle($objWriter, $shape); + } elseif ($shape instanceof RichText) { + $this->writeRichTextStyle($objWriter, $shape); + } + } + $oBkgImage = $oSlide->getBackground(); + if ($oBkgImage instanceof Image) { + $this->writeBackgroundStyle($objWriter, $oBkgImage, $keySlide); + } + } + // > office:styles + $objWriter->endElement(); + + // office:automatic-styles + $objWriter->startElement('office:automatic-styles'); + // style:page-layout + $objWriter->startElement('style:page-layout'); + $objWriter->writeAttribute('style:name', $stylePageLayout); + // style:page-layout-properties + $objWriter->startElement('style:page-layout-properties'); + $objWriter->writeAttribute('fo:margin-top', '0cm'); + $objWriter->writeAttribute('fo:margin-bottom', '0cm'); + $objWriter->writeAttribute('fo:margin-left', '0cm'); + $objWriter->writeAttribute('fo:margin-right', '0cm'); + $objWriter->writeAttribute('fo:page-width', Text::numberFormat(CommonDrawing::pixelsToCentimeters(CommonDrawing::emuToPixels($this->getPresentation()->getLayout()->getCX())), 1) . 'cm'); + $objWriter->writeAttribute('fo:page-height', Text::numberFormat(CommonDrawing::pixelsToCentimeters(CommonDrawing::emuToPixels($this->getPresentation()->getLayout()->getCY())), 1) . 'cm'); + $printOrientation = 'portrait'; + if ($this->getPresentation()->getLayout()->getCX() > $this->getPresentation()->getLayout()->getCY()) { + $printOrientation = 'landscape'; + } + $objWriter->writeAttribute('style:print-orientation', $printOrientation); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + + // office:master-styles + $objWriter->startElement('office:master-styles'); + // style:master-page + $objWriter->startElement('style:master-page'); + $objWriter->writeAttribute('style:name', 'Standard'); + $objWriter->writeAttribute('style:display-name', 'Standard'); + $objWriter->writeAttribute('style:page-layout-name', $stylePageLayout); + $objWriter->writeAttribute('draw:style-name', 'sPres0'); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write the default style information for a RichText shape + * + * @param XMLWriter $objWriter + * @param RichText $shape + */ + protected function writeRichTextStyle(XMLWriter $objWriter, RichText $shape) + { + $oFill = $shape->getFill(); + if ($oFill->getFillType() == Fill::FILL_GRADIENT_LINEAR || $oFill->getFillType() == Fill::FILL_GRADIENT_PATH) { + if (!in_array($oFill->getHashCode(), $this->arrayGradient)) { + $this->writeGradientFill($objWriter, $oFill); + } + } + $oBorder = $shape->getBorder(); + if ($oBorder->getDashStyle() != Border::DASH_SOLID) { + if (!in_array($oBorder->getDashStyle(), $this->arrayStrokeDash)) { + $objWriter->startElement('draw:stroke-dash'); + $objWriter->writeAttribute('draw:name', 'strokeDash_'.$oBorder->getDashStyle()); + $objWriter->writeAttribute('draw:style', 'rect'); + switch ($oBorder->getDashStyle()) { + case Border::DASH_DASH: + $objWriter->writeAttribute('draw:distance', '0.105cm'); + $objWriter->writeAttribute('draw:dots2', '1'); + $objWriter->writeAttribute('draw:dots2-length', '0.14cm'); + break; + case Border::DASH_DASHDOT: + $objWriter->writeAttribute('draw:distance', '0.105cm'); + $objWriter->writeAttribute('draw:dots1', '1'); + $objWriter->writeAttribute('draw:dots1-length', '0.035cm'); + $objWriter->writeAttribute('draw:dots2', '1'); + $objWriter->writeAttribute('draw:dots2-length', '0.14cm'); + break; + case Border::DASH_DOT: + $objWriter->writeAttribute('draw:distance', '0.105cm'); + $objWriter->writeAttribute('draw:dots1', '1'); + $objWriter->writeAttribute('draw:dots1-length', '0.035cm'); + break; + case Border::DASH_LARGEDASH: + $objWriter->writeAttribute('draw:distance', '0.105cm'); + $objWriter->writeAttribute('draw:dots2', '1'); + $objWriter->writeAttribute('draw:dots2-length', '0.28cm'); + break; + case Border::DASH_LARGEDASHDOT: + $objWriter->writeAttribute('draw:distance', '0.105cm'); + $objWriter->writeAttribute('draw:dots1', '1'); + $objWriter->writeAttribute('draw:dots1-length', '0.035cm'); + $objWriter->writeAttribute('draw:dots2', '1'); + $objWriter->writeAttribute('draw:dots2-length', '0.28cm'); + break; + case Border::DASH_LARGEDASHDOTDOT: + $objWriter->writeAttribute('draw:distance', '0.105cm'); + $objWriter->writeAttribute('draw:dots1', '2'); + $objWriter->writeAttribute('draw:dots1-length', '0.035cm'); + $objWriter->writeAttribute('draw:dots2', '1'); + $objWriter->writeAttribute('draw:dots2-length', '0.28cm'); + break; + case Border::DASH_SYSDASH: + $objWriter->writeAttribute('draw:distance', '0.035cm'); + $objWriter->writeAttribute('draw:dots2', '1'); + $objWriter->writeAttribute('draw:dots2-length', '0.105cm'); + break; + case Border::DASH_SYSDASHDOT: + $objWriter->writeAttribute('draw:distance', '0.035cm'); + $objWriter->writeAttribute('draw:dots1', '1'); + $objWriter->writeAttribute('draw:dots1-length', '0.035cm'); + $objWriter->writeAttribute('draw:dots2', '1'); + $objWriter->writeAttribute('draw:dots2-length', '0.105cm'); + break; + case Border::DASH_SYSDASHDOTDOT: + $objWriter->writeAttribute('draw:distance', '0.035cm'); + $objWriter->writeAttribute('draw:dots1', '2'); + $objWriter->writeAttribute('draw:dots1-length', '0.035cm'); + $objWriter->writeAttribute('draw:dots2', '1'); + $objWriter->writeAttribute('draw:dots2-length', '0.105cm'); + break; + case Border::DASH_SYSDOT: + $objWriter->writeAttribute('draw:distance', '0.035cm'); + $objWriter->writeAttribute('draw:dots1', '1'); + $objWriter->writeAttribute('draw:dots1-length', '0.035cm'); + break; + } + $objWriter->endElement(); + $this->arrayStrokeDash[] = $oBorder->getDashStyle(); + } + } + } + + /** + * Write the default style information for a Table shape + * + * @param XMLWriter $objWriter + * @param Table $shape + */ + protected function writeTableStyle(XMLWriter $objWriter, Table $shape) + { + foreach ($shape->getRows() as $row) { + foreach ($row->getCells() as $cell) { + if ($cell->getFill()->getFillType() == Fill::FILL_GRADIENT_LINEAR) { + if (!in_array($cell->getFill()->getHashCode(), $this->arrayGradient)) { + $this->writeGradientFill($objWriter, $cell->getFill()); + } + } + } + } + } + + /** + * Writes the style information for a group of shapes + * + * @param XMLWriter $objWriter + * @param Group $group + */ + protected function writeGroupStyle(XMLWriter $objWriter, Group $group) + { + $shapes = $group->getShapeCollection(); + foreach ($shapes as $shape) { + if ($shape instanceof Table) { + $this->writeTableStyle($objWriter, $shape); + } elseif ($shape instanceof Group) { + $this->writeGroupStyle($objWriter, $shape); + } + } + } + + /** + * Write the gradient style + * @param XMLWriter $objWriter + * @param Fill $oFill + */ + protected function writeGradientFill(XMLWriter $objWriter, Fill $oFill) + { + $objWriter->startElement('draw:gradient'); + $objWriter->writeAttribute('draw:name', 'gradient_'.$oFill->getHashCode()); + $objWriter->writeAttribute('draw:display-name', 'gradient_'.$oFill->getHashCode()); + $objWriter->writeAttribute('draw:style', 'linear'); + $objWriter->writeAttribute('draw:start-intensity', '100%'); + $objWriter->writeAttribute('draw:end-intensity', '100%'); + $objWriter->writeAttribute('draw:start-color', '#'.$oFill->getStartColor()->getRGB()); + $objWriter->writeAttribute('draw:end-color', '#'.$oFill->getEndColor()->getRGB()); + $objWriter->writeAttribute('draw:border', '0%'); + $objWriter->writeAttribute('draw:angle', $oFill->getRotation() - 90); + $objWriter->endElement(); + $this->arrayGradient[] = $oFill->getHashCode(); + } + + /** + * Write the background image style + * @param XMLWriter $objWriter + * @param Image $oBkgImage + * @param $numSlide + */ + protected function writeBackgroundStyle(XMLWriter $objWriter, Image $oBkgImage, $numSlide) + { + $objWriter->startElement('draw:fill-image'); + $objWriter->writeAttribute('draw:name', 'background_'.$numSlide); + $objWriter->writeAttribute('xlink:href', 'Pictures/'.str_replace(' ', '_', $oBkgImage->getIndexedFilename($numSlide))); + $objWriter->writeAttribute('xlink:type', 'simple'); + $objWriter->writeAttribute('xlink:show', 'embed'); + $objWriter->writeAttribute('xlink:actuate', 'onLoad'); + $objWriter->endElement(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php new file mode 100644 index 0000000..b481a56 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php @@ -0,0 +1,42 @@ +getPresentation()->getPresentationProperties()->getThumbnailPath(); + if ($pathThumbnail) { + // Size : 128x128 pixel + // PNG : 8bit, non-interlaced with full alpha transparency + $gdImage = imagecreatefromstring(file_get_contents($pathThumbnail)); + if ($gdImage) { + list($width, $height) = getimagesize($pathThumbnail); + + $gdRender = imagecreatetruecolor(128, 128); + $colorBgAlpha = imagecolorallocatealpha($gdRender, 0, 0, 0, 127); + imagecolortransparent($gdRender, $colorBgAlpha); + imagefill($gdRender, 0, 0, $colorBgAlpha); + imagecopyresampled($gdRender, $gdImage, 0, 0, 0, 0, 128, 128, $width, $height); + imagetruecolortopalette($gdRender, false, 255); + imagesavealpha($gdRender, true); + + ob_start(); + imagepng($gdRender); + $imageContents = ob_get_contents(); + ob_end_clean(); + + imagedestroy($gdRender); + imagedestroy($gdImage); + + $this->getZip()->addFromString('Thumbnails/thumbnail.png', $imageContents); + } + } + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007.php new file mode 100644 index 0000000..2b6e2c2 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007.php @@ -0,0 +1,211 @@ +setPhpPresentation($pPhpPresentation); + + // Set up disk caching location + $this->diskCachingDir = './'; + + // Set layout pack + $this->layoutPack = new PackDefault(); + + // Set HashTable variables + $this->oDrawingHashTable = new HashTable(); + + $this->setZipAdapter(new ZipArchiveAdapter()); + } + + /** + * Save PhpPresentation to file + * + * @param string $pFilename + * @throws \Exception + */ + public function save($pFilename) + { + if (empty($pFilename)) { + throw new \Exception("Filename is empty"); + } + $oPresentation = $this->getPhpPresentation(); + + // If $pFilename is php://output or php://stdout, make it a temporary file... + $originalFilename = $pFilename; + if (strtolower($pFilename) == 'php://output' || strtolower($pFilename) == 'php://stdout') { + $pFilename = @tempnam('./', 'phppttmp'); + if ($pFilename == '') { + $pFilename = $originalFilename; + } + } + + // Create drawing dictionary + $this->getDrawingHashTable()->addFromSource($this->allDrawings()); + + $oZip = $this->getZipAdapter(); + $oZip->open($pFilename); + + $oDir = new DirectoryIterator(dirname(__FILE__).DIRECTORY_SEPARATOR.'PowerPoint2007'); + $arrayFiles = array(); + foreach ($oDir as $oFile) { + if (!$oFile->isFile()) { + continue; + } + + $class = __NAMESPACE__ . '\\PowerPoint2007\\' . $oFile->getBasename('.php'); + $o = new \ReflectionClass($class); + + if ($o->isAbstract() || !$o->isSubclassOf('PhpOffice\PhpPresentation\Writer\PowerPoint2007\AbstractDecoratorWriter')) { + continue; + } + $arrayFiles[$oFile->getBasename('.php')] = $o; + } + + ksort($arrayFiles); + + foreach ($arrayFiles as $o) { + $oService = $o->newInstance(); + $oService->setZip($oZip); + $oService->setPresentation($oPresentation); + $oService->setDrawingHashTable($this->getDrawingHashTable()); + $oZip = $oService->render(); + unset($oService); + } + + // Close file + $oZip->close(); + + // If a temporary file was used, copy it to the correct file stream + if ($originalFilename != $pFilename) { + if (copy($pFilename, $originalFilename) === false) { + throw new \Exception("Could not copy temporary zip file $pFilename to $originalFilename."); + } + if (@unlink($pFilename) === false) { + throw new \Exception('The file '.$pFilename.' could not be removed.'); + } + } + } + + /** + * Get use disk caching where possible? + * + * @return boolean + */ + public function hasDiskCaching() + { + return $this->useDiskCaching; + } + + /** + * Set use disk caching where possible? + * + * @param boolean $pValue + * @param string $pDirectory Disk caching directory + * @throws \Exception + * @return \PhpOffice\PhpPresentation\Writer\PowerPoint2007 + */ + public function setUseDiskCaching($pValue = false, $pDirectory = null) + { + $this->useDiskCaching = $pValue; + + if (!is_null($pDirectory)) { + if (!is_dir($pDirectory)) { + throw new \Exception("Directory does not exist: $pDirectory"); + } + $this->diskCachingDir = $pDirectory; + } + + return $this; + } + + /** + * Get disk caching directory + * + * @return string + */ + public function getDiskCachingDirectory() + { + return $this->diskCachingDir; + } + + /** + * Get layout pack to use + * + * @deprecated 0.7 + * @return \PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\AbstractLayoutPack + */ + public function getLayoutPack() + { + return $this->layoutPack; + } + + /** + * Set layout pack to use + * + * @deprecated 0.7 + * @param \PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\AbstractLayoutPack $pValue + * @return \PhpOffice\PhpPresentation\Writer\PowerPoint2007 + */ + public function setLayoutPack(AbstractLayoutPack $pValue = null) + { + $this->layoutPack = $pValue; + + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php new file mode 100644 index 0000000..c8d0b7a --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php @@ -0,0 +1,311 @@ +startElement('Relationship'); + $objWriter->writeAttribute('Id', $pId); + $objWriter->writeAttribute('Type', $pType); + $objWriter->writeAttribute('Target', $pTarget); + + if ($pTargetMode != '') { + $objWriter->writeAttribute('TargetMode', $pTargetMode); + } + + $objWriter->endElement(); + } + + /** + * Write Border + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Style\Border $pBorder Border + * @param string $pElementName Element name + * @throws \Exception + */ + protected function writeBorder(XMLWriter $objWriter, $pBorder, $pElementName = 'L') + { + if (!($pBorder instanceof Border)) { + return; + } + + if ($pBorder->getLineStyle() == Border::LINE_NONE && $pElementName == '') { + return; + } + + // Line style + $lineStyle = $pBorder->getLineStyle(); + if ($lineStyle == Border::LINE_NONE) { + $lineStyle = Border::LINE_SINGLE; + } + + // Line width + $lineWidth = 12700 * $pBorder->getLineWidth(); + + // a:ln $pElementName + $objWriter->startElement('a:ln' . $pElementName); + $objWriter->writeAttribute('w', $lineWidth); + $objWriter->writeAttribute('cap', 'flat'); + $objWriter->writeAttribute('cmpd', $lineStyle); + $objWriter->writeAttribute('algn', 'ctr'); + + // Fill? + if ($pBorder->getLineStyle() == Border::LINE_NONE) { + // a:noFill + $objWriter->writeElement('a:noFill', null); + } else { + // a:solidFill + $objWriter->startElement('a:solidFill'); + $this->writeColor($objWriter, $pBorder->getColor()); + $objWriter->endElement(); + } + + // Dash + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', $pBorder->getDashStyle()); + $objWriter->endElement(); + + // a:round + $objWriter->writeElement('a:round', null); + + // a:headEnd + $objWriter->startElement('a:headEnd'); + $objWriter->writeAttribute('type', 'none'); + $objWriter->writeAttribute('w', 'med'); + $objWriter->writeAttribute('len', 'med'); + $objWriter->endElement(); + + // a:tailEnd + $objWriter->startElement('a:tailEnd'); + $objWriter->writeAttribute('type', 'none'); + $objWriter->writeAttribute('w', 'med'); + $objWriter->writeAttribute('len', 'med'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * @param XMLWriter $objWriter + * @param Color $color + * @param int|null $alpha + */ + protected function writeColor(XMLWriter $objWriter, Color $color, $alpha = null) + { + if (is_null($alpha)) { + $alpha = $color->getAlpha(); + } + + // a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $color->getRGB()); + + // a:alpha + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', $alpha * 1000); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Fill + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Style\Fill $pFill Fill style + * @throws \Exception + */ + protected function writeFill(XMLWriter $objWriter, $pFill) + { + if (! $pFill instanceof Fill) { + return; + } + + // Is it a fill? + if ($pFill->getFillType() == Fill::FILL_NONE) { + $objWriter->writeElement('a:noFill'); + return; + } + + // Is it a solid fill? + if ($pFill->getFillType() == Fill::FILL_SOLID) { + $this->writeSolidFill($objWriter, $pFill); + return; + } + + // Check if this is a pattern type or gradient type + if ($pFill->getFillType() == Fill::FILL_GRADIENT_LINEAR || $pFill->getFillType() == Fill::FILL_GRADIENT_PATH) { + // Gradient fill + $this->writeGradientFill($objWriter, $pFill); + } else { + // Pattern fill + $this->writePatternFill($objWriter, $pFill); + } + } + + /** + * Write Solid Fill + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Style\Fill $pFill Fill style + * @throws \Exception + */ + protected function writeSolidFill(XMLWriter $objWriter, Fill $pFill) + { + // a:gradFill + $objWriter->startElement('a:solidFill'); + $this->writeColor($objWriter, $pFill->getStartColor()); + $objWriter->endElement(); + } + + /** + * Write Gradient Fill + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Style\Fill $pFill Fill style + * @throws \Exception + */ + protected function writeGradientFill(XMLWriter $objWriter, Fill $pFill) + { + // a:gradFill + $objWriter->startElement('a:gradFill'); + + // a:gsLst + $objWriter->startElement('a:gsLst'); + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + $this->writeColor($objWriter, $pFill->getStartColor()); + $objWriter->endElement(); + + // a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + $this->writeColor($objWriter, $pFill->getEndColor()); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:lin + $objWriter->startElement('a:lin'); + $objWriter->writeAttribute('ang', CommonDrawing::degreesToAngle($pFill->getRotation())); + $objWriter->writeAttribute('scaled', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Pattern Fill + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Style\Fill $pFill Fill style + * @throws \Exception + */ + protected function writePatternFill(XMLWriter $objWriter, Fill $pFill) + { + // a:pattFill + $objWriter->startElement('a:pattFill'); + + // fgClr + $objWriter->startElement('a:fgClr'); + + $this->writeColor($objWriter, $pFill->getStartColor()); + + $objWriter->endElement(); + + // bgClr + $objWriter->startElement('a:bgClr'); + + $this->writeColor($objWriter, $pFill->getEndColor()); + + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Outline + * @param XMLWriter $objWriter + * @param Outline $oOutline + * @throws \Exception + */ + protected function writeOutline(XMLWriter $objWriter, $oOutline) + { + if (!$oOutline instanceof Outline) { + return; + } + // Width : pts + $width = $oOutline->getWidth(); + // Width : pts => px + $width = CommonDrawing::pointsToPixels($width); + // Width : px => emu + $width = CommonDrawing::pixelsToEmu($width); + + // a:ln + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', $width); + + // Fill + $this->writeFill($objWriter, $oOutline->getFill()); + + // > a:ln + $objWriter->endElement(); + } + + /** + * Determine absolute zip path + * + * @param string $path + * @return string + */ + protected function absoluteZipPath($path) + { + $path = str_replace(array( + '/', + '\\' + ), DIRECTORY_SEPARATOR, $path); + $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); + $absolutes = array(); + foreach ($parts as $part) { + if ('.' == $part) { + continue; + } + if ('..' == $part) { + array_pop($absolutes); + } else { + $absolutes[] = $part; + } + } + + return implode('/', $absolutes); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php new file mode 100644 index 0000000..0c220f0 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php @@ -0,0 +1,1611 @@ +getShapeCollection()->count() > 0) { + // Loop trough images and write relationships + $iterator = $pSlideMaster->getShapeCollection()->getIterator(); + while ($iterator->valid()) { + if ($iterator->current() instanceof ShapeDrawingFile || $iterator->current() instanceof ShapeDrawingGd) { + // Write relationship for image drawing + $this->writeRelationship( + $objWriter, + $relId, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + '../media/' . str_replace(' ', '_', $iterator->current()->getIndexedFilename()) + ); + $iterator->current()->relationId = 'rId' . $relId; + ++$relId; + } elseif ($iterator->current() instanceof ShapeChart) { + // Write relationship for chart drawing + $this->writeRelationship( + $objWriter, + $relId, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', + '../charts/' . $iterator->current()->getIndexedFilename() + ); + $iterator->current()->relationId = 'rId' . $relId; + ++$relId; + } elseif ($iterator->current() instanceof Group) { + $iterator2 = $iterator->current()->getShapeCollection()->getIterator(); + while ($iterator2->valid()) { + if ($iterator2->current() instanceof ShapeDrawingFile || + $iterator2->current() instanceof ShapeDrawingGd + ) { + // Write relationship for image drawing + $this->writeRelationship( + $objWriter, + $relId, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + '../media/' . str_replace(' ', '_', $iterator2->current()->getIndexedFilename()) + ); + $iterator2->current()->relationId = 'rId' . $relId; + ++$relId; + } elseif ($iterator2->current() instanceof ShapeChart) { + // Write relationship for chart drawing + $this->writeRelationship( + $objWriter, + $relId, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', + '../charts/' . $iterator2->current()->getIndexedFilename() + ); + $iterator2->current()->relationId = 'rId' . $relId; + ++$relId; + } + $iterator2->next(); + } + } + $iterator->next(); + } + } + + return $relId; + } + + /** + * @param XMLWriter $objWriter + * @param \ArrayObject|\PhpOffice\PhpPresentation\AbstractShape[] $shapes + * @param int $shapeId + * @throws \Exception + */ + protected function writeShapeCollection(XMLWriter $objWriter, $shapes = array(), &$shapeId = 0) + { + if (count($shapes) == 0) { + return; + } + foreach ($shapes as $shape) { + // Increment $shapeId + ++$shapeId; + // Check type + if ($shape instanceof RichText) { + $this->writeShapeText($objWriter, $shape, $shapeId); + } elseif ($shape instanceof ShapeTable) { + $this->writeShapeTable($objWriter, $shape, $shapeId); + } elseif ($shape instanceof Line) { + $this->writeShapeLine($objWriter, $shape, $shapeId); + } elseif ($shape instanceof ShapeChart) { + $this->writeShapeChart($objWriter, $shape, $shapeId); + } elseif ($shape instanceof AbstractGraphic) { + $this->writeShapePic($objWriter, $shape, $shapeId); + } elseif ($shape instanceof Group) { + $this->writeShapeGroup($objWriter, $shape, $shapeId); + } elseif ($shape instanceof Comment) { + } else { + throw new \Exception("Unknown Shape type: {get_class($shape)}"); + } + } + } + + /** + * Write txt + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\RichText $shape + * @param int $shapeId + * @throws \Exception + */ + protected function writeShapeText(XMLWriter $objWriter, RichText $shape, $shapeId) + { + // p:sp + $objWriter->startElement('p:sp'); + // p:sp\p:nvSpPr + $objWriter->startElement('p:nvSpPr'); + // p:sp\p:nvSpPr\p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', $shapeId); + if ($shape->isPlaceholder()) { + $objWriter->writeAttribute('name', 'Placeholder for ' . $shape->getPlaceholder()->getType()); + } else { + $objWriter->writeAttribute('name', ''); + } + // Hyperlink + if ($shape->hasHyperlink()) { + $this->writeHyperlink($objWriter, $shape); + } + // > p:sp\p:nvSpPr + $objWriter->endElement(); + // p:sp\p:cNvSpPr + $objWriter->startElement('p:cNvSpPr'); + $objWriter->writeAttribute('txBox', '1'); + $objWriter->endElement(); + // p:sp\p:cNvSpPr\p:nvPr + if ($shape->isPlaceholder()) { + $objWriter->startElement('p:nvPr'); + $objWriter->startElement('p:ph'); + $objWriter->writeAttribute('type', $shape->getPlaceholder()->getType()); + if (!is_null($shape->getPlaceholder()->getIdx())) { + $objWriter->writeAttribute('idx', $shape->getPlaceholder()->getIdx()); + } + $objWriter->endElement(); + $objWriter->endElement(); + } else { + $objWriter->writeElement('p:nvPr', null); + } + // > p:sp\p:cNvSpPr + $objWriter->endElement(); + // p:sp\p:spPr + $objWriter->startElement('p:spPr'); + + if (!$shape->isPlaceholder()) { + // p:sp\p:spPr\a:xfrm + $objWriter->startElement('a:xfrm'); + $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle($shape->getRotation())); + // p:sp\p:spPr\a:xfrm\a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); + $objWriter->endElement(); + // p:sp\p:spPr\a:xfrm\a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); + $objWriter->endElement(); + // > p:sp\p:spPr\a:xfrm + $objWriter->endElement(); + // p:sp\p:spPr\a:prstGeom + $objWriter->startElement('a:prstGeom'); + $objWriter->writeAttribute('prst', 'rect'); + + // p:sp\p:spPr\a:prstGeom\a:avLst + $objWriter->writeElement('a:avLst'); + + $objWriter->endElement(); + } + $this->writeFill($objWriter, $shape->getFill()); + $this->writeBorder($objWriter, $shape->getBorder(), ''); + $this->writeShadow($objWriter, $shape->getShadow()); + + // > p:sp\p:spPr + $objWriter->endElement(); + // p:txBody + $objWriter->startElement('p:txBody'); + // a:bodyPr + //@link :http://msdn.microsoft.com/en-us/library/documentformat.openxml.drawing.bodyproperties%28v=office.14%29.aspx + $objWriter->startElement('a:bodyPr'); + if (!$shape->isPlaceholder()) { + $verticalAlign = $shape->getActiveParagraph()->getAlignment()->getVertical(); + if ($verticalAlign != Alignment::VERTICAL_BASE && $verticalAlign != Alignment::VERTICAL_AUTO) { + $objWriter->writeAttribute('anchor', $verticalAlign); + } + if ($shape->getWrap() != RichText::WRAP_SQUARE) { + $objWriter->writeAttribute('wrap', $shape->getWrap()); + } + $objWriter->writeAttribute('rtlCol', '0'); + if ($shape->getHorizontalOverflow() != RichText::OVERFLOW_OVERFLOW) { + $objWriter->writeAttribute('horzOverflow', $shape->getHorizontalOverflow()); + } + if ($shape->getVerticalOverflow() != RichText::OVERFLOW_OVERFLOW) { + $objWriter->writeAttribute('vertOverflow', $shape->getVerticalOverflow()); + } + if ($shape->isUpright()) { + $objWriter->writeAttribute('upright', '1'); + } + if ($shape->isVertical()) { + $objWriter->writeAttribute('vert', 'vert'); + } + $objWriter->writeAttribute('bIns', CommonDrawing::pixelsToEmu($shape->getInsetBottom())); + $objWriter->writeAttribute('lIns', CommonDrawing::pixelsToEmu($shape->getInsetLeft())); + $objWriter->writeAttribute('rIns', CommonDrawing::pixelsToEmu($shape->getInsetRight())); + $objWriter->writeAttribute('tIns', CommonDrawing::pixelsToEmu($shape->getInsetTop())); + if ($shape->getColumns() <> 1) { + $objWriter->writeAttribute('numCol', $shape->getColumns()); + } + // a:spAutoFit + $objWriter->startElement('a:' . $shape->getAutoFit()); + if ($shape->getAutoFit() == RichText::AUTOFIT_NORMAL) { + if (!is_null($shape->getFontScale())) { + $objWriter->writeAttribute('fontScale', $shape->getFontScale() * 1000); + } + if (!is_null($shape->getLineSpaceReduction())) { + $objWriter->writeAttribute('lnSpcReduction', $shape->getLineSpaceReduction() * 1000); + } + } + $objWriter->endElement(); + } + $objWriter->endElement(); + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + if ($shape->isPlaceholder() && + ($shape->getPlaceholder()->getType() == Placeholder::PH_TYPE_SLIDENUM || + $shape->getPlaceholder()->getType() == Placeholder::PH_TYPE_DATETIME) + ) { + $objWriter->startElement('a:p'); + $objWriter->startElement('a:fld'); + $objWriter->writeAttribute('id', $this->getGUID()); + $objWriter->writeAttribute('type', ( + $shape->getPlaceholder()->getType() == Placeholder::PH_TYPE_SLIDENUM ? 'slidenum' : 'datetime')); + $objWriter->writeElement('a:t', ( + $shape->getPlaceholder()->getType() == Placeholder::PH_TYPE_SLIDENUM ? '' : '03-04-05')); + $objWriter->endElement(); + $objWriter->endElement(); + } else { + // Write paragraphs + $this->writeParagraphs($objWriter, $shape->getParagraphs()); + } + $objWriter->endElement(); + $objWriter->endElement(); + } + + /** + * Write table + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Table $shape + * @param int $shapeId + * @throws \Exception + */ + protected function writeShapeTable(XMLWriter $objWriter, ShapeTable $shape, $shapeId) + { + // p:graphicFrame + $objWriter->startElement('p:graphicFrame'); + // p:graphicFrame/p:nvGraphicFramePr + $objWriter->startElement('p:nvGraphicFramePr'); + // p:graphicFrame/p:nvGraphicFramePr/p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', $shapeId); + $objWriter->writeAttribute('name', $shape->getName()); + $objWriter->writeAttribute('descr', $shape->getDescription()); + $objWriter->endElement(); + // p:graphicFrame/p:nvGraphicFramePr/p:cNvGraphicFramePr + $objWriter->startElement('p:cNvGraphicFramePr'); + // p:graphicFrame/p:nvGraphicFramePr/p:cNvGraphicFramePr/a:graphicFrameLocks + $objWriter->startElement('a:graphicFrameLocks'); + $objWriter->writeAttribute('noGrp', '1'); + $objWriter->endElement(); + // p:graphicFrame/p:nvGraphicFramePr/p:cNvGraphicFramePr/ + $objWriter->endElement(); + // p:graphicFrame/p:nvGraphicFramePr/p:nvPr + $objWriter->startElement('p:nvPr'); + if ($shape->isPlaceholder()) { + $objWriter->startElement('p:ph'); + $objWriter->writeAttribute('type', $shape->getPlaceholder()->getType()); + $objWriter->endElement(); + } + $objWriter->endElement(); + // p:graphicFrame/p:nvGraphicFramePr/ + $objWriter->endElement(); + // p:graphicFrame/p:xfrm + $objWriter->startElement('p:xfrm'); + // p:graphicFrame/p:xfrm/a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); + $objWriter->endElement(); + // p:graphicFrame/p:xfrm/a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); + $objWriter->endElement(); + // p:graphicFrame/p:xfrm/ + $objWriter->endElement(); + // p:graphicFrame/a:graphic + $objWriter->startElement('a:graphic'); + // p:graphicFrame/a:graphic/a:graphicData + $objWriter->startElement('a:graphicData'); + $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/table'); + // p:graphicFrame/a:graphic/a:graphicData/a:tbl + $objWriter->startElement('a:tbl'); + // p:graphicFrame/a:graphic/a:graphicData/a:tbl/a:tblPr + $objWriter->startElement('a:tblPr'); + $objWriter->writeAttribute('firstRow', '1'); + $objWriter->writeAttribute('bandRow', '1'); + $objWriter->endElement(); + // p:graphicFrame/a:graphic/a:graphicData/a:tbl/a:tblGrid + $objWriter->startElement('a:tblGrid'); + // Write cell widths + $countCells = count($shape->getRow(0)->getCells()); + for ($cell = 0; $cell < $countCells; $cell++) { + // p:graphicFrame/a:graphic/a:graphicData/a:tbl/a:tblGrid/a:gridCol + $objWriter->startElement('a:gridCol'); + // Calculate column width + $width = $shape->getRow(0)->getCell($cell)->getWidth(); + if ($width == 0) { + $colCount = count($shape->getRow(0)->getCells()); + $totalWidth = $shape->getWidth(); + $width = $totalWidth / $colCount; + } + $objWriter->writeAttribute('w', CommonDrawing::pixelsToEmu($width)); + $objWriter->endElement(); + } + // p:graphicFrame/a:graphic/a:graphicData/a:tbl/a:tblGrid/ + $objWriter->endElement(); + // Colspan / rowspan containers + $colSpan = array(); + $rowSpan = array(); + // Default border style + $defaultBorder = new Border(); + // Write rows + $countRows = count($shape->getRows()); + for ($row = 0; $row < $countRows; $row++) { + // p:graphicFrame/a:graphic/a:graphicData/a:tbl/a:tr + $objWriter->startElement('a:tr'); + $objWriter->writeAttribute('h', CommonDrawing::pixelsToEmu($shape->getRow($row)->getHeight())); + // Write cells + $countCells = count($shape->getRow($row)->getCells()); + for ($cell = 0; $cell < $countCells; $cell++) { + // Current cell + $currentCell = $shape->getRow($row)->getCell($cell); + // Next cell right + $nextCellRight = $shape->getRow($row)->getCell($cell + 1, true); + // Next cell below + $nextRowBelow = $shape->getRow($row + 1, true); + $nextCellBelow = null; + if ($nextRowBelow != null) { + $nextCellBelow = $nextRowBelow->getCell($cell, true); + } + // a:tc + $objWriter->startElement('a:tc'); + // Colspan + if ($currentCell->getColSpan() > 1) { + $objWriter->writeAttribute('gridSpan', $currentCell->getColSpan()); + $colSpan[$row] = $currentCell->getColSpan() - 1; + } elseif (isset($colSpan[$row]) && $colSpan[$row] > 0) { + $colSpan[$row]--; + $objWriter->writeAttribute('hMerge', '1'); + } + // Rowspan + if ($currentCell->getRowSpan() > 1) { + $objWriter->writeAttribute('rowSpan', $currentCell->getRowSpan()); + $rowSpan[$cell] = $currentCell->getRowSpan() - 1; + } elseif (isset($rowSpan[$cell]) && $rowSpan[$cell] > 0) { + $rowSpan[$cell]--; + $objWriter->writeAttribute('vMerge', '1'); + } + // a:txBody + $objWriter->startElement('a:txBody'); + // a:txBody/a:bodyPr + $objWriter->startElement('a:bodyPr'); + $objWriter->writeAttribute('wrap', 'square'); + $objWriter->writeAttribute('rtlCol', '0'); + // a:txBody/a:bodyPr/a:spAutoFit + $objWriter->writeElement('a:spAutoFit', null); + // a:txBody/a:bodyPr/ + $objWriter->endElement(); + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + // Write paragraphs + $this->writeParagraphs($objWriter, $currentCell->getParagraphs()); + $objWriter->endElement(); + // a:tcPr + $objWriter->startElement('a:tcPr'); + $firstParagraph = $currentCell->getParagraph(0); + $firstParagraphAlignment = $firstParagraph->getAlignment(); + + // Text Direction + $textDirection = $firstParagraphAlignment->getTextDirection(); + if ($textDirection != Alignment::TEXT_DIRECTION_HORIZONTAL) { + $objWriter->writeAttribute('vert', $textDirection); + } + // Alignment (horizontal) + $verticalAlign = $firstParagraphAlignment->getVertical(); + if ($verticalAlign != Alignment::VERTICAL_BASE && $verticalAlign != Alignment::VERTICAL_AUTO) { + $objWriter->writeAttribute('anchor', $verticalAlign); + } + + // Margins + $objWriter->writeAttribute('marL', CommonDrawing::pixelsToEmu($firstParagraphAlignment->getMarginLeft())); + $objWriter->writeAttribute('marR', CommonDrawing::pixelsToEmu($firstParagraphAlignment->getMarginRight())); + $objWriter->writeAttribute('marT', CommonDrawing::pixelsToEmu($firstParagraphAlignment->getMarginTop())); + $objWriter->writeAttribute('marB', CommonDrawing::pixelsToEmu($firstParagraphAlignment->getMarginBottom())); + + // Determine borders + $borderLeft = $currentCell->getBorders()->getLeft(); + $borderRight = $currentCell->getBorders()->getRight(); + $borderTop = $currentCell->getBorders()->getTop(); + $borderBottom = $currentCell->getBorders()->getBottom(); + $borderDiagonalDown = $currentCell->getBorders()->getDiagonalDown(); + $borderDiagonalUp = $currentCell->getBorders()->getDiagonalUp(); + // Fix PowerPoint implementation + if (!is_null($nextCellRight) + && $nextCellRight->getBorders()->getRight()->getHashCode() != $defaultBorder->getHashCode() + ) { + $borderRight = $nextCellRight->getBorders()->getLeft(); + } + if (!is_null($nextCellBelow) + && $nextCellBelow->getBorders()->getBottom()->getHashCode() != $defaultBorder->getHashCode() + ) { + $borderBottom = $nextCellBelow->getBorders()->getTop(); + } + // Write borders + $this->writeBorder($objWriter, $borderLeft, 'L'); + $this->writeBorder($objWriter, $borderRight, 'R'); + $this->writeBorder($objWriter, $borderTop, 'T'); + $this->writeBorder($objWriter, $borderBottom, 'B'); + $this->writeBorder($objWriter, $borderDiagonalDown, 'TlToBr'); + $this->writeBorder($objWriter, $borderDiagonalUp, 'BlToTr'); + // Fill + $this->writeFill($objWriter, $currentCell->getFill()); + $objWriter->endElement(); + $objWriter->endElement(); + } + $objWriter->endElement(); + } + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + + /** + * Write paragraphs + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] $paragraphs + * @param bool $bIsPlaceholder + * @throws \Exception + */ + protected function writeParagraphs(XMLWriter $objWriter, $paragraphs, $bIsPlaceholder = false) + { + // Loop trough paragraphs + foreach ($paragraphs as $paragraph) { + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + if (!$bIsPlaceholder) { + $objWriter->startElement('a:pPr'); + $objWriter->writeAttribute('algn', $paragraph->getAlignment()->getHorizontal()); + $objWriter->writeAttribute('fontAlgn', $paragraph->getAlignment()->getVertical()); + $objWriter->writeAttribute('marL', CommonDrawing::pixelsToEmu($paragraph->getAlignment()->getMarginLeft())); + $objWriter->writeAttribute('marR', CommonDrawing::pixelsToEmu($paragraph->getAlignment()->getMarginRight())); + $objWriter->writeAttribute('indent', CommonDrawing::pixelsToEmu($paragraph->getAlignment()->getIndent())); + $objWriter->writeAttribute('lvl', $paragraph->getAlignment()->getLevel()); + + $objWriter->startElement('a:lnSpc'); + $objWriter->startElement('a:spcPct'); + $objWriter->writeAttribute('val', $paragraph->getLineSpacing() * 1000); + $objWriter->endElement(); + $objWriter->endElement(); + + // Bullet type specified? + if ($paragraph->getBulletStyle()->getBulletType() != Bullet::TYPE_NONE) { + // Color + // a:buClr must be before a:buFont (else PowerPoint crashes at launch) + if ($paragraph->getBulletStyle()->getBulletColor() instanceof Color) { + $objWriter->startElement('a:buClr'); + $this->writeColor($objWriter, $paragraph->getBulletStyle()->getBulletColor()); + $objWriter->endElement(); + } + + // a:buFont + $objWriter->startElement('a:buFont'); + $objWriter->writeAttribute('typeface', $paragraph->getBulletStyle()->getBulletFont()); + $objWriter->endElement(); + + if ($paragraph->getBulletStyle()->getBulletType() == Bullet::TYPE_BULLET) { + // a:buChar + $objWriter->startElement('a:buChar'); + $objWriter->writeAttribute('char', $paragraph->getBulletStyle()->getBulletChar()); + $objWriter->endElement(); + } elseif ($paragraph->getBulletStyle()->getBulletType() == Bullet::TYPE_NUMERIC) { + // a:buAutoNum + $objWriter->startElement('a:buAutoNum'); + $objWriter->writeAttribute('type', $paragraph->getBulletStyle()->getBulletNumericStyle()); + if ($paragraph->getBulletStyle()->getBulletNumericStartAt() != 1) { + $objWriter->writeAttribute('startAt', $paragraph->getBulletStyle()->getBulletNumericStartAt()); + } + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + } + + // Loop trough rich text elements + $elements = $paragraph->getRichTextElements(); + foreach ($elements as $element) { + if ($element instanceof BreakElement) { + // a:br + $objWriter->writeElement('a:br', null); + } elseif ($element instanceof Run || $element instanceof TextElement) { + // a:r + $objWriter->startElement('a:r'); + + // a:rPr + if ($element instanceof Run && !$bIsPlaceholder) { + // a:rPr + $objWriter->startElement('a:rPr'); + + // Lang + $objWriter->writeAttribute('lang', ($element->getLanguage() ? $element->getLanguage() : 'en-US')); + $objWriter->writeAttributeIf($element->getFont()->isBold(), 'b', '1'); + $objWriter->writeAttributeIf($element->getFont()->isItalic(), 'i', '1'); + $objWriter->writeAttributeIf($element->getFont()->isStrikethrough(), 'strike', 'sngStrike'); + $objWriter->writeAttribute('sz', ($element->getFont()->getSize() * 100)); + $objWriter->writeAttribute('spc', $element->getFont()->getCharacterSpacing()); + $objWriter->writeAttribute('u', $element->getFont()->getUnderline()); + $objWriter->writeAttributeIf($element->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($element->getFont()->isSubScript(), 'baseline', '-250000'); + + // Color - a:solidFill + $objWriter->startElement('a:solidFill'); + $this->writeColor($objWriter, $element->getFont()->getColor()); + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $element->getFont()->getName()); + $objWriter->endElement(); + + // a:hlinkClick + $this->writeHyperlink($objWriter, $element); + + $objWriter->endElement(); + } + + // t + $objWriter->startElement('a:t'); + $objWriter->writeCData(Text::controlCharacterPHP2OOXML($element->getText())); + $objWriter->endElement(); + + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + } + } + + /** + * Write Line Shape + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Line $shape + * @param int $shapeId + * @throws \Exception + */ + protected function writeShapeLine(XMLWriter $objWriter, Line $shape, $shapeId) + { + // p:sp + $objWriter->startElement('p:cxnSp'); + // p:nvSpPr + $objWriter->startElement('p:nvCxnSpPr'); + // p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', $shapeId); + $objWriter->writeAttribute('name', ''); + $objWriter->endElement(); + // p:cNvCxnSpPr + $objWriter->writeElement('p:cNvCxnSpPr', null); + // p:nvPr + $objWriter->startElement('p:nvPr'); + if ($shape->isPlaceholder()) { + $objWriter->startElement('p:ph'); + $objWriter->writeAttribute('type', $shape->getPlaceholder()->getType()); + $objWriter->endElement(); + } + $objWriter->endElement(); + $objWriter->endElement(); + // p:spPr + $objWriter->startElement('p:spPr'); + // a:xfrm + $objWriter->startElement('a:xfrm'); + if ($shape->getWidth() >= 0 && $shape->getHeight() >= 0) { + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); + $objWriter->endElement(); + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); + $objWriter->endElement(); + } elseif ($shape->getWidth() < 0 && $shape->getHeight() < 0) { + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX() + $shape->getWidth())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY() + $shape->getHeight())); + $objWriter->endElement(); + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu(-$shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu(-$shape->getHeight())); + $objWriter->endElement(); + } elseif ($shape->getHeight() < 0) { + $objWriter->writeAttribute('flipV', 1); + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY() + $shape->getHeight())); + $objWriter->endElement(); + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu(-$shape->getHeight())); + $objWriter->endElement(); + } elseif ($shape->getWidth() < 0) { + $objWriter->writeAttribute('flipV', 1); + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX() + $shape->getWidth())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); + $objWriter->endElement(); + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu(-$shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); + $objWriter->endElement(); + } + $objWriter->endElement(); + // a:prstGeom + $objWriter->startElement('a:prstGeom'); + $objWriter->writeAttribute('prst', 'line'); + + // a:prstGeom/a:avLst + $objWriter->writeElement('a:avLst'); + + $objWriter->endElement(); + $this->writeBorder($objWriter, $shape->getBorder(), ''); + $objWriter->endElement(); + $objWriter->endElement(); + } + + /** + * Write Shadow + * @param XMLWriter $objWriter + * @param Shadow $oShadow + */ + protected function writeShadow(XMLWriter $objWriter, $oShadow) + { + if (!($oShadow instanceof Shadow)) { + return; + } + + if (!$oShadow->isVisible()) { + return; + } + + // a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', CommonDrawing::pixelsToEmu($oShadow->getBlurRadius())); + $objWriter->writeAttribute('dist', CommonDrawing::pixelsToEmu($oShadow->getDistance())); + $objWriter->writeAttribute('dir', CommonDrawing::degreesToAngle($oShadow->getDirection())); + $objWriter->writeAttribute('algn', $oShadow->getAlignment()); + $objWriter->writeAttribute('rotWithShape', '0'); + + $this->writeColor($objWriter, $oShadow->getColor(), $oShadow->getAlpha()); + + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write hyperlink + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\AbstractShape|\PhpOffice\PhpPresentation\Shape\RichText\TextElement $shape + * @throws \Exception + */ + protected function writeHyperlink(XMLWriter $objWriter, $shape) + { + if (!$shape->hasHyperlink()) { + return; + } + // a:hlinkClick + $objWriter->startElement('a:hlinkClick'); + $objWriter->writeAttribute('r:id', $shape->getHyperlink()->relationId); + $objWriter->writeAttribute('tooltip', $shape->getHyperlink()->getTooltip()); + if ($shape->getHyperlink()->isInternal()) { + $objWriter->writeAttribute('action', $shape->getHyperlink()->getUrl()); + } + $objWriter->endElement(); + } + + /** + * Write Note Slide + * @param Note $pNote + * @throws \Exception + * @return string + */ + protected function writeNote(Note $pNote) + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // p:notes + $objWriter->startElement('p:notes'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + // p:notes/p:cSld + $objWriter->startElement('p:cSld'); + + // p:notes/p:cSld/p:spTree + $objWriter->startElement('p:spTree'); + + // p:notes/p:cSld/p:spTree/p:nvGrpSpPr + $objWriter->startElement('p:nvGrpSpPr'); + + // p:notes/p:cSld/p:spTree/p:nvGrpSpPr/p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', '1'); + $objWriter->writeAttribute('name', ''); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:nvGrpSpPr/p:cNvGrpSpPr + $objWriter->writeElement('p:cNvGrpSpPr', null); + + // p:notes/p:cSld/p:spTree/p:nvGrpSpPr/p:nvPr + $objWriter->writeElement('p:nvPr', null); + + // p:notes/p:cSld/p:spTree/p:nvGrpSpPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:grpSpPr + $objWriter->startElement('p:grpSpPr'); + + // p:notes/p:cSld/p:spTree/p:grpSpPr/a:xfrm + $objWriter->startElement('a:xfrm'); + + // p:notes/p:cSld/p:spTree/p:grpSpPr/a:xfrm/a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($pNote->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($pNote->getOffsetY())); + $objWriter->endElement(); // a:off + + // p:notes/p:cSld/p:spTree/p:grpSpPr/a:xfrm/a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($pNote->getExtentX())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($pNote->getExtentY())); + $objWriter->endElement(); // a:ext + + // p:notes/p:cSld/p:spTree/p:grpSpPr/a:xfrm/a:chOff + $objWriter->startElement('a:chOff'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($pNote->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($pNote->getOffsetY())); + $objWriter->endElement(); // a:chOff + + // p:notes/p:cSld/p:spTree/p:grpSpPr/a:xfrm/a:chExt + $objWriter->startElement('a:chExt'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($pNote->getExtentX())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($pNote->getExtentY())); + $objWriter->endElement(); // a:chExt + + // p:notes/p:cSld/p:spTree/p:grpSpPr/a:xfrm + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:grpSpPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1] + $objWriter->startElement('p:sp'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr + $objWriter->startElement('p:nvSpPr'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr/p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', '2'); + $objWriter->writeAttribute('name', 'Slide Image Placeholder 1'); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr/p:cNvSpPr + $objWriter->startElement('p:cNvSpPr'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr/p:cNvSpPr/a:spLocks + $objWriter->startElement('a:spLocks'); + $objWriter->writeAttribute('noGrp', '1'); + $objWriter->writeAttribute('noRot', '1'); + $objWriter->writeAttribute('noChangeAspect', '1'); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr/p:cNvSpPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr/p:nvPr + $objWriter->startElement('p:nvPr'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr/p:nvPr/p:ph + $objWriter->startElement('p:ph'); + $objWriter->writeAttribute('type', 'sldImg'); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr/p:nvPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:nvSpPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr + $objWriter->startElement('p:spPr'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:xfrm + $objWriter->startElement('a:xfrm'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:xfrm/a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', 0); + $objWriter->writeAttribute('y', 0); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:xfrm/a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu(round($pNote->getExtentX() / 2))); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu(round($pNote->getExtentY() / 2))); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:xfrm + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:prstGeom + $objWriter->startElement('a:prstGeom'); + $objWriter->writeAttribute('prst', 'rect'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:prstGeom/a:avLst + $objWriter->writeElement('a:avLst', null); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:prstGeom + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:noFill + $objWriter->writeElement('a:noFill', null); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:ln + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', '12700'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:ln/a:solidFill + $objWriter->startElement('a:solidFill'); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:ln/a:solidFill/a:prstClr + $objWriter->startElement('a:prstClr'); + $objWriter->writeAttribute('val', 'black'); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:ln/a:solidFill + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr/a:ln + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1]/p:spPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[1] + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2] + $objWriter->startElement('p:sp'); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr + $objWriter->startElement('p:nvSpPr'); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr/p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', '3'); + $objWriter->writeAttribute('name', 'Notes Placeholder'); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr/p:cNvSpPr + $objWriter->startElement('p:cNvSpPr'); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr/p:cNvSpPr/a:spLocks + $objWriter->startElement('a:spLocks'); + $objWriter->writeAttribute('noGrp', '1'); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr/p:cNvSpPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr/p:nvPr + $objWriter->startElement('p:nvPr'); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr/p:nvPr/p:ph + $objWriter->startElement('p:ph'); + $objWriter->writeAttribute('type', 'body'); + $objWriter->writeAttribute('idx', '1'); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr/p:nvPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:nvSpPr + $objWriter->endElement(); + + // START notes print below rectangle section + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr + $objWriter->startElement('p:spPr'); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr/a:xfrm + $objWriter->startElement('a:xfrm'); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr/a:xfrm/a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($pNote->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu(round($pNote->getExtentY() / 2) + $pNote->getOffsetY())); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr/a:xfrm/a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', '5486400'); + $objWriter->writeAttribute('cy', '3600450'); + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr/a:xfrm + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr/a:prstGeom + $objWriter->startElement('a:prstGeom'); + $objWriter->writeAttribute('prst', 'rect'); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr/a:prstGeom/a:avLst + $objWriter->writeElement('a:avLst', null); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr/a:prstGeom + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:spPr + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:txBody + $objWriter->startElement('p:txBody'); + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:txBody/a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + // p:notes/p:cSld/p:spTree/p:sp[2]/p:txBody/a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // Loop shapes + $shapes = $pNote->getShapeCollection(); + foreach ($shapes as $shape) { + // Check type + if ($shape instanceof RichText) { + $paragraphs = $shape->getParagraphs(); + $this->writeParagraphs($objWriter, $paragraphs); + } + } + + // p:notes/p:cSld/p:spTree/p:sp[2]/p:txBody + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree/p:sp[2] + $objWriter->endElement(); + + // p:notes/p:cSld/p:spTree + $objWriter->endElement(); + + // p:notes/p:cSld + $objWriter->endElement(); + + // p:notes + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write chart + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart $shape + * @param int $shapeId + */ + protected function writeShapeChart(XMLWriter $objWriter, ShapeChart $shape, $shapeId) + { + // p:graphicFrame + $objWriter->startElement('p:graphicFrame'); + // p:nvGraphicFramePr + $objWriter->startElement('p:nvGraphicFramePr'); + // p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', $shapeId); + $objWriter->writeAttribute('name', $shape->getName()); + $objWriter->writeAttribute('descr', $shape->getDescription()); + $objWriter->endElement(); + // p:cNvGraphicFramePr + $objWriter->writeElement('p:cNvGraphicFramePr', null); + // p:nvPr + $objWriter->startElement('p:nvPr'); + if ($shape->isPlaceholder()) { + $objWriter->startElement('p:ph'); + $objWriter->writeAttribute('type', $shape->getPlaceholder()->getType()); + $objWriter->endElement(); + } + $objWriter->endElement(); + $objWriter->endElement(); + // p:xfrm + $objWriter->startElement('p:xfrm'); + $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle($shape->getRotation())); + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); + $objWriter->endElement(); + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); + $objWriter->endElement(); + $objWriter->endElement(); + // a:graphic + $objWriter->startElement('a:graphic'); + // a:graphicData + $objWriter->startElement('a:graphicData'); + $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + // c:chart + $objWriter->startElement('c:chart'); + $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('r:id', $shape->relationId); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + + /** + * Write pic + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\AbstractGraphic $shape + * @param int $shapeId + * @throws \Exception + */ + protected function writeShapePic(XMLWriter $objWriter, AbstractGraphic $shape, $shapeId) + { + // p:pic + $objWriter->startElement('p:pic'); + // p:nvPicPr + $objWriter->startElement('p:nvPicPr'); + // p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', $shapeId); + $objWriter->writeAttribute('name', $shape->getName()); + $objWriter->writeAttribute('descr', $shape->getDescription()); + // a:hlinkClick + if ($shape->hasHyperlink()) { + $this->writeHyperlink($objWriter, $shape); + } + $objWriter->endElement(); + // p:cNvPicPr + $objWriter->startElement('p:cNvPicPr'); + // a:picLocks + $objWriter->startElement('a:picLocks'); + $objWriter->writeAttribute('noChangeAspect', '1'); + $objWriter->endElement(); + $objWriter->endElement(); + // p:nvPr + $objWriter->startElement('p:nvPr'); + /** + * @link : https://github.com/stefslon/exportToPPTX/blob/master/exportToPPTX.m#L2128 + */ + if ($shape instanceof Media) { + // p:nvPr > a:videoFile + $objWriter->startElement('a:videoFile'); + $objWriter->writeAttribute('r:link', $shape->relationId); + $objWriter->endElement(); + // p:nvPr > p:extLst + $objWriter->startElement('p:extLst'); + // p:nvPr > p:extLst > p:ext + $objWriter->startElement('p:ext'); + $objWriter->writeAttribute('uri', '{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}'); + // p:nvPr > p:extLst > p:ext > p14:media + $objWriter->startElement('p14:media'); + $objWriter->writeAttribute('r:embed', $shape->relationId); + $objWriter->writeAttribute('xmlns:p14', 'http://schemas.microsoft.com/office/powerpoint/2010/main'); + // p:nvPr > p:extLst > p:ext > ##p14:media + $objWriter->endElement(); + // p:nvPr > p:extLst > ##p:ext + $objWriter->endElement(); + // p:nvPr > ##p:extLst + $objWriter->endElement(); + } + // ##p:nvPr + $objWriter->endElement(); + $objWriter->endElement(); + // p:blipFill + $objWriter->startElement('p:blipFill'); + // a:blip + $objWriter->startElement('a:blip'); + $objWriter->writeAttribute('r:embed', $shape->relationId); + $objWriter->endElement(); + // a:stretch + $objWriter->startElement('a:stretch'); + $objWriter->writeElement('a:fillRect', null); + $objWriter->endElement(); + $objWriter->endElement(); + // p:spPr + $objWriter->startElement('p:spPr'); + // a:xfrm + $objWriter->startElement('a:xfrm'); + $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle($shape->getRotation())); + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); + $objWriter->endElement(); + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); + $objWriter->endElement(); + $objWriter->endElement(); + // a:prstGeom + $objWriter->startElement('a:prstGeom'); + $objWriter->writeAttribute('prst', 'rect'); + // a:avLst + $objWriter->writeElement('a:avLst', null); + $objWriter->endElement(); + + $this->writeFill($objWriter, $shape->getFill()); + $this->writeBorder($objWriter, $shape->getBorder(), ''); + $this->writeShadow($objWriter, $shape->getShadow()); + + $objWriter->endElement(); + $objWriter->endElement(); + } + + /** + * Write group + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Group $group + * @param int $shapeId + * @throws \Exception + */ + protected function writeShapeGroup(XMLWriter $objWriter, Group $group, &$shapeId) + { + // p:grpSp + $objWriter->startElement('p:grpSp'); + // p:nvGrpSpPr + $objWriter->startElement('p:nvGrpSpPr'); + // p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('name', 'Group ' . $shapeId++); + $objWriter->writeAttribute('id', $shapeId); + $objWriter->endElement(); // p:cNvPr + // NOTE: Re: $shapeId This seems to be how PowerPoint 2010 does business. + // p:cNvGrpSpPr + $objWriter->writeElement('p:cNvGrpSpPr', null); + // p:nvPr + $objWriter->writeElement('p:nvPr', null); + $objWriter->endElement(); // p:nvGrpSpPr + // p:grpSpPr + $objWriter->startElement('p:grpSpPr'); + // a:xfrm + $objWriter->startElement('a:xfrm'); + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($group->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($group->getOffsetY())); + $objWriter->endElement(); // a:off + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($group->getExtentX())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($group->getExtentY())); + $objWriter->endElement(); // a:ext + // a:chOff + $objWriter->startElement('a:chOff'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($group->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($group->getOffsetY())); + $objWriter->endElement(); // a:chOff + // a:chExt + $objWriter->startElement('a:chExt'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($group->getExtentX())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($group->getExtentY())); + $objWriter->endElement(); // a:chExt + $objWriter->endElement(); // a:xfrm + $objWriter->endElement(); // p:grpSpPr + + $this->writeShapeCollection($objWriter, $group->getShapeCollection(), $shapeId); + + $objWriter->endElement(); // p:grpSp + } + + /** + * @param \PhpOffice\PhpPresentation\Slide\AbstractSlide $pSlide + * @param $objWriter + */ + protected function writeSlideBackground(AbstractSlideAlias $pSlide, XMLWriter $objWriter) + { + if (!($pSlide->getBackground() instanceof Slide\AbstractBackground)) { + return; + } + $oBackground = $pSlide->getBackground(); + // p:bg + $objWriter->startElement('p:bg'); + if ($oBackground instanceof Slide\Background\Color) { + // p:bgPr + $objWriter->startElement('p:bgPr'); + // a:solidFill + $objWriter->startElement('a:solidFill'); + // a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $oBackground->getColor()->getRGB()); + $objWriter->endElement(); + // > a:solidFill + $objWriter->endElement(); + + // p:bgPr/a:effectLst + $objWriter->writeElement('a:effectLst'); + + // > p:bgPr + $objWriter->endElement(); + } + if ($oBackground instanceof Slide\Background\Image) { + // p:bgPr + $objWriter->startElement('p:bgPr'); + // a:blipFill + $objWriter->startElement('a:blipFill'); + // a:blip + $objWriter->startElement('a:blip'); + $objWriter->writeAttribute('r:embed', $oBackground->relationId); + // > a:blipFill + $objWriter->endElement(); + // a:stretch + $objWriter->startElement('a:stretch'); + // a:fillRect + $objWriter->writeElement('a:fillRect'); + // > a:stretch + $objWriter->endElement(); + // > a:blipFill + $objWriter->endElement(); + // > p:bgPr + $objWriter->endElement(); + } + /** + * @link : http://www.officeopenxml.com/prSlide-background.php + */ + if ($oBackground instanceof Slide\Background\SchemeColor) { + // p:bgRef + $objWriter->startElement('p:bgRef'); + $objWriter->writeAttribute('idx', '1001'); + // a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', $oBackground->getSchemeColor()->getValue()); + $objWriter->endElement(); + // > p:bgRef + $objWriter->endElement(); + } + // > p:bg + $objWriter->endElement(); + } + + + /** + * Write Transition Slide + * @link http://officeopenxml.com/prSlide-transitions.php + * @param XMLWriter $objWriter + * @param Slide\Transition $transition + */ + protected function writeSlideTransition(XMLWriter $objWriter, $transition) + { + if (!$transition instanceof Slide\Transition) { + return; + } + $objWriter->startElement('p:transition'); + if (!is_null($transition->getSpeed())) { + $objWriter->writeAttribute('spd', $transition->getSpeed()); + } + $objWriter->writeAttribute('advClick', $transition->hasManualTrigger() ? '1' : '0'); + if ($transition->hasTimeTrigger()) { + $objWriter->writeAttribute('advTm', $transition->getAdvanceTimeTrigger()); + } + + switch ($transition->getTransitionType()) { + case Slide\Transition::TRANSITION_BLINDS_HORIZONTAL: + $objWriter->startElement('p:blinds'); + $objWriter->writeAttribute('dir', 'horz'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_BLINDS_VERTICAL: + $objWriter->startElement('p:blinds'); + $objWriter->writeAttribute('dir', 'vert'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_CHECKER_HORIZONTAL: + $objWriter->startElement('p:checker'); + $objWriter->writeAttribute('dir', 'horz'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_CHECKER_VERTICAL: + $objWriter->startElement('p:checker'); + $objWriter->writeAttribute('dir', 'vert'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_CIRCLE: + $objWriter->writeElement('p:circle'); + break; + case Slide\Transition::TRANSITION_COMB_HORIZONTAL: + $objWriter->startElement('p:comb'); + $objWriter->writeAttribute('dir', 'horz'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COMB_VERTICAL: + $objWriter->startElement('p:comb'); + $objWriter->writeAttribute('dir', 'vert'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COVER_DOWN: + $objWriter->startElement('p:cover'); + $objWriter->writeAttribute('dir', 'd'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COVER_LEFT: + $objWriter->startElement('p:cover'); + $objWriter->writeAttribute('dir', 'l'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COVER_LEFT_DOWN: + $objWriter->startElement('p:cover'); + $objWriter->writeAttribute('dir', 'ld'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COVER_LEFT_UP: + $objWriter->startElement('p:cover'); + $objWriter->writeAttribute('dir', 'lu'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COVER_RIGHT: + $objWriter->startElement('p:cover'); + $objWriter->writeAttribute('dir', 'r'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COVER_RIGHT_DOWN: + $objWriter->startElement('p:cover'); + $objWriter->writeAttribute('dir', 'rd'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COVER_RIGHT_UP: + $objWriter->startElement('p:cover'); + $objWriter->writeAttribute('dir', 'ru'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_COVER_UP: + $objWriter->startElement('p:cover'); + $objWriter->writeAttribute('dir', 'u'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_CUT: + $objWriter->writeElement('p:cut'); + break; + case Slide\Transition::TRANSITION_DIAMOND: + $objWriter->writeElement('p:diamond'); + break; + case Slide\Transition::TRANSITION_DISSOLVE: + $objWriter->writeElement('p:dissolve'); + break; + case Slide\Transition::TRANSITION_FADE: + $objWriter->writeElement('p:fade'); + break; + case Slide\Transition::TRANSITION_NEWSFLASH: + $objWriter->writeElement('p:newsflash'); + break; + case Slide\Transition::TRANSITION_PLUS: + $objWriter->writeElement('p:plus'); + break; + case Slide\Transition::TRANSITION_PULL_DOWN: + $objWriter->startElement('p:pull'); + $objWriter->writeAttribute('dir', 'd'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_PULL_LEFT: + $objWriter->startElement('p:pull'); + $objWriter->writeAttribute('dir', 'l'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_PULL_RIGHT: + $objWriter->startElement('p:pull'); + $objWriter->writeAttribute('dir', 'r'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_PULL_UP: + $objWriter->startElement('p:pull'); + $objWriter->writeAttribute('dir', 'u'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_PUSH_DOWN: + $objWriter->startElement('p:push'); + $objWriter->writeAttribute('dir', 'd'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_PUSH_LEFT: + $objWriter->startElement('p:push'); + $objWriter->writeAttribute('dir', 'l'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_PUSH_RIGHT: + $objWriter->startElement('p:push'); + $objWriter->writeAttribute('dir', 'r'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_PUSH_UP: + $objWriter->startElement('p:push'); + $objWriter->writeAttribute('dir', 'u'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_RANDOM: + $objWriter->writeElement('p:random'); + break; + case Slide\Transition::TRANSITION_RANDOMBAR_HORIZONTAL: + $objWriter->startElement('p:randomBar'); + $objWriter->writeAttribute('dir', 'horz'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_RANDOMBAR_VERTICAL: + $objWriter->startElement('p:randomBar'); + $objWriter->writeAttribute('dir', 'vert'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_SPLIT_IN_HORIZONTAL: + $objWriter->startElement('p:split'); + $objWriter->writeAttribute('dir', 'in'); + $objWriter->writeAttribute('orient', 'horz'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_SPLIT_OUT_HORIZONTAL: + $objWriter->startElement('p:split'); + $objWriter->writeAttribute('dir', 'out'); + $objWriter->writeAttribute('orient', 'horz'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_SPLIT_IN_VERTICAL: + $objWriter->startElement('p:split'); + $objWriter->writeAttribute('dir', 'in'); + $objWriter->writeAttribute('orient', 'vert'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_SPLIT_OUT_VERTICAL: + $objWriter->startElement('p:split'); + $objWriter->writeAttribute('dir', 'out'); + $objWriter->writeAttribute('orient', 'vert'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_STRIPS_LEFT_DOWN: + $objWriter->startElement('p:strips'); + $objWriter->writeAttribute('dir', 'ld'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_STRIPS_LEFT_UP: + $objWriter->startElement('p:strips'); + $objWriter->writeAttribute('dir', 'lu'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_STRIPS_RIGHT_DOWN: + $objWriter->startElement('p:strips'); + $objWriter->writeAttribute('dir', 'rd'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_STRIPS_RIGHT_UP: + $objWriter->startElement('p:strips'); + $objWriter->writeAttribute('dir', 'ru'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_WEDGE: + $objWriter->writeElement('p:wedge'); + break; + case Slide\Transition::TRANSITION_WIPE_DOWN: + $objWriter->startElement('p:wipe'); + $objWriter->writeAttribute('dir', 'd'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_WIPE_LEFT: + $objWriter->startElement('p:wipe'); + $objWriter->writeAttribute('dir', 'l'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_WIPE_RIGHT: + $objWriter->startElement('p:wipe'); + $objWriter->writeAttribute('dir', 'r'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_WIPE_UP: + $objWriter->startElement('p:wipe'); + $objWriter->writeAttribute('dir', 'u'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_ZOOM_IN: + $objWriter->startElement('p:zoom'); + $objWriter->writeAttribute('dir', 'in'); + $objWriter->endElement(); + break; + case Slide\Transition::TRANSITION_ZOOM_OUT: + $objWriter->startElement('p:zoom'); + $objWriter->writeAttribute('dir', 'out'); + $objWriter->endElement(); + break; + } + + $objWriter->endElement(); + } + + private function getGUID() + { + if (function_exists('com_create_guid')) { + return com_create_guid(); + } else { + mt_srand((double)microtime() * 10000);//optional for php 4.2.0 and up. + $charid = strtoupper(md5(uniqid(rand(), true))); + $hyphen = chr(45);// "-" + $uuid = chr(123)// "{" + . substr($charid, 0, 8) . $hyphen + . substr($charid, 8, 4) . $hyphen + . substr($charid, 12, 4) . $hyphen + . substr($charid, 16, 4) . $hyphen + . substr($charid, 20, 12) + . chr(125);// "}" + return $uuid; + } + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php new file mode 100644 index 0000000..07e5d1d --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php @@ -0,0 +1,76 @@ +getPresentation()->getAllSlides() as $oSlide) { + foreach ($oSlide->getShapeCollection() as $oShape) { + if (!($oShape instanceof Comment)) { + continue; + } + $oAuthor = $oShape->getAuthor(); + if (!($oAuthor instanceof Author)) { + continue; + } + if (array_key_exists($oAuthor->getHashCode(), $arrayAuthors)) { + continue; + } + $arrayAuthors[$oAuthor->getHashCode()] = $oAuthor; + } + } + if (!empty($arrayAuthors)) { + $this->getZip()->addFromString('ppt/commentAuthors.xml', $this->writeCommentsAuthors($arrayAuthors)); + } + + return $this->getZip(); + } + + /** + * @param Author[] $arrayAuthors + * @return string + */ + protected function writeCommentsAuthors($arrayAuthors) + { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // p:cmAuthorLst + $objWriter->startElement('p:cmAuthorLst'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + + $idxAuthor = 0; + foreach ($arrayAuthors as $oAuthor) { + $oAuthor->setIndex($idxAuthor++); + + // p:cmAuthor + $objWriter->startElement('p:cmAuthor'); + $objWriter->writeAttribute('id', $oAuthor->getIndex()); + $objWriter->writeAttribute('name', $oAuthor->getName()); + $objWriter->writeAttribute('initials', $oAuthor->getInitials()); + $objWriter->writeAttribute('lastIdx', "2"); + $objWriter->writeAttribute('clrIdx', 0); + $objWriter->endElement(); + } + + // ## p:cmAuthorLst + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php new file mode 100644 index 0000000..34ca83e --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php @@ -0,0 +1,182 @@ +startDocument('1.0', 'UTF-8', 'yes'); + + // Types + $objWriter->startElement('Types'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types'); + + // Rels + $this->writeDefaultContentType($objWriter, 'rels', 'application/vnd.openxmlformats-package.relationships+xml'); + + // XML + $this->writeDefaultContentType($objWriter, 'xml', 'application/xml'); + + // Presentation + $this->writeOverrideContentType($objWriter, '/ppt/presentation.xml', 'application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml'); + + // PptProps + $this->writeOverrideContentType($objWriter, '/ppt/presProps.xml', 'application/vnd.openxmlformats-officedocument.presentationml.presProps+xml'); + $this->writeOverrideContentType($objWriter, '/ppt/tableStyles.xml', 'application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml'); + $this->writeOverrideContentType($objWriter, '/ppt/viewProps.xml', 'application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml'); + + // DocProps + $this->writeOverrideContentType($objWriter, '/docProps/app.xml', 'application/vnd.openxmlformats-officedocument.extended-properties+xml'); + $this->writeOverrideContentType($objWriter, '/docProps/core.xml', 'application/vnd.openxmlformats-package.core-properties+xml'); + $this->writeOverrideContentType($objWriter, '/docProps/custom.xml', 'application/vnd.openxmlformats-officedocument.custom-properties+xml'); + + // Slide masters + $sldLayoutNr = 0; + $sldLayoutId = time() + 689016272; // requires minimum value of 2 147 483 648 + foreach ($this->oPresentation->getAllMasterSlides() as $idx => $oSlideMaster) { + $oSlideMaster->setRelsIndex($idx + 1); + $this->writeOverrideContentType($objWriter, '/ppt/slideMasters/slideMaster' . $oSlideMaster->getRelsIndex() . '.xml', 'application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml'); + $this->writeOverrideContentType($objWriter, '/ppt/theme/theme' . $oSlideMaster->getRelsIndex() . '.xml', 'application/vnd.openxmlformats-officedocument.theme+xml'); + foreach ($oSlideMaster->getAllSlideLayouts() as $oSlideLayout) { + $oSlideLayout->layoutNr = ++$sldLayoutNr; + $oSlideLayout->layoutId = ++$sldLayoutId; + $this->writeOverrideContentType($objWriter, '/ppt/slideLayouts/slideLayout' . $oSlideLayout->layoutNr . '.xml', 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml'); + } + } + + // Slides + $hasComments = false; + $slideCount = $this->oPresentation->getSlideCount(); + for ($i = 0; $i < $slideCount; ++$i) { + $oSlide = $this->oPresentation->getSlide($i); + $this->writeOverrideContentType($objWriter, '/ppt/slides/slide' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml'); + if ($oSlide->getNote()->getShapeCollection()->count() > 0) { + $this->writeOverrideContentType($objWriter, '/ppt/notesSlides/notesSlide' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml'); + } + foreach ($oSlide->getShapeCollection() as $oShape) { + if ($oShape instanceof Comment) { + $this->writeOverrideContentType($objWriter, '/ppt/comments/comment' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.presentationml.comments+xml'); + $hasComments = true; + break; + } + } + } + + if ($hasComments) { + $this->writeOverrideContentType($objWriter, '/ppt/commentAuthors.xml', 'application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml'); + } + + // Add media content-types + $aMediaContentTypes = array(); + + // GIF, JPEG, PNG + $aMediaContentTypes['gif'] = 'image/gif'; + $aMediaContentTypes['jpg'] = 'image/jpeg'; + $aMediaContentTypes['jpeg'] = 'image/jpeg'; + $aMediaContentTypes['png'] = 'image/png'; + foreach ($aMediaContentTypes as $key => $value) { + $this->writeDefaultContentType($objWriter, $key, $value); + } + + // XLSX + $this->writeDefaultContentType($objWriter, 'xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + + // Other media content types + $mediaCount = $this->getDrawingHashTable()->count(); + for ($i = 0; $i < $mediaCount; ++$i) { + $shapeIndex = $this->getDrawingHashTable()->getByIndex($i); + if ($shapeIndex instanceof ShapeChart) { + // Chart content type + $this->writeOverrideContentType($objWriter, '/ppt/charts/chart' . $shapeIndex->getImageIndex() . '.xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'); + } else { + $extension = strtolower($shapeIndex->getExtension()); + $mimeType = $shapeIndex->getMimeType(); + + if (!isset($aMediaContentTypes[$extension])) { + $aMediaContentTypes[$extension] = $mimeType; + + $this->writeDefaultContentType($objWriter, $extension, $mimeType); + } + } + } + + $objWriter->endElement(); + + $this->oZip->addFromString('[Content_Types].xml', $objWriter->getData()); + + return $this->oZip; + } + + /** + * Write Default content type + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param string $pPartname Part name + * @param string $pContentType Content type + * @throws \Exception + */ + private function writeDefaultContentType(XMLWriter $objWriter, $pPartname = '', $pContentType = '') + { + if ($pPartname == '' || $pContentType == '') { + throw new \Exception("Invalid parameters passed."); + } + // Write content type + $objWriter->startElement('Default'); + $objWriter->writeAttribute('Extension', $pPartname); + $objWriter->writeAttribute('ContentType', $pContentType); + $objWriter->endElement(); + } + + /** + * Write Override content type + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param string $pPartname Part name + * @param string $pContentType Content type + * @throws \Exception + */ + private function writeOverrideContentType(XMLWriter $objWriter, $pPartname = '', $pContentType = '') + { + if ($pPartname == '' || $pContentType == '') { + throw new \Exception("Invalid parameters passed."); + } + // Write content type + $objWriter->startElement('Override'); + $objWriter->writeAttribute('PartName', $pPartname); + $objWriter->writeAttribute('ContentType', $pContentType); + $objWriter->endElement(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php new file mode 100644 index 0000000..5bb8714 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php @@ -0,0 +1,103 @@ +startDocument('1.0', 'UTF-8', 'yes'); + + // Properties + $objWriter->startElement('Properties'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'); + $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + + // Application + $objWriter->writeElement('Application', 'Microsoft Office PowerPoint'); + + // Slides + $objWriter->writeElement('Slides', $this->getPresentation()->getSlideCount()); + + // ScaleCrop + $objWriter->writeElement('ScaleCrop', 'false'); + + // HeadingPairs + $objWriter->startElement('HeadingPairs'); + + // Vector + $objWriter->startElement('vt:vector'); + $objWriter->writeAttribute('size', '4'); + $objWriter->writeAttribute('baseType', 'variant'); + + // Variant + $objWriter->startElement('vt:variant'); + $objWriter->writeElement('vt:lpstr', 'Theme'); + $objWriter->endElement(); + + // Variant + $objWriter->startElement('vt:variant'); + $objWriter->writeElement('vt:i4', '1'); + $objWriter->endElement(); + + // Variant + $objWriter->startElement('vt:variant'); + $objWriter->writeElement('vt:lpstr', 'Slide Titles'); + $objWriter->endElement(); + + // Variant + $objWriter->startElement('vt:variant'); + $objWriter->writeElement('vt:i4', '1'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // TitlesOfParts + $objWriter->startElement('TitlesOfParts'); + + // Vector + $objWriter->startElement('vt:vector'); + $objWriter->writeAttribute('size', '1'); + $objWriter->writeAttribute('baseType', 'lpstr'); + + $objWriter->writeElement('vt:lpstr', 'Office Theme'); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // Company + $objWriter->writeElement('Company', $this->getPresentation()->getDocumentProperties()->getCompany()); + + // LinksUpToDate + $objWriter->writeElement('LinksUpToDate', 'false'); + + // SharedDoc + $objWriter->writeElement('SharedDoc', 'false'); + + // HyperlinksChanged + $objWriter->writeElement('HyperlinksChanged', 'false'); + + // AppVersion + $objWriter->writeElement('AppVersion', '12.0000'); + + $objWriter->endElement(); + + $this->oZip->addFromString('docProps/app.xml', $objWriter->getData()); + + // Return + return $this->oZip; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php new file mode 100644 index 0000000..591f42b --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php @@ -0,0 +1,74 @@ +startDocument('1.0', 'UTF-8', 'yes'); + + // cp:coreProperties + $objWriter->startElement('cp:coreProperties'); + $objWriter->writeAttribute('xmlns:cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'); + $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $objWriter->writeAttribute('xmlns:dcterms', 'http://purl.org/dc/terms/'); + $objWriter->writeAttribute('xmlns:dcmitype', 'http://purl.org/dc/dcmitype/'); + $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + + // dc:creator + $objWriter->writeElement('dc:creator', $this->oPresentation->getDocumentProperties()->getCreator()); + + // cp:lastModifiedBy + $objWriter->writeElement('cp:lastModifiedBy', $this->oPresentation->getDocumentProperties()->getLastModifiedBy()); + + // dcterms:created + $objWriter->startElement('dcterms:created'); + $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); + $objWriter->writeRaw(gmdate("Y-m-d\TH:i:s\Z", $this->oPresentation->getDocumentProperties()->getCreated())); + $objWriter->endElement(); + + // dcterms:modified + $objWriter->startElement('dcterms:modified'); + $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); + $objWriter->writeRaw(gmdate("Y-m-d\TH:i:s\Z", $this->oPresentation->getDocumentProperties()->getModified())); + $objWriter->endElement(); + + // dc:title + $objWriter->writeElement('dc:title', $this->oPresentation->getDocumentProperties()->getTitle()); + + // dc:description + $objWriter->writeElement('dc:description', $this->oPresentation->getDocumentProperties()->getDescription()); + + // dc:subject + $objWriter->writeElement('dc:subject', $this->oPresentation->getDocumentProperties()->getSubject()); + + // cp:keywords + $objWriter->writeElement('cp:keywords', $this->oPresentation->getDocumentProperties()->getKeywords()); + + // cp:category + $objWriter->writeElement('cp:category', $this->oPresentation->getDocumentProperties()->getCategory()); + + if ($this->oPresentation->getPresentationProperties()->isMarkedAsFinal()) { + // cp:contentStatus = Final + $objWriter->writeElement('cp:contentStatus', 'Final'); + } + + $objWriter->endElement(); + + $this->oZip->addFromString('docProps/core.xml', $objWriter->getData()); + + // Return + return $this->oZip; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCustom.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCustom.php new file mode 100644 index 0000000..b017654 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCustom.php @@ -0,0 +1,48 @@ +startDocument('1.0', 'UTF-8', 'yes'); + + // Properties + $objWriter->startElement('Properties'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties'); + $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + + if ($this->getPresentation()->getPresentationProperties()->isMarkedAsFinal()) { + // property + $objWriter->startElement('property'); + $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}'); + $objWriter->writeAttribute('pid', 2); + $objWriter->writeAttribute('name', '_MarkAsFinal'); + + // property > vt:bool + $objWriter->writeElement('vt:bool', 'true'); + + // > property + $objWriter->endElement(); + } + + // > Properties + $objWriter->endElement(); + + $this->oZip->addFromString('docProps/custom.xml', $objWriter->getData()); + + // Return + return $this->oZip; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php new file mode 100644 index 0000000..64308a4 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php @@ -0,0 +1,32 @@ +getPresentation()->getPresentationProperties()->getThumbnailPath(); + + if ($pathThumbnail) { + $fileThumbnail = file_get_contents($pathThumbnail); + $gdImage = imagecreatefromstring($fileThumbnail); + if ($gdImage) { + ob_start(); + imagejpeg($gdImage); + $imageContents = ob_get_contents(); + ob_end_clean(); + imagedestroy($gdImage); + + $this->getZip()->addFromString('docProps/thumbnail.jpeg', $imageContents); + } + } + + // Return + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/AbstractLayoutPack.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/AbstractLayoutPack.php new file mode 100644 index 0000000..8401aa4 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/AbstractLayoutPack.php @@ -0,0 +1,225 @@ +masterSlides; + } + + /** + * Get master slide relations + * + * @return array + */ + public function getMasterSlideRelations() + { + return $this->masterSlideRels; + } + + /** + * Get themes + * + * @return array + */ + public function getThemes() + { + return $this->themes; + } + + /** + * Get theme relations + * + * @return array + */ + public function getThemeRelations() + { + return $this->themeRelations; + } + + /** + * Get array of slide layouts + * + * @return array + */ + public function getLayouts() + { + return $this->layouts; + } + + /** + * Get array of slide layout relations + * + * @return array + */ + public function getLayoutRelations() + { + return $this->layoutRelations; + } + + /** + * Find specific slide layout. + * + * This is an array consisting of: + * - masterid + * - name (string) + * - body (string) + * + * @param string $name + * @param int $masterId + * @return array + * @throws \Exception + */ + public function findLayout($name = '', $masterId = 1) + { + foreach ($this->layouts as $layout) { + if ($layout['name'] == $name && $layout['masterid'] == $masterId) { + return $layout; + } + } + + throw new \Exception("Could not find slide layout $name in current layout pack."); + } + + /** + * Find specific slide layout id. + * + * @param string $name + * @return int + * @throws \Exception + */ + public function findLayoutId($name = '') + { + foreach ($this->layouts as $layoutId => $layout) { + if ($layout['name'] == $name) { + return $layoutId; + } + } + + throw new \Exception("Could not find slide layout $name in current layout pack."); + } + + /** + * Find specific slide layout name. + * + * @param int $idLayout + * @return int + * @throws \Exception + */ + public function findLayoutName($idLayout = null) + { + foreach ($this->layouts as $layoutId => $layout) { + if ($layoutId == $idLayout) { + return $layout['name']; + } + } + + throw new \Exception("Could not find slide layout $idLayout in current layout pack."); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/PackDefault.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/PackDefault.php new file mode 100644 index 0000000..0df0c01 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/PackDefault.php @@ -0,0 +1,3281 @@ +masterSlides = array( + array( + 'masterid' => 1, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +')); + + // Master slide relations + $this->masterSlideRels = array( + //array('masterid' => '', 'id' => '', 'type' => '', 'contentType' => '', 'target' => '', 'contents' => '') + ); + + // Theme + $this->themes = array( + array( + 'masterid' => 1, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +')); + + // Theme relations + $this->themeRelations = array( + //array('masterid' => 1, 'id' => '', 'type' => '', 'contentType' => '', 'target' => '', 'contents' => '') + ); + + // Layouts - Layout::TITLE_SLIDE + $this->layouts[1] = array( + 'masterid' => 1, + 'name' => Layout::TITLE_SLIDE, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master subtitle style + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::TITLE_AND_CONTENT + $this->layouts[2] = array( + 'masterid' => 1, + 'name' => Layout::TITLE_AND_CONTENT, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::SECTION_HEADER + $this->layouts[3] = array( + 'masterid' => 1, + 'name' => Layout::SECTION_HEADER, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::TWO_CONTENT + $this->layouts[4] = array( + 'masterid' => 1, + 'name' => Layout::TWO_CONTENT, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::COMPARISON + $this->layouts[5] = array( + 'masterid' => 1, + 'name' => Layout::COMPARISON, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::TITLE_ONLY + $this->layouts[6] = array( + 'masterid' => 1, + 'name' => Layout::TITLE_ONLY, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::BLANK + $this->layouts[7] = array( + 'masterid' => 1, + 'name' => Layout::BLANK, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::CONTENT_WITH_CAPTION + $this->layouts[8] = array( + 'masterid' => 1, + 'name' => Layout::CONTENT_WITH_CAPTION, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::PICTURE_WITH_CAPTION + $this->layouts[9] = array( + 'masterid' => 1, + 'name' => Layout::PICTURE_WITH_CAPTION, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::TITLE_AND_VERTICAL_TEXT + $this->layouts[10] = array( + 'masterid' => 1, + 'name' => Layout::TITLE_AND_VERTICAL_TEXT, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layouts - Layout::VERTICAL_TITLE_AND_TEXT + $this->layouts[11] = array( + 'masterid' => 1, + 'name' => Layout::VERTICAL_TITLE_AND_TEXT, + 'body' => ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master title style + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Click to edit Master text styles + + + + + + + Second level + + + + + + + Third level + + + + + + + Fourth level + + + + + + + Fifth level + + + + + + + + + + + + + + + + + + + + + + + 16/04/2009 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ‹#› + + + + + + + + + + +'); + + // Layout relations + $this->layoutRelations = array( + //array('layoutId' => 0, 'id' => '', 'type' => '', 'contentType' => '', 'target' => '', 'contents' => '') + ); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/TemplateBased.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/TemplateBased.php new file mode 100644 index 0000000..7a42c4c --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/TemplateBased.php @@ -0,0 +1,203 @@ +masterSlideRels = array(); + + // Theme relations + $this->themeRelations = array(); + + // Layout relations + $this->layoutRelations = array(); + + // Open package + $package = new \ZipArchive; + $package->open($fileName); + + // Read relations and search for officeDocument + $relations = simplexml_load_string($package->getFromName("_rels/.rels")); + foreach ($relations->Relationship as $rel) { + if ($rel["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument") { + // Found office document! Search for master slide... + $presentationRels = simplexml_load_string($package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/_rels/" . basename($rel["Target"]) . ".rels"))); + foreach ($presentationRels->Relationship as $presRel) { + if ($presRel["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster") { + // Found slide master! + $slideMasterId = str_replace('slideMaster', '', basename($presRel["Target"], '.xml')); + $this->masterSlides[] = array( + 'masterid' => $slideMasterId, + 'body' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . basename($presRel["Target"]))) + ); + + // Search for theme & slide layouts + $masterRelations = simplexml_load_string($package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/_rels/" . basename($presRel["Target"]) . ".rels"))); + foreach ($masterRelations->Relationship as $masterRel) { + if ($masterRel["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") { + // Found theme! + $themeId = str_replace('theme', '', basename($masterRel["Target"], '.xml')); + $this->themes[$themeId - 1] = array( + 'masterid' => $slideMasterId, + 'body' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . basename($masterRel["Target"]))) + ); + + // Search for theme relations + $themeRelations = @simplexml_load_string($package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/_rels/" . basename($masterRel["Target"]) . ".rels"))); + if ($themeRelations && $themeRelations->Relationship) { + foreach ($themeRelations->Relationship as $themeRel) { + if ($themeRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" && $themeRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" && $themeRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") { + // Theme relation + $this->themeRelations[] = array( + 'masterid' => $slideMasterId, + 'id' => $themeRel["Id"], + 'type' => $themeRel["Type"], + 'contentType' => '', + 'target' => $themeRel["Target"], + 'contents' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . dirname($themeRel["Target"]) . "/" . basename($themeRel["Target"]))) + ); + } + } + } + } elseif ($masterRel["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") { + // Found slide layout! + $layoutId = str_replace('slideLayout', '', basename($masterRel["Target"], '.xml')); + $layout = array( + 'id' => $layoutId, + 'masterid' => $slideMasterId, + 'name' => '-unknown-', + 'body' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . basename($masterRel["Target"]))) + ); + if (utf8_encode(utf8_decode($layout['body'])) == $layout['body']) { + $layoutXml = simplexml_load_string($layout['body']); + } else { + $layoutXml = simplexml_load_string(utf8_encode($layout['body'])); + } + $layoutXml->registerXPathNamespace("p", "http://schemas.openxmlformats.org/presentationml/2006/main"); + $slide = $layoutXml->xpath('/p:sldLayout/p:cSld'); + $layout['name'] = (string) $slide[0]['name']; + $this->layouts[$layoutId] = $layout; + + // Search for slide layout relations + $layoutRelations = @simplexml_load_string($package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/_rels/" . basename($masterRel["Target"]) . ".rels"))); + if ($layoutRelations && $layoutRelations->Relationship) { + foreach ($layoutRelations->Relationship as $layoutRel) { + if ($layoutRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" && $layoutRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" && $layoutRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") { + // Layout relation + $this->layoutRelations[] = array( + 'layoutId' => $layoutId, + 'id' => $layoutRel["Id"], + 'type' => $layoutRel["Type"], + 'contentType' => '', + 'target' => $layoutRel["Target"], + 'contents' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . dirname($layoutRel["Target"]) . "/" . basename($layoutRel["Target"]))) + ); + } + } + } + } else { + // Master slide relation + $this->masterSlideRels[] = array( + 'masterid' => $slideMasterId, + 'id' => $masterRel["Id"], + 'type' => $masterRel["Type"], + 'contentType' => '', + 'target' => $masterRel["Target"], + 'contents' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . basename($masterRel["Target"]))) + ); + } + } + } + } + + break; + } + } + + // Sort master slides + usort($this->masterSlides, array( + "\PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\TemplateBased", + "cmpMaster" + )); + + // Close package + $package->close(); + } + + /** + * Compare master slides + * + * @param array $firstSlide + * @param array $secondSlide + * @return int + */ + public static function cmpMaster($firstSlide, $secondSlide) + { + if ($firstSlide['masterid'] == $secondSlide['masterid']) { + return 0; + } + + return ($firstSlide['masterid'] < $secondSlide['masterid']) ? -1 : 1; + } + + /** + * Determine absolute zip path + * + * @param string $path + * @return string + */ + protected function absoluteZipPath($path) + { + $path = str_replace(array( + '/', + '\\' + ), DIRECTORY_SEPARATOR, $path); + $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); + $absolutes = array(); + foreach ($parts as $part) { + if ('.' == $part) { + continue; + } + if ('..' == $part) { + array_pop($absolutes); + } else { + $absolutes[] = $part; + } + } + + return implode('/', $absolutes); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php new file mode 100644 index 0000000..7109881 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php @@ -0,0 +1,2358 @@ +getDrawingHashTable()->count(); ++$i) { + $shape = $this->getDrawingHashTable()->getByIndex($i); + if ($shape instanceof Chart) { + $this->getZip()->addFromString('ppt/charts/' . $shape->getIndexedFilename(), $this->writeChart($shape)); + + if ($shape->hasIncludedSpreadsheet()) { + $this->getZip()->addFromString('ppt/charts/_rels/' . $shape->getIndexedFilename() . '.rels', $this->writeChartRelationships($shape)); + $pFilename = tempnam(sys_get_temp_dir(), 'PHPExcel'); + $this->getZip()->addFromString('ppt/embeddings/' . $shape->getIndexedFilename() . '.xlsx', $this->writeSpreadsheet($this->getPresentation(), $shape, $pFilename . '.xlsx')); + + // remove temp file + if (@unlink($pFilename) === false) { + throw new \Exception('The file ' . $pFilename . ' could not removed.'); + } + } + } + } + return $this->getZip(); + } + + + /** + * Write chart to XML format + * + * @param \PhpOffice\PhpPresentation\Shape\Chart $chart + * @return string XML Output + * @throws \Exception + */ + public function writeChart(Chart $chart) + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // c:chartSpace + $objWriter->startElement('c:chartSpace'); + $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + // c:date1904 + $objWriter->startElement('c:date1904'); + $objWriter->writeAttribute('val', '1'); + $objWriter->endElement(); + + // c:lang + $objWriter->startElement('c:lang'); + $objWriter->writeAttribute('val', 'en-US'); + $objWriter->endElement(); + + // c:chart + $objWriter->startElement('c:chart'); + + // Title? + if ($chart->getTitle()->isVisible()) { + // Write title + $this->writeTitle($objWriter, $chart->getTitle()); + } + + // c:autoTitleDeleted + $objWriter->startElement('c:autoTitleDeleted'); + $objWriter->writeAttribute('val', $chart->getTitle()->isVisible() ? '0' : '1'); + $objWriter->endElement(); + + // c:view3D + $objWriter->startElement('c:view3D'); + + // c:rotX + $objWriter->startElement('c:rotX'); + $objWriter->writeAttribute('val', $chart->getView3D()->getRotationX()); + $objWriter->endElement(); + + // c:hPercent + $hPercent = $chart->getView3D()->getHeightPercent(); + $objWriter->writeElementIf($hPercent != null, 'c:hPercent', 'val', $hPercent); + + // c:rotY + $objWriter->startElement('c:rotY'); + $objWriter->writeAttribute('val', $chart->getView3D()->getRotationY()); + $objWriter->endElement(); + + // c:depthPercent + $objWriter->startElement('c:depthPercent'); + $objWriter->writeAttribute('val', $chart->getView3D()->getDepthPercent()); + $objWriter->endElement(); + + // c:rAngAx + $objWriter->startElement('c:rAngAx'); + $objWriter->writeAttribute('val', $chart->getView3D()->hasRightAngleAxes() ? '1' : '0'); + $objWriter->endElement(); + + // c:perspective + $objWriter->startElement('c:perspective'); + $objWriter->writeAttribute('val', $chart->getView3D()->getPerspective()); + $objWriter->endElement(); + + $objWriter->endElement(); + + // Write plot area + $this->writePlotArea($objWriter, $chart->getPlotArea(), $chart); + + // Legend? + if ($chart->getLegend()->isVisible()) { + // Write legend + $this->writeLegend($objWriter, $chart->getLegend()); + } + + // c:plotVisOnly + $objWriter->startElement('c:plotVisOnly'); + $objWriter->writeAttribute('val', '1'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // c:spPr + $objWriter->startElement('c:spPr'); + + // Fill + $this->writeFill($objWriter, $chart->getFill()); + + // Border + if ($chart->getBorder()->getLineStyle() != Border::LINE_NONE) { + $this->writeBorder($objWriter, $chart->getBorder(), ''); + } + + // Shadow + if ($chart->getShadow()->isVisible()) { + // a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', CommonDrawing::pixelsToEmu($chart->getShadow()->getBlurRadius())); + $objWriter->writeAttribute('dist', CommonDrawing::pixelsToEmu($chart->getShadow()->getDistance())); + $objWriter->writeAttribute('dir', CommonDrawing::degreesToAngle($chart->getShadow()->getDirection())); + $objWriter->writeAttribute('algn', $chart->getShadow()->getAlignment()); + $objWriter->writeAttribute('rotWithShape', '0'); + + $this->writeColor($objWriter, $chart->getShadow()->getColor(), $chart->getShadow()->getAlpha()); + + $objWriter->endElement(); + + $objWriter->endElement(); + } + + $objWriter->endElement(); + + // External data? + if ($chart->hasIncludedSpreadsheet()) { + // c:externalData + $objWriter->startElement('c:externalData'); + $objWriter->writeAttribute('r:id', 'rId1'); + + // c:autoUpdate + $objWriter->startElement('c:autoUpdate'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write chart to XML format + * + * @param PhpPresentation $presentation + * @param \PhpOffice\PhpPresentation\Shape\Chart $chart + * @param string $tempName + * @return string String output + * @throws \Exception + */ + public function writeSpreadsheet(PhpPresentation $presentation, $chart, $tempName) + { + // Need output? + if (!$chart->hasIncludedSpreadsheet()) { + throw new \Exception('No spreadsheet output is required for the given chart.'); + } + + // Verify PHPExcel + if (!class_exists('PHPExcel')) { + throw new \Exception('PHPExcel has not been loaded. Include PHPExcel.php in your script, e.g. require_once \'PHPExcel.php\'.'); + } + + // Create new spreadsheet + $workbook = new \PHPExcel(); + + // Set properties + $title = $chart->getTitle()->getText(); + if (strlen($title) == 0) { + $title = 'Chart'; + } + $workbook->getProperties()->setCreator($presentation->getDocumentProperties()->getCreator())->setLastModifiedBy($presentation->getDocumentProperties()->getLastModifiedBy())->setTitle($title); + + // Add chart data + $sheet = $workbook->setActiveSheetIndex(0); + $sheet->setTitle('Sheet1'); + + // Write series + $seriesIndex = 0; + foreach ($chart->getPlotArea()->getType()->getSeries() as $series) { + // Title + $sheet->setCellValueByColumnAndRow(1 + $seriesIndex, 1, $series->getTitle()); + + // X-axis + $axisXData = array_keys($series->getValues()); + $numAxisXData = count($axisXData); + for ($i = 0; $i < $numAxisXData; $i++) { + $sheet->setCellValueByColumnAndRow(0, $i + 2, $axisXData[$i]); + } + + // Y-axis + $axisYData = array_values($series->getValues()); + $numAxisYData = count($axisYData); + for ($i = 0; $i < $numAxisYData; $i++) { + $sheet->setCellValueByColumnAndRow(1 + $seriesIndex, $i + 2, $axisYData[$i]); + } + + ++$seriesIndex; + } + + // Save to string + $writer = \PHPExcel_IOFactory::createWriter($workbook, 'Excel2007'); + $writer->save($tempName); + + // Load file in memory + $returnValue = file_get_contents($tempName); + if (@unlink($tempName) === false) { + throw new \Exception('The file ' . $tempName . ' could not removed.'); + } + + return $returnValue; + } + + /** + * Write element with value attribute + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param string $elementName + * @param string $value + */ + protected function writeElementWithValAttribute($objWriter, $elementName, $value) + { + $objWriter->startElement($elementName); + $objWriter->writeAttribute('val', $value); + $objWriter->endElement(); + } + + /** + * Write single value or reference + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param boolean $isReference + * @param mixed $value + * @param string $reference + */ + protected function writeSingleValueOrReference($objWriter, $isReference, $value, $reference) + { + if (!$isReference) { + // Value + $objWriter->writeElement('c:v', $value); + return; + } + + // Reference and cache + $objWriter->startElement('c:strRef'); + $objWriter->writeElement('c:f', $reference); + $objWriter->startElement('c:strCache'); + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', '1'); + $objWriter->endElement(); + + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', '0'); + $objWriter->writeElement('c:v', $value); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + + /** + * Write series value or reference + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param boolean $isReference + * @param mixed $values + * @param string $reference + */ + protected function writeMultipleValuesOrReference($objWriter, $isReference, $values, $reference) + { + // c:strLit / c:numLit + // c:strRef / c:numRef + $referenceType = ($isReference ? 'Ref' : 'Lit'); + $dataType = 'str'; + if (is_int($values[0]) || is_float($values[0])) { + $dataType = 'num'; + } + $objWriter->startElement('c:' . $dataType . $referenceType); + + $numValues = count($values); + if (!$isReference) { + // Value + + // c:ptCount + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', count($values)); + $objWriter->endElement(); + + // Add points + for ($i = 0; $i < $numValues; $i++) { + // c:pt + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', $i); + $objWriter->writeElement('c:v', $values[$i]); + $objWriter->endElement(); + } + } else { + // Reference + $objWriter->writeElement('c:f', $reference); + $objWriter->startElement('c:' . $dataType . 'Cache'); + + // c:ptCount + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', count($values)); + $objWriter->endElement(); + + // Add points + for ($i = 0; $i < $numValues; $i++) { + // c:pt + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', $i); + $objWriter->writeElement('c:v', $values[$i]); + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + $objWriter->endElement(); + } + + /** + * Write Title + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Title $subject + * @throws \Exception + */ + protected function writeTitle(XMLWriter $objWriter, Title $subject) + { + // c:title + $objWriter->startElement('c:title'); + + // c:tx + $objWriter->startElement('c:tx'); + + // c:rich + $objWriter->startElement('c:rich'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + $objWriter->writeAttribute('algn', $subject->getAlignment()->getHorizontal()); + $objWriter->writeAttribute('fontAlgn', $subject->getAlignment()->getVertical()); + $objWriter->writeAttribute('marL', CommonDrawing::pixelsToEmu($subject->getAlignment()->getMarginLeft())); + $objWriter->writeAttribute('marR', CommonDrawing::pixelsToEmu($subject->getAlignment()->getMarginRight())); + $objWriter->writeAttribute('indent', CommonDrawing::pixelsToEmu($subject->getAlignment()->getIndent())); + $objWriter->writeAttribute('lvl', $subject->getAlignment()->getLevel()); + + // a:defRPr + $objWriter->writeElement('a:defRPr', null); + + $objWriter->endElement(); + + // a:r + $objWriter->startElement('a:r'); + + // a:rPr + $objWriter->startElement('a:rPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->writeAttribute('b', ($subject->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($subject->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($subject->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($subject->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $subject->getFont()->getUnderline()); + $objWriter->writeAttributeIf($subject->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($subject->getFont()->isSubScript(), 'baseline', '-250000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $subject->getFont()->getColor()); + + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $subject->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:t + $objWriter->writeElement('a:t', $subject->getText()); + + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // Write layout + $this->writeLayout($objWriter, $subject); + + // c:overlay + $objWriter->startElement('c:overlay'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Plot Area + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\PlotArea $subject + * @param \PhpOffice\PhpPresentation\Shape\Chart $chart + * @throws \Exception + */ + protected function writePlotArea(XMLWriter $objWriter, PlotArea $subject, Chart $chart) + { + // c:plotArea + $objWriter->startElement('c:plotArea'); + + // Write layout + $this->writeLayout($objWriter, $subject); + + // Write chart + $chartType = $subject->getType(); + if ($chartType instanceof Area) { + $this->writeTypeArea($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } elseif ($chartType instanceof Bar) { + $this->writeTypeBar($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } elseif ($chartType instanceof Bar3D) { + $this->writeTypeBar3D($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } elseif ($chartType instanceof Doughnut) { + $this->writeTypeDoughnut($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } elseif ($chartType instanceof Pie) { + $this->writeTypePie($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } elseif ($chartType instanceof Pie3D) { + $this->writeTypePie3D($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } elseif ($chartType instanceof Line) { + $this->writeTypeLine($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } elseif ($chartType instanceof Scatter) { + $this->writeTypeScatter($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } else { + throw new \Exception('The chart type provided could not be rendered.'); + } + + // Write X axis? + if ($chartType->hasAxisX()) { + $this->writeAxis($objWriter, $subject->getAxisX(), Chart\Axis::AXIS_X, $chartType); + } + + // Write Y axis? + if ($chartType->hasAxisY()) { + $this->writeAxis($objWriter, $subject->getAxisY(), Chart\Axis::AXIS_Y, $chartType); + } + + $objWriter->endElement(); + } + + /** + * Write Legend + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Legend $subject + * @throws \Exception + */ + protected function writeLegend(XMLWriter $objWriter, Legend $subject) + { + // c:legend + $objWriter->startElement('c:legend'); + + // c:legendPos + $objWriter->startElement('c:legendPos'); + $objWriter->writeAttribute('val', $subject->getPosition()); + $objWriter->endElement(); + + // Write layout + $this->writeLayout($objWriter, $subject); + + // c:overlay + $objWriter->startElement('c:overlay'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + // c:spPr + $objWriter->startElement('c:spPr'); + + // Fill + $this->writeFill($objWriter, $subject->getFill()); + + // Border + if ($subject->getBorder()->getLineStyle() != Border::LINE_NONE) { + $this->writeBorder($objWriter, $subject->getBorder(), ''); + } + + $objWriter->endElement(); + + // c:txPr + $objWriter->startElement('c:txPr'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + $objWriter->writeAttribute('algn', $subject->getAlignment()->getHorizontal()); + $objWriter->writeAttribute('fontAlgn', $subject->getAlignment()->getVertical()); + $objWriter->writeAttribute('marL', CommonDrawing::pixelsToEmu($subject->getAlignment()->getMarginLeft())); + $objWriter->writeAttribute('marR', CommonDrawing::pixelsToEmu($subject->getAlignment()->getMarginRight())); + $objWriter->writeAttribute('indent', CommonDrawing::pixelsToEmu($subject->getAlignment()->getIndent())); + $objWriter->writeAttribute('lvl', $subject->getAlignment()->getLevel()); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($subject->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($subject->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($subject->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($subject->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $subject->getFont()->getUnderline()); + $objWriter->writeAttributeIf($subject->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($subject->getFont()->isSubScript(), 'baseline', '-250000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $subject->getFont()->getColor()); + + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $subject->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Layout + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param mixed $subject + * @throws \Exception + */ + protected function writeLayout(XMLWriter $objWriter, $subject) + { + // c:layout + $objWriter->startElement('c:layout'); + + // c:manualLayout + $objWriter->startElement('c:manualLayout'); + // c:xMode + $objWriter->startElement('c:xMode'); + $objWriter->writeAttribute('val', 'edge'); + $objWriter->endElement(); + + // c:yMode + $objWriter->startElement('c:yMode'); + $objWriter->writeAttribute('val', 'edge'); + $objWriter->endElement(); + + if ($subject->getOffsetX() != 0) { + // c:x + $objWriter->startElement('c:x'); + $objWriter->writeAttribute('val', $subject->getOffsetX()); + $objWriter->endElement(); + } + + if ($subject->getOffsetY() != 0) { + // c:y + $objWriter->startElement('c:y'); + $objWriter->writeAttribute('val', $subject->getOffsetY()); + $objWriter->endElement(); + } + + if ($subject->getWidth() != 0) { + // c:w + $objWriter->startElement('c:w'); + $objWriter->writeAttribute('val', $subject->getWidth()); + $objWriter->endElement(); + } + + if ($subject->getHeight() != 0) { + // c:h + $objWriter->startElement('c:h'); + $objWriter->writeAttribute('val', $subject->getHeight()); + $objWriter->endElement(); + } + + $objWriter->endElement(); + $objWriter->endElement(); + } + + /** + * Write Type Area + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Area $subject + * @param boolean $includeSheet + * @throws \Exception + */ + protected function writeTypeArea(XMLWriter $objWriter, Area $subject, $includeSheet = false) + { + // c:lineChart + $objWriter->startElement('c:areaChart'); + + // c:grouping + $objWriter->startElement('c:grouping'); + $objWriter->writeAttribute('val', 'standard'); + $objWriter->endElement(); + + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:ser > c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:ser > c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:ser > c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // c:ser > c:dLbls + // @link : https://msdn.microsoft.com/en-us/library/documentformat.openxml.drawing.charts.areachartseries.aspx + $objWriter->startElement('c:dLbls'); + + // c:ser > c:dLbls > c:showVal + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + + // c:ser > c:dLbls > c:showCatName + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + + // c:ser > c:dLbls > c:showSerName + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + + // c:ser > c:dLbls > c:showPercent + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + + // c:ser > ##c:dLbls + $objWriter->endElement(); + + if ($series->getFill()->getFillType() != Fill::FILL_NONE) { + // c:spPr + $objWriter->startElement('c:spPr'); + // Write fill + $this->writeFill($objWriter, $series->getFill()); + // ## c:spPr + $objWriter->endElement(); + } + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:cat + $objWriter->startElement('c:cat'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:val + $objWriter->startElement('c:val'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52743552'); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52749440'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Type Bar + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar $subject + * @param boolean $includeSheet + * @throws \Exception + */ + protected function writeTypeBar(XMLWriter $objWriter, Bar $subject, $includeSheet = false) + { + // c:barChart + $objWriter->startElement('c:barChart'); + + // c:barDir + $objWriter->startElement('c:barDir'); + $objWriter->writeAttribute('val', $subject->getBarDirection()); + $objWriter->endElement(); + + // c:grouping + $objWriter->startElement('c:grouping'); + $objWriter->writeAttribute('val', $subject->getBarGrouping()); + $objWriter->endElement(); + + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // Fills for points? + $dataPointFills = $series->getDataPointFills(); + foreach ($dataPointFills as $key => $value) { + // c:dPt + $objWriter->startElement('c:dPt'); + + // c:idx + $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + + if ($value->getFillType() != Fill::FILL_NONE) { + // c:spPr + $objWriter->startElement('c:spPr'); + // Write fill + $this->writeFill($objWriter, $value); + // ## c:spPr + $objWriter->endElement(); + } + + // ## c:dPt + $objWriter->endElement(); + } + + // c:dLbls + $objWriter->startElement('c:dLbls'); + + if ($series->hasDlblNumFormat()) { + //c:numFmt + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $series->getDlblNumFormat()); + $objWriter->writeAttribute('sourceLinked', '0'); + $objWriter->endElement(); + } + + // c:txPr + $objWriter->startElement('c:txPr'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr'); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle'); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); + + // a:solidFill + $objWriter->startElement('a:solidFill'); + $this->writeColor($objWriter, $series->getFont()->getColor()); + // >a:solidFill + $objWriter->endElement(); + // a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + // >a:latin + $objWriter->endElement(); + + // >a:defRPr + $objWriter->endElement(); + // >a:pPr + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + // >a:p + $objWriter->endElement(); + // >a:lstStyle + $objWriter->endElement(); + + // c:dLblPos + $this->writeElementWithValAttribute($objWriter, 'c:dLblPos', $series->getLabelPosition()); + + // c:showVal + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + + // c:showCatName + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + + // c:showSerName + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + + // c:showPercent + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + + // c:separator + $objWriter->writeElement('c:separator', $series->hasShowSeparator() ? $series->getSeparator() : ''); + + // c:showLeaderLines + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + + $objWriter->endElement(); + + // c:spPr + if ($series->getFill()->getFillType() != Fill::FILL_NONE) { + // c:spPr + $objWriter->startElement('c:spPr'); + // Write fill + $this->writeFill($objWriter, $series->getFill()); + // ## c:spPr + $objWriter->endElement(); + } + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:cat + $objWriter->startElement('c:cat'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:val + $objWriter->startElement('c:val'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + + // c:gapWidth + $objWriter->startElement('c:gapWidth'); + $objWriter->writeAttribute('val', $subject->getGapWidthPercent()); + $objWriter->endElement(); + + // c:overlap + $barGrouping = $subject->getBarGrouping(); + $objWriter->startElement('c:overlap'); + if ($barGrouping === Bar::GROUPING_CLUSTERED) { + $objWriter->writeAttribute('val', '0'); + } elseif ($barGrouping === Bar::GROUPING_STACKED || $barGrouping === Bar::GROUPING_PERCENTSTACKED) { + $objWriter->writeAttribute('val', '100000'); + } + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52743552'); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52749440'); + $objWriter->endElement(); + + // c:extLst + $objWriter->startElement('c:extLst'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Type Bar3D + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar3D $subject + * @param boolean $includeSheet + * @throws \Exception + */ + protected function writeTypeBar3D(XMLWriter $objWriter, Bar3D $subject, $includeSheet = false) + { + // c:bar3DChart + $objWriter->startElement('c:bar3DChart'); + + // c:barDir + $objWriter->startElement('c:barDir'); + $objWriter->writeAttribute('val', $subject->getBarDirection()); + $objWriter->endElement(); + + // c:grouping + $objWriter->startElement('c:grouping'); + $objWriter->writeAttribute('val', $subject->getBarGrouping()); + $objWriter->endElement(); + + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // Fills for points? + $dataPointFills = $series->getDataPointFills(); + foreach ($dataPointFills as $key => $value) { + // c:dPt + $objWriter->startElement('c:dPt'); + + // c:idx + $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + + if ($value->getFillType() != Fill::FILL_NONE) { + // c:spPr + $objWriter->startElement('c:spPr'); + // Write fill + $this->writeFill($objWriter, $value); + // ## c:spPr + $objWriter->endElement(); + } + + // ## c:dPt + $objWriter->endElement(); + } + + // c:dLbls + $objWriter->startElement('c:dLbls'); + + // c:txPr + $objWriter->startElement('c:txPr'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $series->getFont()->getColor()); + + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // c:showVal + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + + // c:showCatName + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + + // c:showSerName + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + + // c:showPercent + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + + // c:showLeaderLines + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + + $objWriter->endElement(); + + // c:spPr + if ($series->getFill()->getFillType() != Fill::FILL_NONE) { + // c:spPr + $objWriter->startElement('c:spPr'); + // Write fill + $this->writeFill($objWriter, $series->getFill()); + // ## c:spPr + $objWriter->endElement(); + } + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:cat + $objWriter->startElement('c:cat'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:val + $objWriter->startElement('c:val'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + + // c:gapWidth + $objWriter->startElement('c:gapWidth'); + $objWriter->writeAttribute('val', $subject->getGapWidthPercent()); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52743552'); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52749440'); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Type Pie + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Doughnut $subject + * @param boolean $includeSheet + * @throws \Exception + */ + protected function writeTypeDoughnut(XMLWriter $objWriter, Doughnut $subject, $includeSheet = false) + { + // c:pieChart + $objWriter->startElement('c:doughnutChart'); + + // c:varyColors + $objWriter->startElement('c:varyColors'); + $objWriter->writeAttribute('val', '1'); + $objWriter->endElement(); + + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // Fills for points? + $dataPointFills = $series->getDataPointFills(); + foreach ($dataPointFills as $key => $value) { + // c:dPt + $objWriter->startElement('c:dPt'); + $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + // c:dPt/c:spPr + $objWriter->startElement('c:spPr'); + $this->writeFill($objWriter, $value); + // c:dPt/##c:spPr + $objWriter->endElement(); + // ##c:dPt + $objWriter->endElement(); + } + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:cat + $objWriter->startElement('c:cat'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:val + $objWriter->startElement('c:val'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + + // c:dLbls + $objWriter->startElement('c:dLbls'); + + $this->writeElementWithValAttribute($objWriter, 'c:showLegendKey', $series->hasShowLegendKey() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showBubbleSize', '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + + if ($series->hasDlblNumFormat()) { + //c:numFmt + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $series->getDlblNumFormat()); + $objWriter->writeAttribute('sourceLinked', '0'); + $objWriter->endElement(); + } + + // c:dLbls\c:txPr + $objWriter->startElement('c:txPr'); + $objWriter->writeElement('a:bodyPr', null); + $objWriter->writeElement('a:lstStyle', null); + + // c:dLbls\c:txPr\a:p + $objWriter->startElement('a:p'); + + // c:dLbls\c:txPr\a:p\a:pPr + $objWriter->startElement('a:pPr'); + + // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr + $objWriter->startElement('a:defRPr'); + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); + + // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\a:solidFill + $objWriter->startElement('a:solidFill'); + $this->writeColor($objWriter, $series->getFont()->getColor()); + $objWriter->endElement(); + + // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + $objWriter->endElement(); + + // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\ + $objWriter->endElement(); + // c:dLbls\c:txPr\a:p\a:pPr\ + $objWriter->endElement(); + + // c:dLbls\c:txPr\a:p\a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + // c:dLbls\c:txPr\a:p\ + $objWriter->endElement(); + // c:dLbls\c:txPr\ + $objWriter->endElement(); + + $separator = $series->getSeparator(); + if (!empty($separator) && $separator != PHP_EOL) { + // c:dLbls\c:separator + $objWriter->writeElement('c:separator', $separator); + } + + // c:dLbls\ + $objWriter->endElement(); + + $this->writeElementWithValAttribute($objWriter, 'c:firstSliceAng', '0'); + $this->writeElementWithValAttribute($objWriter, 'c:holeSize', $subject->getHoleSize()); + + $objWriter->endElement(); + } + + /** + * Write Type Pie + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Pie $subject + * @param boolean $includeSheet + * @throws \Exception + */ + protected function writeTypePie(XMLWriter $objWriter, Pie $subject, $includeSheet = false) + { + // c:pieChart + $objWriter->startElement('c:pieChart'); + + // c:varyColors + $objWriter->startElement('c:varyColors'); + $objWriter->writeAttribute('val', '1'); + $objWriter->endElement(); + + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // Fills for points? + $dataPointFills = $series->getDataPointFills(); + foreach ($dataPointFills as $key => $value) { + // c:dPt + $objWriter->startElement('c:dPt'); + $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + // c:dPt/c:spPr + $objWriter->startElement('c:spPr'); + $this->writeFill($objWriter, $value); + // c:dPt/##c:spPr + $objWriter->endElement(); + // ##c:dPt + $objWriter->endElement(); + } + + // c:dLbls + $objWriter->startElement('c:dLbls'); + + if ($series->hasDlblNumFormat()) { + //c:numFmt + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $series->getDlblNumFormat()); + $objWriter->writeAttribute('sourceLinked', '0'); + $objWriter->endElement(); + } + + // c:txPr + $objWriter->startElement('c:txPr'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $series->getFont()->getColor()); + + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // c:dLblPos + $this->writeElementWithValAttribute($objWriter, 'c:dLblPos', $series->getLabelPosition()); + + // c:showLegendKey + $this->writeElementWithValAttribute($objWriter, 'c:showLegendKey', $series->hasShowLegendKey() ? '1' : '0'); + + // c:showVal + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + + // c:showCatName + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + + // c:showSerName + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + + // c:showPercent + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + + // c:showLeaderLines + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + + $objWriter->endElement(); + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:cat + $objWriter->startElement('c:cat'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:val + $objWriter->startElement('c:val'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + + $objWriter->endElement(); + } + + /** + * Write Type Pie3D + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Pie3D $subject + * @param boolean $includeSheet + * @throws \Exception + */ + protected function writeTypePie3D(XMLWriter $objWriter, Pie3D $subject, $includeSheet = false) + { + // c:pie3DChart + $objWriter->startElement('c:pie3DChart'); + + // c:varyColors + $objWriter->startElement('c:varyColors'); + $objWriter->writeAttribute('val', '1'); + $objWriter->endElement(); + + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // c:explosion + $objWriter->startElement('c:explosion'); + $objWriter->writeAttribute('val', $subject->getExplosion()); + $objWriter->endElement(); + + // Fills for points? + $dataPointFills = $series->getDataPointFills(); + foreach ($dataPointFills as $key => $value) { + // c:dPt + $objWriter->startElement('c:dPt'); + $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + // c:dPt/c:spPr + $objWriter->startElement('c:spPr'); + $this->writeFill($objWriter, $value); + // c:dPt/##c:spPr + $objWriter->endElement(); + // ##c:dPt + $objWriter->endElement(); + } + + // c:dLbls + $objWriter->startElement('c:dLbls'); + + // c:txPr + $objWriter->startElement('c:txPr'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $series->getFont()->getColor()); + + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // c:dLblPos + $this->writeElementWithValAttribute($objWriter, 'c:dLblPos', $series->getLabelPosition()); + + // c:showVal + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + + // c:showCatName + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + + // c:showSerName + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + + // c:showPercent + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + + // c:showLeaderLines + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + + $objWriter->endElement(); + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:cat + $objWriter->startElement('c:cat'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:val + $objWriter->startElement('c:val'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + + $objWriter->endElement(); + } + + /** + * Write Type Line + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Line $subject + * @param boolean $includeSheet + * @throws \Exception + */ + protected function writeTypeLine(XMLWriter $objWriter, Line $subject, $includeSheet = false) + { + // c:lineChart + $objWriter->startElement('c:lineChart'); + + // c:grouping + $objWriter->startElement('c:grouping'); + $objWriter->writeAttribute('val', 'standard'); + $objWriter->endElement(); + + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // c:spPr + $objWriter->startElement('c:spPr'); + // Write fill + $this->writeFill($objWriter, $series->getFill()); + // Write outline + $this->writeOutline($objWriter, $series->getOutline()); + // ## c:spPr + $objWriter->endElement(); + + // Marker + $this->writeSeriesMarker($objWriter, $series->getMarker()); + + // c:dLbls + $objWriter->startElement('c:dLbls'); + + // c:txPr + $objWriter->startElement('c:txPr'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $series->getFont()->getColor()); + + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // c:showVal + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + + // c:showCatName + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + + // c:showSerName + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + + // c:showPercent + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + + // c:showLeaderLines + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + + // > c:dLbls + $objWriter->endElement(); + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:cat + $objWriter->startElement('c:cat'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:val + $objWriter->startElement('c:val'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + + // c:marker + $objWriter->startElement('c:marker'); + $objWriter->writeAttribute('val', '1'); + $objWriter->endElement(); + + // c:smooth + $objWriter->startElement('c:smooth'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52743552'); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52749440'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Type Scatter + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Scatter $subject + * @param boolean $includeSheet + * @throws \Exception + */ + protected function writeTypeScatter(XMLWriter $objWriter, Scatter $subject, $includeSheet = false) + { + // c:scatterChart + $objWriter->startElement('c:scatterChart'); + + // c:scatterStyle + $objWriter->startElement('c:scatterStyle'); + $objWriter->writeAttribute('val', 'lineMarker'); + $objWriter->endElement(); + + // c:varyColors + $objWriter->startElement('c:varyColors'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // c:spPr + $objWriter->startElement('c:spPr'); + // Write fill + $this->writeFill($objWriter, $series->getFill()); + // Write outline + $this->writeOutline($objWriter, $series->getOutline()); + // ## c:spPr + $objWriter->endElement(); + + // Marker + $this->writeSeriesMarker($objWriter, $series->getMarker()); + + // c:dLbls + $objWriter->startElement('c:dLbls'); + + // c:txPr + $objWriter->startElement('c:txPr'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $series->getFont()->getColor()); + + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // c:showLegendKey + $this->writeElementWithValAttribute($objWriter, 'c:showLegendKey', $series->hasShowLegendKey() ? '1' : '0'); + + // c:showVal + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + + // c:showCatName + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + + // c:showSerName + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + + // c:showPercent + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + + // c:separator + $separator = $series->getSeparator(); + if (!empty($separator) && $separator != PHP_EOL) { + // c:dLbls\c:separator + $objWriter->writeElement('c:separator', $separator); + } + + // c:showLeaderLines + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + + $objWriter->endElement(); + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:xVal + $objWriter->startElement('c:xVal'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:yVal + $objWriter->startElement('c:yVal'); + $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + // c:smooth + $objWriter->startElement('c:smooth'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52743552'); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52749440'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write chart relationships to XML format + * + * @param \PhpOffice\PhpPresentation\Shape\Chart $pChart + * @return string XML Output + * @throws \Exception + */ + public function writeChartRelationships(Chart $pChart) + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Write spreadsheet relationship? + if ($pChart->hasIncludedSpreadsheet()) { + $this->writeRelationship($objWriter, 1, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/package', '../embeddings/' . $pChart->getIndexedFilename() . '.xlsx'); + } + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * @param XMLWriter $objWriter + * @param Chart\Marker $oMarker + */ + protected function writeSeriesMarker(XMLWriter $objWriter, Chart\Marker $oMarker) + { + // c:marker + $objWriter->startElement('c:marker'); + // c:marker > c:symbol + $objWriter->startElement('c:symbol'); + $objWriter->writeAttribute('val', $oMarker->getSymbol()); + $objWriter->endElement(); + + // Size if different of none + if ($oMarker->getSymbol() != Chart\Marker::SYMBOL_NONE) { + $markerSize = (int)$oMarker->getSize(); + if ($markerSize < 2) { + $markerSize = 2; + } + if ($markerSize > 72) { + $markerSize = 72; + } + + /** + * c:marker > c:size + * Size in points + * @link : https://msdn.microsoft.com/en-us/library/hh658135(v=office.12).aspx + */ + $objWriter->startElement('c:size'); + $objWriter->writeAttribute('val', $markerSize); + $objWriter->endElement(); + } + $objWriter->endElement(); + } + + /** + * @param XMLWriter $objWriter + * @param Chart\Axis $oAxis + * @param $typeAxis + * @param Chart\Type\AbstractType $typeChart + * @throws \Exception + */ + protected function writeAxis(XMLWriter $objWriter, Chart\Axis $oAxis, $typeAxis, Chart\Type\AbstractType $typeChart) + { + if ($typeAxis != Chart\Axis::AXIS_X && $typeAxis != Chart\Axis::AXIS_Y) { + return; + } + + if ($typeAxis == Chart\Axis::AXIS_X) { + $mainElement = 'c:catAx'; + $axIdVal = '52743552'; + $axPosVal = 'b'; + $crossAxVal = '52749440'; + } else { + $mainElement = 'c:valAx'; + $axIdVal = '52749440'; + $axPosVal = 'l'; + $crossAxVal = '52743552'; + } + + // $mainElement + $objWriter->startElement($mainElement); + + // $mainElement > c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', $axIdVal); + $objWriter->endElement(); + + // $mainElement > c:scaling + $objWriter->startElement('c:scaling'); + + // $mainElement > c:scaling > c:orientation + $objWriter->startElement('c:orientation'); + $objWriter->writeAttribute('val', 'minMax'); + $objWriter->endElement(); + + if ($oAxis->getMaxBounds() != null) { + $objWriter->startElement('c:max'); + $objWriter->writeAttribute('val', $oAxis->getMaxBounds()); + $objWriter->endElement(); + } + + if ($oAxis->getMinBounds() != null) { + $objWriter->startElement('c:min'); + $objWriter->writeAttribute('val', $oAxis->getMinBounds()); + $objWriter->endElement(); + } + + // $mainElement > ##c:scaling + $objWriter->endElement(); + + // $mainElement > c:delete + $objWriter->startElement('c:delete'); + $objWriter->writeAttribute('val', $oAxis->isVisible() ? '0' : '1'); + $objWriter->endElement(); + + // $mainElement > c:axPos + $objWriter->startElement('c:axPos'); + $objWriter->writeAttribute('val', $axPosVal); + $objWriter->endElement(); + + $oMajorGridlines = $oAxis->getMajorGridlines(); + if ($oMajorGridlines instanceof Gridlines) { + $objWriter->startElement('c:majorGridlines'); + + $this->writeAxisGridlines($objWriter, $oMajorGridlines); + + $objWriter->endElement(); + } + + $oMinorGridlines = $oAxis->getMinorGridlines(); + if ($oMinorGridlines instanceof Gridlines) { + $objWriter->startElement('c:minorGridlines'); + + $this->writeAxisGridlines($objWriter, $oMinorGridlines); + + $objWriter->endElement(); + } + + if ($oAxis->getTitle() != '') { + // c:title + $objWriter->startElement('c:title'); + + // c:tx + $objWriter->startElement('c:tx'); + + // c:rich + $objWriter->startElement('c:rich'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($oAxis->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($oAxis->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($oAxis->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($oAxis->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $oAxis->getFont()->getUnderline()); + $objWriter->writeAttributeIf($oAxis->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($oAxis->getFont()->isSubScript(), 'baseline', '-250000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + $this->writeColor($objWriter, $oAxis->getFont()->getColor()); + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $oAxis->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + // ## a:pPr + $objWriter->endElement(); + + // a:r + $objWriter->startElement('a:r'); + + // a:rPr + $objWriter->startElement('a:rPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + // a:t + $objWriter->writeElement('a:t', $oAxis->getTitle()); + + // ## a:r + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + // ## a:p + $objWriter->endElement(); + + // ## c:rich + $objWriter->endElement(); + + // ## c:tx + $objWriter->endElement(); + + // ## c:title + $objWriter->endElement(); + } + + // c:numFmt + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $oAxis->getFormatCode()); + $objWriter->writeAttribute('sourceLinked', '1'); + $objWriter->endElement(); + + // c:majorTickMark + $objWriter->startElement('c:majorTickMark'); + $objWriter->writeAttribute('val', $oAxis->getMajorTickMark()); + $objWriter->endElement(); + + // c:minorTickMark + $objWriter->startElement('c:minorTickMark'); + $objWriter->writeAttribute('val', $oAxis->getMinorTickMark()); + $objWriter->endElement(); + + // c:tickLblPos + $objWriter->startElement('c:tickLblPos'); + $objWriter->writeAttribute('val', 'nextTo'); + $objWriter->endElement(); + + // c:spPr + $objWriter->startElement('c:spPr'); + // Outline + $this->writeOutline($objWriter, $oAxis->getOutline()); + // ##c:spPr + $objWriter->endElement(); + + // c:crossAx + $objWriter->startElement('c:crossAx'); + $objWriter->writeAttribute('val', $crossAxVal); + $objWriter->endElement(); + + // c:crosses + $objWriter->startElement('c:crosses'); + $objWriter->writeAttribute('val', 'autoZero'); + $objWriter->endElement(); + + if ($typeAxis == Chart\Axis::AXIS_X) { + // c:lblAlgn + $objWriter->startElement('c:lblAlgn'); + $objWriter->writeAttribute('val', 'ctr'); + $objWriter->endElement(); + + // c:lblOffset + $objWriter->startElement('c:lblOffset'); + $objWriter->writeAttribute('val', '100'); + $objWriter->endElement(); + } + + if ($typeAxis == Chart\Axis::AXIS_Y) { + // c:crossBetween + $objWriter->startElement('c:crossBetween'); + // midCat : Position Axis On Tick Marks + // between : Between Tick Marks + if ($typeChart instanceof Area) { + $objWriter->writeAttribute('val', 'midCat'); + } else { + $objWriter->writeAttribute('val', 'between'); + } + $objWriter->endElement(); + + // c:majorUnit + if ($oAxis->getMajorUnit() != null) { + $objWriter->startElement('c:majorUnit'); + $objWriter->writeAttribute('val', $oAxis->getMajorUnit()); + $objWriter->endElement(); + } + + // c:minorUnit + if ($oAxis->getMinorUnit() != null) { + $objWriter->startElement('c:minorUnit'); + $objWriter->writeAttribute('val', $oAxis->getMinorUnit()); + $objWriter->endElement(); + } + } + + $objWriter->endElement(); + } + + /** + * @param XMLWriter $objWriter + * @param Gridlines $oGridlines + * @throws \Exception + */ + protected function writeAxisGridlines(XMLWriter $objWriter, Gridlines $oGridlines) + { + // c:spPr + $objWriter->startElement('c:spPr'); + + // Outline + $this->writeOutline($objWriter, $oGridlines->getOutline()); + + // ##c:spPr + $objWriter->endElement(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php new file mode 100644 index 0000000..4c1cc19 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php @@ -0,0 +1,85 @@ +getPresentation()->getAllSlides() as $numSlide => $oSlide) { + $contentXml = $this->writeSlideComments($oSlide); + if (empty($contentXml)) { + continue; + } + $this->getZip()->addFromString('ppt/comments/comment'.($numSlide + 1).'.xml', $contentXml); + } + return $this->getZip(); + } + + /** + * @param Slide $oSlide + * @return string + */ + protected function writeSlideComments(Slide $oSlide) + { + /** + * @var Comment[] + */ + $arrayComment = array(); + foreach ($oSlide->getShapeCollection() as $oShape) { + if ($oShape instanceof Comment) { + $arrayComment[] = $oShape; + } + } + + if (empty($arrayComment)) { + return ''; + } + + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // p:cmLst + $objWriter->startElement('p:cmLst'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + + foreach ($arrayComment as $idxComment => $oComment) { + $oAuthor = $oComment->getAuthor(); + + // p:cmLst > p:cm + $objWriter->startElement('p:cm'); + $objWriter->writeAttribute('authorId', $oAuthor instanceof Comment\Author ? $oAuthor->getIndex() : 0); + $objWriter->writeAttribute('dt', date('Y-m-d\TH:i:s.000000000', $oComment->getDate())); + $objWriter->writeAttribute('idx', $idxComment); + + // p:cmLst > p:cm > p:pos + // Uses 1/8pt for positionning + // @link : https://social.msdn.microsoft.com/Forums/fr-FR/ebdc12f2-0cff-4fa8-b901-fa6e3198364e/comment-position-units + $objWriter->startElement('p:pos'); + $objWriter->writeAttribute('x', (int) CommonDrawing::pixelsToPoints($oComment->getOffsetX() * 8)); + $objWriter->writeAttribute('y', (int) CommonDrawing::pixelsToPoints($oComment->getOffsetY() * 8)); + $objWriter->endElement(); + + // p:cmLst > p:cm > p:text + $objWriter->writeElement('p:text', $oComment->getText()); + + // p:cmLst > ## p:cm + $objWriter->endElement(); + } + + // ## p:cmLst + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php new file mode 100644 index 0000000..58121be --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php @@ -0,0 +1,25 @@ +getDrawingHashTable()->count(); ++$i) { + $shape = $this->getDrawingHashTable()->getByIndex($i); + if (!$shape instanceof AbstractDrawingAdapter) { + continue; + } + $this->getZip()->addFromString('ppt/media/' . $shape->getIndexedFilename(), $shape->getContents()); + } + + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php new file mode 100644 index 0000000..d434e03 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php @@ -0,0 +1,72 @@ +oPresentation->getPresentationProperties(); + + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // p:presentationPr + $objWriter->startElement('p:presentationPr'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + + // p:presentationPr > p:showPr + if ($presentationPpts->isLoopContinuouslyUntilEsc()) { + $objWriter->startElement('p:showPr'); + $objWriter->writeAttribute('loop', '1'); + $objWriter->endElement(); + } + + // p:extLst + $objWriter->startElement('p:extLst'); + + // p:ext + $objWriter->startElement('p:ext'); + $objWriter->writeAttribute('uri', '{E76CE94A-603C-4142-B9EB-6D1370010A27}'); + + // p14:discardImageEditData + $objWriter->startElement('p14:discardImageEditData'); + $objWriter->writeAttribute('xmlns:p14', 'http://schemas.microsoft.com/office/powerpoint/2010/main'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + // > p:ext + $objWriter->endElement(); + + // p:ext + $objWriter->startElement('p:ext'); + $objWriter->writeAttribute('uri', '{D31A062A-798A-4329-ABDD-BBA856620510}'); + + // p14:defaultImageDpi + $objWriter->startElement('p14:defaultImageDpi'); + $objWriter->writeAttribute('xmlns:p14', 'http://schemas.microsoft.com/office/powerpoint/2010/main'); + $objWriter->writeAttribute('val', '220'); + $objWriter->endElement(); + + // > p:ext + $objWriter->endElement(); + // > p:extLst + $objWriter->endElement(); + // > p:presentationPr + $objWriter->endElement(); + + $this->getZip()->addFromString('ppt/presProps.xml', $objWriter->getData()); + + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php new file mode 100644 index 0000000..a38c016 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php @@ -0,0 +1,183 @@ +startDocument('1.0', 'UTF-8', 'yes'); + + // p:presentation + $objWriter->startElement('p:presentation'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + + // p:sldMasterIdLst + $objWriter->startElement('p:sldMasterIdLst'); + + // Add slide masters + $relationId = 1; + $slideMasterId = 2147483648; + + $countMasterSlides = count($this->oPresentation->getAllMasterSlides()); + for ($inc = 1; $inc <= $countMasterSlides; $inc++) { + // p:sldMasterId + $objWriter->startElement('p:sldMasterId'); + $objWriter->writeAttribute('id', $slideMasterId); + $objWriter->writeAttribute('r:id', 'rId' . $relationId++); + $objWriter->endElement(); + + // Increase identifier + $slideMasterId += 12; + } + $objWriter->endElement(); + + // theme + $relationId++; + + // p:sldIdLst + $objWriter->startElement('p:sldIdLst'); + // Write slides + $slideCount = $this->oPresentation->getSlideCount(); + for ($i = 0; $i < $slideCount; ++$i) { + // p:sldId + $objWriter->startElement('p:sldId'); + $objWriter->writeAttribute('id', ($i + 256)); + $objWriter->writeAttribute('r:id', 'rId' . ($i + $relationId)); + $objWriter->endElement(); + } + $objWriter->endElement(); + + // p:sldSz + $objWriter->startElement('p:sldSz'); + $objWriter->writeAttribute('cx', $this->oPresentation->getLayout()->getCX()); + $objWriter->writeAttribute('cy', $this->oPresentation->getLayout()->getCY()); + if ($this->oPresentation->getLayout()->getDocumentLayout() != DocumentLayout::LAYOUT_CUSTOM) { + $objWriter->writeAttribute('type', $this->oPresentation->getLayout()->getDocumentLayout()); + } + $objWriter->endElement(); + + // p:notesSz + $objWriter->startElement('p:notesSz'); + $objWriter->writeAttribute('cx', '6858000'); + $objWriter->writeAttribute('cy', '9144000'); + $objWriter->endElement(); + + $objWriter->writeRaw(' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +'); + + $objWriter->endElement(); + + $this->oZip->addFromString('ppt/presentation.xml', $objWriter->getData()); + + // Return + return $this->oZip; + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php new file mode 100644 index 0000000..fe7a694 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php @@ -0,0 +1,179 @@ +oPresentation->getAllMasterSlides() as $oSlideMaster) { + foreach ($oSlideMaster->getAllSlideLayouts() as $oSlideLayout) { + $this->oZip->addFromString('ppt/slideLayouts/_rels/slideLayout' . $oSlideLayout->layoutNr . '.xml.rels', $this->writeSlideLayoutRelationships($oSlideLayout)); + $this->oZip->addFromString('ppt/slideLayouts/slideLayout' . $oSlideLayout->layoutNr . '.xml', $this->writeSlideLayout($oSlideLayout)); + + // Add background image slide + $oBkgImage = $oSlideLayout->getBackground(); + if ($oBkgImage instanceof Image) { + $this->oZip->addFromString('ppt/media/' . $oBkgImage->getIndexedFilename($oSlideLayout->getRelsIndex()), file_get_contents($oBkgImage->getPath())); + } + } + } + + return $this->oZip; + } + + /** + * Write slide layout relationships to XML format + * + * @param \PhpOffice\PhpPresentation\Slide\SlideLayout $oSlideLayout + * @return string XML Output + * @throws \Exception + */ + public function writeSlideLayoutRelationships(SlideLayout $oSlideLayout) + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + $relId = 0; + + // Write slideMaster relationship + $this->writeRelationship($objWriter, ++$relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster', '../slideMasters/slideMaster' . $oSlideLayout->getSlideMaster()->getRelsIndex() . '.xml'); + + // Write drawing relationships? + $relId = $this->writeDrawingRelations($oSlideLayout, $objWriter, ++$relId); + + // Write background relationships? + $oBackground = $oSlideLayout->getBackground(); + if ($oBackground instanceof Image) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $oBackground->getIndexedFilename($oSlideLayout->getRelsIndex())); + $oBackground->relationId = 'rId' . $relId; + + $relId++; + } + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write slide to XML format + * + * @param \PhpOffice\PhpPresentation\Slide\SlideLayout $pSlideLayout + * @return string XML Output + * @throws \Exception + */ + public function writeSlideLayout(SlideLayout $pSlideLayout) + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + // p:sldLayout + $objWriter->startElement('p:sldLayout'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + $objWriter->writeAttribute('preserve', 1); + // p:sldLayout\p:cSld + $objWriter->startElement('p:cSld'); + $objWriter->writeAttributeIf($pSlideLayout->getLayoutName() != '', 'name', $pSlideLayout->getLayoutName()); + // Background + $this->writeSlideBackground($pSlideLayout, $objWriter); + // p:sldLayout\p:cSld\p:spTree + $objWriter->startElement('p:spTree'); + // p:sldLayout\p:cSld\p:spTree\p:nvGrpSpPr + $objWriter->startElement('p:nvGrpSpPr'); + // p:sldLayout\p:cSld\p:spTree\p:nvGrpSpPr\p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', '1'); + $objWriter->writeAttribute('name', ''); + $objWriter->endElement(); + // p:sldLayout\p:cSld\p:spTree\p:nvGrpSpPr\p:cNvGrpSpPr + $objWriter->writeElement('p:cNvGrpSpPr', null); + // p:sldLayout\p:cSld\p:spTree\p:nvGrpSpPr\p:nvPr + $objWriter->writeElement('p:nvPr', null); + // p:sldLayout\p:cSld\p:spTree\p:nvGrpSpPr + $objWriter->endElement(); + // p:sldLayout\p:cSld\p:spTree\p:grpSpPr + $objWriter->startElement('p:grpSpPr'); + // p:sldLayout\p:cSld\p:spTree\p:grpSpPr\a:xfrm + $objWriter->startElement('a:xfrm'); + // p:sldLayout\p:cSld\p:spTree\p:grpSpPr\a:xfrm\a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($pSlideLayout->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($pSlideLayout->getOffsetY())); + $objWriter->endElement(); + // p:sldLayout\p:cSld\p:spTree\p:grpSpPr\a:xfrm\a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($pSlideLayout->getExtentX())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($pSlideLayout->getExtentY())); + $objWriter->endElement(); + // p:sldLayout\p:cSld\p:spTree\p:grpSpPr\a:xfrm\a:chOff + $objWriter->startElement('a:chOff'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($pSlideLayout->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($pSlideLayout->getOffsetY())); + $objWriter->endElement(); + // p:sldLayout\p:cSld\p:spTree\p:grpSpPr\a:xfrm\a:chExt + $objWriter->startElement('a:chExt'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($pSlideLayout->getExtentX())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($pSlideLayout->getExtentY())); + $objWriter->endElement(); + // p:sldLayout\p:cSld\p:spTree\p:grpSpPr\a:xfrm\ + $objWriter->endElement(); + // p:sldLayout\p:cSld\p:spTree\p:grpSpPr\ + $objWriter->endElement(); + + // Loop shapes + $this->writeShapeCollection($objWriter, $pSlideLayout->getShapeCollection()); + // p:sldLayout\p:cSld\p:spTree\ + $objWriter->endElement(); + // p:sldLayout\p:cSld\ + $objWriter->endElement(); + + // p:sldLayout\p:clrMapOvr + $objWriter->startElement('p:clrMapOvr'); + $arrayDiff = array_diff_assoc(ColorMap::$mappingDefault, $pSlideLayout->colorMap->getMapping()); + if (!empty($arrayDiff)) { + // p:sldLayout\p:clrMapOvr\a:overrideClrMapping + $objWriter->startElement('a:overrideClrMapping'); + foreach ($pSlideLayout->colorMap->getMapping() as $n => $v) { + $objWriter->writeAttribute($n, $v); + } + $objWriter->endElement(); + } else { + // p:sldLayout\p:clrMapOvr\a:masterClrMapping + $objWriter->writeElement('a:masterClrMapping'); + } + // p:sldLayout\p:clrMapOvr\ + $objWriter->endElement(); + + if (!is_null($pSlideLayout->getTransition())) { + $this->writeSlideTransition($objWriter, $pSlideLayout->getTransition()); + } + + // p:sldLayout\ + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php new file mode 100644 index 0000000..56379c2 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php @@ -0,0 +1,238 @@ +oPresentation->getAllMasterSlides() as $oMasterSlide) { + // Add the relations from the masterSlide to the ZIP file + $this->oZip->addFromString('ppt/slideMasters/_rels/slideMaster' . $oMasterSlide->getRelsIndex() . '.xml.rels', $this->writeSlideMasterRelationships($oMasterSlide)); + // Add the information from the masterSlide to the ZIP file + $this->oZip->addFromString('ppt/slideMasters/slideMaster' . $oMasterSlide->getRelsIndex() . '.xml', $this->writeSlideMaster($oMasterSlide)); + + // Add background image slide + $oBkgImage = $oMasterSlide->getBackground(); + if ($oBkgImage instanceof Image) { + $this->oZip->addFromString('ppt/media/' . $oBkgImage->getIndexedFilename($oMasterSlide->getRelsIndex()), file_get_contents($oBkgImage->getPath())); + } + } + + return $this->oZip; + } + + /** + * Write slide master relationships to XML format + * + * @param SlideMaster $oMasterSlide + * @return string XML Output + * @throws \Exception + * @internal param int $masterId Master slide id + */ + public function writeSlideMasterRelationships(SlideMaster $oMasterSlide) + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + // Starting relation id + $relId = 0; + // Write all the relations to the Layout Slides + foreach ($oMasterSlide->getAllSlideLayouts() as $slideLayout) { + $this->writeRelationship($objWriter, ++$relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', '../slideLayouts/slideLayout' . $slideLayout->layoutNr . '.xml'); + // Save the used relationId + $slideLayout->relationId = 'rId' . $relId; + } + + // Write drawing relationships? + $relId = $this->writeDrawingRelations($oMasterSlide, $objWriter, ++$relId); + + // Write background relationships? + $oBackground = $oMasterSlide->getBackground(); + if ($oBackground instanceof Image) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $oBackground->getIndexedFilename($oMasterSlide->getRelsIndex())); + $oBackground->relationId = 'rId' . $relId; + + $relId++; + } + + // TODO: Write hyperlink relationships? + // TODO: Write comment relationships + // Relationship theme/theme1.xml + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', '../theme/theme' . $oMasterSlide->getRelsIndex() . '.xml'); + $objWriter->endElement(); + // Return + return $objWriter->getData(); + } + + /** + * Write slide to XML format + * + * @param \PhpOffice\PhpPresentation\Slide\SlideMaster $pSlide + * @return string XML Output + * @throws \Exception + */ + public function writeSlideMaster(SlideMaster $pSlide) + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + // p:sldMaster + $objWriter->startElement('p:sldMaster'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + // p:sldMaster\p:cSld + $objWriter->startElement('p:cSld'); + // Background + $this->writeSlideBackground($pSlide, $objWriter); + // p:sldMaster\p:cSld\p:spTree + $objWriter->startElement('p:spTree'); + // p:sldMaster\p:cSld\p:spTree\p:nvGrpSpPr + $objWriter->startElement('p:nvGrpSpPr'); + // p:sldMaster\p:cSld\p:spTree\p:nvGrpSpPr\p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', '1'); + $objWriter->writeAttribute('name', ''); + $objWriter->endElement(); + // p:sldMaster\p:cSld\p:spTree\p:nvGrpSpPr\p:cNvGrpSpPr + $objWriter->writeElement('p:cNvGrpSpPr', null); + // p:sldMaster\p:cSld\p:spTree\p:nvGrpSpPr\p:nvPr + $objWriter->writeElement('p:nvPr', null); + // p:sldMaster\p:cSld\p:spTree\p:nvGrpSpPr + $objWriter->endElement(); + // p:sldMaster\p:cSld\p:spTree\p:grpSpPr + $objWriter->startElement('p:grpSpPr'); + // p:sldMaster\p:cSld\p:spTree\p:grpSpPr\a:xfrm + $objWriter->startElement('a:xfrm'); + // p:sldMaster\p:cSld\p:spTree\p:grpSpPr\a:xfrm\a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', 0); + $objWriter->writeAttribute('y', 0); + $objWriter->endElement(); + // p:sldMaster\p:cSld\p:spTree\p:grpSpPr\a:xfrm\a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', 0); + $objWriter->writeAttribute('cy', 0); + $objWriter->endElement(); + // p:sldMaster\p:cSld\p:spTree\p:grpSpPr\a:xfrm\a:chOff + $objWriter->startElement('a:chOff'); + $objWriter->writeAttribute('x', 0); + $objWriter->writeAttribute('y', 0); + $objWriter->endElement(); + // p:sldMaster\p:cSld\p:spTree\p:grpSpPr\a:xfrm\a:chExt + $objWriter->startElement('a:chExt'); + $objWriter->writeAttribute('cx', 0); + $objWriter->writeAttribute('cy', 0); + $objWriter->endElement(); + // p:sldMaster\p:cSld\p:spTree\p:grpSpPr\a:xfrm\ + $objWriter->endElement(); + // p:sldMaster\p:cSld\p:spTree\p:grpSpPr\ + $objWriter->endElement(); + // Loop shapes + $this->writeShapeCollection($objWriter, $pSlide->getShapeCollection()); + // p:sldMaster\p:cSld\p:spTree\ + $objWriter->endElement(); + // p:sldMaster\p:cSld\ + $objWriter->endElement(); + + // p:sldMaster\p:clrMap + $objWriter->startElement('p:clrMap'); + foreach ($pSlide->colorMap->getMapping() as $n => $v) { + $objWriter->writeAttribute($n, $v); + } + $objWriter->endElement(); + // p:sldMaster\p:clrMap\ + + // p:sldMaster\p:sldLayoutIdLst + $objWriter->startElement('p:sldLayoutIdLst'); + foreach ($pSlide->getAllSlideLayouts() as $layout) { + /* @var $layout Slide\SlideLayout */ + $objWriter->startElement('p:sldLayoutId'); + $objWriter->writeAttribute('id', $layout->layoutId); + $objWriter->writeAttribute('r:id', $layout->relationId); + $objWriter->endElement(); + } + $objWriter->endElement(); + // p:sldMaster\p:sldLayoutIdLst\ + + // p:sldMaster\p:txStyles + $objWriter->startElement('p:txStyles'); + foreach (array( + 'p:titleStyle' => $pSlide->getTextStyles()->getTitleStyle(), + 'p:bodyStyle' => $pSlide->getTextStyles()->getBodyStyle(), + 'p:otherStyle' => $pSlide->getTextStyles()->getOtherStyle() + ) as $startElement => $stylesArray) { + // titleStyle + $objWriter->startElement($startElement); + foreach ($stylesArray as $lvl => $oParagraph) { + /** @var RichText\Paragraph $oParagraph */ + $elementName = ($lvl == 0 ? 'a:defPPr' : 'a:lvl' . $lvl . 'pPr'); + $objWriter->startElement($elementName); + $objWriter->writeAttribute('algn', $oParagraph->getAlignment()->getHorizontal()); + $objWriter->writeAttributeIf( + $oParagraph->getAlignment()->getMarginLeft() != 0, + 'marL', + CommonDrawing::pixelsToEmu($oParagraph->getAlignment()->getMarginLeft()) + ); + $objWriter->writeAttributeIf( + $oParagraph->getAlignment()->getMarginRight() != 0, + 'marR', + CommonDrawing::pixelsToEmu($oParagraph->getAlignment()->getMarginRight()) + ); + $objWriter->writeAttributeIf( + $oParagraph->getAlignment()->getIndent() != 0, + 'indent', + CommonDrawing::pixelsToEmu($oParagraph->getAlignment()->getIndent()) + ); + $objWriter->startElement('a:defRPr'); + $objWriter->writeAttributeIf($oParagraph->getFont()->getSize() != 10, 'sz', $oParagraph->getFont()->getSize() * 100); + $objWriter->writeAttributeIf($oParagraph->getFont()->isBold(), 'b', 1); + $objWriter->writeAttributeIf($oParagraph->getFont()->isItalic(), 'i', 1); + $objWriter->writeAttribute('kern', '1200'); + if ($oParagraph->getFont()->getColor() instanceof SchemeColor) { + $objWriter->startElement('a:solidFill'); + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', $oParagraph->getFont()->getColor()->getValue()); + $objWriter->endElement(); + $objWriter->endElement(); + } + $objWriter->endElement(); + $objWriter->endElement(); + } + $objWriter->writeElement('a:extLst'); + $objWriter->endElement(); + } + $objWriter->endElement(); + // p:sldMaster\p:txStyles\ + + if (!is_null($pSlide->getTransition())) { + $this->writeSlideTransition($objWriter, $pSlide->getTransition()); + } + + // p:sldMaster\ + $objWriter->endElement(); + + return $objWriter->getData(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php new file mode 100644 index 0000000..14e73d8 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php @@ -0,0 +1,872 @@ +oPresentation->getAllSlides() as $idx => $oSlide) { + $this->oZip->addFromString('ppt/slides/_rels/slide' . ($idx + 1) . '.xml.rels', $this->writeSlideRelationships($oSlide)); + $this->oZip->addFromString('ppt/slides/slide' . ($idx + 1) . '.xml', $this->writeSlide($oSlide)); + + // Add note slide + if ($oSlide->getNote() instanceof Note) { + if ($oSlide->getNote()->getShapeCollection()->count() > 0) { + $this->oZip->addFromString('ppt/notesSlides/notesSlide' . ($idx + 1) . '.xml', $this->writeNote($oSlide->getNote())); + } + } + + // Add background image slide + $oBkgImage = $oSlide->getBackground(); + if ($oBkgImage instanceof Image) { + $this->oZip->addFromString('ppt/media/'.$oBkgImage->getIndexedFilename($idx), file_get_contents($oBkgImage->getPath())); + } + } + + return $this->oZip; + } + + /** + * Write slide relationships to XML format + * + * @param \PhpOffice\PhpPresentation\Slide $pSlide + * @return string XML Output + * @throws \Exception + */ + protected function writeSlideRelationships(Slide $pSlide) + { + //@todo Group all getShapeCollection()->getIterator + + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Starting relation id + $relId = 1; + $idxSlide = $pSlide->getParent()->getIndex($pSlide); + + // Write slideLayout relationship + $layoutId = 1; + if ($pSlide->getSlideLayout()) { + $layoutId = $pSlide->getSlideLayout()->layoutNr; + } + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', '../slideLayouts/slideLayout' . $layoutId . '.xml'); + ++$relId; + + // Write drawing relationships? + if ($pSlide->getShapeCollection()->count() > 0) { + // Loop trough images and write relationships + $iterator = $pSlide->getShapeCollection()->getIterator(); + while ($iterator->valid()) { + if ($iterator->current() instanceof Media) { + // Write relationship for image drawing + $iterator->current()->relationId = 'rId' . $relId; + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/video', '../media/' . $iterator->current()->getIndexedFilename()); + ++$relId; + $this->writeRelationship($objWriter, $relId, 'http://schemas.microsoft.com/office/2007/relationships/media', '../media/' . $iterator->current()->getIndexedFilename()); + ++$relId; + } elseif ($iterator->current() instanceof ShapeDrawing\AbstractDrawingAdapter) { + // Write relationship for image drawing + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $iterator->current()->getIndexedFilename()); + $iterator->current()->relationId = 'rId' . $relId; + ++$relId; + } elseif ($iterator->current() instanceof ShapeChart) { + // Write relationship for chart drawing + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', '../charts/' . $iterator->current()->getIndexedFilename()); + + $iterator->current()->relationId = 'rId' . $relId; + + ++$relId; + } elseif ($iterator->current() instanceof Group) { + $iterator2 = $iterator->current()->getShapeCollection()->getIterator(); + while ($iterator2->valid()) { + if ($iterator2->current() instanceof Media) { + // Write relationship for image drawing + $iterator2->current()->relationId = 'rId' . $relId; + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/video', '../media/' . $iterator->current()->getIndexedFilename()); + ++$relId; + $this->writeRelationship($objWriter, $relId, 'http://schemas.microsoft.com/office/2007/relationships/media', '../media/' . $iterator->current()->getIndexedFilename()); + ++$relId; + } elseif ($iterator2->current() instanceof ShapeDrawing\AbstractDrawingAdapter) { + // Write relationship for image drawing + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $iterator2->current()->getIndexedFilename()); + $iterator2->current()->relationId = 'rId' . $relId; + + ++$relId; + } elseif ($iterator2->current() instanceof ShapeChart) { + // Write relationship for chart drawing + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', '../charts/' . $iterator2->current()->getIndexedFilename()); + $iterator2->current()->relationId = 'rId' . $relId; + + ++$relId; + } + $iterator2->next(); + } + } + + $iterator->next(); + } + } + + // Write background relationships? + $oBackground = $pSlide->getBackground(); + if ($oBackground instanceof Image) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $oBackground->getIndexedFilename($idxSlide)); + $oBackground->relationId = 'rId' . $relId; + ++$relId; + } + + // Write hyperlink relationships? + if ($pSlide->getShapeCollection()->count() > 0) { + // Loop trough hyperlinks and write relationships + $iterator = $pSlide->getShapeCollection()->getIterator(); + while ($iterator->valid()) { + // Hyperlink on shape + if ($iterator->current()->hasHyperlink()) { + // Write relationship for hyperlink + $hyperlink = $iterator->current()->getHyperlink(); + $hyperlink->relationId = 'rId' . $relId; + + if (!$hyperlink->isInternal()) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', $hyperlink->getUrl(), 'External'); + } else { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', 'slide' . $hyperlink->getSlideNumber() . '.xml'); + } + + ++$relId; + } + + // Hyperlink on rich text run + if ($iterator->current() instanceof RichText) { + foreach ($iterator->current()->getParagraphs() as $paragraph) { + foreach ($paragraph->getRichTextElements() as $element) { + if ($element instanceof Run || $element instanceof TextElement) { + if ($element->hasHyperlink()) { + // Write relationship for hyperlink + $hyperlink = $element->getHyperlink(); + $hyperlink->relationId = 'rId' . $relId; + + if (!$hyperlink->isInternal()) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', $hyperlink->getUrl(), 'External'); + } else { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', 'slide' . $hyperlink->getSlideNumber() . '.xml'); + } + + ++$relId; + } + } + } + } + } + + // Hyperlink in table + if ($iterator->current() instanceof ShapeTable) { + // Rows + $countRows = count($iterator->current()->getRows()); + for ($row = 0; $row < $countRows; $row++) { + // Cells in rows + $countCells = count($iterator->current()->getRow($row)->getCells()); + for ($cell = 0; $cell < $countCells; $cell++) { + $currentCell = $iterator->current()->getRow($row)->getCell($cell); + // Paragraphs in cell + foreach ($currentCell->getParagraphs() as $paragraph) { + // RichText in paragraph + foreach ($paragraph->getRichTextElements() as $element) { + // Run or Text in RichText + if ($element instanceof Run || $element instanceof TextElement) { + if ($element->hasHyperlink()) { + // Write relationship for hyperlink + $hyperlink = $element->getHyperlink(); + $hyperlink->relationId = 'rId' . $relId; + + if (!$hyperlink->isInternal()) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', $hyperlink->getUrl(), 'External'); + } else { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', 'slide' . $hyperlink->getSlideNumber() . '.xml'); + } + + ++$relId; + } + } + } + } + } + } + } + + if ($iterator->current() instanceof Group) { + $iterator2 = $pSlide->getShapeCollection()->getIterator(); + while ($iterator2->valid()) { + // Hyperlink on shape + if ($iterator2->current()->hasHyperlink()) { + // Write relationship for hyperlink + $hyperlink = $iterator2->current()->getHyperlink(); + $hyperlink->relationId = 'rId' . $relId; + + if (!$hyperlink->isInternal()) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', $hyperlink->getUrl(), 'External'); + } else { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', 'slide' . $hyperlink->getSlideNumber() . '.xml'); + } + + ++$relId; + } + + // Hyperlink on rich text run + if ($iterator2->current() instanceof RichText) { + foreach ($iterator2->current()->getParagraphs() as $paragraph) { + foreach ($paragraph->getRichTextElements() as $element) { + if ($element instanceof Run || $element instanceof TextElement) { + if ($element->hasHyperlink()) { + // Write relationship for hyperlink + $hyperlink = $element->getHyperlink(); + $hyperlink->relationId = 'rId' . $relId; + + if (!$hyperlink->isInternal()) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', $hyperlink->getUrl(), 'External'); + } else { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', 'slide' . $hyperlink->getSlideNumber() . '.xml'); + } + + ++$relId; + } + } + } + } + } + + // Hyperlink in table + if ($iterator2->current() instanceof ShapeTable) { + // Rows + $countRows = count($iterator2->current()->getRows()); + for ($row = 0; $row < $countRows; $row++) { + // Cells in rows + $countCells = count($iterator2->current()->getRow($row)->getCells()); + for ($cell = 0; $cell < $countCells; $cell++) { + $currentCell = $iterator2->current()->getRow($row)->getCell($cell); + // Paragraphs in cell + foreach ($currentCell->getParagraphs() as $paragraph) { + // RichText in paragraph + foreach ($paragraph->getRichTextElements() as $element) { + // Run or Text in RichText + if ($element instanceof Run || $element instanceof TextElement) { + if ($element->hasHyperlink()) { + // Write relationship for hyperlink + $hyperlink = $element->getHyperlink(); + $hyperlink->relationId = 'rId' . $relId; + + if (!$hyperlink->isInternal()) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', $hyperlink->getUrl(), 'External'); + } else { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', 'slide' . $hyperlink->getSlideNumber() . '.xml'); + } + + ++$relId; + } + } + } + } + } + } + } + + $iterator2->next(); + } + } + + $iterator->next(); + } + } + + // Write comment relationships + if ($pSlide->getShapeCollection()->count() > 0) { + $hasSlideComment = false; + + // Loop trough images and write relationships + $iterator = $pSlide->getShapeCollection()->getIterator(); + while ($iterator->valid()) { + if ($iterator->current() instanceof Comment) { + $hasSlideComment = true; + break; + } elseif ($iterator->current() instanceof Group) { + $iterator2 = $iterator->current()->getShapeCollection()->getIterator(); + while ($iterator2->valid()) { + if ($iterator2->current() instanceof Comment) { + $hasSlideComment = true; + break 2; + } + $iterator2->next(); + } + } + + $iterator->next(); + } + + if ($hasSlideComment) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', '../comments/comment'.($idxSlide + 1).'.xml'); + ++$relId; + } + } + + if ($pSlide->getNote()->getShapeCollection()->count() > 0) { + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide', '../notesSlides/notesSlide'.($idxSlide + 1).'.xml'); + } + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write slide to XML format + * + * @param \PhpOffice\PhpPresentation\Slide $pSlide + * @return string XML Output + * @throws \Exception + */ + public function writeSlide(Slide $pSlide) + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // p:sld + $objWriter->startElement('p:sld'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + $objWriter->writeAttributeIf(!$pSlide->isVisible(), 'show', 0); + + // p:sld/p:cSld + $objWriter->startElement('p:cSld'); + + // Background + if ($pSlide->getBackground() instanceof Slide\AbstractBackground) { + $oBackground = $pSlide->getBackground(); + // p:bg + $objWriter->startElement('p:bg'); + + // p:bgPr + $objWriter->startElement('p:bgPr'); + + if ($oBackground instanceof Slide\Background\Color) { + // a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $oBackground->getColor()); + + // > a:solidFill + $objWriter->endElement(); + } + + if ($oBackground instanceof Slide\Background\Image) { + // a:blipFill + $objWriter->startElement('a:blipFill'); + + // a:blip + $objWriter->startElement('a:blip'); + $objWriter->writeAttribute('r:embed', $oBackground->relationId); + + // > a:blipFill + $objWriter->endElement(); + + // a:stretch + $objWriter->startElement('a:stretch'); + + // a:fillRect + $objWriter->writeElement('a:fillRect'); + + // > a:stretch + $objWriter->endElement(); + + // > a:blipFill + $objWriter->endElement(); + } + + // > p:bgPr + $objWriter->endElement(); + + // > p:bg + $objWriter->endElement(); + } + + // p:spTree + $objWriter->startElement('p:spTree'); + + // p:nvGrpSpPr + $objWriter->startElement('p:nvGrpSpPr'); + + // p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', '1'); + $objWriter->writeAttribute('name', ''); + $objWriter->endElement(); + + // p:cNvGrpSpPr + $objWriter->writeElement('p:cNvGrpSpPr', null); + + // p:nvPr + $objWriter->writeElement('p:nvPr', null); + + $objWriter->endElement(); + + // p:grpSpPr + $objWriter->startElement('p:grpSpPr'); + + // a:xfrm + $objWriter->startElement('a:xfrm'); + + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($pSlide->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($pSlide->getOffsetY())); + $objWriter->endElement(); // a:off + + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($pSlide->getExtentX())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($pSlide->getExtentY())); + $objWriter->endElement(); // a:ext + + // a:chOff + $objWriter->startElement('a:chOff'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($pSlide->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($pSlide->getOffsetY())); + $objWriter->endElement(); // a:chOff + + // a:chExt + $objWriter->startElement('a:chExt'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($pSlide->getExtentX())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($pSlide->getExtentY())); + $objWriter->endElement(); // a:chExt + + $objWriter->endElement(); + + $objWriter->endElement(); + + // Loop shapes + $this->writeShapeCollection($objWriter, $pSlide->getShapeCollection()); + + // TODO + $objWriter->endElement(); + + $objWriter->endElement(); + + // p:clrMapOvr + $objWriter->startElement('p:clrMapOvr'); + // p:clrMapOvr\a:masterClrMapping + $objWriter->writeElement('a:masterClrMapping', null); + // ##p:clrMapOvr + $objWriter->endElement(); + + $this->writeSlideTransition($objWriter, $pSlide->getTransition()); + + $this->writeSlideAnimations($objWriter, $pSlide); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * @param XMLWriter $objWriter + * @param Slide $oSlide + */ + protected function writeSlideAnimations(XMLWriter $objWriter, Slide $oSlide) + { + $arrayAnimations = $oSlide->getAnimations(); + if (empty($arrayAnimations)) { + return; + } + + // Variables + $shapeId = 1; + $idCount = 1; + $hashToIdMap = array(); + $arrayAnimationIds = array(); + + foreach ($oSlide->getShapeCollection() as $shape) { + $hashToIdMap[$shape->getHashCode()] = ++$shapeId; + } + foreach ($arrayAnimations as $oAnimation) { + foreach ($oAnimation->getShapeCollection() as $oShape) { + $arrayAnimationIds[] = $hashToIdMap[$oShape->getHashCode()]; + } + } + + // p:timing + $objWriter->startElement('p:timing'); + // p:timing/p:tnLst + $objWriter->startElement('p:tnLst'); + // p:timing/p:tnLst/p:par + $objWriter->startElement('p:par'); + // p:timing/p:tnLst/p:par/p:cTn + $objWriter->startElement('p:cTn'); + $objWriter->writeAttribute('id', $idCount++); + $objWriter->writeAttribute('dur', 'indefinite'); + $objWriter->writeAttribute('restart', 'never'); + $objWriter->writeAttribute('nodeType', 'tmRoot'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst + $objWriter->startElement('p:childTnLst'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq + $objWriter->startElement('p:seq'); + $objWriter->writeAttribute('concurrent', '1'); + $objWriter->writeAttribute('nextAc', 'seek'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn + $objWriter->startElement('p:cTn'); + $objWriter->writeAttribute('id', $idCount++); + $objWriter->writeAttribute('dur', 'indefinite'); + $objWriter->writeAttribute('nodeType', 'mainSeq'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst + $objWriter->startElement('p:childTnLst'); + + // Each animation has multiple shapes + foreach ($arrayAnimations as $oAnimation) { + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par + $objWriter->startElement('p:par'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn + $objWriter->startElement('p:cTn'); + $objWriter->writeAttribute('id', $idCount++); + $objWriter->writeAttribute('fill', 'hold'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:stCondLst + $objWriter->startElement('p:stCondLst'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:stCondLst/p:cond + $objWriter->startElement('p:cond'); + $objWriter->writeAttribute('delay', 'indefinite'); + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn\##p:stCondLst + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst + $objWriter->startElement('p:childTnLst'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par + $objWriter->startElement('p:par'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par/p:cTn + $objWriter->startElement('p:cTn'); + $objWriter->writeAttribute('id', $idCount++); + $objWriter->writeAttribute('fill', 'hold'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par/p:cTn/p:stCondLst + $objWriter->startElement('p:stCondLst'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par/p:cTn/p:stCondLst/p:cond + $objWriter->startElement('p:cond'); + $objWriter->writeAttribute('delay', '0'); + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par/p:cTn\##p:stCondLst + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst + $objWriter->startElement('p:childTnLst'); + + $firstAnimation = true; + foreach ($oAnimation->getShapeCollection() as $oShape) { + $nodeType = $firstAnimation ? 'clickEffect' : 'withEffect'; + $shapeId = $hashToIdMap[$oShape->getHashCode()]; + + // p:par + $objWriter->startElement('p:par'); + // p:par/p:cTn + $objWriter->startElement('p:cTn'); + $objWriter->writeAttribute('id', $idCount++); + $objWriter->writeAttribute('presetID', '1'); + $objWriter->writeAttribute('presetClass', 'entr'); + $objWriter->writeAttribute('fill', 'hold'); + $objWriter->writeAttribute('presetSubtype', '0'); + $objWriter->writeAttribute('grpId', '0'); + $objWriter->writeAttribute('nodeType', $nodeType); + // p:par/p:cTn/p:stCondLst + $objWriter->startElement('p:stCondLst'); + // p:par/p:cTn/p:stCondLst/p:cond + $objWriter->startElement('p:cond'); + $objWriter->writeAttribute('delay', '0'); + $objWriter->endElement(); + // p:par/p:cTn\##p:stCondLst + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst + $objWriter->startElement('p:childTnLst'); + // p:par/p:cTn/p:childTnLst/p:set + $objWriter->startElement('p:set'); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr + $objWriter->startElement('p:cBhvr'); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr/p:cTn + $objWriter->startElement('p:cTn'); + $objWriter->writeAttribute('id', $idCount++); + $objWriter->writeAttribute('dur', '1'); + $objWriter->writeAttribute('fill', 'hold'); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr/p:cTn/p:stCondLst + $objWriter->startElement('p:stCondLst'); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr/p:cTn/p:stCondLst/p:cond + $objWriter->startElement('p:cond'); + $objWriter->writeAttribute('delay', '0'); + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr/p:cTn\##p:stCondLst + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr\##p:cTn + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr/p:tgtEl + $objWriter->startElement('p:tgtEl'); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr/p:tgtEl/p:spTgt + $objWriter->startElement('p:spTgt'); + $objWriter->writeAttribute('spid', $shapeId); + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr\##p:tgtEl + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr/p:attrNameLst + $objWriter->startElement('p:attrNameLst'); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr/p:attrNameLst/p:attrName + $objWriter->writeElement('p:attrName', 'style.visibility'); + // p:par/p:cTn/p:childTnLst/p:set/p:cBhvr\##p:attrNameLst + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst/p:set\##p:cBhvr + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst/p:set/p:to + $objWriter->startElement('p:to'); + // p:par/p:cTn/p:childTnLst/p:set/p:to/p:strVal + $objWriter->startElement('p:strVal'); + $objWriter->writeAttribute('val', 'visible'); + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst/p:set\##p:to + $objWriter->endElement(); + // p:par/p:cTn/p:childTnLst\##p:set + $objWriter->endElement(); + // p:par/p:cTn\##p:childTnLst + $objWriter->endElement(); + // p:par\##p:cTn + $objWriter->endElement(); + // ##p:par + $objWriter->endElement(); + + $firstAnimation = false; + } + + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par/p:cTn\##p:childTnLst + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst/p:par\##p:cTn + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn/p:childTnLst\##p:par + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par/p:cTn\##p:childTnLst + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst/p:par\##p:cTn + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn/p:childTnLst\##p:par + $objWriter->endElement(); + } + + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:cTn\##p:childTnLst + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq\##p:cTn + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:prevCondLst + $objWriter->startElement('p:prevCondLst'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:prevCondLst/p:cond + $objWriter->startElement('p:cond'); + $objWriter->writeAttribute('evt', 'onPrev'); + $objWriter->writeAttribute('delay', '0'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:prevCondLst/p:cond/p:tgtEl + $objWriter->startElement('p:tgtEl'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:prevCondLst/p:cond/p:tgtEl/p:sldTgt + $objWriter->writeElement('p:sldTgt', null); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:prevCondLst/p:cond\##p:tgtEl + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:prevCondLst\##p:cond + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq\##p:prevCondLst + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:nextCondLst + $objWriter->startElement('p:nextCondLst'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:nextCondLst/p:cond + $objWriter->startElement('p:cond'); + $objWriter->writeAttribute('evt', 'onNext'); + $objWriter->writeAttribute('delay', '0'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:nextCondLst/p:cond/p:tgtEl + $objWriter->startElement('p:tgtEl'); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:nextCondLst/p:cond/p:tgtEl/p:sldTgt + $objWriter->writeElement('p:sldTgt', null); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:nextCondLst/p:cond\##p:tgtEl + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq/p:nextCondLst\##p:cond + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst/p:seq\##p:nextCondLst + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn/p:childTnLst\##p:seq + $objWriter->endElement(); + // p:timing/p:tnLst/p:par/p:cTn\##p:childTnLst + $objWriter->endElement(); + // p:timing/p:tnLst/p:par\##p:cTn + $objWriter->endElement(); + // p:timing/p:tnLst\##p:par + $objWriter->endElement(); + // p:timing\##p:tnLst + $objWriter->endElement(); + + // p:timing/p:bldLst + $objWriter->startElement('p:bldLst'); + + // Add in ids of all shapes in this slides animations + foreach ($arrayAnimationIds as $id) { + // p:timing/p:bldLst/p:bldP + $objWriter->startElement('p:bldP'); + $objWriter->writeAttribute('spid', $id); + $objWriter->writeAttribute('grpId', 0); + $objWriter->endELement(); + } + + // p:timing\##p:bldLst + $objWriter->endElement(); + + // ##p:timing + $objWriter->endElement(); + } + + /** + * Write pic + * + * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer + * @param \PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter $shape + * @param int $shapeId + * @throws \Exception + */ + protected function writeShapeDrawing(XMLWriter $objWriter, ShapeDrawing\AbstractDrawingAdapter $shape, $shapeId) + { + // p:pic + $objWriter->startElement('p:pic'); + + // p:nvPicPr + $objWriter->startElement('p:nvPicPr'); + + // p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', $shapeId); + $objWriter->writeAttribute('name', $shape->getName()); + $objWriter->writeAttribute('descr', $shape->getDescription()); + + // a:hlinkClick + if ($shape->hasHyperlink()) { + $this->writeHyperlink($objWriter, $shape); + } + + $objWriter->endElement(); + + // p:cNvPicPr + $objWriter->startElement('p:cNvPicPr'); + + // a:picLocks + $objWriter->startElement('a:picLocks'); + $objWriter->writeAttribute('noChangeAspect', '1'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // p:nvPr + $objWriter->startElement('p:nvPr'); + // PlaceHolder + if ($shape->isPlaceholder()) { + $objWriter->startElement('p:ph'); + $objWriter->writeAttribute('type', $shape->getPlaceholder()->getType()); + $objWriter->endElement(); + } + /** + * @link : https://github.com/stefslon/exportToPPTX/blob/master/exportToPPTX.m#L2128 + */ + if ($shape instanceof Media) { + // p:nvPr > a:videoFile + $objWriter->startElement('a:videoFile'); + $objWriter->writeAttribute('r:link', $shape->relationId); + $objWriter->endElement(); + // p:nvPr > p:extLst + $objWriter->startElement('p:extLst'); + // p:nvPr > p:extLst > p:ext + $objWriter->startElement('p:ext'); + $objWriter->writeAttribute('uri', '{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}'); + // p:nvPr > p:extLst > p:ext > p14:media + $objWriter->startElement('p14:media'); + $objWriter->writeAttribute('r:embed', ($shape->relationId + 1)); + $objWriter->writeAttribute('xmlns:p14', 'http://schemas.microsoft.com/office/powerpoint/2010/main'); + // p:nvPr > p:extLst > p:ext > ##p14:media + $objWriter->endElement(); + // p:nvPr > p:extLst > ##p:ext + $objWriter->endElement(); + // p:nvPr > ##p:extLst + $objWriter->endElement(); + } + // ##p:nvPr + $objWriter->endElement(); + $objWriter->endElement(); + + // p:blipFill + $objWriter->startElement('p:blipFill'); + + // a:blip + $objWriter->startElement('a:blip'); + $objWriter->writeAttribute('r:embed', $shape->relationId); + $objWriter->endElement(); + + // a:stretch + $objWriter->startElement('a:stretch'); + $objWriter->writeElement('a:fillRect'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // p:spPr + $objWriter->startElement('p:spPr'); + // a:xfrm + $objWriter->startElement('a:xfrm'); + $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle($shape->getRotation())); + + // a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); + $objWriter->endElement(); + + // a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:prstGeom + $objWriter->startElement('a:prstGeom'); + $objWriter->writeAttribute('prst', 'rect'); + // // a:prstGeom/a:avLst + $objWriter->writeElement('a:avLst', null); + // ##a:prstGeom + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php new file mode 100644 index 0000000..e9dd71d --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php @@ -0,0 +1,30 @@ +startDocument('1.0', 'UTF-8', 'yes'); + + // a:tblStyleLst + $objWriter->startElement('a:tblStyleLst'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('def', '{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}'); + $objWriter->endElement(); + + $this->getZip()->addFromString('ppt/tableStyles.xml', $objWriter->getData()); + + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTheme.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTheme.php new file mode 100644 index 0000000..d6e5ac1 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTheme.php @@ -0,0 +1,857 @@ +oPresentation->getAllMasterSlides() as $oMasterSlide) { + $this->getZip()->addFromString('ppt/theme/theme' . $oMasterSlide->getRelsIndex() . '.xml', $this->writeTheme($oMasterSlide)); + } + + return $this->getZip(); + } + + + /** + * Write theme to XML format + * + * @param Slide\SlideMaster $oMasterSlide + * @return string XML Output + */ + protected function writeTheme(Slide\SlideMaster $oMasterSlide) + { + $arrayFont = array( + 'Jpan' => 'MS Pゴシック', + 'Hang' => '맑은 고딕', + 'Hans' => '宋体', + 'Hant' => '新細明體', + 'Arab' => 'Times New Roman', + 'Hebr' => 'Times New Roman', + 'Thai' => 'Angsana New', + 'Ethi' => 'Nyala', + 'Beng' => 'Vrinda', + 'Gujr' => 'Shruti', + 'Khmr' => 'MoolBoran', + 'Knda' => 'Tunga', + 'Guru' => 'Raavi', + 'Cans' => 'Euphemia', + 'Cher' => 'Plantagenet Cherokee', + 'Yiii' => 'Microsoft Yi Baiti', + 'Tibt' => 'Microsoft Himalaya', + 'Thaa' => 'MV Boli', + 'Deva' => 'Mangal', + 'Telu' => 'Gautami', + 'Taml' => 'Latha', + 'Syrc' => 'Estrangelo Edessa', + 'Orya' => 'Kalinga', + 'Mlym' => 'Kartika', + 'Laoo' => 'DokChampa', + 'Sinh' => 'Iskoola Pota', + 'Mong' => 'Mongolian Baiti', + 'Viet' => 'Times New Roman', + 'Uigh' => 'Microsoft Uighur', + ); + + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + $name = 'Theme'.rand(1, 100); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // a:theme + $objWriter->startElement('a:theme'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('name', $name); + + // a:theme/a:themeElements + $objWriter->startElement('a:themeElements'); + + // a:theme/a:themeElements/a:clrScheme + $objWriter->startElement('a:clrScheme'); + $objWriter->writeAttribute('name', $name); + + foreach ($oMasterSlide->getAllSchemeColors() as $oSchemeColor) { + // a:theme/a:themeElements/a:clrScheme/a:* + $objWriter->startElement('a:'.$oSchemeColor->getValue()); + + if (in_array($oSchemeColor->getValue(), array( + 'dk1', 'lt1' + ))) { + $objWriter->startElement('a:sysClr'); + $objWriter->writeAttribute('val', ($oSchemeColor->getValue() == 'dk1' ? 'windowText' : 'window')); + $objWriter->writeAttribute('lastClr', $oSchemeColor->getRGB()); + $objWriter->endElement(); + } else { + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $oSchemeColor->getRGB()); + $objWriter->endElement(); + } + + // a:theme/a:themeElements/a:clrScheme/a:*/ + $objWriter->endElement(); + } + + // a:theme/a:themeElements/a:clrScheme/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fontScheme + $objWriter->startElement('a:fontScheme'); + $objWriter->writeAttribute('name', $name); + + // a:theme/a:themeElements/a:fontScheme/a:majorFont + $objWriter->startElement('a:majorFont'); + + // a:theme/a:themeElements/a:fontScheme/a:majorFont/a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', 'Calibri'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fontScheme/a:majorFont/a:ea + $objWriter->startElement('a:ea'); + $objWriter->writeAttribute('typeface', ''); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fontScheme/a:majorFont/a:cs + $objWriter->startElement('a:cs'); + $objWriter->writeAttribute('typeface', ''); + $objWriter->endElement(); + + foreach ($arrayFont as $script => $typeface) { + // a:theme/a:themeElements/a:fontScheme/a:majorFont/a:font + $objWriter->startElement('a:font'); + $objWriter->writeAttribute('script', $script); + $objWriter->writeAttribute('typeface', $typeface); + $objWriter->endElement(); + } + + // a:theme/a:themeElements/a:fontScheme/a:majorFont/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fontScheme/a:minorFont + $objWriter->startElement('a:minorFont'); + + // a:theme/a:themeElements/a:fontScheme/a:majorFont/a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', 'Calibri'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fontScheme/a:majorFont/a:ea + $objWriter->startElement('a:ea'); + $objWriter->writeAttribute('typeface', ''); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fontScheme/a:majorFont/a:cs + $objWriter->startElement('a:cs'); + $objWriter->writeAttribute('typeface', ''); + $objWriter->endElement(); + + foreach ($arrayFont as $script => $typeface) { + // a:theme/a:themeElements/a:fontScheme/a:majorFont/a:font + $objWriter->startElement('a:font'); + $objWriter->writeAttribute('script', $script); + $objWriter->writeAttribute('typeface', $typeface); + $objWriter->endElement(); + } + + // a:theme/a:themeElements/a:fontScheme/a:minorFont/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fontScheme/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme + $objWriter->startElement('a:fmtScheme'); + $objWriter->writeAttribute('name', $name); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst + $objWriter->startElement('a:fillStyleLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:solidFill/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:solidFill/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:solidFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill + $objWriter->startElement('a:gradFill'); + $objWriter->writeAttribute('rotWithShape', 1); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst + $objWriter->startElement('a:gsLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '50000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '300000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '35000'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '37000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '300000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '15000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '350000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:lin + $objWriter->startElement('a:lin'); + $objWriter->writeAttribute('ang', 16200000); + $objWriter->writeAttribute('scaled', 1); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill + $objWriter->startElement('a:gradFill'); + $objWriter->writeAttribute('rotWithShape', 1); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst + $objWriter->startElement('a:gsLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '51000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '130000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '80000'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '93000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '130000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '94000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '135000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:lin + $objWriter->startElement('a:lin'); + $objWriter->writeAttribute('ang', 16200000); + $objWriter->writeAttribute('scaled', 0); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst + $objWriter->startElement('a:lnStyleLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', 9525); + $objWriter->writeAttribute('cap', 'flat'); + $objWriter->writeAttribute('cmpd', 'sng'); + $objWriter->writeAttribute('algn', 'ctr'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '95000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:fillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '105000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:prstDash + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', 'solid'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', 25400); + $objWriter->writeAttribute('cap', 'flat'); + $objWriter->writeAttribute('cmpd', 'sng'); + $objWriter->writeAttribute('algn', 'ctr'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:prstDash + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', 'solid'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln + $objWriter->startElement('a:ln'); + $objWriter->writeAttribute('w', 38100); + $objWriter->writeAttribute('cap', 'flat'); + $objWriter->writeAttribute('cmpd', 'sng'); + $objWriter->writeAttribute('algn', 'ctr'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:solidFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/a:prstDash + $objWriter->startElement('a:prstDash'); + $objWriter->writeAttribute('val', 'solid'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/a:ln/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:lnStyleLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst + $objWriter->startElement('a:effectStyleLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle + $objWriter->startElement('a:effectStyle'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', 40000); + $objWriter->writeAttribute('dir', 5400000); + $objWriter->writeAttribute('dist', 20000); + $objWriter->writeAttribute('rotWithShape', 0); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', '000000'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/a:alpha + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', '38000'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/a:alpha/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle + $objWriter->startElement('a:effectStyle'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', 40000); + $objWriter->writeAttribute('dir', 5400000); + $objWriter->writeAttribute('dist', 23000); + $objWriter->writeAttribute('rotWithShape', 0); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', '000000'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/a:alpha + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', '35'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/a:alpha/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle + $objWriter->startElement('a:effectStyle'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst + $objWriter->startElement('a:effectLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw + $objWriter->startElement('a:outerShdw'); + $objWriter->writeAttribute('blurRad', 40000); + $objWriter->writeAttribute('dir', 5400000); + $objWriter->writeAttribute('dist', 23000); + $objWriter->writeAttribute('rotWithShape', 0); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', '000000'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/a:alpha + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', '35000'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/a:alpha/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/a:srgbClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/a:outerShdw/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:effectLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:scene3d + $objWriter->startElement('a:scene3d'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:scene3d/a:camera + $objWriter->startElement('a:camera'); + $objWriter->writeAttribute('prst', 'orthographicFront'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:scene3d/a:camera/a:rot + $objWriter->startElement('a:rot'); + $objWriter->writeAttribute('lat', 0); + $objWriter->writeAttribute('lon', 0); + $objWriter->writeAttribute('rev', 0); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:scene3d/a:camera/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:scene3d/a:lightRig + $objWriter->startElement('a:lightRig'); + $objWriter->writeAttribute('dir', 't'); + $objWriter->writeAttribute('rig', 'threePt'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:scene3d/a:lightRig/a:rot + $objWriter->startElement('a:rot'); + $objWriter->writeAttribute('lat', 0); + $objWriter->writeAttribute('lon', 0); + $objWriter->writeAttribute('rev', 1200000); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:scene3d/a:lightRig/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:scene3d/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:sp3d + $objWriter->startElement('a:sp3d'); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:sp3d/a:bevelT + $objWriter->startElement('a:bevelT'); + $objWriter->writeAttribute('h', 25400); + $objWriter->writeAttribute('w', 63500); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/a:sp3d/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/a:effectStyle/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:effectStyleLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst + $objWriter->startElement('a:bgFillStyleLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:solidFill + $objWriter->startElement('a:solidFill'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:solidFill/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:solidFill/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:solidFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill + $objWriter->startElement('a:gradFill'); + $objWriter->writeAttribute('rotWithShape', 1); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst + $objWriter->startElement('a:gsLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '40000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '350000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '40000'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:shade + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '45000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '99000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '350000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '20000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '255000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:path + $objWriter->startElement('a:path'); + $objWriter->writeAttribute('path', 'circle'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:path/a:fillToRect + $objWriter->startElement('a:fillToRect'); + $objWriter->writeAttribute('b', '180000'); + $objWriter->writeAttribute('l', '50000'); + $objWriter->writeAttribute('r', '50000'); + $objWriter->writeAttribute('t', '-80000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:path/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill + $objWriter->startElement('a:gradFill'); + $objWriter->writeAttribute('rotWithShape', 1); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst + $objWriter->startElement('a:gsLst'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '0'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:tint + $objWriter->startElement('a:tint'); + $objWriter->writeAttribute('val', '80000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '300000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', '100000'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', 'phClr'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:shade + $objWriter->startElement('a:shade'); + $objWriter->writeAttribute('val', '30000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/a:satMod + $objWriter->startElement('a:satMod'); + $objWriter->writeAttribute('val', '200000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/a:schemeClr/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/a:gs/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:gsLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:path + $objWriter->startElement('a:path'); + $objWriter->writeAttribute('path', 'circle'); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:path/a:fillToRect + $objWriter->startElement('a:fillToRect'); + $objWriter->writeAttribute('b', '50000'); + $objWriter->writeAttribute('l', '50000'); + $objWriter->writeAttribute('r', '50000'); + $objWriter->writeAttribute('t', '50000'); + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/a:path/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/a:gradFill/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/a:bgFillStyleLst/ + $objWriter->endElement(); + + // a:theme/a:themeElements/a:fmtScheme/ + $objWriter->endElement(); + + // a:theme/a:themeElements/ + $objWriter->endElement(); + + // a:theme/a:themeElements + $objWriter->writeElement('a:objectDefaults'); + + // a:theme/a:extraClrSchemeLst + $objWriter->writeElement('a:extraClrSchemeLst'); + + // a:theme/ + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php new file mode 100644 index 0000000..f014fe7 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php @@ -0,0 +1,74 @@ +startDocument('1.0', 'UTF-8', 'yes'); + + // p:viewPr + $objWriter->startElement('p:viewPr'); + $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); + $objWriter->writeAttribute('showComments', $this->getPresentation()->getPresentationProperties()->isCommentVisible() ? 1 : 0); + $objWriter->writeAttribute('lastView', $this->getPresentation()->getPresentationProperties()->getLastView()); + + // p:viewPr > p:slideViewPr + $objWriter->startElement('p:slideViewPr'); + + // p:viewPr > p:slideViewPr > p:cSldViewPr + $objWriter->startElement('p:cSldViewPr'); + + // p:viewPr > p:slideViewPr > p:cSldViewPr > p:cViewPr + $objWriter->startElement('p:cViewPr'); + + // p:viewPr > p:slideViewPr > p:cSldViewPr > p:cViewPr > p:scale + $objWriter->startElement('p:scale'); + + $objWriter->startElement('a:sx'); + $objWriter->writeAttribute('d', '100'); + $objWriter->writeAttribute('n', (int)($this->getPresentation()->getPresentationProperties()->getZoom() * 100)); + $objWriter->endElement(); + + $objWriter->startElement('a:sy'); + $objWriter->writeAttribute('d', '100'); + $objWriter->writeAttribute('n', (int)($this->getPresentation()->getPresentationProperties()->getZoom() * 100)); + $objWriter->endElement(); + + // > // p:viewPr > p:slideViewPr > p:cSldViewPr > p:cViewPr > p:scale + $objWriter->endElement(); + + $objWriter->startElement('p:origin'); + $objWriter->writeAttribute('x', '0'); + $objWriter->writeAttribute('y', '0'); + $objWriter->endElement(); + + // > // p:viewPr > p:slideViewPr > p:cSldViewPr > p:cViewPr + $objWriter->endElement(); + + // > // p:viewPr > p:slideViewPr > p:cSldViewPr + $objWriter->endElement(); + + // > // p:viewPr > p:slideViewPr + $objWriter->endElement(); + + // > // p:viewPr + $objWriter->endElement(); + + $this->getZip()->addFromString('ppt/viewProps.xml', $objWriter->getData()); + + return $this->getZip(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php new file mode 100644 index 0000000..ccc5768 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php @@ -0,0 +1,129 @@ +getZip()->addFromString('_rels/.rels', $this->writeRelationships()); + $this->getZip()->addFromString('ppt/_rels/presentation.xml.rels', $this->writePresentationRelationships()); + + return $this->getZip(); + } + + /** + * Write relationships to XML format + * + * @return string XML Output + * @throws \Exception + */ + public function writeRelationships() + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + // Relationship ppt/presentation.xml + $this->writeRelationship($objWriter, 1, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', 'ppt/presentation.xml'); + // Relationship docProps/core.xml + $this->writeRelationship($objWriter, 2, 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', 'docProps/core.xml'); + // Relationship docProps/app.xml + $this->writeRelationship($objWriter, 3, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties', 'docProps/app.xml'); + // Relationship docProps/custom.xml + $this->writeRelationship($objWriter, 4, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties', 'docProps/custom.xml'); + + $idxRelation = 5; + // Thumbnail + if ($this->getPresentation()->getPresentationProperties()->getThumbnailPath()) { + $pathThumbnail = file_get_contents($this->getPresentation()->getPresentationProperties()->getThumbnailPath()); + $gdImage = imagecreatefromstring($pathThumbnail); + if ($gdImage) { + imagedestroy($gdImage); + // Relationship docProps/thumbnail.jpeg + $this->writeRelationship($objWriter, $idxRelation, 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail', 'docProps/thumbnail.jpeg'); + } + } + // ++$idxRelation + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } + + /** + * Write presentation relationships to XML format + * + * @return string XML Output + * @throws \Exception + */ + public function writePresentationRelationships() + { + // Create XML writer + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // Relation id + $relationId = 1; + + foreach ($this->getPresentation()->getAllMasterSlides() as $oMasterSlide) { + // Relationship slideMasters/slideMasterX.xml + $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster', 'slideMasters/slideMaster' . $oMasterSlide->getRelsIndex() . '.xml'); + } + + // Add slide theme (only one!) + // Relationship theme/theme1.xml + $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', 'theme/theme1.xml'); + + // Relationships with slides + $slideCount = $this->getPresentation()->getSlideCount(); + for ($i = 0; $i < $slideCount; ++$i) { + $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', 'slides/slide' . ($i + 1) . '.xml'); + } + + $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps', 'presProps.xml'); + $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps', 'viewProps.xml'); + $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles', 'tableStyles.xml'); + + + // Comments Authors + foreach ($this->getPresentation()->getAllSlides() as $oSlide) { + foreach ($oSlide->getShapeCollection() as $oShape) { + if (!($oShape instanceof Comment)) { + continue; + } + $oAuthor = $oShape->getAuthor(); + if (!($oAuthor instanceof Author)) { + continue; + } + $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors', 'commentAuthors.xml'); + break 2; + } + } + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/Serialized.php b/PhpOffice/PhpPresentation/Writer/Serialized.php new file mode 100644 index 0000000..2d57e84 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/Serialized.php @@ -0,0 +1,131 @@ +setPhpPresentation($pPhpPresentation); + + // Set ZIP Adapter + $this->setZipAdapter(new ZipArchiveAdapter()); + } + + /** + * Save PhpPresentation to file + * + * @param string $pFilename + * @throws \Exception + */ + public function save($pFilename) + { + if (empty($pFilename)) { + throw new \Exception("Filename is empty."); + } + $oPresentation = $this->getPhpPresentation(); + + // Create new ZIP file and open it for writing + $objZip = $this->getZipAdapter(); + + // Try opening the ZIP file + $objZip->open($pFilename); + + // Add media + $slideCount = $oPresentation->getSlideCount(); + for ($i = 0; $i < $slideCount; ++$i) { + for ($j = 0; $j < $oPresentation->getSlide($i)->getShapeCollection()->count(); ++$j) { + if ($oPresentation->getSlide($i)->getShapeCollection()->offsetGet($j) instanceof AbstractDrawingAdapter) { + $imgTemp = $oPresentation->getSlide($i)->getShapeCollection()->offsetGet($j); + $objZip->addFromString('media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME), file_get_contents($imgTemp->getPath())); + } + } + } + + // Add PhpPresentation.xml to the document, which represents a PHP serialized PhpPresentation object + $objZip->addFromString('PhpPresentation.xml', $this->writeSerialized($oPresentation, $pFilename)); + + // Close file + $objZip->close(); + } + + /** + * Serialize PhpPresentation object to XML + * + * @param PhpPresentation $pPhpPresentation + * @param string $pFilename + * @return string XML Output + * @throws \Exception + */ + private function writeSerialized(PhpPresentation $pPhpPresentation = null, $pFilename = '') + { + // Clone $pPhpPresentation + $pPhpPresentation = clone $pPhpPresentation; + + // Update media links + $slideCount = $pPhpPresentation->getSlideCount(); + for ($i = 0; $i < $slideCount; ++$i) { + for ($j = 0; $j < $pPhpPresentation->getSlide($i)->getShapeCollection()->count(); ++$j) { + if ($pPhpPresentation->getSlide($i)->getShapeCollection()->offsetGet($j) instanceof AbstractDrawingAdapter) { + $imgTemp = $pPhpPresentation->getSlide($i)->getShapeCollection()->offsetGet($j); + $imgTemp->setPath('zip://' . $pFilename . '#media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME), false); + } + } + } + + // Create XML writer + $objWriter = new XMLWriter(); + $objWriter->openMemory(); + $objWriter->setIndent(true); + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // PhpPresentation + $objWriter->startElement('PhpPresentation'); + $objWriter->writeAttribute('version', '##VERSION##'); + + // Comment + $objWriter->writeComment('This file has been generated using PhpPresentation v##VERSION## (http://github.com/PHPOffice/PhpPresentation). It contains a base64 encoded serialized version of the PhpPresentation internal object.'); + + // Data + $objWriter->startElement('data'); + $objWriter->writeCData(base64_encode(serialize($pPhpPresentation))); + $objWriter->endElement(); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } +} diff --git a/PhpOffice/PhpPresentation/Writer/WriterInterface.php b/PhpOffice/PhpPresentation/Writer/WriterInterface.php new file mode 100644 index 0000000..bf63a35 --- /dev/null +++ b/PhpOffice/PhpPresentation/Writer/WriterInterface.php @@ -0,0 +1,32 @@ +items; + } + + /** + * Get item by index + * + * @param int $index + * @return \PhpOffice\PhpWord\Element\AbstractContainer + */ + public function getItem($index) + { + if (array_key_exists($index, $this->items)) { + return $this->items[$index]; + } + + return null; + } + + /** + * Set item. + * + * @param int $index + * @param \PhpOffice\PhpWord\Element\AbstractContainer $item + */ + public function setItem($index, $item) + { + if (array_key_exists($index, $this->items)) { + $this->items[$index] = $item; + } + } + + /** + * Add new item + * + * @param \PhpOffice\PhpWord\Element\AbstractContainer $item + * @return int + */ + public function addItem($item) + { + $index = $this->countItems() + 1; + $this->items[$index] = $item; + + return $index; + } + + /** + * Get item count + * + * @return int + */ + public function countItems() + { + return count($this->items); + } +} diff --git a/PhpOffice/PhpWord/Collection/Bookmarks.php b/PhpOffice/PhpWord/Collection/Bookmarks.php new file mode 100644 index 0000000..b5ffd5f --- /dev/null +++ b/PhpOffice/PhpWord/Collection/Bookmarks.php @@ -0,0 +1,27 @@ +pos; + } + + /** + * Set the Footnote Positioning Location (pageBottom, beneathText, sectEnd, docEnd) + * + * @param string $pos + * @throws \InvalidArgumentException + * @return self + */ + public function setPos($pos) + { + $position = array( + self::POSITION_PAGE_BOTTOM, + self::POSITION_BENEATH_TEXT, + self::POSITION_SECTION_END, + self::POSITION_DOC_END, + ); + + if (in_array($pos, $position)) { + $this->pos = $pos; + } else { + throw new \InvalidArgumentException('Invalid value, on of ' . implode(', ', $position) . ' possible'); + } + + return $this; + } + + /** + * Get the Footnote Numbering Format + * + * @return string + */ + public function getNumFmt() + { + return $this->numFmt; + } + + /** + * Set the Footnote Numbering Format + * + * @param string $numFmt One of NumberFormat + * @return self + */ + public function setNumFmt($numFmt) + { + NumberFormat::validate($numFmt); + $this->numFmt = $numFmt; + + return $this; + } + + /** + * Get the Footnote Numbering Format + * + * @return float + */ + public function getNumStart() + { + return $this->numStart; + } + + /** + * Set the Footnote Numbering Format + * + * @param float $numStart + * @return self + */ + public function setNumStart($numStart) + { + $this->numStart = $numStart; + + return $this; + } + + /** + * Get the Footnote and Endnote Numbering Starting Value + * + * @return string + */ + public function getNumRestart() + { + return $this->numRestart; + } + + /** + * Set the Footnote and Endnote Numbering Starting Value (continuous, eachSect, eachPage) + * + * @param string $numRestart + * @throws \InvalidArgumentException + * @return self + */ + public function setNumRestart($numRestart) + { + $restartNumbers = array( + self::RESTART_NUMBER_CONTINUOUS, + self::RESTART_NUMBER_EACH_SECTION, + self::RESTART_NUMBER_EACH_PAGE, + ); + + if (in_array($numRestart, $restartNumbers)) { + $this->numRestart = $numRestart; + } else { + throw new \InvalidArgumentException('Invalid value, on of ' . implode(', ', $restartNumbers) . ' possible'); + } + + return $this; + } +} diff --git a/PhpOffice/PhpWord/ComplexType/ProofState.php b/PhpOffice/PhpWord/ComplexType/ProofState.php new file mode 100644 index 0000000..4f8dafe --- /dev/null +++ b/PhpOffice/PhpWord/ComplexType/ProofState.php @@ -0,0 +1,106 @@ +spelling = $spelling; + } else { + throw new \InvalidArgumentException('Invalid value, dirty or clean possible'); + } + + return $this; + } + + /** + * Get the Spell Checking State + * + * @return string + */ + public function getSpelling() + { + return $this->spelling; + } + + /** + * Set the Grammatical Checking State (dirty or clean) + * + * @param string $grammar + * @throws \InvalidArgumentException + * @return self + */ + public function setGrammar($grammar) + { + if ($grammar == self::CLEAN || $grammar == self::DIRTY) { + $this->grammar = $grammar; + } else { + throw new \InvalidArgumentException('Invalid value, dirty or clean possible'); + } + + return $this; + } + + /** + * Get the Grammatical Checking State + * + * @return string + */ + public function getGrammar() + { + return $this->grammar; + } +} diff --git a/PhpOffice/PhpWord/ComplexType/TblWidth.php b/PhpOffice/PhpWord/ComplexType/TblWidth.php new file mode 100644 index 0000000..0d1a241 --- /dev/null +++ b/PhpOffice/PhpWord/ComplexType/TblWidth.php @@ -0,0 +1,59 @@ +value = $value; + TblWidthSimpleType::validate($type); + $this->type = $type; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getValue() + { + return $this->value; + } +} diff --git a/PhpOffice/PhpWord/ComplexType/TrackChangesView.php b/PhpOffice/PhpWord/ComplexType/TrackChangesView.php new file mode 100644 index 0000000..92ea05e --- /dev/null +++ b/PhpOffice/PhpWord/ComplexType/TrackChangesView.php @@ -0,0 +1,166 @@ +markup; + } + + /** + * Set Display Visual Indicator Of Markup Area + * + * @param bool $markup + * Set to true to show markup + */ + public function setMarkup($markup) + { + $this->markup = $markup === null ? true : $markup; + } + + /** + * Get Display Comments + * + * @return bool True if comments are shown + */ + public function hasComments() + { + return $this->comments; + } + + /** + * Set Display Comments + * + * @param bool $comments + * Set to true to show comments + */ + public function setComments($comments) + { + $this->comments = $comments === null ? true : $comments; + } + + /** + * Get Display Content Revisions + * + * @return bool True if content revisions are shown + */ + public function hasInsDel() + { + return $this->insDel; + } + + /** + * Set Display Content Revisions + * + * @param bool $insDel + * Set to true to show content revisions + */ + public function setInsDel($insDel) + { + $this->insDel = $insDel === null ? true : $insDel; + } + + /** + * Get Display Formatting Revisions + * + * @return bool True if formatting revisions are shown + */ + public function hasFormatting() + { + return $this->formatting; + } + + /** + * Set Display Formatting Revisions + * + * @param bool|null $formatting + * Set to true to show formatting revisions + */ + public function setFormatting($formatting = null) + { + $this->formatting = $formatting === null ? true : $formatting; + } + + /** + * Get Display Ink Annotations + * + * @return bool True if ink annotations are shown + */ + public function hasInkAnnotations() + { + return $this->inkAnnotations; + } + + /** + * Set Display Ink Annotations + * + * @param bool $inkAnnotations + * Set to true to show ink annotations + */ + public function setInkAnnotations($inkAnnotations) + { + $this->inkAnnotations = $inkAnnotations === null ? true : $inkAnnotations; + } +} diff --git a/PhpOffice/PhpWord/Element/AbstractContainer.php b/PhpOffice/PhpWord/Element/AbstractContainer.php new file mode 100644 index 0000000..0c773db --- /dev/null +++ b/PhpOffice/PhpWord/Element/AbstractContainer.php @@ -0,0 +1,315 @@ +addElement($element, $fontStyle, $paragraphStyle); + } + } else { + // All other elements + array_unshift($args, $element); // Prepend element name to the beginning of args array + return call_user_func_array(array($this, 'addElement'), $args); + } + } + + return null; + } + + /** + * Add element + * + * Each element has different number of parameters passed + * + * @param string $elementName + * @return \PhpOffice\PhpWord\Element\AbstractElement + */ + protected function addElement($elementName) + { + $elementClass = __NAMESPACE__ . '\\' . $elementName; + $this->checkValidity($elementName); + + // Get arguments + $args = func_get_args(); + $withoutP = in_array($this->container, array('TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field')); + if ($withoutP && ($elementName == 'Text' || $elementName == 'PreserveText')) { + $args[3] = null; // Remove paragraph style for texts in textrun + } + + // Create element using reflection + $reflection = new \ReflectionClass($elementClass); + $elementArgs = $args; + array_shift($elementArgs); // Shift the $elementName off the beginning of array + + /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */ + $element = $reflection->newInstanceArgs($elementArgs); + + // Set parent container + $element->setParentContainer($this); + $element->setElementIndex($this->countElements() + 1); + $element->setElementId(); + + $this->elements[] = $element; + + return $element; + } + + /** + * Get all elements + * + * @return \PhpOffice\PhpWord\Element\AbstractElement[] + */ + public function getElements() + { + return $this->elements; + } + + /** + * Returns the element at the requested position + * + * @param int $index + * @return \PhpOffice\PhpWord\Element\AbstractElement|null + */ + public function getElement($index) + { + if (array_key_exists($index, $this->elements)) { + return $this->elements[$index]; + } + + return null; + } + + /** + * Removes the element at requested index + * + * @param int|\PhpOffice\PhpWord\Element\AbstractElement $toRemove + */ + public function removeElement($toRemove) + { + if (is_int($toRemove) && array_key_exists($toRemove, $this->elements)) { + unset($this->elements[$toRemove]); + } elseif ($toRemove instanceof \PhpOffice\PhpWord\Element\AbstractElement) { + foreach ($this->elements as $key => $element) { + if ($element->getElementId() === $toRemove->getElementId()) { + unset($this->elements[$key]); + + return; + } + } + } + } + + /** + * Count elements + * + * @return int + */ + public function countElements() + { + return count($this->elements); + } + + /** + * Check if a method is allowed for the current container + * + * @param string $method + * + * @throws \BadMethodCallException + * @return bool + */ + private function checkValidity($method) + { + $generalContainers = array( + 'Section', 'Header', 'Footer', 'Footnote', 'Endnote', 'Cell', 'TextRun', 'TextBox', 'ListItemRun', 'TrackChange', + ); + + $validContainers = array( + 'Text' => $generalContainers, + 'Bookmark' => $generalContainers, + 'Link' => $generalContainers, + 'TextBreak' => $generalContainers, + 'Image' => $generalContainers, + 'OLEObject' => $generalContainers, + 'Field' => $generalContainers, + 'Line' => $generalContainers, + 'Shape' => $generalContainers, + 'FormField' => $generalContainers, + 'SDT' => $generalContainers, + 'TrackChange' => $generalContainers, + 'TextRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox', 'TrackChange', 'ListItemRun'), + 'ListItem' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), + 'ListItemRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), + 'Table' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), + 'CheckBox' => array('Section', 'Header', 'Footer', 'Cell', 'TextRun'), + 'TextBox' => array('Section', 'Header', 'Footer', 'Cell'), + 'Footnote' => array('Section', 'TextRun', 'Cell', 'ListItemRun'), + 'Endnote' => array('Section', 'TextRun', 'Cell'), + 'PreserveText' => array('Section', 'Header', 'Footer', 'Cell'), + 'Title' => array('Section', 'Cell'), + 'TOC' => array('Section'), + 'PageBreak' => array('Section'), + 'Chart' => array('Section', 'Cell'), + ); + + // Special condition, e.g. preservetext can only exists in cell when + // the cell is located in header or footer + $validSubcontainers = array( + 'PreserveText' => array(array('Cell'), array('Header', 'Footer', 'Section')), + 'Footnote' => array(array('Cell', 'TextRun'), array('Section')), + 'Endnote' => array(array('Cell', 'TextRun'), array('Section')), + ); + + // Check if a method is valid for current container + if (isset($validContainers[$method])) { + if (!in_array($this->container, $validContainers[$method])) { + throw new \BadMethodCallException("Cannot add {$method} in {$this->container}."); + } + } + + // Check if a method is valid for current container, located in other container + if (isset($validSubcontainers[$method])) { + $rules = $validSubcontainers[$method]; + $containers = $rules[0]; + $allowedDocParts = $rules[1]; + foreach ($containers as $container) { + if ($this->container == $container && !in_array($this->getDocPart(), $allowedDocParts)) { + throw new \BadMethodCallException("Cannot add {$method} in {$this->container}."); + } + } + } + + return true; + } + + /** + * Create textrun element + * + * @deprecated 0.10.0 + * + * @param mixed $paragraphStyle + * + * @return \PhpOffice\PhpWord\Element\TextRun + * + * @codeCoverageIgnore + */ + public function createTextRun($paragraphStyle = null) + { + return $this->addTextRun($paragraphStyle); + } + + /** + * Create footnote element + * + * @deprecated 0.10.0 + * + * @param mixed $paragraphStyle + * + * @return \PhpOffice\PhpWord\Element\Footnote + * + * @codeCoverageIgnore + */ + public function createFootnote($paragraphStyle = null) + { + return $this->addFootnote($paragraphStyle); + } +} diff --git a/PhpOffice/PhpWord/Element/AbstractElement.php b/PhpOffice/PhpWord/Element/AbstractElement.php new file mode 100644 index 0000000..46372b7 --- /dev/null +++ b/PhpOffice/PhpWord/Element/AbstractElement.php @@ -0,0 +1,507 @@ +phpWord; + } + + /** + * Set PhpWord as reference. + * + * @param \PhpOffice\PhpWord\PhpWord $phpWord + */ + public function setPhpWord(PhpWord $phpWord = null) + { + $this->phpWord = $phpWord; + } + + /** + * Get section number + * + * @return int + */ + public function getSectionId() + { + return $this->sectionId; + } + + /** + * Set doc part. + * + * @param string $docPart + * @param int $docPartId + */ + public function setDocPart($docPart, $docPartId = 1) + { + $this->docPart = $docPart; + $this->docPartId = $docPartId; + } + + /** + * Get doc part + * + * @return string + */ + public function getDocPart() + { + return $this->docPart; + } + + /** + * Get doc part Id + * + * @return int + */ + public function getDocPartId() + { + return $this->docPartId; + } + + /** + * Return media element (image, object, link) container name + * + * @return string section|headerx|footerx|footnote|endnote + */ + private function getMediaPart() + { + $mediaPart = $this->docPart; + if ($mediaPart == 'Header' || $mediaPart == 'Footer') { + $mediaPart .= $this->docPartId; + } + + return strtolower($mediaPart); + } + + /** + * Get element index + * + * @return int + */ + public function getElementIndex() + { + return $this->elementIndex; + } + + /** + * Set element index. + * + * @param int $value + */ + public function setElementIndex($value) + { + $this->elementIndex = $value; + } + + /** + * Get element unique ID + * + * @return string + */ + public function getElementId() + { + return $this->elementId; + } + + /** + * Set element unique ID from 6 first digit of md5. + */ + public function setElementId() + { + $this->elementId = substr(md5(rand()), 0, 6); + } + + /** + * Get relation Id + * + * @return int + */ + public function getRelationId() + { + return $this->relationId; + } + + /** + * Set relation Id. + * + * @param int $value + */ + public function setRelationId($value) + { + $this->relationId = $value; + } + + /** + * Get nested level + * + * @return int + */ + public function getNestedLevel() + { + return $this->nestedLevel; + } + + /** + * Get comment start + * + * @return Comment + */ + public function getCommentRangeStart() + { + return $this->commentRangeStart; + } + + /** + * Set comment start + * + * @param Comment $value + */ + public function setCommentRangeStart(Comment $value) + { + if ($this instanceof Comment) { + throw new \InvalidArgumentException('Cannot set a Comment on a Comment'); + } + $this->commentRangeStart = $value; + $this->commentRangeStart->setStartElement($this); + } + + /** + * Get comment end + * + * @return Comment + */ + public function getCommentRangeEnd() + { + return $this->commentRangeEnd; + } + + /** + * Set comment end + * + * @param Comment $value + */ + public function setCommentRangeEnd(Comment $value) + { + if ($this instanceof Comment) { + throw new \InvalidArgumentException('Cannot set a Comment on a Comment'); + } + $this->commentRangeEnd = $value; + $this->commentRangeEnd->setEndElement($this); + } + + /** + * Get parent element + * + * @return AbstractElement|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * Set parent container + * + * Passed parameter should be a container, except for Table (contain Row) and Row (contain Cell) + * + * @param \PhpOffice\PhpWord\Element\AbstractElement $container + */ + public function setParentContainer(self $container) + { + $this->parentContainer = substr(get_class($container), strrpos(get_class($container), '\\') + 1); + $this->parent = $container; + + // Set nested level + $this->nestedLevel = $container->getNestedLevel(); + if ($this->parentContainer == 'Cell') { + $this->nestedLevel++; + } + + // Set phpword + $this->setPhpWord($container->getPhpWord()); + + // Set doc part + if (!$this instanceof Footnote) { + $this->setDocPart($container->getDocPart(), $container->getDocPartId()); + } + + $this->setMediaRelation(); + $this->setCollectionRelation(); + } + + /** + * Set relation Id for media elements (link, image, object; legacy of OOXML) + * + * - Image element needs to be passed to Media object + * - Icon needs to be set for Object element + */ + private function setMediaRelation() + { + if (!$this instanceof Link && !$this instanceof Image && !$this instanceof OLEObject) { + return; + } + + $elementName = substr(get_class($this), strrpos(get_class($this), '\\') + 1); + if ($elementName == 'OLEObject') { + $elementName = 'Object'; + } + $mediaPart = $this->getMediaPart(); + $source = $this->getSource(); + $image = null; + if ($this instanceof Image) { + $image = $this; + } + $rId = Media::addElement($mediaPart, strtolower($elementName), $source, $image); + $this->setRelationId($rId); + + if ($this instanceof OLEObject) { + $icon = $this->getIcon(); + $rId = Media::addElement($mediaPart, 'image', $icon, new Image($icon)); + $this->setImageRelationId($rId); + } + } + + /** + * Set relation Id for elements that will be registered in the Collection subnamespaces. + */ + private function setCollectionRelation() + { + if ($this->collectionRelation === true && $this->phpWord instanceof PhpWord) { + $elementName = substr(get_class($this), strrpos(get_class($this), '\\') + 1); + $addMethod = "add{$elementName}"; + $rId = $this->phpWord->$addMethod($this); + $this->setRelationId($rId); + } + } + + /** + * Check if element is located in Section doc part (as opposed to Header/Footer) + * + * @return bool + */ + public function isInSection() + { + return $this->docPart == 'Section'; + } + + /** + * Set new style value + * + * @param mixed $styleObject Style object + * @param mixed $styleValue Style value + * @param bool $returnObject Always return object + * @return mixed + */ + protected function setNewStyle($styleObject, $styleValue = null, $returnObject = false) + { + if (!is_null($styleValue) && is_array($styleValue)) { + $styleObject->setStyleByArray($styleValue); + $style = $styleObject; + } else { + $style = $returnObject ? $styleObject : $styleValue; + } + + return $style; + } + + /** + * Sets the trackChange information + * + * @param TrackChange $trackChange + */ + public function setTrackChange(TrackChange $trackChange) + { + $this->trackChange = $trackChange; + } + + /** + * Gets the trackChange information + * + * @return TrackChange + */ + public function getTrackChange() + { + return $this->trackChange; + } + + /** + * Set changed + * + * @param string $type INSERTED|DELETED + * @param string $author + * @param null|int|\DateTime $date allways in UTC + */ + public function setChangeInfo($type, $author, $date = null) + { + $this->trackChange = new TrackChange($type, $author, $date); + } + + /** + * Set enum value + * + * @param string|null $value + * @param string[] $enum + * @param string|null $default + * + * @throws \InvalidArgumentException + * @return string|null + * + * @todo Merge with the same method in AbstractStyle + */ + protected function setEnumVal($value = null, $enum = array(), $default = null) + { + if ($value !== null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { + throw new \InvalidArgumentException("Invalid style value: {$value}"); + } elseif ($value === null || trim($value) == '') { + $value = $default; + } + + return $value; + } +} diff --git a/PhpOffice/PhpWord/Element/Bookmark.php b/PhpOffice/PhpWord/Element/Bookmark.php new file mode 100644 index 0000000..16b020d --- /dev/null +++ b/PhpOffice/PhpWord/Element/Bookmark.php @@ -0,0 +1,60 @@ +name = CommonText::toUTF8($name); + } + + /** + * Get Bookmark name + * + * @return string + */ + public function getName() + { + return $this->name; + } +} diff --git a/PhpOffice/PhpWord/Element/Cell.php b/PhpOffice/PhpWord/Element/Cell.php new file mode 100644 index 0000000..68f5df6 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Cell.php @@ -0,0 +1,77 @@ +width = $width; + $this->style = $this->setNewStyle(new CellStyle(), $style, true); + } + + /** + * Get cell style + * + * @return \PhpOffice\PhpWord\Style\Cell + */ + public function getStyle() + { + return $this->style; + } + + /** + * Get cell width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } +} diff --git a/PhpOffice/PhpWord/Element/Chart.php b/PhpOffice/PhpWord/Element/Chart.php new file mode 100644 index 0000000..92152c8 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Chart.php @@ -0,0 +1,129 @@ +setType($type); + $this->addSeries($categories, $values, $seriesName); + $this->style = $this->setNewStyle(new ChartStyle(), $style, true); + } + + /** + * Get type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set type. + * + * @param string $value + */ + public function setType($value) + { + $enum = array('pie', 'doughnut', 'line', 'bar', 'stacked_bar', 'percent_stacked_bar', 'column', 'stacked_column', 'percent_stacked_column', 'area', 'radar', 'scatter'); + $this->type = $this->setEnumVal($value, $enum, 'pie'); + } + + /** + * Add series + * + * @param array $categories + * @param array $values + * @param null|mixed $name + */ + public function addSeries($categories, $values, $name = null) + { + $this->series[] = array( + 'categories' => $categories, + 'values' => $values, + 'name' => $name, + ); + } + + /** + * Get series + * + * @return array + */ + public function getSeries() + { + return $this->series; + } + + /** + * Get chart style + * + * @return \PhpOffice\PhpWord\Style\Chart + */ + public function getStyle() + { + return $this->style; + } +} diff --git a/PhpOffice/PhpWord/Element/CheckBox.php b/PhpOffice/PhpWord/Element/CheckBox.php new file mode 100644 index 0000000..f3e8717 --- /dev/null +++ b/PhpOffice/PhpWord/Element/CheckBox.php @@ -0,0 +1,72 @@ +setName($name); + parent::__construct($text, $fontStyle, $paragraphStyle); + } + + /** + * Set name content + * + * @param string $name + * @return self + */ + public function setName($name) + { + $this->name = CommonText::toUTF8($name); + + return $this; + } + + /** + * Get name content + * + * @return string + */ + public function getName() + { + return $this->name; + } +} diff --git a/PhpOffice/PhpWord/Element/Comment.php b/PhpOffice/PhpWord/Element/Comment.php new file mode 100644 index 0000000..96ad15e --- /dev/null +++ b/PhpOffice/PhpWord/Element/Comment.php @@ -0,0 +1,122 @@ +initials = $initials; + } + + /** + * Get Initials + * + * @return string + */ + public function getInitials() + { + return $this->initials; + } + + /** + * Sets the element where this comment starts + * + * @param \PhpOffice\PhpWord\Element\AbstractElement $value + */ + public function setStartElement(AbstractElement $value) + { + $this->startElement = $value; + if ($value->getCommentRangeStart() == null) { + $value->setCommentRangeStart($this); + } + } + + /** + * Get the element where this comment starts + * + * @return \PhpOffice\PhpWord\Element\AbstractElement + */ + public function getStartElement() + { + return $this->startElement; + } + + /** + * Sets the element where this comment ends + * + * @param \PhpOffice\PhpWord\Element\AbstractElement $value + */ + public function setEndElement(AbstractElement $value) + { + $this->endElement = $value; + if ($value->getCommentRangeEnd() == null) { + $value->setCommentRangeEnd($this); + } + } + + /** + * Get the element where this comment ends + * + * @return \PhpOffice\PhpWord\Element\AbstractElement + */ + public function getEndElement() + { + return $this->endElement; + } +} diff --git a/PhpOffice/PhpWord/Element/Endnote.php b/PhpOffice/PhpWord/Element/Endnote.php new file mode 100644 index 0000000..b962719 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Endnote.php @@ -0,0 +1,41 @@ + array( + 'properties' => array( + 'format' => array('Arabic', 'ArabicDash', 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN'), + ), + 'options' => array('PreserveFormat'), + ), + 'NUMPAGES' => array( + 'properties' => array( + 'format' => array('Arabic', 'ArabicDash', 'CardText', 'DollarText', 'Ordinal', 'OrdText', + 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN', 'Caps', 'FirstCap', 'Lower', 'Upper', ), + 'numformat' => array('0', '0,00', '#.##0', '#.##0,00', '€ #.##0,00(€ #.##0,00)', '0%', '0,00%'), + ), + 'options' => array('PreserveFormat'), + ), + 'DATE' => array( + 'properties' => array( + 'dateformat' => array( + /* Generic formats */ + 'yyyy-MM-dd', 'yyyy-MM', 'MMM-yy', 'MMM-yyyy', 'h:mm am/pm', 'h:mm:ss am/pm', 'HH:mm', 'HH:mm:ss', + /* Day-Month-Year formats */ + 'dddd d MMMM yyyy', 'd MMMM yyyy', 'd-MMM-yy', 'd MMM. yy', + 'd-M-yy', 'd-M-yy h:mm', 'd-M-yy h:mm:ss', 'd-M-yy h:mm am/pm', 'd-M-yy h:mm:ss am/pm', 'd-M-yy HH:mm', 'd-M-yy HH:mm:ss', + 'd/M/yy', 'd/M/yy h:mm', 'd/M/yy h:mm:ss', 'd/M/yy h:mm am/pm', 'd/M/yy h:mm:ss am/pm', 'd/M/yy HH:mm', 'd/M/yy HH:mm:ss', + 'd-M-yyyy', 'd-M-yyyy h:mm', 'd-M-yyyy h:mm:ss', 'd-M-yyyy h:mm am/pm', 'd-M-yyyy h:mm:ss am/pm', 'd-M-yyyy HH:mm', 'd-M-yyyy HH:mm:ss', + 'd/M/yyyy', 'd/M/yyyy h:mm', 'd/M/yyyy h:mm:ss', 'd/M/yyyy h:mm am/pm', 'd/M/yyyy h:mm:ss am/pm', 'd/M/yyyy HH:mm', 'd/M/yyyy HH:mm:ss', + /* Month-Day-Year formats */ + 'dddd, MMMM d yyyy', 'MMMM d yyyy', 'MMM-d-yy', 'MMM. d yy', + 'M-d-yy', 'M-d-yy h:mm', 'M-d-yy h:mm:ss', 'M-d-yy h:mm am/pm', 'M-d-yy h:mm:ss am/pm', 'M-d-yy HH:mm', 'M-d-yy HH:mm:ss', + 'M/d/yy', 'M/d/yy h:mm', 'M/d/yy h:mm:ss', 'M/d/yy h:mm am/pm', 'M/d/yy h:mm:ss am/pm', 'M/d/yy HH:mm', 'M/d/yy HH:mm:ss', + 'M-d-yyyy', 'M-d-yyyy h:mm', 'M-d-yyyy h:mm:ss', 'M-d-yyyy h:mm am/pm', 'M-d-yyyy h:mm:ss am/pm', 'M-d-yyyy HH:mm', 'M-d-yyyy HH:mm:ss', + 'M/d/yyyy', 'M/d/yyyy h:mm', 'M/d/yyyy h:mm:ss', 'M/d/yyyy h:mm am/pm', 'M/d/yyyy h:mm:ss am/pm', 'M/d/yyyy HH:mm', 'M/d/yyyy HH:mm:ss', + ), + ), + 'options' => array('PreserveFormat', 'LunarCalendar', 'SakaEraCalendar', 'LastUsedFormat'), + ), + 'MACROBUTTON' => array( + 'properties' => array('macroname' => ''), + ), + 'XE' => array( + 'properties' => array(), + 'options' => array('Bold', 'Italic'), + ), + 'INDEX' => array( + 'properties' => array(), + 'options' => array('PreserveFormat'), + ), + 'STYLEREF' => array( + 'properties' => array('StyleIdentifier' => ''), + 'options' => array('PreserveFormat'), + ), + ); + + /** + * Field type + * + * @var string + */ + protected $type; + + /** + * Field text + * + * @var TextRun|string + */ + protected $text; + + /** + * Field properties + * + * @var array + */ + protected $properties = array(); + + /** + * Field options + * + * @var array + */ + protected $options = array(); + + /** + * Font style + * + * @var string|\PhpOffice\PhpWord\Style\Font + */ + protected $fontStyle; + + /** + * Set Font style + * + * @param string|array|\PhpOffice\PhpWord\Style\Font $style + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function setFontStyle($style = null) + { + if ($style instanceof Font) { + $this->fontStyle = $style; + } elseif (is_array($style)) { + $this->fontStyle = new Font('text'); + $this->fontStyle->setStyleByArray($style); + } elseif (null === $style) { + $this->fontStyle = null; + } else { + $this->fontStyle = $style; + } + + return $this->fontStyle; + } + + /** + * Get Font style + * + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function getFontStyle() + { + return $this->fontStyle; + } + + /** + * Create a new Field Element + * + * @param string $type + * @param array $properties + * @param array $options + * @param TextRun|string|null $text + * @param string|array|\PhpOffice\PhpWord\Style\Font $fontStyle + */ + public function __construct($type = null, $properties = array(), $options = array(), $text = null, $fontStyle = null) + { + $this->setType($type); + $this->setProperties($properties); + $this->setOptions($options); + $this->setText($text); + $this->setFontStyle($fontStyle); + } + + /** + * Set Field type + * + * @param string $type + * + * @throws \InvalidArgumentException + * @return string + */ + public function setType($type = null) + { + if (isset($type)) { + if (isset($this->fieldsArray[$type])) { + $this->type = $type; + } else { + throw new \InvalidArgumentException("Invalid type '$type'"); + } + } + + return $this->type; + } + + /** + * Get Field type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set Field properties + * + * @param array $properties + * + * @throws \InvalidArgumentException + * @return self + */ + public function setProperties($properties = array()) + { + if (is_array($properties)) { + foreach (array_keys($properties) as $propkey) { + if (!(isset($this->fieldsArray[$this->type]['properties'][$propkey]))) { + throw new \InvalidArgumentException("Invalid property '$propkey'"); + } + } + $this->properties = array_merge($this->properties, $properties); + } + + return $this->properties; + } + + /** + * Get Field properties + * + * @return array + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Set Field options + * + * @param array $options + * + * @throws \InvalidArgumentException + * @return self + */ + public function setOptions($options = array()) + { + if (is_array($options)) { + foreach (array_keys($options) as $optionkey) { + if (!(isset($this->fieldsArray[$this->type]['options'][$optionkey])) && substr($optionkey, 0, 1) !== '\\') { + throw new \InvalidArgumentException("Invalid option '$optionkey', possible values are " . implode(', ', $this->fieldsArray[$this->type]['options'])); + } + } + $this->options = array_merge($this->options, $options); + } + + return $this->options; + } + + /** + * Get Field properties + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Set Field text + * + * @param string|TextRun $text + * + * @throws \InvalidArgumentException + * @return null|string|TextRun + */ + public function setText($text = null) + { + if (isset($text)) { + if (is_string($text) || $text instanceof TextRun) { + $this->text = $text; + } else { + throw new \InvalidArgumentException('Invalid text'); + } + } + + return $this->text; + } + + /** + * Get Field text + * + * @return string|TextRun + */ + public function getText() + { + return $this->text; + } +} diff --git a/PhpOffice/PhpWord/Element/Footer.php b/PhpOffice/PhpWord/Element/Footer.php new file mode 100644 index 0000000..0290d7c --- /dev/null +++ b/PhpOffice/PhpWord/Element/Footer.php @@ -0,0 +1,116 @@ +sectionId = $sectionId; + $this->setType($type); + $this->setDocPart($this->container, ($sectionId - 1) * 3 + $containerId); + } + + /** + * Set type. + * + * @since 0.10.0 + * + * @param string $value + */ + public function setType($value = self::AUTO) + { + if (!in_array($value, array(self::AUTO, self::FIRST, self::EVEN))) { + $value = self::AUTO; + } + $this->type = $value; + } + + /** + * Get type + * + * @return string + * @since 0.10.0 + */ + public function getType() + { + return $this->type; + } + + /** + * Reset type to default + * + * @return string + */ + public function resetType() + { + return $this->type = self::AUTO; + } + + /** + * First page only header + * + * @return string + */ + public function firstPage() + { + return $this->type = self::FIRST; + } + + /** + * Even numbered pages only + * + * @return string + */ + public function evenPage() + { + return $this->type = self::EVEN; + } +} diff --git a/PhpOffice/PhpWord/Element/Footnote.php b/PhpOffice/PhpWord/Element/Footnote.php new file mode 100644 index 0000000..90aabcc --- /dev/null +++ b/PhpOffice/PhpWord/Element/Footnote.php @@ -0,0 +1,89 @@ +paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); + $this->setDocPart($this->container); + } + + /** + * Get paragraph style + * + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function getParagraphStyle() + { + return $this->paragraphStyle; + } + + /** + * Get Footnote Reference ID + * + * @deprecated 0.10.0 + * @codeCoverageIgnore + * + * @return int + */ + public function getReferenceId() + { + return $this->getRelationId(); + } + + /** + * Set Footnote Reference ID + * + * @deprecated 0.10.0 + * @codeCoverageIgnore + * + * @param int $rId + */ + public function setReferenceId($rId) + { + $this->setRelationId($rId); + } +} diff --git a/PhpOffice/PhpWord/Element/FormField.php b/PhpOffice/PhpWord/Element/FormField.php new file mode 100644 index 0000000..f937df5 --- /dev/null +++ b/PhpOffice/PhpWord/Element/FormField.php @@ -0,0 +1,195 @@ +setType($type); + } + + /** + * Get type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set type + * + * @param string $value + * @return self + */ + public function setType($value) + { + $enum = array('textinput', 'checkbox', 'dropdown'); + $this->type = $this->setEnumVal($value, $enum, $this->type); + + return $this; + } + + /** + * Get name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set name + * + * @param string|bool|int $value + * @return self + */ + public function setName($value) + { + $this->name = $value; + + return $this; + } + + /** + * Get default + * + * @return string|bool|int + */ + public function getDefault() + { + return $this->default; + } + + /** + * Set default + * + * @param string|bool|int $value + * @return self + */ + public function setDefault($value) + { + $this->default = $value; + + return $this; + } + + /** + * Get value + * + * @return string|bool|int + */ + public function getValue() + { + return $this->value; + } + + /** + * Set value + * + * @param string|bool|int $value + * @return self + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * Get entries + * + * @return array + */ + public function getEntries() + { + return $this->entries; + } + + /** + * Set entries + * + * @param array $value + * @return self + */ + public function setEntries($value) + { + $this->entries = $value; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Element/Header.php b/PhpOffice/PhpWord/Element/Header.php new file mode 100644 index 0000000..8a01946 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Header.php @@ -0,0 +1,41 @@ +addImage($src, $style, true); + } +} diff --git a/PhpOffice/PhpWord/Element/Image.php b/PhpOffice/PhpWord/Element/Image.php new file mode 100644 index 0000000..bae87ff --- /dev/null +++ b/PhpOffice/PhpWord/Element/Image.php @@ -0,0 +1,602 @@ +source = $source; + $this->style = $this->setNewStyle(new ImageStyle(), $style, true); + $this->setIsWatermark($watermark); + $this->setName($name); + + $this->checkImage(); + } + + /** + * Get Image style + * + * @return ImageStyle + */ + public function getStyle() + { + return $this->style; + } + + /** + * Get image source + * + * @return string + */ + public function getSource() + { + return $this->source; + } + + /** + * Get image source type + * + * @return string + */ + public function getSourceType() + { + return $this->sourceType; + } + + /** + * Sets the image name + * + * @param string $value + */ + public function setName($value) + { + $this->name = $value; + } + + /** + * Get image name + * + * @return null|string + */ + public function getName() + { + return $this->name; + } + + /** + * Get image media ID + * + * @return string + */ + public function getMediaId() + { + return md5($this->source); + } + + /** + * Get is watermark + * + * @return bool + */ + public function isWatermark() + { + return $this->watermark; + } + + /** + * Set is watermark + * + * @param bool $value + */ + public function setIsWatermark($value) + { + $this->watermark = $value; + } + + /** + * Get image type + * + * @return string + */ + public function getImageType() + { + return $this->imageType; + } + + /** + * Get image create function + * + * @return string + */ + public function getImageCreateFunction() + { + return $this->imageCreateFunc; + } + + /** + * Get image function + * + * @return string + */ + public function getImageFunction() + { + return $this->imageFunc; + } + + /** + * Get image extension + * + * @return string + */ + public function getImageExtension() + { + return $this->imageExtension; + } + + /** + * Get is memory image + * + * @return bool + */ + public function isMemImage() + { + return $this->memoryImage; + } + + /** + * Get target file name + * + * @return string + */ + public function getTarget() + { + return $this->target; + } + + /** + * Set target file name. + * + * @param string $value + */ + public function setTarget($value) + { + $this->target = $value; + } + + /** + * Get media index + * + * @return int + */ + public function getMediaIndex() + { + return $this->mediaIndex; + } + + /** + * Set media index. + * + * @param int $value + */ + public function setMediaIndex($value) + { + $this->mediaIndex = $value; + } + + /** + * Get image string data + * + * @param bool $base64 + * @return string|null + * @since 0.11.0 + */ + public function getImageStringData($base64 = false) + { + $source = $this->source; + $actualSource = null; + $imageBinary = null; + $imageData = null; + $isTemp = false; + + // Get actual source from archive image or other source + // Return null if not found + if ($this->sourceType == self::SOURCE_ARCHIVE) { + $source = substr($source, 6); + list($zipFilename, $imageFilename) = explode('#', $source); + + $zip = new ZipArchive(); + if ($zip->open($zipFilename) !== false) { + if ($zip->locateName($imageFilename) !== false) { + $isTemp = true; + $zip->extractTo(Settings::getTempDir(), $imageFilename); + $actualSource = Settings::getTempDir() . DIRECTORY_SEPARATOR . $imageFilename; + } + } + $zip->close(); + } else { + $actualSource = $source; + } + + // Can't find any case where $actualSource = null hasn't captured by + // preceding exceptions. Please uncomment when you find the case and + // put the case into Element\ImageTest. + // if ($actualSource === null) { + // return null; + // } + + // Read image binary data and convert to hex/base64 string + if ($this->sourceType == self::SOURCE_GD) { + $imageResource = call_user_func($this->imageCreateFunc, $actualSource); + if ($this->imageType === 'image/png') { + // PNG images need to preserve alpha channel information + imagesavealpha($imageResource, true); + } + ob_start(); + call_user_func($this->imageFunc, $imageResource); + $imageBinary = ob_get_contents(); + ob_end_clean(); + } elseif ($this->sourceType == self::SOURCE_STRING) { + $imageBinary = $this->source; + } else { + $fileHandle = fopen($actualSource, 'rb', false); + if ($fileHandle !== false) { + $imageBinary = fread($fileHandle, filesize($actualSource)); + fclose($fileHandle); + } + } + if ($imageBinary !== null) { + if ($base64) { + $imageData = chunk_split(base64_encode($imageBinary)); + } else { + $imageData = chunk_split(bin2hex($imageBinary)); + } + } + + // Delete temporary file if necessary + if ($isTemp === true) { + @unlink($actualSource); + } + + return $imageData; + } + + /** + * Check memory image, supported type, image functions, and proportional width/height. + * + * @throws \PhpOffice\PhpWord\Exception\InvalidImageException + * @throws \PhpOffice\PhpWord\Exception\UnsupportedImageTypeException + */ + private function checkImage() + { + $this->setSourceType(); + + // Check image data + if ($this->sourceType == self::SOURCE_ARCHIVE) { + $imageData = $this->getArchiveImageSize($this->source); + } elseif ($this->sourceType == self::SOURCE_STRING) { + $imageData = $this->getStringImageSize($this->source); + } else { + $imageData = @getimagesize($this->source); + } + if (!is_array($imageData)) { + throw new InvalidImageException(sprintf('Invalid image: %s', $this->source)); + } + list($actualWidth, $actualHeight, $imageType) = $imageData; + + // Check image type support + $supportedTypes = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG); + if ($this->sourceType != self::SOURCE_GD && $this->sourceType != self::SOURCE_STRING) { + $supportedTypes = array_merge($supportedTypes, array(IMAGETYPE_BMP, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM)); + } + if (!in_array($imageType, $supportedTypes)) { + throw new UnsupportedImageTypeException(); + } + + // Define image functions + $this->imageType = image_type_to_mime_type($imageType); + $this->setFunctions(); + $this->setProportionalSize($actualWidth, $actualHeight); + } + + /** + * Set source type. + */ + private function setSourceType() + { + if (stripos(strrev($this->source), strrev('.php')) === 0) { + $this->memoryImage = true; + $this->sourceType = self::SOURCE_GD; + } elseif (strpos($this->source, 'zip://') !== false) { + $this->memoryImage = false; + $this->sourceType = self::SOURCE_ARCHIVE; + } elseif (filter_var($this->source, FILTER_VALIDATE_URL) !== false) { + $this->memoryImage = true; + if (strpos($this->source, 'https') === 0) { + $fileContent = file_get_contents($this->source); + $this->source = $fileContent; + $this->sourceType = self::SOURCE_STRING; + } else { + $this->sourceType = self::SOURCE_GD; + } + } elseif (@file_exists($this->source)) { + $this->memoryImage = false; + $this->sourceType = self::SOURCE_LOCAL; + } else { + $this->memoryImage = true; + $this->sourceType = self::SOURCE_STRING; + } + } + + /** + * Get image size from archive + * + * @since 0.12.0 Throws CreateTemporaryFileException. + * + * @param string $source + * + * @throws \PhpOffice\PhpWord\Exception\CreateTemporaryFileException + * + * @return array|null + */ + private function getArchiveImageSize($source) + { + $imageData = null; + $source = substr($source, 6); + list($zipFilename, $imageFilename) = explode('#', $source); + + $tempFilename = tempnam(Settings::getTempDir(), 'PHPWordImage'); + if (false === $tempFilename) { + throw new CreateTemporaryFileException(); // @codeCoverageIgnore + } + + $zip = new ZipArchive(); + if ($zip->open($zipFilename) !== false) { + if ($zip->locateName($imageFilename) !== false) { + $imageContent = $zip->getFromName($imageFilename); + if ($imageContent !== false) { + file_put_contents($tempFilename, $imageContent); + $imageData = getimagesize($tempFilename); + unlink($tempFilename); + } + } + $zip->close(); + } + + return $imageData; + } + + /** + * get image size from string + * + * @param string $source + * + * @codeCoverageIgnore this method is just a replacement for getimagesizefromstring which exists only as of PHP 5.4 + */ + private function getStringImageSize($source) + { + $result = false; + if (!function_exists('getimagesizefromstring')) { + $uri = 'data://application/octet-stream;base64,' . base64_encode($source); + $result = @getimagesize($uri); + } else { + $result = @getimagesizefromstring($source); + } + + return $result; + } + + /** + * Set image functions and extensions. + */ + private function setFunctions() + { + switch ($this->imageType) { + case 'image/png': + $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefrompng'; + $this->imageFunc = 'imagepng'; + $this->imageExtension = 'png'; + break; + case 'image/gif': + $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromgif'; + $this->imageFunc = 'imagegif'; + $this->imageExtension = 'gif'; + break; + case 'image/jpeg': + case 'image/jpg': + $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromjpeg'; + $this->imageFunc = 'imagejpeg'; + $this->imageExtension = 'jpg'; + break; + case 'image/bmp': + case 'image/x-ms-bmp': + $this->imageType = 'image/bmp'; + $this->imageExtension = 'bmp'; + break; + case 'image/tiff': + $this->imageExtension = 'tif'; + break; + } + } + + /** + * Set proportional width/height if one dimension not available. + * + * @param int $actualWidth + * @param int $actualHeight + */ + private function setProportionalSize($actualWidth, $actualHeight) + { + $styleWidth = $this->style->getWidth(); + $styleHeight = $this->style->getHeight(); + if (!($styleWidth && $styleHeight)) { + if ($styleWidth == null && $styleHeight == null) { + $this->style->setWidth($actualWidth); + $this->style->setHeight($actualHeight); + } elseif ($styleWidth) { + $this->style->setHeight($actualHeight * ($styleWidth / $actualWidth)); + } else { + $this->style->setWidth($actualWidth * ($styleHeight / $actualHeight)); + } + } + } + + /** + * Get is watermark + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getIsWatermark() + { + return $this->isWatermark(); + } + + /** + * Get is memory image + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getIsMemImage() + { + return $this->isMemImage(); + } +} diff --git a/PhpOffice/PhpWord/Element/Line.php b/PhpOffice/PhpWord/Element/Line.php new file mode 100644 index 0000000..7e40b94 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Line.php @@ -0,0 +1,53 @@ +style = $this->setNewStyle(new LineStyle(), $style); + } + + /** + * Get line style + * + * @return \PhpOffice\PhpWord\Style\Line + */ + public function getStyle() + { + return $this->style; + } +} diff --git a/PhpOffice/PhpWord/Element/Link.php b/PhpOffice/PhpWord/Element/Link.php new file mode 100644 index 0000000..2bec32d --- /dev/null +++ b/PhpOffice/PhpWord/Element/Link.php @@ -0,0 +1,180 @@ +source = CommonText::toUTF8($source); + $this->text = is_null($text) ? $this->source : CommonText::toUTF8($text); + $this->fontStyle = $this->setNewStyle(new Font('text'), $fontStyle); + $this->paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); + $this->internal = $internal; + } + + /** + * Get link source + * + * @return string + */ + public function getSource() + { + return $this->source; + } + + /** + * Get link text + * + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * Get Text style + * + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function getFontStyle() + { + return $this->fontStyle; + } + + /** + * Get Paragraph style + * + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function getParagraphStyle() + { + return $this->paragraphStyle; + } + + /** + * Get link target + * + * @deprecated 0.12.0 + * + * @return string + * + * @codeCoverageIgnore + */ + public function getTarget() + { + return $this->source; + } + + /** + * Get Link source + * + * @deprecated 0.10.0 + * + * @return string + * + * @codeCoverageIgnore + */ + public function getLinkSrc() + { + return $this->getSource(); + } + + /** + * Get Link name + * + * @deprecated 0.10.0 + * + * @return string + * + * @codeCoverageIgnore + */ + public function getLinkName() + { + return $this->getText(); + } + + /** + * is internal + * + * @return bool + */ + public function isInternal() + { + return $this->internal; + } +} diff --git a/PhpOffice/PhpWord/Element/ListItem.php b/PhpOffice/PhpWord/Element/ListItem.php new file mode 100644 index 0000000..8b064c4 --- /dev/null +++ b/PhpOffice/PhpWord/Element/ListItem.php @@ -0,0 +1,111 @@ +textObject = new Text(CommonText::toUTF8($text), $fontStyle, $paragraphStyle); + $this->depth = $depth; + + // Version >= 0.10.0 will pass numbering style name. Older version will use old method + if (!is_null($listStyle) && is_string($listStyle)) { + $this->style = new ListItemStyle($listStyle); // @codeCoverageIgnore + } else { + $this->style = $this->setNewStyle(new ListItemStyle(), $listStyle, true); + } + } + + /** + * Get style + * + * @return \PhpOffice\PhpWord\Style\ListItem + */ + public function getStyle() + { + return $this->style; + } + + /** + * Get Text object + * + * @return \PhpOffice\PhpWord\Element\Text + */ + public function getTextObject() + { + return $this->textObject; + } + + /** + * Get depth + * + * @return int + */ + public function getDepth() + { + return $this->depth; + } + + /** + * Get text + * + * @return string + * @since 0.11.0 + */ + public function getText() + { + return $this->textObject->getText(); + } +} diff --git a/PhpOffice/PhpWord/Element/ListItemRun.php b/PhpOffice/PhpWord/Element/ListItemRun.php new file mode 100644 index 0000000..6e48a69 --- /dev/null +++ b/PhpOffice/PhpWord/Element/ListItemRun.php @@ -0,0 +1,85 @@ +depth = $depth; + + // Version >= 0.10.0 will pass numbering style name. Older version will use old method + if (!is_null($listStyle) && is_string($listStyle)) { + $this->style = new ListItemStyle($listStyle); + } else { + $this->style = $this->setNewStyle(new ListItemStyle(), $listStyle, true); + } + parent::__construct($paragraphStyle); + } + + /** + * Get ListItem style. + * + * @return \PhpOffice\PhpWord\Style\ListItem + */ + public function getStyle() + { + return $this->style; + } + + /** + * Get ListItem depth. + * + * @return int + */ + public function getDepth() + { + return $this->depth; + } +} diff --git a/PhpOffice/PhpWord/Element/OLEObject.php b/PhpOffice/PhpWord/Element/OLEObject.php new file mode 100644 index 0000000..1a17b74 --- /dev/null +++ b/PhpOffice/PhpWord/Element/OLEObject.php @@ -0,0 +1,169 @@ +source = $source; + $this->style = $this->setNewStyle(new ImageStyle(), $style, true); + $this->icon = realpath(__DIR__ . "/../resources/{$ext}.png"); + + return; + } + + throw new InvalidObjectException(); + } + + /** + * Get object source + * + * @return string + */ + public function getSource() + { + return $this->source; + } + + /** + * Get object style + * + * @return \PhpOffice\PhpWord\Style\Image + */ + public function getStyle() + { + return $this->style; + } + + /** + * Get object icon + * + * @return string + */ + public function getIcon() + { + return $this->icon; + } + + /** + * Get image relation ID + * + * @return int + */ + public function getImageRelationId() + { + return $this->imageRelationId; + } + + /** + * Set Image Relation ID. + * + * @param int $rId + */ + public function setImageRelationId($rId) + { + $this->imageRelationId = $rId; + } + + /** + * Get Object ID + * + * @deprecated 0.10.0 + * + * @return int + * + * @codeCoverageIgnore + */ + public function getObjectId() + { + return $this->relationId + 1325353440; + } + + /** + * Set Object ID + * + * @deprecated 0.10.0 + * + * @param int $objId + * + * @codeCoverageIgnore + */ + public function setObjectId($objId) + { + $this->relationId = $objId; + } +} diff --git a/PhpOffice/PhpWord/Element/PageBreak.php b/PhpOffice/PhpWord/Element/PageBreak.php new file mode 100644 index 0000000..1e2ada8 --- /dev/null +++ b/PhpOffice/PhpWord/Element/PageBreak.php @@ -0,0 +1,31 @@ +fontStyle = $this->setNewStyle(new Font('text'), $fontStyle); + $this->paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); + + $this->text = CommonText::toUTF8($text); + $matches = preg_split('/({.*?})/', $this->text, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + if (isset($matches[0])) { + $this->text = $matches; + } + } + + /** + * Get Text style + * + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function getFontStyle() + { + return $this->fontStyle; + } + + /** + * Get Paragraph style + * + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function getParagraphStyle() + { + return $this->paragraphStyle; + } + + /** + * Get Text content + * + * @return string|array + */ + public function getText() + { + return $this->text; + } +} diff --git a/PhpOffice/PhpWord/Element/Row.php b/PhpOffice/PhpWord/Element/Row.php new file mode 100644 index 0000000..da4dfe5 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Row.php @@ -0,0 +1,107 @@ +height = $height; + $this->style = $this->setNewStyle(new RowStyle(), $style, true); + } + + /** + * Add a cell + * + * @param int $width + * @param mixed $style + * @return \PhpOffice\PhpWord\Element\Cell + */ + public function addCell($width = null, $style = null) + { + $cell = new Cell($width, $style); + $cell->setParentContainer($this); + $this->cells[] = $cell; + + return $cell; + } + + /** + * Get all cells + * + * @return \PhpOffice\PhpWord\Element\Cell[] + */ + public function getCells() + { + return $this->cells; + } + + /** + * Get row style + * + * @return \PhpOffice\PhpWord\Style\Row + */ + public function getStyle() + { + return $this->style; + } + + /** + * Get row height + * + * @return int + */ + public function getHeight() + { + return $this->height; + } +} diff --git a/PhpOffice/PhpWord/Element/SDT.php b/PhpOffice/PhpWord/Element/SDT.php new file mode 100644 index 0000000..5548f76 --- /dev/null +++ b/PhpOffice/PhpWord/Element/SDT.php @@ -0,0 +1,190 @@ +setType($type); + } + + /** + * Get type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set type + * + * @param string $value + * @return self + */ + public function setType($value) + { + $enum = array('plainText', 'comboBox', 'dropDownList', 'date'); + $this->type = $this->setEnumVal($value, $enum, 'comboBox'); + + return $this; + } + + /** + * Get value + * + * @return string|bool|int + */ + public function getValue() + { + return $this->value; + } + + /** + * Set value + * + * @param string|bool|int $value + * @return self + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * Get listItems + * + * @return array + */ + public function getListItems() + { + return $this->listItems; + } + + /** + * Set listItems + * + * @param array $value + * @return self + */ + public function setListItems($value) + { + $this->listItems = $value; + + return $this; + } + + /** + * Get tag + * + * @return string + */ + public function getTag() + { + return $this->tag; + } + + /** + * Set tag + * + * @param string $tag + * @return self + */ + public function setTag($tag) + { + $this->tag = $tag; + + return $this; + } + + /** + * Get alias + * + * @return string + */ + public function getAlias() + { + return $this->alias; + } + + /** + * Set alias + * + * @param string $alias + * @return self + */ + public function setAlias($alias) + { + $this->alias = $alias; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Element/Section.php b/PhpOffice/PhpWord/Element/Section.php new file mode 100644 index 0000000..b6da9f3 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Section.php @@ -0,0 +1,305 @@ +sectionId = $sectionCount; + $this->setDocPart($this->container, $this->sectionId); + if (null === $style) { + $style = new SectionStyle(); + } + $this->style = $this->setNewStyle(new SectionStyle(), $style); + } + + /** + * Set section style. + * + * @param array $style + */ + public function setStyle($style = null) + { + if (!is_null($style) && is_array($style)) { + $this->style->setStyleByArray($style); + } + } + + /** + * Get section style + * + * @return \PhpOffice\PhpWord\Style\Section + */ + public function getStyle() + { + return $this->style; + } + + /** + * Add header + * + * @since 0.10.0 + * + * @param string $type + * + * @return Header + */ + public function addHeader($type = Header::AUTO) + { + return $this->addHeaderFooter($type, true); + } + + /** + * Add footer + * + * @since 0.10.0 + * + * @param string $type + * + * @return Footer + */ + public function addFooter($type = Header::AUTO) + { + return $this->addHeaderFooter($type, false); + } + + /** + * Get header elements + * + * @return Header[] + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Get footer elements + * + * @return Footer[] + */ + public function getFooters() + { + return $this->footers; + } + + /** + * Get the footnote properties + * + * @return FootnoteProperties + */ + public function getFootnoteProperties() + { + return $this->footnoteProperties; + } + + /** + * Get the footnote properties + * + * @deprecated Use the `getFootnoteProperties` method instead + * + * @return FootnoteProperties + */ + public function getFootnotePropoperties() + { + return $this->footnoteProperties; + } + + /** + * Set the footnote properties + * + * @param FootnoteProperties $footnoteProperties + */ + public function setFootnoteProperties(FootnoteProperties $footnoteProperties = null) + { + $this->footnoteProperties = $footnoteProperties; + } + + /** + * Is there a header for this section that is for the first page only? + * + * If any of the Header instances have a type of Header::FIRST then this method returns true. + * False otherwise. + * + * @return bool + */ + public function hasDifferentFirstPage() + { + foreach ($this->headers as $header) { + if ($header->getType() == Header::FIRST) { + return true; + } + } + foreach ($this->footers as $footer) { + if ($footer->getType() == Header::FIRST) { + return true; + } + } + + return false; + } + + /** + * Add header/footer + * + * @since 0.10.0 + * + * @param string $type + * @param bool $header + * + * @throws \Exception + * + * @return Header|Footer + */ + private function addHeaderFooter($type = Header::AUTO, $header = true) + { + $containerClass = substr(get_class($this), 0, strrpos(get_class($this), '\\')) . '\\' . + ($header ? 'Header' : 'Footer'); + $collectionArray = $header ? 'headers' : 'footers'; + $collection = &$this->$collectionArray; + + if (in_array($type, array(Header::AUTO, Header::FIRST, Header::EVEN))) { + $index = count($collection); + /** @var \PhpOffice\PhpWord\Element\AbstractContainer $container Type hint */ + $container = new $containerClass($this->sectionId, ++$index, $type); + $container->setPhpWord($this->phpWord); + + $collection[$index] = $container; + + return $container; + } + throw new \Exception('Invalid header/footer type.'); + } + + /** + * Set section style + * + * @deprecated 0.12.0 + * + * @param array $settings + * + * @codeCoverageIgnore + */ + public function setSettings($settings = null) + { + $this->setStyle($settings); + } + + /** + * Get section style + * + * @deprecated 0.12.0 + * + * @return \PhpOffice\PhpWord\Style\Section + * + * @codeCoverageIgnore + */ + public function getSettings() + { + return $this->getStyle(); + } + + /** + * Create header + * + * @deprecated 0.10.0 + * + * @return Header + * + * @codeCoverageIgnore + */ + public function createHeader() + { + return $this->addHeader(); + } + + /** + * Create footer + * + * @deprecated 0.10.0 + * + * @return Footer + * + * @codeCoverageIgnore + */ + public function createFooter() + { + return $this->addFooter(); + } + + /** + * Get footer + * + * @deprecated 0.10.0 + * + * @return Footer + * + * @codeCoverageIgnore + */ + public function getFooter() + { + if (empty($this->footers)) { + return null; + } + + return $this->footers[1]; + } +} diff --git a/PhpOffice/PhpWord/Element/Shape.php b/PhpOffice/PhpWord/Element/Shape.php new file mode 100644 index 0000000..d143c9b --- /dev/null +++ b/PhpOffice/PhpWord/Element/Shape.php @@ -0,0 +1,88 @@ +setType($type); + $this->style = $this->setNewStyle(new ShapeStyle(), $style); + } + + /** + * Get type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set pattern + * + * @param string $value + * @return self + */ + public function setType($value = null) + { + $enum = array('arc', 'curve', 'line', 'polyline', 'rect', 'oval'); + $this->type = $this->setEnumVal($value, $enum, null); + + return $this; + } + + /** + * Get shape style + * + * @return \PhpOffice\PhpWord\Style\Shape + */ + public function getStyle() + { + return $this->style; + } +} diff --git a/PhpOffice/PhpWord/Element/TOC.php b/PhpOffice/PhpWord/Element/TOC.php new file mode 100644 index 0000000..c51d0e6 --- /dev/null +++ b/PhpOffice/PhpWord/Element/TOC.php @@ -0,0 +1,169 @@ +TOCStyle = new TOCStyle(); + + if (!is_null($tocStyle) && is_array($tocStyle)) { + $this->TOCStyle->setStyleByArray($tocStyle); + } + + if (!is_null($fontStyle) && is_array($fontStyle)) { + $this->fontStyle = new Font(); + $this->fontStyle->setStyleByArray($fontStyle); + } else { + $this->fontStyle = $fontStyle; + } + + $this->minDepth = $minDepth; + $this->maxDepth = $maxDepth; + } + + /** + * Get all titles + * + * @return array + */ + public function getTitles() + { + if (!$this->phpWord instanceof PhpWord) { + return array(); + } + + $titles = $this->phpWord->getTitles()->getItems(); + foreach ($titles as $i => $title) { + /** @var \PhpOffice\PhpWord\Element\Title $title Type hint */ + $depth = $title->getDepth(); + if ($this->minDepth > $depth) { + unset($titles[$i]); + } + if (($this->maxDepth != 0) && ($this->maxDepth < $depth)) { + unset($titles[$i]); + } + } + + return $titles; + } + + /** + * Get TOC Style + * + * @return \PhpOffice\PhpWord\Style\TOC + */ + public function getStyleTOC() + { + return $this->TOCStyle; + } + + /** + * Get Font Style + * + * @return \PhpOffice\PhpWord\Style\Font|string + */ + public function getStyleFont() + { + return $this->fontStyle; + } + + /** + * Set max depth. + * + * @param int $value + */ + public function setMaxDepth($value) + { + $this->maxDepth = $value; + } + + /** + * Get Max Depth + * + * @return int Max depth of titles + */ + public function getMaxDepth() + { + return $this->maxDepth; + } + + /** + * Set min depth. + * + * @param int $value + */ + public function setMinDepth($value) + { + $this->minDepth = $value; + } + + /** + * Get Min Depth + * + * @return int Min depth of titles + */ + public function getMinDepth() + { + return $this->minDepth; + } +} diff --git a/PhpOffice/PhpWord/Element/Table.php b/PhpOffice/PhpWord/Element/Table.php new file mode 100644 index 0000000..44fe374 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Table.php @@ -0,0 +1,174 @@ +style = $this->setNewStyle(new TableStyle(), $style); + } + + /** + * Add a row + * + * @param int $height + * @param mixed $style + * @return \PhpOffice\PhpWord\Element\Row + */ + public function addRow($height = null, $style = null) + { + $row = new Row($height, $style); + $row->setParentContainer($this); + $this->rows[] = $row; + + return $row; + } + + /** + * Add a cell + * + * @param int $width + * @param mixed $style + * @return \PhpOffice\PhpWord\Element\Cell + */ + public function addCell($width = null, $style = null) + { + $index = count($this->rows) - 1; + $row = $this->rows[$index]; + $cell = $row->addCell($width, $style); + + return $cell; + } + + /** + * Get all rows + * + * @return \PhpOffice\PhpWord\Element\Row[] + */ + public function getRows() + { + return $this->rows; + } + + /** + * Get table style + * + * @return \PhpOffice\PhpWord\Style\Table + */ + public function getStyle() + { + return $this->style; + } + + /** + * Get table width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set table width. + * + * @param int $width + */ + public function setWidth($width) + { + $this->width = $width; + } + + /** + * Get column count + * + * @return int + */ + public function countColumns() + { + $columnCount = 0; + + $rowCount = count($this->rows); + for ($i = 0; $i < $rowCount; $i++) { + /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ + $row = $this->rows[$i]; + $cellCount = count($row->getCells()); + if ($columnCount < $cellCount) { + $columnCount = $cellCount; + } + } + + return $columnCount; + } + + /** + * The first declared cell width for each column + * + * @return int[] + */ + public function findFirstDefinedCellWidths() + { + $cellWidths = array(); + + foreach ($this->rows as $row) { + $cells = $row->getCells(); + if (count($cells) <= count($cellWidths)) { + continue; + } + $cellWidths = array(); + foreach ($cells as $cell) { + $cellWidths[] = $cell->getWidth(); + } + } + + return $cellWidths; + } +} diff --git a/PhpOffice/PhpWord/Element/Text.php b/PhpOffice/PhpWord/Element/Text.php new file mode 100644 index 0000000..f4d7f08 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Text.php @@ -0,0 +1,153 @@ +setText($text); + $paragraphStyle = $this->setParagraphStyle($paragraphStyle); + $this->setFontStyle($fontStyle, $paragraphStyle); + } + + /** + * Set Text style + * + * @param string|array|\PhpOffice\PhpWord\Style\Font $style + * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $paragraphStyle + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function setFontStyle($style = null, $paragraphStyle = null) + { + if ($style instanceof Font) { + $this->fontStyle = $style; + $this->setParagraphStyle($paragraphStyle); + } elseif (is_array($style)) { + $this->fontStyle = new Font('text', $paragraphStyle); + $this->fontStyle->setStyleByArray($style); + } elseif (null === $style) { + $this->fontStyle = new Font('text', $paragraphStyle); + } else { + $this->fontStyle = $style; + $this->setParagraphStyle($paragraphStyle); + } + + return $this->fontStyle; + } + + /** + * Get Text style + * + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function getFontStyle() + { + return $this->fontStyle; + } + + /** + * Set Paragraph style + * + * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $style + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function setParagraphStyle($style = null) + { + if (is_array($style)) { + $this->paragraphStyle = new Paragraph(); + $this->paragraphStyle->setStyleByArray($style); + } elseif ($style instanceof Paragraph) { + $this->paragraphStyle = $style; + } elseif (null === $style) { + $this->paragraphStyle = new Paragraph(); + } else { + $this->paragraphStyle = $style; + } + + return $this->paragraphStyle; + } + + /** + * Get Paragraph style + * + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function getParagraphStyle() + { + return $this->paragraphStyle; + } + + /** + * Set text content + * + * @param string $text + * @return self + */ + public function setText($text) + { + $this->text = CommonText::toUTF8($text); + + return $this; + } + + /** + * Get Text content + * + * @return string + */ + public function getText() + { + return $this->text; + } +} diff --git a/PhpOffice/PhpWord/Element/TextBox.php b/PhpOffice/PhpWord/Element/TextBox.php new file mode 100644 index 0000000..b9f274d --- /dev/null +++ b/PhpOffice/PhpWord/Element/TextBox.php @@ -0,0 +1,60 @@ +style = $this->setNewStyle(new TextBoxStyle(), $style); + } + + /** + * Get textbox style + * + * @return \PhpOffice\PhpWord\Style\TextBox + */ + public function getStyle() + { + return $this->style; + } +} diff --git a/PhpOffice/PhpWord/Element/TextBreak.php b/PhpOffice/PhpWord/Element/TextBreak.php new file mode 100644 index 0000000..385fec5 --- /dev/null +++ b/PhpOffice/PhpWord/Element/TextBreak.php @@ -0,0 +1,130 @@ +setParagraphStyle($paragraphStyle); + } + if (!is_null($fontStyle)) { + $this->setFontStyle($fontStyle, $paragraphStyle); + } + } + + /** + * Set Text style + * + * @param mixed $style + * @param mixed $paragraphStyle + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function setFontStyle($style = null, $paragraphStyle = null) + { + if ($style instanceof Font) { + $this->fontStyle = $style; + $this->setParagraphStyle($paragraphStyle); + } elseif (is_array($style)) { + $this->fontStyle = new Font('text', $paragraphStyle); + $this->fontStyle->setStyleByArray($style); + } else { + $this->fontStyle = $style; + $this->setParagraphStyle($paragraphStyle); + } + + return $this->fontStyle; + } + + /** + * Get Text style + * + * @return string|\PhpOffice\PhpWord\Style\Font + */ + public function getFontStyle() + { + return $this->fontStyle; + } + + /** + * Set Paragraph style + * + * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $style + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function setParagraphStyle($style = null) + { + if (is_array($style)) { + $this->paragraphStyle = new Paragraph(); + $this->paragraphStyle->setStyleByArray($style); + } elseif ($style instanceof Paragraph) { + $this->paragraphStyle = $style; + } else { + $this->paragraphStyle = $style; + } + + return $this->paragraphStyle; + } + + /** + * Get Paragraph style + * + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function getParagraphStyle() + { + return $this->paragraphStyle; + } + + /** + * Has font/paragraph style defined + * + * @return bool + */ + public function hasStyle() + { + return !is_null($this->fontStyle) || !is_null($this->paragraphStyle); + } +} diff --git a/PhpOffice/PhpWord/Element/TextRun.php b/PhpOffice/PhpWord/Element/TextRun.php new file mode 100644 index 0000000..9af55d4 --- /dev/null +++ b/PhpOffice/PhpWord/Element/TextRun.php @@ -0,0 +1,80 @@ +paragraphStyle = $this->setParagraphStyle($paragraphStyle); + } + + /** + * Get Paragraph style + * + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function getParagraphStyle() + { + return $this->paragraphStyle; + } + + /** + * Set Paragraph style + * + * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $style + * @return string|\PhpOffice\PhpWord\Style\Paragraph + */ + public function setParagraphStyle($style = null) + { + if (is_array($style)) { + $this->paragraphStyle = new Paragraph(); + $this->paragraphStyle->setStyleByArray($style); + } elseif ($style instanceof Paragraph) { + $this->paragraphStyle = $style; + } elseif (null === $style) { + $this->paragraphStyle = new Paragraph(); + } else { + $this->paragraphStyle = $style; + } + + return $this->paragraphStyle; + } +} diff --git a/PhpOffice/PhpWord/Element/Title.php b/PhpOffice/PhpWord/Element/Title.php new file mode 100644 index 0000000..d01f7f3 --- /dev/null +++ b/PhpOffice/PhpWord/Element/Title.php @@ -0,0 +1,108 @@ +text = CommonText::toUTF8($text); + } elseif ($text instanceof TextRun) { + $this->text = $text; + } else { + throw new \InvalidArgumentException('Invalid text, should be a string or a TextRun'); + } + + $this->depth = $depth; + $styleName = $depth === 0 ? 'Title' : "Heading_{$this->depth}"; + if (array_key_exists($styleName, Style::getStyles())) { + $this->style = str_replace('_', '', $styleName); + } + } + + /** + * Get Title Text content + * + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * Get depth + * + * @return int + */ + public function getDepth() + { + return $this->depth; + } + + /** + * Get Title style + * + * @return string + */ + public function getStyle() + { + return $this->style; + } +} diff --git a/PhpOffice/PhpWord/Element/TrackChange.php b/PhpOffice/PhpWord/Element/TrackChange.php new file mode 100644 index 0000000..91c221f --- /dev/null +++ b/PhpOffice/PhpWord/Element/TrackChange.php @@ -0,0 +1,101 @@ +changeType = $changeType; + $this->author = $author; + if ($date !== null && $date !== false) { + $this->date = ($date instanceof \DateTime) ? $date : new \DateTime('@' . $date); + } + } + + /** + * Get TrackChange Author + * + * @return string + */ + public function getAuthor() + { + return $this->author; + } + + /** + * Get TrackChange Date + * + * @return \DateTime + */ + public function getDate() + { + return $this->date; + } + + /** + * Get the Change type + * + * @return string + */ + public function getChangeType() + { + return $this->changeType; + } +} diff --git a/PhpOffice/PhpWord/Escaper/AbstractEscaper.php b/PhpOffice/PhpWord/Escaper/AbstractEscaper.php new file mode 100644 index 0000000..1575c06 --- /dev/null +++ b/PhpOffice/PhpWord/Escaper/AbstractEscaper.php @@ -0,0 +1,46 @@ +escapeSingleValue($item); + } + } else { + $input = $this->escapeSingleValue($input); + } + + return $input; + } +} diff --git a/PhpOffice/PhpWord/Escaper/EscaperInterface.php b/PhpOffice/PhpWord/Escaper/EscaperInterface.php new file mode 100644 index 0000000..deb2cfb --- /dev/null +++ b/PhpOffice/PhpWord/Escaper/EscaperInterface.php @@ -0,0 +1,33 @@ + $code || $code >= 0x80) { + return '{\\u' . $code . '}'; + } + if ($code == 123 || $code == 125 || $code == 92) { // open or close brace or backslash + return '\\' . chr($code); + } + + return chr($code); + } + + protected function escapeMultibyteCharacter($code) + { + return '\\uc0{\\u' . $code . '}'; + } + + /** + * @see http://www.randomchaos.com/documents/?source=php_and_unicode + * @param string $input + */ + protected function escapeSingleValue($input) + { + $escapedValue = ''; + + $numberOfBytes = 1; + $bytes = array(); + for ($i = 0; $i < strlen($input); ++$i) { + $character = $input[$i]; + $asciiCode = ord($character); + + if ($asciiCode < 128) { + $escapedValue .= $this->escapeAsciiCharacter($asciiCode); + } else { + if (0 == count($bytes)) { + if ($asciiCode < 224) { + $numberOfBytes = 2; + } elseif ($asciiCode < 240) { + $numberOfBytes = 3; + } elseif ($asciiCode < 248) { + $numberOfBytes = 4; + } + } + + $bytes[] = $asciiCode; + + if ($numberOfBytes == count($bytes)) { + if (4 == $numberOfBytes) { + $multibyteCode = ($bytes[0] % 8) * 262144 + ($bytes[1] % 64) * 4096 + ($bytes[2] % 64) * 64 + ($bytes[3] % 64); + } elseif (3 == $numberOfBytes) { + $multibyteCode = ($bytes[0] % 16) * 4096 + ($bytes[1] % 64) * 64 + ($bytes[2] % 64); + } else { + $multibyteCode = ($bytes[0] % 32) * 64 + ($bytes[1] % 64); + } + + if (65279 != $multibyteCode) { + $escapedValue .= $multibyteCode < 128 ? $this->escapeAsciiCharacter($multibyteCode) : $this->escapeMultibyteCharacter($multibyteCode); + } + + $numberOfBytes = 1; + $bytes = array(); + } + } + } + + return $escapedValue; + } +} diff --git a/PhpOffice/PhpWord/Escaper/Xml.php b/PhpOffice/PhpWord/Escaper/Xml.php new file mode 100644 index 0000000..a769c5e --- /dev/null +++ b/PhpOffice/PhpWord/Escaper/Xml.php @@ -0,0 +1,32 @@ +load($filename); + } + + /** + * Check if it's a concrete class (not abstract nor interface) + * + * @param string $class + * @return bool + */ + private static function isConcreteClass($class) + { + $reflection = new \ReflectionClass($class); + + return !$reflection->isAbstract() && !$reflection->isInterface(); + } +} diff --git a/PhpOffice/PhpWord/Media.php b/PhpOffice/PhpWord/Media.php new file mode 100644 index 0000000..cc1b290 --- /dev/null +++ b/PhpOffice/PhpWord/Media.php @@ -0,0 +1,365 @@ + $mediaTypeCount); + + switch ($mediaType) { + // Images + case 'image': + if (is_null($image)) { + throw new Exception('Image object not assigned.'); + } + $isMemImage = $image->isMemImage(); + $extension = $image->getImageExtension(); + $mediaData['imageExtension'] = $extension; + $mediaData['imageType'] = $image->getImageType(); + if ($isMemImage) { + $mediaData['isMemImage'] = true; + $mediaData['createFunction'] = $image->getImageCreateFunction(); + $mediaData['imageFunction'] = $image->getImageFunction(); + } + $target = "{$container}_image{$mediaTypeCount}.{$extension}"; + $image->setTarget($target); + $image->setMediaIndex($mediaTypeCount); + break; + // Objects + case 'object': + $target = "{$container}_oleObject{$mediaTypeCount}.bin"; + break; + // Links + case 'link': + $target = $source; + break; + } + + $mediaData['source'] = $source; + $mediaData['target'] = $target; + $mediaData['type'] = $mediaType; + $mediaData['rID'] = $rId; + self::$elements[$container][$mediaId] = $mediaData; + + return $rId; + } + + $mediaData = self::$elements[$container][$mediaId]; + if (!is_null($image)) { + $image->setTarget($mediaData['target']); + $image->setMediaIndex($mediaData['mediaIndex']); + } + + return $mediaData['rID']; + } + + /** + * Get media elements count + * + * @param string $container section|headerx|footerx|footnote|endnote + * @param string $mediaType image|object|link + * @return int + * @since 0.10.0 + */ + public static function countElements($container, $mediaType = null) + { + $mediaCount = 0; + + if (isset(self::$elements[$container])) { + foreach (self::$elements[$container] as $mediaData) { + if (!is_null($mediaType)) { + if ($mediaType == $mediaData['type']) { + $mediaCount++; + } + } else { + $mediaCount++; + } + } + } + + return $mediaCount; + } + + /** + * Get media elements + * + * @param string $container section|headerx|footerx|footnote|endnote + * @param string $type image|object|link + * @return array + * @since 0.10.0 + */ + public static function getElements($container, $type = null) + { + $elements = array(); + + // If header/footer, search for headerx and footerx where x is number + if ($container == 'header' || $container == 'footer') { + foreach (self::$elements as $key => $val) { + if (substr($key, 0, 6) == $container) { + $elements[$key] = $val; + } + } + + return $elements; + } + + if (!isset(self::$elements[$container])) { + return $elements; + } + + return self::getElementsByType($container, $type); + } + + /** + * Get elements by media type + * + * @param string $container section|footnote|endnote + * @param string $type image|object|link + * @return array + * @since 0.11.0 Splitted from `getElements` to reduce complexity + */ + private static function getElementsByType($container, $type = null) + { + $elements = array(); + + foreach (self::$elements[$container] as $key => $data) { + if ($type !== null) { + if ($type == $data['type']) { + $elements[$key] = $data; + } + } else { + $elements[$key] = $data; + } + } + + return $elements; + } + + /** + * Reset media elements + */ + public static function resetElements() + { + self::$elements = array(); + } + + /** + * Add new Section Media Element + * + * @deprecated 0.10.0 + * + * @param string $src + * @param string $type + * @param \PhpOffice\PhpWord\Element\Image $image + * + * @return int + * + * @codeCoverageIgnore + */ + public static function addSectionMediaElement($src, $type, Image $image = null) + { + return self::addElement('section', $type, $src, $image); + } + + /** + * Add new Section Link Element + * + * @deprecated 0.10.0 + * + * @param string $linkSrc + * + * @return int + * + * @codeCoverageIgnore + */ + public static function addSectionLinkElement($linkSrc) + { + return self::addElement('section', 'link', $linkSrc); + } + + /** + * Get Section Media Elements + * + * @deprecated 0.10.0 + * + * @param string $key + * + * @return array + * + * @codeCoverageIgnore + */ + public static function getSectionMediaElements($key = null) + { + return self::getElements('section', $key); + } + + /** + * Get Section Media Elements Count + * + * @deprecated 0.10.0 + * + * @param string $key + * + * @return int + * + * @codeCoverageIgnore + */ + public static function countSectionMediaElements($key = null) + { + return self::countElements('section', $key); + } + + /** + * Add new Header Media Element + * + * @deprecated 0.10.0 + * + * @param int $headerCount + * @param string $src + * @param \PhpOffice\PhpWord\Element\Image $image + * + * @return int + * + * @codeCoverageIgnore + */ + public static function addHeaderMediaElement($headerCount, $src, Image $image = null) + { + return self::addElement("header{$headerCount}", 'image', $src, $image); + } + + /** + * Get Header Media Elements Count + * + * @deprecated 0.10.0 + * + * @param string $key + * + * @return int + * + * @codeCoverageIgnore + */ + public static function countHeaderMediaElements($key) + { + return self::countElements($key); + } + + /** + * Get Header Media Elements + * + * @deprecated 0.10.0 + * + * @return array + * + * @codeCoverageIgnore + */ + public static function getHeaderMediaElements() + { + return self::getElements('header'); + } + + /** + * Add new Footer Media Element + * + * @deprecated 0.10.0 + * + * @param int $footerCount + * @param string $src + * @param \PhpOffice\PhpWord\Element\Image $image + * + * @return int + * + * @codeCoverageIgnore + */ + public static function addFooterMediaElement($footerCount, $src, Image $image = null) + { + return self::addElement("footer{$footerCount}", 'image', $src, $image); + } + + /** + * Get Footer Media Elements Count + * + * @deprecated 0.10.0 + * + * @param string $key + * + * @return int + * + * @codeCoverageIgnore + */ + public static function countFooterMediaElements($key) + { + return self::countElements($key); + } + + /** + * Get Footer Media Elements + * + * @deprecated 0.10.0 + * + * @return array + * + * @codeCoverageIgnore + */ + public static function getFooterMediaElements() + { + return self::getElements('footer'); + } +} diff --git a/PhpOffice/PhpWord/Metadata/Compatibility.php b/PhpOffice/PhpWord/Metadata/Compatibility.php new file mode 100644 index 0000000..bf0363a --- /dev/null +++ b/PhpOffice/PhpWord/Metadata/Compatibility.php @@ -0,0 +1,62 @@ +ooxmlVersion; + } + + /** + * Set OOXML version + * + * @param int $value + * @return self + */ + public function setOoxmlVersion($value) + { + $this->ooxmlVersion = $value; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Metadata/DocInfo.php b/PhpOffice/PhpWord/Metadata/DocInfo.php new file mode 100644 index 0000000..8a2f7d3 --- /dev/null +++ b/PhpOffice/PhpWord/Metadata/DocInfo.php @@ -0,0 +1,581 @@ +creator = ''; + $this->lastModifiedBy = $this->creator; + $this->created = time(); + $this->modified = time(); + $this->title = ''; + $this->subject = ''; + $this->description = ''; + $this->keywords = ''; + $this->category = ''; + $this->company = ''; + $this->manager = ''; + } + + /** + * Get Creator + * + * @return string + */ + public function getCreator() + { + return $this->creator; + } + + /** + * Set Creator + * + * @param string $value + * @return self + */ + public function setCreator($value = '') + { + $this->creator = $this->setValue($value, ''); + + return $this; + } + + /** + * Get Last Modified By + * + * @return string + */ + public function getLastModifiedBy() + { + return $this->lastModifiedBy; + } + + /** + * Set Last Modified By + * + * @param string $value + * @return self + */ + public function setLastModifiedBy($value = '') + { + $this->lastModifiedBy = $this->setValue($value, $this->creator); + + return $this; + } + + /** + * Get Created + * + * @return int + */ + public function getCreated() + { + return $this->created; + } + + /** + * Set Created + * + * @param int $value + * @return self + */ + public function setCreated($value = null) + { + $this->created = $this->setValue($value, time()); + + return $this; + } + + /** + * Get Modified + * + * @return int + */ + public function getModified() + { + return $this->modified; + } + + /** + * Set Modified + * + * @param int $value + * @return self + */ + public function setModified($value = null) + { + $this->modified = $this->setValue($value, time()); + + return $this; + } + + /** + * Get Title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set Title + * + * @param string $value + * @return self + */ + public function setTitle($value = '') + { + $this->title = $this->setValue($value, ''); + + return $this; + } + + /** + * Get Description + * + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Set Description + * + * @param string $value + * @return self + */ + public function setDescription($value = '') + { + $this->description = $this->setValue($value, ''); + + return $this; + } + + /** + * Get Subject + * + * @return string + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Set Subject + * + * @param string $value + * @return self + */ + public function setSubject($value = '') + { + $this->subject = $this->setValue($value, ''); + + return $this; + } + + /** + * Get Keywords + * + * @return string + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * Set Keywords + * + * @param string $value + * @return self + */ + public function setKeywords($value = '') + { + $this->keywords = $this->setValue($value, ''); + + return $this; + } + + /** + * Get Category + * + * @return string + */ + public function getCategory() + { + return $this->category; + } + + /** + * Set Category + * + * @param string $value + * @return self + */ + public function setCategory($value = '') + { + $this->category = $this->setValue($value, ''); + + return $this; + } + + /** + * Get Company + * + * @return string + */ + public function getCompany() + { + return $this->company; + } + + /** + * Set Company + * + * @param string $value + * @return self + */ + public function setCompany($value = '') + { + $this->company = $this->setValue($value, ''); + + return $this; + } + + /** + * Get Manager + * + * @return string + */ + public function getManager() + { + return $this->manager; + } + + /** + * Set Manager + * + * @param string $value + * @return self + */ + public function setManager($value = '') + { + $this->manager = $this->setValue($value, ''); + + return $this; + } + + /** + * Get a List of Custom Property Names + * + * @return array of string + */ + public function getCustomProperties() + { + return array_keys($this->customProperties); + } + + /** + * Check if a Custom Property is defined + * + * @param string $propertyName + * @return bool + */ + public function isCustomPropertySet($propertyName) + { + return isset($this->customProperties[$propertyName]); + } + + /** + * Get a Custom Property Value + * + * @param string $propertyName + * @return mixed + */ + public function getCustomPropertyValue($propertyName) + { + if ($this->isCustomPropertySet($propertyName)) { + return $this->customProperties[$propertyName]['value']; + } + + return null; + } + + /** + * Get a Custom Property Type + * + * @param string $propertyName + * @return string + */ + public function getCustomPropertyType($propertyName) + { + if ($this->isCustomPropertySet($propertyName)) { + return $this->customProperties[$propertyName]['type']; + } + + return null; + } + + /** + * Set a Custom Property + * + * @param string $propertyName + * @param mixed $propertyValue + * @param string $propertyType + * 'i': Integer + * 'f': Floating Point + * 's': String + * 'd': Date/Time + * 'b': Boolean + * @return self + */ + public function setCustomProperty($propertyName, $propertyValue = '', $propertyType = null) + { + $propertyTypes = array( + self::PROPERTY_TYPE_INTEGER, + self::PROPERTY_TYPE_FLOAT, + self::PROPERTY_TYPE_STRING, + self::PROPERTY_TYPE_DATE, + self::PROPERTY_TYPE_BOOLEAN, + ); + if (($propertyType === null) || (!in_array($propertyType, $propertyTypes))) { + if ($propertyValue === null) { + $propertyType = self::PROPERTY_TYPE_STRING; + } elseif (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; + } elseif ($propertyValue instanceof \DateTime) { + $propertyType = self::PROPERTY_TYPE_DATE; + } else { + $propertyType = self::PROPERTY_TYPE_STRING; + } + } + + $this->customProperties[$propertyName] = array( + 'value' => $propertyValue, + 'type' => $propertyType, + ); + + return $this; + } + + /** + * Convert document property based on type + * + * @param string $propertyValue + * @param string $propertyType + * @return mixed + */ + public static function convertProperty($propertyValue, $propertyType) + { + $conversion = self::getConversion($propertyType); + + switch ($conversion) { + case 'empty': // Empty + return ''; + case 'null': // Null + return null; + case 'int': // Signed integer + return (int) $propertyValue; + case 'uint': // Unsigned integer + return abs((int) $propertyValue); + case 'float': // Float + return (float) $propertyValue; + case 'date': // Date + return strtotime($propertyValue); + case 'bool': // Boolean + return $propertyValue == 'true'; + } + + return $propertyValue; + } + + /** + * Convert document property type + * + * @param string $propertyType + * @return string + */ + public static function convertPropertyType($propertyType) + { + $typeGroups = array( + self::PROPERTY_TYPE_INTEGER => array('i1', 'i2', 'i4', 'i8', 'int', 'ui1', 'ui2', 'ui4', 'ui8', 'uint'), + self::PROPERTY_TYPE_FLOAT => array('r4', 'r8', 'decimal'), + self::PROPERTY_TYPE_STRING => array('empty', 'null', 'lpstr', 'lpwstr', 'bstr'), + self::PROPERTY_TYPE_DATE => array('date', 'filetime'), + self::PROPERTY_TYPE_BOOLEAN => array('bool'), + ); + foreach ($typeGroups as $groupId => $groupMembers) { + if (in_array($propertyType, $groupMembers)) { + return $groupId; + } + } + + return self::PROPERTY_TYPE_UNKNOWN; + } + + /** + * Set default for null and empty value + * + * @param mixed $value + * @param mixed $default + * @return mixed + */ + private function setValue($value, $default) + { + if ($value === null || $value == '') { + $value = $default; + } + + return $value; + } + + /** + * Get conversion model depending on property type + * + * @param string $propertyType + * @return string + */ + private static function getConversion($propertyType) + { + $conversions = array( + 'empty' => array('empty'), + 'null' => array('null'), + 'int' => array('i1', 'i2', 'i4', 'i8', 'int'), + 'uint' => array('ui1', 'ui2', 'ui4', 'ui8', 'uint'), + 'float' => array('r4', 'r8', 'decimal'), + 'bool' => array('bool'), + 'date' => array('date', 'filetime'), + ); + foreach ($conversions as $conversion => $types) { + if (in_array($propertyType, $types)) { + return $conversion; + } + } + + return 'string'; + } +} diff --git a/PhpOffice/PhpWord/Metadata/Protection.php b/PhpOffice/PhpWord/Metadata/Protection.php new file mode 100644 index 0000000..15aa3ff --- /dev/null +++ b/PhpOffice/PhpWord/Metadata/Protection.php @@ -0,0 +1,199 @@ +setEditing($editing); + } + } + + /** + * Get editing protection + * + * @return string + */ + public function getEditing() + { + return $this->editing; + } + + /** + * Set editing protection + * + * @param string $editing Any value of \PhpOffice\PhpWord\SimpleType\DocProtect + * @return self + */ + public function setEditing($editing = null) + { + DocProtect::validate($editing); + $this->editing = $editing; + + return $this; + } + + /** + * Get password + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set password + * + * @param string $password + * @return self + */ + public function setPassword($password) + { + $this->password = $password; + + return $this; + } + + /** + * Get count for hash iterations + * + * @return int + */ + public function getSpinCount() + { + return $this->spinCount; + } + + /** + * Set count for hash iterations + * + * @param int $spinCount + * @return self + */ + public function setSpinCount($spinCount) + { + $this->spinCount = $spinCount; + + return $this; + } + + /** + * Get algorithm + * + * @return string + */ + public function getAlgorithm() + { + return $this->algorithm; + } + + /** + * Set algorithm + * + * @param string $algorithm + * @return self + */ + public function setAlgorithm($algorithm) + { + $this->algorithm = $algorithm; + + return $this; + } + + /** + * Get salt + * + * @return string + */ + public function getSalt() + { + return $this->salt; + } + + /** + * Set salt. Salt HAS to be 16 characters, or an exception will be thrown. + * + * @param string $salt + * @throws \InvalidArgumentException + * @return self + */ + public function setSalt($salt) + { + if ($salt !== null && strlen($salt) !== 16) { + throw new \InvalidArgumentException('salt has to be of exactly 16 bytes length'); + } + + $this->salt = $salt; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Metadata/Settings.php b/PhpOffice/PhpWord/Metadata/Settings.php new file mode 100644 index 0000000..b1552e0 --- /dev/null +++ b/PhpOffice/PhpWord/Metadata/Settings.php @@ -0,0 +1,480 @@ +documentProtection == null) { + $this->documentProtection = new Protection(); + } + + return $this->documentProtection; + } + + /** + * @param Protection $documentProtection + */ + public function setDocumentProtection($documentProtection) + { + $this->documentProtection = $documentProtection; + } + + /** + * @return ProofState + */ + public function getProofState() + { + if ($this->proofState == null) { + $this->proofState = new ProofState(); + } + + return $this->proofState; + } + + /** + * @param ProofState $proofState + */ + public function setProofState($proofState) + { + $this->proofState = $proofState; + } + + /** + * Are spelling errors hidden + * + * @return bool + */ + public function hasHideSpellingErrors() + { + return $this->hideSpellingErrors; + } + + /** + * Hide spelling errors + * + * @param bool $hideSpellingErrors + */ + public function setHideSpellingErrors($hideSpellingErrors) + { + $this->hideSpellingErrors = $hideSpellingErrors === null ? true : $hideSpellingErrors; + } + + /** + * Are grammatical errors hidden + * + * @return bool + */ + public function hasHideGrammaticalErrors() + { + return $this->hideGrammaticalErrors; + } + + /** + * Hide grammatical errors + * + * @param bool $hideGrammaticalErrors + */ + public function setHideGrammaticalErrors($hideGrammaticalErrors) + { + $this->hideGrammaticalErrors = $hideGrammaticalErrors === null ? true : $hideGrammaticalErrors; + } + + /** + * @return bool + */ + public function hasEvenAndOddHeaders() + { + return $this->evenAndOddHeaders; + } + + /** + * @param bool $evenAndOddHeaders + */ + public function setEvenAndOddHeaders($evenAndOddHeaders) + { + $this->evenAndOddHeaders = $evenAndOddHeaders === null ? true : $evenAndOddHeaders; + } + + /** + * Get the Visibility of Annotation Types + * + * @return \PhpOffice\PhpWord\ComplexType\TrackChangesView + */ + public function getRevisionView() + { + return $this->revisionView; + } + + /** + * Set the Visibility of Annotation Types + * + * @param TrackChangesView $trackChangesView + */ + public function setRevisionView(TrackChangesView $trackChangesView = null) + { + $this->revisionView = $trackChangesView; + } + + /** + * @return bool + */ + public function hasTrackRevisions() + { + return $this->trackRevisions; + } + + /** + * @param bool $trackRevisions + */ + public function setTrackRevisions($trackRevisions) + { + $this->trackRevisions = $trackRevisions === null ? true : $trackRevisions; + } + + /** + * @return bool + */ + public function hasDoNotTrackMoves() + { + return $this->doNotTrackMoves; + } + + /** + * @param bool $doNotTrackMoves + */ + public function setDoNotTrackMoves($doNotTrackMoves) + { + $this->doNotTrackMoves = $doNotTrackMoves === null ? true : $doNotTrackMoves; + } + + /** + * @return bool + */ + public function hasDoNotTrackFormatting() + { + return $this->doNotTrackFormatting; + } + + /** + * @param bool $doNotTrackFormatting + */ + public function setDoNotTrackFormatting($doNotTrackFormatting) + { + $this->doNotTrackFormatting = $doNotTrackFormatting === null ? true : $doNotTrackFormatting; + } + + /** + * @return mixed + */ + public function getZoom() + { + return $this->zoom; + } + + /** + * @param mixed $zoom + */ + public function setZoom($zoom) + { + if (is_numeric($zoom)) { + // zoom is a percentage + $this->zoom = $zoom; + } else { + Zoom::validate($zoom); + $this->zoom = $zoom; + } + } + + /** + * @return bool + */ + public function hasMirrorMargins() + { + return $this->mirrorMargins; + } + + /** + * @param bool $mirrorMargins + */ + public function setMirrorMargins($mirrorMargins) + { + $this->mirrorMargins = $mirrorMargins; + } + + /** + * Returns the Language + * + * @return Language + */ + public function getThemeFontLang() + { + return $this->themeFontLang; + } + + /** + * sets the Language for this document + * + * @param Language $themeFontLang + */ + public function setThemeFontLang($themeFontLang) + { + $this->themeFontLang = $themeFontLang; + } + + /** + * @return bool + */ + public function hasUpdateFields() + { + return $this->updateFields; + } + + /** + * @param bool $updateFields + */ + public function setUpdateFields($updateFields) + { + $this->updateFields = $updateFields === null ? false : $updateFields; + } + + /** + * Returns the Radix Point for Field Code Evaluation + * + * @return string + */ + public function getDecimalSymbol() + { + return $this->decimalSymbol; + } + + /** + * sets the Radix Point for Field Code Evaluation + * + * @param string $decimalSymbol + */ + public function setDecimalSymbol($decimalSymbol) + { + $this->decimalSymbol = $decimalSymbol; + } + + /** + * @return bool|null + */ + public function hasAutoHyphenation() + { + return $this->autoHyphenation; + } + + /** + * @param bool $autoHyphenation + */ + public function setAutoHyphenation($autoHyphenation) + { + $this->autoHyphenation = (bool) $autoHyphenation; + } + + /** + * @return int|null + */ + public function getConsecutiveHyphenLimit() + { + return $this->consecutiveHyphenLimit; + } + + /** + * @param int $consecutiveHyphenLimit + */ + public function setConsecutiveHyphenLimit($consecutiveHyphenLimit) + { + $this->consecutiveHyphenLimit = (int) $consecutiveHyphenLimit; + } + + /** + * @return float|null + */ + public function getHyphenationZone() + { + return $this->hyphenationZone; + } + + /** + * @param float $hyphenationZone Measurement unit is twip + */ + public function setHyphenationZone($hyphenationZone) + { + $this->hyphenationZone = $hyphenationZone; + } + + /** + * @return null|bool + */ + public function hasDoNotHyphenateCaps() + { + return $this->doNotHyphenateCaps; + } + + /** + * @param bool $doNotHyphenateCaps + */ + public function setDoNotHyphenateCaps($doNotHyphenateCaps) + { + $this->doNotHyphenateCaps = (bool) $doNotHyphenateCaps; + } +} diff --git a/PhpOffice/PhpWord/PhpWord.php b/PhpOffice/PhpWord/PhpWord.php new file mode 100644 index 0000000..a78df2c --- /dev/null +++ b/PhpOffice/PhpWord/PhpWord.php @@ -0,0 +1,425 @@ +collections[$collection] = new $class(); + } + + // Metadata + $metadata = array('DocInfo', 'Settings', 'Compatibility'); + foreach ($metadata as $meta) { + $class = 'PhpOffice\\PhpWord\\Metadata\\' . $meta; + $this->metadata[$meta] = new $class(); + } + } + + /** + * Dynamic function call to reduce static dependency + * + * @since 0.12.0 + * + * @param mixed $function + * @param mixed $args + * + * @throws \BadMethodCallException + * + * @return mixed + */ + public function __call($function, $args) + { + $function = strtolower($function); + + $getCollection = array(); + $addCollection = array(); + $addStyle = array(); + + $collections = array('Bookmark', 'Title', 'Footnote', 'Endnote', 'Chart', 'Comment'); + foreach ($collections as $collection) { + $getCollection[] = strtolower("get{$collection}s"); + $addCollection[] = strtolower("add{$collection}"); + } + + $styles = array('Paragraph', 'Font', 'Table', 'Numbering', 'Link', 'Title'); + foreach ($styles as $style) { + $addStyle[] = strtolower("add{$style}Style"); + } + + // Run get collection method + if (in_array($function, $getCollection)) { + $key = ucfirst(str_replace('get', '', $function)); + + return $this->collections[$key]; + } + + // Run add collection item method + if (in_array($function, $addCollection)) { + $key = ucfirst(str_replace('add', '', $function) . 's'); + + /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collectionObject */ + $collectionObject = $this->collections[$key]; + + return $collectionObject->addItem(isset($args[0]) ? $args[0] : null); + } + + // Run add style method + if (in_array($function, $addStyle)) { + return forward_static_call_array(array('PhpOffice\\PhpWord\\Style', $function), $args); + } + + // Exception + throw new \BadMethodCallException("Method $function is not defined."); + } + + /** + * Get document properties object + * + * @return \PhpOffice\PhpWord\Metadata\DocInfo + */ + public function getDocInfo() + { + return $this->metadata['DocInfo']; + } + + /** + * Get protection + * + * @return \PhpOffice\PhpWord\Metadata\Protection + * @since 0.12.0 + * @deprecated Get the Document protection from PhpWord->getSettings()->getDocumentProtection(); + * @codeCoverageIgnore + */ + public function getProtection() + { + return $this->getSettings()->getDocumentProtection(); + } + + /** + * Get compatibility + * + * @return \PhpOffice\PhpWord\Metadata\Compatibility + * @since 0.12.0 + */ + public function getCompatibility() + { + return $this->metadata['Compatibility']; + } + + /** + * Get compatibility + * + * @return \PhpOffice\PhpWord\Metadata\Settings + * @since 0.14.0 + */ + public function getSettings() + { + return $this->metadata['Settings']; + } + + /** + * Get all sections + * + * @return \PhpOffice\PhpWord\Element\Section[] + */ + public function getSections() + { + return $this->sections; + } + + /** + * Returns the section at the requested position + * + * @param int $index + * @return \PhpOffice\PhpWord\Element\Section|null + */ + public function getSection($index) + { + if (array_key_exists($index, $this->sections)) { + return $this->sections[$index]; + } + + return null; + } + + /** + * Create new section + * + * @param array $style + * @return \PhpOffice\PhpWord\Element\Section + */ + public function addSection($style = null) + { + $section = new Section(count($this->sections) + 1, $style); + $section->setPhpWord($this); + $this->sections[] = $section; + + return $section; + } + + /** + * Sorts the sections using the callable passed + * + * @see http://php.net/manual/en/function.usort.php for usage + * @param callable $sorter + */ + public function sortSections($sorter) + { + usort($this->sections, $sorter); + } + + /** + * Get default font name + * + * @return string + */ + public function getDefaultFontName() + { + return Settings::getDefaultFontName(); + } + + /** + * Set default font name. + * + * @param string $fontName + */ + public function setDefaultFontName($fontName) + { + Settings::setDefaultFontName($fontName); + } + + /** + * Get default font size + * + * @return int + */ + public function getDefaultFontSize() + { + return Settings::getDefaultFontSize(); + } + + /** + * Set default font size. + * + * @param int $fontSize + */ + public function setDefaultFontSize($fontSize) + { + Settings::setDefaultFontSize($fontSize); + } + + /** + * Set default paragraph style definition to styles.xml + * + * @param array $styles Paragraph style definition + * @return \PhpOffice\PhpWord\Style\Paragraph + */ + public function setDefaultParagraphStyle($styles) + { + return Style::setDefaultParagraphStyle($styles); + } + + /** + * Load template by filename + * + * @deprecated 0.12.0 Use `new TemplateProcessor($documentTemplate)` instead. + * + * @param string $filename Fully qualified filename + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * + * @return TemplateProcessor + * + * @codeCoverageIgnore + */ + public function loadTemplate($filename) + { + if (file_exists($filename)) { + return new TemplateProcessor($filename); + } + throw new Exception("Template file {$filename} not found."); + } + + /** + * Save to file or download + * + * All exceptions should already been handled by the writers + * + * @param string $filename + * @param string $format + * @param bool $download + * @return bool + */ + public function save($filename, $format = 'Word2007', $download = false) + { + $mime = array( + 'Word2007' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'ODText' => 'application/vnd.oasis.opendocument.text', + 'RTF' => 'application/rtf', + 'HTML' => 'text/html', + 'PDF' => 'application/pdf', + ); + + $writer = IOFactory::createWriter($this, $format); + + if ($download === true) { + header('Content-Description: File Transfer'); + header('Content-Disposition: attachment; filename="' . $filename . '"'); + header('Content-Type: ' . $mime[$format]); + header('Content-Transfer-Encoding: binary'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Expires: 0'); + $filename = 'php://output'; // Change filename to force download + } + + $writer->save($filename); + + return true; + } + + /** + * Create new section + * + * @deprecated 0.10.0 + * + * @param array $settings + * + * @return \PhpOffice\PhpWord\Element\Section + * + * @codeCoverageIgnore + */ + public function createSection($settings = null) + { + return $this->addSection($settings); + } + + /** + * Get document properties object + * + * @deprecated 0.12.0 + * + * @return \PhpOffice\PhpWord\Metadata\DocInfo + * + * @codeCoverageIgnore + */ + public function getDocumentProperties() + { + return $this->getDocInfo(); + } + + /** + * Set document properties object + * + * @deprecated 0.12.0 + * + * @param \PhpOffice\PhpWord\Metadata\DocInfo $documentProperties + * + * @return self + * + * @codeCoverageIgnore + */ + public function setDocumentProperties($documentProperties) + { + $this->metadata['Document'] = $documentProperties; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Reader/AbstractReader.php b/PhpOffice/PhpWord/Reader/AbstractReader.php new file mode 100644 index 0000000..7db285f --- /dev/null +++ b/PhpOffice/PhpWord/Reader/AbstractReader.php @@ -0,0 +1,124 @@ +readDataOnly; + return true; + } + + /** + * Set read data only + * + * @param bool $value + * @return self + */ + public function setReadDataOnly($value = true) + { + $this->readDataOnly = $value; + + return $this; + } + + /** + * Open file for reading + * + * @param string $filename + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * + * @return resource + */ + protected function openFile($filename) + { + // Check if file exists + if (!file_exists($filename) || !is_readable($filename)) { + throw new Exception("Could not open $filename for reading! File does not exist."); + } + + // Open file + $this->fileHandle = fopen($filename, 'r'); + if ($this->fileHandle === false) { + throw new Exception("Could not open file $filename for reading."); + } + } + + /** + * Can the current ReaderInterface read the file? + * + * @param string $filename + * @return bool + */ + public function canRead($filename) + { + // Check if file exists + try { + $this->openFile($filename); + } catch (Exception $e) { + return false; + } + if (is_resource($this->fileHandle)) { + fclose($this->fileHandle); + } + + return true; + } + + /** + * Read data only? + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getReadDataOnly() + { + return $this->isReadDataOnly(); + } +} diff --git a/PhpOffice/PhpWord/Reader/HTML.php b/PhpOffice/PhpWord/Reader/HTML.php new file mode 100644 index 0000000..db9f208 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/HTML.php @@ -0,0 +1,52 @@ +canRead($docFile)) { + $section = $phpWord->addSection(); + HTMLParser::addHtml($section, file_get_contents($docFile), true); + } else { + throw new \Exception("Cannot read {$docFile}."); + } + + return $phpWord; + } +} diff --git a/PhpOffice/PhpWord/Reader/MsDoc.php b/PhpOffice/PhpWord/Reader/MsDoc.php new file mode 100644 index 0000000..1a9e498 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/MsDoc.php @@ -0,0 +1,2356 @@ +phpWord = new PhpWord(); + + $this->loadOLE($filename); + + $this->readFib($this->dataWorkDocument); + $this->readFibContent(); + + return $this->phpWord; + } + + /** + * Load an OLE Document + * @param string $filename + */ + private function loadOLE($filename) + { + // OLE reader + $ole = new OLERead(); + $ole->read($filename); + + // Get WorkDocument stream + $this->dataWorkDocument = $ole->getStream($ole->wrkdocument); + // Get 1Table stream + $this->data1Table = $ole->getStream($ole->wrk1Table); + // Get Data stream + $this->dataData = $ole->getStream($ole->wrkData); + // Get Data stream + $this->dataObjectPool = $ole->getStream($ole->wrkObjectPool); + // Get Summary Information data + $this->_SummaryInformation = $ole->getStream($ole->summaryInformation); + // Get Document Summary Information data + $this->_DocumentSummaryInformation = $ole->getStream($ole->docSummaryInfos); + } + + private function getNumInLcb($lcb, $iSize) + { + return ($lcb - 4) / (4 + $iSize); + } + + private function getArrayCP($data, $posMem, $iNum) + { + $arrayCP = array(); + for ($inc = 0; $inc < $iNum; $inc++) { + $arrayCP[$inc] = self::getInt4d($data, $posMem); + $posMem += 4; + } + + return $arrayCP; + } + + /** + * @see http://msdn.microsoft.com/en-us/library/dd949344%28v=office.12%29.aspx + * @see https://igor.io/2012/09/24/binary-parsing.html + * @param string $data + */ + private function readFib($data) + { + $pos = 0; + //----- FibBase + // wIdent + $pos += 2; + // nFib + $pos += 2; + // unused + $pos += 2; + // lid : Language Identifier + $pos += 2; + // pnNext + $pos += 2; + + // $mem = self::getInt2d($data, $pos); + // $fDot = ($mem >> 15) & 1; + // $fGlsy = ($mem >> 14) & 1; + // $fComplex = ($mem >> 13) & 1; + // $fHasPic = ($mem >> 12) & 1; + // $cQuickSaves = ($mem >> 8) & bindec('1111'); + // $fEncrypted = ($mem >> 7) & 1; + // $fWhichTblStm = ($mem >> 6) & 1; + // $fReadOnlyRecommended = ($mem >> 5) & 1; + // $fWriteReservation = ($mem >> 4) & 1; + // $fExtChar = ($mem >> 3) & 1; + // $fLoadOverride = ($mem >> 2) & 1; + // $fFarEast = ($mem >> 1) & 1; + // $fObfuscated = ($mem >> 0) & 1; + $pos += 2; + // nFibBack + $pos += 2; + // lKey + $pos += 4; + // envr + $pos += 1; + + // $mem = self::getInt1d($data, $pos); + // $fMac = ($mem >> 7) & 1; + // $fEmptySpecial = ($mem >> 6) & 1; + // $fLoadOverridePage = ($mem >> 5) & 1; + // $reserved1 = ($mem >> 4) & 1; + // $reserved2 = ($mem >> 3) & 1; + // $fSpare0 = ($mem >> 0) & bindec('111'); + $pos += 1; + + // reserved3 + $pos += 2; + // reserved4 + $pos += 2; + // reserved5 + $pos += 4; + // reserved6 + $pos += 4; + + //----- csw + $pos += 2; + + //----- fibRgW + // reserved1 + $pos += 2; + // reserved2 + $pos += 2; + // reserved3 + $pos += 2; + // reserved4 + $pos += 2; + // reserved5 + $pos += 2; + // reserved6 + $pos += 2; + // reserved7 + $pos += 2; + // reserved8 + $pos += 2; + // reserved9 + $pos += 2; + // reserved10 + $pos += 2; + // reserved11 + $pos += 2; + // reserved12 + $pos += 2; + // reserved13 + $pos += 2; + // lidFE + $pos += 2; + + //----- cslw + $pos += 2; + + //----- fibRgLw + // cbMac + $pos += 4; + // reserved1 + $pos += 4; + // reserved2 + $pos += 4; + $this->arrayFib['ccpText'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['ccpFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['ccpHdd'] = self::getInt4d($data, $pos); + $pos += 4; + // reserved3 + $pos += 4; + // ccpAtn + $pos += 4; + // ccpEdn + $pos += 4; + // ccpTxbx + $pos += 4; + // ccpHdrTxbx + $pos += 4; + // reserved4 + $pos += 4; + // reserved5 + $pos += 4; + // reserved6 + $pos += 4; + // reserved7 + $pos += 4; + // reserved8 + $pos += 4; + // reserved9 + $pos += 4; + // reserved10 + $pos += 4; + // reserved11 + $pos += 4; + // reserved12 + $pos += 4; + // reserved13 + $pos += 4; + // reserved14 + $pos += 4; + + //----- cbRgFcLcb + $cbRgFcLcb = self::getInt2d($data, $pos); + $pos += 2; + //----- fibRgFcLcbBlob + switch ($cbRgFcLcb) { + case 0x005D: + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); + break; + case 0x006C: + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); + break; + case 0x0088: + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); + break; + case 0x00A4: + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2003); + break; + case 0x00B7: + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2003); + $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2007); + break; + } + //----- cswNew + $this->arrayFib['cswNew'] = self::getInt2d($data, $pos); + $pos += 2; + + if ($this->arrayFib['cswNew'] != 0) { + //@todo : fibRgCswNew + } + + return $pos; + } + + private function readBlockFibRgFcLcb($data, $pos, $version) + { + if ($version == self::VERSION_97) { + $this->arrayFib['fcStshfOrig'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbStshfOrig'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcStshf'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbStshf'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcffndRef'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcffndRef'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcffndTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcffndTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfandRef'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfandRef'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfandTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfandTxt '] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfSed'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfSed'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcPad'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcPad'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfPhe'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfPhe'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfGlsy'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfGlsy'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfGlsy'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfGlsy'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfHdd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfHdd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBteChpx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBteChpx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBtePapx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBtePapx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfSea'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfSea'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfFfn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfFfn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfFldMom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfFldMom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfFldHdr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfFldHdr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfFldFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfFldFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfFldAtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfFldAtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfFldMcr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfFldMcr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfBkmk'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfBkmk'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkf'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkf'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcCmds'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbCmds'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused1'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused1'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfMcr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfMcr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPrDrvr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPrDrvr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPrEnvPort'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPrEnvPort'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPrEnvLand'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPrEnvLand'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcWss'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbWss'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcDop'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbDop'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfAssoc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfAssoc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcClx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbClx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfPgdFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfPgdFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcAutosaveSource'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbAutosaveSource'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcGrpXstAtnOwners'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbGrpXstAtnOwners'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfAtnBkmk'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfAtnBkmk'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused2'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused2'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused3'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused3'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcSpaMom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcSpaMom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcSpaHdr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcSpaHdr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfAtnBkf'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfAtnBkf'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfAtnBkl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfAtnBkl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPms'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPms'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcFormFldSttbs'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbFormFldSttbs'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfendRef'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfendRef'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfendTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfendTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfFldEdn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfFldEdn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused4'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused4'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcDggInfo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbDggInfo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfRMark'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfRMark'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfCaption'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfCaption'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfAutoCaption'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfAutoCaption'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfWkb'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfWkb'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfSpl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfSpl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcftxbxTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcftxbxTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfFldTxbx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfFldTxbx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfHdrtxbxTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfHdrtxbxTxt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcffldHdrTxbx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcffldHdrTxbx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcStwUser'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbStwUser'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbTtmbd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbTtmbd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcCookieData'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbCookieData'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdMotherOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdMotherOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdMotherOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdMotherOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdFtnOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdFtnOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdFtnOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdFtnOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdEdnOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdEdnOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdEdnOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdEdnOldOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfIntlFld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfIntlFld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcRouteSlip'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbRouteSlip'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbSavedBy'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbSavedBy'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbFnm'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbFnm'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlfLst'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlfLst'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlfLfo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlfLfo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfTxbxBkd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfTxbxBkd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfTxbxHdrBkd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfTxbxHdrBkd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcDocUndoWord9'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbDocUndoWord9'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcRgbUse'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbRgbUse'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUsp'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUsp'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUskf'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUskf'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcupcRgbUse'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcupcRgbUse'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcupcUsp'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcupcUsp'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbGlsyStyle'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbGlsyStyle'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlgosl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlgosl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcocx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcocx'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBteLvc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBteLvc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['dwLowDateTime'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['dwHighDateTime'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfLvcPre10'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfLvcPre10'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfAsumy'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfAsumy'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfGram'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfGram'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbListNames'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbListNames'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfUssr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfUssr'] = self::getInt4d($data, $pos); + $pos += 4; + } + if ($version == self::VERSION_2000) { + $this->arrayFib['fcPlcfTch'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfTch'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcRmdThreading'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbRmdThreading'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcMid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbMid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbRgtplc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbRgtplc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcMsoEnvelope'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbMsoEnvelope'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfLad'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfLad'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcRgDofr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbRgDofr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcosl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcosl'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfCookieOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfCookieOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdMotherOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdMotherOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdMotherOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdMotherOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdFtnOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdFtnOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdFtnOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdFtnOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdEdnOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdEdnOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdEdnOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdEdnOld'] = self::getInt4d($data, $pos); + $pos += 4; + } + if ($version == self::VERSION_2002) { + $this->arrayFib['fcUnused1'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused1'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfPgp'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfPgp'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfuim'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfuim'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlfguidUim'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlfguidUim'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcAtrdExtra'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbAtrdExtra'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlrsid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlrsid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfBkmkFactoid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfBkmkFactoid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkfFactoid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkfFactoid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfcookie'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfcookie'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBklFactoid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBklFactoid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcFactoidData'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbFactoidData'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcDocUndo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbDocUndo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfBkmkFcc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfBkmkFcc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkfFcc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkfFcc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBklFcc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBklFcc'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfbkmkBPRepairs'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfbkmkBPRepairs'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfbkfBPRepairs'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfbkfBPRepairs'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfbklBPRepairs'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfbklBPRepairs'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPmsNew'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPmsNew'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcODSO'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbODSO'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfpmiOldXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfpmiOldXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfpmiNewXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfpmiNewXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfpmiMixedXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfpmiMixedXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused2'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused2'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcffactoid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcffactoid'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcflvcOldXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcflvcOldXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcflvcNewXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcflvcNewXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcflvcMixedXP'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcflvcMixedXP'] = self::getInt4d($data, $pos); + $pos += 4; + } + if ($version == self::VERSION_2003) { + $this->arrayFib['fcHplxsdr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbHplxsdr'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfBkmkSdt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfBkmkSdt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkfSdt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkfSdt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBklSdt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBklSdt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcCustomXForm'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbCustomXForm'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfBkmkProt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfBkmkProt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkfProt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkfProt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBklProt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBklProt'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbProtUser'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbProtUser'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfpmiOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfpmiOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfpmiOldInline'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfpmiOldInline'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfpmiNew'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfpmiNew'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfpmiNewInline'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfpmiNewInline'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcflvcOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcflvcOld'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcflvcOldInline'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcflvcOldInline'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcflvcNew'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcflvcNew'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcflvcNewInline'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcflvcNewInline'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdMother'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdMother'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdMother'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdMother'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcAfdMother'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbAfdMother'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcAfdFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbAfdFtn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPgdEdn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPgdEdn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcBkdEdn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbBkdEdn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcAfdEdn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbAfdEdn'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcAfd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbAfd'] = self::getInt4d($data, $pos); + $pos += 4; + } + if ($version == self::VERSION_2007) { + $this->arrayFib['fcPlcfmthd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfmthd'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfBkmkMoveFrom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfBkmkMoveFrom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkfMoveFrom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkfMoveFrom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBklMoveFrom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBklMoveFrom'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfBkmkMoveTo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfBkmkMoveTo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkfMoveTo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkfMoveTo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBklMoveTo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBklMoveTo'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused1'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused1'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused2'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused2'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused3'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused3'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcSttbfBkmkArto'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbSttbfBkmkArto'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBkfArto'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBkfArto'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcPlcfBklArto'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbPlcfBklArto'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcArtoData'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbArtoData'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused4'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused4'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused5'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused5'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcUnused6'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbUnused6'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcOssTheme'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbOssTheme'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['fcColorSchemeMapping'] = self::getInt4d($data, $pos); + $pos += 4; + $this->arrayFib['lcbColorSchemeMapping'] = self::getInt4d($data, $pos); + $pos += 4; + } + + return $pos; + } + + private function readFibContent() + { + // Informations about Font + $this->readRecordSttbfFfn(); + + // Informations about page + $this->readRecordPlcfSed(); + + // reading paragraphs + //@see https://github.com/notmasteryet/CompoundFile/blob/ec118f354efebdee9102e41b5b7084fce81125b0/WordFileReader/WordDocument.cs#L86 + $this->readRecordPlcfBtePapx(); + + // reading character formattings + //@see https://github.com/notmasteryet/CompoundFile/blob/ec118f354efebdee9102e41b5b7084fce81125b0/WordFileReader/WordDocument.cs#L94 + $this->readRecordPlcfBteChpx(); + + $this->generatePhpWord(); + } + + /** + * Section and information about them + * @see : http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx + */ + private function readRecordPlcfSed() + { + $posMem = $this->arrayFib['fcPlcfSed']; + // PlcfSed + // PlcfSed : aCP + $aCP = array(); + $aCP[0] = self::getInt4d($this->data1Table, $posMem); + $posMem += 4; + $aCP[1] = self::getInt4d($this->data1Table, $posMem); + $posMem += 4; + + // PlcfSed : aSed + //@see : http://msdn.microsoft.com/en-us/library/dd950194%28v=office.12%29.aspx + $numSed = $this->getNumInLcb($this->arrayFib['lcbPlcfSed'], 12); + + $aSed = array(); + for ($iInc = 0; $iInc < $numSed; ++$iInc) { + // Sed : http://msdn.microsoft.com/en-us/library/dd950982%28v=office.12%29.aspx + // fn + $posMem += 2; + // fnMpr + $aSed[$iInc] = self::getInt4d($this->data1Table, $posMem); + $posMem += 4; + // fnMpr + $posMem += 2; + // fcMpr + $posMem += 4; + } + + foreach ($aSed as $offsetSed) { + // Sepx : http://msdn.microsoft.com/en-us/library/dd921348%28v=office.12%29.aspx + $cb = self::getInt2d($this->dataWorkDocument, $offsetSed); + $offsetSed += 2; + + $oStylePrl = $this->readPrl($this->dataWorkDocument, $offsetSed, $cb); + $offsetSed += $oStylePrl->length; + + $this->arraySections[] = $oStylePrl; + } + } + + /** + * Specifies the fonts that are used in the document + * @see : http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx + */ + private function readRecordSttbfFfn() + { + $posMem = $this->arrayFib['fcSttbfFfn']; + + $cData = self::getInt2d($this->data1Table, $posMem); + $posMem += 2; + $cbExtra = self::getInt2d($this->data1Table, $posMem); + $posMem += 2; + + if ($cData < 0x7FF0 && $cbExtra == 0) { + for ($inc = 0; $inc < $cData; $inc++) { + // len + $posMem += 1; + // ffid + $posMem += 1; + // wWeight (400 : Normal - 700 bold) + $posMem += 2; + // chs + $posMem += 1; + // ixchSzAlt + $ixchSzAlt = self::getInt1d($this->data1Table, $posMem); + $posMem += 1; + // panose + $posMem += 10; + // fs + $posMem += 24; + // xszFfn + $xszFfn = ''; + do { + $char = self::getInt2d($this->data1Table, $posMem); + $posMem += 2; + if ($char > 0) { + $xszFfn .= chr($char); + } + } while ($char != 0); + // xszAlt + $xszAlt = ''; + if ($ixchSzAlt > 0) { + do { + $char = self::getInt2d($this->data1Table, $posMem); + $posMem += 2; + if ($char == 0) { + break; + } + $xszAlt .= chr($char); + } while ($char != 0); + } + $this->arrayFonts[] = array( + 'main' => $xszFfn, + 'alt' => $xszAlt, + ); + } + } + } + + /** + * Paragraph and information about them + * @see http://msdn.microsoft.com/en-us/library/dd908569%28v=office.12%29.aspx + */ + private function readRecordPlcfBtePapx() + { + $posMem = $this->arrayFib['fcPlcfBtePapx']; + $num = $this->getNumInLcb($this->arrayFib['lcbPlcfBtePapx'], 4); + $posMem += 4 * ($num + 1); + $arrAPnBtePapx = $this->getArrayCP($this->data1Table, $posMem, $num); + $posMem += 4 * $num; + + foreach ($arrAPnBtePapx as $aPnBtePapx) { + $offsetBase = $aPnBtePapx * 512; + $offset = $offsetBase; + + $string = ''; + + $numRun = self::getInt1d($this->dataWorkDocument, $offset + 511); + $arrayRGFC = array(); + for ($inc = 0; $inc <= $numRun; $inc++) { + $arrayRGFC[$inc] = self::getInt4d($this->dataWorkDocument, $offset); + $offset += 4; + } + $arrayRGB = array(); + for ($inc = 1; $inc <= $numRun; $inc++) { + // @see http://msdn.microsoft.com/en-us/library/dd925804(v=office.12).aspx + $arrayRGB[$inc] = self::getInt1d($this->dataWorkDocument, $offset); + $offset += 1; + // reserved + $offset += 12; + } + + foreach (array_keys($arrayRGFC) as $key) { + if (!isset($arrayRGFC[$key + 1])) { + break; + } + $strLen = $arrayRGFC[$key + 1] - $arrayRGFC[$key] - 1; + for ($inc = 0; $inc < $strLen; $inc++) { + $byte = self::getInt1d($this->dataWorkDocument, $arrayRGFC[$key] + $inc); + if ($byte > 0) { + $string .= chr($byte); + } + } + } + $this->arrayParagraphs[] = $string; + + //@todo readPrl for paragraphs + /*// use $this->readPrl() + foreach ($arrayRGB as $key => $rgb) { + $offset = $offsetBase + ($rgb * 2); + + $cb = self::getInt1d($this->dataWorkDocument, $offset); + $offset += 1; + print_r('$cb : '.$cb.PHP_EOL); + if ($cb == 0) { + $cb = self::getInt1d($this->dataWorkDocument, $offset); + $cb = $cb * 2; + $offset += 1; + print_r('$cb0 : '.$cb.PHP_EOL); + } else { + $cb = $cb * 2 - 1; + print_r('$cbD : '.$cb.PHP_EOL); + } + $istd = self::getInt2d($this->dataWorkDocument, $offset); + $offset += 2; + $cb -= 2; + print_r('$istd : '.$istd.($istd == 0 ? ' (Normal)' : '').PHP_EOL); + if ($cb > 0) { + do{ + $sprm = self::getInt2d($this->dataWorkDocument, $offset); + $offset += 2; + $cb -= 2; + $sprm_IsPmd = $sprm & 0x01FF; + $sprm_F = ($sprm/512) & 0x0001; + $sprm_Sgc = ($sprm/1024) & 0x0007; + $sprm_Spra = ($sprm/8192); + + print_r('$sprm : 0x'.dechex($sprm).PHP_EOL); + print_r('$sprm.ispmd : 0x'.dechex($sprm_IsPmd).PHP_EOL); + print_r('$sprm.f : 0x'.dechex($sprm_F).PHP_EOL); + print_r('$sprm.sgc : 0x'.dechex($sprm_Sgc)); + switch (dechex($sprm_Sgc)) { + case 0x01: + print_r(' (Paragraph property)'); + break; + case 0x02: + print_r(' (Character property)'); + break; + case 0x03: + print_r(' (Picture property)'); + break; + case 0x04: + print_r(' (Section property)'); + break; + case 0x05: + print_r(' (Table property)'); + break; + } + print_r(PHP_EOL); + print_r('$sprm.spra : 0x'.dechex($sprm_Spra).PHP_EOL); + switch (dechex($sprm_Spra)) { + case 0x0: + $operand = self::getInt1d($this->dataWorkDocument, $offset); + $offset += 1; + $cb -= 1; + switch (dechex($operand)) { + case 0x00: + $operand = 'OFF'; + break; + case 0x01: + $operand = 'ON'; + break; + case 0x80: + $operand = 'CURRENT VALUE'; + print_r(''.PHP_EOL.PHP_EOL); + break; + case 0x81: + $operand = 'OPPOSITE OF THE CURRENT VALUE'; + break; + } + break; + case 0x1: + $operand = self::getInt1d($this->dataWorkDocument, $offset); + $offset += 1; + $cb -= 1; + print_r('$operand : 0x'.dechex($operand).PHP_EOL); + break; + case 0x2: + case 0x4: + case 0x5: + $operand = self::getInt2d($this->dataWorkDocument, $offset); + $offset += 2; + $cb -= 2; + print_r('$operand : 0x'.dechex($operand).PHP_EOL); + break; + case 0x3: + if ($sprm_IsPmd != 0x70) { + $operand = self::getInt4d($this->dataWorkDocument, $offset); + $offset += 4; + $cb -= 4; + print_r('$operand : 0x'.dechex($operand).PHP_EOL); + } + break; + case 0x7: + $operand = self::getInt3d($this->dataWorkDocument, $offset); + $offset += 3; + $cb -= 3; + print_r('$operand : 0x'.dechex($operand).PHP_EOL); + break; + default: + print_r('YO YO YO : '.PHP_EOL); + } + + // + switch (dechex($sprm_Sgc)) { + case 0x01: // Sprm is modifying a paragraph property. + switch ($sprm_IsPmd) { + case 0x0A: // sprmPIlvl + print_r('sprmPIlvl : '.$operand.PHP_EOL.PHP_EOL); + break; + case 0x0B: // sprmPIlfo + print_r('sprmPIlfo : '.$operand.PHP_EOL.PHP_EOL); + break; + default: + print_r('$sprm_IsPmd(1) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); + break; + } + break; + case 0x02: // Sprm is modifying a character property. + switch ($sprm_IsPmd) { + default: + print_r('$sprm_IsPmd(2) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); + break; + } + break; + case 0x03: // Sprm is modifying a picture property. + switch ($sprm_IsPmd) { + default: + print_r('$sprm_IsPmd(3) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); + break; + } + break; + case 0x04: // Sprm is modifying a section property. + switch ($sprm_IsPmd) { + default: + print_r('$sprm_IsPmd(4) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); + break; + } + break; + case 0x05: // Sprm is modifying a table property. + switch ($sprm_IsPmd) { + default: + print_r('$sprm_IsPmd(4) : '.$sprm_IsPmd.PHP_EOL.PHP_EOL); + break; + } + break; + default: + print_r('$sprm_Sgc : '.dechex($sprm_Sgc).PHP_EOL.PHP_EOL); + break; + } + } while ($cb > 0); + } else { + if ($istd > 0) { + // @todo : Determining Properties of a Paragraph Style + # @see http://msdn.microsoft.com/en-us/library/dd948631%28v=office.12%29.aspx + } + } + }*/ + } + } + + /** + * Character formatting properties to text in a document + * @see http://msdn.microsoft.com/en-us/library/dd907108%28v=office.12%29.aspx + */ + private function readRecordPlcfBteChpx() + { + $posMem = $this->arrayFib['fcPlcfBteChpx']; + $num = $this->getNumInLcb($this->arrayFib['lcbPlcfBteChpx'], 4); + $aPnBteChpx = array(); + for ($inc = 0; $inc <= $num; $inc++) { + $aPnBteChpx[$inc] = self::getInt4d($this->data1Table, $posMem); + $posMem += 4; + } + $pnFkpChpx = self::getInt4d($this->data1Table, $posMem); + $posMem += 4; + + $offsetBase = $pnFkpChpx * 512; + $offset = $offsetBase; + + // ChpxFkp + // @see : http://msdn.microsoft.com/en-us/library/dd910989%28v=office.12%29.aspx + $numRGFC = self::getInt1d($this->dataWorkDocument, $offset + 511); + $arrayRGFC = array(); + for ($inc = 0; $inc <= $numRGFC; $inc++) { + $arrayRGFC[$inc] = self::getInt4d($this->dataWorkDocument, $offset); + $offset += 4; + } + + $arrayRGB = array(); + for ($inc = 1; $inc <= $numRGFC; $inc++) { + $arrayRGB[$inc] = self::getInt1d($this->dataWorkDocument, $offset); + $offset += 1; + } + + $start = 0; + foreach ($arrayRGB as $keyRGB => $rgb) { + $oStyle = new \stdClass(); + $oStyle->pos_start = $start; + $oStyle->pos_len = (int) ceil((($arrayRGFC[$keyRGB] - 1) - $arrayRGFC[$keyRGB - 1]) / 2); + $start += $oStyle->pos_len; + + if ($rgb > 0) { + // Chp Structure + // @see : http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx + $posRGB = $offsetBase + $rgb * 2; + + $cb = self::getInt1d($this->dataWorkDocument, $posRGB); + $posRGB += 1; + + $oStyle->style = $this->readPrl($this->dataWorkDocument, $posRGB, $cb); + $posRGB += $oStyle->style->length; + } + $this->arrayCharacters[] = $oStyle; + } + } + + /** + * @param $sprm + * @return \stdClass + */ + private function readSprm($sprm) + { + $oSprm = new \stdClass(); + $oSprm->isPmd = $sprm & 0x01FF; + $oSprm->f = ($sprm / 512) & 0x0001; + $oSprm->sgc = ($sprm / 1024) & 0x0007; + $oSprm->spra = ($sprm / 8192); + + return $oSprm; + } + + /** + * @param string $data + * @param int $pos + * @param \stdClass $oSprm + * @return array + */ + private function readSprmSpra($data, $pos, $oSprm) + { + $length = 0; + $operand = null; + + switch (dechex($oSprm->spra)) { + case 0x0: + $operand = self::getInt1d($data, $pos); + $length = 1; + switch (dechex($operand)) { + case 0x00: + $operand = false; + break; + case 0x01: + $operand = true; + break; + case 0x80: + $operand = self::SPRA_VALUE; + break; + case 0x81: + $operand = self::SPRA_VALUE_OPPOSITE; + break; + } + break; + case 0x1: + $operand = self::getInt1d($data, $pos); + $length = 1; + break; + case 0x2: + case 0x4: + case 0x5: + $operand = self::getInt2d($data, $pos); + $length = 2; + break; + case 0x3: + if ($oSprm->isPmd != 0x70) { + $operand = self::getInt4d($data, $pos); + $length = 4; + } + break; + case 0x7: + $operand = self::getInt3d($data, $pos); + $length = 3; + break; + default: + // print_r('YO YO YO : '.PHP_EOL); + } + + return array( + 'length' => $length, + 'operand' => $operand, + ); + } + + /** + * @param $data int + * @param $pos int + * @param $cbNum int + * @return \stdClass + * @see http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx + */ + private function readPrl($data, $pos, $cbNum) + { + $posStart = $pos; + $oStylePrl = new \stdClass(); + + // Variables + $sprmCPicLocation = null; + $sprmCFData = null; + $sprmCFSpec = null; + + do { + // Variables + $operand = null; + + $sprm = self::getInt2d($data, $pos); + $oSprm = $this->readSprm($sprm); + $pos += 2; + $cbNum -= 2; + + $arrayReturn = $this->readSprmSpra($data, $pos, $oSprm); + $pos += $arrayReturn['length']; + $cbNum -= $arrayReturn['length']; + $operand = $arrayReturn['operand']; + + switch (dechex($oSprm->sgc)) { + // Paragraph property + case 0x01: + break; + // Character property + case 0x02: + if (!isset($oStylePrl->styleFont)) { + $oStylePrl->styleFont = array(); + } + switch ($oSprm->isPmd) { + // sprmCFRMarkIns + case 0x01: + break; + // sprmCFFldVanish + case 0x02: + break; + // sprmCPicLocation + case 0x03: + $sprmCPicLocation = $operand; + break; + // sprmCFData + case 0x06: + $sprmCFData = dechex($operand) != 0x00; + break; + // sprmCFItalic + case 0x36: + // By default, text is not italicized. + switch ($operand) { + case false: + case true: + $oStylePrl->styleFont['italic'] = $operand; + break; + case self::SPRA_VALUE: + $oStylePrl->styleFont['italic'] = false; + break; + case self::SPRA_VALUE_OPPOSITE: + $oStylePrl->styleFont['italic'] = true; + break; + } + break; + // sprmCIstd + case 0x30: + //print_r('sprmCIstd : '.dechex($operand).PHP_EOL.PHP_EOL); + break; + // sprmCFBold + case 0x35: + // By default, text is not bold. + switch ($operand) { + case false: + case true: + $oStylePrl->styleFont['bold'] = $operand; + break; + case self::SPRA_VALUE: + $oStylePrl->styleFont['bold'] = false; + break; + case self::SPRA_VALUE_OPPOSITE: + $oStylePrl->styleFont['bold'] = true; + break; + } + break; + // sprmCFStrike + case 0x37: + // By default, text is not struck through. + switch ($operand) { + case false: + case true: + $oStylePrl->styleFont['strikethrough'] = $operand; + break; + case self::SPRA_VALUE: + $oStylePrl->styleFont['strikethrough'] = false; + break; + case self::SPRA_VALUE_OPPOSITE: + $oStylePrl->styleFont['strikethrough'] = true; + break; + } + break; + // sprmCKul + case 0x3E: + switch (dechex($operand)) { + case 0x00: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_NONE; + break; + case 0x01: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_SINGLE; + break; + case 0x02: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WORDS; + break; + case 0x03: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOUBLE; + break; + case 0x04: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTTED; + break; + case 0x06: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_HEAVY; + break; + case 0x07: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASH; + break; + case 0x09: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDASH; + break; + case 0x0A: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDOTDASH; + break; + case 0x0B: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVY; + break; + case 0x14: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTTEDHEAVY; + break; + case 0x17: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHHEAVY; + break; + case 0x19: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDASHHEAVY; + break; + case 0x1A: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDOTDASHHEAVY; + break; + case 0x1B: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVYHEAVY; + break; + case 0x27: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHLONG; + break; + case 0x2B: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVYDOUBLE; + break; + case 0x37: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHLONGHEAVY; + break; + default: + $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_NONE; + break; + } + break; + // sprmCIco + //@see http://msdn.microsoft.com/en-us/library/dd773060%28v=office.12%29.aspx + case 0x42: + switch (dechex($operand)) { + case 0x00: + case 0x01: + $oStylePrl->styleFont['color'] = '000000'; + break; + case 0x02: + $oStylePrl->styleFont['color'] = '0000FF'; + break; + case 0x03: + $oStylePrl->styleFont['color'] = '00FFFF'; + break; + case 0x04: + $oStylePrl->styleFont['color'] = '00FF00'; + break; + case 0x05: + $oStylePrl->styleFont['color'] = 'FF00FF'; + break; + case 0x06: + $oStylePrl->styleFont['color'] = 'FF0000'; + break; + case 0x07: + $oStylePrl->styleFont['color'] = 'FFFF00'; + break; + case 0x08: + $oStylePrl->styleFont['color'] = 'FFFFFF'; + break; + case 0x09: + $oStylePrl->styleFont['color'] = '000080'; + break; + case 0x0A: + $oStylePrl->styleFont['color'] = '008080'; + break; + case 0x0B: + $oStylePrl->styleFont['color'] = '008000'; + break; + case 0x0C: + $oStylePrl->styleFont['color'] = '800080'; + break; + case 0x0D: + $oStylePrl->styleFont['color'] = '800080'; + break; + case 0x0E: + $oStylePrl->styleFont['color'] = '808000'; + break; + case 0x0F: + $oStylePrl->styleFont['color'] = '808080'; + break; + case 0x10: + $oStylePrl->styleFont['color'] = 'C0C0C0'; + } + break; + // sprmCHps + case 0x43: + $oStylePrl->styleFont['size'] = dechex($operand / 2); + break; + // sprmCIss + case 0x48: + if (!isset($oStylePrl->styleFont['superScript'])) { + $oStylePrl->styleFont['superScript'] = false; + } + if (!isset($oStylePrl->styleFont['subScript'])) { + $oStylePrl->styleFont['subScript'] = false; + } + switch (dechex($operand)) { + case 0x00: + // Normal text + break; + case 0x01: + $oStylePrl->styleFont['superScript'] = true; + break; + case 0x02: + $oStylePrl->styleFont['subScript'] = true; + break; + } + break; + // sprmCRgFtc0 + case 0x4F: + $oStylePrl->styleFont['name'] = ''; + if (isset($this->arrayFonts[$operand])) { + $oStylePrl->styleFont['name'] = $this->arrayFonts[$operand]['main']; + } + break; + // sprmCRgFtc1 + case 0x50: + // if the language for the text is an East Asian language + break; + // sprmCRgFtc2 + case 0x51: + // if the character falls outside the Unicode character range + break; + // sprmCFSpec + case 0x55: + $sprmCFSpec = $operand; + break; + // sprmCFtcBi + case 0x5E: + break; + // sprmCFItalicBi + case 0x5D: + break; + // sprmCHpsBi + case 0x61: + break; + // sprmCShd80 + //@see http://msdn.microsoft.com/en-us/library/dd923447%28v=office.12%29.aspx + case 0x66: + // $operand = self::getInt2d($data, $pos); + $pos += 2; + $cbNum -= 2; + // $ipat = ($operand >> 0) && bindec('111111'); + // $icoBack = ($operand >> 6) && bindec('11111'); + // $icoFore = ($operand >> 11) && bindec('11111'); + break; + // sprmCCv + //@see : http://msdn.microsoft.com/en-us/library/dd952824%28v=office.12%29.aspx + case 0x70: + $red = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); + $pos += 1; + $green = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); + $pos += 1; + $blue = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); + $pos += 1; + $pos += 1; + $oStylePrl->styleFont['color'] = $red . $green . $blue; + $cbNum -= 4; + break; + default: + // print_r('@todo Character : 0x'.dechex($oSprm->isPmd)); + // print_r(PHP_EOL); + } + break; + // Picture property + case 0x03: + break; + // Section property + case 0x04: + if (!isset($oStylePrl->styleSection)) { + $oStylePrl->styleSection = array(); + } + switch ($oSprm->isPmd) { + // sprmSNfcPgn + case 0x0E: + // numbering format used for page numbers + break; + // sprmSXaPage + case 0x1F: + $oStylePrl->styleSection['pageSizeW'] = $operand; + break; + // sprmSYaPage + case 0x20: + $oStylePrl->styleSection['pageSizeH'] = $operand; + break; + // sprmSDxaLeft + case 0x21: + $oStylePrl->styleSection['marginLeft'] = $operand; + break; + // sprmSDxaRight + case 0x22: + $oStylePrl->styleSection['marginRight'] = $operand; + break; + // sprmSDyaTop + case 0x23: + $oStylePrl->styleSection['marginTop'] = $operand; + break; + // sprmSDyaBottom + case 0x24: + $oStylePrl->styleSection['marginBottom'] = $operand; + break; + // sprmSFBiDi + case 0x28: + // RTL layout + break; + // sprmSDxtCharSpace + case 0x30: + // characpter pitch + break; + // sprmSDyaLinePitch + case 0x31: + // line height + break; + // sprmSClm + case 0x32: + // document grid mode + break; + // sprmSTextFlow + case 0x33: + // text flow + break; + default: + // print_r('@todo Section : 0x'.dechex($oSprm->isPmd)); + // print_r(PHP_EOL); + } + break; + // Table property + case 0x05: + break; + } + } while ($cbNum > 0); + + if (!is_null($sprmCPicLocation)) { + if (!is_null($sprmCFData) && $sprmCFData == 0x01) { + // NilPICFAndBinData + //@todo Read Hyperlink structure + /*$lcb = self::getInt4d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 4; + $cbHeader = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // ignored + $sprmCPicLocation += 62; + // depending of the element + // Hyperlink => HFD + // HFD > bits + $sprmCPicLocation += 1; + // HFD > clsid + $sprmCPicLocation += 16; + // HFD > hyperlink + //@see : http://msdn.microsoft.com/en-us/library/dd909835%28v=office.12%29.aspx + $streamVersion = self::getInt4d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 4; + $data = self::getInt4d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 4; + $hlstmfAbsFromGetdataRel = ($data >> 9) & bindec('1'); + $hlstmfMonikerSavedAsStr = ($data >> 8) & bindec('1'); + $hlstmfHasFrameName = ($data >> 7) & bindec('1'); + $hlstmfHasCreationTime = ($data >> 6) & bindec('1'); + $hlstmfHasGUID = ($data >> 5) & bindec('1'); + $hlstmfHasDisplayName = ($data >> 4) & bindec('1'); + $hlstmfHasLocationStr = ($data >> 3) & bindec('1'); + $hlstmfSiteGaveDisplayName = ($data >> 2) & bindec('1'); + $hlstmfIsAbsolute = ($data >> 1) & bindec('1'); + $hlstmfHasMoniker = ($data >> 0) & bindec('1'); + for ($inc = 0; $inc <= 32; $inc++) { + echo ($data >> $inc) & bindec('1'); + } + + print_r('$hlstmfHasMoniker > '.$hlstmfHasMoniker.PHP_EOL); + print_r('$hlstmfIsAbsolute > '.$hlstmfIsAbsolute.PHP_EOL); + print_r('$hlstmfSiteGaveDisplayName > '.$hlstmfSiteGaveDisplayName.PHP_EOL); + print_r('$hlstmfHasLocationStr > '.$hlstmfHasLocationStr.PHP_EOL); + print_r('$hlstmfHasDisplayName > '.$hlstmfHasDisplayName.PHP_EOL); + print_r('$hlstmfHasGUID > '.$hlstmfHasGUID.PHP_EOL); + print_r('$hlstmfHasCreationTime > '.$hlstmfHasCreationTime.PHP_EOL); + print_r('$hlstmfHasFrameName > '.$hlstmfHasFrameName.PHP_EOL); + print_r('$hlstmfMonikerSavedAsStr > '.$hlstmfMonikerSavedAsStr.PHP_EOL); + print_r('$hlstmfAbsFromGetdataRel > '.$hlstmfAbsFromGetdataRel.PHP_EOL); + if ($streamVersion == 2) { + $AAA = self::getInt4d($this->dataData, $sprmCPicLocation); + echo 'AAAA : '.$AAA.PHP_EOL; + if ($hlstmfHasDisplayName == 1) { + echo 'displayName'.PHP_EOL; + } + if ($hlstmfHasFrameName == 1) { + echo 'targetFrameName'.PHP_EOL; + } + if ($hlstmfHasMoniker == 1 || $hlstmfMonikerSavedAsStr == 1) { + $sprmCPicLocation += 16; + $length = self::getInt4d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 4; + for ($inc = 0; $inc < ($length / 2); $inc++) { + $chr = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + print_r(chr($chr)); + } + echo PHP_EOL; + echo 'moniker : '.$length.PHP_EOL; + } + if ($hlstmfHasMoniker == 1 || $hlstmfMonikerSavedAsStr == 1) { + echo 'oleMoniker'.PHP_EOL; + } + if ($hlstmfHasLocationStr == 1) { + echo 'location'.PHP_EOL; + } + if ($hlstmfHasGUID == 1) { + echo 'guid'.PHP_EOL; + $sprmCPicLocation += 16; + } + if ($hlstmfHasCreationTime == 1) { + echo 'fileTime'.PHP_EOL; + $sprmCPicLocation += 4; + } + echo 'HYPERLINK'.PHP_EOL; + }*/ + } else { + // Pictures + //@see : http://msdn.microsoft.com/en-us/library/dd925458%28v=office.12%29.aspx + //@see : http://msdn.microsoft.com/en-us/library/dd926136%28v=office.12%29.aspx + // PICF : lcb + $sprmCPicLocation += 4; + // PICF : cbHeader + $sprmCPicLocation += 2; + // PICF : mfpf : mm + $mfpfMm = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : mfpf : xExt + $sprmCPicLocation += 2; + // PICF : mfpf : yExt + $sprmCPicLocation += 2; + // PICF : mfpf : swHMF + $sprmCPicLocation += 2; + // PICF : innerHeader : grf + $sprmCPicLocation += 4; + // PICF : innerHeader : padding1 + $sprmCPicLocation += 4; + // PICF : innerHeader : mmPM + $sprmCPicLocation += 2; + // PICF : innerHeader : padding2 + $sprmCPicLocation += 4; + // PICF : picmid : dxaGoal + $picmidDxaGoal = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : picmid : dyaGoal + $picmidDyaGoal = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : picmid : mx + $picmidMx = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : picmid : my + $picmidMy = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : picmid : dxaReserved1 + $picmidDxaCropLeft = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : picmid : dyaReserved1 + $picmidDxaCropTop = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : picmid : dxaReserved2 + $picmidDxaCropRight = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : picmid : dyaReserved2 + $picmidDxaCropBottom = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + // PICF : picmid : fReserved + $sprmCPicLocation += 1; + // PICF : picmid : bpp + $sprmCPicLocation += 1; + // PICF : picmid : brcTop80 + $sprmCPicLocation += 4; + // PICF : picmid : brcLeft80 + $sprmCPicLocation += 4; + // PICF : picmid : brcBottom80 + $sprmCPicLocation += 4; + // PICF : picmid : brcRight80 + $sprmCPicLocation += 4; + // PICF : picmid : dxaReserved3 + $sprmCPicLocation += 2; + // PICF : picmid : dyaReserved3 + $sprmCPicLocation += 2; + // PICF : cProps + $sprmCPicLocation += 2; + + if ($mfpfMm == 0x0066) { + // cchPicName + $cchPicName = self::getInt1d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 1; + + // stPicName + $stPicName = ''; + for ($inc = 0; $inc <= $cchPicName; $inc++) { + $chr = self::getInt1d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 1; + $stPicName .= chr($chr); + } + } + + // picture (OfficeArtInlineSpContainer) + // picture : shape + $shapeRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 8; + if ($shapeRH['recVer'] == 0xF && $shapeRH['recInstance'] == 0x000 && $shapeRH['recType'] == 0xF004) { + $sprmCPicLocation += $shapeRH['recLen']; + } + // picture : rgfb + //@see : http://msdn.microsoft.com/en-us/library/dd950560%28v=office.12%29.aspx + $fileBlockRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation); + while ($fileBlockRH['recType'] == 0xF007 || ($fileBlockRH['recType'] >= 0xF018 && $fileBlockRH['recType'] <= 0xF117)) { + $sprmCPicLocation += 8; + switch ($fileBlockRH['recType']) { + // OfficeArtFBSE + //@see : http://msdn.microsoft.com/en-us/library/dd944923%28v=office.12%29.aspx + case 0xF007: + // btWin32 + $sprmCPicLocation += 1; + // btMacOS + $sprmCPicLocation += 1; + // rgbUid + $sprmCPicLocation += 16; + // tag + $sprmCPicLocation += 2; + // size + $sprmCPicLocation += 4; + // cRef + $sprmCPicLocation += 4; + // foDelay + $sprmCPicLocation += 4; + // unused1 + $sprmCPicLocation += 1; + // cbName + $cbName = self::getInt1d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 1; + // unused2 + $sprmCPicLocation += 1; + // unused3 + $sprmCPicLocation += 1; + // nameData + if ($cbName > 0) { + $nameData = ''; + for ($inc = 0; $inc <= ($cbName / 2); $inc++) { + $chr = self::getInt2d($this->dataData, $sprmCPicLocation); + $sprmCPicLocation += 2; + $nameData .= chr($chr); + } + } + // embeddedBlip + //@see : http://msdn.microsoft.com/en-us/library/dd910081%28v=office.12%29.aspx + $embeddedBlipRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation); + switch ($embeddedBlipRH['recType']) { + case self::OFFICEARTBLIPJPG: + case self::OFFICEARTBLIPJPEG: + if (!isset($oStylePrl->image)) { + $oStylePrl->image = array(); + } + $sprmCPicLocation += 8; + // embeddedBlip : rgbUid1 + $sprmCPicLocation += 16; + if ($embeddedBlipRH['recInstance'] == 0x6E1) { + // rgbUid2 + $sprmCPicLocation += 16; + } + // embeddedBlip : tag + $sprmCPicLocation += 1; + // embeddedBlip : BLIPFileData + $oStylePrl->image['data'] = substr($this->dataData, $sprmCPicLocation, $embeddedBlipRH['recLen']); + $oStylePrl->image['format'] = 'jpg'; + // Image Size + $iCropWidth = $picmidDxaGoal - ($picmidDxaCropLeft + $picmidDxaCropRight); + $iCropHeight = $picmidDyaGoal - ($picmidDxaCropTop + $picmidDxaCropBottom); + if (!$iCropWidth) { + $iCropWidth = 1; + } + if (!$iCropHeight) { + $iCropHeight = 1; + } + $oStylePrl->image['width'] = Drawing::twipsToPixels($iCropWidth * $picmidMx / 1000); + $oStylePrl->image['height'] = Drawing::twipsToPixels($iCropHeight * $picmidMy / 1000); + + $sprmCPicLocation += $embeddedBlipRH['recLen']; + break; + case self::OFFICEARTBLIPPNG: + break; + default: + // print_r(dechex($embeddedBlipRH['recType'])); + } + break; + } + $fileBlockRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation); + } + } + } + + $oStylePrl->length = $pos - $posStart; + + return $oStylePrl; + } + + /** + * Read a record header + * @param string $stream + * @param int $pos + * @return array + */ + private function loadRecordHeader($stream, $pos) + { + $rec = self::getInt2d($stream, $pos); + $recType = self::getInt2d($stream, $pos + 2); + $recLen = self::getInt4d($stream, $pos + 4); + + return array( + 'recVer' => ($rec >> 0) & bindec('1111'), + 'recInstance' => ($rec >> 4) & bindec('111111111111'), + 'recType' => $recType, + 'recLen' => $recLen, + ); + } + + private function generatePhpWord() + { + foreach ($this->arraySections as $itmSection) { + $oSection = $this->phpWord->addSection(); + $oSection->setStyle($itmSection->styleSection); + + $sHYPERLINK = ''; + foreach ($this->arrayParagraphs as $itmParagraph) { + $textPara = $itmParagraph; + foreach ($this->arrayCharacters as $oCharacters) { + $subText = substr($textPara, $oCharacters->pos_start, $oCharacters->pos_len); + $subText = str_replace(chr(13), PHP_EOL, $subText); + $arrayText = explode(PHP_EOL, $subText); + if (end($arrayText) == '') { + array_pop($arrayText); + } + if (reset($arrayText) == '') { + array_shift($arrayText); + } + + // Style Character + $styleFont = array(); + if (isset($oCharacters->style)) { + if (isset($oCharacters->style->styleFont)) { + $styleFont = $oCharacters->style->styleFont; + } + } + + foreach ($arrayText as $sText) { + // HyperLink + if (empty($sText) && !empty($sHYPERLINK)) { + $arrHYPERLINK = explode('"', $sHYPERLINK); + $oSection->addLink($arrHYPERLINK[1], null); + // print_r('>addHyperLink<'.$sHYPERLINK.'>'.ord($sHYPERLINK[0]).EOL); + $sHYPERLINK = ''; + } + + // TextBreak + if (empty($sText)) { + $oSection->addTextBreak(); + $sHYPERLINK = ''; + // print_r('>addTextBreak<' . EOL); + } + + if (!empty($sText)) { + if (!empty($sHYPERLINK) && ord($sText[0]) > 20) { + $sHYPERLINK .= $sText; + } + if (empty($sHYPERLINK)) { + if (ord($sText[0]) > 20) { + if (strpos(trim($sText), 'HYPERLINK "') === 0) { + $sHYPERLINK = $sText; + } else { + $oSection->addText($sText, $styleFont); + // print_r('>addText<'.$sText.'>'.ord($sText[0]).EOL); + } + } + if (ord($sText[0]) == 1) { + if (isset($oCharacters->style->image)) { + $fileImage = tempnam(sys_get_temp_dir(), 'PHPWord_MsDoc') . '.' . $oCharacters->style->image['format']; + file_put_contents($fileImage, $oCharacters->style->image['data']); + $oSection->addImage($fileImage, array('width' => $oCharacters->style->image['width'], 'height' => $oCharacters->style->image['height'])); + // print_r('>addImage<'.$fileImage.'>'.EOL); + } + } + } + } + } + } + } + } + } + + /** + * Read 8-bit unsigned integer + * + * @param string $data + * @param int $pos + * @return int + */ + public static function getInt1d($data, $pos) + { + return ord($data[$pos]); + } + + /** + * Read 16-bit unsigned integer + * + * @param string $data + * @param int $pos + * @return int + */ + public static function getInt2d($data, $pos) + { + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8); + } + + /** + * Read 24-bit signed integer + * + * @param string $data + * @param int $pos + * @return int + */ + public static function getInt3d($data, $pos) + { + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16); + } + + /** + * Read 32-bit signed integer + * + * @param string $data + * @param int $pos + * @return int + */ + public static function getInt4d($data, $pos) + { + // FIX: represent numbers correctly on 64-bit system + // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 + // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems + $or24 = ord($data[$pos + 3]); + if ($or24 >= 128) { + // negative number + $ord24 = -abs((256 - $or24) << 24); + } else { + $ord24 = ($or24 & 127) << 24; + } + + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24; + } +} diff --git a/PhpOffice/PhpWord/Reader/ODText.php b/PhpOffice/PhpWord/Reader/ODText.php new file mode 100644 index 0000000..0b58dc5 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/ODText.php @@ -0,0 +1,94 @@ +readRelationships($docFile); + + $readerParts = array( + 'content.xml' => 'Content', + 'meta.xml' => 'Meta', + ); + + foreach ($readerParts as $xmlFile => $partName) { + $this->readPart($phpWord, $relationships, $partName, $docFile, $xmlFile); + } + + return $phpWord; + } + + /** + * Read document part. + * + * @param \PhpOffice\PhpWord\PhpWord $phpWord + * @param array $relationships + * @param string $partName + * @param string $docFile + * @param string $xmlFile + */ + private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile) + { + $partClass = "PhpOffice\\PhpWord\\Reader\\ODText\\{$partName}"; + if (class_exists($partClass)) { + /** @var \PhpOffice\PhpWord\Reader\ODText\AbstractPart $part Type hint */ + $part = new $partClass($docFile, $xmlFile); + $part->setRels($relationships); + $part->read($phpWord); + } + } + + /** + * Read all relationship files + * + * @param string $docFile + * @return array + */ + private function readRelationships($docFile) + { + $rels = array(); + $xmlFile = 'META-INF/manifest.xml'; + $xmlReader = new XMLReader(); + $xmlReader->getDomFromZip($docFile, $xmlFile); + $nodes = $xmlReader->getElements('manifest:file-entry'); + foreach ($nodes as $node) { + $type = $xmlReader->getAttribute('manifest:media-type', $node); + $target = $xmlReader->getAttribute('manifest:full-path', $node); + $rels[] = array('type' => $type, 'target' => $target); + } + + return $rels; + } +} diff --git a/PhpOffice/PhpWord/Reader/ODText/AbstractPart.php b/PhpOffice/PhpWord/Reader/ODText/AbstractPart.php new file mode 100644 index 0000000..ff664e0 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/ODText/AbstractPart.php @@ -0,0 +1,30 @@ +getDomFromZip($this->docFile, $this->xmlFile); + + $trackedChanges = array(); + + $nodes = $xmlReader->getElements('office:body/office:text/*'); + if ($nodes->length > 0) { + $section = $phpWord->addSection(); + foreach ($nodes as $node) { + // $styleName = $xmlReader->getAttribute('text:style-name', $node); + switch ($node->nodeName) { + case 'text:h': // Heading + $depth = $xmlReader->getAttribute('text:outline-level', $node); + $section->addTitle($node->nodeValue, $depth); + break; + case 'text:p': // Paragraph + $children = $node->childNodes; + foreach ($children as $child) { + switch ($child->nodeName) { + case 'text:change-start': + $changeId = $child->getAttribute('text:change-id'); + if (isset($trackedChanges[$changeId])) { + $changed = $trackedChanges[$changeId]; + } + break; + case 'text:change-end': + unset($changed); + break; + case 'text:change': + $changeId = $child->getAttribute('text:change-id'); + if (isset($trackedChanges[$changeId])) { + $changed = $trackedChanges[$changeId]; + } + break; + } + } + + $element = $section->addText($node->nodeValue); + if (isset($changed) && is_array($changed)) { + $element->setTrackChange($changed['changed']); + if (isset($changed['textNodes'])) { + foreach ($changed['textNodes'] as $changedNode) { + $element = $section->addText($changedNode->nodeValue); + $element->setTrackChange($changed['changed']); + } + } + } + break; + case 'text:list': // List + $listItems = $xmlReader->getElements('text:list-item/text:p', $node); + foreach ($listItems as $listItem) { + // $listStyleName = $xmlReader->getAttribute('text:style-name', $listItem); + $section->addListItem($listItem->nodeValue, 0); + } + break; + case 'text:tracked-changes': + $changedRegions = $xmlReader->getElements('text:changed-region', $node); + foreach ($changedRegions as $changedRegion) { + $type = ($changedRegion->firstChild->nodeName == 'text:insertion') ? TrackChange::INSERTED : TrackChange::DELETED; + $creatorNode = $xmlReader->getElements('office:change-info/dc:creator', $changedRegion->firstChild); + $author = $creatorNode[0]->nodeValue; + $dateNode = $xmlReader->getElements('office:change-info/dc:date', $changedRegion->firstChild); + $date = $dateNode[0]->nodeValue; + $date = preg_replace('/\.\d+$/', '', $date); + $date = \DateTime::createFromFormat('Y-m-d\TH:i:s', $date); + $changed = new TrackChange($type, $author, $date); + $textNodes = $xmlReader->getElements('text:deletion/text:p', $changedRegion); + $trackedChanges[$changedRegion->getAttribute('text:id')] = array('changed' => $changed, 'textNodes'=> $textNodes); + } + break; + } + } + } + } +} diff --git a/PhpOffice/PhpWord/Reader/ODText/Meta.php b/PhpOffice/PhpWord/Reader/ODText/Meta.php new file mode 100644 index 0000000..8801a54 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/ODText/Meta.php @@ -0,0 +1,78 @@ +getDomFromZip($this->docFile, $this->xmlFile); + $docProps = $phpWord->getDocInfo(); + + $metaNode = $xmlReader->getElement('office:meta'); + + // Standard properties + $properties = array( + 'title' => 'dc:title', + 'subject' => 'dc:subject', + 'description' => 'dc:description', + 'keywords' => 'meta:keyword', + 'creator' => 'meta:initial-creator', + 'lastModifiedBy' => 'dc:creator', + // 'created' => 'meta:creation-date', + // 'modified' => 'dc:date', + ); + foreach ($properties as $property => $path) { + $method = "set{$property}"; + $propertyNode = $xmlReader->getElement($path, $metaNode); + if ($propertyNode !== null && method_exists($docProps, $method)) { + $docProps->$method($propertyNode->nodeValue); + } + } + + // Custom properties + $propertyNodes = $xmlReader->getElements('meta:user-defined', $metaNode); + foreach ($propertyNodes as $propertyNode) { + $property = $xmlReader->getAttribute('meta:name', $propertyNode); + + // Set category, company, and manager property + if (in_array($property, array('Category', 'Company', 'Manager'))) { + $method = "set{$property}"; + $docProps->$method($propertyNode->nodeValue); + } else { + // Set other custom properties + $docProps->setCustomProperty($property, $propertyNode->nodeValue); + } + } + } +} diff --git a/PhpOffice/PhpWord/Reader/RTF.php b/PhpOffice/PhpWord/Reader/RTF.php new file mode 100644 index 0000000..620252f --- /dev/null +++ b/PhpOffice/PhpWord/Reader/RTF.php @@ -0,0 +1,53 @@ +canRead($docFile)) { + $doc = new Document(); + $doc->rtf = file_get_contents($docFile); + $doc->read($phpWord); + } else { + throw new \Exception("Cannot read {$docFile}."); + } + + return $phpWord; + } +} diff --git a/PhpOffice/PhpWord/Reader/RTF/Document.php b/PhpOffice/PhpWord/Reader/RTF/Document.php new file mode 100644 index 0000000..b9509d7 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/RTF/Document.php @@ -0,0 +1,394 @@ + 'markOpening', // { + 125 => 'markClosing', // } + 92 => 'markBackslash', // \ + 10 => 'markNewline', // LF + 13 => 'markNewline', // CR + ); + + $this->phpWord = $phpWord; + $this->section = $phpWord->addSection(); + $this->textrun = $this->section->addTextRun(); + $this->length = strlen($this->rtf); + + $this->flags['paragraph'] = true; // Set paragraph flag from the beginning + + // Walk each characters + while ($this->offset < $this->length) { + $char = $this->rtf[$this->offset]; + $ascii = ord($char); + + if (isset($markers[$ascii])) { // Marker found: {, }, \, LF, or CR + $markerFunction = $markers[$ascii]; + $this->$markerFunction(); + } else { + if (false === $this->isControl) { // Non control word: Push character + $this->pushText($char); + } else { + if (preg_match('/^[a-zA-Z0-9-]?$/', $char)) { // No delimiter: Buffer control + $this->control .= $char; + $this->isFirst = false; + } else { // Delimiter found: Parse buffered control + if ($this->isFirst) { + $this->isFirst = false; + } else { + if (' ' == $char) { // Discard space as a control word delimiter + $this->flushControl(true); + } + } + } + } + } + $this->offset++; + } + $this->flushText(); + } + + /** + * Mark opening braket `{` character. + */ + private function markOpening() + { + $this->flush(true); + array_push($this->groups, $this->flags); + } + + /** + * Mark closing braket `}` character. + */ + private function markClosing() + { + $this->flush(true); + $this->flags = array_pop($this->groups); + } + + /** + * Mark backslash `\` character. + */ + private function markBackslash() + { + if ($this->isFirst) { + $this->setControl(false); + $this->text .= '\\'; + } else { + $this->flush(); + $this->setControl(true); + $this->control = ''; + } + } + + /** + * Mark newline character: Flush control word because it's not possible to span multiline. + */ + private function markNewline() + { + if ($this->isControl) { + $this->flushControl(true); + } + } + + /** + * Flush control word or text. + * + * @param bool $isControl + */ + private function flush($isControl = false) + { + if ($this->isControl) { + $this->flushControl($isControl); + } else { + $this->flushText(); + } + } + + /** + * Flush control word. + * + * @param bool $isControl + */ + private function flushControl($isControl = false) + { + if (1 === preg_match('/^([A-Za-z]+)(-?[0-9]*) ?$/', $this->control, $match)) { + list(, $control, $parameter) = $match; + $this->parseControl($control, $parameter); + } + + if (true === $isControl) { + $this->setControl(false); + } + } + + /** + * Flush text in queue. + */ + private function flushText() + { + if ($this->text != '') { + if (isset($this->flags['property'])) { // Set property + $this->flags['value'] = $this->text; + } else { // Set text + if (true === $this->flags['paragraph']) { + $this->flags['paragraph'] = false; + $this->flags['text'] = $this->text; + } + } + + // Add text if it's not flagged as skipped + if (!isset($this->flags['skipped'])) { + $this->readText(); + } + + $this->text = ''; + } + } + + /** + * Reset control word and first char state. + * + * @param bool $value + */ + private function setControl($value) + { + $this->isControl = $value; + $this->isFirst = $value; + } + + /** + * Push text into queue. + * + * @param string $char + */ + private function pushText($char) + { + if ('<' == $char) { + $this->text .= '<'; + } elseif ('>' == $char) { + $this->text .= '>'; + } else { + $this->text .= $char; + } + } + + /** + * Parse control. + * + * @param string $control + * @param string $parameter + */ + private function parseControl($control, $parameter) + { + $controls = array( + 'par' => array(self::PARA, 'paragraph', true), + 'b' => array(self::STYL, 'font', 'bold', true), + 'i' => array(self::STYL, 'font', 'italic', true), + 'u' => array(self::STYL, 'font', 'underline', true), + 'strike' => array(self::STYL, 'font', 'strikethrough', true), + 'fs' => array(self::STYL, 'font', 'size', $parameter), + 'qc' => array(self::STYL, 'paragraph', 'alignment', Jc::CENTER), + 'sa' => array(self::STYL, 'paragraph', 'spaceAfter', $parameter), + 'fonttbl' => array(self::SKIP, 'fonttbl', null), + 'colortbl' => array(self::SKIP, 'colortbl', null), + 'info' => array(self::SKIP, 'info', null), + 'generator' => array(self::SKIP, 'generator', null), + 'title' => array(self::SKIP, 'title', null), + 'subject' => array(self::SKIP, 'subject', null), + 'category' => array(self::SKIP, 'category', null), + 'keywords' => array(self::SKIP, 'keywords', null), + 'comment' => array(self::SKIP, 'comment', null), + 'shppict' => array(self::SKIP, 'pic', null), + 'fldinst' => array(self::SKIP, 'link', null), + ); + + if (isset($controls[$control])) { + list($function) = $controls[$control]; + if (method_exists($this, $function)) { + $directives = $controls[$control]; + array_shift($directives); // remove the function variable; we won't need it + $this->$function($directives); + } + } + } + + /** + * Read paragraph. + * + * @param array $directives + */ + private function readParagraph($directives) + { + list($property, $value) = $directives; + $this->textrun = $this->section->addTextRun(); + $this->flags[$property] = $value; + } + + /** + * Read style. + * + * @param array $directives + */ + private function readStyle($directives) + { + list($style, $property, $value) = $directives; + $this->flags['styles'][$style][$property] = $value; + } + + /** + * Read skip. + * + * @param array $directives + */ + private function readSkip($directives) + { + list($property) = $directives; + $this->flags['property'] = $property; + $this->flags['skipped'] = true; + } + + /** + * Read text. + */ + private function readText() + { + $text = $this->textrun->addText($this->text); + if (isset($this->flags['styles']['font'])) { + $text->getFontStyle()->setStyleByArray($this->flags['styles']['font']); + } + } +} diff --git a/PhpOffice/PhpWord/Reader/ReaderInterface.php b/PhpOffice/PhpWord/Reader/ReaderInterface.php new file mode 100644 index 0000000..4024cdb --- /dev/null +++ b/PhpOffice/PhpWord/Reader/ReaderInterface.php @@ -0,0 +1,41 @@ +readRelationships($docFile); + + $steps = array( + array('stepPart' => 'document', 'stepItems' => array( + 'styles' => 'Styles', + 'numbering' => 'Numbering', + )), + array('stepPart' => 'main', 'stepItems' => array( + 'officeDocument' => 'Document', + 'core-properties' => 'DocPropsCore', + 'extended-properties' => 'DocPropsApp', + 'custom-properties' => 'DocPropsCustom', + )), + array('stepPart' => 'document', 'stepItems' => array( + 'endnotes' => 'Endnotes', + 'footnotes' => 'Footnotes', + 'settings' => 'Settings', + )), + ); + + foreach ($steps as $step) { + $stepPart = $step['stepPart']; + $stepItems = $step['stepItems']; + if (!isset($relationships[$stepPart])) { + continue; + } + foreach ($relationships[$stepPart] as $relItem) { + $relType = $relItem['type']; + if (isset($stepItems[$relType])) { + $partName = $stepItems[$relType]; + $xmlFile = $relItem['target']; + $this->readPart($phpWord, $relationships, $partName, $docFile, $xmlFile); + } + } + } + + return $phpWord; + } + + /** + * Read document part. + * + * @param \PhpOffice\PhpWord\PhpWord $phpWord + * @param array $relationships + * @param string $partName + * @param string $docFile + * @param string $xmlFile + */ + private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile) + { + $partClass = "PhpOffice\\PhpWord\\Reader\\Word2007\\{$partName}"; + if (class_exists($partClass)) { + /** @var \PhpOffice\PhpWord\Reader\Word2007\AbstractPart $part Type hint */ + $part = new $partClass($docFile, $xmlFile); + $part->setRels($relationships); + $part->read($phpWord); + } + } + + /** + * Read all relationship files + * + * @param string $docFile + * @return array + */ + private function readRelationships($docFile) + { + $relationships = array(); + + // _rels/.rels + $relationships['main'] = $this->getRels($docFile, '_rels/.rels'); + + // word/_rels/*.xml.rels + $wordRelsPath = 'word/_rels/'; + $zip = new ZipArchive(); + if ($zip->open($docFile) === true) { + for ($i = 0; $i < $zip->numFiles; $i++) { + $xmlFile = $zip->getNameIndex($i); + if ((substr($xmlFile, 0, strlen($wordRelsPath))) == $wordRelsPath && (substr($xmlFile, -1)) != '/') { + $docPart = str_replace('.xml.rels', '', str_replace($wordRelsPath, '', $xmlFile)); + $relationships[$docPart] = $this->getRels($docFile, $xmlFile, 'word/'); + } + } + $zip->close(); + } + + return $relationships; + } + + /** + * Get relationship array + * + * @param string $docFile + * @param string $xmlFile + * @param string $targetPrefix + * @return array + */ + private function getRels($docFile, $xmlFile, $targetPrefix = '') + { + $metaPrefix = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/'; + $officePrefix = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/'; + + $rels = array(); + + $xmlReader = new XMLReader(); + $xmlReader->getDomFromZip($docFile, $xmlFile); + $nodes = $xmlReader->getElements('*'); + foreach ($nodes as $node) { + $rId = $xmlReader->getAttribute('Id', $node); + $type = $xmlReader->getAttribute('Type', $node); + $target = $xmlReader->getAttribute('Target', $node); + $mode = $xmlReader->getAttribute('TargetMode', $node); + + // Remove URL prefixes from $type to make it easier to read + $type = str_replace($metaPrefix, '', $type); + $type = str_replace($officePrefix, '', $type); + $docPart = str_replace('.xml', '', $target); + + // Do not add prefix to link source + if ($type != 'hyperlink' && $mode != 'External') { + $target = $targetPrefix . $target; + } + + // Push to return array + $rels[$rId] = array('type' => $type, 'target' => $target, 'docPart' => $docPart, 'targetMode' => $mode); + } + ksort($rels); + + return $rels; + } +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php b/PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php new file mode 100644 index 0000000..06dfe37 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php @@ -0,0 +1,733 @@ +docFile = $docFile; + $this->xmlFile = $xmlFile; + } + + /** + * Set relationships. + * + * @param array $value + */ + public function setRels($value) + { + $this->rels = $value; + } + + /** + * Read w:p. + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @param \PhpOffice\PhpWord\Element\AbstractContainer $parent + * @param string $docPart + * + * @todo Get font style for preserve text + */ + protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart = 'document') + { + // Paragraph style + $paragraphStyle = null; + $headingDepth = null; + if ($xmlReader->elementExists('w:pPr', $domNode)) { + $paragraphStyle = $this->readParagraphStyle($xmlReader, $domNode); + $headingDepth = $this->getHeadingDepth($paragraphStyle); + } + + // PreserveText + if ($xmlReader->elementExists('w:r/w:instrText', $domNode)) { + $ignoreText = false; + $textContent = ''; + $fontStyle = $this->readFontStyle($xmlReader, $domNode); + $nodes = $xmlReader->getElements('w:r', $domNode); + foreach ($nodes as $node) { + $instrText = $xmlReader->getValue('w:instrText', $node); + if ($xmlReader->elementExists('w:fldChar', $node)) { + $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar'); + if ('begin' == $fldCharType) { + $ignoreText = true; + } elseif ('end' == $fldCharType) { + $ignoreText = false; + } + } + if (!is_null($instrText)) { + $textContent .= '{' . $instrText . '}'; + } else { + if (false === $ignoreText) { + $textContent .= $xmlReader->getValue('w:t', $node); + } + } + } + $parent->addPreserveText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'), $fontStyle, $paragraphStyle); + } elseif ($xmlReader->elementExists('w:pPr/w:numPr', $domNode)) { + // List item + $numId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:numId'); + $levelId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:ilvl'); + $nodes = $xmlReader->getElements('*', $domNode); + + $listItemRun = $parent->addListItemRun($levelId, "PHPWordList{$numId}", $paragraphStyle); + + foreach ($nodes as $node) { + $this->readRun($xmlReader, $node, $listItemRun, $docPart, $paragraphStyle); + } + } elseif ($headingDepth !== null) { + // Heading or Title + $textContent = null; + $nodes = $xmlReader->getElements('w:r', $domNode); + if ($nodes->length === 1) { + $textContent = htmlspecialchars($xmlReader->getValue('w:t', $nodes->item(0)), ENT_QUOTES, 'UTF-8'); + } else { + $textContent = new TextRun($paragraphStyle); + foreach ($nodes as $node) { + $this->readRun($xmlReader, $node, $textContent, $docPart, $paragraphStyle); + } + } + $parent->addTitle($textContent, $headingDepth); + } else { + // Text and TextRun + $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode); + if (0 === $textRunContainers) { + $parent->addTextBreak(null, $paragraphStyle); + } else { + $nodes = $xmlReader->getElements('*', $domNode); + $paragraph = $parent->addTextRun($paragraphStyle); + foreach ($nodes as $node) { + $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle); + } + } + } + } + + /** + * Returns the depth of the Heading, returns 0 for a Title + * + * @param array $paragraphStyle + * @return number|null + */ + private function getHeadingDepth(array $paragraphStyle = null) + { + if (is_array($paragraphStyle) && isset($paragraphStyle['styleName'])) { + if ('Title' === $paragraphStyle['styleName']) { + return 0; + } + + $headingMatches = array(); + preg_match('/Heading(\d)/', $paragraphStyle['styleName'], $headingMatches); + if (!empty($headingMatches)) { + return $headingMatches[1]; + } + } + + return null; + } + + /** + * Read w:r. + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @param \PhpOffice\PhpWord\Element\AbstractContainer $parent + * @param string $docPart + * @param mixed $paragraphStyle + * + * @todo Footnote paragraph style + */ + protected function readRun(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart, $paragraphStyle = null) + { + if (in_array($domNode->nodeName, array('w:ins', 'w:del', 'w:smartTag', 'w:hyperlink'))) { + $nodes = $xmlReader->getElements('*', $domNode); + foreach ($nodes as $node) { + $this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle); + } + } elseif ($domNode->nodeName == 'w:r') { + $fontStyle = $this->readFontStyle($xmlReader, $domNode); + $nodes = $xmlReader->getElements('*', $domNode); + foreach ($nodes as $node) { + $this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle); + } + } + } + + /** + * Parses nodes under w:r + * + * @param XMLReader $xmlReader + * @param \DOMElement $node + * @param AbstractContainer $parent + * @param string $docPart + * @param mixed $paragraphStyle + * @param mixed $fontStyle + */ + protected function readRunChild(XMLReader $xmlReader, \DOMElement $node, AbstractContainer $parent, $docPart, $paragraphStyle = null, $fontStyle = null) + { + $runParent = $node->parentNode->parentNode; + if ($node->nodeName == 'w:footnoteReference') { + // Footnote + $wId = $xmlReader->getAttribute('w:id', $node); + $footnote = $parent->addFootnote(); + $footnote->setRelationId($wId); + } elseif ($node->nodeName == 'w:endnoteReference') { + // Endnote + $wId = $xmlReader->getAttribute('w:id', $node); + $endnote = $parent->addEndnote(); + $endnote->setRelationId($wId); + } elseif ($node->nodeName == 'w:pict') { + // Image + $rId = $xmlReader->getAttribute('r:id', $node, 'v:shape/v:imagedata'); + $target = $this->getMediaTarget($docPart, $rId); + if (!is_null($target)) { + if ('External' == $this->getTargetMode($docPart, $rId)) { + $imageSource = $target; + } else { + $imageSource = "zip://{$this->docFile}#{$target}"; + } + $parent->addImage($imageSource); + } + } elseif ($node->nodeName == 'w:drawing') { + // Office 2011 Image + $xmlReader->registerNamespace('wp', 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'); + $xmlReader->registerNamespace('r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlReader->registerNamespace('pic', 'http://schemas.openxmlformats.org/drawingml/2006/picture'); + $xmlReader->registerNamespace('a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + + $name = $xmlReader->getAttribute('name', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr'); + $embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip'); + if ($name === null && $embedId === null) { // some Converters puts images on a different path + $name = $xmlReader->getAttribute('name', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr'); + $embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip'); + } + $target = $this->getMediaTarget($docPart, $embedId); + if (!is_null($target)) { + $imageSource = "zip://{$this->docFile}#{$target}"; + $parent->addImage($imageSource, null, false, $name); + } + } elseif ($node->nodeName == 'w:object') { + // Object + $rId = $xmlReader->getAttribute('r:id', $node, 'o:OLEObject'); + // $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata'); + $target = $this->getMediaTarget($docPart, $rId); + if (!is_null($target)) { + $textContent = "<Object: {$target}>"; + $parent->addText($textContent, $fontStyle, $paragraphStyle); + } + } elseif ($node->nodeName == 'w:br') { + $parent->addTextBreak(); + } elseif ($node->nodeName == 'w:tab') { + $parent->addText("\t"); + } elseif ($node->nodeName == 'w:t' || $node->nodeName == 'w:delText') { + // TextRun + $textContent = htmlspecialchars($xmlReader->getValue('.', $node), ENT_QUOTES, 'UTF-8'); + + if ($runParent->nodeName == 'w:hyperlink') { + $rId = $xmlReader->getAttribute('r:id', $runParent); + $target = $this->getMediaTarget($docPart, $rId); + if (!is_null($target)) { + $parent->addLink($target, $textContent, $fontStyle, $paragraphStyle); + } else { + $parent->addText($textContent, $fontStyle, $paragraphStyle); + } + } else { + /** @var AbstractElement $element */ + $element = $parent->addText($textContent, $fontStyle, $paragraphStyle); + if (in_array($runParent->nodeName, array('w:ins', 'w:del'))) { + $type = ($runParent->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED; + $author = $runParent->getAttribute('w:author'); + $date = \DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $runParent->getAttribute('w:date')); + $element->setChangeInfo($type, $author, $date); + } + } + } + } + + /** + * Read w:tbl. + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @param mixed $parent + * @param string $docPart + */ + protected function readTable(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart = 'document') + { + // Table style + $tblStyle = null; + if ($xmlReader->elementExists('w:tblPr', $domNode)) { + $tblStyle = $this->readTableStyle($xmlReader, $domNode); + } + + /** @var \PhpOffice\PhpWord\Element\Table $table Type hint */ + $table = $parent->addTable($tblStyle); + $tblNodes = $xmlReader->getElements('*', $domNode); + foreach ($tblNodes as $tblNode) { + if ('w:tblGrid' == $tblNode->nodeName) { // Column + // @todo Do something with table columns + } elseif ('w:tr' == $tblNode->nodeName) { // Row + $rowHeight = $xmlReader->getAttribute('w:val', $tblNode, 'w:trPr/w:trHeight'); + $rowHRule = $xmlReader->getAttribute('w:hRule', $tblNode, 'w:trPr/w:trHeight'); + $rowHRule = $rowHRule == 'exact'; + $rowStyle = array( + 'tblHeader' => $xmlReader->elementExists('w:trPr/w:tblHeader', $tblNode), + 'cantSplit' => $xmlReader->elementExists('w:trPr/w:cantSplit', $tblNode), + 'exactHeight' => $rowHRule, + ); + + $row = $table->addRow($rowHeight, $rowStyle); + $rowNodes = $xmlReader->getElements('*', $tblNode); + foreach ($rowNodes as $rowNode) { + if ('w:trPr' == $rowNode->nodeName) { // Row style + // @todo Do something with row style + } elseif ('w:tc' == $rowNode->nodeName) { // Cell + $cellWidth = $xmlReader->getAttribute('w:w', $rowNode, 'w:tcPr/w:tcW'); + $cellStyle = null; + $cellStyleNode = $xmlReader->getElement('w:tcPr', $rowNode); + if (!is_null($cellStyleNode)) { + $cellStyle = $this->readCellStyle($xmlReader, $cellStyleNode); + } + + $cell = $row->addCell($cellWidth, $cellStyle); + $cellNodes = $xmlReader->getElements('*', $rowNode); + foreach ($cellNodes as $cellNode) { + if ('w:p' == $cellNode->nodeName) { // Paragraph + $this->readParagraph($xmlReader, $cellNode, $cell, $docPart); + } + } + } + } + } + } + } + + /** + * Read w:pPr. + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @return array|null + */ + protected function readParagraphStyle(XMLReader $xmlReader, \DOMElement $domNode) + { + if (!$xmlReader->elementExists('w:pPr', $domNode)) { + return null; + } + + $styleNode = $xmlReader->getElement('w:pPr', $domNode); + $styleDefs = array( + 'styleName' => array(self::READ_VALUE, array('w:pStyle', 'w:name')), + 'alignment' => array(self::READ_VALUE, 'w:jc'), + 'basedOn' => array(self::READ_VALUE, 'w:basedOn'), + 'next' => array(self::READ_VALUE, 'w:next'), + 'indent' => array(self::READ_VALUE, 'w:ind', 'w:left'), + 'hanging' => array(self::READ_VALUE, 'w:ind', 'w:hanging'), + 'spaceAfter' => array(self::READ_VALUE, 'w:spacing', 'w:after'), + 'spaceBefore' => array(self::READ_VALUE, 'w:spacing', 'w:before'), + 'widowControl' => array(self::READ_FALSE, 'w:widowControl'), + 'keepNext' => array(self::READ_TRUE, 'w:keepNext'), + 'keepLines' => array(self::READ_TRUE, 'w:keepLines'), + 'pageBreakBefore' => array(self::READ_TRUE, 'w:pageBreakBefore'), + 'contextualSpacing' => array(self::READ_TRUE, 'w:contextualSpacing'), + 'bidi' => array(self::READ_TRUE, 'w:bidi'), + 'suppressAutoHyphens' => array(self::READ_TRUE, 'w:suppressAutoHyphens'), + ); + + return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); + } + + /** + * Read w:rPr + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @return array|null + */ + protected function readFontStyle(XMLReader $xmlReader, \DOMElement $domNode) + { + if (is_null($domNode)) { + return null; + } + // Hyperlink has an extra w:r child + if ('w:hyperlink' == $domNode->nodeName) { + $domNode = $xmlReader->getElement('w:r', $domNode); + } + if (!$xmlReader->elementExists('w:rPr', $domNode)) { + return null; + } + + $styleNode = $xmlReader->getElement('w:rPr', $domNode); + $styleDefs = array( + 'styleName' => array(self::READ_VALUE, 'w:rStyle'), + 'name' => array(self::READ_VALUE, 'w:rFonts', array('w:ascii', 'w:hAnsi', 'w:eastAsia', 'w:cs')), + 'hint' => array(self::READ_VALUE, 'w:rFonts', 'w:hint'), + 'size' => array(self::READ_SIZE, array('w:sz', 'w:szCs')), + 'color' => array(self::READ_VALUE, 'w:color'), + 'underline' => array(self::READ_VALUE, 'w:u'), + 'bold' => array(self::READ_TRUE, 'w:b'), + 'italic' => array(self::READ_TRUE, 'w:i'), + 'strikethrough' => array(self::READ_TRUE, 'w:strike'), + 'doubleStrikethrough' => array(self::READ_TRUE, 'w:dstrike'), + 'smallCaps' => array(self::READ_TRUE, 'w:smallCaps'), + 'allCaps' => array(self::READ_TRUE, 'w:caps'), + 'superScript' => array(self::READ_EQUAL, 'w:vertAlign', 'w:val', 'superscript'), + 'subScript' => array(self::READ_EQUAL, 'w:vertAlign', 'w:val', 'subscript'), + 'fgColor' => array(self::READ_VALUE, 'w:highlight'), + 'rtl' => array(self::READ_TRUE, 'w:rtl'), + 'lang' => array(self::READ_VALUE, 'w:lang'), + 'position' => array(self::READ_VALUE, 'w:position'), + 'hidden' => array(self::READ_TRUE, 'w:vanish'), + ); + + return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); + } + + /** + * Read w:tblPr + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @return string|array|null + * @todo Capture w:tblStylePr w:type="firstRow" + */ + protected function readTableStyle(XMLReader $xmlReader, \DOMElement $domNode) + { + $style = null; + $margins = array('top', 'left', 'bottom', 'right'); + $borders = array_merge($margins, array('insideH', 'insideV')); + + if ($xmlReader->elementExists('w:tblPr', $domNode)) { + if ($xmlReader->elementExists('w:tblPr/w:tblStyle', $domNode)) { + $style = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle'); + } else { + $styleNode = $xmlReader->getElement('w:tblPr', $domNode); + $styleDefs = array(); + foreach ($margins as $side) { + $ucfSide = ucfirst($side); + $styleDefs["cellMargin$ucfSide"] = array(self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w'); + } + foreach ($borders as $side) { + $ucfSide = ucfirst($side); + $styleDefs["border{$ucfSide}Size"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz'); + $styleDefs["border{$ucfSide}Color"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:color'); + $styleDefs["border{$ucfSide}Style"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:val'); + } + $styleDefs['layout'] = array(self::READ_VALUE, 'w:tblLayout', 'w:type'); + $styleDefs['bidiVisual'] = array(self::READ_TRUE, 'w:bidiVisual'); + $styleDefs['cellSpacing'] = array(self::READ_VALUE, 'w:tblCellSpacing', 'w:w'); + $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); + + $tablePositionNode = $xmlReader->getElement('w:tblpPr', $styleNode); + if ($tablePositionNode !== null) { + $style['position'] = $this->readTablePosition($xmlReader, $tablePositionNode); + } + + $indentNode = $xmlReader->getElement('w:tblInd', $styleNode); + if ($indentNode !== null) { + $style['indent'] = $this->readTableIndent($xmlReader, $indentNode); + } + } + } + + return $style; + } + + /** + * Read w:tblpPr + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @return array + */ + private function readTablePosition(XMLReader $xmlReader, \DOMElement $domNode) + { + $styleDefs = array( + 'leftFromText' => array(self::READ_VALUE, '.', 'w:leftFromText'), + 'rightFromText' => array(self::READ_VALUE, '.', 'w:rightFromText'), + 'topFromText' => array(self::READ_VALUE, '.', 'w:topFromText'), + 'bottomFromText' => array(self::READ_VALUE, '.', 'w:bottomFromText'), + 'vertAnchor' => array(self::READ_VALUE, '.', 'w:vertAnchor'), + 'horzAnchor' => array(self::READ_VALUE, '.', 'w:horzAnchor'), + 'tblpXSpec' => array(self::READ_VALUE, '.', 'w:tblpXSpec'), + 'tblpX' => array(self::READ_VALUE, '.', 'w:tblpX'), + 'tblpYSpec' => array(self::READ_VALUE, '.', 'w:tblpYSpec'), + 'tblpY' => array(self::READ_VALUE, '.', 'w:tblpY'), + ); + + return $this->readStyleDefs($xmlReader, $domNode, $styleDefs); + } + + /** + * Read w:tblInd + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @return TblWidthComplexType + */ + private function readTableIndent(XMLReader $xmlReader, \DOMElement $domNode) + { + $styleDefs = array( + 'value' => array(self::READ_VALUE, '.', 'w:w'), + 'type' => array(self::READ_VALUE, '.', 'w:type'), + ); + $styleDefs = $this->readStyleDefs($xmlReader, $domNode, $styleDefs); + + return new TblWidthComplexType((int) $styleDefs['value'], $styleDefs['type']); + } + + /** + * Read w:tcPr + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @return array + */ + private function readCellStyle(XMLReader $xmlReader, \DOMElement $domNode) + { + $styleDefs = array( + 'valign' => array(self::READ_VALUE, 'w:vAlign'), + 'textDirection' => array(self::READ_VALUE, 'w:textDirection'), + 'gridSpan' => array(self::READ_VALUE, 'w:gridSpan'), + 'vMerge' => array(self::READ_VALUE, 'w:vMerge'), + 'bgColor' => array(self::READ_VALUE, 'w:shd', 'w:fill'), + ); + + return $this->readStyleDefs($xmlReader, $domNode, $styleDefs); + } + + /** + * Returns the first child element found + * + * @param XMLReader $xmlReader + * @param \DOMElement $parentNode + * @param string|array $elements + * @return string|null + */ + private function findPossibleElement(XMLReader $xmlReader, \DOMElement $parentNode = null, $elements) + { + if (is_array($elements)) { + //if element is an array, we take the first element that exists in the XML + foreach ($elements as $possibleElement) { + if ($xmlReader->elementExists($possibleElement, $parentNode)) { + return $possibleElement; + } + } + } else { + return $elements; + } + + return null; + } + + /** + * Returns the first attribute found + * + * @param XMLReader $xmlReader + * @param \DOMElement $node + * @param string|array $attributes + * @return string|null + */ + private function findPossibleAttribute(XMLReader $xmlReader, \DOMElement $node, $attributes) + { + //if attribute is an array, we take the first attribute that exists in the XML + if (is_array($attributes)) { + foreach ($attributes as $possibleAttribute) { + if ($xmlReader->getAttribute($possibleAttribute, $node)) { + return $possibleAttribute; + } + } + + return null; + } + + return $attributes; + } + + /** + * Read style definition + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $parentNode + * @param array $styleDefs + * @ignoreScrutinizerPatch + * @return array + */ + protected function readStyleDefs(XMLReader $xmlReader, \DOMElement $parentNode = null, $styleDefs = array()) + { + $styles = array(); + + foreach ($styleDefs as $styleProp => $styleVal) { + list($method, $element, $attribute, $expected) = array_pad($styleVal, 4, null); + + $element = $this->findPossibleElement($xmlReader, $parentNode, $element); + if ($element === null) { + continue; + } + + if ($xmlReader->elementExists($element, $parentNode)) { + $node = $xmlReader->getElement($element, $parentNode); + + $attribute = $this->findPossibleAttribute($xmlReader, $node, $attribute); + + // Use w:val as default if no attribute assigned + $attribute = ($attribute === null) ? 'w:val' : $attribute; + $attributeValue = $xmlReader->getAttribute($attribute, $node); + + $styleValue = $this->readStyleDef($method, $attributeValue, $expected); + if ($styleValue !== null) { + $styles[$styleProp] = $styleValue; + } + } + } + + return $styles; + } + + /** + * Return style definition based on conversion method + * + * @param string $method + * @ignoreScrutinizerPatch + * @param string|null $attributeValue + * @param mixed $expected + * @return mixed + */ + private function readStyleDef($method, $attributeValue, $expected) + { + $style = $attributeValue; + + if (self::READ_SIZE == $method) { + $style = $attributeValue / 2; + } elseif (self::READ_TRUE == $method) { + $style = $this->isOn($attributeValue); + } elseif (self::READ_FALSE == $method) { + $style = !$this->isOn($attributeValue); + } elseif (self::READ_EQUAL == $method) { + $style = $attributeValue == $expected; + } + + return $style; + } + + /** + * Parses the value of the on/off value, null is considered true as it means the w:val attribute was not present + * + * @see http://www.datypic.com/sc/ooxml/t-w_ST_OnOff.html + * @param string $value + * @return bool + */ + private function isOn($value = null) + { + return $value === null || $value === '1' || $value === 'true' || $value === 'on'; + } + + /** + * Returns the target of image, object, or link as stored in ::readMainRels + * + * @param string $docPart + * @param string $rId + * @return string|null + */ + private function getMediaTarget($docPart, $rId) + { + $target = null; + + if (isset($this->rels[$docPart]) && isset($this->rels[$docPart][$rId])) { + $target = $this->rels[$docPart][$rId]['target']; + } + + return $target; + } + + /** + * Returns the target mode + * + * @param string $docPart + * @param string $rId + * @return string|null + */ + private function getTargetMode($docPart, $rId) + { + $mode = null; + + if (isset($this->rels[$docPart]) && isset($this->rels[$docPart][$rId])) { + $mode = $this->rels[$docPart][$rId]['targetMode']; + } + + return $mode; + } +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php b/PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php new file mode 100644 index 0000000..decc510 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php @@ -0,0 +1,40 @@ + 'setCompany', 'Manager' => 'setManager'); + + /** + * Callback functions + * + * @var array + */ + protected $callbacks = array(); +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php b/PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php new file mode 100644 index 0000000..36eeceb --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php @@ -0,0 +1,83 @@ + 'setCreator', + 'dc:title' => 'setTitle', + 'dc:description' => 'setDescription', + 'dc:subject' => 'setSubject', + 'cp:keywords' => 'setKeywords', + 'cp:category' => 'setCategory', + 'cp:lastModifiedBy' => 'setLastModifiedBy', + 'dcterms:created' => 'setCreated', + 'dcterms:modified' => 'setModified', + ); + + /** + * Callback functions + * + * @var array + */ + protected $callbacks = array('dcterms:created' => 'strtotime', 'dcterms:modified' => 'strtotime'); + + /** + * Read core/extended document properties. + * + * @param \PhpOffice\PhpWord\PhpWord $phpWord + */ + public function read(PhpWord $phpWord) + { + $xmlReader = new XMLReader(); + $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); + + $docProps = $phpWord->getDocInfo(); + + $nodes = $xmlReader->getElements('*'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + if (!isset($this->mapping[$node->nodeName])) { + continue; + } + $method = $this->mapping[$node->nodeName]; + $value = $node->nodeValue == '' ? null : $node->nodeValue; + if (isset($this->callbacks[$node->nodeName])) { + $value = $this->callbacks[$node->nodeName]($value); + } + if (method_exists($docProps, $method)) { + $docProps->$method($value); + } + } + } + } +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php b/PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php new file mode 100644 index 0000000..a6835aa --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php @@ -0,0 +1,55 @@ +getDomFromZip($this->docFile, $this->xmlFile); + $docProps = $phpWord->getDocInfo(); + + $nodes = $xmlReader->getElements('*'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $propertyName = $xmlReader->getAttribute('name', $node); + $attributeNode = $xmlReader->getElement('*', $node); + $attributeType = $attributeNode->nodeName; + $attributeValue = $attributeNode->nodeValue; + $attributeValue = DocInfo::convertProperty($attributeValue, $attributeType); + $attributeType = DocInfo::convertPropertyType($attributeType); + $docProps->setCustomProperty($propertyName, $attributeValue, $attributeType); + } + } + } +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/Document.php b/PhpOffice/PhpWord/Reader/Word2007/Document.php new file mode 100644 index 0000000..f0d1194 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/Document.php @@ -0,0 +1,183 @@ +phpWord = $phpWord; + $xmlReader = new XMLReader(); + $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); + $readMethods = array('w:p' => 'readWPNode', 'w:tbl' => 'readTable', 'w:sectPr' => 'readWSectPrNode'); + + $nodes = $xmlReader->getElements('w:body/*'); + if ($nodes->length > 0) { + $section = $this->phpWord->addSection(); + foreach ($nodes as $node) { + if (isset($readMethods[$node->nodeName])) { + $readMethod = $readMethods[$node->nodeName]; + $this->$readMethod($xmlReader, $node, $section); + } + } + } + } + + /** + * Read header footer. + * + * @param array $settings + * @param \PhpOffice\PhpWord\Element\Section &$section + */ + private function readHeaderFooter($settings, Section &$section) + { + $readMethods = array('w:p' => 'readParagraph', 'w:tbl' => 'readTable'); + + if (is_array($settings) && isset($settings['hf'])) { + foreach ($settings['hf'] as $rId => $hfSetting) { + if (isset($this->rels['document'][$rId])) { + list($hfType, $xmlFile, $docPart) = array_values($this->rels['document'][$rId]); + $addMethod = "add{$hfType}"; + $hfObject = $section->$addMethod($hfSetting['type']); + + // Read header/footer content + $xmlReader = new XMLReader(); + $xmlReader->getDomFromZip($this->docFile, $xmlFile); + $nodes = $xmlReader->getElements('*'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + if (isset($readMethods[$node->nodeName])) { + $readMethod = $readMethods[$node->nodeName]; + $this->$readMethod($xmlReader, $node, $hfObject, $docPart); + } + } + } + } + } + } + } + + /** + * Read w:sectPr + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $domNode + * @ignoreScrutinizerPatch + * @return array + */ + private function readSectionStyle(XMLReader $xmlReader, \DOMElement $domNode) + { + $styleDefs = array( + 'breakType' => array(self::READ_VALUE, 'w:type'), + 'vAlign' => array(self::READ_VALUE, 'w:vAlign'), + 'pageSizeW' => array(self::READ_VALUE, 'w:pgSz', 'w:w'), + 'pageSizeH' => array(self::READ_VALUE, 'w:pgSz', 'w:h'), + 'orientation' => array(self::READ_VALUE, 'w:pgSz', 'w:orient'), + 'colsNum' => array(self::READ_VALUE, 'w:cols', 'w:num'), + 'colsSpace' => array(self::READ_VALUE, 'w:cols', 'w:space'), + 'marginTop' => array(self::READ_VALUE, 'w:pgMar', 'w:top'), + 'marginLeft' => array(self::READ_VALUE, 'w:pgMar', 'w:left'), + 'marginBottom' => array(self::READ_VALUE, 'w:pgMar', 'w:bottom'), + 'marginRight' => array(self::READ_VALUE, 'w:pgMar', 'w:right'), + 'headerHeight' => array(self::READ_VALUE, 'w:pgMar', 'w:header'), + 'footerHeight' => array(self::READ_VALUE, 'w:pgMar', 'w:footer'), + 'gutter' => array(self::READ_VALUE, 'w:pgMar', 'w:gutter'), + ); + $styles = $this->readStyleDefs($xmlReader, $domNode, $styleDefs); + + // Header and footer + // @todo Cleanup this part + $nodes = $xmlReader->getElements('*', $domNode); + foreach ($nodes as $node) { + if ($node->nodeName == 'w:headerReference' || $node->nodeName == 'w:footerReference') { + $id = $xmlReader->getAttribute('r:id', $node); + $styles['hf'][$id] = array( + 'method' => str_replace('w:', '', str_replace('Reference', '', $node->nodeName)), + 'type' => $xmlReader->getAttribute('w:type', $node), + ); + } + } + + return $styles; + } + + /** + * Read w:p node. + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $node + * @param \PhpOffice\PhpWord\Element\Section &$section + * + * @todo + */ + private function readWPNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) + { + // Page break + if ($xmlReader->getAttribute('w:type', $node, 'w:r/w:br') == 'page') { + $section->addPageBreak(); // PageBreak + } + + // Paragraph + $this->readParagraph($xmlReader, $node, $section); + + // Section properties + if ($xmlReader->elementExists('w:pPr/w:sectPr', $node)) { + $sectPrNode = $xmlReader->getElement('w:pPr/w:sectPr', $node); + if ($sectPrNode !== null) { + $this->readWSectPrNode($xmlReader, $sectPrNode, $section); + } + $section = $this->phpWord->addSection(); + } + } + + /** + * Read w:sectPr node. + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $node + * @param \PhpOffice\PhpWord\Element\Section &$section + */ + private function readWSectPrNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) + { + $style = $this->readSectionStyle($xmlReader, $node); + $section->setStyle($style); + $this->readHeaderFooter($style, $section); + } +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/Endnotes.php b/PhpOffice/PhpWord/Reader/Word2007/Endnotes.php new file mode 100644 index 0000000..aa8b65d --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/Endnotes.php @@ -0,0 +1,40 @@ +getDomFromZip($this->docFile, $this->xmlFile); + $nodes = $xmlReader->getElements('*'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $id = $xmlReader->getAttribute('w:id', $node); + $type = $xmlReader->getAttribute('w:type', $node); + + // Avoid w:type "separator" and "continuationSeparator" + // Only look for or without w:type attribute, or with w:type = normal + if ((is_null($type) || $type === 'normal')) { + $element = $this->getElement($phpWord, $id); + if ($element !== null) { + $pNodes = $xmlReader->getElements('w:p/*', $node); + foreach ($pNodes as $pNode) { + $this->readRun($xmlReader, $pNode, $element, $this->collection); + } + $addMethod = "add{$this->element}"; + $phpWord->$addMethod($element); + } + } + } + } + } + + /** + * Searches for the element with the given relationId + * + * @param PhpWord $phpWord + * @param int $relationId + * @return \PhpOffice\PhpWord\Element\AbstractContainer|null + */ + private function getElement(PhpWord $phpWord, $relationId) + { + $getMethod = "get{$this->collection}"; + $collection = $phpWord->$getMethod()->getItems(); + + //not found by key, looping to search by relationId + foreach ($collection as $collectionElement) { + if ($collectionElement->getRelationId() == $relationId) { + return $collectionElement; + } + } + + return null; + } +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/Numbering.php b/PhpOffice/PhpWord/Reader/Word2007/Numbering.php new file mode 100644 index 0000000..3f57cbf --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/Numbering.php @@ -0,0 +1,122 @@ +getDomFromZip($this->docFile, $this->xmlFile); + + // Abstract numbering definition + $nodes = $xmlReader->getElements('w:abstractNum'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $abstractId = $xmlReader->getAttribute('w:abstractNumId', $node); + $abstracts[$abstractId] = array('levels' => array()); + $abstract = &$abstracts[$abstractId]; + $subnodes = $xmlReader->getElements('*', $node); + foreach ($subnodes as $subnode) { + switch ($subnode->nodeName) { + case 'w:multiLevelType': + $abstract['type'] = $xmlReader->getAttribute('w:val', $subnode); + break; + case 'w:lvl': + $levelId = $xmlReader->getAttribute('w:ilvl', $subnode); + $abstract['levels'][$levelId] = $this->readLevel($xmlReader, $subnode, $levelId); + break; + } + } + } + } + + // Numbering instance definition + $nodes = $xmlReader->getElements('w:num'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $numId = $xmlReader->getAttribute('w:numId', $node); + $abstractId = $xmlReader->getAttribute('w:val', $node, 'w:abstractNumId'); + $numberings[$numId] = $abstracts[$abstractId]; + $numberings[$numId]['numId'] = $numId; + $subnodes = $xmlReader->getElements('w:lvlOverride/w:lvl', $node); + foreach ($subnodes as $subnode) { + $levelId = $xmlReader->getAttribute('w:ilvl', $subnode); + $overrides = $this->readLevel($xmlReader, $subnode, $levelId); + foreach ($overrides as $key => $value) { + $numberings[$numId]['levels'][$levelId][$key] = $value; + } + } + } + } + + // Push to Style collection + foreach ($numberings as $numId => $numbering) { + $phpWord->addNumberingStyle("PHPWordList{$numId}", $numbering); + } + } + + /** + * Read numbering level definition from w:abstractNum and w:num + * + * @param \PhpOffice\Common\XMLReader $xmlReader + * @param \DOMElement $subnode + * @param int $levelId + * @return array + */ + private function readLevel(XMLReader $xmlReader, \DOMElement $subnode, $levelId) + { + $level = array(); + + $level['level'] = $levelId; + $level['start'] = $xmlReader->getAttribute('w:val', $subnode, 'w:start'); + $level['format'] = $xmlReader->getAttribute('w:val', $subnode, 'w:numFmt'); + $level['restart'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlRestart'); + $level['suffix'] = $xmlReader->getAttribute('w:val', $subnode, 'w:suff'); + $level['text'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlText'); + $level['alignment'] = $xmlReader->getAttribute('w:val', $subnode, 'w:lvlJc'); + $level['tab'] = $xmlReader->getAttribute('w:pos', $subnode, 'w:pPr/w:tabs/w:tab'); + $level['left'] = $xmlReader->getAttribute('w:left', $subnode, 'w:pPr/w:ind'); + $level['hanging'] = $xmlReader->getAttribute('w:hanging', $subnode, 'w:pPr/w:ind'); + $level['font'] = $xmlReader->getAttribute('w:ascii', $subnode, 'w:rPr/w:rFonts'); + $level['hint'] = $xmlReader->getAttribute('w:hint', $subnode, 'w:rPr/w:rFonts'); + + foreach ($level as $key => $value) { + if (is_null($value)) { + unset($level[$key]); + } + } + + return $level; + } +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/Settings.php b/PhpOffice/PhpWord/Reader/Word2007/Settings.php new file mode 100644 index 0000000..3084943 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/Settings.php @@ -0,0 +1,201 @@ +getDomFromZip($this->docFile, $this->xmlFile); + + $docSettings = $phpWord->getSettings(); + + $nodes = $xmlReader->getElements('*'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $name = str_replace('w:', '', $node->nodeName); + $value = $xmlReader->getAttribute('w:val', $node); + $method = 'set' . $name; + + if (in_array($name, $this::$booleanProperties)) { + if ($value == 'false') { + $docSettings->$method(false); + } else { + $docSettings->$method(true); + } + } elseif (method_exists($this, $method)) { + $this->$method($xmlReader, $phpWord, $node); + } elseif (method_exists($docSettings, $method)) { + $docSettings->$method($value); + } + } + } + } + + /** + * Sets the document Language + * + * @param XMLReader $xmlReader + * @param PhpWord $phpWord + * @param \DOMElement $node + */ + protected function setThemeFontLang(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + { + $val = $xmlReader->getAttribute('w:val', $node); + $eastAsia = $xmlReader->getAttribute('w:eastAsia', $node); + $bidi = $xmlReader->getAttribute('w:bidi', $node); + + $themeFontLang = new Language(); + $themeFontLang->setLatin($val); + $themeFontLang->setEastAsia($eastAsia); + $themeFontLang->setBidirectional($bidi); + + $phpWord->getSettings()->setThemeFontLang($themeFontLang); + } + + /** + * Sets the document protection + * + * @param XMLReader $xmlReader + * @param PhpWord $phpWord + * @param \DOMElement $node + */ + protected function setDocumentProtection(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + { + $documentProtection = $phpWord->getSettings()->getDocumentProtection(); + + $edit = $xmlReader->getAttribute('w:edit', $node); + if ($edit !== null) { + $documentProtection->setEditing($edit); + } + } + + /** + * Sets the proof state + * + * @param XMLReader $xmlReader + * @param PhpWord $phpWord + * @param \DOMElement $node + */ + protected function setProofState(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + { + $proofState = $phpWord->getSettings()->getProofState(); + + $spelling = $xmlReader->getAttribute('w:spelling', $node); + $grammar = $xmlReader->getAttribute('w:grammar', $node); + + if ($spelling !== null) { + $proofState->setSpelling($spelling); + } + if ($grammar !== null) { + $proofState->setGrammar($grammar); + } + } + + /** + * Sets the proof state + * + * @param XMLReader $xmlReader + * @param PhpWord $phpWord + * @param \DOMElement $node + */ + protected function setZoom(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + { + $percent = $xmlReader->getAttribute('w:percent', $node); + $val = $xmlReader->getAttribute('w:val', $node); + + if ($percent !== null || $val !== null) { + $phpWord->getSettings()->setZoom($percent === null ? $val : $percent); + } + } + + /** + * Set the Revision view + * + * @param XMLReader $xmlReader + * @param PhpWord $phpWord + * @param \DOMElement $node + */ + protected function setRevisionView(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + { + $revisionView = new TrackChangesView(); + $revisionView->setMarkup(filter_var($xmlReader->getAttribute('w:markup', $node), FILTER_VALIDATE_BOOLEAN)); + $revisionView->setComments($xmlReader->getAttribute('w:comments', $node)); + $revisionView->setInsDel(filter_var($xmlReader->getAttribute('w:insDel', $node), FILTER_VALIDATE_BOOLEAN)); + $revisionView->setFormatting(filter_var($xmlReader->getAttribute('w:formatting', $node), FILTER_VALIDATE_BOOLEAN)); + $revisionView->setInkAnnotations(filter_var($xmlReader->getAttribute('w:inkAnnotations', $node), FILTER_VALIDATE_BOOLEAN)); + $phpWord->getSettings()->setRevisionView($revisionView); + } + + /** + * @param XMLReader $xmlReader + * @param PhpWord $phpWord + * @param \DOMElement $node + */ + protected function setConsecutiveHyphenLimit(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + { + $value = $xmlReader->getAttribute('w:val', $node); + + if ($value !== null) { + $phpWord->getSettings()->setConsecutiveHyphenLimit($value); + } + } + + /** + * @param XMLReader $xmlReader + * @param PhpWord $phpWord + * @param \DOMElement $node + */ + protected function setHyphenationZone(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + { + $value = $xmlReader->getAttribute('w:val', $node); + + if ($value !== null) { + $phpWord->getSettings()->setHyphenationZone($value); + } + } +} diff --git a/PhpOffice/PhpWord/Reader/Word2007/Styles.php b/PhpOffice/PhpWord/Reader/Word2007/Styles.php new file mode 100644 index 0000000..97f29b4 --- /dev/null +++ b/PhpOffice/PhpWord/Reader/Word2007/Styles.php @@ -0,0 +1,106 @@ +getDomFromZip($this->docFile, $this->xmlFile); + + $fontDefaults = $xmlReader->getElement('w:docDefaults/w:rPrDefault'); + if ($fontDefaults !== null) { + $fontDefaultStyle = $this->readFontStyle($xmlReader, $fontDefaults); + if (array_key_exists('name', $fontDefaultStyle)) { + $phpWord->setDefaultFontName($fontDefaultStyle['name']); + } + if (array_key_exists('size', $fontDefaultStyle)) { + $phpWord->setDefaultFontSize($fontDefaultStyle['size']); + } + if (array_key_exists('lang', $fontDefaultStyle)) { + $phpWord->getSettings()->setThemeFontLang(new Language($fontDefaultStyle['lang'])); + } + } + + $paragraphDefaults = $xmlReader->getElement('w:docDefaults/w:pPrDefault'); + if ($paragraphDefaults !== null) { + $paragraphDefaultStyle = $this->readParagraphStyle($xmlReader, $paragraphDefaults); + if ($paragraphDefaultStyle != null) { + $phpWord->setDefaultParagraphStyle($paragraphDefaultStyle); + } + } + + $nodes = $xmlReader->getElements('w:style'); + if ($nodes->length > 0) { + foreach ($nodes as $node) { + $type = $xmlReader->getAttribute('w:type', $node); + $name = $xmlReader->getAttribute('w:val', $node, 'w:name'); + if (is_null($name)) { + $name = $xmlReader->getAttribute('w:styleId', $node); + } + $headingMatches = array(); + preg_match('/Heading\s*(\d)/i', $name, $headingMatches); + // $default = ($xmlReader->getAttribute('w:default', $node) == 1); + switch ($type) { + case 'paragraph': + $paragraphStyle = $this->readParagraphStyle($xmlReader, $node); + $fontStyle = $this->readFontStyle($xmlReader, $node); + if (!empty($headingMatches)) { + $phpWord->addTitleStyle($headingMatches[1], $fontStyle, $paragraphStyle); + } else { + if (empty($fontStyle)) { + if (is_array($paragraphStyle)) { + $phpWord->addParagraphStyle($name, $paragraphStyle); + } + } else { + $phpWord->addFontStyle($name, $fontStyle, $paragraphStyle); + } + } + break; + case 'character': + $fontStyle = $this->readFontStyle($xmlReader, $node); + if (!empty($fontStyle)) { + $phpWord->addFontStyle($name, $fontStyle); + } + break; + case 'table': + $tStyle = $this->readTableStyle($xmlReader, $node); + if (!empty($tStyle)) { + $phpWord->addTableStyle($name, $tStyle); + } + break; + } + } + } + } +} diff --git a/PhpOffice/PhpWord/Settings.php b/PhpOffice/PhpWord/Settings.php new file mode 100644 index 0000000..8de1a8d --- /dev/null +++ b/PhpOffice/PhpWord/Settings.php @@ -0,0 +1,446 @@ + 0) { + self::$defaultFontSize = $value; + + return true; + } + + return false; + } + + /** + * Load setting from phpword.yml or phpword.yml.dist + * + * @param string $filename + * @return array + */ + public static function loadConfig($filename = null) + { + // Get config file + $configFile = null; + $configPath = __DIR__ . '/../../'; + if ($filename !== null) { + $files = array($filename); + } else { + $files = array("{$configPath}phpword.ini", "{$configPath}phpword.ini.dist"); + } + foreach ($files as $file) { + if (file_exists($file)) { + $configFile = realpath($file); + break; + } + } + + // Parse config file + $config = array(); + if ($configFile !== null) { + $config = @parse_ini_file($configFile); + if ($config === false) { + return $config; + } + } + + // Set config value + foreach ($config as $key => $value) { + $method = "set{$key}"; + if (method_exists(__CLASS__, $method)) { + self::$method($value); + } + } + + return $config; + } + + /** + * Return the compatibility option used by the XMLWriter + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public static function getCompatibility() + { + return self::hasCompatibility(); + } +} diff --git a/PhpOffice/PhpWord/Shared/AbstractEnum.php b/PhpOffice/PhpWord/Shared/AbstractEnum.php new file mode 100644 index 0000000..f2375d8 --- /dev/null +++ b/PhpOffice/PhpWord/Shared/AbstractEnum.php @@ -0,0 +1,75 @@ +getConstants(); + } + + return self::$constCacheArray[$calledClass]; + } + + /** + * Returns all values for this enum + * + * @return array + */ + public static function values() + { + return array_values(self::getConstants()); + } + + /** + * Returns true the value is valid for this enum + * + * @param string $value + * @return bool true if value is valid + */ + public static function isValid($value) + { + $values = array_values(self::getConstants()); + + return in_array($value, $values, true); + } + + /** + * Validates that the value passed is a valid value + * + * @param string $value + * @throws \InvalidArgumentException if the value passed is not valid for this enum + */ + public static function validate($value) + { + if (!self::isValid($value)) { + $calledClass = get_called_class(); + $values = array_values(self::getConstants()); + throw new \InvalidArgumentException("$value is not a valid value for $calledClass, possible values are " . implode(', ', $values)); + } + } +} diff --git a/PhpOffice/PhpWord/Shared/Converter.php b/PhpOffice/PhpWord/Shared/Converter.php new file mode 100644 index 0000000..39e8054 --- /dev/null +++ b/PhpOffice/PhpWord/Shared/Converter.php @@ -0,0 +1,426 @@ +. + * + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element Where the parts need to be added + * @param string $html The code to parse + * @param bool $fullHTML If it's a full HTML, no need to add 'body' tag + * @param bool $preserveWhiteSpace If false, the whitespaces between nodes will be removed + * @param array $options: + * + IMG_SRC_SEARCH: optional to speed up images loading from remote url when files can be found locally + * + IMG_SRC_REPLACE: optional to speed up images loading from remote url when files can be found locally + */ + public static function addHtml($element, $html, $fullHTML = false, $preserveWhiteSpace = true, $options = null) + { + /* + * @todo parse $stylesheet for default styles. Should result in an array based on id, class and element, + * which could be applied when such an element occurs in the parseNode function. + */ + self::$options = $options; + + // Preprocess: remove all line ends, decode HTML entity, + // fix ampersand and angle brackets and add body tag for HTML fragments + $html = str_replace(array("\n", "\r"), '', $html); + $html = str_replace(array('<', '>', '&'), array('_lt_', '_gt_', '_amp_'), $html); + $html = html_entity_decode($html, ENT_QUOTES, 'UTF-8'); + $html = str_replace('&', '&', $html); + $html = str_replace(array('_lt_', '_gt_', '_amp_'), array('<', '>', '&'), $html); + + if (false === $fullHTML) { + $html = '' . $html . ''; + } + + // Load DOM + $orignalLibEntityLoader = libxml_disable_entity_loader(true); + $dom = new \DOMDocument(); + $dom->preserveWhiteSpace = $preserveWhiteSpace; + $dom->loadXML($html); + self::$xpath = new \DOMXPath($dom); + $node = $dom->getElementsByTagName('body'); + + self::parseNode($node->item(0), $element); + libxml_disable_entity_loader($orignalLibEntityLoader); + } + + /** + * parse Inline style of a node + * + * @param \DOMNode $node Node to check on attributes and to compile a style array + * @param array $styles is supplied, the inline style attributes are added to the already existing style + * @return array + */ + protected static function parseInlineStyle($node, $styles = array()) + { + if (XML_ELEMENT_NODE == $node->nodeType) { + $attributes = $node->attributes; // get all the attributes(eg: id, class) + + foreach ($attributes as $attribute) { + switch ($attribute->name) { + case 'style': + $styles = self::parseStyle($attribute, $styles); + break; + case 'align': + $styles['alignment'] = self::mapAlign($attribute->value); + break; + case 'lang': + $styles['lang'] = $attribute->value; + break; + } + } + } + + return $styles; + } + + /** + * Parse a node and add a corresponding element to the parent element. + * + * @param \DOMNode $node node to parse + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element object to add an element corresponding with the node + * @param array $styles Array with all styles + * @param array $data Array to transport data to a next level in the DOM tree, for example level of listitems + */ + protected static function parseNode($node, $element, $styles = array(), $data = array()) + { + // Populate styles array + $styleTypes = array('font', 'paragraph', 'list', 'table', 'row', 'cell'); + foreach ($styleTypes as $styleType) { + if (!isset($styles[$styleType])) { + $styles[$styleType] = array(); + } + } + + // Node mapping table + $nodes = array( + // $method $node $element $styles $data $argument1 $argument2 + 'p' => array('Paragraph', $node, $element, $styles, null, null, null), + 'h1' => array('Heading', null, $element, $styles, null, 'Heading1', null), + 'h2' => array('Heading', null, $element, $styles, null, 'Heading2', null), + 'h3' => array('Heading', null, $element, $styles, null, 'Heading3', null), + 'h4' => array('Heading', null, $element, $styles, null, 'Heading4', null), + 'h5' => array('Heading', null, $element, $styles, null, 'Heading5', null), + 'h6' => array('Heading', null, $element, $styles, null, 'Heading6', null), + '#text' => array('Text', $node, $element, $styles, null, null, null), + 'strong' => array('Property', null, null, $styles, null, 'bold', true), + 'b' => array('Property', null, null, $styles, null, 'bold', true), + 'em' => array('Property', null, null, $styles, null, 'italic', true), + 'i' => array('Property', null, null, $styles, null, 'italic', true), + 'u' => array('Property', null, null, $styles, null, 'underline', 'single'), + 'sup' => array('Property', null, null, $styles, null, 'superScript', true), + 'sub' => array('Property', null, null, $styles, null, 'subScript', true), + 'span' => array('Span', $node, null, $styles, null, null, null), + 'font' => array('Span', $node, null, $styles, null, null, null), + 'table' => array('Table', $node, $element, $styles, null, null, null), + 'tr' => array('Row', $node, $element, $styles, null, null, null), + 'td' => array('Cell', $node, $element, $styles, null, null, null), + 'th' => array('Cell', $node, $element, $styles, null, null, null), + 'ul' => array('List', $node, $element, $styles, $data, null, null), + 'ol' => array('List', $node, $element, $styles, $data, null, null), + 'li' => array('ListItem', $node, $element, $styles, $data, null, null), + 'img' => array('Image', $node, $element, $styles, null, null, null), + 'br' => array('LineBreak', null, $element, $styles, null, null, null), + 'a' => array('Link', $node, $element, $styles, null, null, null), + ); + + $newElement = null; + $keys = array('node', 'element', 'styles', 'data', 'argument1', 'argument2'); + + if (isset($nodes[$node->nodeName])) { + // Execute method based on node mapping table and return $newElement or null + // Arguments are passed by reference + $arguments = array(); + $args = array(); + list($method, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5]) = $nodes[$node->nodeName]; + for ($i = 0; $i <= 5; $i++) { + if ($args[$i] !== null) { + $arguments[$keys[$i]] = &$args[$i]; + } + } + $method = "parse{$method}"; + $newElement = call_user_func_array(array('PhpOffice\PhpWord\Shared\Html', $method), $arguments); + + // Retrieve back variables from arguments + foreach ($keys as $key) { + if (array_key_exists($key, $arguments)) { + $$key = $arguments[$key]; + } + } + } + + if ($newElement === null) { + $newElement = $element; + } + + static::parseChildNodes($node, $newElement, $styles, $data); + } + + /** + * Parse child nodes. + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array $styles + * @param array $data + */ + protected static function parseChildNodes($node, $element, $styles, $data) + { + if ('li' != $node->nodeName) { + $cNodes = $node->childNodes; + if (!empty($cNodes)) { + foreach ($cNodes as $cNode) { + if ($element instanceof AbstractContainer || $element instanceof Table || $element instanceof Row) { + self::parseNode($cNode, $element, $styles, $data); + } + } + } + } + } + + /** + * Parse paragraph node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array &$styles + * @return \PhpOffice\PhpWord\Element\TextRun + */ + protected static function parseParagraph($node, $element, &$styles) + { + $styles['paragraph'] = self::recursiveParseStylesInHierarchy($node, $styles['paragraph']); + $newElement = $element->addTextRun($styles['paragraph']); + + return $newElement; + } + + /** + * Parse heading node + * + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array &$styles + * @param string $argument1 Name of heading style + * @return \PhpOffice\PhpWord\Element\TextRun + * + * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that + * Heading1 - Heading6 are already defined somewhere + */ + protected static function parseHeading($element, &$styles, $argument1) + { + $styles['paragraph'] = $argument1; + $newElement = $element->addTextRun($styles['paragraph']); + + return $newElement; + } + + /** + * Parse text node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array &$styles + */ + protected static function parseText($node, $element, &$styles) + { + $styles['font'] = self::recursiveParseStylesInHierarchy($node, $styles['font']); + + //alignment applies on paragraph, not on font. Let's copy it there + if (isset($styles['font']['alignment']) && is_array($styles['paragraph'])) { + $styles['paragraph']['alignment'] = $styles['font']['alignment']; + } + + if (is_callable(array($element, 'addText'))) { + $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); + } + } + + /** + * Parse property node + * + * @param array &$styles + * @param string $argument1 Style name + * @param string $argument2 Style value + */ + protected static function parseProperty(&$styles, $argument1, $argument2) + { + $styles['font'][$argument1] = $argument2; + } + + /** + * Parse span node + * + * @param \DOMNode $node + * @param array &$styles + */ + protected static function parseSpan($node, &$styles) + { + self::parseInlineStyle($node, $styles['font']); + } + + /** + * Parse table node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array &$styles + * @return Table $element + * + * @todo As soon as TableItem, RowItem and CellItem support relative width and height + */ + protected static function parseTable($node, $element, &$styles) + { + $elementStyles = self::parseInlineStyle($node, $styles['table']); + + $newElement = $element->addTable($elementStyles); + + // $attributes = $node->attributes; + // if ($attributes->getNamedItem('width') !== null) { + // $newElement->setWidth($attributes->getNamedItem('width')->value); + // } + + // if ($attributes->getNamedItem('height') !== null) { + // $newElement->setHeight($attributes->getNamedItem('height')->value); + // } + // if ($attributes->getNamedItem('width') !== null) { + // $newElement=$element->addCell($width=$attributes->getNamedItem('width')->value); + // } + + return $newElement; + } + + /** + * Parse a table row + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\Table $element + * @param array &$styles + * @return Row $element + */ + protected static function parseRow($node, $element, &$styles) + { + $rowStyles = self::parseInlineStyle($node, $styles['row']); + if ($node->parentNode->nodeName == 'thead') { + $rowStyles['tblHeader'] = true; + } + + return $element->addRow(null, $rowStyles); + } + + /** + * Parse table cell + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\Table $element + * @param array &$styles + * @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element + */ + protected static function parseCell($node, $element, &$styles) + { + $cellStyles = self::recursiveParseStylesInHierarchy($node, $styles['cell']); + + $colspan = $node->getAttribute('colspan'); + if (!empty($colspan)) { + $cellStyles['gridSpan'] = $colspan - 0; + } + $cell = $element->addCell(null, $cellStyles); + + if (self::shouldAddTextRun($node)) { + return $cell->addTextRun(self::parseInlineStyle($node, $styles['paragraph'])); + } + + return $cell; + } + + /** + * Checks if $node contains an HTML element that cannot be added to TextRun + * + * @param \DOMNode $node + * @return bool Returns true if the node contains an HTML element that cannot be added to TextRun + */ + protected static function shouldAddTextRun(\DOMNode $node) + { + $containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol', $node)->length > 0; + if ($containsBlockElement) { + return false; + } + + return true; + } + + /** + * Recursively parses styles on parent nodes + * TODO if too slow, add caching of parent nodes, !! everything is static here so watch out for concurrency !! + * + * @param \DOMNode $node + * @param array &$styles + */ + protected static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style) + { + $parentStyle = self::parseInlineStyle($node, array()); + $style = array_merge($parentStyle, $style); + if ($node->parentNode != null && XML_ELEMENT_NODE == $node->parentNode->nodeType) { + $style = self::recursiveParseStylesInHierarchy($node->parentNode, $style); + } + + return $style; + } + + /** + * Parse list node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array &$styles + * @param array &$data + */ + protected static function parseList($node, $element, &$styles, &$data) + { + $isOrderedList = $node->nodeName === 'ol'; + if (isset($data['listdepth'])) { + $data['listdepth']++; + } else { + $data['listdepth'] = 0; + $styles['list'] = 'listStyle_' . self::$listIndex++; + $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList)); + } + if ($node->parentNode->nodeName === 'li') { + return $element->getParent(); + } + } + + /** + * @param bool $isOrderedList + * @return array + */ + protected static function getListStyle($isOrderedList) + { + if ($isOrderedList) { + return array( + 'type' => 'multilevel', + 'levels' => array( + array('format' => NumberFormat::DECIMAL, 'text' => '%1.', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360), + array('format' => NumberFormat::LOWER_LETTER, 'text' => '%2.', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360), + array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%3.', 'alignment' => 'right', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 180), + array('format' => NumberFormat::DECIMAL, 'text' => '%4.', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360), + array('format' => NumberFormat::LOWER_LETTER, 'text' => '%5.', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360), + array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%6.', 'alignment' => 'right', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 180), + array('format' => NumberFormat::DECIMAL, 'text' => '%7.', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360), + array('format' => NumberFormat::LOWER_LETTER, 'text' => '%8.', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360), + array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%9.', 'alignment' => 'right', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 180), + ), + ); + } + + return array( + 'type' => 'hybridMultilevel', + 'levels' => array( + array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'), + array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'), + array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'), + array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'), + array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'), + array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'), + array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'), + array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'), + array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'), + ), + ); + } + + /** + * Parse list item node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array &$styles + * @param array $data + * + * @todo This function is almost the same like `parseChildNodes`. Merged? + * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes + */ + protected static function parseListItem($node, $element, &$styles, $data) + { + $cNodes = $node->childNodes; + if (!empty($cNodes)) { + $listRun = $element->addListItemRun($data['listdepth'], $styles['list'], $styles['paragraph']); + foreach ($cNodes as $cNode) { + self::parseNode($cNode, $listRun, $styles, $data); + } + } + } + + /** + * Parse style + * + * @param \DOMAttr $attribute + * @param array $styles + * @return array + */ + protected static function parseStyle($attribute, $styles) + { + $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); + + foreach ($properties as $property) { + list($cKey, $cValue) = array_pad(explode(':', $property, 2), 2, null); + $cValue = trim($cValue); + switch (trim($cKey)) { + case 'text-decoration': + switch ($cValue) { + case 'underline': + $styles['underline'] = 'single'; + break; + case 'line-through': + $styles['strikethrough'] = true; + break; + } + break; + case 'text-align': + $styles['alignment'] = self::mapAlign($cValue); + break; + case 'display': + $styles['hidden'] = $cValue === 'none' || $cValue === 'hidden'; + break; + case 'direction': + $styles['rtl'] = $cValue === 'rtl'; + break; + case 'font-size': + $styles['size'] = Converter::cssToPoint($cValue); + break; + case 'font-family': + $cValue = array_map('trim', explode(',', $cValue)); + $styles['name'] = ucwords($cValue[0]); + break; + case 'color': + $styles['color'] = trim($cValue, '#'); + break; + case 'background-color': + $styles['bgColor'] = trim($cValue, '#'); + break; + case 'line-height': + $matches = array(); + if (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $cValue, $matches)) { + //matches number with a unit, e.g. 12px, 15pt, 20mm, ... + $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::EXACT; + $spacing = Converter::cssToTwip($matches[1]); + } elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) { + //matches percentages + $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; + //we are subtracting 1 line height because the Spacing writer is adding one line + $spacing = ((((int) $matches[1]) / 100) * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT; + } else { + //any other, wich is a multiplier. E.g. 1.2 + $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; + //we are subtracting 1 line height because the Spacing writer is adding one line + $spacing = ($cValue * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT; + } + $styles['spacingLineRule'] = $spacingLineRule; + $styles['line-spacing'] = $spacing; + break; + case 'letter-spacing': + $styles['letter-spacing'] = Converter::cssToTwip($cValue); + break; + case 'text-indent': + $styles['indentation']['firstLine'] = Converter::cssToTwip($cValue); + break; + case 'font-weight': + $tValue = false; + if (preg_match('#bold#', $cValue)) { + $tValue = true; // also match bolder + } + $styles['bold'] = $tValue; + break; + case 'font-style': + $tValue = false; + if (preg_match('#(?:italic|oblique)#', $cValue)) { + $tValue = true; + } + $styles['italic'] = $tValue; + break; + case 'margin-top': + $styles['spaceBefore'] = Converter::cssToPoint($cValue); + break; + case 'margin-bottom': + $styles['spaceAfter'] = Converter::cssToPoint($cValue); + break; + case 'border-color': + self::mapBorderColor($styles, $cValue); + break; + case 'border-width': + $styles['borderSize'] = Converter::cssToPoint($cValue); + break; + case 'border-style': + $styles['borderStyle'] = self::mapBorderStyle($cValue); + break; + case 'width': + if (preg_match('/([0-9]+[a-z]+)/', $cValue, $matches)) { + $styles['width'] = Converter::cssToTwip($matches[1]); + $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP; + } elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) { + $styles['width'] = $matches[1] * 50; + $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT; + } elseif (preg_match('/([0-9]+)/', $cValue, $matches)) { + $styles['width'] = $matches[1]; + $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::AUTO; + } + break; + case 'border': + if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+)\s+([a-z]+)/', $cValue, $matches)) { + $styles['borderSize'] = Converter::cssToPoint($matches[1]); + $styles['borderColor'] = trim($matches[2], '#'); + $styles['borderStyle'] = self::mapBorderStyle($matches[3]); + } + break; + } + } + + return $styles; + } + + /** + * Parse image node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * + * @return \PhpOffice\PhpWord\Element\Image + **/ + protected static function parseImage($node, $element) + { + $style = array(); + $src = null; + foreach ($node->attributes as $attribute) { + switch ($attribute->name) { + case 'src': + $src = $attribute->value; + break; + case 'width': + $width = $attribute->value; + $style['width'] = $width; + $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; + break; + case 'height': + $height = $attribute->value; + $style['height'] = $height; + $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; + break; + case 'style': + $styleattr = explode(';', $attribute->value); + foreach ($styleattr as $attr) { + if (strpos($attr, ':')) { + list($k, $v) = explode(':', $attr); + switch ($k) { + case 'float': + if (trim($v) == 'right') { + $style['hPos'] = \PhpOffice\PhpWord\Style\Image::POS_RIGHT; + $style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_PAGE; + $style['pos'] = \PhpOffice\PhpWord\Style\Image::POS_RELATIVE; + $style['wrap'] = \PhpOffice\PhpWord\Style\Image::WRAP_TIGHT; + $style['overlap'] = true; + } + if (trim($v) == 'left') { + $style['hPos'] = \PhpOffice\PhpWord\Style\Image::POS_LEFT; + $style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_PAGE; + $style['pos'] = \PhpOffice\PhpWord\Style\Image::POS_RELATIVE; + $style['wrap'] = \PhpOffice\PhpWord\Style\Image::WRAP_TIGHT; + $style['overlap'] = true; + } + break; + } + } + } + break; + } + } + $originSrc = $src; + if (strpos($src, 'data:image') !== false) { + $tmpDir = Settings::getTempDir() . '/'; + + $match = array(); + preg_match('/data:image\/(\w+);base64,(.+)/', $src, $match); + + $src = $imgFile = $tmpDir . uniqid() . '.' . $match[1]; + + $ifp = fopen($imgFile, 'wb'); + + if ($ifp !== false) { + fwrite($ifp, base64_decode($match[2])); + fclose($ifp); + } + } + $src = urldecode($src); + + if (!is_file($src) + && !is_null(self::$options) + && isset(self::$options['IMG_SRC_SEARCH']) + && isset(self::$options['IMG_SRC_REPLACE'])) { + $src = str_replace(self::$options['IMG_SRC_SEARCH'], self::$options['IMG_SRC_REPLACE'], $src); + } + + if (!is_file($src)) { + if ($imgBlob = @file_get_contents($src)) { + $tmpDir = Settings::getTempDir() . '/'; + $match = array(); + preg_match('/.+\.(\w+)$/', $src, $match); + $src = $tmpDir . uniqid() . '.' . $match[1]; + + $ifp = fopen($src, 'wb'); + + if ($ifp !== false) { + fwrite($ifp, $imgBlob); + fclose($ifp); + } + } + } + + if (is_file($src)) { + $newElement = $element->addImage($src, $style); + } else { + throw new \Exception("Could not load image $originSrc"); + } + + return $newElement; + } + + /** + * Transforms a CSS border style into a word border style + * + * @param string $cssBorderStyle + * @return null|string + */ + protected static function mapBorderStyle($cssBorderStyle) + { + switch ($cssBorderStyle) { + case 'none': + case 'dashed': + case 'dotted': + case 'double': + return $cssBorderStyle; + default: + return 'single'; + } + } + + protected static function mapBorderColor(&$styles, $cssBorderColor) + { + $numColors = substr_count($cssBorderColor, '#'); + if ($numColors === 1) { + $styles['borderColor'] = trim($cssBorderColor, '#'); + } elseif ($numColors > 1) { + $colors = explode(' ', $cssBorderColor); + $borders = array('borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'); + for ($i = 0; $i < min(4, $numColors, count($colors)); $i++) { + $styles[$borders[$i]] = trim($colors[$i], '#'); + } + } + } + + /** + * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc + * + * @param string $cssAlignment + * @return string|null + */ + protected static function mapAlign($cssAlignment) + { + switch ($cssAlignment) { + case 'right': + return Jc::END; + case 'center': + return Jc::CENTER; + case 'justify': + return Jc::BOTH; + default: + return Jc::START; + } + } + + /** + * Parse line break + * + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + */ + protected static function parseLineBreak($element) + { + $element->addTextBreak(); + } + + /** + * Parse link node + * + * @param \DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array $styles + */ + protected static function parseLink($node, $element, &$styles) + { + $target = null; + foreach ($node->attributes as $attribute) { + switch ($attribute->name) { + case 'href': + $target = $attribute->value; + break; + } + } + $styles['font'] = self::parseInlineStyle($node, $styles['font']); + + if (strpos($target, '#') === 0) { + return $element->addLink(substr($target, 1), $node->textContent, $styles['font'], $styles['paragraph'], true); + } + + return $element->addLink($target, $node->textContent, $styles['font'], $styles['paragraph']); + } +} diff --git a/PhpOffice/PhpWord/Shared/OLERead.php b/PhpOffice/PhpWord/Shared/OLERead.php new file mode 100644 index 0000000..2e6a899 --- /dev/null +++ b/PhpOffice/PhpWord/Shared/OLERead.php @@ -0,0 +1,323 @@ +data = file_get_contents($sFileName, false, null, 0, 8); + + // Check OLE identifier + if ($this->data != self::IDENTIFIER_OLE) { + throw new Exception('The filename ' . $sFileName . ' is not recognised as an OLE file'); + } + + // Get the file data + $this->data = file_get_contents($sFileName); + + // Total number of sectors used for the SAT + $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); + + // SecID of the first sector of the directory stream + $this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS); + + // SecID of the first sector of the SSAT (or -2 if not extant) + $this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS); + + // SecID of the first sector of the MSAT (or -2 if no additional sectors are used) + $this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS); + + // Total number of sectors used by MSAT + $this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS); + + $bigBlockDepotBlocks = array(); + $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS; + + $bbdBlocks = $this->numBigBlockDepotBlocks; + + // @codeCoverageIgnoreStart + if ($this->numExtensionBlocks != 0) { + $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4; + } + // @codeCoverageIgnoreEnd + + for ($i = 0; $i < $bbdBlocks; ++$i) { + $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); + $pos += 4; + } + + // @codeCoverageIgnoreStart + for ($j = 0; $j < $this->numExtensionBlocks; ++$j) { + $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE; + $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1); + + for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) { + $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); + $pos += 4; + } + + $bbdBlocks += $blocksToRead; + if ($bbdBlocks < $this->numBigBlockDepotBlocks) { + $this->extensionBlock = self::getInt4d($this->data, $pos); + } + } + // @codeCoverageIgnoreEnd + + $pos = 0; + $this->bigBlockChain = ''; + $bbs = self::BIG_BLOCK_SIZE / 4; + for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) { + $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE; + + $this->bigBlockChain .= substr($this->data, $pos, 4*$bbs); + $pos += 4*$bbs; + } + + $pos = 0; + $sbdBlock = $this->sbdStartBlock; + $this->smallBlockChain = ''; + while ($sbdBlock != -2) { + $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE; + + $this->smallBlockChain .= substr($this->data, $pos, 4*$bbs); + $pos += 4*$bbs; + + $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock*4); + } + + // read the directory stream + $block = $this->rootStartBlock; + $this->entry = $this->readData($block); + + $this->readPropertySets(); + } + + /** + * Extract binary stream data + * + * @return string + */ + public function getStream($stream) + { + if ($stream === null) { + return null; + } + + $streamData = ''; + + if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) { + $rootdata = $this->readData($this->props[$this->rootentry]['startBlock']); + + $block = $this->props[$stream]['startBlock']; + + while ($block != -2) { + $pos = $block * self::SMALL_BLOCK_SIZE; + $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE); + + $block = self::getInt4d($this->smallBlockChain, $block*4); + } + + return $streamData; + } + + $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE; + if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) { + ++$numBlocks; + } + + if ($numBlocks == 0) { + return '';// @codeCoverageIgnore + } + + $block = $this->props[$stream]['startBlock']; + + while ($block != -2) { + $pos = ($block + 1) * self::BIG_BLOCK_SIZE; + $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); + $block = self::getInt4d($this->bigBlockChain, $block*4); + } + + return $streamData; + } + + /** + * Read a standard stream (by joining sectors using information from SAT) + * + * @param int $blSectorId Sector ID where the stream starts + * @return string Data for standard stream + */ + private function readData($blSectorId) + { + $block = $blSectorId; + $data = ''; + + while ($block != -2) { + $pos = ($block + 1) * self::BIG_BLOCK_SIZE; + $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); + $block = self::getInt4d($this->bigBlockChain, $block*4); + } + return $data; + } + + /** + * Read entries in the directory stream. + */ + private function readPropertySets() + { + $offset = 0; + + // loop through entires, each entry is 128 bytes + $entryLen = strlen($this->entry); + while ($offset < $entryLen) { + // entry data (128 bytes) + $data = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE); + + // size in bytes of name + $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS+1]) << 8); + + // type of entry + $type = ord($data[self::TYPE_POS]); + + // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook) + // sectorID of first sector of the short-stream container stream, if this entry is root entry + $startBlock = self::getInt4d($data, self::START_BLOCK_POS); + + $size = self::getInt4d($data, self::SIZE_POS); + + $name = str_replace("\x00", "", substr($data, 0, $nameSize)); + + + $this->props[] = array ( + 'name' => $name, + 'type' => $type, + 'startBlock' => $startBlock, + 'size' => $size); + + // tmp helper to simplify checks + $upName = strtoupper($name); + + // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook) + // print_r($upName.PHP_EOL); + if (($upName === 'WORDDOCUMENT')) { + $this->wrkdocument = count($this->props) - 1; + } elseif ($upName === '1TABLE') { + $this->wrk1Table = count($this->props) - 1; + } elseif ($upName === 'DATA') { + $this->wrkData = count($this->props) - 1; + } elseif ($upName === 'OBJECTPOOL') { + $this->wrkObjectPoolelseif = count($this->props) - 1; + } elseif ($upName === 'ROOT ENTRY' || $upName === 'R') { + $this->rootentry = count($this->props) - 1; + } + + // Summary information + if ($name == chr(5) . 'SummaryInformation') { + $this->summaryInformation = count($this->props) - 1; + } + + // Additional Document Summary information + if ($name == chr(5) . 'DocumentSummaryInformation') { + $this->docSummaryInfos = count($this->props) - 1; + } + + $offset += self::PROPERTY_STORAGE_BLOCK_SIZE; + } + } + + /** + * Read 4 bytes of data at specified position + * + * @param string $data + * @param int $pos + * @return int + */ + private static function getInt4d($data, $pos) + { + // FIX: represent numbers correctly on 64-bit system + // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 + // Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems + $or24 = ord($data[$pos + 3]); + if ($or24 >= 128) { + // negative number + $ord24 = -abs((256 - $or24) << 24); + } else { + $ord24 = ($or24 & 127) << 24; + } + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24; + } +} diff --git a/PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php b/PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php new file mode 100644 index 0000000..3fbc932 --- /dev/null +++ b/PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php @@ -0,0 +1,5415 @@ +zipname = $p_zipname; + $this->zip_fd = 0; + $this->magic_quotes_status = -1; + + // ----- Return + return; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // create($p_filelist, $p_add_dir="", $p_remove_dir="") + // create($p_filelist, $p_option, $p_option_value, ...) + // Description : + // This method supports two different synopsis. The first one is historical. + // This method creates a Zip Archive. The Zip file is created in the + // filesystem. The files and directories indicated in $p_filelist + // are added in the archive. See the parameters description for the + // supported format of $p_filelist. + // When a directory is in the list, the directory and its content is added + // in the archive. + // In this synopsis, the function takes an optional variable list of + // options. See bellow the supported options. + // Parameters : + // $p_filelist : An array containing file or directory names, or + // a string containing one filename or one directory name, or + // a string containing a list of filenames and/or directory + // names separated by spaces. + // $p_add_dir : A path to add before the real path of the archived file, + // in order to have it memorized in the archive. + // $p_remove_dir : A path to remove from the real path of the file to archive, + // in order to have a shorter path memorized in the archive. + // When $p_add_dir and $p_remove_dir are set, $p_remove_dir + // is removed first, before $p_add_dir is added. + // Options : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_COMMENT : + // PCLZIP_CB_PRE_ADD : + // PCLZIP_CB_POST_ADD : + // Return Values : + // 0 on failure, + // The list of the added files, with a status of the add action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + public function create($p_filelist) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Set default values + $v_options = array(); + $v_options[PCLZIP_OPT_NO_COMPRESSION] = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove from the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_ADD => 'optional', + PCLZIP_CB_POST_ADD => 'optional', + PCLZIP_OPT_NO_COMPRESSION => 'optional', + PCLZIP_OPT_COMMENT => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + //, PCLZIP_OPT_CRYPT => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + } else { + + // ----- Get the first argument + $v_options[PCLZIP_OPT_ADD_PATH] = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1]; + } elseif ($v_size > 2) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + return 0; + } + } + } + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Init + $v_string_list = array(); + $v_att_list = array(); + $v_filedescr_list = array(); + $p_result_list = array(); + + // ----- Look if the $p_filelist is really an array + if (is_array($p_filelist)) { + + // ----- Look if the first element is also an array + // This will mean that this is a file description entry + if (isset($p_filelist[0]) && is_array($p_filelist[0])) { + $v_att_list = $p_filelist; + + // ----- The list is a list of string names + } else { + $v_string_list = $p_filelist; + } + + // ----- Look if the $p_filelist is a string + } elseif (is_string($p_filelist)) { + // ----- Create a list from the string + $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist); + + // ----- Invalid variable type for $p_filelist + } else { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_filelist"); + + return 0; + } + + // ----- Reformat the string list + if (sizeof($v_string_list) != 0) { + foreach ($v_string_list as $v_string) { + if ($v_string != '') { + $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string; + } else { + } + } + } + + // ----- For each file in the list check the attributes + $v_supported_attributes = array( + PCLZIP_ATT_FILE_NAME => 'mandatory', + PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional', + PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional', + PCLZIP_ATT_FILE_MTIME => 'optional', + PCLZIP_ATT_FILE_CONTENT => 'optional', + PCLZIP_ATT_FILE_COMMENT => 'optional' + ); + foreach ($v_att_list as $v_entry) { + $v_result = $this->privFileDescrParseAtt($v_entry, $v_filedescr_list[], $v_options, $v_supported_attributes); + if ($v_result != 1) { + return 0; + } + } + + // ----- Expand the filelist (expand directories) + $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options); + if ($v_result != 1) { + return 0; + } + + // ----- Call the create fct + $v_result = $this->privCreate($v_filedescr_list, $p_result_list, $v_options); + if ($v_result != 1) { + return 0; + } + + // ----- Return + return $p_result_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // add($p_filelist, $p_add_dir="", $p_remove_dir="") + // add($p_filelist, $p_option, $p_option_value, ...) + // Description : + // This method supports two synopsis. The first one is historical. + // This methods add the list of files in an existing archive. + // If a file with the same name already exists, it is added at the end of the + // archive, the first one is still present. + // If the archive does not exist, it is created. + // Parameters : + // $p_filelist : An array containing file or directory names, or + // a string containing one filename or one directory name, or + // a string containing a list of filenames and/or directory + // names separated by spaces. + // $p_add_dir : A path to add before the real path of the archived file, + // in order to have it memorized in the archive. + // $p_remove_dir : A path to remove from the real path of the file to archive, + // in order to have a shorter path memorized in the archive. + // When $p_add_dir and $p_remove_dir are set, $p_remove_dir + // is removed first, before $p_add_dir is added. + // Options : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_COMMENT : + // PCLZIP_OPT_ADD_COMMENT : + // PCLZIP_OPT_PREPEND_COMMENT : + // PCLZIP_CB_PRE_ADD : + // PCLZIP_CB_POST_ADD : + // Return Values : + // 0 on failure, + // The list of the added files, with a status of the add action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + public function add($p_filelist) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Set default values + $v_options = array(); + $v_options[PCLZIP_OPT_NO_COMPRESSION] = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove form the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_ADD => 'optional', + PCLZIP_CB_POST_ADD => 'optional', + PCLZIP_OPT_NO_COMPRESSION => 'optional', + PCLZIP_OPT_COMMENT => 'optional', + PCLZIP_OPT_ADD_COMMENT => 'optional', + PCLZIP_OPT_PREPEND_COMMENT => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + //, PCLZIP_OPT_CRYPT => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + } else { + + // ----- Get the first argument + $v_options[PCLZIP_OPT_ADD_PATH] = $v_add_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1]; + } elseif ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } + } + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Init + $v_string_list = array(); + $v_att_list = array(); + $v_filedescr_list = array(); + $p_result_list = array(); + + // ----- Look if the $p_filelist is really an array + if (is_array($p_filelist)) { + + // ----- Look if the first element is also an array + // This will mean that this is a file description entry + if (isset($p_filelist[0]) && is_array($p_filelist[0])) { + $v_att_list = $p_filelist; + + // ----- The list is a list of string names + } else { + $v_string_list = $p_filelist; + } + + // ----- Look if the $p_filelist is a string + } elseif (is_string($p_filelist)) { + // ----- Create a list from the string + $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist); + + // ----- Invalid variable type for $p_filelist + } else { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type '" . gettype($p_filelist) . "' for p_filelist"); + + return 0; + } + + // ----- Reformat the string list + if (sizeof($v_string_list) != 0) { + foreach ($v_string_list as $v_string) { + $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string; + } + } + + // ----- For each file in the list check the attributes + $v_supported_attributes = array( + PCLZIP_ATT_FILE_NAME => 'mandatory', + PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional', + PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional', + PCLZIP_ATT_FILE_MTIME => 'optional', + PCLZIP_ATT_FILE_CONTENT => 'optional', + PCLZIP_ATT_FILE_COMMENT => 'optional' + ); + foreach ($v_att_list as $v_entry) { + $v_result = $this->privFileDescrParseAtt($v_entry, $v_filedescr_list[], $v_options, $v_supported_attributes); + if ($v_result != 1) { + return 0; + } + } + + // ----- Expand the filelist (expand directories) + $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options); + if ($v_result != 1) { + return 0; + } + + // ----- Call the create fct + $v_result = $this->privAdd($v_filedescr_list, $p_result_list, $v_options); + if ($v_result != 1) { + return 0; + } + + // ----- Return + return $p_result_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : listContent() + // Description : + // This public method, gives the list of the files and directories, with their + // properties. + // The properties of each entries in the list are (used also in other functions) : + // filename : Name of the file. For a create or add action it is the filename + // given by the user. For an extract function it is the filename + // of the extracted file. + // stored_filename : Name of the file / directory stored in the archive. + // size : Size of the stored file. + // compressed_size : Size of the file's data compressed in the archive + // (without the headers overhead) + // mtime : Last known modification date of the file (UNIX timestamp) + // comment : Comment associated with the file + // folder : true | false + // index : index of the file in the archive + // status : status of the action (depending of the action) : + // Values are : + // ok : OK ! + // filtered : the file / dir is not extracted (filtered by user) + // already_a_directory : the file can not be extracted because a + // directory with the same name already exists + // write_protected : the file can not be extracted because a file + // with the same name already exists and is + // write protected + // newer_exist : the file was not extracted because a newer file exists + // path_creation_fail : the file is not extracted because the folder + // does not exist and can not be created + // write_error : the file was not extracted because there was a + // error while writing the file + // read_error : the file was not extracted because there was a error + // while reading the file + // invalid_header : the file was not extracted because of an archive + // format error (bad file header) + // Note that each time a method can continue operating when there + // is an action error on a file, the error is only logged in the file status. + // Return Values : + // 0 on an unrecoverable failure, + // The list of the files in the archive. + // -------------------------------------------------------------------------------- + public function listContent() + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); + } + + // ----- Call the extracting fct + $p_list = array(); + if (($v_result = $this->privList($p_list)) != 1) { + unset($p_list); + + return (0); + } + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // extract($p_path="./", $p_remove_path="") + // extract([$p_option, $p_option_value, ...]) + // Description : + // This method supports two synopsis. The first one is historical. + // This method extract all the files / directories from the archive to the + // folder indicated in $p_path. + // If you want to ignore the 'root' part of path of the memorized files + // you can indicate this in the optional $p_remove_path parameter. + // By default, if a newer file with the same name already exists, the + // file is not extracted. + // + // If both PCLZIP_OPT_PATH and PCLZIP_OPT_ADD_PATH aoptions + // are used, the path indicated in PCLZIP_OPT_ADD_PATH is append + // at the end of the path value of PCLZIP_OPT_PATH. + // Parameters : + // $p_path : Path where the files and directories are to be extracted + // $p_remove_path : First part ('root' part) of the memorized path + // (if any similar) to remove while extracting. + // Options : + // PCLZIP_OPT_PATH : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_CB_PRE_EXTRACT : + // PCLZIP_CB_POST_EXTRACT : + // Return Values : + // 0 or a negative value on failure, + // The list of the extracted files, with a status of the action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + public function extract() + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); + } + + // ----- Set default values + $v_options = array(); + // $v_path = "./"; + $v_path = ''; + $v_remove_path = ""; + $v_remove_all_path = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Default values for option + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = false; + + // ----- Look for arguments + if ($v_size > 0) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_PATH => 'optional', + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_EXTRACT => 'optional', + PCLZIP_CB_POST_EXTRACT => 'optional', + PCLZIP_OPT_SET_CHMOD => 'optional', + PCLZIP_OPT_BY_NAME => 'optional', + PCLZIP_OPT_BY_EREG => 'optional', + PCLZIP_OPT_BY_PREG => 'optional', + PCLZIP_OPT_BY_INDEX => 'optional', + PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', + PCLZIP_OPT_EXTRACT_IN_OUTPUT => 'optional', + PCLZIP_OPT_REPLACE_NEWER => 'optional', + PCLZIP_OPT_STOP_ON_ERROR => 'optional', + PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Set the arguments + if (isset($v_options[PCLZIP_OPT_PATH])) { + $v_path = $v_options[PCLZIP_OPT_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) { + $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } + if (isset($v_options[PCLZIP_OPT_ADD_PATH])) { + // ----- Check for '/' in last path char + if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) { + $v_path .= '/'; + } + $v_path .= $v_options[PCLZIP_OPT_ADD_PATH]; + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + } else { + + // ----- Get the first argument + $v_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_remove_path = $v_arg_list[1]; + } elseif ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } + } + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Trace + + // ----- Call the extracting fct + $p_list = array(); + $v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options); + if ($v_result < 1) { + unset($p_list); + + return (0); + } + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + + // -------------------------------------------------------------------------------- + // Function : + // extractByIndex($p_index, $p_path="./", $p_remove_path="") + // extractByIndex($p_index, [$p_option, $p_option_value, ...]) + // Description : + // This method supports two synopsis. The first one is historical. + // This method is doing a partial extract of the archive. + // The extracted files or folders are identified by their index in the + // archive (from 0 to n). + // Note that if the index identify a folder, only the folder entry is + // extracted, not all the files included in the archive. + // Parameters : + // $p_index : A single index (integer) or a string of indexes of files to + // extract. The form of the string is "0,4-6,8-12" with only numbers + // and '-' for range or ',' to separate ranges. No spaces or ';' + // are allowed. + // $p_path : Path where the files and directories are to be extracted + // $p_remove_path : First part ('root' part) of the memorized path + // (if any similar) to remove while extracting. + // Options : + // PCLZIP_OPT_PATH : + // PCLZIP_OPT_ADD_PATH : + // PCLZIP_OPT_REMOVE_PATH : + // PCLZIP_OPT_REMOVE_ALL_PATH : + // PCLZIP_OPT_EXTRACT_AS_STRING : The files are extracted as strings and + // not as files. + // The resulting content is in a new field 'content' in the file + // structure. + // This option must be used alone (any other options are ignored). + // PCLZIP_CB_PRE_EXTRACT : + // PCLZIP_CB_POST_EXTRACT : + // Return Values : + // 0 on failure, + // The list of the extracted files, with a status of the action. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + //function extractByIndex($p_index, options...) + public function extractByIndex($p_index) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); + } + + // ----- Set default values + $v_options = array(); + // $v_path = "./"; + $v_path = ''; + $v_remove_path = ""; + $v_remove_all_path = false; + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Default values for option + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = false; + + // ----- Look for arguments + if ($v_size > 1) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Remove form the options list the first argument + array_shift($v_arg_list); + $v_size--; + + // ----- Look for first arg + if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) { + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_PATH => 'optional', + PCLZIP_OPT_REMOVE_PATH => 'optional', + PCLZIP_OPT_REMOVE_ALL_PATH => 'optional', + PCLZIP_OPT_EXTRACT_AS_STRING => 'optional', + PCLZIP_OPT_ADD_PATH => 'optional', + PCLZIP_CB_PRE_EXTRACT => 'optional', + PCLZIP_CB_POST_EXTRACT => 'optional', + PCLZIP_OPT_SET_CHMOD => 'optional', + PCLZIP_OPT_REPLACE_NEWER => 'optional', + PCLZIP_OPT_STOP_ON_ERROR => 'optional', + PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional', + PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional', + PCLZIP_OPT_TEMP_FILE_ON => 'optional', + PCLZIP_OPT_TEMP_FILE_OFF => 'optional' + )); + if ($v_result != 1) { + return 0; + } + + // ----- Set the arguments + if (isset($v_options[PCLZIP_OPT_PATH])) { + $v_path = $v_options[PCLZIP_OPT_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) { + $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH]; + } + if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } + if (isset($v_options[PCLZIP_OPT_ADD_PATH])) { + // ----- Check for '/' in last path char + if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) { + $v_path .= '/'; + } + $v_path .= $v_options[PCLZIP_OPT_ADD_PATH]; + } + if (!isset($v_options[PCLZIP_OPT_EXTRACT_AS_STRING])) { + $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = false; + } else { + } + + // ----- Look for 2 args + // Here we need to support the first historic synopsis of the + // method. + } else { + + // ----- Get the first argument + $v_path = $v_arg_list[0]; + + // ----- Look for the optional second argument + if ($v_size == 2) { + $v_remove_path = $v_arg_list[1]; + } elseif ($v_size > 2) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments"); + + // ----- Return + return 0; + } + } + } + + // ----- Trace + + // ----- Trick + // Here I want to reuse extractByRule(), so I need to parse the $p_index + // with privParseOptions() + $v_arg_trick = array( + PCLZIP_OPT_BY_INDEX, + $p_index + ); + $v_options_trick = array(); + $v_result = $this->privParseOptions($v_arg_trick, sizeof($v_arg_trick), $v_options_trick, array( + PCLZIP_OPT_BY_INDEX => 'optional' + )); + if ($v_result != 1) { + return 0; + } + $v_options[PCLZIP_OPT_BY_INDEX] = $v_options_trick[PCLZIP_OPT_BY_INDEX]; + + // ----- Look for default option values + $this->privOptionDefaultThreshold($v_options); + + // ----- Call the extracting fct + if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) < 1) { + return (0); + } + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : + // delete([$p_option, $p_option_value, ...]) + // Description : + // This method removes files from the archive. + // If no parameters are given, then all the archive is emptied. + // Parameters : + // None or optional arguments. + // Options : + // PCLZIP_OPT_BY_INDEX : + // PCLZIP_OPT_BY_NAME : + // PCLZIP_OPT_BY_EREG : + // PCLZIP_OPT_BY_PREG : + // Return Values : + // 0 on failure, + // The list of the files which are still present in the archive. + // (see PclZip::listContent() for list entry format) + // -------------------------------------------------------------------------------- + public function delete() + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); + } + + // ----- Set default values + $v_options = array(); + + // ----- Look for variable options arguments + $v_size = func_num_args(); + + // ----- Look for arguments + if ($v_size > 0) { + // ----- Get the arguments + $v_arg_list = func_get_args(); + + // ----- Parse the options + $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options, array( + PCLZIP_OPT_BY_NAME => 'optional', + PCLZIP_OPT_BY_EREG => 'optional', + PCLZIP_OPT_BY_PREG => 'optional', + PCLZIP_OPT_BY_INDEX => 'optional' + )); + if ($v_result != 1) { + return 0; + } + } + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Call the delete fct + $v_list = array(); + if (($v_result = $this->privDeleteByRule($v_list, $v_options)) != 1) { + $this->privSwapBackMagicQuotes(); + unset($v_list); + + return (0); + } + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : deleteByIndex() + // Description : + // ***** Deprecated ***** + // delete(PCLZIP_OPT_BY_INDEX, $p_index) should be prefered. + // -------------------------------------------------------------------------------- + public function deleteByIndex($p_index) + { + + $p_list = $this->delete(PCLZIP_OPT_BY_INDEX, $p_index); + + // ----- Return + return $p_list; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : properties() + // Description : + // This method gives the properties of the archive. + // The properties are : + // nb : Number of files in the archive + // comment : Comment associated with the archive file + // status : not_exist, ok + // Parameters : + // None + // Return Values : + // 0 on failure, + // An array with the archive properties. + // -------------------------------------------------------------------------------- + public function properties() + { + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + $this->privSwapBackMagicQuotes(); + + return (0); + } + + // ----- Default properties + $v_prop = array(); + $v_prop['comment'] = ''; + $v_prop['nb'] = 0; + $v_prop['status'] = 'not_exist'; + + // ----- Look if file exists + if (@is_file($this->zipname)) { + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) { + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in binary read mode'); + + // ----- Return + return 0; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privSwapBackMagicQuotes(); + + return 0; + } + + // ----- Close the zip file + $this->privCloseFd(); + + // ----- Set the user attributes + $v_prop['comment'] = $v_central_dir['comment']; + $v_prop['nb'] = $v_central_dir['entries']; + $v_prop['status'] = 'ok'; + } + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_prop; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : duplicate() + // Description : + // This method creates an archive by copying the content of an other one. If + // the archive already exist, it is replaced by the new one without any warning. + // Parameters : + // $p_archive : The filename of a valid archive, or + // a valid PclZip object. + // Return Values : + // 1 on success. + // 0 or a negative value on error (error code). + // -------------------------------------------------------------------------------- + public function duplicate($p_archive) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Look if the $p_archive is a PclZip object + if ((is_object($p_archive)) && (get_class($p_archive) == 'pclzip')) { + + // ----- Duplicate the archive + $v_result = $this->privDuplicate($p_archive->zipname); + + // ----- Look if the $p_archive is a string (so a filename) + } elseif (is_string($p_archive)) { + + // ----- Check that $p_archive is a valid zip file + // TBC : Should also check the archive format + if (!is_file($p_archive)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "No file with filename '" . $p_archive . "'"); + $v_result = PCLZIP_ERR_MISSING_FILE; + } else { + // ----- Duplicate the archive + $v_result = $this->privDuplicate($p_archive); + } + + // ----- Invalid variable + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add"); + $v_result = PCLZIP_ERR_INVALID_PARAMETER; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : merge() + // Description : + // This method merge the $p_archive_to_add archive at the end of the current + // one ($this). + // If the archive ($this) does not exist, the merge becomes a duplicate. + // If the $p_archive_to_add archive does not exist, the merge is a success. + // Parameters : + // $p_archive_to_add : It can be directly the filename of a valid zip archive, + // or a PclZip object archive. + // Return Values : + // 1 on success, + // 0 or negative values on error (see below). + // -------------------------------------------------------------------------------- + public function merge($p_archive_to_add) + { + $v_result = 1; + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Check archive + if (!$this->privCheckFormat()) { + return (0); + } + + // ----- Look if the $p_archive_to_add is a PclZip object + if ((is_object($p_archive_to_add)) && (get_class($p_archive_to_add) == 'pclzip')) { + + // ----- Merge the archive + $v_result = $this->privMerge($p_archive_to_add); + + // ----- Look if the $p_archive_to_add is a string (so a filename) + } elseif (is_string($p_archive_to_add)) { + + // ----- Create a temporary archive + $v_object_archive = new PclZip($p_archive_to_add); + + // ----- Merge the archive + $v_result = $this->privMerge($v_object_archive); + + // ----- Invalid variable + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add"); + $v_result = PCLZIP_ERR_INVALID_PARAMETER; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : errorCode() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function errorCode() + { + if (PCLZIP_ERROR_EXTERNAL == 1) { + return (PclErrorCode()); + } + + return ($this->error_code); + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : errorName() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function errorName($p_with_code = false) + { + $v_name = array( + PCLZIP_ERR_NO_ERROR => 'PCLZIP_ERR_NO_ERROR', + PCLZIP_ERR_WRITE_OPEN_FAIL => 'PCLZIP_ERR_WRITE_OPEN_FAIL', + PCLZIP_ERR_READ_OPEN_FAIL => 'PCLZIP_ERR_READ_OPEN_FAIL', + PCLZIP_ERR_INVALID_PARAMETER => 'PCLZIP_ERR_INVALID_PARAMETER', + PCLZIP_ERR_MISSING_FILE => 'PCLZIP_ERR_MISSING_FILE', + PCLZIP_ERR_FILENAME_TOO_LONG => 'PCLZIP_ERR_FILENAME_TOO_LONG', + PCLZIP_ERR_INVALID_ZIP => 'PCLZIP_ERR_INVALID_ZIP', + PCLZIP_ERR_BAD_EXTRACTED_FILE => 'PCLZIP_ERR_BAD_EXTRACTED_FILE', + PCLZIP_ERR_DIR_CREATE_FAIL => 'PCLZIP_ERR_DIR_CREATE_FAIL', + PCLZIP_ERR_BAD_EXTENSION => 'PCLZIP_ERR_BAD_EXTENSION', + PCLZIP_ERR_BAD_FORMAT => 'PCLZIP_ERR_BAD_FORMAT', + PCLZIP_ERR_DELETE_FILE_FAIL => 'PCLZIP_ERR_DELETE_FILE_FAIL', + PCLZIP_ERR_RENAME_FILE_FAIL => 'PCLZIP_ERR_RENAME_FILE_FAIL', + PCLZIP_ERR_BAD_CHECKSUM => 'PCLZIP_ERR_BAD_CHECKSUM', + PCLZIP_ERR_INVALID_ARCHIVE_ZIP => 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', + PCLZIP_ERR_MISSING_OPTION_VALUE => 'PCLZIP_ERR_MISSING_OPTION_VALUE', + PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE', + PCLZIP_ERR_UNSUPPORTED_COMPRESSION => 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', + PCLZIP_ERR_UNSUPPORTED_ENCRYPTION => 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', + PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE => 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', + PCLZIP_ERR_DIRECTORY_RESTRICTION => 'PCLZIP_ERR_DIRECTORY_RESTRICTION' + ); + + if (isset($v_name[$this->error_code])) { + $v_value = $v_name[$this->error_code]; + } else { + $v_value = 'NoName'; + } + + if ($p_with_code) { + return ($v_value . ' (' . $this->error_code . ')'); + } + + return ($v_value); + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : errorInfo() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function errorInfo($p_full = false) + { + if (PCLZIP_ERROR_EXTERNAL == 1) { + return (PclErrorString()); + } + + if ($p_full) { + return ($this->errorName(true) . " : " . $this->error_string); + } + + return ($this->error_string . " [code " . $this->error_code . "]"); + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // ***** UNDER THIS LINE ARE DEFINED PRIVATE INTERNAL FUNCTIONS ***** + // ***** ***** + // ***** THESES FUNCTIONS MUST NOT BE USED DIRECTLY ***** + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCheckFormat() + // Description : + // This method check that the archive exists and is a valid zip archive. + // Several level of check exists. (futur) + // Parameters : + // $p_level : Level of check. Default 0. + // 0 : Check the first bytes (magic codes) (default value)) + // 1 : 0 + Check the central directory (futur) + // 2 : 1 + Check each file header (futur) + // Return Values : + // true on success, + // false on error, the error code is set. + // -------------------------------------------------------------------------------- + public function privCheckFormat($p_level = 0) + { + $v_result = true; + + // ----- Reset the file system cache + clearstatcache(); + + // ----- Reset the error handler + $this->privErrorReset(); + + // ----- Look if the file exits + if (!is_file($this->zipname)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "Missing archive file '" . $this->zipname . "'"); + + return (false); + } + + // ----- Check that the file is readeable + if (!is_readable($this->zipname)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to read archive '" . $this->zipname . "'"); + + return (false); + } + + // ----- Check the magic code + // TBC + + // ----- Check the central header + // TBC + + // ----- Check each file header + // TBC + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privParseOptions() + // Description : + // This internal methods reads the variable list of arguments ($p_options_list, + // $p_size) and generate an array with the options and values ($v_result_list). + // $v_requested_options contains the options that can be present and those that + // must be present. + // $v_requested_options is an array, with the option value as key, and 'optional', + // or 'mandatory' as value. + // Parameters : + // See above. + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + public function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options = false) + { + $v_result = 1; + + // ----- Read the options + $i = 0; + while ($i < $p_size) { + + // ----- Check if the option is supported + if (!isset($v_requested_options[$p_options_list[$i]])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid optional parameter '" . $p_options_list[$i] . "' for this method"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for next option + switch ($p_options_list[$i]) { + // ----- Look for options that request a path value + case PCLZIP_OPT_PATH: + case PCLZIP_OPT_REMOVE_PATH: + case PCLZIP_OPT_ADD_PATH: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i + 1], false); + $i++; + break; + + case PCLZIP_OPT_TEMP_FILE_THRESHOLD: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + return PclZip::errorCode(); + } + + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'"); + + return PclZip::errorCode(); + } + + // ----- Check the value + $v_value = $p_options_list[$i + 1]; + if ((!is_integer($v_value)) || ($v_value < 0)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Integer expected for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + return PclZip::errorCode(); + } + + // ----- Get the value (and convert it in bytes) + $v_result_list[$p_options_list[$i]] = $v_value * 1048576; + $i++; + break; + + case PCLZIP_OPT_TEMP_FILE_ON: + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'"); + + return PclZip::errorCode(); + } + + $v_result_list[$p_options_list[$i]] = true; + break; + + case PCLZIP_OPT_TEMP_FILE_OFF: + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_ON])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_ON'"); + + return PclZip::errorCode(); + } + // ----- Check for incompatible options + if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '" . PclZipUtilOptionText($p_options_list[$i]) . "' can not be used with option 'PCLZIP_OPT_TEMP_FILE_THRESHOLD'"); + + return PclZip::errorCode(); + } + + $v_result_list[$p_options_list[$i]] = true; + break; + + case PCLZIP_OPT_EXTRACT_DIR_RESTRICTION: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i + 1]) && ($p_options_list[$i + 1] != '')) { + $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i + 1], false); + $i++; + } else { + } + break; + + // ----- Look for options that request an array of string for value + case PCLZIP_OPT_BY_NAME: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i + 1])) { + $v_result_list[$p_options_list[$i]][0] = $p_options_list[$i + 1]; + } elseif (is_array($p_options_list[$i + 1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that request an EREG or PREG expression + case PCLZIP_OPT_BY_EREG: + $p_options_list[$i] = PCLZIP_OPT_BY_PREG; + // ereg() is deprecated starting with PHP 5.3. Move PCLZIP_OPT_BY_EREG + // to PCLZIP_OPT_BY_PREG + case PCLZIP_OPT_BY_PREG: + //case PCLZIP_OPT_CRYPT : + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i + 1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that takes a string + case PCLZIP_OPT_COMMENT: + case PCLZIP_OPT_ADD_COMMENT: + case PCLZIP_OPT_PREPEND_COMMENT: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + if (is_string($p_options_list[$i + 1])) { + $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + $i++; + break; + + // ----- Look for options that request an array of index + case PCLZIP_OPT_BY_INDEX: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_work_list = array(); + if (is_string($p_options_list[$i + 1])) { + + // ----- Remove spaces + $p_options_list[$i + 1] = strtr($p_options_list[$i + 1], ' ', ''); + + // ----- Parse items + $v_work_list = explode(",", $p_options_list[$i + 1]); + } elseif (is_integer($p_options_list[$i + 1])) { + $v_work_list[0] = $p_options_list[$i + 1] . '-' . $p_options_list[$i + 1]; + } elseif (is_array($p_options_list[$i + 1])) { + $v_work_list = $p_options_list[$i + 1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Value must be integer, string or array for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Reduce the index list + // each index item in the list must be a couple with a start and + // an end value : [0,3], [5-5], [8-10], ... + // ----- Check the format of each item + $v_sort_flag = false; + $v_sort_value = 0; + for ($j = 0; $j < sizeof($v_work_list); $j++) { + // ----- Explode the item + $v_item_list = explode("-", $v_work_list[$j]); + $v_size_item_list = sizeof($v_item_list); + + // ----- TBC : Here we might check that each item is a + // real integer ... + + // ----- Look for single value + if ($v_size_item_list == 1) { + // ----- Set the option value + $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0]; + $v_result_list[$p_options_list[$i]][$j]['end'] = $v_item_list[0]; + } elseif ($v_size_item_list == 2) { + // ----- Set the option value + $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0]; + $v_result_list[$p_options_list[$i]][$j]['end'] = $v_item_list[1]; + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Too many values in index range for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for list sort + if ($v_result_list[$p_options_list[$i]][$j]['start'] < $v_sort_value) { + $v_sort_flag = true; + + // ----- TBC : An automatic sort should be writen ... + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Invalid order of index range for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + $v_sort_value = $v_result_list[$p_options_list[$i]][$j]['start']; + } + + // ----- Sort the items + if ($v_sort_flag) { + // TBC : To Be Completed + } + + // ----- Next option + $i++; + break; + + // ----- Look for options that request no value + case PCLZIP_OPT_REMOVE_ALL_PATH: + case PCLZIP_OPT_EXTRACT_AS_STRING: + case PCLZIP_OPT_NO_COMPRESSION: + case PCLZIP_OPT_EXTRACT_IN_OUTPUT: + case PCLZIP_OPT_REPLACE_NEWER: + case PCLZIP_OPT_STOP_ON_ERROR: + $v_result_list[$p_options_list[$i]] = true; + break; + + // ----- Look for options that request an octal value + case PCLZIP_OPT_SET_CHMOD: + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_result_list[$p_options_list[$i]] = $p_options_list[$i + 1]; + $i++; + break; + + // ----- Look for options that request a call-back + case PCLZIP_CB_PRE_EXTRACT: + case PCLZIP_CB_POST_EXTRACT: + case PCLZIP_CB_PRE_ADD: + case PCLZIP_CB_POST_ADD: + /* for futur use + case PCLZIP_CB_PRE_DELETE : + case PCLZIP_CB_POST_DELETE : + case PCLZIP_CB_PRE_LIST : + case PCLZIP_CB_POST_LIST : + */ + // ----- Check the number of parameters + if (($i + 1) >= $p_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Get the value + $v_function_name = $p_options_list[$i + 1]; + + // ----- Check that the value is a valid existing function + if (!function_exists($v_function_name)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Function '" . $v_function_name . "()' is not an existing function for option '" . PclZipUtilOptionText($p_options_list[$i]) . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Set the attribute + $v_result_list[$p_options_list[$i]] = $v_function_name; + $i++; + break; + + default: + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Unknown parameter '" . $p_options_list[$i] . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Next options + $i++; + } + + // ----- Look for mandatory options + if ($v_requested_options !== false) { + for ($key = reset($v_requested_options); $key = key($v_requested_options); $key = next($v_requested_options)) { + // ----- Look for mandatory option + if ($v_requested_options[$key] == 'mandatory') { + // ----- Look if present + if (!isset($v_result_list[$key])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter " . PclZipUtilOptionText($key) . "(" . $key . ")"); + + // ----- Return + return PclZip::errorCode(); + } + } + } + } + + // ----- Look for default values + if (!isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) { + + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privOptionDefaultThreshold() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privOptionDefaultThreshold(&$p_options) + { + $v_result = 1; + + if (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) || isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) { + return $v_result; + } + + // ----- Get 'memory_limit' configuration value + $v_memory_limit = ini_get('memory_limit'); + $v_memory_limit = trim($v_memory_limit); + $last = strtolower(substr($v_memory_limit, -1)); + $v_memory_limit = preg_replace('/[^0-9,.]/', '', $v_memory_limit); + + if ($last == 'g') { + //$v_memory_limit = $v_memory_limit*1024*1024*1024; + $v_memory_limit = $v_memory_limit * 1073741824; + } + if ($last == 'm') { + //$v_memory_limit = $v_memory_limit*1024*1024; + $v_memory_limit = $v_memory_limit * 1048576; + } + if ($last == 'k') { + $v_memory_limit = $v_memory_limit * 1024; + } + + $p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] = floor($v_memory_limit * PCLZIP_TEMPORARY_FILE_RATIO); + + // ----- Sanity check : No threshold if value lower than 1M + if ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] < 1048576) { + unset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privFileDescrParseAtt() + // Description : + // Parameters : + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + public function privFileDescrParseAtt(&$p_file_list, &$p_filedescr, $v_options, $v_requested_options = false) + { + $v_result = 1; + + // ----- For each file in the list check the attributes + foreach ($p_file_list as $v_key => $v_value) { + + // ----- Check if the option is supported + if (!isset($v_requested_options[$v_key])) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file attribute '" . $v_key . "' for this file"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for attribute + switch ($v_key) { + case PCLZIP_ATT_FILE_NAME: + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); + + return PclZip::errorCode(); + } + + $p_filedescr['filename'] = PclZipUtilPathReduction($v_value); + + if ($p_filedescr['filename'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty filename for attribute '" . PclZipUtilOptionText($v_key) . "'"); + + return PclZip::errorCode(); + } + + break; + + case PCLZIP_ATT_FILE_NEW_SHORT_NAME: + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); + + return PclZip::errorCode(); + } + + $p_filedescr['new_short_name'] = PclZipUtilPathReduction($v_value); + + if ($p_filedescr['new_short_name'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty short filename for attribute '" . PclZipUtilOptionText($v_key) . "'"); + + return PclZip::errorCode(); + } + break; + + case PCLZIP_ATT_FILE_NEW_FULL_NAME: + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); + + return PclZip::errorCode(); + } + + $p_filedescr['new_full_name'] = PclZipUtilPathReduction($v_value); + + if ($p_filedescr['new_full_name'] == '') { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty full filename for attribute '" . PclZipUtilOptionText($v_key) . "'"); + + return PclZip::errorCode(); + } + break; + + // ----- Look for options that takes a string + case PCLZIP_ATT_FILE_COMMENT: + if (!is_string($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". String expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); + + return PclZip::errorCode(); + } + + $p_filedescr['comment'] = $v_value; + break; + + case PCLZIP_ATT_FILE_MTIME: + if (!is_integer($v_value)) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type " . gettype($v_value) . ". Integer expected for attribute '" . PclZipUtilOptionText($v_key) . "'"); + + return PclZip::errorCode(); + } + + $p_filedescr['mtime'] = $v_value; + break; + + case PCLZIP_ATT_FILE_CONTENT: + $p_filedescr['content'] = $v_value; + break; + + default: + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Unknown parameter '" . $v_key . "'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for mandatory options + if ($v_requested_options !== false) { + for ($key = reset($v_requested_options); $key = key($v_requested_options); $key = next($v_requested_options)) { + // ----- Look for mandatory option + if ($v_requested_options[$key] == 'mandatory') { + // ----- Look if present + if (!isset($p_file_list[$key])) { + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter " . PclZipUtilOptionText($key) . "(" . $key . ")"); + + return PclZip::errorCode(); + } + } + } + } + + // end foreach + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privFileDescrExpand() + // Description : + // This method look for each item of the list to see if its a file, a folder + // or a string to be added as file. For any other type of files (link, other) + // just ignore the item. + // Then prepare the information that will be stored for that file. + // When its a folder, expand the folder with all the files that are in that + // folder (recursively). + // Parameters : + // Return Values : + // 1 on success. + // 0 on failure. + // -------------------------------------------------------------------------------- + public function privFileDescrExpand(&$p_filedescr_list, &$p_options) + { + $v_result = 1; + + // ----- Create a result list + $v_result_list = array(); + + // ----- Look each entry + for ($i = 0; $i < sizeof($p_filedescr_list); $i++) { + + // ----- Get filedescr + $v_descr = $p_filedescr_list[$i]; + + // ----- Reduce the filename + $v_descr['filename'] = PclZipUtilTranslateWinPath($v_descr['filename'], false); + $v_descr['filename'] = PclZipUtilPathReduction($v_descr['filename']); + + // ----- Look for real file or folder + if (file_exists($v_descr['filename'])) { + if (@is_file($v_descr['filename'])) { + $v_descr['type'] = 'file'; + } elseif (@is_dir($v_descr['filename'])) { + $v_descr['type'] = 'folder'; + } elseif (@is_link($v_descr['filename'])) { + // skip + continue; + } else { + // skip + continue; + } + + // ----- Look for string added as file + } elseif (isset($v_descr['content'])) { + $v_descr['type'] = 'virtual_file'; + + // ----- Missing file + } else { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '" . $v_descr['filename'] . "' does not exist"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Calculate the stored filename + $this->privCalculateStoredFilename($v_descr, $p_options); + + // ----- Add the descriptor in result list + $v_result_list[sizeof($v_result_list)] = $v_descr; + + // ----- Look for folder + if ($v_descr['type'] == 'folder') { + // ----- List of items in folder + $v_dirlist_descr = array(); + $v_dirlist_nb = 0; + if ($v_folder_handler = @opendir($v_descr['filename'])) { + while (($v_item_handler = @readdir($v_folder_handler)) !== false) { + + // ----- Skip '.' and '..' + if (($v_item_handler == '.') || ($v_item_handler == '..')) { + continue; + } + + // ----- Compose the full filename + $v_dirlist_descr[$v_dirlist_nb]['filename'] = $v_descr['filename'] . '/' . $v_item_handler; + + // ----- Look for different stored filename + // Because the name of the folder was changed, the name of the + // files/sub-folders also change + if (($v_descr['stored_filename'] != $v_descr['filename']) && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) { + if ($v_descr['stored_filename'] != '') { + $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_descr['stored_filename'] . '/' . $v_item_handler; + } else { + $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_item_handler; + } + } + + $v_dirlist_nb++; + } + + @closedir($v_folder_handler); + } else { + // TBC : unable to open folder in read mode + } + + // ----- Expand each element of the list + if ($v_dirlist_nb != 0) { + // ----- Expand + if (($v_result = $this->privFileDescrExpand($v_dirlist_descr, $p_options)) != 1) { + return $v_result; + } + + // ----- Concat the resulting list + $v_result_list = array_merge($v_result_list, $v_dirlist_descr); + } else { + } + + // ----- Free local array + unset($v_dirlist_descr); + } + } + + // ----- Get the result list + $p_filedescr_list = $v_result_list; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCreate() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privCreate($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result = 1; + $v_list_detail = array(); + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Open the file in write mode + if (($v_result = $this->privOpenFd('wb')) != 1) { + // ----- Return + return $v_result; + } + + // ----- Add the list of files + $v_result = $this->privAddList($p_filedescr_list, $p_result_list, $p_options); + + // ----- Close + $this->privCloseFd(); + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAdd() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privAdd($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result = 1; + $v_list_detail = array(); + + // ----- Look if the archive exists or is empty + if ((!is_file($this->zipname)) || (filesize($this->zipname) == 0)) { + + // ----- Do a create + $v_result = $this->privCreate($p_filedescr_list, $p_result_list, $p_options); + + // ----- Return + return $v_result; + } + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) { + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Go to beginning of File + @rewind($this->zip_fd); + + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.tmp'; + + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_zip_temp_name . '\' in binary write mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = $v_central_dir['offset']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Swap the file descriptor + // Here is a trick : I swap the temporary fd with the zip fd, in order to use + // the following methods on the temporary fil and not the real archive + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Add the files + $v_header_list = array(); + if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) { + fclose($v_zip_temp_fd); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($this->zip_fd); + + // ----- Copy the block of file headers from the old archive + $v_size = $v_central_dir['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_zip_temp_fd, $v_read_size); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Create the Central Dir files header + for ($i = 0, $v_count = 0; $i < sizeof($v_header_list); $i++) { + // ----- Create the file header + if ($v_header_list[$i]['status'] == 'ok') { + if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) { + fclose($v_zip_temp_fd); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + $v_count++; + } + + // ----- Transform the header to a 'usable' info + $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + } + + // ----- Zip file comment + $v_comment = $v_central_dir['comment']; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + if (isset($p_options[PCLZIP_OPT_ADD_COMMENT])) { + $v_comment = $v_comment . $p_options[PCLZIP_OPT_ADD_COMMENT]; + } + if (isset($p_options[PCLZIP_OPT_PREPEND_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_PREPEND_COMMENT] . $v_comment; + } + + // ----- Calculate the size of the central header + $v_size = @ftell($this->zip_fd) - $v_offset; + + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_count + $v_central_dir['entries'], $v_size, $v_offset, $v_comment)) != 1) { + // ----- Reset the file list + unset($v_header_list); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + + // ----- Swap back the file descriptor + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Close + $this->privCloseFd(); + + // ----- Close the temporary file + @fclose($v_zip_temp_fd); + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); + + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privOpenFd() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function privOpenFd($p_mode) + { + $v_result = 1; + + // ----- Look if already open + if ($this->zip_fd != 0) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Zip file \'' . $this->zipname . '\' already open'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, $p_mode)) == 0) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in ' . $p_mode . ' mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCloseFd() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function privCloseFd() + { + $v_result = 1; + + if ($this->zip_fd != 0) { + @fclose($this->zip_fd); + } + $this->zip_fd = 0; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddList() + // Description : + // $p_add_dir and $p_remove_dir will give the ability to memorize a path which is + // different from the real path of the file. This is usefull if you want to have PclTar + // running in any directory, and memorize relative path from an other directory. + // Parameters : + // $p_list : An array containing the file or directory names to add in the tar + // $p_result_list : list of added files with their properties (specially the status field) + // $p_add_dir : Path to add in the filename path archived + // $p_remove_dir : Path to remove in the filename path archived + // Return Values : + // -------------------------------------------------------------------------------- + // function privAddList($p_list, &$p_result_list, $p_add_dir, $p_remove_dir, $p_remove_all_dir, &$p_options) + public function privAddList($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result = 1; + + // ----- Add the files + $v_header_list = array(); + if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1) { + // ----- Return + return $v_result; + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($this->zip_fd); + + // ----- Create the Central Dir files header + for ($i = 0, $v_count = 0; $i < sizeof($v_header_list); $i++) { + // ----- Create the file header + if ($v_header_list[$i]['status'] == 'ok') { + if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) { + // ----- Return + return $v_result; + } + $v_count++; + } + + // ----- Transform the header to a 'usable' info + $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + } + + // ----- Zip file comment + $v_comment = ''; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + + // ----- Calculate the size of the central header + $v_size = @ftell($this->zip_fd) - $v_offset; + + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_count, $v_size, $v_offset, $v_comment)) != 1) { + // ----- Reset the file list + unset($v_header_list); + + // ----- Return + return $v_result; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddFileList() + // Description : + // Parameters : + // $p_filedescr_list : An array containing the file description + // or directory names to add in the zip + // $p_result_list : list of added files with their properties (specially the status field) + // Return Values : + // -------------------------------------------------------------------------------- + public function privAddFileList($p_filedescr_list, &$p_result_list, &$p_options) + { + $v_result = 1; + $v_header = array(); + + // ----- Recuperate the current number of elt in list + $v_nb = sizeof($p_result_list); + + // ----- Loop on the files + for ($j = 0; ($j < sizeof($p_filedescr_list)) && ($v_result == 1); $j++) { + // ----- Format the filename + $p_filedescr_list[$j]['filename'] = PclZipUtilTranslateWinPath($p_filedescr_list[$j]['filename'], false); + + // ----- Skip empty file names + // TBC : Can this be possible ? not checked in DescrParseAtt ? + if ($p_filedescr_list[$j]['filename'] == "") { + continue; + } + + // ----- Check the filename + if (($p_filedescr_list[$j]['type'] != 'virtual_file') && (!file_exists($p_filedescr_list[$j]['filename']))) { + PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '" . $p_filedescr_list[$j]['filename'] . "' does not exist"); + + return PclZip::errorCode(); + } + + // ----- Look if it is a file or a dir with no all path remove option + // or a dir with all its path removed + // if ( (is_file($p_filedescr_list[$j]['filename'])) + // || ( is_dir($p_filedescr_list[$j]['filename']) + if (($p_filedescr_list[$j]['type'] == 'file') || ($p_filedescr_list[$j]['type'] == 'virtual_file') || (($p_filedescr_list[$j]['type'] == 'folder') && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]) || !$p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) { + + // ----- Add the file + $v_result = $this->privAddFile($p_filedescr_list[$j], $v_header, $p_options); + if ($v_result != 1) { + return $v_result; + } + + // ----- Store the file infos + $p_result_list[$v_nb++] = $v_header; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privAddFile($p_filedescr, &$p_header, &$p_options) + { + $v_result = 1; + + // ----- Working variable + $p_filename = $p_filedescr['filename']; + + // TBC : Already done in the fileAtt check ... ? + if ($p_filename == "") { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file list parameter (invalid or empty list)"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for a stored different filename + /* TBC : Removed + if (isset($p_filedescr['stored_filename'])) { + $v_stored_filename = $p_filedescr['stored_filename']; + } else { + $v_stored_filename = $p_filedescr['stored_filename']; + } + */ + + // ----- Set the file properties + clearstatcache(); + $p_header['version'] = 20; + $p_header['version_extracted'] = 10; + $p_header['flag'] = 0; + $p_header['compression'] = 0; + $p_header['crc'] = 0; + $p_header['compressed_size'] = 0; + $p_header['filename_len'] = strlen($p_filename); + $p_header['extra_len'] = 0; + $p_header['disk'] = 0; + $p_header['internal'] = 0; + $p_header['offset'] = 0; + $p_header['filename'] = $p_filename; + // TBC : Removed $p_header['stored_filename'] = $v_stored_filename; + $p_header['stored_filename'] = $p_filedescr['stored_filename']; + $p_header['extra'] = ''; + $p_header['status'] = 'ok'; + $p_header['index'] = -1; + + // ----- Look for regular file + if ($p_filedescr['type'] == 'file') { + $p_header['external'] = 0x00000000; + $p_header['size'] = filesize($p_filename); + + // ----- Look for regular folder + } elseif ($p_filedescr['type'] == 'folder') { + $p_header['external'] = 0x00000010; + $p_header['mtime'] = filemtime($p_filename); + $p_header['size'] = filesize($p_filename); + + // ----- Look for virtual file + } elseif ($p_filedescr['type'] == 'virtual_file') { + $p_header['external'] = 0x00000000; + $p_header['size'] = strlen($p_filedescr['content']); + } + + // ----- Look for filetime + if (isset($p_filedescr['mtime'])) { + $p_header['mtime'] = $p_filedescr['mtime']; + } elseif ($p_filedescr['type'] == 'virtual_file') { + $p_header['mtime'] = time(); + } else { + $p_header['mtime'] = filemtime($p_filename); + } + + // ------ Look for file comment + if (isset($p_filedescr['comment'])) { + $p_header['comment_len'] = strlen($p_filedescr['comment']); + $p_header['comment'] = $p_filedescr['comment']; + } else { + $p_header['comment_len'] = 0; + $p_header['comment'] = ''; + } + + // ----- Look for pre-add callback + if (isset($p_options[PCLZIP_CB_PRE_ADD])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_header, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_PRE_ADD].'(PCLZIP_CB_PRE_ADD, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_ADD](PCLZIP_CB_PRE_ADD, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_header['status'] = "skipped"; + $v_result = 1; + } + + // ----- Update the informations + // Only some fields can be modified + if ($p_header['stored_filename'] != $v_local_header['stored_filename']) { + $p_header['stored_filename'] = PclZipUtilPathReduction($v_local_header['stored_filename']); + } + } + + // ----- Look for empty stored filename + if ($p_header['stored_filename'] == "") { + $p_header['status'] = "filtered"; + } + + // ----- Check the path length + if (strlen($p_header['stored_filename']) > 0xFF) { + $p_header['status'] = 'filename_too_long'; + } + + // ----- Look if no error, or file not skipped + if ($p_header['status'] == 'ok') { + + // ----- Look for a file + if ($p_filedescr['type'] == 'file') { + // ----- Look for using temporary file to zip + if ((!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_header['size'])))) { + $v_result = $this->privAddFileUsingTempFile($p_filedescr, $p_header, $p_options); + if ($v_result < PCLZIP_ERR_NO_ERROR) { + return $v_result; + } + + // ----- Use "in memory" zip algo + } else { + + // ----- Open the source file + if (($v_file = @fopen($p_filename, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); + + return PclZip::errorCode(); + } + + // ----- Read the file content + $v_content = @fread($v_file, $p_header['size']); + + // ----- Close the file + @fclose($v_file); + + // ----- Calculate the CRC + $p_header['crc'] = @crc32($v_content); + + // ----- Look for no compression + if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { + // ----- Set header parameters + $p_header['compressed_size'] = $p_header['size']; + $p_header['compression'] = 0; + + // ----- Look for normal compression + } else { + // ----- Compress the content + $v_content = @gzdeflate($v_content); + + // ----- Set header parameters + $p_header['compressed_size'] = strlen($v_content); + $p_header['compression'] = 8; + } + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + @fclose($v_file); + + return $v_result; + } + + // ----- Write the compressed (or not) content + @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']); + + } + + // ----- Look for a virtual file (a file from string) + } elseif ($p_filedescr['type'] == 'virtual_file') { + + $v_content = $p_filedescr['content']; + + // ----- Calculate the CRC + $p_header['crc'] = @crc32($v_content); + + // ----- Look for no compression + if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) { + // ----- Set header parameters + $p_header['compressed_size'] = $p_header['size']; + $p_header['compression'] = 0; + + // ----- Look for normal compression + } else { + // ----- Compress the content + $v_content = @gzdeflate($v_content); + + // ----- Set header parameters + $p_header['compressed_size'] = strlen($v_content); + $p_header['compression'] = 8; + } + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + @fclose($v_file); + + return $v_result; + } + + // ----- Write the compressed (or not) content + @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']); + + // ----- Look for a directory + } elseif ($p_filedescr['type'] == 'folder') { + // ----- Look for directory last '/' + if (@substr($p_header['stored_filename'], -1) != '/') { + $p_header['stored_filename'] .= '/'; + } + + // ----- Set the file properties + $p_header['size'] = 0; + //$p_header['external'] = 0x41FF0010; // Value for a folder : to be checked + $p_header['external'] = 0x00000010; // Value for a folder : to be checked + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + return $v_result; + } + } + } + + // ----- Look for post-add callback + if (isset($p_options[PCLZIP_CB_POST_ADD])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_header, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_POST_ADD].'(PCLZIP_CB_POST_ADD, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_ADD](PCLZIP_CB_POST_ADD, $v_local_header); + if ($v_result == 0) { + // ----- Ignored + $v_result = 1; + } + + // ----- Update the informations + // Nothing can be modified + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privAddFileUsingTempFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privAddFileUsingTempFile($p_filedescr, &$p_header, &$p_options) + { + $v_result = PCLZIP_ERR_NO_ERROR; + + // ----- Working variable + $p_filename = $p_filedescr['filename']; + + // ----- Open the source file + if (($v_file = @fopen($p_filename, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode"); + + return PclZip::errorCode(); + } + + // ----- Creates a compressed temporary file + $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.gz'; + if (($v_file_compressed = @gzopen($v_gzip_temp_name, "wb")) == 0) { + fclose($v_file); + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary write mode'); + + return PclZip::errorCode(); + } + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = filesize($p_filename); + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_file, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @gzputs($v_file_compressed, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Close the file + @fclose($v_file); + @gzclose($v_file_compressed); + + // ----- Check the minimum file size + if (filesize($v_gzip_temp_name) < 18) { + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'gzip temporary file \'' . $v_gzip_temp_name . '\' has invalid filesize - should be minimum 18 bytes'); + + return PclZip::errorCode(); + } + + // ----- Extract the compressed attributes + if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary read mode'); + + return PclZip::errorCode(); + } + + // ----- Read the gzip file header + $v_binary_data = @fread($v_file_compressed, 10); + $v_data_header = unpack('a1id1/a1id2/a1cm/a1flag/Vmtime/a1xfl/a1os', $v_binary_data); + + // ----- Check some parameters + $v_data_header['os'] = bin2hex($v_data_header['os']); + + // ----- Read the gzip file footer + @fseek($v_file_compressed, filesize($v_gzip_temp_name) - 8); + $v_binary_data = @fread($v_file_compressed, 8); + $v_data_footer = unpack('Vcrc/Vcompressed_size', $v_binary_data); + + // ----- Set the attributes + $p_header['compression'] = ord($v_data_header['cm']); + //$p_header['mtime'] = $v_data_header['mtime']; + $p_header['crc'] = $v_data_footer['crc']; + $p_header['compressed_size'] = filesize($v_gzip_temp_name) - 18; + + // ----- Close the file + @fclose($v_file_compressed); + + // ----- Call the header generation + if (($v_result = $this->privWriteFileHeader($p_header)) != 1) { + return $v_result; + } + + // ----- Add the compressed data + if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) { + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary read mode'); + + return PclZip::errorCode(); + } + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + fseek($v_file_compressed, 10); + $v_size = $p_header['compressed_size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($v_file_compressed, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Close the file + @fclose($v_file_compressed); + + // ----- Unlink the temporary file + @unlink($v_gzip_temp_name); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCalculateStoredFilename() + // Description : + // Based on file descriptor properties and global options, this method + // calculate the filename that will be stored in the archive. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privCalculateStoredFilename(&$p_filedescr, &$p_options) + { + $v_result = 1; + + // ----- Working variables + $p_filename = $p_filedescr['filename']; + if (isset($p_options[PCLZIP_OPT_ADD_PATH])) { + $p_add_dir = $p_options[PCLZIP_OPT_ADD_PATH]; + } else { + $p_add_dir = ''; + } + if (isset($p_options[PCLZIP_OPT_REMOVE_PATH])) { + $p_remove_dir = $p_options[PCLZIP_OPT_REMOVE_PATH]; + } else { + $p_remove_dir = ''; + } + if (isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])) { + $p_remove_all_dir = $p_options[PCLZIP_OPT_REMOVE_ALL_PATH]; + } else { + $p_remove_all_dir = 0; + } + + // ----- Look for full name change + if (isset($p_filedescr['new_full_name'])) { + // ----- Remove drive letter if any + $v_stored_filename = PclZipUtilTranslateWinPath($p_filedescr['new_full_name']); + + // ----- Look for path and/or short name change + } else { + + // ----- Look for short name change + // Its when we cahnge just the filename but not the path + if (isset($p_filedescr['new_short_name'])) { + $v_path_info = pathinfo($p_filename); + $v_dir = ''; + if ($v_path_info['dirname'] != '') { + $v_dir = $v_path_info['dirname'] . '/'; + } + $v_stored_filename = $v_dir . $p_filedescr['new_short_name']; + } else { + // ----- Calculate the stored filename + $v_stored_filename = $p_filename; + } + + // ----- Look for all path to remove + if ($p_remove_all_dir) { + $v_stored_filename = basename($p_filename); + + // ----- Look for partial path remove + } elseif ($p_remove_dir != "") { + if (substr($p_remove_dir, -1) != '/') { + $p_remove_dir .= "/"; + } + + if ((substr($p_filename, 0, 2) == "./") || (substr($p_remove_dir, 0, 2) == "./")) { + + if ((substr($p_filename, 0, 2) == "./") && (substr($p_remove_dir, 0, 2) != "./")) { + $p_remove_dir = "./" . $p_remove_dir; + } + if ((substr($p_filename, 0, 2) != "./") && (substr($p_remove_dir, 0, 2) == "./")) { + $p_remove_dir = substr($p_remove_dir, 2); + } + } + + $v_compare = PclZipUtilPathInclusion($p_remove_dir, $v_stored_filename); + if ($v_compare > 0) { + if ($v_compare == 2) { + $v_stored_filename = ""; + } else { + $v_stored_filename = substr($v_stored_filename, strlen($p_remove_dir)); + } + } + } + + // ----- Remove drive letter if any + $v_stored_filename = PclZipUtilTranslateWinPath($v_stored_filename); + + // ----- Look for path to add + if ($p_add_dir != "") { + if (substr($p_add_dir, -1) == "/") { + $v_stored_filename = $p_add_dir . $v_stored_filename; + } else { + $v_stored_filename = $p_add_dir . "/" . $v_stored_filename; + } + } + } + + // ----- Filename (reduce the path of stored name) + $v_stored_filename = PclZipUtilPathReduction($v_stored_filename); + $p_filedescr['stored_filename'] = $v_stored_filename; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privWriteFileHeader(&$p_header) + { + $v_result = 1; + + // ----- Store the offset position of the file + $p_header['offset'] = ftell($this->zip_fd); + + // ----- Transform UNIX mtime to DOS format mdate/mtime + $v_date = getdate($p_header['mtime']); + $v_mtime = ($v_date['hours'] << 11) + ($v_date['minutes'] << 5) + $v_date['seconds'] / 2; + $v_mdate = (($v_date['year'] - 1980) << 9) + ($v_date['mon'] << 5) + $v_date['mday']; + + // ----- Packed data + $v_binary_data = pack("VvvvvvVVVvv", 0x04034b50, $p_header['version_extracted'], $p_header['flag'], $p_header['compression'], $v_mtime, $v_mdate, $p_header['crc'], $p_header['compressed_size'], $p_header['size'], strlen($p_header['stored_filename']), $p_header['extra_len']); + + // ----- Write the first 148 bytes of the header in the archive + fputs($this->zip_fd, $v_binary_data, 30); + + // ----- Write the variable fields + if (strlen($p_header['stored_filename']) != 0) { + fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename'])); + } + if ($p_header['extra_len'] != 0) { + fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteCentralFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privWriteCentralFileHeader(&$p_header) + { + $v_result = 1; + + // TBC + //for (reset($p_header); $key = key($p_header); next($p_header)) { + //} + + // ----- Transform UNIX mtime to DOS format mdate/mtime + $v_date = getdate($p_header['mtime']); + $v_mtime = ($v_date['hours'] << 11) + ($v_date['minutes'] << 5) + $v_date['seconds'] / 2; + $v_mdate = (($v_date['year'] - 1980) << 9) + ($v_date['mon'] << 5) + $v_date['mday']; + + // ----- Packed data + $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50, $p_header['version'], $p_header['version_extracted'], $p_header['flag'], $p_header['compression'], $v_mtime, $v_mdate, $p_header['crc'], $p_header['compressed_size'], $p_header['size'], strlen($p_header['stored_filename']), $p_header['extra_len'], $p_header['comment_len'], $p_header['disk'], $p_header['internal'], $p_header['external'], $p_header['offset']); + + // ----- Write the 42 bytes of the header in the zip file + fputs($this->zip_fd, $v_binary_data, 46); + + // ----- Write the variable fields + if (strlen($p_header['stored_filename']) != 0) { + fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename'])); + } + if ($p_header['extra_len'] != 0) { + fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']); + } + if ($p_header['comment_len'] != 0) { + fputs($this->zip_fd, $p_header['comment'], $p_header['comment_len']); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privWriteCentralHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privWriteCentralHeader($p_nb_entries, $p_size, $p_offset, $p_comment) + { + $v_result = 1; + + // ----- Packed data + $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries, $p_nb_entries, $p_size, $p_offset, strlen($p_comment)); + + // ----- Write the 22 bytes of the header in the zip file + fputs($this->zip_fd, $v_binary_data, 22); + + // ----- Write the variable fields + if (strlen($p_comment) != 0) { + fputs($this->zip_fd, $p_comment, strlen($p_comment)); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privList() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privList(&$p_list) + { + $v_result = 1; + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Open the zip file + if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0) { + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \'' . $this->zipname . '\' in binary read mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Go to beginning of Central Dir + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_central_dir['offset'])) { + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read each entry + for ($i = 0; $i < $v_central_dir['entries']; $i++) { + // ----- Read the file header + if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) { + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + $v_header['index'] = $i; + + // ----- Get the only interesting attributes + $this->privConvertHeader2FileInfo($v_header, $p_list[$i]); + unset($v_header); + } + + // ----- Close the zip file + $this->privCloseFd(); + + // ----- Magic quotes trick + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privConvertHeader2FileInfo() + // Description : + // This function takes the file informations from the central directory + // entries and extract the interesting parameters that will be given back. + // The resulting file infos are set in the array $p_info + // $p_info['filename'] : Filename with full path. Given by user (add), + // extracted in the filesystem (extract). + // $p_info['stored_filename'] : Stored filename in the archive. + // $p_info['size'] = Size of the file. + // $p_info['compressed_size'] = Compressed size of the file. + // $p_info['mtime'] = Last modification date of the file. + // $p_info['comment'] = Comment associated with the file. + // $p_info['folder'] = true/false : indicates if the entry is a folder or not. + // $p_info['status'] = status of the action on the file. + // $p_info['crc'] = CRC of the file content. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privConvertHeader2FileInfo($p_header, &$p_info) + { + $v_result = 1; + + // ----- Get the interesting attributes + $v_temp_path = PclZipUtilPathReduction($p_header['filename']); + $p_info['filename'] = $v_temp_path; + $v_temp_path = PclZipUtilPathReduction($p_header['stored_filename']); + $p_info['stored_filename'] = $v_temp_path; + $p_info['size'] = $p_header['size']; + $p_info['compressed_size'] = $p_header['compressed_size']; + $p_info['mtime'] = $p_header['mtime']; + $p_info['comment'] = $p_header['comment']; + $p_info['folder'] = (($p_header['external'] & 0x00000010) == 0x00000010); + $p_info['index'] = $p_header['index']; + $p_info['status'] = $p_header['status']; + $p_info['crc'] = $p_header['crc']; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractByRule() + // Description : + // Extract a file or directory depending of rules (by index, by name, ...) + // Parameters : + // $p_file_list : An array where will be placed the properties of each + // extracted file + // $p_path : Path to add while writing the extracted files + // $p_remove_path : Path to remove (from the file memorized path) while writing the + // extracted files. If the path does not match the file path, + // the file is extracted with its memorized path. + // $p_remove_path does not apply to 'list' mode. + // $p_path and $p_remove_path are commulative. + // Return Values : + // 1 on success,0 or less on error (see error code list) + // -------------------------------------------------------------------------------- + public function privExtractByRule(&$p_file_list, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) + { + $v_result = 1; + + // ----- Magic quotes trick + $this->privDisableMagicQuotes(); + + // ----- Check the path + if (($p_path == "") || ((substr($p_path, 0, 1) != "/") && (substr($p_path, 0, 3) != "../") && (substr($p_path, 1, 2) != ":/"))) { + $p_path = "./" . $p_path; + } + + // ----- Reduce the path last (and duplicated) '/' + if (($p_path != "./") && ($p_path != "/")) { + // ----- Look for the path end '/' + while (substr($p_path, -1) == "/") { + $p_path = substr($p_path, 0, strlen($p_path) - 1); + } + } + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/')) { + $p_remove_path .= '/'; + } + $p_remove_path_size = strlen($p_remove_path); + + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) { + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Start at beginning of Central Dir + $v_pos_entry = $v_central_dir['offset']; + + // ----- Read each entry + $j_start = 0; + for ($i = 0, $v_nb_extracted = 0; $i < $v_central_dir['entries']; $i++) { + + // ----- Read next Central dir entry + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_pos_entry)) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the file header + $v_header = array(); + if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Store the index + $v_header['index'] = $i; + + // ----- Store the file position + $v_pos_entry = ftell($this->zip_fd); + + // ----- Look for the specific extract rules + $v_extract = false; + + // ----- Look for extract by name rule + if ((isset($p_options[PCLZIP_OPT_BY_NAME])) && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) { + + // ----- Look if the filename is in the list + for ($j = 0; ($j < sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_extract); $j++) { + + // ----- Look for a directory + if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") { + + // ----- Look if the directory is in the filename path + if ((strlen($v_header['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) && (substr($v_header['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_extract = true; + } + + // ----- Look for a filename + } elseif ($v_header['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) { + $v_extract = true; + } + } + // ----- Look for extract by ereg rule + // ereg() is deprecated with PHP 5.3 + /* + elseif ( (isset($p_options[PCLZIP_OPT_BY_EREG])) + && ($p_options[PCLZIP_OPT_BY_EREG] != "")) { + + if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header['stored_filename'])) { + $v_extract = true; + } + } + */ + + // ----- Look for extract by preg rule + } elseif ((isset($p_options[PCLZIP_OPT_BY_PREG])) && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { + + if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header['stored_filename'])) { + $v_extract = true; + } + + // ----- Look for extract by index rule + } elseif ((isset($p_options[PCLZIP_OPT_BY_INDEX])) && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) { + + // ----- Look if the index is in the list + for ($j = $j_start; ($j < sizeof($p_options[PCLZIP_OPT_BY_INDEX])) && (!$v_extract); $j++) { + + if (($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i <= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) { + $v_extract = true; + } + if ($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) { + $j_start = $j + 1; + } + + if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start'] > $i) { + break; + } + } + + // ----- Look for no rule, which means extract all the archive + } else { + $v_extract = true; + } + + // ----- Check compression method + if (($v_extract) && (($v_header['compression'] != 8) && ($v_header['compression'] != 0))) { + $v_header['status'] = 'unsupported_compression'; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { + + $this->privSwapBackMagicQuotes(); + + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_COMPRESSION, "Filename '" . $v_header['stored_filename'] . "' is " . "compressed by an unsupported compression " . "method (" . $v_header['compression'] . ") "); + + return PclZip::errorCode(); + } + } + + // ----- Check encrypted files + if (($v_extract) && (($v_header['flag'] & 1) == 1)) { + $v_header['status'] = 'unsupported_encryption'; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { + + $this->privSwapBackMagicQuotes(); + + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, "Unsupported encryption for " . " filename '" . $v_header['stored_filename'] . "'"); + + return PclZip::errorCode(); + } + } + + // ----- Look for real extraction + if (($v_extract) && ($v_header['status'] != 'ok')) { + $v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++]); + if ($v_result != 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + $v_extract = false; + } + + // ----- Look for real extraction + if ($v_extract) { + + // ----- Go to the file position + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_header['offset'])) { + // ----- Close the zip file + $this->privCloseFd(); + + $this->privSwapBackMagicQuotes(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Look for extraction as string + if ($p_options[PCLZIP_OPT_EXTRACT_AS_STRING]) { + + $v_string = ''; + + // ----- Extracting the file + $v_result1 = $this->privExtractFileAsString($v_header, $v_string, $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Set the file content + $p_file_list[$v_nb_extracted]['content'] = $v_string; + + // ----- Next extracted file + $v_nb_extracted++; + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + + // ----- Look for extraction in standard output + } elseif ((isset($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) && ($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) { + // ----- Extracting the file in standard output + $v_result1 = $this->privExtractFileInOutput($v_header, $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + + // ----- Look for normal extraction + } else { + // ----- Extracting the file + $v_result1 = $this->privExtractFile($v_header, $p_path, $p_remove_path, $p_remove_all_path, $p_options); + if ($v_result1 < 1) { + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result1; + } + + // ----- Get the only interesting attributes + if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + return $v_result; + } + + // ----- Look for user callback abort + if ($v_result1 == 2) { + break; + } + } + } + } + + // ----- Close the zip file + $this->privCloseFd(); + $this->privSwapBackMagicQuotes(); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFile() + // Description : + // Parameters : + // Return Values : + // + // 1 : ... ? + // PCLZIP_ERR_USER_ABORTED(2) : User ask for extraction stop in callback + // -------------------------------------------------------------------------------- + public function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options) + { + $v_result = 1; + + // ----- Read the file header + if (($v_result = $this->privReadFileHeader($v_header)) != 1) { + // ----- Return + return $v_result; + } + + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } + + // ----- Look for all path to remove + if ($p_remove_all_path == true) { + // ----- Look for folder entry that not need to be extracted + if (($p_entry['external'] & 0x00000010) == 0x00000010) { + + $p_entry['status'] = "filtered"; + + return $v_result; + } + + // ----- Get the basename of the path + $p_entry['filename'] = basename($p_entry['filename']); + + // ----- Look for path to remove + } elseif ($p_remove_path != "") { + if (PclZipUtilPathInclusion($p_remove_path, $p_entry['filename']) == 2) { + + // ----- Change the file status + $p_entry['status'] = "filtered"; + + // ----- Return + return $v_result; + } + + $p_remove_path_size = strlen($p_remove_path); + if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path) { + + // ----- Remove the path + $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size); + + } + } + + // ----- Add the path + if ($p_path != '') { + $p_entry['filename'] = $p_path . "/" . $p_entry['filename']; + } + + // ----- Check a base_dir_restriction + if (isset($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION])) { + $v_inclusion = PclZipUtilPathInclusion($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION], $p_entry['filename']); + if ($v_inclusion == 0) { + + PclZip::privErrorLog(PCLZIP_ERR_DIRECTORY_RESTRICTION, "Filename '" . $p_entry['filename'] . "' is " . "outside PCLZIP_OPT_EXTRACT_DIR_RESTRICTION"); + + return PclZip::errorCode(); + } + } + + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } + + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } + + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + } + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Look for specific actions while the file exist + if (file_exists($p_entry['filename'])) { + + // ----- Look if file is a directory + if (is_dir($p_entry['filename'])) { + + // ----- Change the file status + $p_entry['status'] = "already_a_directory"; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { + + PclZip::privErrorLog(PCLZIP_ERR_ALREADY_A_DIRECTORY, "Filename '" . $p_entry['filename'] . "' is " . "already used by an existing directory"); + + return PclZip::errorCode(); + } + + // ----- Look if file is write protected + } elseif (!is_writeable($p_entry['filename'])) { + + // ----- Change the file status + $p_entry['status'] = "write_protected"; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { + + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, "Filename '" . $p_entry['filename'] . "' exists " . "and is write protected"); + + return PclZip::errorCode(); + } + + // ----- Look if the extracted file is older + } elseif (filemtime($p_entry['filename']) > $p_entry['mtime']) { + // ----- Change the file status + if ((isset($p_options[PCLZIP_OPT_REPLACE_NEWER])) && ($p_options[PCLZIP_OPT_REPLACE_NEWER] === true)) { + } else { + $p_entry['status'] = "newer_exist"; + + // ----- Look for PCLZIP_OPT_STOP_ON_ERROR + // For historical reason first PclZip implementation does not stop + // when this kind of error occurs. + if ((isset($p_options[PCLZIP_OPT_STOP_ON_ERROR])) && ($p_options[PCLZIP_OPT_STOP_ON_ERROR] === true)) { + + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, "Newer version of '" . $p_entry['filename'] . "' exists " . "and option PCLZIP_OPT_REPLACE_NEWER is not selected"); + + return PclZip::errorCode(); + } + } + } else { + } + + // ----- Check the directory availability and create it if necessary + } else { + if ((($p_entry['external'] & 0x00000010) == 0x00000010) || (substr($p_entry['filename'], -1) == '/')) { + $v_dir_to_check = $p_entry['filename']; + } elseif (!strstr($p_entry['filename'], "/")) { + $v_dir_to_check = ""; + } else { + $v_dir_to_check = dirname($p_entry['filename']); + } + + if (($v_result = $this->privDirCheck($v_dir_to_check, (($p_entry['external'] & 0x00000010) == 0x00000010))) != 1) { + + // ----- Change the file status + $p_entry['status'] = "path_creation_fail"; + + // ----- Return + //return $v_result; + $v_result = 1; + } + } + } + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external'] & 0x00000010) == 0x00000010)) { + // ----- Look for not compressed file + if ($p_entry['compression'] == 0) { + + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + + // ----- Change the file status + $p_entry['status'] = "write_error"; + + // ----- Return + return $v_result; + } + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['compressed_size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + /* Try to speed up the code + $v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_binary_data, $v_read_size); + */ + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Closing the destination file + fclose($v_dest_file); + + // ----- Change the file mtime + touch($p_entry['filename'], $p_entry['mtime']); + + } else { + // ----- TBC + // Need to be finished + if (($p_entry['flag'] & 1) == 1) { + PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, 'File \'' . $p_entry['filename'] . '\' is encrypted. Encrypted files are not supported.'); + + return PclZip::errorCode(); + } + + // ----- Look for using temporary file to unzip + if ((!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON]) || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]) && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_entry['size'])))) { + $v_result = $this->privExtractFileUsingTempFile($p_entry, $p_options); + if ($v_result < PCLZIP_ERR_NO_ERROR) { + return $v_result; + } + + // ----- Look for extract in memory + } else { + + // ----- Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Decompress the file + $v_file_content = @gzinflate($v_buffer); + unset($v_buffer); + if ($v_file_content === false) { + + // ----- Change the file status + // TBC + $p_entry['status'] = "error"; + + return $v_result; + } + + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + + // ----- Change the file status + $p_entry['status'] = "write_error"; + + return $v_result; + } + + // ----- Write the uncompressed data + @fwrite($v_dest_file, $v_file_content, $p_entry['size']); + unset($v_file_content); + + // ----- Closing the destination file + @fclose($v_dest_file); + + } + + // ----- Change the file mtime + @touch($p_entry['filename'], $p_entry['mtime']); + } + + // ----- Look for chmod option + if (isset($p_options[PCLZIP_OPT_SET_CHMOD])) { + + // ----- Change the mode of the file + @chmod($p_entry['filename'], $p_options[PCLZIP_OPT_SET_CHMOD]); + } + + } + } + + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; + + // ----- Look for post-extract callback + } elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFileUsingTempFile() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privExtractFileUsingTempFile(&$p_entry, &$p_options) + { + $v_result = 1; + + // ----- Creates a temporary file + $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.gz'; + if (($v_dest_file = @fopen($v_gzip_temp_name, "wb")) == 0) { + fclose($v_file); + PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary write mode'); + + return PclZip::errorCode(); + } + + // ----- Write gz file format header + $v_binary_data = pack('va1a1Va1a1', 0x8b1f, chr($p_entry['compression']), chr(0x00), time(), chr(0x00), chr(3)); + @fwrite($v_dest_file, $v_binary_data, 10); + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['compressed_size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Write gz file format footer + $v_binary_data = pack('VV', $p_entry['crc'], $p_entry['size']); + @fwrite($v_dest_file, $v_binary_data, 8); + + // ----- Close the temporary file + @fclose($v_dest_file); + + // ----- Opening destination file + if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) { + $p_entry['status'] = "write_error"; + + return $v_result; + } + + // ----- Open the temporary gz file + if (($v_src_file = @gzopen($v_gzip_temp_name, 'rb')) == 0) { + @fclose($v_dest_file); + $p_entry['status'] = "read_error"; + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_gzip_temp_name . '\' in binary read mode'); + + return PclZip::errorCode(); + } + + // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks + $v_size = $p_entry['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($v_src_file, $v_read_size); + //$v_binary_data = pack('a'.$v_read_size, $v_buffer); + @fwrite($v_dest_file, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + @fclose($v_dest_file); + @gzclose($v_src_file); + + // ----- Delete the temporary file + @unlink($v_gzip_temp_name); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFileInOutput() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privExtractFileInOutput(&$p_entry, &$p_options) + { + $v_result = 1; + + // ----- Read the file header + if (($v_result = $this->privReadFileHeader($v_header)) != 1) { + return $v_result; + } + + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } + + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } + + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } + + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + } + + // ----- Trace + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external'] & 0x00000010) == 0x00000010)) { + // ----- Look for not compressed file + if ($p_entry['compressed_size'] == $p_entry['size']) { + + // ----- Read the file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Send the file to the output + echo $v_buffer; + unset($v_buffer); + } else { + + // ----- Read the compressed file in a buffer (one shot) + $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Decompress the file + $v_file_content = gzinflate($v_buffer); + unset($v_buffer); + + // ----- Send the file to the output + echo $v_file_content; + unset($v_file_content); + } + } + } + + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; + + // ----- Look for post-extract callback + } elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } + + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privExtractFileAsString() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privExtractFileAsString(&$p_entry, &$p_string, &$p_options) + { + $v_result = 1; + + // ----- Read the file header + $v_header = array(); + if (($v_result = $this->privReadFileHeader($v_header)) != 1) { + // ----- Return + return $v_result; + } + + // ----- Check that the file header is coherent with $p_entry info + if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) { + // TBC + } + + // ----- Look for pre-extract callback + if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header); + if ($v_result == 0) { + // ----- Change the file status + $p_entry['status'] = "skipped"; + $v_result = 1; + } + + // ----- Look for abort result + if ($v_result == 2) { + // ----- This status is internal and will be changed in 'skipped' + $p_entry['status'] = "aborted"; + $v_result = PCLZIP_ERR_USER_ABORTED; + } + + // ----- Update the informations + // Only some fields can be modified + $p_entry['filename'] = $v_local_header['filename']; + } + + // ----- Look if extraction should be done + if ($p_entry['status'] == 'ok') { + + // ----- Do the extraction (if not a folder) + if (!(($p_entry['external'] & 0x00000010) == 0x00000010)) { + // ----- Look for not compressed file + // if ($p_entry['compressed_size'] == $p_entry['size']) + if ($p_entry['compression'] == 0) { + + // ----- Reading the file + $p_string = @fread($this->zip_fd, $p_entry['compressed_size']); + } else { + + // ----- Reading the file + $v_data = @fread($this->zip_fd, $p_entry['compressed_size']); + + // ----- Decompress the file + if (($p_string = @gzinflate($v_data)) === false) { + // TBC + } + } + + // ----- Trace + } else { + // TBC : error : can not extract a folder in a string + } + + } + + // ----- Change abort status + if ($p_entry['status'] == "aborted") { + $p_entry['status'] = "skipped"; + + // ----- Look for post-extract callback + } elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) { + + // ----- Generate a local information + $v_local_header = array(); + $this->privConvertHeader2FileInfo($p_entry, $v_local_header); + + // ----- Swap the content to header + $v_local_header['content'] = $p_string; + $p_string = ''; + + // ----- Call the callback + // Here I do not use call_user_func() because I need to send a reference to the + // header. + // eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);'); + $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header); + + // ----- Swap back the content to header + $p_string = $v_local_header['content']; + unset($v_local_header['content']); + + // ----- Look for abort result + if ($v_result == 2) { + $v_result = PCLZIP_ERR_USER_ABORTED; + } + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privReadFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privReadFileHeader(&$p_header) + { + $v_result = 1; + + // ----- Read the 4 bytes signature + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); + + // ----- Check signature + if ($v_data['id'] != 0x04034b50) { + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the first 42 bytes of the header + $v_binary_data = fread($this->zip_fd, 26); + + // ----- Look for invalid block size + if (strlen($v_binary_data) != 26) { + $p_header['filename'] = ""; + $p_header['status'] = "invalid_header"; + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : " . strlen($v_binary_data)); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Extract the values + $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data); + + // ----- Get filename + $p_header['filename'] = fread($this->zip_fd, $v_data['filename_len']); + + // ----- Get extra_fields + if ($v_data['extra_len'] != 0) { + $p_header['extra'] = fread($this->zip_fd, $v_data['extra_len']); + } else { + $p_header['extra'] = ''; + } + + // ----- Extract properties + $p_header['version_extracted'] = $v_data['version']; + $p_header['compression'] = $v_data['compression']; + $p_header['size'] = $v_data['size']; + $p_header['compressed_size'] = $v_data['compressed_size']; + $p_header['crc'] = $v_data['crc']; + $p_header['flag'] = $v_data['flag']; + $p_header['filename_len'] = $v_data['filename_len']; + + // ----- Recuperate date in UNIX format + $p_header['mdate'] = $v_data['mdate']; + $p_header['mtime'] = $v_data['mtime']; + if ($p_header['mdate'] && $p_header['mtime']) { + // ----- Extract time + $v_hour = ($p_header['mtime'] & 0xF800) >> 11; + $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; + $v_seconde = ($p_header['mtime'] & 0x001F) * 2; + + // ----- Extract date + $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; + $v_month = ($p_header['mdate'] & 0x01E0) >> 5; + $v_day = $p_header['mdate'] & 0x001F; + + // ----- Get UNIX date format + $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + + } else { + $p_header['mtime'] = time(); + } + + // TBC + //for (reset($v_data); $key = key($v_data); next($v_data)) { + //} + + // ----- Set the stored filename + $p_header['stored_filename'] = $p_header['filename']; + + // ----- Set the status field + $p_header['status'] = "ok"; + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privReadCentralFileHeader() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privReadCentralFileHeader(&$p_header) + { + $v_result = 1; + + // ----- Read the 4 bytes signature + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = unpack('Vid', $v_binary_data); + + // ----- Check signature + if ($v_data['id'] != 0x02014b50) { + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the first 42 bytes of the header + $v_binary_data = fread($this->zip_fd, 42); + + // ----- Look for invalid block size + if (strlen($v_binary_data) != 42) { + $p_header['filename'] = ""; + $p_header['status'] = "invalid_header"; + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : " . strlen($v_binary_data)); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Extract the values + $p_header = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $v_binary_data); + + // ----- Get filename + if ($p_header['filename_len'] != 0) { + $p_header['filename'] = fread($this->zip_fd, $p_header['filename_len']); + } else { + $p_header['filename'] = ''; + } + + // ----- Get extra + if ($p_header['extra_len'] != 0) { + $p_header['extra'] = fread($this->zip_fd, $p_header['extra_len']); + } else { + $p_header['extra'] = ''; + } + + // ----- Get comment + if ($p_header['comment_len'] != 0) { + $p_header['comment'] = fread($this->zip_fd, $p_header['comment_len']); + } else { + $p_header['comment'] = ''; + } + + // ----- Extract properties + + // ----- Recuperate date in UNIX format + //if ($p_header['mdate'] && $p_header['mtime']) + // TBC : bug : this was ignoring time with 0/0/0 + if (1) { + // ----- Extract time + $v_hour = ($p_header['mtime'] & 0xF800) >> 11; + $v_minute = ($p_header['mtime'] & 0x07E0) >> 5; + $v_seconde = ($p_header['mtime'] & 0x001F) * 2; + + // ----- Extract date + $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980; + $v_month = ($p_header['mdate'] & 0x01E0) >> 5; + $v_day = $p_header['mdate'] & 0x001F; + + // ----- Get UNIX date format + $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year); + + } else { + $p_header['mtime'] = time(); + } + + // ----- Set the stored filename + $p_header['stored_filename'] = $p_header['filename']; + + // ----- Set default status to ok + $p_header['status'] = 'ok'; + + // ----- Look if it is a directory + if (substr($p_header['filename'], -1) == '/') { + //$p_header['external'] = 0x41FF0010; + $p_header['external'] = 0x00000010; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privCheckFileHeaders() + // Description : + // Parameters : + // Return Values : + // 1 on success, + // 0 on error; + // -------------------------------------------------------------------------------- + public function privCheckFileHeaders(&$p_local_header, &$p_central_header) + { + $v_result = 1; + + // ----- Check the static values + // TBC + if ($p_local_header['filename'] != $p_central_header['filename']) { + } + if ($p_local_header['version_extracted'] != $p_central_header['version_extracted']) { + } + if ($p_local_header['flag'] != $p_central_header['flag']) { + } + if ($p_local_header['compression'] != $p_central_header['compression']) { + } + if ($p_local_header['mtime'] != $p_central_header['mtime']) { + } + if ($p_local_header['filename_len'] != $p_central_header['filename_len']) { + } + + // ----- Look for flag bit 3 + if (($p_local_header['flag'] & 8) == 8) { + $p_local_header['size'] = $p_central_header['size']; + $p_local_header['compressed_size'] = $p_central_header['compressed_size']; + $p_local_header['crc'] = $p_central_header['crc']; + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privReadEndCentralDir() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privReadEndCentralDir(&$p_central_dir) + { + $v_result = 1; + + // ----- Go to the end of the zip file + $v_size = filesize($this->zipname); + @fseek($this->zip_fd, $v_size); + if (@ftell($this->zip_fd) != $v_size) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to go to the end of the archive \'' . $this->zipname . '\''); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- First try : look if this is an archive with no commentaries (most of the time) + // in this case the end of central dir is at 22 bytes of the file end + $v_found = 0; + if ($v_size > 26) { + @fseek($this->zip_fd, $v_size - 22); + if (($v_pos = @ftell($this->zip_fd)) != ($v_size - 22)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \'' . $this->zipname . '\''); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read for bytes + $v_binary_data = @fread($this->zip_fd, 4); + $v_data = @unpack('Vid', $v_binary_data); + + // ----- Check signature + if ($v_data['id'] == 0x06054b50) { + $v_found = 1; + } + + $v_pos = ftell($this->zip_fd); + } + + // ----- Go back to the maximum possible size of the Central Dir End Record + if (!$v_found) { + $v_maximum_size = 65557; // 0xFFFF + 22; + if ($v_maximum_size > $v_size) { + $v_maximum_size = $v_size; + } + @fseek($this->zip_fd, $v_size - $v_maximum_size); + if (@ftell($this->zip_fd) != ($v_size - $v_maximum_size)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \'' . $this->zipname . '\''); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read byte per byte in order to find the signature + $v_pos = ftell($this->zip_fd); + $v_bytes = 0x00000000; + while ($v_pos < $v_size) { + // ----- Read a byte + $v_byte = @fread($this->zip_fd, 1); + + // ----- Add the byte + //$v_bytes = ($v_bytes << 8) | Ord($v_byte); + // Note we mask the old value down such that once shifted we can never end up with more than a 32bit number + // Otherwise on systems where we have 64bit integers the check below for the magic number will fail. + $v_bytes = (($v_bytes & 0xFFFFFF) << 8) | ord($v_byte); + + // ----- Compare the bytes + if ($v_bytes == 0x504b0506) { + $v_pos++; + break; + } + + $v_pos++; + } + + // ----- Look if not found end of central dir + if ($v_pos == $v_size) { + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Unable to find End of Central Dir Record signature"); + + // ----- Return + return PclZip::errorCode(); + } + } + + // ----- Read the first 18 bytes of the header + $v_binary_data = fread($this->zip_fd, 18); + + // ----- Look for invalid block size + if (strlen($v_binary_data) != 18) { + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid End of Central Dir Record size : " . strlen($v_binary_data)); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Extract the values + $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data); + + // ----- Check the global size + if (($v_pos + $v_data['comment_size'] + 18) != $v_size) { + + // ----- Removed in release 2.2 see readme file + // The check of the file size is a little too strict. + // Some bugs where found when a zip is encrypted/decrypted with 'crypt'. + // While decrypted, zip has training 0 bytes + if (0) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'The central dir is not at the end of the archive.' . ' Some trailing bytes exists after the archive.'); + + // ----- Return + return PclZip::errorCode(); + } + } + + // ----- Get comment + if ($v_data['comment_size'] != 0) { + $p_central_dir['comment'] = fread($this->zip_fd, $v_data['comment_size']); + } else { + $p_central_dir['comment'] = ''; + } + + $p_central_dir['entries'] = $v_data['entries']; + $p_central_dir['disk_entries'] = $v_data['disk_entries']; + $p_central_dir['offset'] = $v_data['offset']; + $p_central_dir['size'] = $v_data['size']; + $p_central_dir['disk'] = $v_data['disk']; + $p_central_dir['disk_start'] = $v_data['disk_start']; + + // TBC + //for (reset($p_central_dir); $key = key($p_central_dir); next($p_central_dir)) { + //} + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDeleteByRule() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privDeleteByRule(&$p_result_list, &$p_options) + { + $v_result = 1; + $v_list_detail = array(); + + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) { + // ----- Return + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privCloseFd(); + + return $v_result; + } + + // ----- Go to beginning of File + @rewind($this->zip_fd); + + // ----- Scan all the files + // ----- Start at beginning of Central Dir + $v_pos_entry = $v_central_dir['offset']; + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_pos_entry)) { + // ----- Close the zip file + $this->privCloseFd(); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read each entry + $v_header_list = array(); + $j_start = 0; + for ($i = 0, $v_nb_extracted = 0; $i < $v_central_dir['entries']; $i++) { + + // ----- Read the file header + $v_header_list[$v_nb_extracted] = array(); + if (($v_result = $this->privReadCentralFileHeader($v_header_list[$v_nb_extracted])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + + return $v_result; + } + + // ----- Store the index + $v_header_list[$v_nb_extracted]['index'] = $i; + + // ----- Look for the specific extract rules + $v_found = false; + + // ----- Look for extract by name rule + if ((isset($p_options[PCLZIP_OPT_BY_NAME])) && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) { + + // ----- Look if the filename is in the list + for ($j = 0; ($j < sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_found); $j++) { + + // ----- Look for a directory + if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") { + + // ----- Look if the directory is in the filename path + if ((strlen($v_header_list[$v_nb_extracted]['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) && (substr($v_header_list[$v_nb_extracted]['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_found = true; + } elseif ((($v_header_list[$v_nb_extracted]['external'] & 0x00000010) == 0x00000010) /* Indicates a folder */ && ($v_header_list[$v_nb_extracted]['stored_filename'] . '/' == $p_options[PCLZIP_OPT_BY_NAME][$j])) { + $v_found = true; + } + + // ----- Look for a filename + } elseif ($v_header_list[$v_nb_extracted]['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) { + $v_found = true; + } + } + + // ----- Look for extract by ereg rule + // ereg() is deprecated with PHP 5.3 + /* + elseif ( (isset($p_options[PCLZIP_OPT_BY_EREG])) + && ($p_options[PCLZIP_OPT_BY_EREG] != "")) { + + if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header_list[$v_nb_extracted]['stored_filename'])) { + $v_found = true; + } + } + */ + + // ----- Look for extract by preg rule + } elseif ((isset($p_options[PCLZIP_OPT_BY_PREG])) && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { + + if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header_list[$v_nb_extracted]['stored_filename'])) { + $v_found = true; + } + + // ----- Look for extract by index rule + } elseif ((isset($p_options[PCLZIP_OPT_BY_INDEX])) && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) { + + // ----- Look if the index is in the list + for ($j = $j_start; ($j < sizeof($p_options[PCLZIP_OPT_BY_INDEX])) && (!$v_found); $j++) { + + if (($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i <= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) { + $v_found = true; + } + if ($i >= $p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) { + $j_start = $j + 1; + } + + if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start'] > $i) { + break; + } + } + } else { + $v_found = true; + } + + // ----- Look for deletion + if ($v_found) { + unset($v_header_list[$v_nb_extracted]); + } else { + $v_nb_extracted++; + } + } + + // ----- Look if something need to be deleted + if ($v_nb_extracted > 0) { + + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.tmp'; + + // ----- Creates a temporary zip archive + $v_temp_zip = new PclZip($v_zip_temp_name); + + // ----- Open the temporary zip file in write mode + if (($v_result = $v_temp_zip->privOpenFd('wb')) != 1) { + $this->privCloseFd(); + + // ----- Return + return $v_result; + } + + // ----- Look which file need to be kept + for ($i = 0; $i < sizeof($v_header_list); $i++) { + + // ----- Calculate the position of the header + @rewind($this->zip_fd); + if (@fseek($this->zip_fd, $v_header_list[$i]['offset'])) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Read the file header + $v_local_header = array(); + if (($v_result = $this->privReadFileHeader($v_local_header)) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Check that local file header is same as central file header + if ($this->privCheckFileHeaders($v_local_header, $v_header_list[$i]) != 1) { + // TBC + } + unset($v_local_header); + + // ----- Write the file header + if (($v_result = $v_temp_zip->privWriteFileHeader($v_header_list[$i])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Read/write the data block + if (($v_result = PclZipUtilCopyBlock($this->zip_fd, $v_temp_zip->zip_fd, $v_header_list[$i]['compressed_size'])) != 1) { + // ----- Close the zip file + $this->privCloseFd(); + $v_temp_zip->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($v_temp_zip->zip_fd); + + // ----- Re-Create the Central Dir files header + for ($i = 0; $i < sizeof($v_header_list); $i++) { + // ----- Create the file header + if (($v_result = $v_temp_zip->privWriteCentralFileHeader($v_header_list[$i])) != 1) { + $v_temp_zip->privCloseFd(); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Transform the header to a 'usable' info + $v_temp_zip->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]); + } + + // ----- Zip file comment + $v_comment = ''; + if (isset($p_options[PCLZIP_OPT_COMMENT])) { + $v_comment = $p_options[PCLZIP_OPT_COMMENT]; + } + + // ----- Calculate the size of the central header + $v_size = @ftell($v_temp_zip->zip_fd) - $v_offset; + + // ----- Create the central dir footer + if (($v_result = $v_temp_zip->privWriteCentralHeader(sizeof($v_header_list), $v_size, $v_offset, $v_comment)) != 1) { + // ----- Reset the file list + unset($v_header_list); + $v_temp_zip->privCloseFd(); + $this->privCloseFd(); + @unlink($v_zip_temp_name); + + // ----- Return + return $v_result; + } + + // ----- Close + $v_temp_zip->privCloseFd(); + $this->privCloseFd(); + + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); + + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); + + // ----- Destroy the temporary archive + unset($v_temp_zip); + + // ----- Remove every files : reset the file + } elseif ($v_central_dir['entries'] != 0) { + $this->privCloseFd(); + + if (($v_result = $this->privOpenFd('wb')) != 1) { + return $v_result; + } + + if (($v_result = $this->privWriteCentralHeader(0, 0, 0, '')) != 1) { + return $v_result; + } + + $this->privCloseFd(); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDirCheck() + // Description : + // Check if a directory exists, if not it creates it and all the parents directory + // which may be useful. + // Parameters : + // $p_dir : Directory path to check. + // Return Values : + // 1 : OK + // -1 : Unable to create directory + // -------------------------------------------------------------------------------- + public function privDirCheck($p_dir, $p_is_dir = false) + { + $v_result = 1; + + // ----- Remove the final '/' + if (($p_is_dir) && (substr($p_dir, -1) == '/')) { + $p_dir = substr($p_dir, 0, strlen($p_dir) - 1); + } + + // ----- Check the directory availability + if ((is_dir($p_dir)) || ($p_dir == "")) { + return 1; + } + + // ----- Extract parent directory + $p_parent_dir = dirname($p_dir); + + // ----- Just a check + if ($p_parent_dir != $p_dir) { + // ----- Look for parent directory + if ($p_parent_dir != "") { + if (($v_result = $this->privDirCheck($p_parent_dir)) != 1) { + return $v_result; + } + } + } + + // ----- Create the directory + if (!@mkdir($p_dir, 0777)) { + // ----- Error log + PclZip::privErrorLog(PCLZIP_ERR_DIR_CREATE_FAIL, "Unable to create directory '$p_dir'"); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privMerge() + // Description : + // If $p_archive_to_add does not exist, the function exit with a success result. + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privMerge(&$p_archive_to_add) + { + $v_result = 1; + + // ----- Look if the archive_to_add exists + if (!is_file($p_archive_to_add->zipname)) { + + // ----- Nothing to merge, so merge is a success + $v_result = 1; + + // ----- Return + return $v_result; + } + + // ----- Look if the archive exists + if (!is_file($this->zipname)) { + + // ----- Do a duplicate + $v_result = $this->privDuplicate($p_archive_to_add->zipname); + + // ----- Return + return $v_result; + } + + // ----- Open the zip file + if (($v_result = $this->privOpenFd('rb')) != 1) { + // ----- Return + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir = array(); + if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1) { + $this->privCloseFd(); + + return $v_result; + } + + // ----- Go to beginning of File + @rewind($this->zip_fd); + + // ----- Open the archive_to_add file + if (($v_result = $p_archive_to_add->privOpenFd('rb')) != 1) { + $this->privCloseFd(); + + // ----- Return + return $v_result; + } + + // ----- Read the central directory informations + $v_central_dir_to_add = array(); + if (($v_result = $p_archive_to_add->privReadEndCentralDir($v_central_dir_to_add)) != 1) { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + + return $v_result; + } + + // ----- Go to beginning of File + @rewind($p_archive_to_add->zip_fd); + + // ----- Creates a temporay file + $v_zip_temp_name = PCLZIP_TEMPORARY_DIR . uniqid('pclzip-') . '.tmp'; + + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0) { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \'' . $v_zip_temp_name . '\' in binary write mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = $v_central_dir['offset']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Copy the files from the archive_to_add into the temporary file + $v_size = $v_central_dir_to_add['offset']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($p_archive_to_add->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Store the offset of the central dir + $v_offset = @ftell($v_zip_temp_fd); + + // ----- Copy the block of file headers from the old archive + $v_size = $v_central_dir['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($this->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Copy the block of file headers from the archive_to_add + $v_size = $v_central_dir_to_add['size']; + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_archive_to_add->zip_fd, $v_read_size); + @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Merge the file comments + $v_comment = $v_central_dir['comment'] . ' ' . $v_central_dir_to_add['comment']; + + // ----- Calculate the size of the (new) central header + $v_size = @ftell($v_zip_temp_fd) - $v_offset; + + // ----- Swap the file descriptor + // Here is a trick : I swap the temporary fd with the zip fd, in order to use + // the following methods on the temporary fil and not the real archive fd + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Create the central dir footer + if (($v_result = $this->privWriteCentralHeader($v_central_dir['entries'] + $v_central_dir_to_add['entries'], $v_size, $v_offset, $v_comment)) != 1) { + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + @fclose($v_zip_temp_fd); + $this->zip_fd = null; + + // ----- Reset the file list + unset($v_header_list); + + // ----- Return + return $v_result; + } + + // ----- Swap back the file descriptor + $v_swap = $this->zip_fd; + $this->zip_fd = $v_zip_temp_fd; + $v_zip_temp_fd = $v_swap; + + // ----- Close + $this->privCloseFd(); + $p_archive_to_add->privCloseFd(); + + // ----- Close the temporary file + @fclose($v_zip_temp_fd); + + // ----- Delete the zip file + // TBC : I should test the result ... + @unlink($this->zipname); + + // ----- Rename the temporary file + // TBC : I should test the result ... + //@rename($v_zip_temp_name, $this->zipname); + PclZipUtilRename($v_zip_temp_name, $this->zipname); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDuplicate() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privDuplicate($p_archive_filename) + { + $v_result = 1; + + // ----- Look if the $p_archive_filename exists + if (!is_file($p_archive_filename)) { + + // ----- Nothing to duplicate, so duplicate is a success. + $v_result = 1; + + // ----- Return + return $v_result; + } + + // ----- Open the zip file + if (($v_result = $this->privOpenFd('wb')) != 1) { + // ----- Return + return $v_result; + } + + // ----- Open the temporary file in write mode + if (($v_zip_temp_fd = @fopen($p_archive_filename, 'rb')) == 0) { + $this->privCloseFd(); + + PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive file \'' . $p_archive_filename . '\' in binary write mode'); + + // ----- Return + return PclZip::errorCode(); + } + + // ----- Copy the files from the archive to the temporary file + // TBC : Here I should better append the file and go back to erase the central dir + $v_size = filesize($p_archive_filename); + while ($v_size != 0) { + $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = fread($v_zip_temp_fd, $v_read_size); + @fwrite($this->zip_fd, $v_buffer, $v_read_size); + $v_size -= $v_read_size; + } + + // ----- Close + $this->privCloseFd(); + + // ----- Close the temporary file + @fclose($v_zip_temp_fd); + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privErrorLog() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function privErrorLog($p_error_code = 0, $p_error_string = '') + { + if (PCLZIP_ERROR_EXTERNAL == 1) { + PclError($p_error_code, $p_error_string); + } else { + $this->error_code = $p_error_code; + $this->error_string = $p_error_string; + } + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privErrorReset() + // Description : + // Parameters : + // -------------------------------------------------------------------------------- + public function privErrorReset() + { + if (PCLZIP_ERROR_EXTERNAL == 1) { + PclErrorReset(); + } else { + $this->error_code = 0; + $this->error_string = ''; + } + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privDisableMagicQuotes() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privDisableMagicQuotes() + { + $v_result = 1; + + // ----- Look if function exists + if ((!function_exists("get_magic_quotes_runtime")) || (!function_exists("set_magic_quotes_runtime"))) { + return $v_result; + } + + // ----- Look if already done + if ($this->magic_quotes_status != -1) { + return $v_result; + } + + // ----- Get and memorize the magic_quote value + $this->magic_quotes_status = @get_magic_quotes_runtime(); + + // ----- Disable magic_quotes + if ($this->magic_quotes_status == 1) { + @set_magic_quotes_runtime(0); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- + + // -------------------------------------------------------------------------------- + // Function : privSwapBackMagicQuotes() + // Description : + // Parameters : + // Return Values : + // -------------------------------------------------------------------------------- + public function privSwapBackMagicQuotes() + { + $v_result = 1; + + // ----- Look if function exists + if ((!function_exists("get_magic_quotes_runtime")) || (!function_exists("set_magic_quotes_runtime"))) { + return $v_result; + } + + // ----- Look if something to do + if ($this->magic_quotes_status != -1) { + return $v_result; + } + + // ----- Swap back magic_quotes + if ($this->magic_quotes_status == 1) { + @set_magic_quotes_runtime($this->magic_quotes_status); + } + + // ----- Return + return $v_result; + } + // -------------------------------------------------------------------------------- +} + +// End of class +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilPathReduction() +// Description : +// Parameters : +// Return Values : +// -------------------------------------------------------------------------------- +function PclZipUtilPathReduction($p_dir) +{ + $v_result = ""; + + // ----- Look for not empty path + if ($p_dir != "") { + // ----- Explode path by directory names + $v_list = explode("/", $p_dir); + + // ----- Study directories from last to first + $v_skip = 0; + for ($i = sizeof($v_list) - 1; $i >= 0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } elseif ($v_list[$i] == "..") { + $v_skip++; + } elseif ($v_list[$i] == "") { + // ----- First '/' i.e. root slash + if ($i == 0) { + $v_result = "/" . $v_result; + if ($v_skip > 0) { + // ----- It is an invalid path, so the path is not modified + // TBC + $v_result = $p_dir; + $v_skip = 0; + } + + // ----- Last '/' i.e. indicates a directory + } elseif ($i == (sizeof($v_list) - 1)) { + $v_result = $v_list[$i]; + + // ----- Double '/' inside the path + } else { + // ----- Ignore only the double '//' in path, + // but not the first and last '/' + } + } else { + // ----- Look for item to skip + if ($v_skip > 0) { + $v_skip--; + } else { + $v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? "/" . $v_result : ""); + } + } + } + + // ----- Look for skip + if ($v_skip > 0) { + while ($v_skip > 0) { + $v_result = '../' . $v_result; + $v_skip--; + } + } + } + + // ----- Return + return $v_result; +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilPathInclusion() +// Description : +// This function indicates if the path $p_path is under the $p_dir tree. Or, +// said in an other way, if the file or sub-dir $p_path is inside the dir +// $p_dir. +// The function indicates also if the path is exactly the same as the dir. +// This function supports path with duplicated '/' like '//', but does not +// support '.' or '..' statements. +// Parameters : +// Return Values : +// 0 if $p_path is not inside directory $p_dir +// 1 if $p_path is inside directory $p_dir +// 2 if $p_path is exactly the same as $p_dir +// -------------------------------------------------------------------------------- +function PclZipUtilPathInclusion($p_dir, $p_path) +{ + $v_result = 1; + + // ----- Look for path beginning by ./ + if (($p_dir == '.') || ((strlen($p_dir) >= 2) && (substr($p_dir, 0, 2) == './'))) { + $p_dir = PclZipUtilTranslateWinPath(getcwd(), false) . '/' . substr($p_dir, 1); + } + if (($p_path == '.') || ((strlen($p_path) >= 2) && (substr($p_path, 0, 2) == './'))) { + $p_path = PclZipUtilTranslateWinPath(getcwd(), false) . '/' . substr($p_path, 1); + } + + // ----- Explode dir and path by directory separator + $v_list_dir = explode("/", $p_dir); + $v_list_dir_size = sizeof($v_list_dir); + $v_list_path = explode("/", $p_path); + $v_list_path_size = sizeof($v_list_path); + + // ----- Study directories paths + $i = 0; + $j = 0; + while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result)) { + + // ----- Look for empty dir (path reduction) + if ($v_list_dir[$i] == '') { + $i++; + continue; + } + if ($v_list_path[$j] == '') { + $j++; + continue; + } + + // ----- Compare the items + if (($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ($v_list_path[$j] != '')) { + $v_result = 0; + } + + // ----- Next items + $i++; + $j++; + } + + // ----- Look if everything seems to be the same + if ($v_result) { + // ----- Skip all the empty items + while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) { + $j++; + } + while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) { + $i++; + } + + if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) { + // ----- There are exactly the same + $v_result = 2; + } elseif ($i < $v_list_dir_size) { + // ----- The path is shorter than the dir + $v_result = 0; + } + } + + // ----- Return + return $v_result; +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilCopyBlock() +// Description : +// Parameters : +// $p_mode : read/write compression mode +// 0 : src & dest normal +// 1 : src gzip, dest normal +// 2 : src normal, dest gzip +// 3 : src & dest gzip +// Return Values : +// -------------------------------------------------------------------------------- +function PclZipUtilCopyBlock($p_src, $p_dest, $p_size, $p_mode = 0) +{ + $v_result = 1; + + if ($p_mode == 0) { + while ($p_size != 0) { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_src, $v_read_size); + @fwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } elseif ($p_mode == 1) { + while ($p_size != 0) { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($p_src, $v_read_size); + @fwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } elseif ($p_mode == 2) { + while ($p_size != 0) { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @fread($p_src, $v_read_size); + @gzwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } elseif ($p_mode == 3) { + while ($p_size != 0) { + $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE); + $v_buffer = @gzread($p_src, $v_read_size); + @gzwrite($p_dest, $v_buffer, $v_read_size); + $p_size -= $v_read_size; + } + } + + // ----- Return + return $v_result; +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilRename() +// Description : +// This function tries to do a simple rename() function. If it fails, it +// tries to copy the $p_src file in a new $p_dest file and then unlink the +// first one. +// Parameters : +// $p_src : Old filename +// $p_dest : New filename +// Return Values : +// 1 on success, 0 on failure. +// -------------------------------------------------------------------------------- +function PclZipUtilRename($p_src, $p_dest) +{ + $v_result = 1; + + // ----- Try to rename the files + if (!@rename($p_src, $p_dest)) { + + // ----- Try to copy & unlink the src + if (!@copy($p_src, $p_dest)) { + $v_result = 0; + } elseif (!@unlink($p_src)) { + $v_result = 0; + } + } + + // ----- Return + return $v_result; +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilOptionText() +// Description : +// Translate option value in text. Mainly for debug purpose. +// Parameters : +// $p_option : the option value. +// Return Values : +// The option text value. +// -------------------------------------------------------------------------------- +function PclZipUtilOptionText($p_option) +{ + + $v_list = get_defined_constants(); + for (reset($v_list); $v_key = key($v_list); next($v_list)) { + $v_prefix = substr($v_key, 0, 10); + if ((($v_prefix == 'PCLZIP_OPT') || ($v_prefix == 'PCLZIP_CB_') || ($v_prefix == 'PCLZIP_ATT')) && ($v_list[$v_key] == $p_option)) { + return $v_key; + } + } + + $v_result = 'Unknown'; + + return $v_result; +} +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Function : PclZipUtilTranslateWinPath() +// Description : +// Translate windows path by replacing '\' by '/' and optionally removing +// drive letter. +// Parameters : +// $p_path : path to translate. +// $p_remove_disk_letter : true | false +// Return Values : +// The path translated. +// -------------------------------------------------------------------------------- +function PclZipUtilTranslateWinPath($p_path, $p_remove_disk_letter = true) +{ + if (stristr(php_uname(), 'windows')) { + // ----- Look for potential disk letter + if (($p_remove_disk_letter) && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position + 1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + + return $p_path; +} +// -------------------------------------------------------------------------------- diff --git a/PhpOffice/PhpWord/Shared/ZipArchive.php b/PhpOffice/PhpWord/Shared/ZipArchive.php new file mode 100644 index 0000000..bc71e74 --- /dev/null +++ b/PhpOffice/PhpWord/Shared/ZipArchive.php @@ -0,0 +1,399 @@ +usePclzip = (Settings::getZipClass() != 'ZipArchive'); + if ($this->usePclzip) { + if (!defined('PCLZIP_TEMPORARY_DIR')) { + define('PCLZIP_TEMPORARY_DIR', Settings::getTempDir() . '/'); + } + require_once 'PCLZip/pclzip.lib.php'; + } + } + + /** + * Catch function calls: pass to ZipArchive or PCLZip + * + * `call_user_func_array` can only used for public function, hence the `public` in all `pcl...` methods + * + * @param mixed $function + * @param mixed $args + * @return mixed + */ + public function __call($function, $args) + { + // Set object and function + $zipFunction = $function; + if (!$this->usePclzip) { + $zipObject = $this->zip; + } else { + $zipObject = $this; + $zipFunction = "pclzip{$zipFunction}"; + } + + // Run function + $result = false; + if (method_exists($zipObject, $zipFunction)) { + $result = @call_user_func_array(array($zipObject, $zipFunction), $args); + } + + return $result; + } + + /** + * Open a new zip archive + * + * @param string $filename The file name of the ZIP archive to open + * @param int $flags The mode to use to open the archive + * @return bool + */ + public function open($filename, $flags = null) + { + $result = true; + $this->filename = $filename; + $this->tempDir = Settings::getTempDir(); + + if (!$this->usePclzip) { + $zip = new \ZipArchive(); + $result = $zip->open($this->filename, $flags); + + // Scrutizer will report the property numFiles does not exist + // See https://github.com/scrutinizer-ci/php-analyzer/issues/190 + $this->numFiles = $zip->numFiles; + } else { + $zip = new \PclZip($this->filename); + $zipContent = $zip->listContent(); + $this->numFiles = is_array($zipContent) ? count($zipContent) : 0; + } + $this->zip = $zip; + + return $result; + } + + /** + * Close the active archive + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * + * @return bool + * + * @codeCoverageIgnore Can't find any test case. Uncomment when found. + */ + public function close() + { + if (!$this->usePclzip) { + if ($this->zip->close() === false) { + throw new Exception("Could not close zip file {$this->filename}: "); + } + } + + return true; + } + + /** + * Extract the archive contents (emulate \ZipArchive) + * + * @param string $destination + * @param string|array $entries + * @return bool + * @since 0.10.0 + */ + public function extractTo($destination, $entries = null) + { + if (!is_dir($destination)) { + return false; + } + + if (!$this->usePclzip) { + return $this->zip->extractTo($destination, $entries); + } + + return $this->pclzipExtractTo($destination, $entries); + } + + /** + * Extract file from archive by given file name (emulate \ZipArchive) + * + * @param string $filename Filename for the file in zip archive + * @return string $contents File string contents + */ + public function getFromName($filename) + { + if (!$this->usePclzip) { + $contents = $this->zip->getFromName($filename); + if ($contents === false) { + $filename = substr($filename, 1); + $contents = $this->zip->getFromName($filename); + } + } else { + $contents = $this->pclzipGetFromName($filename); + } + + return $contents; + } + + /** + * Add a new file to the zip archive (emulate \ZipArchive) + * + * @param string $filename Directory/Name of the file to add to the zip archive + * @param string $localname Directory/Name of the file added to the zip + * @return bool + */ + public function pclzipAddFile($filename, $localname = null) + { + /** @var \PclZip $zip Type hint */ + $zip = $this->zip; + + // Bugfix GH-261 https://github.com/PHPOffice/PHPWord/pull/261 + $realpathFilename = realpath($filename); + if ($realpathFilename !== false) { + $filename = $realpathFilename; + } + + $filenameParts = pathinfo($filename); + $localnameParts = pathinfo($localname); + + // To Rename the file while adding it to the zip we + // need to create a temp file with the correct name + $tempFile = false; + if ($filenameParts['basename'] != $localnameParts['basename']) { + $tempFile = true; // temp file created + $temppath = $this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename']; + copy($filename, $temppath); + $filename = $temppath; + $filenameParts = pathinfo($temppath); + } + + $pathRemoved = $filenameParts['dirname']; + $pathAdded = $localnameParts['dirname']; + + if (!$this->usePclzip) { + $pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/'); + //$res = $zip->addFile($filename, $pathAdded); + $res = $zip->addFromString($pathAdded, file_get_contents($filename)); // addFile can't use subfolders in some cases + } else { + $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded); + } + + if ($tempFile) { + // Remove temp file, if created + unlink($this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename']); + } + + return $res != 0; + } + + /** + * Add a new file to the zip archive from a string of raw data (emulate \ZipArchive) + * + * @param string $localname Directory/Name of the file to add to the zip archive + * @param string $contents String of data to add to the zip archive + * @return bool + */ + public function pclzipAddFromString($localname, $contents) + { + /** @var \PclZip $zip Type hint */ + $zip = $this->zip; + $filenameParts = pathinfo($localname); + + // Write $contents to a temp file + $handle = fopen($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'], 'wb'); + fwrite($handle, $contents); + fclose($handle); + + // Add temp file to zip + $filename = $this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']; + $pathRemoved = $this->tempDir; + $pathAdded = $filenameParts['dirname']; + + $res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded); + + // Remove temp file + @unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']); + + return $res != 0; + } + + /** + * Extract the archive contents (emulate \ZipArchive) + * + * @param string $destination + * @param string|array $entries + * @return bool + * @since 0.10.0 + */ + public function pclzipExtractTo($destination, $entries = null) + { + /** @var \PclZip $zip Type hint */ + $zip = $this->zip; + + // Extract all files + if (is_null($entries)) { + $result = $zip->extract(PCLZIP_OPT_PATH, $destination); + + return $result > 0; + } + + // Extract by entries + if (!is_array($entries)) { + $entries = array($entries); + } + foreach ($entries as $entry) { + $entryIndex = $this->locateName($entry); + $result = $zip->extractByIndex($entryIndex, PCLZIP_OPT_PATH, $destination); + if ($result <= 0) { + return false; + } + } + + return true; + } + + /** + * Extract file from archive by given file name (emulate \ZipArchive) + * + * @param string $filename Filename for the file in zip archive + * @return string $contents File string contents + */ + public function pclzipGetFromName($filename) + { + /** @var \PclZip $zip Type hint */ + $zip = $this->zip; + $listIndex = $this->pclzipLocateName($filename); + $contents = false; + + if ($listIndex !== false) { + $extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING); + } else { + $filename = substr($filename, 1); + $listIndex = $this->pclzipLocateName($filename); + $extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING); + } + if ((is_array($extracted)) && ($extracted != 0)) { + $contents = $extracted[0]['content']; + } + + return $contents; + } + + /** + * Returns the name of an entry using its index (emulate \ZipArchive) + * + * @param int $index + * @return string|bool + * @since 0.10.0 + */ + public function pclzipGetNameIndex($index) + { + /** @var \PclZip $zip Type hint */ + $zip = $this->zip; + $list = $zip->listContent(); + if (isset($list[$index])) { + return $list[$index]['filename']; + } + + return false; + } + + /** + * Returns the index of the entry in the archive (emulate \ZipArchive) + * + * @param string $filename Filename for the file in zip archive + * @return int + */ + public function pclzipLocateName($filename) + { + /** @var \PclZip $zip Type hint */ + $zip = $this->zip; + $list = $zip->listContent(); + $listCount = count($list); + $listIndex = -1; + for ($i = 0; $i < $listCount; ++$i) { + if (strtolower($list[$i]['filename']) == strtolower($filename) || + strtolower($list[$i]['stored_filename']) == strtolower($filename)) { + $listIndex = $i; + break; + } + } + + return ($listIndex > -1) ? $listIndex : false; + } +} diff --git a/PhpOffice/PhpWord/SimpleType/DocProtect.php b/PhpOffice/PhpWord/SimpleType/DocProtect.php new file mode 100644 index 0000000..e386913 --- /dev/null +++ b/PhpOffice/PhpWord/SimpleType/DocProtect.php @@ -0,0 +1,55 @@ +setStyleByArray($value); + } elseif ($value instanceof AbstractStyle) { + if (get_class($style) == get_class($value)) { + $style = $value; + } + } + } + $style->setStyleName($name); + $style->setIndex(self::countStyles() + 1); // One based index + self::$styles[$name] = $style; + } + + return self::getStyle($name); + } +} diff --git a/PhpOffice/PhpWord/Style/AbstractStyle.php b/PhpOffice/PhpWord/Style/AbstractStyle.php new file mode 100644 index 0000000..8edbe80 --- /dev/null +++ b/PhpOffice/PhpWord/Style/AbstractStyle.php @@ -0,0 +1,354 @@ +styleName; + } + + /** + * Set style name + * + * @param string $value + * @return self + */ + public function setStyleName($value) + { + $this->styleName = $value; + + return $this; + } + + /** + * Get index number + * + * @return int|null + */ + public function getIndex() + { + return $this->index; + } + + /** + * Set index number + * + * @param int|null $value + * @return self + */ + public function setIndex($value = null) + { + $this->index = $this->setIntVal($value, $this->index); + + return $this; + } + + /** + * Get is automatic style flag + * + * @return bool + */ + public function isAuto() + { + return $this->isAuto; + } + + /** + * Set is automatic style flag + * + * @param bool $value + * @return self + */ + public function setAuto($value = true) + { + $this->isAuto = $this->setBoolVal($value, $this->isAuto); + + return $this; + } + + /** + * Return style value of child style object, e.g. `left` from `Indentation` child style of `Paragraph` + * + * @param \PhpOffice\PhpWord\Style\AbstractStyle $substyleObject + * @param string $substyleProperty + * @return mixed + * @since 0.12.0 + */ + public function getChildStyleValue($substyleObject, $substyleProperty) + { + if ($substyleObject !== null) { + $method = "get{$substyleProperty}"; + + return $substyleObject->$method(); + } + + return null; + } + + /** + * Set style value template method + * + * Some child classes have their own specific overrides. + * Backward compability check for versions < 0.10.0 which use underscore + * prefix for their private properties. + * Check if the set method is exists. Throws an exception? + * + * @param string $key + * @param string $value + * @return self + */ + public function setStyleValue($key, $value) + { + if (isset($this->aliases[$key])) { + $key = $this->aliases[$key]; + } + $method = 'set' . Text::removeUnderscorePrefix($key); + if (method_exists($this, $method)) { + $this->$method($value); + } + + return $this; + } + + /** + * Set style by using associative array + * + * @param array $values + * @return self + */ + public function setStyleByArray($values = array()) + { + foreach ($values as $key => $value) { + $this->setStyleValue($key, $value); + } + + return $this; + } + + /** + * Set default for null and empty value + * + * @param string $value (was: mixed) + * @param string $default (was: mixed) + * @return string (was: mixed) + */ + protected function setNonEmptyVal($value, $default) + { + if ($value === null || $value == '') { + $value = $default; + } + + return $value; + } + + /** + * Set bool value + * + * @param bool $value + * @param bool $default + * @return bool + */ + protected function setBoolVal($value, $default) + { + if (!is_bool($value)) { + $value = $default; + } + + return $value; + } + + /** + * Set numeric value + * + * @param mixed $value + * @param int|float|null $default + * @return int|float|null + */ + protected function setNumericVal($value, $default = null) + { + if (!is_numeric($value)) { + $value = $default; + } + + return $value; + } + + /** + * Set integer value: Convert string that contains only numeric into integer + * + * @param int|null $value + * @param int|null $default + * @return int|null + */ + protected function setIntVal($value, $default = null) + { + if (is_string($value) && (preg_match('/[^\d]/', $value) == 0)) { + $value = (int) $value; + } + if (!is_numeric($value)) { + $value = $default; + } else { + $value = (int) $value; + } + + return $value; + } + + /** + * Set float value: Convert string that contains only numeric into float + * + * @param mixed $value + * @param float|null $default + * @return float|null + */ + protected function setFloatVal($value, $default = null) + { + if (is_string($value) && (preg_match('/[^\d\.\,]/', $value) == 0)) { + $value = (float) $value; + } + if (!is_numeric($value)) { + $value = $default; + } + + return $value; + } + + /** + * Set enum value + * + * @param mixed $value + * @param array $enum + * @param mixed $default + * + * @throws \InvalidArgumentException + * @return mixed + */ + protected function setEnumVal($value = null, $enum = array(), $default = null) + { + if ($value != null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { + throw new \InvalidArgumentException("Invalid style value: {$value} Options:" . implode(',', $enum)); + } elseif ($value === null || trim($value) == '') { + $value = $default; + } + + return $value; + } + + /** + * Set object value + * + * @param mixed $value + * @param string $styleName + * @param mixed &$style + * @return mixed + */ + protected function setObjectVal($value, $styleName, &$style) + { + $styleClass = substr(get_class($this), 0, strrpos(get_class($this), '\\')) . '\\' . $styleName; + if (is_array($value)) { + /** @var \PhpOffice\PhpWord\Style\AbstractStyle $style Type hint */ + if (!$style instanceof $styleClass) { + $style = new $styleClass(); + } + $style->setStyleByArray($value); + } else { + $style = $value; + } + + return $style; + } + + /** + * Set $property value and set $pairProperty = false when $value = true + * + * @param bool &$property + * @param bool &$pairProperty + * @param bool $value + * @return self + */ + protected function setPairedVal(&$property, &$pairProperty, $value) + { + $property = $this->setBoolVal($value, $property); + if ($value === true) { + $pairProperty = false; + } + + return $this; + } + + /** + * Set style using associative array + * + * @deprecated 0.11.0 + * + * @param array $style + * + * @return self + * + * @codeCoverageIgnore + */ + public function setArrayStyle(array $style = array()) + { + return $this->setStyleByArray($style); + } +} diff --git a/PhpOffice/PhpWord/Style/Border.php b/PhpOffice/PhpWord/Style/Border.php new file mode 100644 index 0000000..d032d07 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Border.php @@ -0,0 +1,489 @@ +getBorderTopSize(), + $this->getBorderLeftSize(), + $this->getBorderRightSize(), + $this->getBorderBottomSize(), + ); + } + + /** + * Set border size + * + * @param int|float $value + * @return self + */ + public function setBorderSize($value = null) + { + $this->setBorderTopSize($value); + $this->setBorderLeftSize($value); + $this->setBorderRightSize($value); + $this->setBorderBottomSize($value); + + return $this; + } + + /** + * Get border color + * + * @return string[] + */ + public function getBorderColor() + { + return array( + $this->getBorderTopColor(), + $this->getBorderLeftColor(), + $this->getBorderRightColor(), + $this->getBorderBottomColor(), + ); + } + + /** + * Set border color + * + * @param string $value + * @return self + */ + public function setBorderColor($value = null) + { + $this->setBorderTopColor($value); + $this->setBorderLeftColor($value); + $this->setBorderRightColor($value); + $this->setBorderBottomColor($value); + + return $this; + } + + /** + * Get border style + * + * @return string[] + */ + public function getBorderStyle() + { + return array( + $this->getBorderTopStyle(), + $this->getBorderLeftStyle(), + $this->getBorderRightStyle(), + $this->getBorderBottomStyle(), + ); + } + + /** + * Set border style + * + * @param string $value + * @return self + */ + public function setBorderStyle($value = null) + { + $this->setBorderTopStyle($value); + $this->setBorderLeftStyle($value); + $this->setBorderRightStyle($value); + $this->setBorderBottomStyle($value); + + return $this; + } + + /** + * Get border top size + * + * @return int|float + */ + public function getBorderTopSize() + { + return $this->borderTopSize; + } + + /** + * Set border top size + * + * @param int|float $value + * @return self + */ + public function setBorderTopSize($value = null) + { + $this->borderTopSize = $this->setNumericVal($value, $this->borderTopSize); + + return $this; + } + + /** + * Get border top color + * + * @return string + */ + public function getBorderTopColor() + { + return $this->borderTopColor; + } + + /** + * Set border top color + * + * @param string $value + * @return self + */ + public function setBorderTopColor($value = null) + { + $this->borderTopColor = $value; + + return $this; + } + + /** + * Get border top style + * + * @return string + */ + public function getBorderTopStyle() + { + return $this->borderTopStyle; + } + + /** + * Set border top Style + * + * @param string $value + * @return self + */ + public function setBorderTopStyle($value = null) + { + $this->borderTopStyle = $value; + + return $this; + } + + /** + * Get border left size + * + * @return int|float + */ + public function getBorderLeftSize() + { + return $this->borderLeftSize; + } + + /** + * Set border left size + * + * @param int|float $value + * @return self + */ + public function setBorderLeftSize($value = null) + { + $this->borderLeftSize = $this->setNumericVal($value, $this->borderLeftSize); + + return $this; + } + + /** + * Get border left color + * + * @return string + */ + public function getBorderLeftColor() + { + return $this->borderLeftColor; + } + + /** + * Set border left color + * + * @param string $value + * @return self + */ + public function setBorderLeftColor($value = null) + { + $this->borderLeftColor = $value; + + return $this; + } + + /** + * Get border left style + * + * @return string + */ + public function getBorderLeftStyle() + { + return $this->borderLeftStyle; + } + + /** + * Set border left style + * + * @param string $value + * @return self + */ + public function setBorderLeftStyle($value = null) + { + $this->borderLeftStyle = $value; + + return $this; + } + + /** + * Get border right size + * + * @return int|float + */ + public function getBorderRightSize() + { + return $this->borderRightSize; + } + + /** + * Set border right size + * + * @param int|float $value + * @return self + */ + public function setBorderRightSize($value = null) + { + $this->borderRightSize = $this->setNumericVal($value, $this->borderRightSize); + + return $this; + } + + /** + * Get border right color + * + * @return string + */ + public function getBorderRightColor() + { + return $this->borderRightColor; + } + + /** + * Set border right color + * + * @param string $value + * @return self + */ + public function setBorderRightColor($value = null) + { + $this->borderRightColor = $value; + + return $this; + } + + /** + * Get border right style + * + * @return string + */ + public function getBorderRightStyle() + { + return $this->borderRightStyle; + } + + /** + * Set border right style + * + * @param string $value + * @return self + */ + public function setBorderRightStyle($value = null) + { + $this->borderRightStyle = $value; + + return $this; + } + + /** + * Get border bottom size + * + * @return int|float + */ + public function getBorderBottomSize() + { + return $this->borderBottomSize; + } + + /** + * Set border bottom size + * + * @param int|float $value + * @return self + */ + public function setBorderBottomSize($value = null) + { + $this->borderBottomSize = $this->setNumericVal($value, $this->borderBottomSize); + + return $this; + } + + /** + * Get border bottom color + * + * @return string + */ + public function getBorderBottomColor() + { + return $this->borderBottomColor; + } + + /** + * Set border bottom color + * + * @param string $value + * @return self + */ + public function setBorderBottomColor($value = null) + { + $this->borderBottomColor = $value; + + return $this; + } + + /** + * Get border bottom style + * + * @return string + */ + public function getBorderBottomStyle() + { + return $this->borderBottomStyle; + } + + /** + * Set border bottom style + * + * @param string $value + * @return self + */ + public function setBorderBottomStyle($value = null) + { + $this->borderBottomStyle = $value; + + return $this; + } + + /** + * Check if any of the border is not null + * + * @return bool + */ + public function hasBorder() + { + $borders = $this->getBorderSize(); + + return $borders !== array_filter($borders, 'is_null'); + } +} diff --git a/PhpOffice/PhpWord/Style/Cell.php b/PhpOffice/PhpWord/Style/Cell.php new file mode 100644 index 0000000..1276b5b --- /dev/null +++ b/PhpOffice/PhpWord/Style/Cell.php @@ -0,0 +1,340 @@ +vAlign; + } + + /** + * Set vertical align + * + * @param string $value + * @return self + */ + public function setVAlign($value = null) + { + VerticalJc::validate($value); + $this->vAlign = $this->setEnumVal($value, VerticalJc::values(), $this->vAlign); + + return $this; + } + + /** + * Get text direction. + * + * @return string + */ + public function getTextDirection() + { + return $this->textDirection; + } + + /** + * Set text direction + * + * @param string $value + * @return self + */ + public function setTextDirection($value = null) + { + $enum = array(self::TEXT_DIR_BTLR, self::TEXT_DIR_TBRL); + $this->textDirection = $this->setEnumVal($value, $enum, $this->textDirection); + + return $this; + } + + /** + * Get background + * + * @return string + */ + public function getBgColor() + { + if ($this->shading !== null) { + return $this->shading->getFill(); + } + + return null; + } + + /** + * Set background + * + * @param string $value + * @return self + */ + public function setBgColor($value = null) + { + return $this->setShading(array('fill' => $value)); + } + + /** + * Get grid span (colspan). + * + * @return int + */ + public function getGridSpan() + { + return $this->gridSpan; + } + + /** + * Set grid span (colspan) + * + * @param int $value + * @return self + */ + public function setGridSpan($value = null) + { + $this->gridSpan = $this->setIntVal($value, $this->gridSpan); + + return $this; + } + + /** + * Get vertical merge (rowspan). + * + * @return string + */ + public function getVMerge() + { + return $this->vMerge; + } + + /** + * Set vertical merge (rowspan) + * + * @param string $value + * @return self + */ + public function setVMerge($value = null) + { + $enum = array(self::VMERGE_RESTART, self::VMERGE_CONTINUE); + $this->vMerge = $this->setEnumVal($value, $enum, $this->vMerge); + + return $this; + } + + /** + * Get shading + * + * @return \PhpOffice\PhpWord\Style\Shading + */ + public function getShading() + { + return $this->shading; + } + + /** + * Set shading + * + * @param mixed $value + * @return self + */ + public function setShading($value = null) + { + $this->setObjectVal($value, 'Shading', $this->shading); + + return $this; + } + + /** + * Get cell width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set cell width + * + * @param int $value + * @return self + */ + public function setWidth($value) + { + $this->setIntVal($value); + + return $this; + } + + /** + * Get width unit + * + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * Set width unit + * + * @param string $value + */ + public function setUnit($value) + { + $this->unit = $this->setEnumVal($value, array(TblWidth::AUTO, TblWidth::PERCENT, TblWidth::TWIP), TblWidth::TWIP); + + return $this; + } + + /** + * Get default border color + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getDefaultBorderColor() + { + return self::DEFAULT_BORDER_COLOR; + } +} diff --git a/PhpOffice/PhpWord/Style/Chart.php b/PhpOffice/PhpWord/Style/Chart.php new file mode 100644 index 0000000..06b4829 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Chart.php @@ -0,0 +1,492 @@ + true, // value + 'showCatName' => true, // category name + 'showLegendKey' => false, //show the cart legend + 'showSerName' => false, // series name + 'showPercent' => false, + 'showLeaderLines' => false, + 'showBubbleSize' => false, + ); + + /** + * A string that tells the writer where to write chart labels or to skip + * "nextTo" - sets labels next to the axis (bar graphs on the left) (default) + * "low" - labels on the left side of the graph + * "high" - labels on the right side of the graph + * + * @var string + */ + private $categoryLabelPosition = 'nextTo'; + + /** + * A string that tells the writer where to write chart labels or to skip + * "nextTo" - sets labels next to the axis (bar graphs on the bottom) (default) + * "low" - labels are below the graph + * "high" - labels above the graph + * + * @var string + */ + private $valueLabelPosition = 'nextTo'; + + /** + * @var string + */ + private $categoryAxisTitle; + + /** + * @var string + */ + private $valueAxisTitle; + + /** + * The position for major tick marks + * Possible values are 'in', 'out', 'cross', 'none' + * + * @var string + */ + private $majorTickMarkPos = 'none'; + + /** + * Show labels for axis + * + * @var bool + */ + private $showAxisLabels = false; + + /** + * Show Gridlines for Y-Axis + * + * @var bool + */ + private $gridY = false; + + /** + * Show Gridlines for X-Axis + * + * @var bool + */ + private $gridX = false; + + /** + * Create a new instance + * + * @param array $style + */ + public function __construct($style = array()) + { + $this->setStyleByArray($style); + } + + /** + * Get width + * + * @return int + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set width + * + * @param int $value + * @return self + */ + public function setWidth($value = null) + { + $this->width = $this->setIntVal($value, $this->width); + + return $this; + } + + /** + * Get height + * + * @return int + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set height + * + * @param int $value + * @return self + */ + public function setHeight($value = null) + { + $this->height = $this->setIntVal($value, $this->height); + + return $this; + } + + /** + * Is 3D + * + * @return bool + */ + public function is3d() + { + return $this->is3d; + } + + /** + * Set 3D + * + * @param bool $value + * @return self + */ + public function set3d($value = true) + { + $this->is3d = $this->setBoolVal($value, $this->is3d); + + return $this; + } + + /** + * Get the list of colors to use in a chart. + * + * @return array + */ + public function getColors() + { + return $this->colors; + } + + /** + * Set the colors to use in a chart. + * + * @param array $value a list of colors to use in the chart + */ + public function setColors($value = array()) + { + $this->colors = $value; + + return $this; + } + + /** + * Get the chart title + * + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * Set the chart title + * + * @param string $value + */ + public function setTitle($value = null) + { + $this->title = $value; + + return $this; + } + + /** + * Get chart legend visibility + * + * @return bool + */ + public function isShowLegend() + { + return $this->showLegend; + } + + /** + * Set chart legend visibility + * + * @param bool $value + */ + public function setShowLegend($value = false) + { + $this->showLegend = $value; + + return $this; + } + + /* + * Show labels for axis + * + * @return bool + */ + public function showAxisLabels() + { + return $this->showAxisLabels; + } + + /** + * Set show Gridlines for Y-Axis + * + * @param bool $value + * @return self + */ + public function setShowAxisLabels($value = true) + { + $this->showAxisLabels = $this->setBoolVal($value, $this->showAxisLabels); + + return $this; + } + + /** + * get the list of options for data labels + * + * @return array + */ + public function getDataLabelOptions() + { + return $this->dataLabelOptions; + } + + /** + * Set values for data label options. + * This will only change values for options defined in $this->dataLabelOptions, and cannot create new ones. + * + * @param array $values [description] + */ + public function setDataLabelOptions($values = array()) + { + foreach (array_keys($this->dataLabelOptions) as $option) { + if (isset($values[$option])) { + $this->dataLabelOptions[$option] = $this->setBoolVal($values[$option], $this->dataLabelOptions[$option]); + } + } + } + + /* + * Show Gridlines for Y-Axis + * + * @return bool + */ + public function showGridY() + { + return $this->gridY; + } + + /** + * Set show Gridlines for Y-Axis + * + * @param bool $value + * @return self + */ + public function setShowGridY($value = true) + { + $this->gridY = $this->setBoolVal($value, $this->gridY); + + return $this; + } + + /** + * Get the categoryLabelPosition setting + * + * @return string + */ + public function getCategoryLabelPosition() + { + return $this->categoryLabelPosition; + } + + /** + * Set the categoryLabelPosition setting + * "none" - skips writing labels + * "nextTo" - sets labels next to the (bar graphs on the left) + * "low" - labels on the left side of the graph + * "high" - labels on the right side of the graph + * + * @param mixed $labelPosition + * @return self + */ + public function setCategoryLabelPosition($labelPosition) + { + $enum = array('nextTo', 'low', 'high'); + $this->categoryLabelPosition = $this->setEnumVal($labelPosition, $enum, $this->categoryLabelPosition); + + return $this; + } + + /** + * Get the valueAxisLabelPosition setting + * + * @return string + */ + public function getValueLabelPosition() + { + return $this->valueLabelPosition; + } + + /** + * Set the valueLabelPosition setting + * "none" - skips writing labels + * "nextTo" - sets labels next to the value + * "low" - sets labels are below the graph + * "high" - sets labels above the graph + * + * @param string + * @param mixed $labelPosition + */ + public function setValueLabelPosition($labelPosition) + { + $enum = array('nextTo', 'low', 'high'); + $this->valueLabelPosition = $this->setEnumVal($labelPosition, $enum, $this->valueLabelPosition); + + return $this; + } + + /** + * Get the categoryAxisTitle + * @return string + */ + public function getCategoryAxisTitle() + { + return $this->categoryAxisTitle; + } + + /** + * Set the title that appears on the category side of the chart + * @param string $axisTitle + */ + public function setCategoryAxisTitle($axisTitle) + { + $this->categoryAxisTitle = $axisTitle; + + return $this; + } + + /** + * Get the valueAxisTitle + * @return string + */ + public function getValueAxisTitle() + { + return $this->valueAxisTitle; + } + + /** + * Set the title that appears on the value side of the chart + * @param string $axisTitle + */ + public function setValueAxisTitle($axisTitle) + { + $this->valueAxisTitle = $axisTitle; + + return $this; + } + + public function getMajorTickPosition() + { + return $this->majorTickMarkPos; + } + + /** + * Set the position for major tick marks + * @param string $position + */ + public function setMajorTickPosition($position) + { + $enum = array('in', 'out', 'cross', 'none'); + $this->majorTickMarkPos = $this->setEnumVal($position, $enum, $this->majorTickMarkPos); + } + + /** + * Show Gridlines for X-Axis + * + * @return bool + */ + public function showGridX() + { + return $this->gridX; + } + + /** + * Set show Gridlines for X-Axis + * + * @param bool $value + * @return self + */ + public function setShowGridX($value = true) + { + $this->gridX = $this->setBoolVal($value, $this->gridX); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Extrusion.php b/PhpOffice/PhpWord/Style/Extrusion.php new file mode 100644 index 0000000..4c860bc --- /dev/null +++ b/PhpOffice/PhpWord/Style/Extrusion.php @@ -0,0 +1,106 @@ +setStyleByArray($style); + } + + /** + * Get type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set pattern + * + * @param string $value + * @return self + */ + public function setType($value = null) + { + $enum = array(self::EXTRUSION_PARALLEL, self::EXTRUSION_PERSPECTIVE); + $this->type = $this->setEnumVal($value, $enum, null); + + return $this; + } + + /** + * Get color + * + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * Set color + * + * @param string $value + * @return self + */ + public function setColor($value = null) + { + $this->color = $value; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Fill.php b/PhpOffice/PhpWord/Style/Fill.php new file mode 100644 index 0000000..360bcf3 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Fill.php @@ -0,0 +1,69 @@ +setStyleByArray($style); + } + + /** + * Get color + * + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * Set color + * + * @param string $value + * @return self + */ + public function setColor($value = null) + { + $this->color = $value; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Font.php b/PhpOffice/PhpWord/Style/Font.php new file mode 100644 index 0000000..09e6f1a --- /dev/null +++ b/PhpOffice/PhpWord/Style/Font.php @@ -0,0 +1,995 @@ + 'lineHeight', 'letter-spacing' => 'spacing'); + + /** + * Font style type + * + * @var string + */ + private $type; + + /** + * Font name + * + * @var string + */ + private $name; + + /** + * Font Content Type + * + * @var string + */ + private $hint; + + /** + * Font size + * + * @var int|float + */ + private $size; + + /** + * Font color + * + * @var string + */ + private $color; + + /** + * Bold + * + * @var bool + */ + private $bold; + + /** + * Italic + * + * @var bool + */ + private $italic; + + /** + * Undeline + * + * @var string + */ + private $underline = self::UNDERLINE_NONE; + + /** + * Superscript + * + * @var bool + */ + private $superScript = false; + + /** + * Subscript + * + * @var bool + */ + private $subScript = false; + + /** + * Strikethrough + * + * @var bool + */ + private $strikethrough; + + /** + * Double strikethrough + * + * @var bool + */ + private $doubleStrikethrough; + + /** + * Small caps + * + * @var bool + * @see http://www.schemacentral.com/sc/ooxml/e-w_smallCaps-1.html + */ + private $smallCaps; + + /** + * All caps + * + * @var bool + * @see http://www.schemacentral.com/sc/ooxml/e-w_caps-1.html + */ + private $allCaps; + + /** + * Foreground/highlight + * + * @var string + */ + private $fgColor; + + /** + * Expanded/compressed text: 0-600 (percent) + * + * @var int + * @since 0.12.0 + * @see http://www.schemacentral.com/sc/ooxml/e-w_w-1.html + */ + private $scale; + + /** + * Character spacing adjustment: twip + * + * @var int|float + * @since 0.12.0 + * @see http://www.schemacentral.com/sc/ooxml/e-w_spacing-2.html + */ + private $spacing; + + /** + * Font kerning: halfpoint + * + * @var int|float + * @since 0.12.0 + * @see http://www.schemacentral.com/sc/ooxml/e-w_kern-1.html + */ + private $kerning; + + /** + * Paragraph style + * + * @var \PhpOffice\PhpWord\Style\Paragraph + */ + private $paragraph; + + /** + * Shading + * + * @var \PhpOffice\PhpWord\Style\Shading + */ + private $shading; + + /** + * Right to left languages + * + * @var bool + */ + private $rtl; + + /** + * noProof (disables AutoCorrect) + * + * @var bool + * http://www.datypic.com/sc/ooxml/e-w_noProof-1.html + */ + private $noProof; + + /** + * Languages + * + * @var \PhpOffice\PhpWord\Style\Language + */ + private $lang; + + /** + * Hidden text + * + * @var bool + * @see http://www.datypic.com/sc/ooxml/e-w_vanish-1.html + */ + private $hidden; + + /** + * Vertically Raised or Lowered Text + * + * @var int Signed Half-Point Measurement + * @see http://www.datypic.com/sc/ooxml/e-w_position-1.html + */ + private $position; + + /** + * Create new font style + * + * @param string $type Type of font + * @param array|string|\PhpOffice\PhpWord\Style\AbstractStyle $paragraph Paragraph styles definition + */ + public function __construct($type = 'text', $paragraph = null) + { + $this->type = $type; + $this->setParagraph($paragraph); + } + + /** + * Get style values + * + * @return array + * @since 0.12.0 + */ + public function getStyleValues() + { + $styles = array( + 'name' => $this->getStyleName(), + 'basic' => array( + 'name' => $this->getName(), + 'size' => $this->getSize(), + 'color' => $this->getColor(), + 'hint' => $this->getHint(), + ), + 'style' => array( + 'bold' => $this->isBold(), + 'italic' => $this->isItalic(), + 'underline' => $this->getUnderline(), + 'strike' => $this->isStrikethrough(), + 'dStrike' => $this->isDoubleStrikethrough(), + 'super' => $this->isSuperScript(), + 'sub' => $this->isSubScript(), + 'smallCaps' => $this->isSmallCaps(), + 'allCaps' => $this->isAllCaps(), + 'fgColor' => $this->getFgColor(), + 'hidden' => $this->isHidden(), + ), + 'spacing' => array( + 'scale' => $this->getScale(), + 'spacing' => $this->getSpacing(), + 'kerning' => $this->getKerning(), + 'position' => $this->getPosition(), + ), + 'paragraph' => $this->getParagraph(), + 'rtl' => $this->isRTL(), + 'shading' => $this->getShading(), + 'lang' => $this->getLang(), + ); + + return $styles; + } + + /** + * Get style type + * + * @return string + */ + public function getStyleType() + { + return $this->type; + } + + /** + * Get font name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set font name + * + * @param string $value + * @return self + */ + public function setName($value = null) + { + $this->name = $value; + + return $this; + } + + /** + * Get Font Content Type + * + * @return string + */ + public function getHint() + { + return $this->hint; + } + + /** + * Set Font Content Type + * + * @param string $value + * @return self + */ + public function setHint($value = null) + { + $this->hint = $value; + + return $this; + } + + /** + * Get font size + * + * @return int|float + */ + public function getSize() + { + return $this->size; + } + + /** + * Set font size + * + * @param int|float $value + * @return self + */ + public function setSize($value = null) + { + $this->size = $this->setNumericVal($value, $this->size); + + return $this; + } + + /** + * Get font color + * + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * Set font color + * + * @param string $value + * @return self + */ + public function setColor($value = null) + { + $this->color = $value; + + return $this; + } + + /** + * Get bold + * + * @return bool + */ + public function isBold() + { + return $this->bold; + } + + /** + * Set bold + * + * @param bool $value + * @return self + */ + public function setBold($value = true) + { + $this->bold = $this->setBoolVal($value, $this->bold); + + return $this; + } + + /** + * Get italic + * + * @return bool + */ + public function isItalic() + { + return $this->italic; + } + + /** + * Set italic + * + * @param bool $value + * @return self + */ + public function setItalic($value = true) + { + $this->italic = $this->setBoolVal($value, $this->italic); + + return $this; + } + + /** + * Get underline + * + * @return string + */ + public function getUnderline() + { + return $this->underline; + } + + /** + * Set underline + * + * @param string $value + * @return self + */ + public function setUnderline($value = self::UNDERLINE_NONE) + { + $this->underline = $this->setNonEmptyVal($value, self::UNDERLINE_NONE); + + return $this; + } + + /** + * Get superscript + * + * @return bool + */ + public function isSuperScript() + { + return $this->superScript; + } + + /** + * Set superscript + * + * @param bool $value + * @return self + */ + public function setSuperScript($value = true) + { + return $this->setPairedVal($this->superScript, $this->subScript, $value); + } + + /** + * Get subscript + * + * @return bool + */ + public function isSubScript() + { + return $this->subScript; + } + + /** + * Set subscript + * + * @param bool $value + * @return self + */ + public function setSubScript($value = true) + { + return $this->setPairedVal($this->subScript, $this->superScript, $value); + } + + /** + * Get strikethrough + * + * @return bool + */ + public function isStrikethrough() + { + return $this->strikethrough; + } + + /** + * Set strikethrough + * + * @param bool $value + * @return self + */ + public function setStrikethrough($value = true) + { + return $this->setPairedVal($this->strikethrough, $this->doubleStrikethrough, $value); + } + + /** + * Get double strikethrough + * + * @return bool + */ + public function isDoubleStrikethrough() + { + return $this->doubleStrikethrough; + } + + /** + * Set double strikethrough + * + * @param bool $value + * @return self + */ + public function setDoubleStrikethrough($value = true) + { + return $this->setPairedVal($this->doubleStrikethrough, $this->strikethrough, $value); + } + + /** + * Get small caps + * + * @return bool + */ + public function isSmallCaps() + { + return $this->smallCaps; + } + + /** + * Set small caps + * + * @param bool $value + * @return self + */ + public function setSmallCaps($value = true) + { + return $this->setPairedVal($this->smallCaps, $this->allCaps, $value); + } + + /** + * Get all caps + * + * @return bool + */ + public function isAllCaps() + { + return $this->allCaps; + } + + /** + * Set all caps + * + * @param bool $value + * @return self + */ + public function setAllCaps($value = true) + { + return $this->setPairedVal($this->allCaps, $this->smallCaps, $value); + } + + /** + * Get foreground/highlight color + * + * @return string + */ + public function getFgColor() + { + return $this->fgColor; + } + + /** + * Set foreground/highlight color + * + * @param string $value + * @return self + */ + public function setFgColor($value = null) + { + $this->fgColor = $value; + + return $this; + } + + /** + * Get background + * + * @return string + */ + public function getBgColor() + { + return $this->getChildStyleValue($this->shading, 'fill'); + } + + /** + * Set background + * + * @param string $value + * @return \PhpOffice\PhpWord\Style\Table + */ + public function setBgColor($value = null) + { + $this->setShading(array('fill' => $value)); + } + + /** + * Get scale + * + * @return int + */ + public function getScale() + { + return $this->scale; + } + + /** + * Set scale + * + * @param int $value + * @return self + */ + public function setScale($value = null) + { + $this->scale = $this->setIntVal($value, null); + + return $this; + } + + /** + * Get font spacing + * + * @return int|float + */ + public function getSpacing() + { + return $this->spacing; + } + + /** + * Set font spacing + * + * @param int|float $value + * @return self + */ + public function setSpacing($value = null) + { + $this->spacing = $this->setNumericVal($value, null); + + return $this; + } + + /** + * Get font kerning + * + * @return int|float + */ + public function getKerning() + { + return $this->kerning; + } + + /** + * Set font kerning + * + * @param int|float $value + * @return self + */ + public function setKerning($value = null) + { + $this->kerning = $this->setNumericVal($value, null); + + return $this; + } + + /** + * Get noProof (disables autocorrect) + * + * @return bool + */ + public function isNoProof() + { + return $this->noProof; + } + + /** + * Set noProof (disables autocorrect) + * + * @param bool $value + * @return $this + */ + public function setNoProof($value = false) + { + $this->noProof = $value; + + return $this; + } + + /** + * Get line height + * + * @return int|float + */ + public function getLineHeight() + { + return $this->getParagraph()->getLineHeight(); + } + + /** + * Set lineheight + * + * @param int|float|string $value + * @return self + */ + public function setLineHeight($value) + { + $this->setParagraph(array('lineHeight' => $value)); + + return $this; + } + + /** + * Get paragraph style + * + * @return \PhpOffice\PhpWord\Style\Paragraph + */ + public function getParagraph() + { + return $this->paragraph; + } + + /** + * Set Paragraph + * + * @param mixed $value + * @return self + */ + public function setParagraph($value = null) + { + $this->setObjectVal($value, 'Paragraph', $this->paragraph); + + return $this; + } + + /** + * Get rtl + * + * @return bool + */ + public function isRTL() + { + return $this->rtl; + } + + /** + * Set rtl + * + * @param bool $value + * @return self + */ + public function setRTL($value = true) + { + $this->rtl = $this->setBoolVal($value, $this->rtl); + + return $this; + } + + /** + * Get shading + * + * @return \PhpOffice\PhpWord\Style\Shading + */ + public function getShading() + { + return $this->shading; + } + + /** + * Set shading + * + * @param mixed $value + * @return self + */ + public function setShading($value = null) + { + $this->setObjectVal($value, 'Shading', $this->shading); + + return $this; + } + + /** + * Get language + * + * @return \PhpOffice\PhpWord\Style\Language + */ + public function getLang() + { + return $this->lang; + } + + /** + * Set language + * + * @param mixed $value + * @return self + */ + public function setLang($value = null) + { + if (is_string($value) && $value != '') { + $value = new Language($value); + } + $this->setObjectVal($value, 'Language', $this->lang); + + return $this; + } + + /** + * Get bold + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getBold() + { + return $this->isBold(); + } + + /** + * Get italic + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getItalic() + { + return $this->isItalic(); + } + + /** + * Get superscript + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getSuperScript() + { + return $this->isSuperScript(); + } + + /** + * Get subscript + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getSubScript() + { + return $this->isSubScript(); + } + + /** + * Get strikethrough + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getStrikethrough() + { + return $this->isStrikethrough(); + } + + /** + * Get paragraph style + * + * @deprecated 0.11.0 + * + * @codeCoverageIgnore + */ + public function getParagraphStyle() + { + return $this->getParagraph(); + } + + /** + * Get hidden text + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Set hidden text + * + * @param bool $value + * @return self + */ + public function setHidden($value = true) + { + $this->hidden = $this->setBoolVal($value, $this->hidden); + + return $this; + } + + /** + * Get position + * + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * Set position + * + * @param int $value + * @return self + */ + public function setPosition($value = null) + { + $this->position = $this->setIntVal($value, null); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Frame.php b/PhpOffice/PhpWord/Style/Frame.php new file mode 100644 index 0000000..e87b7a8 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Frame.php @@ -0,0 +1,692 @@ +setStyleByArray($style); + } + + /** + * @since 0.13.0 + * + * @return string + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * @since 0.13.0 + * + * @param string $value + * + * @return self + */ + public function setAlignment($value) + { + if (Jc::isValid($value)) { + $this->alignment = $value; + } + + return $this; + } + + /** + * @deprecated 0.13.0 Use the `getAlignment` method instead. + * + * @return string + * + * @codeCoverageIgnore + */ + public function getAlign() + { + return $this->getAlignment(); + } + + /** + * @deprecated 0.13.0 Use the `setAlignment` method instead. + * + * @param string $value + * + * @return self + * + * @codeCoverageIgnore + */ + public function setAlign($value = null) + { + return $this->setAlignment($value); + } + + /** + * Get unit + * + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * Set unit + * + * @param string $value + * @return self + */ + public function setUnit($value) + { + $this->unit = $value; + + return $this; + } + + /** + * Get width + * + * @return int|float + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set width + * + * @param int|float $value + * @return self + */ + public function setWidth($value = null) + { + $this->width = $this->setNumericVal($value, null); + + return $this; + } + + /** + * Get height + * + * @return int|float + */ + public function getHeight() + { + return $this->height; + } + + /** + * Set height + * + * @param int|float $value + * @return self + */ + public function setHeight($value = null) + { + $this->height = $this->setNumericVal($value, null); + + return $this; + } + + /** + * Get left + * + * @return int|float + */ + public function getLeft() + { + return $this->left; + } + + /** + * Set left + * + * @param int|float $value + * @return self + */ + public function setLeft($value = 0) + { + $this->left = $this->setNumericVal($value, 0); + + return $this; + } + + /** + * Get topmost position + * + * @return int|float + */ + public function getTop() + { + return $this->top; + } + + /** + * Set topmost position + * + * @param int|float $value + * @return self + */ + public function setTop($value = 0) + { + $this->top = $this->setNumericVal($value, 0); + + return $this; + } + + /** + * Get position type + * + * @return string + */ + public function getPos() + { + return $this->pos; + } + + /** + * Set position type + * + * @param string $value + * @return self + */ + public function setPos($value) + { + $enum = array( + self::POS_ABSOLUTE, + self::POS_RELATIVE, + ); + $this->pos = $this->setEnumVal($value, $enum, $this->pos); + + return $this; + } + + /** + * Get horizontal position + * + * @return string + */ + public function getHPos() + { + return $this->hPos; + } + + /** + * Set horizontal position + * + * @since 0.12.0 "absolute" option is available. + * + * @param string $value + * @return self + */ + public function setHPos($value) + { + $enum = array( + self::POS_ABSOLUTE, + self::POS_LEFT, + self::POS_CENTER, + self::POS_RIGHT, + self::POS_INSIDE, + self::POS_OUTSIDE, + ); + $this->hPos = $this->setEnumVal($value, $enum, $this->hPos); + + return $this; + } + + /** + * Get vertical position + * + * @return string + */ + public function getVPos() + { + return $this->vPos; + } + + /** + * Set vertical position + * + * @since 0.12.0 "absolute" option is available. + * + * @param string $value + * @return self + */ + public function setVPos($value) + { + $enum = array( + self::POS_ABSOLUTE, + self::POS_TOP, + self::POS_CENTER, + self::POS_BOTTOM, + self::POS_INSIDE, + self::POS_OUTSIDE, + ); + $this->vPos = $this->setEnumVal($value, $enum, $this->vPos); + + return $this; + } + + /** + * Get horizontal position relative to + * + * @return string + */ + public function getHPosRelTo() + { + return $this->hPosRelTo; + } + + /** + * Set horizontal position relative to + * + * @param string $value + * @return self + */ + public function setHPosRelTo($value) + { + $enum = array( + self::POS_RELTO_MARGIN, + self::POS_RELTO_PAGE, + self::POS_RELTO_COLUMN, + self::POS_RELTO_CHAR, + self::POS_RELTO_LMARGIN, + self::POS_RELTO_RMARGIN, + self::POS_RELTO_IMARGIN, + self::POS_RELTO_OMARGIN, + ); + $this->hPosRelTo = $this->setEnumVal($value, $enum, $this->hPosRelTo); + + return $this; + } + + /** + * Get vertical position relative to + * + * @return string + */ + public function getVPosRelTo() + { + return $this->vPosRelTo; + } + + /** + * Set vertical position relative to + * + * @param string $value + * @return self + */ + public function setVPosRelTo($value) + { + $enum = array( + self::POS_RELTO_MARGIN, + self::POS_RELTO_PAGE, + self::POS_RELTO_TEXT, + self::POS_RELTO_LINE, + self::POS_RELTO_TMARGIN, + self::POS_RELTO_BMARGIN, + self::POS_RELTO_IMARGIN, + self::POS_RELTO_OMARGIN, + ); + $this->vPosRelTo = $this->setEnumVal($value, $enum, $this->vPosRelTo); + + return $this; + } + + /** + * Get wrap type + * + * @return string + */ + public function getWrap() + { + return $this->wrap; + } + + /** + * Set wrap type + * + * @param string $value + * @return self + */ + public function setWrap($value) + { + $enum = array( + self::WRAP_INLINE, + self::WRAP_SQUARE, + self::WRAP_TIGHT, + self::WRAP_THROUGH, + self::WRAP_TOPBOTTOM, + self::WRAP_BEHIND, + self::WRAP_INFRONT, + ); + $this->wrap = $this->setEnumVal($value, $enum, $this->wrap); + + return $this; + } + + /** + * Get top distance from text wrap + * + * @return float + */ + public function getWrapDistanceTop() + { + return $this->wrapDistanceTop; + } + + /** + * Set top distance from text wrap + * + * @param int $value + * @return self + */ + public function setWrapDistanceTop($value = null) + { + $this->wrapDistanceTop = $this->setFloatVal($value, null); + + return $this; + } + + /** + * Get bottom distance from text wrap + * + * @return float + */ + public function getWrapDistanceBottom() + { + return $this->wrapDistanceBottom; + } + + /** + * Set bottom distance from text wrap + * + * @param float $value + * @return self + */ + public function setWrapDistanceBottom($value = null) + { + $this->wrapDistanceBottom = $this->setFloatVal($value, null); + + return $this; + } + + /** + * Get left distance from text wrap + * + * @return float + */ + public function getWrapDistanceLeft() + { + return $this->wrapDistanceLeft; + } + + /** + * Set left distance from text wrap + * + * @param float $value + * @return self + */ + public function setWrapDistanceLeft($value = null) + { + $this->wrapDistanceLeft = $this->setFloatVal($value, null); + + return $this; + } + + /** + * Get right distance from text wrap + * + * @return float + */ + public function getWrapDistanceRight() + { + return $this->wrapDistanceRight; + } + + /** + * Set right distance from text wrap + * + * @param float $value + * @return self + */ + public function setWrapDistanceRight($value = null) + { + $this->wrapDistanceRight = $this->setFloatVal($value, null); + + return $this; + } + + /** + * Get position + * + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * Set position + * + * @param int $value + * @return self + */ + public function setPosition($value = null) + { + $this->position = $this->setIntVal($value, null); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Image.php b/PhpOffice/PhpWord/Style/Image.php new file mode 100644 index 0000000..70aafe1 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Image.php @@ -0,0 +1,278 @@ +setUnit(self::UNIT_PT); + + // Backward compatibility setting + // @todo Remove on 1.0.0 + $this->setWrap(self::WRAPPING_STYLE_INLINE); + $this->setHPos(self::POSITION_HORIZONTAL_LEFT); + $this->setHPosRelTo(self::POSITION_RELATIVE_TO_CHAR); + $this->setVPos(self::POSITION_VERTICAL_TOP); + $this->setVPosRelTo(self::POSITION_RELATIVE_TO_LINE); + } + + /** + * Get margin top + * + * @return int|float + */ + public function getMarginTop() + { + return $this->getTop(); + } + + /** + * Set margin top + * + * @ignoreScrutinizerPatch + * @param int|float $value + * @return self + */ + public function setMarginTop($value = 0) + { + $this->setTop($value); + + return $this; + } + + /** + * Get margin left + * + * @return int|float + */ + public function getMarginLeft() + { + return $this->getLeft(); + } + + /** + * Set margin left + * + * @ignoreScrutinizerPatch + * @param int|float $value + * @return self + */ + public function setMarginLeft($value = 0) + { + $this->setLeft($value); + + return $this; + } + + /** + * Get wrapping style + * + * @return string + */ + public function getWrappingStyle() + { + return $this->getWrap(); + } + + /** + * Set wrapping style + * + * @param string $wrappingStyle + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setWrappingStyle($wrappingStyle) + { + $this->setWrap($wrappingStyle); + + return $this; + } + + /** + * Get positioning type + * + * @return string + */ + public function getPositioning() + { + return $this->getPos(); + } + + /** + * Set positioning type + * + * @param string $positioning + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setPositioning($positioning) + { + $this->setPos($positioning); + + return $this; + } + + /** + * Get horizontal alignment + * + * @return string + */ + public function getPosHorizontal() + { + return $this->getHPos(); + } + + /** + * Set horizontal alignment + * + * @param string $alignment + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setPosHorizontal($alignment) + { + $this->setHPos($alignment); + + return $this; + } + + /** + * Get vertical alignment + * + * @return string + */ + public function getPosVertical() + { + return $this->getVPos(); + } + + /** + * Set vertical alignment + * + * @param string $alignment + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setPosVertical($alignment) + { + $this->setVPos($alignment); + + return $this; + } + + /** + * Get horizontal relation + * + * @return string + */ + public function getPosHorizontalRel() + { + return $this->getHPosRelTo(); + } + + /** + * Set horizontal relation + * + * @param string $relto + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setPosHorizontalRel($relto) + { + $this->setHPosRelTo($relto); + + return $this; + } + + /** + * Get vertical relation + * + * @return string + */ + public function getPosVerticalRel() + { + return $this->getVPosRelTo(); + } + + /** + * Set vertical relation + * + * @param string $relto + * + * @throws \InvalidArgumentException + * + * @return self + */ + public function setPosVerticalRel($relto) + { + $this->setVPosRelTo($relto); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Indentation.php b/PhpOffice/PhpWord/Style/Indentation.php new file mode 100644 index 0000000..e422395 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Indentation.php @@ -0,0 +1,157 @@ +setStyleByArray($style); + } + + /** + * Get left + * + * @return int|float + */ + public function getLeft() + { + return $this->left; + } + + /** + * Set left + * + * @param int|float $value + * @return self + */ + public function setLeft($value = null) + { + $this->left = $this->setNumericVal($value, $this->left); + + return $this; + } + + /** + * Get right + * + * @return int|float + */ + public function getRight() + { + return $this->right; + } + + /** + * Set right + * + * @param int|float $value + * @return self + */ + public function setRight($value = null) + { + $this->right = $this->setNumericVal($value, $this->right); + + return $this; + } + + /** + * Get first line + * + * @return int|float + */ + public function getFirstLine() + { + return $this->firstLine; + } + + /** + * Set first line + * + * @param int|float $value + * @return self + */ + public function setFirstLine($value = null) + { + $this->firstLine = $this->setNumericVal($value, $this->firstLine); + + return $this; + } + + /** + * Get hanging + * + * @return int|float + */ + public function getHanging() + { + return $this->hanging; + } + + /** + * Set hanging + * + * @param int|float $value + * @return self + */ + public function setHanging($value = null) + { + $this->hanging = $this->setNumericVal($value, $this->hanging); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Language.php b/PhpOffice/PhpWord/Style/Language.php new file mode 100644 index 0000000..18ef889 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Language.php @@ -0,0 +1,241 @@ +setLatin($latin); + } + if (!empty($eastAsia)) { + $this->setEastAsia($eastAsia); + } + if (!empty($bidirectional)) { + $this->setBidirectional($bidirectional); + } + } + + /** + * Set the Latin Language + * + * @param string $latin + * The value for the latin language + * @return self + */ + public function setLatin($latin) + { + $this->latin = $this->validateLocale($latin); + + return $this; + } + + /** + * Get the Latin Language + * + * @return string|null + */ + public function getLatin() + { + return $this->latin; + } + + /** + * Set the Language ID + * + * @param int $langId + * The value for the language ID + * @return self + * @see https://technet.microsoft.com/en-us/library/cc287874(v=office.12).aspx + */ + public function setLangId($langId) + { + $this->langId = $langId; + + return $this; + } + + /** + * Get the Language ID + * + * @return int + */ + public function getLangId() + { + return $this->langId; + } + + /** + * Set the East Asian Language + * + * @param string $eastAsia + * The value for the east asian language + * @return self + */ + public function setEastAsia($eastAsia) + { + $this->eastAsia = $this->validateLocale($eastAsia); + + return $this; + } + + /** + * Get the East Asian Language + * + * @return string|null + */ + public function getEastAsia() + { + return $this->eastAsia; + } + + /** + * Set the Complex Script Language + * + * @param string $bidirectional + * The value for the complex script language + * @return self + */ + public function setBidirectional($bidirectional) + { + $this->bidirectional = $this->validateLocale($bidirectional); + + return $this; + } + + /** + * Get the Complex Script Language + * + * @return string|null + */ + public function getBidirectional() + { + return $this->bidirectional; + } + + /** + * Validates that the language passed is in the format xx-xx + * + * @param string $locale + * @return string + */ + private function validateLocale($locale) + { + if (strlen($locale) === 2) { + return strtolower($locale) . '-' . strtoupper($locale); + } + + if ($locale !== null && $locale !== 'zxx' && strstr($locale, '-') === false) { + throw new \InvalidArgumentException($locale . ' is not a valid language code'); + } + + return $locale; + } +} diff --git a/PhpOffice/PhpWord/Style/Line.php b/PhpOffice/PhpWord/Style/Line.php new file mode 100644 index 0000000..a9952ee --- /dev/null +++ b/PhpOffice/PhpWord/Style/Line.php @@ -0,0 +1,281 @@ +flip; + } + + /** + * Set flip + * + * @param bool $value + * @return self + */ + public function setFlip($value = false) + { + $this->flip = $this->setBoolVal($value, $this->flip); + + return $this; + } + + /** + * Get connectorType + * + * @return string + */ + public function getConnectorType() + { + return $this->connectorType; + } + + /** + * Set connectorType + * + * @param string $value + * @return self + */ + public function setConnectorType($value = null) + { + $enum = array( + self::CONNECTOR_TYPE_STRAIGHT, + ); + $this->connectorType = $this->setEnumVal($value, $enum, $this->connectorType); + + return $this; + } + + /** + * Get weight + * + * @return int + */ + public function getWeight() + { + return $this->weight; + } + + /** + * Set weight + * + * @param int $value Weight in points + * @return self + */ + public function setWeight($value = null) + { + $this->weight = $this->setNumericVal($value, $this->weight); + + return $this; + } + + /** + * Get color + * + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * Set color + * + * @param string $value + * @return self + */ + public function setColor($value = null) + { + $this->color = $value; + + return $this; + } + + /** + * Get beginArrow + * + * @return string + */ + public function getBeginArrow() + { + return $this->beginArrow; + } + + /** + * Set beginArrow + * + * @param string $value + * @return self + */ + public function setBeginArrow($value = null) + { + $enum = array( + self::ARROW_STYLE_BLOCK, self::ARROW_STYLE_CLASSIC, self::ARROW_STYLE_DIAMOND, + self::ARROW_STYLE_OPEN, self::ARROW_STYLE_OVAL, + ); + $this->beginArrow = $this->setEnumVal($value, $enum, $this->beginArrow); + + return $this; + } + + /** + * Get endArrow + * + * @return string + */ + public function getEndArrow() + { + return $this->endArrow; + } + + /** + * Set endArrow + * + * @param string $value + * @return self + */ + public function setEndArrow($value = null) + { + $enum = array( + self::ARROW_STYLE_BLOCK, self::ARROW_STYLE_CLASSIC, self::ARROW_STYLE_DIAMOND, + self::ARROW_STYLE_OPEN, self::ARROW_STYLE_OVAL, + ); + $this->endArrow = $this->setEnumVal($value, $enum, $this->endArrow); + + return $this; + } + + /** + * Get Dash + * + * @return string + */ + public function getDash() + { + return $this->dash; + } + + /** + * Set Dash + * + * @param string $value + * @return self + */ + public function setDash($value = null) + { + $enum = array( + self::DASH_STYLE_DASH, self::DASH_STYLE_DASH_DOT, self::DASH_STYLE_LONG_DASH, + self::DASH_STYLE_LONG_DASH_DOT, self::DASH_STYLE_LONG_DASH_DOT_DOT, self::DASH_STYLE_ROUND_DOT, + self::DASH_STYLE_SQUARE_DOT, + ); + $this->dash = $this->setEnumVal($value, $enum, $this->dash); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/LineNumbering.php b/PhpOffice/PhpWord/Style/LineNumbering.php new file mode 100644 index 0000000..451252d --- /dev/null +++ b/PhpOffice/PhpWord/Style/LineNumbering.php @@ -0,0 +1,164 @@ +setStyleByArray($style); + } + + /** + * Get start + * + * @return int + */ + public function getStart() + { + return $this->start; + } + + /** + * Set start + * + * @param int $value + * @return self + */ + public function setStart($value = null) + { + $this->start = $this->setIntVal($value, $this->start); + + return $this; + } + + /** + * Get increment + * + * @return int + */ + public function getIncrement() + { + return $this->increment; + } + + /** + * Set increment + * + * @param int $value + * @return self + */ + public function setIncrement($value = null) + { + $this->increment = $this->setIntVal($value, $this->increment); + + return $this; + } + + /** + * Get distance + * + * @return int|float + */ + public function getDistance() + { + return $this->distance; + } + + /** + * Set distance + * + * @param int|float $value + * @return self + */ + public function setDistance($value = null) + { + $this->distance = $this->setNumericVal($value, $this->distance); + + return $this; + } + + /** + * Get restart + * + * @return string + */ + public function getRestart() + { + return $this->restart; + } + + /** + * Set distance + * + * @param string $value + * @return self + */ + public function setRestart($value = null) + { + $enum = array(self::LINE_NUMBERING_CONTINUOUS, self::LINE_NUMBERING_NEW_PAGE, self::LINE_NUMBERING_NEW_SECTION); + $this->restart = $this->setEnumVal($value, $enum, $this->restart); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/ListItem.php b/PhpOffice/PhpWord/Style/ListItem.php new file mode 100644 index 0000000..4293940 --- /dev/null +++ b/PhpOffice/PhpWord/Style/ListItem.php @@ -0,0 +1,280 @@ +setNumStyle($numStyle); + } else { + $this->setListType(); + } + } + + /** + * Get List Type + * + * @return int + */ + public function getListType() + { + return $this->listType; + } + + /** + * Set legacy list type for version < 0.10.0 + * + * @param int $value + * @return self + */ + public function setListType($value = self::TYPE_BULLET_FILLED) + { + $enum = array( + self::TYPE_SQUARE_FILLED, self::TYPE_BULLET_FILLED, + self::TYPE_BULLET_EMPTY, self::TYPE_NUMBER, + self::TYPE_NUMBER_NESTED, self::TYPE_ALPHANUM, + ); + $this->listType = $this->setEnumVal($value, $enum, $this->listType); + $this->getListTypeStyle(); + + return $this; + } + + /** + * Get numbering style name + * + * @return string + */ + public function getNumStyle() + { + return $this->numStyle; + } + + /** + * Set numbering style name + * + * @param string $value + * @return self + */ + public function setNumStyle($value) + { + $this->numStyle = $value; + $numStyleObject = Style::getStyle($this->numStyle); + if ($numStyleObject instanceof Numbering) { + $this->numId = $numStyleObject->getIndex(); + $numStyleObject->setNumId($this->numId); + } + + return $this; + } + + /** + * Get numbering Id + * + * @return int + */ + public function getNumId() + { + return $this->numId; + } + + /** + * Set numbering Id. Same numId means same list + * @param mixed $numInt + */ + public function setNumId($numInt) + { + $this->numId = $numInt; + $this->getListTypeStyle(); + } + + /** + * Get legacy numbering definition + * + * @return array + * @since 0.10.0 + */ + private function getListTypeStyle() + { + // Check if legacy style already registered in global Style collection + $numStyle = 'PHPWordListType' . $this->listType; + + if ($this->numId) { + $numStyle .= 'NumId' . $this->numId; + } + + if (Style::getStyle($numStyle) !== null) { + $this->setNumStyle($numStyle); + + return; + } + + // Property mapping for numbering level information + $properties = array('start', 'format', 'text', 'alignment', 'tabPos', 'left', 'hanging', 'font', 'hint'); + + // Legacy level information + $listTypeStyles = array( + self::TYPE_SQUARE_FILLED => array( + 'type' => 'hybridMultilevel', + 'levels' => array( + 0 => '1, bullet, , left, 720, 720, 360, Wingdings, default', + 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', + 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', + 3 => '1, bullet, , left, 2880, 2880, 360, Symbol, default', + 4 => '1, bullet, o, left, 3600, 3600, 360, Courier New, default', + 5 => '1, bullet, , left, 4320, 4320, 360, Wingdings, default', + 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', + 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', + 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', + ), + ), + self::TYPE_BULLET_FILLED => array( + 'type' => 'hybridMultilevel', + 'levels' => array( + 0 => '1, bullet, , left, 720, 720, 360, Symbol, default', + 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', + 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', + 3 => '1, bullet, , left, 2880, 2880, 360, Symbol, default', + 4 => '1, bullet, o, left, 3600, 3600, 360, Courier New, default', + 5 => '1, bullet, , left, 4320, 4320, 360, Wingdings, default', + 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', + 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', + 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', + ), + ), + self::TYPE_BULLET_EMPTY => array( + 'type' => 'hybridMultilevel', + 'levels' => array( + 0 => '1, bullet, o, left, 720, 720, 360, Courier New, default', + 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', + 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', + 3 => '1, bullet, , left, 2880, 2880, 360, Symbol, default', + 4 => '1, bullet, o, left, 3600, 3600, 360, Courier New, default', + 5 => '1, bullet, , left, 4320, 4320, 360, Wingdings, default', + 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', + 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', + 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', + ), + ), + self::TYPE_NUMBER => array( + 'type' => 'hybridMultilevel', + 'levels' => array( + 0 => '1, decimal, %1., left, 720, 720, 360, , default', + 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', + 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', + 3 => '1, bullet, , left, 2880, 2880, 360, Symbol, default', + 4 => '1, bullet, o, left, 3600, 3600, 360, Courier New, default', + 5 => '1, bullet, , left, 4320, 4320, 360, Wingdings, default', + 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', + 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', + 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', + ), + ), + self::TYPE_NUMBER_NESTED => array( + 'type' => 'multilevel', + 'levels' => array( + 0 => '1, decimal, %1., left, 360, 360, 360, , ', + 1 => '1, decimal, %1.%2., left, 792, 792, 432, , ', + 2 => '1, decimal, %1.%2.%3., left, 1224, 1224, 504, , ', + 3 => '1, decimal, %1.%2.%3.%4., left, 1800, 1728, 648, , ', + 4 => '1, decimal, %1.%2.%3.%4.%5., left, 2520, 2232, 792, , ', + 5 => '1, decimal, %1.%2.%3.%4.%5.%6., left, 2880, 2736, 936, , ', + 6 => '1, decimal, %1.%2.%3.%4.%5.%6.%7., left, 3600, 3240, 1080, , ', + 7 => '1, decimal, %1.%2.%3.%4.%5.%6.%7.%8., left, 3960, 3744, 1224, , ', + 8 => '1, decimal, %1.%2.%3.%4.%5.%6.%7.%8.%9., left, 4680, 4320, 1440, , ', + ), + ), + self::TYPE_ALPHANUM => array( + 'type' => 'multilevel', + 'levels' => array( + 0 => '1, decimal, %1., left, 720, 720, 360, , ', + 1 => '1, lowerLetter, %2., left, 1440, 1440, 360, , ', + 2 => '1, lowerRoman, %3., right, 2160, 2160, 180, , ', + 3 => '1, decimal, %4., left, 2880, 2880, 360, , ', + 4 => '1, lowerLetter, %5., left, 3600, 3600, 360, , ', + 5 => '1, lowerRoman, %6., right, 4320, 4320, 180, , ', + 6 => '1, decimal, %7., left, 5040, 5040, 360, , ', + 7 => '1, lowerLetter, %8., left, 5760, 5760, 360, , ', + 8 => '1, lowerRoman, %9., right, 6480, 6480, 180, , ', + ), + ), + ); + + // Populate style and register to global Style register + $style = $listTypeStyles[$this->listType]; + $numProperties = count($properties); + foreach ($style['levels'] as $key => $value) { + $level = array(); + $levelProperties = explode(', ', $value); + $level['level'] = $key; + for ($i = 0; $i < $numProperties; $i++) { + $property = $properties[$i]; + $level[$property] = $levelProperties[$i]; + } + $style['levels'][$key] = $level; + } + Style::addNumberingStyle($numStyle, $style); + $this->setNumStyle($numStyle); + } +} diff --git a/PhpOffice/PhpWord/Style/Numbering.php b/PhpOffice/PhpWord/Style/Numbering.php new file mode 100644 index 0000000..f7855cf --- /dev/null +++ b/PhpOffice/PhpWord/Style/Numbering.php @@ -0,0 +1,131 @@ +numId; + } + + /** + * Set Id + * + * @param int $value + * @return self + */ + public function setNumId($value) + { + $this->numId = $this->setIntVal($value, $this->numId); + + return $this; + } + + /** + * Get multilevel type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set multilevel type + * + * @param string $value + * @return self + */ + public function setType($value) + { + $enum = array('singleLevel', 'multilevel', 'hybridMultilevel'); + $this->type = $this->setEnumVal($value, $enum, $this->type); + + return $this; + } + + /** + * Get levels + * + * @return NumberingLevel[] + */ + public function getLevels() + { + return $this->levels; + } + + /** + * Set multilevel type + * + * @param array $values + * @return self + */ + public function setLevels($values) + { + if (is_array($values)) { + foreach ($values as $key => $value) { + $numberingLevel = new NumberingLevel(); + if (is_array($value)) { + $numberingLevel->setStyleByArray($value); + $numberingLevel->setLevel($key); + } + $this->levels[$key] = $numberingLevel; + } + } + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/NumberingLevel.php b/PhpOffice/PhpWord/Style/NumberingLevel.php new file mode 100644 index 0000000..e9b32f0 --- /dev/null +++ b/PhpOffice/PhpWord/Style/NumberingLevel.php @@ -0,0 +1,458 @@ +level; + } + + /** + * Set level + * + * @param int $value + * @return self + */ + public function setLevel($value) + { + $this->level = $this->setIntVal($value, $this->level); + + return $this; + } + + /** + * Get start + * + * @return int + */ + public function getStart() + { + return $this->start; + } + + /** + * Set start + * + * @param int $value + * @return self + */ + public function setStart($value) + { + $this->start = $this->setIntVal($value, $this->start); + + return $this; + } + + /** + * Get format + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set format + * + * @param string $value + * @return self + */ + public function setFormat($value) + { + $this->format = $this->setEnumVal($value, NumberFormat::values(), $this->format); + + return $this; + } + + /** + * Get restart + * + * @return int + */ + public function getRestart() + { + return $this->restart; + } + + /** + * Set restart + * + * @param int $value + * @return self + */ + public function setRestart($value) + { + $this->restart = $this->setIntVal($value, $this->restart); + + return $this; + } + + /** + * Get related paragraph style + * + * @return string + */ + public function getPStyle() + { + return $this->pStyle; + } + + /** + * Set related paragraph style + * + * @param string $value + * @return self + */ + public function setPStyle($value) + { + $this->pStyle = $value; + + return $this; + } + + /** + * Get suffix + * + * @return string + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * Set suffix + * + * @param string $value + * @return self + */ + public function setSuffix($value) + { + $enum = array('tab', 'space', 'nothing'); + $this->suffix = $this->setEnumVal($value, $enum, $this->suffix); + + return $this; + } + + /** + * Get text + * + * @return string + */ + public function getText() + { + return $this->text; + } + + /** + * Set text + * + * @param string $value + * @return self + */ + public function setText($value) + { + $this->text = $value; + + return $this; + } + + /** + * @since 0.13.0 + * + * @return string + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * @since 0.13.0 + * + * @param string $value + * + * @return self + */ + public function setAlignment($value) + { + if (Jc::isValid($value)) { + $this->alignment = $value; + } + + return $this; + } + + /** + * @deprecated 0.13.0 Use the `getAlignment` method instead. + * + * @return string + * + * @codeCoverageIgnore + */ + public function getAlign() + { + return $this->getAlignment(); + } + + /** + * @deprecated 0.13.0 Use the `setAlignment` method instead. + * + * @param string $value + * + * @return self + * + * @codeCoverageIgnore + */ + public function setAlign($value) + { + return $this->setAlignment($value); + } + + /** + * Get left + * + * @return int + */ + public function getLeft() + { + return $this->left; + } + + /** + * Set left + * + * @param int $value + * @return self + */ + public function setLeft($value) + { + $this->left = $this->setIntVal($value, $this->left); + + return $this; + } + + /** + * Get hanging + * + * @return int + */ + public function getHanging() + { + return $this->hanging; + } + + /** + * Set hanging + * + * @param int $value + * @return self + */ + public function setHanging($value) + { + $this->hanging = $this->setIntVal($value, $this->hanging); + + return $this; + } + + /** + * Get tab + * + * @return int + */ + public function getTabPos() + { + return $this->tabPos; + } + + /** + * Set tab + * + * @param int $value + * @return self + */ + public function setTabPos($value) + { + $this->tabPos = $this->setIntVal($value, $this->tabPos); + + return $this; + } + + /** + * Get font + * + * @return string + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font + * + * @param string $value + * @return self + */ + public function setFont($value) + { + $this->font = $value; + + return $this; + } + + /** + * Get hint + * + * @return string + */ + public function getHint() + { + return $this->hint; + } + + /** + * Set hint + * + * @param string $value + * @return self + */ + public function setHint($value = null) + { + $enum = array('default', 'eastAsia', 'cs'); + $this->hint = $this->setEnumVal($value, $enum, $this->hint); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Outline.php b/PhpOffice/PhpWord/Style/Outline.php new file mode 100644 index 0000000..a04ad97 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Outline.php @@ -0,0 +1,308 @@ +setStyleByArray($style); + } + + /** + * Get unit + * + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * Get weight + * + * @return int|float + */ + public function getWeight() + { + return $this->weight; + } + + /** + * Set weight + * + * @param int|float $value + * @return self + */ + public function setWeight($value = null) + { + $this->weight = $this->setNumericVal($value, null); + + return $this; + } + + /** + * Get color + * + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * Set color + * + * @param string $value + * @return self + */ + public function setColor($value = null) + { + $this->color = $value; + + return $this; + } + + /** + * Get dash type + * + * @return string + */ + public function getDash() + { + return $this->dash; + } + + /** + * Set dash type + * + * @param string $value + * @return self + */ + public function setDash($value = null) + { + $this->dash = $value; + + return $this; + } + + /** + * Get line style + * + * @return string + */ + public function getLine() + { + return $this->line; + } + + /** + * Set line style + * + * @param string $value + * @return self + */ + public function setLine($value = null) + { + $enum = array(self::LINE_SINGLE, self::LINE_THIN_THIN, self::LINE_THIN_THICK, + self::LINE_THICK_THIN, self::LINE_THICK_BETWEEN_THIN, ); + $this->line = $this->setEnumVal($value, $enum, null); + + return $this; + } + + /** + * Get endCap style + * + * @return string + */ + public function getEndCap() + { + return $this->endCap; + } + + /** + * Set endCap style + * + * @param string $value + * @return self + */ + public function setEndCap($value = null) + { + $enum = array(self::ENDCAP_FLAT, self::ENDCAP_SQUARE, self::ENDCAP_ROUND); + $this->endCap = $this->setEnumVal($value, $enum, null); + + return $this; + } + + /** + * Get startArrow + * + * @return string + */ + public function getStartArrow() + { + return $this->startArrow; + } + + /** + * Set pattern + * + * @param string $value + * @return self + */ + public function setStartArrow($value = null) + { + $enum = array(self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, + self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN, ); + $this->startArrow = $this->setEnumVal($value, $enum, null); + + return $this; + } + + /** + * Get endArrow + * + * @return string + */ + public function getEndArrow() + { + return $this->endArrow; + } + + /** + * Set pattern + * + * @param string $value + * @return self + */ + public function setEndArrow($value = null) + { + $enum = array(self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, + self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN, ); + $this->endArrow = $this->setEnumVal($value, $enum, null); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Paper.php b/PhpOffice/PhpWord/Style/Paper.php new file mode 100644 index 0000000..3c93ed8 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Paper.php @@ -0,0 +1,194 @@ + array(297, 420, 'mm'), + 'A4' => array(210, 297, 'mm'), + 'A5' => array(148, 210, 'mm'), + 'B5' => array(176, 250, 'mm'), + 'Folio' => array(8.5, 13, 'in'), + 'Legal' => array(8.5, 14, 'in'), + 'Letter' => array(8.5, 11, 'in'), + ); + + /** + * Paper size + * + * @var string + */ + private $size = 'A4'; + + /** + * Width + * + * @var float (twip) + */ + private $width; + + /** + * Height + * + * @var float (twip) + */ + private $height; + + /** + * Create a new instance + * + * @param string $size + */ + public function __construct($size = 'A4') + { + $this->setSize($size); + } + + /** + * Get size + * + * @return string + */ + public function getSize() + { + return $this->size; + } + + /** + * Set size + * + * @param string $size + * @return self + */ + public function setSize($size) + { + $this->size = $this->setEnumVal($size, array_keys($this->sizes), $this->size); + + list($width, $height, $unit) = $this->sizes[$this->size]; + + if ($unit == 'mm') { + $this->width = Converter::cmToTwip($width / 10); + $this->height = Converter::cmToTwip($height / 10); + } else { + $this->width = Converter::inchToTwip($width); + $this->height = Converter::inchToTwip($height); + } + + return $this; + } + + /** + * Get width + * + * @return float + */ + public function getWidth() + { + return $this->width; + } + + /** + * Get height + * + * @return float + */ + public function getHeight() + { + return $this->height; + } +} diff --git a/PhpOffice/PhpWord/Style/Paragraph.php b/PhpOffice/PhpWord/Style/Paragraph.php new file mode 100644 index 0000000..72f0f80 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Paragraph.php @@ -0,0 +1,874 @@ + 'lineHeight', 'line-spacing' => 'spacing'); + + /** + * Parent style + * + * @var string + */ + private $basedOn = 'Normal'; + + /** + * Style for next paragraph + * + * @var string + */ + private $next; + + /** + * @var string + */ + private $alignment = ''; + + /** + * Indentation + * + * @var \PhpOffice\PhpWord\Style\Indentation + */ + private $indentation; + + /** + * Spacing + * + * @var \PhpOffice\PhpWord\Style\Spacing + */ + private $spacing; + + /** + * Text line height + * + * @var int + */ + private $lineHeight; + + /** + * Allow first/last line to display on a separate page + * + * @var bool + */ + private $widowControl = true; + + /** + * Keep paragraph with next paragraph + * + * @var bool + */ + private $keepNext = false; + + /** + * Keep all lines on one page + * + * @var bool + */ + private $keepLines = false; + + /** + * Start paragraph on next page + * + * @var bool + */ + private $pageBreakBefore = false; + + /** + * Numbering style name + * + * @var string + */ + private $numStyle; + + /** + * Numbering level + * + * @var int + */ + private $numLevel = 0; + + /** + * Set of Custom Tab Stops + * + * @var \PhpOffice\PhpWord\Style\Tab[] + */ + private $tabs = array(); + + /** + * Shading + * + * @var \PhpOffice\PhpWord\Style\Shading + */ + private $shading; + + /** + * Ignore Spacing Above and Below When Using Identical Styles + * + * @var bool + */ + private $contextualSpacing = false; + + /** + * Right to Left Paragraph Layout + * + * @var bool + */ + private $bidi = false; + + /** + * Vertical Character Alignment on Line + * + * @var string + */ + private $textAlignment; + + /** + * Suppress hyphenation for paragraph + * + * @var bool + */ + private $suppressAutoHyphens = false; + + /** + * Set Style value + * + * @param string $key + * @param mixed $value + * @return self + */ + public function setStyleValue($key, $value) + { + $key = Text::removeUnderscorePrefix($key); + if ('indent' == $key || 'hanging' == $key) { + $value = $value * 720; // 720 twips is 0.5 inch + } + + return parent::setStyleValue($key, $value); + } + + /** + * Get style values + * + * An experiment to retrieve all style values in one function. This will + * reduce function call and increase cohesion between functions. Should be + * implemented in all styles. + * + * @ignoreScrutinizerPatch + * @return array + */ + public function getStyleValues() + { + $styles = array( + 'name' => $this->getStyleName(), + 'basedOn' => $this->getBasedOn(), + 'next' => $this->getNext(), + 'alignment' => $this->getAlignment(), + 'indentation' => $this->getIndentation(), + 'spacing' => $this->getSpace(), + 'pagination' => array( + 'widowControl' => $this->hasWidowControl(), + 'keepNext' => $this->isKeepNext(), + 'keepLines' => $this->isKeepLines(), + 'pageBreak' => $this->hasPageBreakBefore(), + ), + 'numbering' => array( + 'style' => $this->getNumStyle(), + 'level' => $this->getNumLevel(), + ), + 'tabs' => $this->getTabs(), + 'shading' => $this->getShading(), + 'contextualSpacing' => $this->hasContextualSpacing(), + 'bidi' => $this->isBidi(), + 'textAlignment' => $this->getTextAlignment(), + 'suppressAutoHyphens' => $this->hasSuppressAutoHyphens(), + ); + + return $styles; + } + + /** + * @since 0.13.0 + * + * @return string + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * @since 0.13.0 + * + * @param string $value + * + * @return self + */ + public function setAlignment($value) + { + if (Jc::isValid($value)) { + $this->alignment = $value; + } + + return $this; + } + + /** + * @deprecated 0.13.0 Use the `getAlignment` method instead. + * + * @return string + * + * @codeCoverageIgnore + */ + public function getAlign() + { + return $this->getAlignment(); + } + + /** + * @deprecated 0.13.0 Use the `setAlignment` method instead. + * + * @param string $value + * + * @return self + * + * @codeCoverageIgnore + */ + public function setAlign($value = null) + { + return $this->setAlignment($value); + } + + /** + * Get parent style ID + * + * @return string + */ + public function getBasedOn() + { + return $this->basedOn; + } + + /** + * Set parent style ID + * + * @param string $value + * @return self + */ + public function setBasedOn($value = 'Normal') + { + $this->basedOn = $value; + + return $this; + } + + /** + * Get style for next paragraph + * + * @return string + */ + public function getNext() + { + return $this->next; + } + + /** + * Set style for next paragraph + * + * @param string $value + * @return self + */ + public function setNext($value = null) + { + $this->next = $value; + + return $this; + } + + /** + * Get shading + * + * @return \PhpOffice\PhpWord\Style\Indentation + */ + public function getIndentation() + { + return $this->indentation; + } + + /** + * Set shading + * + * @param mixed $value + * @return self + */ + public function setIndentation($value = null) + { + $this->setObjectVal($value, 'Indentation', $this->indentation); + + return $this; + } + + /** + * Get indentation + * + * @return int + */ + public function getIndent() + { + return $this->getChildStyleValue($this->indentation, 'left'); + } + + /** + * Set indentation + * + * @param int $value + * @return self + */ + public function setIndent($value = null) + { + return $this->setIndentation(array('left' => $value)); + } + + /** + * Get hanging + * + * @return int + */ + public function getHanging() + { + return $this->getChildStyleValue($this->indentation, 'hanging'); + } + + /** + * Set hanging + * + * @param int $value + * @return self + */ + public function setHanging($value = null) + { + return $this->setIndentation(array('hanging' => $value)); + } + + /** + * Get spacing + * + * @return \PhpOffice\PhpWord\Style\Spacing + * @todo Rename to getSpacing in 1.0 + */ + public function getSpace() + { + return $this->spacing; + } + + /** + * Set spacing + * + * @param mixed $value + * @return self + * @todo Rename to setSpacing in 1.0 + */ + public function setSpace($value = null) + { + $this->setObjectVal($value, 'Spacing', $this->spacing); + + return $this; + } + + /** + * Get space before paragraph + * + * @return int + */ + public function getSpaceBefore() + { + return $this->getChildStyleValue($this->spacing, 'before'); + } + + /** + * Set space before paragraph + * + * @param int $value + * @return self + */ + public function setSpaceBefore($value = null) + { + return $this->setSpace(array('before' => $value)); + } + + /** + * Get space after paragraph + * + * @return int + */ + public function getSpaceAfter() + { + return $this->getChildStyleValue($this->spacing, 'after'); + } + + /** + * Set space after paragraph + * + * @param int $value + * @return self + */ + public function setSpaceAfter($value = null) + { + return $this->setSpace(array('after' => $value)); + } + + /** + * Get spacing between lines + * + * @return int|float + */ + public function getSpacing() + { + return $this->getChildStyleValue($this->spacing, 'line'); + } + + /** + * Set spacing between lines + * + * @param int|float $value + * @return self + */ + public function setSpacing($value = null) + { + return $this->setSpace(array('line' => $value)); + } + + /** + * Get spacing line rule + * + * @return string + */ + public function getSpacingLineRule() + { + return $this->getChildStyleValue($this->spacing, 'lineRule'); + } + + /** + * Set the spacing line rule + * + * @param string $value Possible values are defined in LineSpacingRule + * @return \PhpOffice\PhpWord\Style\Paragraph + */ + public function setSpacingLineRule($value) + { + return $this->setSpace(array('lineRule' => $value)); + } + + /** + * Get line height + * + * @return int|float + */ + public function getLineHeight() + { + return $this->lineHeight; + } + + /** + * Set the line height + * + * @param int|float|string $lineHeight + * + * @throws \PhpOffice\PhpWord\Exception\InvalidStyleException + * @return self + */ + public function setLineHeight($lineHeight) + { + if (is_string($lineHeight)) { + $lineHeight = (float) (preg_replace('/[^0-9\.\,]/', '', $lineHeight)); + } + + if ((!is_int($lineHeight) && !is_float($lineHeight)) || !$lineHeight) { + throw new InvalidStyleException('Line height must be a valid number'); + } + + $this->lineHeight = $lineHeight; + $this->setSpacing(($lineHeight - 1) * self::LINE_HEIGHT); + $this->setSpacingLineRule(\PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO); + + return $this; + } + + /** + * Get allow first/last line to display on a separate page setting + * + * @return bool + */ + public function hasWidowControl() + { + return $this->widowControl; + } + + /** + * Set keep paragraph with next paragraph setting + * + * @param bool $value + * @return self + */ + public function setWidowControl($value = true) + { + $this->widowControl = $this->setBoolVal($value, $this->widowControl); + + return $this; + } + + /** + * Get keep paragraph with next paragraph setting + * + * @return bool + */ + public function isKeepNext() + { + return $this->keepNext; + } + + /** + * Set keep paragraph with next paragraph setting + * + * @param bool $value + * @return self + */ + public function setKeepNext($value = true) + { + $this->keepNext = $this->setBoolVal($value, $this->keepNext); + + return $this; + } + + /** + * Get keep all lines on one page setting + * + * @return bool + */ + public function isKeepLines() + { + return $this->keepLines; + } + + /** + * Set keep all lines on one page setting + * + * @param bool $value + * @return self + */ + public function setKeepLines($value = true) + { + $this->keepLines = $this->setBoolVal($value, $this->keepLines); + + return $this; + } + + /** + * Get start paragraph on next page setting + * + * @return bool + */ + public function hasPageBreakBefore() + { + return $this->pageBreakBefore; + } + + /** + * Set start paragraph on next page setting + * + * @param bool $value + * @return self + */ + public function setPageBreakBefore($value = true) + { + $this->pageBreakBefore = $this->setBoolVal($value, $this->pageBreakBefore); + + return $this; + } + + /** + * Get numbering style name + * + * @return string + */ + public function getNumStyle() + { + return $this->numStyle; + } + + /** + * Set numbering style name + * + * @param string $value + * @return self + */ + public function setNumStyle($value) + { + $this->numStyle = $value; + + return $this; + } + + /** + * Get numbering level + * + * @return int + */ + public function getNumLevel() + { + return $this->numLevel; + } + + /** + * Set numbering level + * + * @param int $value + * @return self + */ + public function setNumLevel($value = 0) + { + $this->numLevel = $this->setIntVal($value, $this->numLevel); + + return $this; + } + + /** + * Get tabs + * + * @return \PhpOffice\PhpWord\Style\Tab[] + */ + public function getTabs() + { + return $this->tabs; + } + + /** + * Set tabs + * + * @param array $value + * @return self + */ + public function setTabs($value = null) + { + if (is_array($value)) { + $this->tabs = $value; + } + + return $this; + } + + /** + * Get allow first/last line to display on a separate page setting + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getWidowControl() + { + return $this->hasWidowControl(); + } + + /** + * Get keep paragraph with next paragraph setting + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getKeepNext() + { + return $this->isKeepNext(); + } + + /** + * Get keep all lines on one page setting + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getKeepLines() + { + return $this->isKeepLines(); + } + + /** + * Get start paragraph on next page setting + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getPageBreakBefore() + { + return $this->hasPageBreakBefore(); + } + + /** + * Get shading + * + * @return \PhpOffice\PhpWord\Style\Shading + */ + public function getShading() + { + return $this->shading; + } + + /** + * Set shading + * + * @param mixed $value + * @return self + */ + public function setShading($value = null) + { + $this->setObjectVal($value, 'Shading', $this->shading); + + return $this; + } + + /** + * Get contextualSpacing + * + * @return bool + */ + public function hasContextualSpacing() + { + return $this->contextualSpacing; + } + + /** + * Set contextualSpacing + * + * @param bool $contextualSpacing + * @return self + */ + public function setContextualSpacing($contextualSpacing) + { + $this->contextualSpacing = $contextualSpacing; + + return $this; + } + + /** + * Get bidirectional + * + * @return bool + */ + public function isBidi() + { + return $this->bidi; + } + + /** + * Set bidi + * + * @param bool $bidi + * Set to true to write from right to left + * @return self + */ + public function setBidi($bidi) + { + $this->bidi = $bidi; + + return $this; + } + + /** + * Get textAlignment + * + * @return string + */ + public function getTextAlignment() + { + return $this->textAlignment; + } + + /** + * Set textAlignment + * + * @param string $textAlignment + * @return self + */ + public function setTextAlignment($textAlignment) + { + TextAlignment::validate($textAlignment); + $this->textAlignment = $textAlignment; + + return $this; + } + + /** + * @return bool + */ + public function hasSuppressAutoHyphens() + { + return $this->suppressAutoHyphens; + } + + /** + * @param bool $suppressAutoHyphens + */ + public function setSuppressAutoHyphens($suppressAutoHyphens) + { + $this->suppressAutoHyphens = (bool) $suppressAutoHyphens; + } +} diff --git a/PhpOffice/PhpWord/Style/Row.php b/PhpOffice/PhpWord/Style/Row.php new file mode 100644 index 0000000..ad801af --- /dev/null +++ b/PhpOffice/PhpWord/Style/Row.php @@ -0,0 +1,159 @@ +tblHeader; + } + + /** + * Is tblHeader + * + * @param bool $value + * @return self + */ + public function setTblHeader($value = true) + { + $this->tblHeader = $this->setBoolVal($value, $this->tblHeader); + + return $this; + } + + /** + * Is cantSplit + * + * @return bool + */ + public function isCantSplit() + { + return $this->cantSplit; + } + + /** + * Is cantSplit + * + * @param bool $value + * @return self + */ + public function setCantSplit($value = true) + { + $this->cantSplit = $this->setBoolVal($value, $this->cantSplit); + + return $this; + } + + /** + * Is exactHeight + * + * @return bool + */ + public function isExactHeight() + { + return $this->exactHeight; + } + + /** + * Set exactHeight + * + * @param bool $value + * @return self + */ + public function setExactHeight($value = true) + { + $this->exactHeight = $this->setBoolVal($value, $this->exactHeight); + + return $this; + } + + /** + * Get tblHeader + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getTblHeader() + { + return $this->isTblHeader(); + } + + /** + * Get cantSplit + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getCantSplit() + { + return $this->isCantSplit(); + } + + /** + * Get exactHeight + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getExactHeight() + { + return $this->isExactHeight(); + } +} diff --git a/PhpOffice/PhpWord/Style/Section.php b/PhpOffice/PhpWord/Style/Section.php new file mode 100644 index 0000000..ff9b0be --- /dev/null +++ b/PhpOffice/PhpWord/Style/Section.php @@ -0,0 +1,636 @@ +setPaperSize(); + } + + /** + * Get paper size + * + * @return string + */ + public function getPaperSize() + { + return $this->paper->getSize(); + } + + /** + * Set paper size + * + * @param string $value + * @return self + */ + public function setPaperSize($value = 'A4') + { + if ($this->paper === null) { + $this->paper = new Paper(); + } + $this->paper->setSize($value); + $this->pageSizeW = $this->paper->getWidth(); + $this->pageSizeH = $this->paper->getHeight(); + + return $this; + } + + /** + * Set Setting Value + * + * @param string $key + * @param string $value + * @return self + */ + public function setSettingValue($key, $value) + { + return $this->setStyleValue($key, $value); + } + + /** + * Set orientation + * + * @param string $value + * @return self + */ + public function setOrientation($value = null) + { + $enum = array(self::ORIENTATION_PORTRAIT, self::ORIENTATION_LANDSCAPE); + $this->orientation = $this->setEnumVal($value, $enum, $this->orientation); + + /** @var int|float $longSide Type hint */ + $longSide = $this->pageSizeW >= $this->pageSizeH ? $this->pageSizeW : $this->pageSizeH; + + /** @var int|float $shortSide Type hint */ + $shortSide = $this->pageSizeW < $this->pageSizeH ? $this->pageSizeW : $this->pageSizeH; + + if ($this->orientation == self::ORIENTATION_PORTRAIT) { + $this->pageSizeW = $shortSide; + $this->pageSizeH = $longSide; + } else { + $this->pageSizeW = $longSide; + $this->pageSizeH = $shortSide; + } + + return $this; + } + + /** + * Get Page Orientation + * + * @return string + */ + public function getOrientation() + { + return $this->orientation; + } + + /** + * Set Portrait Orientation + * + * @return self + */ + public function setPortrait() + { + return $this->setOrientation(self::ORIENTATION_PORTRAIT); + } + + /** + * Set Landscape Orientation + * + * @return self + */ + public function setLandscape() + { + return $this->setOrientation(self::ORIENTATION_LANDSCAPE); + } + + /** + * Get Page Size Width + * + * @return int|float|null + * + * @since 0.12.0 + */ + public function getPageSizeW() + { + return $this->pageSizeW; + } + + /** + * @param int|float|null $value + * + * @return \PhpOffice\PhpWord\Style\Section + * + * @since 0.12.0 + */ + public function setPageSizeW($value = null) + { + $this->pageSizeW = $this->setNumericVal($value, self::DEFAULT_WIDTH); + + return $this; + } + + /** + * Get Page Size Height + * + * @return int|float|null + * + * @since 0.12.0 + */ + public function getPageSizeH() + { + return $this->pageSizeH; + } + + /** + * @param int|float|null $value + * + * @return \PhpOffice\PhpWord\Style\Section + * + * @since 0.12.0 + */ + public function setPageSizeH($value = null) + { + $this->pageSizeH = $this->setNumericVal($value, self::DEFAULT_HEIGHT); + + return $this; + } + + /** + * Get Margin Top + * + * @return int|float + */ + public function getMarginTop() + { + return $this->marginTop; + } + + /** + * Set Margin Top + * + * @param int|float $value + * @return self + */ + public function setMarginTop($value = null) + { + $this->marginTop = $this->setNumericVal($value, self::DEFAULT_MARGIN); + + return $this; + } + + /** + * Get Margin Left + * + * @return int|float + */ + public function getMarginLeft() + { + return $this->marginLeft; + } + + /** + * Set Margin Left + * + * @param int|float $value + * @return self + */ + public function setMarginLeft($value = null) + { + $this->marginLeft = $this->setNumericVal($value, self::DEFAULT_MARGIN); + + return $this; + } + + /** + * Get Margin Right + * + * @return int|float + */ + public function getMarginRight() + { + return $this->marginRight; + } + + /** + * Set Margin Right + * + * @param int|float $value + * @return self + */ + public function setMarginRight($value = null) + { + $this->marginRight = $this->setNumericVal($value, self::DEFAULT_MARGIN); + + return $this; + } + + /** + * Get Margin Bottom + * + * @return int|float + */ + public function getMarginBottom() + { + return $this->marginBottom; + } + + /** + * Set Margin Bottom + * + * @param int|float $value + * @return self + */ + public function setMarginBottom($value = null) + { + $this->marginBottom = $this->setNumericVal($value, self::DEFAULT_MARGIN); + + return $this; + } + + /** + * Get gutter + * + * @return int|float + */ + public function getGutter() + { + return $this->gutter; + } + + /** + * Set gutter + * + * @param int|float $value + * @return self + */ + public function setGutter($value = null) + { + $this->gutter = $this->setNumericVal($value, self::DEFAULT_GUTTER); + + return $this; + } + + /** + * Get Header Height + * + * @return int|float + */ + public function getHeaderHeight() + { + return $this->headerHeight; + } + + /** + * Set Header Height + * + * @param int|float $value + * @return self + */ + public function setHeaderHeight($value = null) + { + $this->headerHeight = $this->setNumericVal($value, self::DEFAULT_HEADER_HEIGHT); + + return $this; + } + + /** + * Get Footer Height + * + * @return int|float + */ + public function getFooterHeight() + { + return $this->footerHeight; + } + + /** + * Set Footer Height + * + * @param int|float $value + * @return self + */ + public function setFooterHeight($value = null) + { + $this->footerHeight = $this->setNumericVal($value, self::DEFAULT_FOOTER_HEIGHT); + + return $this; + } + + /** + * Get page numbering start + * + * @return null|int + */ + public function getPageNumberingStart() + { + return $this->pageNumberingStart; + } + + /** + * Set page numbering start + * + * @param null|int $pageNumberingStart + * @return self + */ + public function setPageNumberingStart($pageNumberingStart = null) + { + $this->pageNumberingStart = $pageNumberingStart; + + return $this; + } + + /** + * Get Section Columns Count + * + * @return int + */ + public function getColsNum() + { + return $this->colsNum; + } + + /** + * Set Section Columns Count + * + * @param int $value + * @return self + */ + public function setColsNum($value = null) + { + $this->colsNum = $this->setIntVal($value, self::DEFAULT_COLUMN_COUNT); + + return $this; + } + + /** + * Get Section Space Between Columns + * + * @return int|float + */ + public function getColsSpace() + { + return $this->colsSpace; + } + + /** + * Set Section Space Between Columns + * + * @param int|float $value + * @return self + */ + public function setColsSpace($value = null) + { + $this->colsSpace = $this->setNumericVal($value, self::DEFAULT_COLUMN_SPACING); + + return $this; + } + + /** + * Get Break Type + * + * @return string + */ + public function getBreakType() + { + return $this->breakType; + } + + /** + * Set Break Type + * + * @param string $value + * @return self + */ + public function setBreakType($value = null) + { + $this->breakType = $value; + + return $this; + } + + /** + * Get line numbering + * + * @return \PhpOffice\PhpWord\Style\LineNumbering + */ + public function getLineNumbering() + { + return $this->lineNumbering; + } + + /** + * Set line numbering + * + * @param mixed $value + * @return self + */ + public function setLineNumbering($value = null) + { + $this->setObjectVal($value, 'LineNumbering', $this->lineNumbering); + + return $this; + } + + /** + * Get vertical alignment + * + * @return string + */ + public function getVAlign() + { + return $this->vAlign; + } + + /** + * Set vertical alignment + * + * @param string $value + * @return self + */ + public function setVAlign($value = null) + { + VerticalJc::validate($value); + $this->vAlign = $value; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Shading.php b/PhpOffice/PhpWord/Style/Shading.php new file mode 100644 index 0000000..154df26 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Shading.php @@ -0,0 +1,146 @@ +setStyleByArray($style); + } + + /** + * Get pattern + * + * @return string + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * Set pattern + * + * @param string $value + * @return self + */ + public function setPattern($value = null) + { + $enum = array( + self::PATTERN_CLEAR, self::PATTERN_SOLID, self::PATTERN_HSTRIPE, + self::PATTERN_VSTRIPE, self::PATTERN_DSTRIPE, self::PATTERN_HCROSS, self::PATTERN_DCROSS, + ); + $this->pattern = $this->setEnumVal($value, $enum, $this->pattern); + + return $this; + } + + /** + * Get color + * + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * Set pattern + * + * @param string $value + * @return self + */ + public function setColor($value = null) + { + $this->color = $value; + + return $this; + } + + /** + * Get fill + * + * @return string + */ + public function getFill() + { + return $this->fill; + } + + /** + * Set fill + * + * @param string $value + * @return self + */ + public function setFill($value = null) + { + $this->fill = $value; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Shadow.php b/PhpOffice/PhpWord/Style/Shadow.php new file mode 100644 index 0000000..1379a32 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Shadow.php @@ -0,0 +1,97 @@ +setStyleByArray($style); + } + + /** + * Get color + * + * @return string + */ + public function getColor() + { + return $this->color; + } + + /** + * Set color + * + * @param string $value + * @return self + */ + public function setColor($value = null) + { + $this->color = $value; + + return $this; + } + + /** + * Get offset + * + * @return string + */ + public function getOffset() + { + return $this->offset; + } + + /** + * Set offset + * + * @param string $value + * @return self + */ + public function setOffset($value = null) + { + $this->offset = $value; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Shape.php b/PhpOffice/PhpWord/Style/Shape.php new file mode 100644 index 0000000..0c3f817 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Shape.php @@ -0,0 +1,255 @@ +setStyleByArray($style); + } + + /** + * Get points + * + * @return string + */ + public function getPoints() + { + return $this->points; + } + + /** + * Set points + * + * @param string $value + * @return self + */ + public function setPoints($value = null) + { + $this->points = $value; + + return $this; + } + + /** + * Get roundness + * + * @return int|float + */ + public function getRoundness() + { + return $this->roundness; + } + + /** + * Set roundness + * + * @param int|float $value + * @return self + */ + public function setRoundness($value = null) + { + $this->roundness = $this->setNumericVal($value, null); + + return $this; + } + + /** + * Get frame + * + * @return \PhpOffice\PhpWord\Style\Frame + */ + public function getFrame() + { + return $this->frame; + } + + /** + * Set frame + * + * @param mixed $value + * @return self + */ + public function setFrame($value = null) + { + $this->setObjectVal($value, 'Frame', $this->frame); + + return $this; + } + + /** + * Get fill + * + * @return \PhpOffice\PhpWord\Style\Fill + */ + public function getFill() + { + return $this->fill; + } + + /** + * Set fill + * + * @param mixed $value + * @return self + */ + public function setFill($value = null) + { + $this->setObjectVal($value, 'Fill', $this->fill); + + return $this; + } + + /** + * Get outline + * + * @return \PhpOffice\PhpWord\Style\Outline + */ + public function getOutline() + { + return $this->outline; + } + + /** + * Set outline + * + * @param mixed $value + * @return self + */ + public function setOutline($value = null) + { + $this->setObjectVal($value, 'Outline', $this->outline); + + return $this; + } + + /** + * Get shadow + * + * @return \PhpOffice\PhpWord\Style\Shadow + */ + public function getShadow() + { + return $this->shadow; + } + + /** + * Set shadow + * + * @param mixed $value + * @return self + */ + public function setShadow($value = null) + { + $this->setObjectVal($value, 'Shadow', $this->shadow); + + return $this; + } + + /** + * Get 3D extrusion + * + * @return \PhpOffice\PhpWord\Style\Extrusion + */ + public function getExtrusion() + { + return $this->extrusion; + } + + /** + * Set 3D extrusion + * + * @param mixed $value + * @return self + */ + public function setExtrusion($value = null) + { + $this->setObjectVal($value, 'Extrusion', $this->extrusion); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Spacing.php b/PhpOffice/PhpWord/Style/Spacing.php new file mode 100644 index 0000000..9bfb228 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Spacing.php @@ -0,0 +1,187 @@ +setStyleByArray($style); + } + + /** + * Get before + * + * @return int|float + */ + public function getBefore() + { + return $this->before; + } + + /** + * Set before + * + * @param int|float $value + * @return self + */ + public function setBefore($value = null) + { + $this->before = $this->setNumericVal($value, $this->before); + + return $this; + } + + /** + * Get after + * + * @return int|float + */ + public function getAfter() + { + return $this->after; + } + + /** + * Set after + * + * @param int|float $value + * @return self + */ + public function setAfter($value = null) + { + $this->after = $this->setNumericVal($value, $this->after); + + return $this; + } + + /** + * Get line + * + * @return int|float + */ + public function getLine() + { + return $this->line; + } + + /** + * Set distance + * + * @param int|float $value + * @return self + */ + public function setLine($value = null) + { + $this->line = $this->setNumericVal($value, $this->line); + + return $this; + } + + /** + * Get line rule + * + * @return string + */ + public function getLineRule() + { + return $this->lineRule; + } + + /** + * Set line rule + * + * @param string $value + * @return self + */ + public function setLineRule($value = null) + { + LineSpacingRule::validate($value); + $this->lineRule = $value; + + return $this; + } + + /** + * Get line rule + * + * @return string + * @deprecated Use getLineRule() instead + * @codeCoverageIgnore + */ + public function getRule() + { + return $this->lineRule; + } + + /** + * Set line rule + * + * @param string $value + * @return self + * @deprecated Use setLineRule() instead + * @codeCoverageIgnore + */ + public function setRule($value = null) + { + $this->lineRule = $value; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/TOC.php b/PhpOffice/PhpWord/Style/TOC.php new file mode 100644 index 0000000..2efd54a --- /dev/null +++ b/PhpOffice/PhpWord/Style/TOC.php @@ -0,0 +1,116 @@ +getPosition(); + } + + /** + * Set Tab Position + * + * @param int|float $value + * @return self + */ + public function setTabPos($value) + { + return $this->setPosition($value); + } + + /** + * Get Tab Leader + * + * @return string + */ + public function getTabLeader() + { + return $this->getLeader(); + } + + /** + * Set Tab Leader + * + * @param string $value + * @return self + */ + public function setTabLeader($value = self::TAB_LEADER_DOT) + { + return $this->setLeader($value); + } + + /** + * Get Indent + * + * @return int|float + */ + public function getIndent() + { + return $this->indent; + } + + /** + * Set Indent + * + * @param int|float $value + * @return self + */ + public function setIndent($value) + { + $this->indent = $this->setNumericVal($value, $this->indent); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Tab.php b/PhpOffice/PhpWord/Style/Tab.php new file mode 100644 index 0000000..d3cf5bd --- /dev/null +++ b/PhpOffice/PhpWord/Style/Tab.php @@ -0,0 +1,173 @@ +type = $this->setEnumVal($type, $stopTypes, $this->type); + $this->position = $this->setNumericVal($position, $this->position); + $this->leader = $this->setEnumVal($leader, $leaderTypes, $this->leader); + } + + /** + * Get stop type + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Set stop type + * + * @param string $value + * @return self + */ + public function setType($value) + { + $enum = array( + self::TAB_STOP_CLEAR, self::TAB_STOP_LEFT, self::TAB_STOP_CENTER, + self::TAB_STOP_RIGHT, self::TAB_STOP_DECIMAL, self::TAB_STOP_BAR, + self::TAB_STOP_NUM, + ); + $this->type = $this->setEnumVal($value, $enum, $this->type); + + return $this; + } + + /** + * Get leader + * + * @return string + */ + public function getLeader() + { + return $this->leader; + } + + /** + * Set leader + * + * @param string $value + * @return self + */ + public function setLeader($value) + { + $enum = array( + self::TAB_LEADER_NONE, self::TAB_LEADER_DOT, self::TAB_LEADER_HYPHEN, + self::TAB_LEADER_UNDERSCORE, self::TAB_LEADER_HEAVY, self::TAB_LEADER_MIDDLEDOT, + ); + $this->leader = $this->setEnumVal($value, $enum, $this->leader); + + return $this; + } + + /** + * Get position + * + * @return int|float + */ + public function getPosition() + { + return $this->position; + } + + /** + * Set position + * + * @param int|float $value + * @return self + */ + public function setPosition($value) + { + $this->position = $this->setNumericVal($value, $this->position); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/Table.php b/PhpOffice/PhpWord/Style/Table.php new file mode 100644 index 0000000..f777ac6 --- /dev/null +++ b/PhpOffice/PhpWord/Style/Table.php @@ -0,0 +1,810 @@ +firstRowStyle = clone $this; + $this->firstRowStyle->isFirstRow = true; + unset($this->firstRowStyle->firstRowStyle, $this->firstRowStyle->borderInsideHSize, $this->firstRowStyle->borderInsideHColor, $this->firstRowStyle->borderInsideVSize, $this->firstRowStyle->borderInsideVColor, $this->firstRowStyle->cellMarginTop, $this->firstRowStyle->cellMarginLeft, $this->firstRowStyle->cellMarginRight, $this->firstRowStyle->cellMarginBottom, $this->firstRowStyle->cellSpacing); + $this->firstRowStyle->setStyleByArray($firstRowStyle); + } + + if ($tableStyle !== null && is_array($tableStyle)) { + $this->setStyleByArray($tableStyle); + } + } + + /** + * @param float|int $cellSpacing + */ + public function setCellSpacing($cellSpacing = null) + { + $this->cellSpacing = $cellSpacing; + } + + /** + * @return float|int + */ + public function getCellSpacing() + { + return $this->cellSpacing; + } + + /** + * Set first row + * + * @return \PhpOffice\PhpWord\Style\Table + */ + public function getFirstRow() + { + return $this->firstRowStyle; + } + + /** + * Get background + * + * @return string + */ + public function getBgColor() + { + if ($this->shading !== null) { + return $this->shading->getFill(); + } + + return null; + } + + /** + * Set background + * + * @param string $value + * @return self + */ + public function setBgColor($value = null) + { + $this->setShading(array('fill' => $value)); + + return $this; + } + + /** + * Get TLRBHV Border Size + * + * @return int[] + */ + public function getBorderSize() + { + return array( + $this->getBorderTopSize(), + $this->getBorderLeftSize(), + $this->getBorderRightSize(), + $this->getBorderBottomSize(), + $this->getBorderInsideHSize(), + $this->getBorderInsideVSize(), + ); + } + + /** + * Set TLRBHV Border Size + * + * @param int $value Border size in eighths of a point (1/8 point) + * @return self + */ + public function setBorderSize($value = null) + { + $this->setBorderTopSize($value); + $this->setBorderLeftSize($value); + $this->setBorderRightSize($value); + $this->setBorderBottomSize($value); + $this->setBorderInsideHSize($value); + $this->setBorderInsideVSize($value); + + return $this; + } + + /** + * Get TLRBHV Border Color + * + * @return string[] + */ + public function getBorderColor() + { + return array( + $this->getBorderTopColor(), + $this->getBorderLeftColor(), + $this->getBorderRightColor(), + $this->getBorderBottomColor(), + $this->getBorderInsideHColor(), + $this->getBorderInsideVColor(), + ); + } + + /** + * Set TLRBHV Border Color + * + * @param string $value + * @return self + */ + public function setBorderColor($value = null) + { + $this->setBorderTopColor($value); + $this->setBorderLeftColor($value); + $this->setBorderRightColor($value); + $this->setBorderBottomColor($value); + $this->setBorderInsideHColor($value); + $this->setBorderInsideVColor($value); + + return $this; + } + + /** + * Get border size inside horizontal + * + * @return int + */ + public function getBorderInsideHSize() + { + return $this->getTableOnlyProperty('borderInsideHSize'); + } + + /** + * Set border size inside horizontal + * + * @param int $value + * @return self + */ + public function setBorderInsideHSize($value = null) + { + return $this->setTableOnlyProperty('borderInsideHSize', $value); + } + + /** + * Get border color inside horizontal + * + * @return string + */ + public function getBorderInsideHColor() + { + return $this->getTableOnlyProperty('borderInsideHColor'); + } + + /** + * Set border color inside horizontal + * + * @param string $value + * @return self + */ + public function setBorderInsideHColor($value = null) + { + return $this->setTableOnlyProperty('borderInsideHColor', $value, false); + } + + /** + * Get border size inside vertical + * + * @return int + */ + public function getBorderInsideVSize() + { + return $this->getTableOnlyProperty('borderInsideVSize'); + } + + /** + * Set border size inside vertical + * + * @param int $value + * @return self + */ + public function setBorderInsideVSize($value = null) + { + return $this->setTableOnlyProperty('borderInsideVSize', $value); + } + + /** + * Get border color inside vertical + * + * @return string + */ + public function getBorderInsideVColor() + { + return $this->getTableOnlyProperty('borderInsideVColor'); + } + + /** + * Set border color inside vertical + * + * @param string $value + * @return self + */ + public function setBorderInsideVColor($value = null) + { + return $this->setTableOnlyProperty('borderInsideVColor', $value, false); + } + + /** + * Get cell margin top + * + * @return int + */ + public function getCellMarginTop() + { + return $this->getTableOnlyProperty('cellMarginTop'); + } + + /** + * Set cell margin top + * + * @param int $value + * @return self + */ + public function setCellMarginTop($value = null) + { + return $this->setTableOnlyProperty('cellMarginTop', $value); + } + + /** + * Get cell margin left + * + * @return int + */ + public function getCellMarginLeft() + { + return $this->getTableOnlyProperty('cellMarginLeft'); + } + + /** + * Set cell margin left + * + * @param int $value + * @return self + */ + public function setCellMarginLeft($value = null) + { + return $this->setTableOnlyProperty('cellMarginLeft', $value); + } + + /** + * Get cell margin right + * + * @return int + */ + public function getCellMarginRight() + { + return $this->getTableOnlyProperty('cellMarginRight'); + } + + /** + * Set cell margin right + * + * @param int $value + * @return self + */ + public function setCellMarginRight($value = null) + { + return $this->setTableOnlyProperty('cellMarginRight', $value); + } + + /** + * Get cell margin bottom + * + * @return int + */ + public function getCellMarginBottom() + { + return $this->getTableOnlyProperty('cellMarginBottom'); + } + + /** + * Set cell margin bottom + * + * @param int $value + * @return self + */ + public function setCellMarginBottom($value = null) + { + return $this->setTableOnlyProperty('cellMarginBottom', $value); + } + + /** + * Get cell margin + * + * @return int[] + */ + public function getCellMargin() + { + return array( + $this->cellMarginTop, + $this->cellMarginLeft, + $this->cellMarginRight, + $this->cellMarginBottom, + ); + } + + /** + * Set TLRB cell margin + * + * @param int $value Margin in twips + * @return self + */ + public function setCellMargin($value = null) + { + $this->setCellMarginTop($value); + $this->setCellMarginLeft($value); + $this->setCellMarginRight($value); + $this->setCellMarginBottom($value); + + return $this; + } + + /** + * Check if any of the margin is not null + * + * @return bool + */ + public function hasMargin() + { + $margins = $this->getCellMargin(); + + return $margins !== array_filter($margins, 'is_null'); + } + + /** + * Get shading + * + * @return \PhpOffice\PhpWord\Style\Shading + */ + public function getShading() + { + return $this->shading; + } + + /** + * Set shading + * + * @param mixed $value + * @return self + */ + public function setShading($value = null) + { + $this->setObjectVal($value, 'Shading', $this->shading); + + return $this; + } + + /** + * @since 0.13.0 + * + * @return string + */ + public function getAlignment() + { + return $this->alignment; + } + + /** + * @since 0.13.0 + * + * @param string $value + * + * @return self + */ + public function setAlignment($value) + { + if (JcTable::isValid($value) || Jc::isValid($value)) { + $this->alignment = $value; + } + + return $this; + } + + /** + * @deprecated 0.13.0 Use the `getAlignment` method instead. + * + * @return string + * + * @codeCoverageIgnore + */ + public function getAlign() + { + return $this->getAlignment(); + } + + /** + * @deprecated 0.13.0 Use the `setAlignment` method instead. + * + * @param string $value + * + * @return self + * + * @codeCoverageIgnore + */ + public function setAlign($value = null) + { + return $this->setAlignment($value); + } + + /** + * Get width + * + * @return int|float + */ + public function getWidth() + { + return $this->width; + } + + /** + * Set width + * + * @param int|float $value + * @return self + */ + public function setWidth($value = null) + { + $this->width = $this->setNumericVal($value, $this->width); + + return $this; + } + + /** + * Get width unit + * + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * Set width unit + * + * @param string $value + * @return self + */ + public function setUnit($value = null) + { + TblWidth::validate($value); + $this->unit = $value; + + return $this; + } + + /** + * Get layout + * + * @return string + */ + public function getLayout() + { + return $this->layout; + } + + /** + * Set layout + * + * @param string $value + * @return self + */ + public function setLayout($value = null) + { + $enum = array(self::LAYOUT_AUTO, self::LAYOUT_FIXED); + $this->layout = $this->setEnumVal($value, $enum, $this->layout); + + return $this; + } + + /** + * Get table style only property by checking if it's a firstRow + * + * This is necessary since firstRow style is cloned from table style but + * without certain properties activated, e.g. margins + * + * @param string $property + * @return int|string|null + */ + private function getTableOnlyProperty($property) + { + if (false === $this->isFirstRow) { + return $this->$property; + } + + return null; + } + + /** + * Set table style only property by checking if it's a firstRow + * + * This is necessary since firstRow style is cloned from table style but + * without certain properties activated, e.g. margins + * + * @param string $property + * @param int|string $value + * @param bool $isNumeric + * @return self + */ + private function setTableOnlyProperty($property, $value, $isNumeric = true) + { + if (false === $this->isFirstRow) { + if (true === $isNumeric) { + $this->$property = $this->setNumericVal($value, $this->$property); + } else { + $this->$property = $value; + } + } + + return $this; + } + + /** + * Get position + * + * @return \PhpOffice\PhpWord\Style\TablePosition + */ + public function getPosition() + { + return $this->position; + } + + /** + * Set position + * + * @param mixed $value + * @return self + */ + public function setPosition($value = null) + { + $this->setObjectVal($value, 'TablePosition', $this->position); + + return $this; + } + + /** + * @return TblWidthComplexType + */ + public function getIndent() + { + return $this->indent; + } + + /** + * @param TblWidthComplexType $indent + * @return self + * @see http://www.datypic.com/sc/ooxml/e-w_tblInd-1.html + */ + public function setIndent(TblWidthComplexType $indent) + { + $this->indent = $indent; + + return $this; + } + + /** + * Get the columnWidths + * + * @return null|int[] + */ + public function getColumnWidths() + { + return $this->columnWidths; + } + + /** + * The column widths + * + * @param int[] $value + */ + public function setColumnWidths(array $value = null) + { + $this->columnWidths = $value; + } + + /** + * Get bidiVisual + * + * @return bool + */ + public function isBidiVisual() + { + return $this->bidiVisual; + } + + /** + * Set bidiVisual + * + * @param bool $bidi + * Set to true to visually present table as Right to Left + * @return self + */ + public function setBidiVisual($bidi) + { + $this->bidiVisual = $bidi; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/TablePosition.php b/PhpOffice/PhpWord/Style/TablePosition.php new file mode 100644 index 0000000..d4b7083 --- /dev/null +++ b/PhpOffice/PhpWord/Style/TablePosition.php @@ -0,0 +1,410 @@ +setStyleByArray($style); + } + + /** + * Get distance from left of table to text + * + * @return int + */ + public function getLeftFromText() + { + return $this->leftFromText; + } + + /** + * Set distance from left of table to text + * + * @param int $value + * @return self + */ + public function setLeftFromText($value = null) + { + $this->leftFromText = $this->setNumericVal($value, $this->leftFromText); + + return $this; + } + + /** + * Get distance from right of table to text + * + * @return int + */ + public function getRightFromText() + { + return $this->rightFromText; + } + + /** + * Set distance from right of table to text + * + * @param int $value + * @return self + */ + public function setRightFromText($value = null) + { + $this->rightFromText = $this->setNumericVal($value, $this->rightFromText); + + return $this; + } + + /** + * Get distance from top of table to text + * + * @return int + */ + public function getTopFromText() + { + return $this->topFromText; + } + + /** + * Set distance from top of table to text + * + * @param int $value + * @return self + */ + public function setTopFromText($value = null) + { + $this->topFromText = $this->setNumericVal($value, $this->topFromText); + + return $this; + } + + /** + * Get distance from bottom of table to text + * + * @return int + */ + public function getBottomFromText() + { + return $this->bottomFromText; + } + + /** + * Set distance from bottom of table to text + * + * @param int $value + * @return self + */ + public function setBottomFromText($value = null) + { + $this->bottomFromText = $this->setNumericVal($value, $this->bottomFromText); + + return $this; + } + + /** + * Get table vertical anchor + * + * @return string + */ + public function getVertAnchor() + { + return $this->vertAnchor; + } + + /** + * Set table vertical anchor + * + * @param string $value + * @return self + */ + public function setVertAnchor($value = null) + { + $enum = array( + self::VANCHOR_TEXT, + self::VANCHOR_MARGIN, + self::VANCHOR_PAGE, + ); + $this->vertAnchor = $this->setEnumVal($value, $enum, $this->vertAnchor); + + return $this; + } + + /** + * Get table horizontal anchor + * + * @return string + */ + public function getHorzAnchor() + { + return $this->horzAnchor; + } + + /** + * Set table horizontal anchor + * + * @param string $value + * @return self + */ + public function setHorzAnchor($value = null) + { + $enum = array( + self::HANCHOR_TEXT, + self::HANCHOR_MARGIN, + self::HANCHOR_PAGE, + ); + $this->horzAnchor = $this->setEnumVal($value, $enum, $this->horzAnchor); + + return $this; + } + + /** + * Get relative horizontal alignment from anchor + * + * @return string + */ + public function getTblpXSpec() + { + return $this->tblpXSpec; + } + + /** + * Set relative horizontal alignment from anchor + * + * @param string $value + * @return self + */ + public function setTblpXSpec($value = null) + { + $enum = array( + self::XALIGN_LEFT, + self::XALIGN_CENTER, + self::XALIGN_RIGHT, + self::XALIGN_INSIDE, + self::XALIGN_OUTSIDE, + ); + $this->tblpXSpec = $this->setEnumVal($value, $enum, $this->tblpXSpec); + + return $this; + } + + /** + * Get absolute horizontal distance from anchor + * + * @return int + */ + public function getTblpX() + { + return $this->tblpX; + } + + /** + * Set absolute horizontal distance from anchor + * + * @param int $value + * @return self + */ + public function setTblpX($value = null) + { + $this->tblpX = $this->setNumericVal($value, $this->tblpX); + + return $this; + } + + /** + * Get relative vertical alignment from anchor + * + * @return string + */ + public function getTblpYSpec() + { + return $this->tblpYSpec; + } + + /** + * Set relative vertical alignment from anchor + * + * @param string $value + * @return self + */ + public function setTblpYSpec($value = null) + { + $enum = array( + self::YALIGN_INLINE, + self::YALIGN_TOP, + self::YALIGN_CENTER, + self::YALIGN_BOTTOM, + self::YALIGN_INSIDE, + self::YALIGN_OUTSIDE, + ); + $this->tblpYSpec = $this->setEnumVal($value, $enum, $this->tblpYSpec); + + return $this; + } + + /** + * Get absolute vertical distance from anchor + * + * @return int + */ + public function getTblpY() + { + return $this->tblpY; + } + + /** + * Set absolute vertical distance from anchor + * + * @param int $value + * @return self + */ + public function setTblpY($value = null) + { + $this->tblpY = $this->setNumericVal($value, $this->tblpY); + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Style/TextBox.php b/PhpOffice/PhpWord/Style/TextBox.php new file mode 100644 index 0000000..e9c0f0c --- /dev/null +++ b/PhpOffice/PhpWord/Style/TextBox.php @@ -0,0 +1,230 @@ +innerMarginTop = $value; + } + + /** + * Get margin top + * + * @return int + */ + public function getInnerMarginTop() + { + return $this->innerMarginTop; + } + + /** + * Set margin left. + * + * @param int $value + */ + public function setInnerMarginLeft($value = null) + { + $this->innerMarginLeft = $value; + } + + /** + * Get margin left + * + * @return int + */ + public function getInnerMarginLeft() + { + return $this->innerMarginLeft; + } + + /** + * Set margin right. + * + * @param int $value + */ + public function setInnerMarginRight($value = null) + { + $this->innerMarginRight = $value; + } + + /** + * Get margin right + * + * @return int + */ + public function getInnerMarginRight() + { + return $this->innerMarginRight; + } + + /** + * Set margin bottom. + * + * @param int $value + */ + public function setInnerMarginBottom($value = null) + { + $this->innerMarginBottom = $value; + } + + /** + * Get margin bottom + * + * @return int + */ + public function getInnerMarginBottom() + { + return $this->innerMarginBottom; + } + + /** + * Set TLRB cell margin. + * + * @param int $value Margin in twips + */ + public function setInnerMargin($value = null) + { + $this->setInnerMarginTop($value); + $this->setInnerMarginLeft($value); + $this->setInnerMarginRight($value); + $this->setInnerMarginBottom($value); + } + + /** + * Get cell margin + * + * @return int[] + */ + public function getInnerMargin() + { + return array($this->innerMarginLeft, $this->innerMarginTop, $this->innerMarginRight, $this->innerMarginBottom); + } + + /** + * Has inner margin? + * + * @return bool + */ + public function hasInnerMargins() + { + $hasInnerMargins = false; + $margins = $this->getInnerMargin(); + $numMargins = count($margins); + for ($i = 0; $i < $numMargins; $i++) { + if ($margins[$i] !== null) { + $hasInnerMargins = true; + } + } + + return $hasInnerMargins; + } + + /** + * Set border size. + * + * @param int $value Size in points + */ + public function setBorderSize($value = null) + { + $this->borderSize = $value; + } + + /** + * Get border size + * + * @return int + */ + public function getBorderSize() + { + return $this->borderSize; + } + + /** + * Set border color. + * + * @param string $value + */ + public function setBorderColor($value = null) + { + $this->borderColor = $value; + } + + /** + * Get border color + * + * @return string + */ + public function getBorderColor() + { + return $this->borderColor; + } +} diff --git a/PhpOffice/PhpWord/Template.php b/PhpOffice/PhpWord/Template.php new file mode 100644 index 0000000..c42696f --- /dev/null +++ b/PhpOffice/PhpWord/Template.php @@ -0,0 +1,27 @@ +tempDocumentFilename = tempnam(Settings::getTempDir(), 'PhpWord'); + if (false === $this->tempDocumentFilename) { + throw new CreateTemporaryFileException(); // @codeCoverageIgnore + } + + // Template file cloning + if (false === copy($documentTemplate, $this->tempDocumentFilename)) { + throw new CopyFileException($documentTemplate, $this->tempDocumentFilename); // @codeCoverageIgnore + } + + // Temporary document content extraction + $this->zipClass = new ZipArchive(); + $this->zipClass->open($this->tempDocumentFilename); + $index = 1; + while (false !== $this->zipClass->locateName($this->getHeaderName($index))) { + $this->tempDocumentHeaders[$index] = $this->readPartWithRels($this->getHeaderName($index)); + $index++; + } + $index = 1; + while (false !== $this->zipClass->locateName($this->getFooterName($index))) { + $this->tempDocumentFooters[$index] = $this->readPartWithRels($this->getFooterName($index)); + $index++; + } + + $this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName()); + $this->tempDocumentSettingsPart = $this->readPartWithRels($this->getSettingsPartName()); + $this->tempDocumentContentTypes = $this->zipClass->getFromName($this->getDocumentContentTypesName()); + } + + /** + * Expose zip class + * + * To replace an image: $templateProcessor->zip()->AddFromString("word/media/image1.jpg", file_get_contents($file));
+ * To read a file: $templateProcessor->zip()->getFromName("word/media/image1.jpg"); + * + * @return \PhpOffice\PhpWord\Shared\ZipArchive + */ + public function zip() + { + return $this->zipClass; + } + + /** + * @param string $fileName + * + * @return string + */ + protected function readPartWithRels($fileName) + { + $relsFileName = $this->getRelationsName($fileName); + $partRelations = $this->zipClass->getFromName($relsFileName); + if ($partRelations !== false) { + $this->tempDocumentRelations[$fileName] = $partRelations; + } + + return $this->fixBrokenMacros($this->zipClass->getFromName($fileName)); + } + + /** + * @param string $xml + * @param \XSLTProcessor $xsltProcessor + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * + * @return string + */ + protected function transformSingleXml($xml, $xsltProcessor) + { + $orignalLibEntityLoader = libxml_disable_entity_loader(true); + $domDocument = new \DOMDocument(); + if (false === $domDocument->loadXML($xml)) { + throw new Exception('Could not load the given XML document.'); + } + + $transformedXml = $xsltProcessor->transformToXml($domDocument); + if (false === $transformedXml) { + throw new Exception('Could not transform the given XML document.'); + } + libxml_disable_entity_loader($orignalLibEntityLoader); + + return $transformedXml; + } + + /** + * @param mixed $xml + * @param \XSLTProcessor $xsltProcessor + * + * @return mixed + */ + protected function transformXml($xml, $xsltProcessor) + { + if (is_array($xml)) { + foreach ($xml as &$item) { + $item = $this->transformSingleXml($item, $xsltProcessor); + } + unset($item); + } else { + $xml = $this->transformSingleXml($xml, $xsltProcessor); + } + + return $xml; + } + + /** + * Applies XSL style sheet to template's parts. + * + * Note: since the method doesn't make any guess on logic of the provided XSL style sheet, + * make sure that output is correctly escaped. Otherwise you may get broken document. + * + * @param \DOMDocument $xslDomDocument + * @param array $xslOptions + * @param string $xslOptionsUri + * + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + public function applyXslStyleSheet($xslDomDocument, $xslOptions = array(), $xslOptionsUri = '') + { + $xsltProcessor = new \XSLTProcessor(); + + $xsltProcessor->importStylesheet($xslDomDocument); + if (false === $xsltProcessor->setParameter($xslOptionsUri, $xslOptions)) { + throw new Exception('Could not set values for the given XSL style sheet parameters.'); + } + + $this->tempDocumentHeaders = $this->transformXml($this->tempDocumentHeaders, $xsltProcessor); + $this->tempDocumentMainPart = $this->transformXml($this->tempDocumentMainPart, $xsltProcessor); + $this->tempDocumentFooters = $this->transformXml($this->tempDocumentFooters, $xsltProcessor); + } + + /** + * @param string $macro + * + * @return string + */ + protected static function ensureMacroCompleted($macro) + { + if (substr($macro, 0, 2) !== '${' && substr($macro, -1) !== '}') { + $macro = '${' . $macro . '}'; + } + + return $macro; + } + + /** + * @param string $subject + * + * @return string + */ + protected static function ensureUtf8Encoded($subject) + { + if (!Text::isUTF8($subject)) { + $subject = utf8_encode($subject); + } + + return $subject; + } + + /** + * @param string $search + * @param \PhpOffice\PhpWord\Element\AbstractElement $complexType + */ + public function setComplexValue($search, \PhpOffice\PhpWord\Element\AbstractElement $complexType) + { + $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; + + $xmlWriter = new XMLWriter(); + /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $elementWriter */ + $elementWriter = new $objectClass($xmlWriter, $complexType, true); + $elementWriter->write(); + + $where = $this->findContainingXmlBlockForMacro($search, 'w:r'); + $block = $this->getSlice($where['start'], $where['end']); + $textParts = $this->splitTextIntoTexts($block); + $this->replaceXmlBlock($search, $textParts, 'w:r'); + + $search = static::ensureMacroCompleted($search); + $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:r'); + } + + /** + * @param string $search + * @param \PhpOffice\PhpWord\Element\AbstractElement $complexType + */ + public function setComplexBlock($search, \PhpOffice\PhpWord\Element\AbstractElement $complexType) + { + $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); + $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; + + $xmlWriter = new XMLWriter(); + /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $elementWriter */ + $elementWriter = new $objectClass($xmlWriter, $complexType, false); + $elementWriter->write(); + + $this->replaceXmlBlock($search, $xmlWriter->getData(), 'w:p'); + } + + /** + * @param mixed $search + * @param mixed $replace + * @param int $limit + */ + public function setValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT) + { + if (is_array($search)) { + foreach ($search as &$item) { + $item = static::ensureMacroCompleted($item); + } + unset($item); + } else { + $search = static::ensureMacroCompleted($search); + } + + if (is_array($replace)) { + foreach ($replace as &$item) { + $item = static::ensureUtf8Encoded($item); + } + unset($item); + } else { + $replace = static::ensureUtf8Encoded($replace); + } + + if (Settings::isOutputEscapingEnabled()) { + $xmlEscaper = new Xml(); + $replace = $xmlEscaper->escape($replace); + } + + $this->tempDocumentHeaders = $this->setValueForPart($search, $replace, $this->tempDocumentHeaders, $limit); + $this->tempDocumentMainPart = $this->setValueForPart($search, $replace, $this->tempDocumentMainPart, $limit); + $this->tempDocumentFooters = $this->setValueForPart($search, $replace, $this->tempDocumentFooters, $limit); + } + + /** + * Set values from a one-dimensional array of "variable => value"-pairs. + * + * @param array $values + */ + public function setValues(array $values) + { + foreach ($values as $macro => $replace) { + $this->setValue($macro, $replace); + } + } + + private function getImageArgs($varNameWithArgs) + { + $varElements = explode(':', $varNameWithArgs); + array_shift($varElements); // first element is name of variable => remove it + + $varInlineArgs = array(); + // size format documentation: https://msdn.microsoft.com/en-us/library/documentformat.openxml.vml.shape%28v=office.14%29.aspx?f=255&MSPPError=-2147217396 + foreach ($varElements as $argIdx => $varArg) { + if (strpos($varArg, '=')) { // arg=value + list($argName, $argValue) = explode('=', $varArg, 2); + $argName = strtolower($argName); + if ($argName == 'size') { + list($varInlineArgs['width'], $varInlineArgs['height']) = explode('x', $argValue, 2); + } else { + $varInlineArgs[strtolower($argName)] = $argValue; + } + } elseif (preg_match('/^([0-9]*[a-z%]{0,2}|auto)x([0-9]*[a-z%]{0,2}|auto)$/i', $varArg)) { // 60x40 + list($varInlineArgs['width'], $varInlineArgs['height']) = explode('x', $varArg, 2); + } else { // :60:40:f + switch ($argIdx) { + case 0: + $varInlineArgs['width'] = $varArg; + break; + case 1: + $varInlineArgs['height'] = $varArg; + break; + case 2: + $varInlineArgs['ratio'] = $varArg; + break; + } + } + } + + return $varInlineArgs; + } + + private function chooseImageDimension($baseValue, $inlineValue, $defaultValue) + { + $value = $baseValue; + if (is_null($value) && isset($inlineValue)) { + $value = $inlineValue; + } + if (!preg_match('/^([0-9]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value)) { + $value = null; + } + if (is_null($value)) { + $value = $defaultValue; + } + if (is_numeric($value)) { + $value .= 'px'; + } + + return $value; + } + + private function fixImageWidthHeightRatio(&$width, &$height, $actualWidth, $actualHeight) + { + $imageRatio = $actualWidth / $actualHeight; + + if (($width === '') && ($height === '')) { // defined size are empty + $width = $actualWidth . 'px'; + $height = $actualHeight . 'px'; + } elseif ($width === '') { // defined width is empty + $heightFloat = (float) $height; + $widthFloat = $heightFloat * $imageRatio; + $matches = array(); + preg_match("/\d([a-z%]+)$/", $height, $matches); + $width = $widthFloat . $matches[1]; + } elseif ($height === '') { // defined height is empty + $widthFloat = (float) $width; + $heightFloat = $widthFloat / $imageRatio; + $matches = array(); + preg_match("/\d([a-z%]+)$/", $width, $matches); + $height = $heightFloat . $matches[1]; + } else { // we have defined size, but we need also check it aspect ratio + $widthMatches = array(); + preg_match("/\d([a-z%]+)$/", $width, $widthMatches); + $heightMatches = array(); + preg_match("/\d([a-z%]+)$/", $height, $heightMatches); + // try to fix only if dimensions are same + if ($widthMatches[1] == $heightMatches[1]) { + $dimention = $widthMatches[1]; + $widthFloat = (float) $width; + $heightFloat = (float) $height; + $definedRatio = $widthFloat / $heightFloat; + + if ($imageRatio > $definedRatio) { // image wider than defined box + $height = ($widthFloat / $imageRatio) . $dimention; + } elseif ($imageRatio < $definedRatio) { // image higher than defined box + $width = ($heightFloat * $imageRatio) . $dimention; + } + } + } + } + + private function prepareImageAttrs($replaceImage, $varInlineArgs) + { + // get image path and size + $width = null; + $height = null; + $ratio = null; + if (is_array($replaceImage) && isset($replaceImage['path'])) { + $imgPath = $replaceImage['path']; + if (isset($replaceImage['width'])) { + $width = $replaceImage['width']; + } + if (isset($replaceImage['height'])) { + $height = $replaceImage['height']; + } + if (isset($replaceImage['ratio'])) { + $ratio = $replaceImage['ratio']; + } + } else { + $imgPath = $replaceImage; + } + + $width = $this->chooseImageDimension($width, isset($varInlineArgs['width']) ? $varInlineArgs['width'] : null, 115); + $height = $this->chooseImageDimension($height, isset($varInlineArgs['height']) ? $varInlineArgs['height'] : null, 70); + + $imageData = @getimagesize($imgPath); + if (!is_array($imageData)) { + throw new Exception(sprintf('Invalid image: %s', $imgPath)); + } + list($actualWidth, $actualHeight, $imageType) = $imageData; + + // fix aspect ratio (by default) + if (is_null($ratio) && isset($varInlineArgs['ratio'])) { + $ratio = $varInlineArgs['ratio']; + } + if (is_null($ratio) || !in_array(strtolower($ratio), array('', '-', 'f', 'false'))) { + $this->fixImageWidthHeightRatio($width, $height, $actualWidth, $actualHeight); + } + + $imageAttrs = array( + 'src' => $imgPath, + 'mime' => image_type_to_mime_type($imageType), + 'width' => $width, + 'height' => $height, + ); + + return $imageAttrs; + } + + private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeType) + { + // define templates + $typeTpl = ''; + $relationTpl = ''; + $newRelationsTpl = '' . "\n" . ''; + $newRelationsTypeTpl = ''; + $extTransform = array( + 'image/jpeg' => 'jpeg', + 'image/png' => 'png', + 'image/bmp' => 'bmp', + 'image/gif' => 'gif', + ); + + // get image embed name + if (isset($this->tempDocumentNewImages[$imgPath])) { + $imgName = $this->tempDocumentNewImages[$imgPath]; + } else { + // transform extension + if (isset($extTransform[$imageMimeType])) { + $imgExt = $extTransform[$imageMimeType]; + } else { + throw new Exception("Unsupported image type $imageMimeType"); + } + + // add image to document + $imgName = 'image_' . $rid . '_' . pathinfo($partFileName, PATHINFO_FILENAME) . '.' . $imgExt; + $this->zipClass->pclzipAddFile($imgPath, 'word/media/' . $imgName); + $this->tempDocumentNewImages[$imgPath] = $imgName; + + // setup type for image + $xmlImageType = str_replace(array('{IMG}', '{EXT}'), array($imgName, $imgExt), $typeTpl); + $this->tempDocumentContentTypes = str_replace('', $xmlImageType, $this->tempDocumentContentTypes) . ''; + } + + $xmlImageRelation = str_replace(array('{RID}', '{IMG}'), array($rid, $imgName), $relationTpl); + + if (!isset($this->tempDocumentRelations[$partFileName])) { + // create new relations file + $this->tempDocumentRelations[$partFileName] = $newRelationsTpl; + // and add it to content types + $xmlRelationsType = str_replace('{RELS}', $this->getRelationsName($partFileName), $newRelationsTypeTpl); + $this->tempDocumentContentTypes = str_replace('', $xmlRelationsType, $this->tempDocumentContentTypes) . ''; + } + + // add image to relations + $this->tempDocumentRelations[$partFileName] = str_replace('', $xmlImageRelation, $this->tempDocumentRelations[$partFileName]) . ''; + } + + /** + * @param mixed $search + * @param mixed $replace Path to image, or array("path" => xx, "width" => yy, "height" => zz) + * @param int $limit + */ + public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT) + { + // prepare $search_replace + if (!is_array($search)) { + $search = array($search); + } + + $replacesList = array(); + if (!is_array($replace) || isset($replace['path'])) { + $replacesList[] = $replace; + } else { + $replacesList = array_values($replace); + } + + $searchReplace = array(); + foreach ($search as $searchIdx => $searchString) { + $searchReplace[$searchString] = isset($replacesList[$searchIdx]) ? $replacesList[$searchIdx] : $replacesList[0]; + } + + // collect document parts + $searchParts = array( + $this->getMainPartName() => &$this->tempDocumentMainPart, + ); + foreach (array_keys($this->tempDocumentHeaders) as $headerIndex) { + $searchParts[$this->getHeaderName($headerIndex)] = &$this->tempDocumentHeaders[$headerIndex]; + } + foreach (array_keys($this->tempDocumentFooters) as $headerIndex) { + $searchParts[$this->getFooterName($headerIndex)] = &$this->tempDocumentFooters[$headerIndex]; + } + + // define templates + // result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425) + $imgTpl = ''; + + foreach ($searchParts as $partFileName => &$partContent) { + $partVariables = $this->getVariablesForPart($partContent); + + foreach ($searchReplace as $searchString => $replaceImage) { + $varsToReplace = array_filter($partVariables, function ($partVar) use ($searchString) { + return ($partVar == $searchString) || preg_match('/^' . preg_quote($searchString) . ':/', $partVar); + }); + + foreach ($varsToReplace as $varNameWithArgs) { + $varInlineArgs = $this->getImageArgs($varNameWithArgs); + $preparedImageAttrs = $this->prepareImageAttrs($replaceImage, $varInlineArgs); + $imgPath = $preparedImageAttrs['src']; + + // get image index + $imgIndex = $this->getNextRelationsIndex($partFileName); + $rid = 'rId' . $imgIndex; + + // replace preparations + $this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']); + $xmlImage = str_replace(array('{RID}', '{WIDTH}', '{HEIGHT}'), array($rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']), $imgTpl); + + // replace variable + $varNameWithArgsFixed = static::ensureMacroCompleted($varNameWithArgs); + $matches = array(); + if (preg_match('/(<[^<]+>)([^<]*)(' . preg_quote($varNameWithArgsFixed) . ')([^>]*)(<[^>]+>)/Uu', $partContent, $matches)) { + $wholeTag = $matches[0]; + array_shift($matches); + list($openTag, $prefix, , $postfix, $closeTag) = $matches; + $replaceXml = $openTag . $prefix . $closeTag . $xmlImage . $openTag . $postfix . $closeTag; + // replace on each iteration, because in one tag we can have 2+ inline variables => before proceed next variable we need to change $partContent + $partContent = $this->setValueForPart($wholeTag, $replaceXml, $partContent, $limit); + } + } + } + } + } + + /** + * Returns count of all variables in template. + * + * @return array + */ + public function getVariableCount() + { + $variables = $this->getVariablesForPart($this->tempDocumentMainPart); + + foreach ($this->tempDocumentHeaders as $headerXML) { + $variables = array_merge( + $variables, + $this->getVariablesForPart($headerXML) + ); + } + + foreach ($this->tempDocumentFooters as $footerXML) { + $variables = array_merge( + $variables, + $this->getVariablesForPart($footerXML) + ); + } + + return array_count_values($variables); + } + + /** + * Returns array of all variables in template. + * + * @return string[] + */ + public function getVariables() + { + return array_keys($this->getVariableCount()); + } + + /** + * Clone a table row in a template document. + * + * @param string $search + * @param int $numberOfClones + * + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + public function cloneRow($search, $numberOfClones) + { + $search = static::ensureMacroCompleted($search); + + $tagPos = strpos($this->tempDocumentMainPart, $search); + if (!$tagPos) { + throw new Exception('Can not clone row, template variable not found or variable contains markup.'); + } + + $rowStart = $this->findRowStart($tagPos); + $rowEnd = $this->findRowEnd($tagPos); + $xmlRow = $this->getSlice($rowStart, $rowEnd); + + // Check if there's a cell spanning multiple rows. + if (preg_match('##', $xmlRow)) { + // $extraRowStart = $rowEnd; + $extraRowEnd = $rowEnd; + while (true) { + $extraRowStart = $this->findRowStart($extraRowEnd + 1); + $extraRowEnd = $this->findRowEnd($extraRowEnd + 1); + + // If extraRowEnd is lower then 7, there was no next row found. + if ($extraRowEnd < 7) { + break; + } + + // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. + $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd); + if (!preg_match('##', $tmpXmlRow) && + !preg_match('##', $tmpXmlRow)) { + break; + } + // This row was a spanned row, update $rowEnd and search for the next row. + $rowEnd = $extraRowEnd; + } + $xmlRow = $this->getSlice($rowStart, $rowEnd); + } + + $result = $this->getSlice(0, $rowStart); + $result .= implode($this->indexClonedVariables($numberOfClones, $xmlRow)); + $result .= $this->getSlice($rowEnd); + + $this->tempDocumentMainPart = $result; + } + + /** + * Clones a table row and populates it's values from a two-dimensional array in a template document. + * + * @param string $search + * @param array $values + */ + public function cloneRowAndSetValues($search, $values) + { + $this->cloneRow($search, count($values)); + + foreach ($values as $rowKey => $rowData) { + $rowNumber = $rowKey + 1; + foreach ($rowData as $macro => $replace) { + $this->setValue($macro . '#' . $rowNumber, $replace); + } + } + } + + /** + * Clone a block. + * + * @param string $blockname + * @param int $clones How many time the block should be cloned + * @param bool $replace + * @param bool $indexVariables If true, any variables inside the block will be indexed (postfixed with #1, #2, ...) + * @param array $variableReplacements Array containing replacements for macros found inside the block to clone + * + * @return string|null + */ + public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null) + { + $xmlBlock = null; + $matches = array(); + preg_match( + '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', + $this->tempDocumentMainPart, + $matches + ); + + if (isset($matches[3])) { + $xmlBlock = $matches[3]; + if ($indexVariables) { + $cloned = $this->indexClonedVariables($clones, $xmlBlock); + } elseif ($variableReplacements !== null && is_array($variableReplacements)) { + $cloned = $this->replaceClonedVariables($variableReplacements, $xmlBlock); + } else { + $cloned = array(); + for ($i = 1; $i <= $clones; $i++) { + $cloned[] = $xmlBlock; + } + } + + if ($replace) { + $this->tempDocumentMainPart = str_replace( + $matches[2] . $matches[3] . $matches[4], + implode('', $cloned), + $this->tempDocumentMainPart + ); + } + } + + return $xmlBlock; + } + + /** + * Replace a block. + * + * @param string $blockname + * @param string $replacement + */ + public function replaceBlock($blockname, $replacement) + { + $matches = array(); + preg_match( + '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', + $this->tempDocumentMainPart, + $matches + ); + + if (isset($matches[3])) { + $this->tempDocumentMainPart = str_replace( + $matches[2] . $matches[3] . $matches[4], + $replacement, + $this->tempDocumentMainPart + ); + } + } + + /** + * Delete a block of text. + * + * @param string $blockname + */ + public function deleteBlock($blockname) + { + $this->replaceBlock($blockname, ''); + } + + /** + * Automatically Recalculate Fields on Open + * + * @param bool $update + */ + public function setUpdateFields($update = true) + { + $string = $update ? 'true' : 'false'; + $matches = array(); + if (preg_match('//', $this->tempDocumentSettingsPart, $matches)) { + $this->tempDocumentSettingsPart = str_replace($matches[0], '', $this->tempDocumentSettingsPart); + } else { + $this->tempDocumentSettingsPart = str_replace('', '', $this->tempDocumentSettingsPart); + } + } + + /** + * Saves the result document. + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * + * @return string + */ + public function save() + { + foreach ($this->tempDocumentHeaders as $index => $xml) { + $this->savePartWithRels($this->getHeaderName($index), $xml); + } + + $this->savePartWithRels($this->getMainPartName(), $this->tempDocumentMainPart); + $this->savePartWithRels($this->getSettingsPartName(), $this->tempDocumentSettingsPart); + + foreach ($this->tempDocumentFooters as $index => $xml) { + $this->savePartWithRels($this->getFooterName($index), $xml); + } + + $this->zipClass->addFromString($this->getDocumentContentTypesName(), $this->tempDocumentContentTypes); + + // Close zip file + if (false === $this->zipClass->close()) { + throw new Exception('Could not close zip file.'); // @codeCoverageIgnore + } + + return $this->tempDocumentFilename; + } + + /** + * @param string $fileName + * @param string $xml + */ + protected function savePartWithRels($fileName, $xml) + { + $this->zipClass->addFromString($fileName, $xml); + if (isset($this->tempDocumentRelations[$fileName])) { + $relsFileName = $this->getRelationsName($fileName); + $this->zipClass->addFromString($relsFileName, $this->tempDocumentRelations[$fileName]); + } + } + + /** + * Saves the result document to the user defined file. + * + * @since 0.8.0 + * + * @param string $fileName + */ + public function saveAs($fileName) + { + $tempFileName = $this->save(); + + if (file_exists($fileName)) { + unlink($fileName); + } + + /* + * Note: we do not use `rename` function here, because it loses file ownership data on Windows platform. + * As a result, user cannot open the file directly getting "Access denied" message. + * + * @see https://github.com/PHPOffice/PHPWord/issues/532 + */ + copy($tempFileName, $fileName); + unlink($tempFileName); + } + + /** + * Finds parts of broken macros and sticks them together. + * Macros, while being edited, could be implicitly broken by some of the word processors. + * + * @param string $documentPart The document part in XML representation + * + * @return string + */ + protected function fixBrokenMacros($documentPart) + { + return preg_replace_callback( + '/\$(?:\{|[^{$]*\>\{)[^}$]*\}/U', + function ($match) { + return strip_tags($match[0]); + }, + $documentPart + ); + } + + /** + * Find and replace macros in the given XML section. + * + * @param mixed $search + * @param mixed $replace + * @param string $documentPartXML + * @param int $limit + * + * @return string + */ + protected function setValueForPart($search, $replace, $documentPartXML, $limit) + { + // Note: we can't use the same function for both cases here, because of performance considerations. + if (self::MAXIMUM_REPLACEMENTS_DEFAULT === $limit) { + return str_replace($search, $replace, $documentPartXML); + } + $regExpEscaper = new RegExp(); + + return preg_replace($regExpEscaper->escape($search), $replace, $documentPartXML, $limit); + } + + /** + * Find all variables in $documentPartXML. + * + * @param string $documentPartXML + * + * @return string[] + */ + protected function getVariablesForPart($documentPartXML) + { + $matches = array(); + preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); + + return $matches[1]; + } + + /** + * Get the name of the header file for $index. + * + * @param int $index + * + * @return string + */ + protected function getHeaderName($index) + { + return sprintf('word/header%d.xml', $index); + } + + /** + * Usually, the name of main part document will be 'document.xml'. However, some .docx files (possibly those from Office 365, experienced also on documents from Word Online created from blank templates) have file 'document22.xml' in their zip archive instead of 'document.xml'. This method searches content types file to correctly determine the file name. + * + * @return string + */ + protected function getMainPartName() + { + $contentTypes = $this->zipClass->getFromName('[Content_Types].xml'); + + $pattern = '~PartName="\/(word\/document.*?\.xml)" ContentType="application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document\.main\+xml"~'; + + $matches = array(); + preg_match($pattern, $contentTypes, $matches); + + return array_key_exists(1, $matches) ? $matches[1] : 'word/document.xml'; + } + + /** + * The name of the file containing the Settings part + * + * @return string + */ + protected function getSettingsPartName() + { + return 'word/settings.xml'; + } + + /** + * Get the name of the footer file for $index. + * + * @param int $index + * + * @return string + */ + protected function getFooterName($index) + { + return sprintf('word/footer%d.xml', $index); + } + + /** + * Get the name of the relations file for document part. + * + * @param string $documentPartName + * + * @return string + */ + protected function getRelationsName($documentPartName) + { + return 'word/_rels/' . pathinfo($documentPartName, PATHINFO_BASENAME) . '.rels'; + } + + protected function getNextRelationsIndex($documentPartName) + { + if (isset($this->tempDocumentRelations[$documentPartName])) { + return substr_count($this->tempDocumentRelations[$documentPartName], 'tempDocumentMainPart, 'tempDocumentMainPart) - $offset) * -1)); + + if (!$rowStart) { + $rowStart = strrpos($this->tempDocumentMainPart, '', ((strlen($this->tempDocumentMainPart) - $offset) * -1)); + } + if (!$rowStart) { + throw new Exception('Can not find the start position of the row to clone.'); + } + + return $rowStart; + } + + /** + * Find the end position of the nearest table row after $offset. + * + * @param int $offset + * + * @return int + */ + protected function findRowEnd($offset) + { + return strpos($this->tempDocumentMainPart, '', $offset) + 7; + } + + /** + * Get a slice of a string. + * + * @param int $startPosition + * @param int $endPosition + * + * @return string + */ + protected function getSlice($startPosition, $endPosition = 0) + { + if (!$endPosition) { + $endPosition = strlen($this->tempDocumentMainPart); + } + + return substr($this->tempDocumentMainPart, $startPosition, ($endPosition - $startPosition)); + } + + /** + * Replaces variable names in cloned + * rows/blocks with indexed names + * + * @param int $count + * @param string $xmlBlock + * + * @return string + */ + protected function indexClonedVariables($count, $xmlBlock) + { + $results = array(); + for ($i = 1; $i <= $count; $i++) { + $results[] = preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlBlock); + } + + return $results; + } + + /** + * Raplaces variables with values from array, array keys are the variable names + * + * @param array $variableReplacements + * @param string $xmlBlock + * + * @return string[] + */ + protected function replaceClonedVariables($variableReplacements, $xmlBlock) + { + $results = array(); + foreach ($variableReplacements as $replacementArray) { + $localXmlBlock = $xmlBlock; + foreach ($replacementArray as $search => $replacement) { + $localXmlBlock = $this->setValueForPart(self::ensureMacroCompleted($search), $replacement, $localXmlBlock, self::MAXIMUM_REPLACEMENTS_DEFAULT); + } + $results[] = $localXmlBlock; + } + + return $results; + } + + /** + * Replace an XML block surrounding a macro with a new block + * + * @param string $macro Name of macro + * @param string $block New block content + * @param string $blockType XML tag type of block + * @return \PhpOffice\PhpWord\TemplateProcessor Fluent interface + */ + protected function replaceXmlBlock($macro, $block, $blockType = 'w:p') + { + $where = $this->findContainingXmlBlockForMacro($macro, $blockType); + if (is_array($where)) { + $this->tempDocumentMainPart = $this->getSlice(0, $where['start']) . $block . $this->getSlice($where['end']); + } + + return $this; + } + + /** + * Find start and end of XML block containing the given macro + * e.g. ...${macro}... + * + * Note that only the first instance of the macro will be found + * + * @param string $macro Name of macro + * @param string $blockType XML tag for block + * @return bool|int[] FALSE if not found, otherwise array with start and end + */ + protected function findContainingXmlBlockForMacro($macro, $blockType = 'w:p') + { + $macroPos = $this->findMacro($macro); + if (0 > $macroPos) { + return false; + } + $start = $this->findXmlBlockStart($macroPos, $blockType); + if (0 > $start) { + return false; + } + $end = $this->findXmlBlockEnd($start, $blockType); + //if not found or if resulting string does not contain the macro we are searching for + if (0 > $end || strstr($this->getSlice($start, $end), $macro) === false) { + return false; + } + + return array('start' => $start, 'end' => $end); + } + + /** + * Find the position of (the start of) a macro + * + * Returns -1 if not found, otherwise position of opening $ + * + * Note that only the first instance of the macro will be found + * + * @param string $search Macro name + * @param int $offset Offset from which to start searching + * @return int -1 if macro not found + */ + protected function findMacro($search, $offset = 0) + { + $search = static::ensureMacroCompleted($search); + $pos = strpos($this->tempDocumentMainPart, $search, $offset); + + return ($pos === false) ? -1 : $pos; + } + + /** + * Find the start position of the nearest XML block start before $offset + * + * @param int $offset Search position + * @param string $blockType XML Block tag + * @return int -1 if block start not found + */ + protected function findXmlBlockStart($offset, $blockType) + { + $reverseOffset = (strlen($this->tempDocumentMainPart) - $offset) * -1; + // first try XML tag with attributes + $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . ' ', $reverseOffset); + // if not found, or if found but contains the XML tag without attribute + if (false === $blockStart || strrpos($this->getSlice($blockStart, $offset), '<' . $blockType . '>')) { + // also try XML tag without attributes + $blockStart = strrpos($this->tempDocumentMainPart, '<' . $blockType . '>', $reverseOffset); + } + + return ($blockStart === false) ? -1 : $blockStart; + } + + /** + * Find the nearest block end position after $offset + * + * @param int $offset Search position + * @param string $blockType XML Block tag + * @return int -1 if block end not found + */ + protected function findXmlBlockEnd($offset, $blockType) + { + $blockEndStart = strpos($this->tempDocumentMainPart, '', $offset); + // return position of end of tag if found, otherwise -1 + + return ($blockEndStart === false) ? -1 : $blockEndStart + 3 + strlen($blockType); + } + + /** + * Splits a w:r/w:t into a list of w:r where each ${macro} is in a separate w:r + * + * @param string $text + * @return string + */ + protected function splitTextIntoTexts($text) + { + if (!$this->textNeedsSplitting($text)) { + return $text; + } + $matches = array(); + if (preg_match('/()/i', $text, $matches)) { + $extractedStyle = $matches[0]; + } else { + $extractedStyle = ''; + } + + $unformattedText = preg_replace('/>\s+<', $text); + $result = str_replace(array('${', '}'), array('' . $extractedStyle . '${', '}' . $extractedStyle . ''), $unformattedText); + + return str_replace(array('' . $extractedStyle . '', '', ''), array('', '', ''), $result); + } + + /** + * Returns true if string contains a macro that is not in it's own w:r + * + * @param string $text + * @return bool + */ + protected function textNeedsSplitting($text) + { + return preg_match('/[^>]\${|}[^<]/i', $text) == 1; + } +} diff --git a/PhpOffice/PhpWord/Writer/AbstractWriter.php b/PhpOffice/PhpWord/Writer/AbstractWriter.php new file mode 100644 index 0000000..2c1ad29 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/AbstractWriter.php @@ -0,0 +1,436 @@ + '', 'object' => ''); + + /** + * Use disk caching + * + * @var bool + */ + private $useDiskCaching = false; + + /** + * Disk caching directory + * + * @var string + */ + private $diskCachingDirectory = './'; + + /** + * Temporary directory + * + * @var string + */ + private $tempDir = ''; + + /** + * Original file name + * + * @var string + */ + private $originalFilename; + + /** + * Temporary file name + * + * @var string + */ + private $tempFilename; + + /** + * Get PhpWord object + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * @return \PhpOffice\PhpWord\PhpWord + */ + public function getPhpWord() + { + if (!is_null($this->phpWord)) { + return $this->phpWord; + } + throw new Exception('No PhpWord assigned.'); + } + + /** + * Set PhpWord object + * + * @param \PhpOffice\PhpWord\PhpWord + * @return self + */ + public function setPhpWord(PhpWord $phpWord = null) + { + $this->phpWord = $phpWord; + + return $this; + } + + /** + * Get writer part + * + * @param string $partName Writer part name + * @return mixed + */ + public function getWriterPart($partName = '') + { + if ($partName != '' && isset($this->writerParts[strtolower($partName)])) { + return $this->writerParts[strtolower($partName)]; + } + + return null; + } + + /** + * Get use disk caching status + * + * @return bool + */ + public function isUseDiskCaching() + { + return $this->useDiskCaching; + } + + /** + * Set use disk caching status + * + * @param bool $value + * @param string $directory + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * @return self + */ + public function setUseDiskCaching($value = false, $directory = null) + { + $this->useDiskCaching = $value; + + if (!is_null($directory)) { + if (is_dir($directory)) { + $this->diskCachingDirectory = $directory; + } else { + throw new Exception("Directory does not exist: $directory"); + } + } + + return $this; + } + + /** + * Get disk caching directory + * + * @return string + */ + public function getDiskCachingDirectory() + { + return $this->diskCachingDirectory; + } + + /** + * Get temporary directory + * + * @return string + */ + public function getTempDir() + { + return $this->tempDir; + } + + /** + * Set temporary directory + * + * @param string $value + * @return self + */ + public function setTempDir($value) + { + if (!is_dir($value)) { + mkdir($value); + } + $this->tempDir = $value; + + return $this; + } + + /** + * Get temporary file name + * + * If $filename is php://output or php://stdout, make it a temporary file + * + * @param string $filename + * @return string + */ + protected function getTempFile($filename) + { + // Temporary directory + $this->setTempDir(Settings::getTempDir() . uniqid('/PHPWordWriter_', true) . '/'); + + // Temporary file + $this->originalFilename = $filename; + if (strpos(strtolower($filename), 'php://') === 0) { + $filename = tempnam(Settings::getTempDir(), 'PhpWord'); + if (false === $filename) { + $filename = $this->originalFilename; // @codeCoverageIgnore + } // @codeCoverageIgnore + } + $this->tempFilename = $filename; + + return $this->tempFilename; + } + + /** + * Cleanup temporary file. + * + * @throws \PhpOffice\PhpWord\Exception\CopyFileException + */ + protected function cleanupTempFile() + { + if ($this->originalFilename != $this->tempFilename) { + // @codeCoverageIgnoreStart + // Can't find any test case. Uncomment when found. + if (false === copy($this->tempFilename, $this->originalFilename)) { + throw new CopyFileException($this->tempFilename, $this->originalFilename); + } + // @codeCoverageIgnoreEnd + @unlink($this->tempFilename); + } + + $this->clearTempDir(); + } + + /** + * Clear temporary directory. + */ + protected function clearTempDir() + { + if (is_dir($this->tempDir)) { + $this->deleteDir($this->tempDir); + } + } + + /** + * Get ZipArchive object + * + * @param string $filename + * + * @throws \Exception + * + * @return \PhpOffice\PhpWord\Shared\ZipArchive + */ + protected function getZipArchive($filename) + { + // Remove any existing file + if (file_exists($filename)) { + unlink($filename); + } + + // Try opening the ZIP file + $zip = new ZipArchive(); + + // @codeCoverageIgnoreStart + // Can't find any test case. Uncomment when found. + if ($zip->open($filename, ZipArchive::OVERWRITE) !== true) { + if ($zip->open($filename, ZipArchive::CREATE) !== true) { + throw new \Exception("Could not open '{$filename}' for writing."); + } + } + // @codeCoverageIgnoreEnd + + return $zip; + } + + /** + * Open file for writing + * + * @since 0.11.0 + * + * @param string $filename + * + * @throws \Exception + * + * @return resource + */ + protected function openFile($filename) + { + $filename = $this->getTempFile($filename); + $fileHandle = fopen($filename, 'w'); + // @codeCoverageIgnoreStart + // Can't find any test case. Uncomment when found. + if ($fileHandle === false) { + throw new \Exception("Could not open '{$filename}' for writing."); + } + // @codeCoverageIgnoreEnd + + return $fileHandle; + } + + /** + * Write content to file. + * + * @since 0.11.0 + * + * @param resource $fileHandle + * @param string $content + */ + protected function writeFile($fileHandle, $content) + { + fwrite($fileHandle, $content); + fclose($fileHandle); + $this->cleanupTempFile(); + } + + /** + * Add files to package. + * + * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip + * @param mixed $elements + */ + protected function addFilesToPackage(ZipArchive $zip, $elements) + { + foreach ($elements as $element) { + $type = $element['type']; // image|object|link + + // Skip nonregistered types and set target + if (!isset($this->mediaPaths[$type])) { + continue; + } + $target = $this->mediaPaths[$type] . $element['target']; + + // Retrive GD image content or get local media + if (isset($element['isMemImage']) && $element['isMemImage']) { + $image = call_user_func($element['createFunction'], $element['source']); + if ($element['imageType'] === 'image/png') { + // PNG images need to preserve alpha channel information + imagesavealpha($image, true); + } + ob_start(); + call_user_func($element['imageFunction'], $image); + $imageContents = ob_get_contents(); + ob_end_clean(); + $zip->addFromString($target, $imageContents); + imagedestroy($image); + } else { + $this->addFileToPackage($zip, $element['source'], $target); + } + } + } + + /** + * Add file to package. + * + * Get the actual source from an archive image. + * + * @param \PhpOffice\PhpWord\Shared\ZipArchive $zipPackage + * @param string $source + * @param string $target + */ + protected function addFileToPackage($zipPackage, $source, $target) + { + $isArchive = strpos($source, 'zip://') !== false; + $actualSource = null; + if ($isArchive) { + $source = substr($source, 6); + list($zipFilename, $imageFilename) = explode('#', $source); + + $zip = new ZipArchive(); + if ($zip->open($zipFilename) !== false) { + if ($zip->locateName($imageFilename)) { + $zip->extractTo($this->getTempDir(), $imageFilename); + $actualSource = $this->getTempDir() . DIRECTORY_SEPARATOR . $imageFilename; + } + } + $zip->close(); + } else { + $actualSource = $source; + } + + if (!is_null($actualSource)) { + $zipPackage->addFile($actualSource, $target); + } + } + + /** + * Delete directory. + * + * @param string $dir + */ + private function deleteDir($dir) + { + foreach (scandir($dir) as $file) { + if ($file === '.' || $file === '..') { + continue; + } elseif (is_file($dir . '/' . $file)) { + unlink($dir . '/' . $file); + } elseif (is_dir($dir . '/' . $file)) { + $this->deleteDir($dir . '/' . $file); + } + } + + rmdir($dir); + } + + /** + * Get use disk caching status + * + * @deprecated 0.10.0 + * + * @codeCoverageIgnore + */ + public function getUseDiskCaching() + { + return $this->isUseDiskCaching(); + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML.php b/PhpOffice/PhpWord/Writer/HTML.php new file mode 100644 index 0000000..7f55b9d --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML.php @@ -0,0 +1,139 @@ +setPhpWord($phpWord); + + $this->parts = array('Head', 'Body'); + foreach ($this->parts as $partName) { + $partClass = 'PhpOffice\\PhpWord\\Writer\\HTML\\Part\\' . $partName; + if (class_exists($partClass)) { + /** @var \PhpOffice\PhpWord\Writer\HTML\Part\AbstractPart $part Type hint */ + $part = new $partClass(); + $part->setParentWriter($this); + $this->writerParts[strtolower($partName)] = $part; + } + } + } + + /** + * Save PhpWord to file. + * + * @param string $filename + * + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + public function save($filename = null) + { + $this->writeFile($this->openFile($filename), $this->getContent()); + } + + /** + * Get content + * + * @return string + * @since 0.11.0 + */ + public function getContent() + { + $content = ''; + + $content .= '' . PHP_EOL; + $content .= '' . PHP_EOL; + $content .= '' . PHP_EOL; + $content .= $this->getWriterPart('Head')->write(); + $content .= $this->getWriterPart('Body')->write(); + $content .= '' . PHP_EOL; + + return $content; + } + + /** + * Get is PDF + * + * @return bool + */ + public function isPdf() + { + return $this->isPdf; + } + + /** + * Get notes + * + * @return array + */ + public function getNotes() + { + return $this->notes; + } + + /** + * Add note. + * + * @param int $noteId + * @param string $noteMark + */ + public function addNote($noteId, $noteMark) + { + $this->notes[$noteId] = $noteMark; + } + + /** + * Write document + * + * @deprecated 0.11.0 + * + * @return string + * + * @codeCoverageIgnore + */ + public function writeDocument() + { + return $this->getContent(); + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php b/PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php new file mode 100644 index 0000000..dc5ccfa --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php @@ -0,0 +1,86 @@ +parentWriter = $parentWriter; + $this->element = $element; + $this->withoutP = $withoutP; + $this->escaper = new Escaper(); + } + + /** + * Set without paragraph. + * + * @param bool $value + */ + public function setWithoutP($value) + { + $this->withoutP = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php b/PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php new file mode 100644 index 0000000..082bd76 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php @@ -0,0 +1,45 @@ +element instanceof \PhpOffice\PhpWord\Element\Bookmark) { + return ''; + } + + $content = ''; + $content .= $this->writeOpening(); + $content .= "element->getName()}\"/>"; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Container.php b/PhpOffice/PhpWord/Writer/HTML/Element/Container.php new file mode 100644 index 0000000..006b588 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Container.php @@ -0,0 +1,64 @@ +element; + if (!$container instanceof ContainerElement) { + return ''; + } + $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); + $withoutP = in_array($containerClass, array('TextRun', 'Footnote', 'Endnote')) ? true : false; + $content = ''; + + $elements = $container->getElements(); + foreach ($elements as $element) { + $elementClass = get_class($element); + $writerClass = str_replace('PhpOffice\\PhpWord\\Element', $this->namespace, $elementClass); + if (class_exists($writerClass)) { + /** @var \PhpOffice\PhpWord\Writer\HTML\Element\AbstractElement $writer Type hint */ + $writer = new $writerClass($this->parentWriter, $element, $withoutP); + $content .= $writer->write(); + } + } + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php b/PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php new file mode 100644 index 0000000..2252dc3 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php @@ -0,0 +1,33 @@ +element instanceof \PhpOffice\PhpWord\Element\Footnote) { + return ''; + } + /** @var \PhpOffice\PhpWord\Writer\HTML $parentWriter Type hint */ + $parentWriter = $this->parentWriter; + + $noteId = count($parentWriter->getNotes()) + 1; + $noteMark = $this->noteType . '-' . $this->element->getRelationId(); + $content = "{$noteId}"; + + $parentWriter->addNote($noteId, $noteMark); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Image.php b/PhpOffice/PhpWord/Writer/HTML/Element/Image.php new file mode 100644 index 0000000..7c22a16 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Image.php @@ -0,0 +1,54 @@ +element instanceof ImageElement) { + return ''; + } + $content = ''; + $imageData = $this->element->getImageStringData(true); + if ($imageData !== null) { + $styleWriter = new ImageStyleWriter($this->element->getStyle()); + $style = $styleWriter->write(); + $imageData = 'data:' . $this->element->getImageType() . ';base64,' . $imageData; + + $content .= $this->writeOpening(); + $content .= ""; + $content .= $this->writeClosing(); + } + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Link.php b/PhpOffice/PhpWord/Writer/HTML/Element/Link.php new file mode 100644 index 0000000..f6dae5c --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Link.php @@ -0,0 +1,51 @@ +element instanceof \PhpOffice\PhpWord\Element\Link) { + return ''; + } + + $prefix = $this->element->isInternal() ? '#' : ''; + $content = $this->writeOpening(); + if (Settings::isOutputEscapingEnabled()) { + $content .= "escaper->escapeHtmlAttr($this->element->getSource())}\">{$this->escaper->escapeHtml($this->element->getText())}"; + } else { + $content .= "element->getSource()}\">{$this->element->getText()}"; + } + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php b/PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php new file mode 100644 index 0000000..384b3ef --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php @@ -0,0 +1,48 @@ +element instanceof \PhpOffice\PhpWord\Element\ListItem) { + return ''; + } + + if (Settings::isOutputEscapingEnabled()) { + $content = '

' . $this->escaper->escapeHtml($this->element->getTextObject()->getText()) . '

' . PHP_EOL; + } else { + $content = '

' . $this->element->getTextObject()->getText() . '

' . PHP_EOL; + } + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php b/PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php new file mode 100644 index 0000000..a4d7e46 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php @@ -0,0 +1,43 @@ +element instanceof \PhpOffice\PhpWord\Element\ListItemRun) { + return ''; + } + + $writer = new Container($this->parentWriter, $this->element); + $content = $writer->write() . PHP_EOL; + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php b/PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php new file mode 100644 index 0000000..f9998e3 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php @@ -0,0 +1,44 @@ +parentWriter; + if ($parentWriter->isPdf()) { + return ''; + } + + return ''; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Table.php b/PhpOffice/PhpWord/Writer/HTML/Element/Table.php new file mode 100644 index 0000000..a6f1479 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Table.php @@ -0,0 +1,141 @@ +element instanceof \PhpOffice\PhpWord\Element\Table) { + return ''; + } + + $content = ''; + $rows = $this->element->getRows(); + $rowCount = count($rows); + if ($rowCount > 0) { + $content .= 'element->getStyle()) . '>' . PHP_EOL; + + for ($i = 0; $i < $rowCount; $i++) { + /** @var $row \PhpOffice\PhpWord\Element\Row Type hint */ + $rowStyle = $rows[$i]->getStyle(); + // $height = $row->getHeight(); + $tblHeader = $rowStyle->isTblHeader(); + $content .= '' . PHP_EOL; + $rowCells = $rows[$i]->getCells(); + $rowCellCount = count($rowCells); + for ($j = 0; $j < $rowCellCount; $j++) { + $cellStyle = $rowCells[$j]->getStyle(); + $cellBgColor = $cellStyle->getBgColor(); + $cellFgColor = null; + if ($cellBgColor) { + $red = hexdec(substr($cellBgColor, 0, 2)); + $green = hexdec(substr($cellBgColor, 2, 2)); + $blue = hexdec(substr($cellBgColor, 4, 2)); + $cellFgColor = (($red * 0.299 + $green * 0.587 + $blue * 0.114) > 186) ? null : 'ffffff'; + } + $cellColSpan = $cellStyle->getGridSpan(); + $cellRowSpan = 1; + $cellVMerge = $cellStyle->getVMerge(); + // If this is the first cell of the vertical merge, find out how man rows it spans + if ($cellVMerge === 'restart') { + for ($k = $i + 1; $k < $rowCount; $k++) { + $kRowCells = $rows[$k]->getCells(); + if (isset($kRowCells[$j])) { + if ($kRowCells[$j]->getStyle()->getVMerge() === 'continue') { + $cellRowSpan++; + } else { + break; + } + } else { + break; + } + } + } + // Ignore cells that are merged vertically with previous rows + if ($cellVMerge !== 'continue') { + $cellTag = $tblHeader ? 'th' : 'td'; + $cellColSpanAttr = (is_numeric($cellColSpan) && ($cellColSpan > 1) ? " colspan=\"{$cellColSpan}\"" : ''); + $cellRowSpanAttr = ($cellRowSpan > 1 ? " rowspan=\"{$cellRowSpan}\"" : ''); + $cellBgColorAttr = (is_null($cellBgColor) ? '' : " bgcolor=\"#{$cellBgColor}\""); + $cellFgColorAttr = (is_null($cellFgColor) ? '' : " color=\"#{$cellFgColor}\""); + $content .= "<{$cellTag}{$cellColSpanAttr}{$cellRowSpanAttr}{$cellBgColorAttr}{$cellFgColorAttr}>" . PHP_EOL; + $writer = new Container($this->parentWriter, $rowCells[$j]); + $content .= $writer->write(); + if ($cellRowSpan > 1) { + // There shouldn't be any content in the subsequent merged cells, but lets check anyway + for ($k = $i + 1; $k < $rowCount; $k++) { + $kRowCells = $rows[$k]->getCells(); + if (isset($kRowCells[$j])) { + if ($kRowCells[$j]->getStyle()->getVMerge() === 'continue') { + $writer = new Container($this->parentWriter, $kRowCells[$j]); + $content .= $writer->write(); + } else { + break; + } + } else { + break; + } + } + } + $content .= "" . PHP_EOL; + } + } + $content .= '' . PHP_EOL; + } + $content .= '' . PHP_EOL; + } + + return $content; + } + + /** + * Translates Table style in CSS equivalent + * + * @param string|\PhpOffice\PhpWord\Style\Table|null $tableStyle + * @return string + */ + private function getTableStyle($tableStyle = null) + { + if ($tableStyle == null) { + return ''; + } + if (is_string($tableStyle)) { + $style = ' class="' . $tableStyle; + } else { + $style = ' style="'; + if ($tableStyle->getLayout() == \PhpOffice\PhpWord\Style\Table::LAYOUT_FIXED) { + $style .= 'table-layout: fixed;'; + } elseif ($tableStyle->getLayout() == \PhpOffice\PhpWord\Style\Table::LAYOUT_AUTO) { + $style .= 'table-layout: auto;'; + } + } + + return $style . '"'; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Text.php b/PhpOffice/PhpWord/Writer/HTML/Element/Text.php new file mode 100644 index 0000000..04d76a8 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Text.php @@ -0,0 +1,265 @@ +element; + $this->getFontStyle(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= $this->openingText; + $content .= $this->openingTags; + if (Settings::isOutputEscapingEnabled()) { + $content .= $this->escaper->escapeHtml($element->getText()); + } else { + $content .= $element->getText(); + } + $content .= $this->closingTags; + $content .= $this->closingText; + $content .= $this->writeClosing(); + + return $content; + } + + /** + * Set opening text. + * + * @param string $value + */ + public function setOpeningText($value) + { + $this->openingText = $value; + } + + /** + * Set closing text. + * + * @param string $value + */ + public function setClosingText($value) + { + $this->closingText = $value; + } + + /** + * Write opening + * + * @return string + */ + protected function writeOpening() + { + $content = ''; + if (!$this->withoutP) { + $style = ''; + if (method_exists($this->element, 'getParagraphStyle')) { + $style = $this->getParagraphStyle(); + } + $content .= ""; + } + + //open track change tag + $content .= $this->writeTrackChangeOpening(); + + return $content; + } + + /** + * Write ending + * + * @return string + */ + protected function writeClosing() + { + $content = ''; + + //close track change tag + $content .= $this->writeTrackChangeClosing(); + + if (!$this->withoutP) { + if (Settings::isOutputEscapingEnabled()) { + $content .= $this->escaper->escapeHtml($this->closingText); + } else { + $content .= $this->closingText; + } + + $content .= '

' . PHP_EOL; + } + + return $content; + } + + /** + * writes the track change opening tag + * + * @return string the HTML, an empty string if no track change information + */ + private function writeTrackChangeOpening() + { + $changed = $this->element->getTrackChange(); + if ($changed == null) { + return ''; + } + + $content = ''; + if (($changed->getChangeType() == TrackChange::INSERTED)) { + $content .= 'getChangeType() == TrackChange::DELETED) { + $content .= ' array('author'=> $changed->getAuthor(), 'id' => $this->element->getElementId())); + if ($changed->getDate() != null) { + $changedProp['changed']['date'] = $changed->getDate()->format('Y-m-d\TH:i:s\Z'); + } + $content .= json_encode($changedProp); + $content .= '\' '; + $content .= 'title="' . $changed->getAuthor(); + if ($changed->getDate() != null) { + $dateUser = $changed->getDate()->format('Y-m-d H:i:s'); + $content .= ' - ' . $dateUser; + } + $content .= '">'; + + return $content; + } + + /** + * writes the track change closing tag + * + * @return string the HTML, an empty string if no track change information + */ + private function writeTrackChangeClosing() + { + $changed = $this->element->getTrackChange(); + if ($changed == null) { + return ''; + } + + $content = ''; + if (($changed->getChangeType() == TrackChange::INSERTED)) { + $content .= ''; + } elseif ($changed->getChangeType() == TrackChange::DELETED) { + $content .= ''; + } + + return $content; + } + + /** + * Write paragraph style + * + * @return string + */ + private function getParagraphStyle() + { + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + $style = ''; + if (!method_exists($element, 'getParagraphStyle')) { + return $style; + } + + $paragraphStyle = $element->getParagraphStyle(); + $pStyleIsObject = ($paragraphStyle instanceof Paragraph); + if ($pStyleIsObject) { + $styleWriter = new ParagraphStyleWriter($paragraphStyle); + $style = $styleWriter->write(); + } elseif (is_string($paragraphStyle)) { + $style = $paragraphStyle; + } + if ($style) { + $attribute = $pStyleIsObject ? 'style' : 'class'; + $style = " {$attribute}=\"{$style}\""; + } + + return $style; + } + + /** + * Get font style. + */ + private function getFontStyle() + { + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + $style = ''; + $fontStyle = $element->getFontStyle(); + $fStyleIsObject = ($fontStyle instanceof Font); + if ($fStyleIsObject) { + $styleWriter = new FontStyleWriter($fontStyle); + $style = $styleWriter->write(); + } elseif (is_string($fontStyle)) { + $style = $fontStyle; + } + if ($style) { + $attribute = $fStyleIsObject ? 'style' : 'class'; + $this->openingTags = ""; + $this->closingTags = ''; + } + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php b/PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php new file mode 100644 index 0000000..6ff092d --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php @@ -0,0 +1,42 @@ +withoutP) { + $content = '
' . PHP_EOL; + } else { + $content = '

 

' . PHP_EOL; + } + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php b/PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php new file mode 100644 index 0000000..b2deaf2 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php @@ -0,0 +1,43 @@ +writeOpening(); + $writer = new Container($this->parentWriter, $this->element); + $content .= $writer->write(); + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Title.php b/PhpOffice/PhpWord/Writer/HTML/Element/Title.php new file mode 100644 index 0000000..04ed61f --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Title.php @@ -0,0 +1,56 @@ +element instanceof \PhpOffice\PhpWord\Element\Title) { + return ''; + } + + $tag = 'h' . $this->element->getDepth(); + + $text = $this->element->getText(); + if (is_string($text)) { + if (Settings::isOutputEscapingEnabled()) { + $text = $this->escaper->escapeHtml($text); + } + } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + $writer = new Container($this->parentWriter, $text); + $text = $writer->write(); + } + + $content = "<{$tag}>{$text}" . PHP_EOL; + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php b/PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php new file mode 100644 index 0000000..2d86f39 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php @@ -0,0 +1,69 @@ +escaper = new Escaper(); + } + + /** + * @return string + */ + abstract public function write(); + + /** + * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer + */ + public function setParentWriter(AbstractWriter $writer = null) + { + $this->parentWriter = $writer; + } + + /** + * @throws \PhpOffice\PhpWord\Exception\Exception + * + * @return \PhpOffice\PhpWord\Writer\AbstractWriter + */ + public function getParentWriter() + { + if ($this->parentWriter !== null) { + return $this->parentWriter; + } + throw new Exception('No parent WriterInterface assigned.'); + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Part/Body.php b/PhpOffice/PhpWord/Writer/HTML/Part/Body.php new file mode 100644 index 0000000..a029f96 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Part/Body.php @@ -0,0 +1,89 @@ +getParentWriter()->getPhpWord(); + + $content = ''; + + $content .= '' . PHP_EOL; + $sections = $phpWord->getSections(); + foreach ($sections as $section) { + $writer = new Container($this->getParentWriter(), $section); + $content .= $writer->write(); + } + + $content .= $this->writeNotes(); + $content .= '' . PHP_EOL; + + return $content; + } + + /** + * Write footnote/endnote contents as textruns + * + * @return string + */ + private function writeNotes() + { + /** @var \PhpOffice\PhpWord\Writer\HTML $parentWriter Type hint */ + $parentWriter = $this->getParentWriter(); + $phpWord = $parentWriter->getPhpWord(); + $notes = $parentWriter->getNotes(); + + $content = ''; + + if (!empty($notes)) { + $content .= '
' . PHP_EOL; + foreach ($notes as $noteId => $noteMark) { + list($noteType, $noteTypeId) = explode('-', $noteMark); + $method = 'get' . ($noteType == 'endnote' ? 'Endnotes' : 'Footnotes'); + $collection = $phpWord->$method()->getItems(); + + if (isset($collection[$noteTypeId])) { + $element = $collection[$noteTypeId]; + $noteAnchor = ""; + $noteAnchor .= "{$noteId}"; + + $writer = new TextRunWriter($this->getParentWriter(), $element); + $writer->setOpeningText($noteAnchor); + $content .= $writer->write(); + } + } + } + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Part/Head.php b/PhpOffice/PhpWord/Writer/HTML/Part/Head.php new file mode 100644 index 0000000..1107bec --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Part/Head.php @@ -0,0 +1,138 @@ +getParentWriter()->getPhpWord()->getDocInfo(); + $propertiesMapping = array( + 'creator' => 'author', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'company' => '', + 'manager' => '', + ); + $title = $docProps->getTitle(); + $title = ($title != '') ? $title : 'PHPWord'; + + $content = ''; + + $content .= '' . PHP_EOL; + $content .= '' . PHP_EOL; + $content .= '' . $title . '' . PHP_EOL; + foreach ($propertiesMapping as $key => $value) { + $value = ($value == '') ? $key : $value; + $method = 'get' . $key; + if ($docProps->$method() != '') { + $content .= '' . PHP_EOL; + } + } + $content .= $this->writeStyles(); + $content .= '' . PHP_EOL; + + return $content; + } + + /** + * Get styles + * + * @return string + */ + private function writeStyles() + { + $css = '' . PHP_EOL; + + return $css; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Style/AbstractStyle.php b/PhpOffice/PhpWord/Writer/HTML/Style/AbstractStyle.php new file mode 100644 index 0000000..cfb54cb --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Style/AbstractStyle.php @@ -0,0 +1,125 @@ +style = $style; + } + + /** + * Set parent writer. + * + * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer + */ + public function setParentWriter($writer) + { + $this->parentWriter = $writer; + } + + /** + * Get parent writer + * + * @return \PhpOffice\PhpWord\Writer\AbstractWriter + */ + public function getParentWriter() + { + return $this->parentWriter; + } + + /** + * Get style + * + * @return array|\PhpOffice\PhpWord\Style\AbstractStyle $style + */ + public function getStyle() + { + if (!$this->style instanceof Style && !is_array($this->style)) { + return ''; + } + + return $this->style; + } + + /** + * Takes array where of CSS properties / values and converts to CSS string + * + * @param array $css + * @return string + */ + protected function assembleCss($css) + { + $pairs = array(); + $string = ''; + foreach ($css as $key => $value) { + if ($value != '') { + $pairs[] = $key . ': ' . $value; + } + } + if (!empty($pairs)) { + $string = implode('; ', $pairs) . ';'; + } + + return $string; + } + + /** + * Get value if ... + * + * @param bool|null $condition + * @param string $value + * @return string + */ + protected function getValueIf($condition, $value) + { + return $condition == true ? $value : ''; + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Style/Font.php b/PhpOffice/PhpWord/Writer/HTML/Style/Font.php new file mode 100644 index 0000000..75c98b9 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Style/Font.php @@ -0,0 +1,70 @@ +getStyle(); + if (!$style instanceof FontStyle) { + return ''; + } + $css = array(); + + $font = $style->getName(); + $size = $style->getSize(); + $color = $style->getColor(); + $fgColor = $style->getFgColor(); + $underline = $style->getUnderline() != FontStyle::UNDERLINE_NONE; + $lineThrough = $style->isStrikethrough() || $style->isDoubleStrikethrough(); + + $css['font-family'] = $this->getValueIf($font !== null, "'{$font}'"); + $css['font-size'] = $this->getValueIf($size !== null, "{$size}pt"); + $css['color'] = $this->getValueIf($color !== null, "#{$color}"); + $css['background'] = $this->getValueIf($fgColor != '', $fgColor); + $css['font-weight'] = $this->getValueIf($style->isBold(), 'bold'); + $css['font-style'] = $this->getValueIf($style->isItalic(), 'italic'); + $css['vertical-align'] = ''; + $css['vertical-align'] .= $this->getValueIf($style->isSuperScript(), 'super'); + $css['vertical-align'] .= $this->getValueIf($style->isSubScript(), 'sub'); + $css['text-decoration'] = ''; + $css['text-decoration'] .= $this->getValueIf($underline, 'underline '); + $css['text-decoration'] .= $this->getValueIf($lineThrough, 'line-through '); + $css['text-transform'] = $this->getValueIf($style->isAllCaps(), 'uppercase'); + $css['font-variant'] = $this->getValueIf($style->isSmallCaps(), 'small-caps'); + $css['display'] = $this->getValueIf($style->isHidden(), 'none'); + + $spacing = $style->getSpacing(); + $css['letter-spacing'] = $this->getValueIf(!is_null($spacing), ($spacing / 20) . 'pt'); + + return $this->assembleCss($css); + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Style/Generic.php b/PhpOffice/PhpWord/Writer/HTML/Style/Generic.php new file mode 100644 index 0000000..ee5d089 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Style/Generic.php @@ -0,0 +1,43 @@ +getStyle(); + $css = array(); + + if (is_array($style) && !empty($style)) { + $css = $style; + } + + return $this->assembleCss($css); + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Style/Image.php b/PhpOffice/PhpWord/Writer/HTML/Style/Image.php new file mode 100644 index 0000000..93747b4 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Style/Image.php @@ -0,0 +1,47 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Image) { + return ''; + } + $css = array(); + + $width = $style->getWidth(); + $height = $style->getHeight(); + $css['width'] = $this->getValueIf(is_numeric($width), $width . 'px'); + $css['height'] = $this->getValueIf(is_numeric($height), $height . 'px'); + + return $this->assembleCss($css); + } +} diff --git a/PhpOffice/PhpWord/Writer/HTML/Style/Paragraph.php b/PhpOffice/PhpWord/Writer/HTML/Style/Paragraph.php new file mode 100644 index 0000000..863ef93 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/HTML/Style/Paragraph.php @@ -0,0 +1,85 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Paragraph) { + return ''; + } + $css = array(); + + // Alignment + if ('' !== $style->getAlignment()) { + $textAlign = ''; + + switch ($style->getAlignment()) { + case Jc::CENTER: + $textAlign = 'center'; + break; + case Jc::END: + case Jc::MEDIUM_KASHIDA: + case Jc::HIGH_KASHIDA: + case Jc::LOW_KASHIDA: + case Jc::RIGHT: + $textAlign = 'right'; + break; + case Jc::BOTH: + case Jc::DISTRIBUTE: + case Jc::THAI_DISTRIBUTE: + case Jc::JUSTIFY: + $textAlign = 'justify'; + break; + default: //all others, align left + $textAlign = 'left'; + break; + } + + $css['text-align'] = $textAlign; + } + + // Spacing + $spacing = $style->getSpace(); + if (!is_null($spacing)) { + $before = $spacing->getBefore(); + $after = $spacing->getAfter(); + $css['margin-top'] = $this->getValueIf(!is_null($before), ($before / 20) . 'pt'); + $css['margin-bottom'] = $this->getValueIf(!is_null($after), ($after / 20) . 'pt'); + } else { + $css['margin-top'] = '0'; + $css['margin-bottom'] = '0'; + } + + return $this->assembleCss($css); + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText.php b/PhpOffice/PhpWord/Writer/ODText.php new file mode 100644 index 0000000..efd0d6a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText.php @@ -0,0 +1,89 @@ +setPhpWord($phpWord); + + // Create parts + $this->parts = array( + 'Mimetype' => 'mimetype', + 'Content' => 'content.xml', + 'Meta' => 'meta.xml', + 'Styles' => 'styles.xml', + 'Manifest' => 'META-INF/manifest.xml', + ); + foreach (array_keys($this->parts) as $partName) { + $partClass = get_class($this) . '\\Part\\' . $partName; + if (class_exists($partClass)) { + /** @var $partObject \PhpOffice\PhpWord\Writer\ODText\Part\AbstractPart Type hint */ + $partObject = new $partClass(); + $partObject->setParentWriter($this); + $this->writerParts[strtolower($partName)] = $partObject; + } + } + + // Set package paths + $this->mediaPaths = array('image' => 'Pictures/'); + } + + /** + * Save PhpWord to file. + * + * @param string $filename + */ + public function save($filename = null) + { + $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 != '') { + $zip->addFromString($fileName, $this->getWriterPart($partName)->write()); + } + } + + // Close zip archive and cleanup temp file + $zip->close(); + $this->cleanupTempFile(); + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Element/AbstractElement.php b/PhpOffice/PhpWord/Writer/ODText/Element/AbstractElement.php new file mode 100644 index 0000000..9c9fc1c --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Element/AbstractElement.php @@ -0,0 +1,29 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Image) { + return; + } + + $mediaIndex = $element->getMediaIndex(); + $target = 'Pictures/' . $element->getTarget(); + $style = $element->getStyle(); + $width = Converter::pixelToCm($style->getWidth()); + $height = Converter::pixelToCm($style->getHeight()); + + $xmlWriter->startElement('text:p'); + $xmlWriter->writeAttribute('text:style-name', 'Standard'); + + $xmlWriter->startElement('draw:frame'); + $xmlWriter->writeAttribute('draw:style-name', 'fr' . $mediaIndex); + $xmlWriter->writeAttribute('draw:name', $element->getElementId()); + $xmlWriter->writeAttribute('text:anchor-type', 'as-char'); + $xmlWriter->writeAttribute('svg:width', $width . 'cm'); + $xmlWriter->writeAttribute('svg:height', $height . 'cm'); + $xmlWriter->writeAttribute('draw:z-index', $mediaIndex); + + $xmlWriter->startElement('draw:image'); + $xmlWriter->writeAttribute('xlink:href', $target); + $xmlWriter->writeAttribute('xlink:type', 'simple'); + $xmlWriter->writeAttribute('xlink:show', 'embed'); + $xmlWriter->writeAttribute('xlink:actuate', 'onLoad'); + $xmlWriter->endElement(); // draw:image + + $xmlWriter->endElement(); // draw:frame + + $xmlWriter->endElement(); // text:p + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Element/Link.php b/PhpOffice/PhpWord/Writer/ODText/Element/Link.php new file mode 100644 index 0000000..d6fec50 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Element/Link.php @@ -0,0 +1,52 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Link) { + return; + } + + if (!$this->withoutP) { + $xmlWriter->startElement('text:p'); // text:p + } + + $xmlWriter->startElement('text:a'); + $xmlWriter->writeAttribute('xlink:type', 'simple'); + $xmlWriter->writeAttribute('xlink:href', $element->getSource()); + $this->writeText($element->getText()); + $xmlWriter->endElement(); // text:a + + if (!$this->withoutP) { + $xmlWriter->endElement(); // text:p + } + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Element/PageBreak.php b/PhpOffice/PhpWord/Writer/ODText/Element/PageBreak.php new file mode 100644 index 0000000..ecf4760 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Element/PageBreak.php @@ -0,0 +1,36 @@ +getXmlWriter(); + + $xmlWriter->startElement('text:p'); + $xmlWriter->writeAttribute('text:style-name', 'P1'); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Element/Table.php b/PhpOffice/PhpWord/Writer/ODText/Element/Table.php new file mode 100644 index 0000000..088330a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Element/Table.php @@ -0,0 +1,98 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Table) { + return; + } + $rows = $element->getRows(); + $rowCount = count($rows); + + if ($rowCount > 0) { + $xmlWriter->startElement('table:table'); + $xmlWriter->writeAttribute('table:name', $element->getElementId()); + $xmlWriter->writeAttribute('table:style', $element->getElementId()); + + // Write columns + $this->writeColumns($xmlWriter, $element); + + // Write rows + foreach ($rows as $row) { + $this->writeRow($xmlWriter, $row); + } + $xmlWriter->endElement(); // table:table + } + } + + /** + * Write column. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Table $element + */ + private function writeColumns(XMLWriter $xmlWriter, TableElement $element) + { + $colCount = $element->countColumns(); + + for ($i = 0; $i < $colCount; $i++) { + $xmlWriter->startElement('table:table-column'); + $xmlWriter->writeAttribute('table:style-name', $element->getElementId() . '.' . $i); + $xmlWriter->endElement(); + } + } + + /** + * Write row. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Row $row + */ + private function writeRow(XMLWriter $xmlWriter, RowElement $row) + { + $xmlWriter->startElement('table:table-row'); + /** @var $row \PhpOffice\PhpWord\Element\Row Type hint */ + foreach ($row->getCells() as $cell) { + $xmlWriter->startElement('table:table-cell'); + $xmlWriter->writeAttribute('office:value-type', 'string'); + + $containerWriter = new Container($xmlWriter, $cell); + $containerWriter->write(); + + $xmlWriter->endElement(); // table:table-cell + } + $xmlWriter->endElement(); // table:table-row + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Element/Text.php b/PhpOffice/PhpWord/Writer/ODText/Element/Text.php new file mode 100644 index 0000000..7dcd28a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Element/Text.php @@ -0,0 +1,101 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Text) { + return; + } + $fontStyle = $element->getFontStyle(); + $paragraphStyle = $element->getParagraphStyle(); + + // @todo Commented for TextRun. Should really checkout this value + // $fStyleIsObject = ($fontStyle instanceof Font) ? true : false; + $fStyleIsObject = false; + + if ($fStyleIsObject) { + // Don't never be the case, because I browse all sections for cleaning all styles not declared + throw new Exception('PhpWord : $fStyleIsObject wouldn\'t be an object'); + } + + if (!$this->withoutP) { + $xmlWriter->startElement('text:p'); // text:p + } + if ($element->getTrackChange() != null && $element->getTrackChange()->getChangeType() == TrackChange::DELETED) { + $xmlWriter->startElement('text:change'); + $xmlWriter->writeAttribute('text:change-id', $element->getTrackChange()->getElementId()); + $xmlWriter->endElement(); + } else { + if (empty($fontStyle)) { + if (empty($paragraphStyle)) { + $xmlWriter->writeAttribute('text:style-name', 'P1'); + } elseif (is_string($paragraphStyle)) { + $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); + } + $this->writeChangeInsertion(true, $element->getTrackChange()); + $this->writeText($element->getText()); + $this->writeChangeInsertion(false, $element->getTrackChange()); + } else { + if (empty($paragraphStyle)) { + $xmlWriter->writeAttribute('text:style-name', 'Standard'); + } elseif (is_string($paragraphStyle)) { + $xmlWriter->writeAttribute('text:style-name', $paragraphStyle); + } + // text:span + $xmlWriter->startElement('text:span'); + if (is_string($fontStyle)) { + $xmlWriter->writeAttribute('text:style-name', $fontStyle); + } + $this->writeChangeInsertion(true, $element->getTrackChange()); + $this->writeText($element->getText()); + $this->writeChangeInsertion(false, $element->getTrackChange()); + $xmlWriter->endElement(); + } + } + if (!$this->withoutP) { + $xmlWriter->endElement(); // text:p + } + } + + private function writeChangeInsertion($start = true, TrackChange $trackChange = null) + { + if ($trackChange == null || $trackChange->getChangeType() != TrackChange::INSERTED) { + return; + } + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('text:change-' . ($start ? 'start' : 'end')); + $xmlWriter->writeAttribute('text:change-id', $trackChange->getElementId()); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Element/TextBreak.php b/PhpOffice/PhpWord/Writer/ODText/Element/TextBreak.php new file mode 100644 index 0000000..80cd138 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Element/TextBreak.php @@ -0,0 +1,38 @@ +getXmlWriter(); + + $xmlWriter->startElement('text:p'); + $xmlWriter->writeAttribute('text:style-name', 'Standard'); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Element/TextRun.php b/PhpOffice/PhpWord/Writer/ODText/Element/TextRun.php new file mode 100644 index 0000000..78e5a8a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Element/TextRun.php @@ -0,0 +1,42 @@ +getXmlWriter(); + $element = $this->getElement(); + + $xmlWriter->startElement('text:p'); + + $containerWriter = new Container($xmlWriter, $element); + $containerWriter->write(); + + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Element/Title.php b/PhpOffice/PhpWord/Writer/ODText/Element/Title.php new file mode 100644 index 0000000..8b9440a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Element/Title.php @@ -0,0 +1,49 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Title) { + return; + } + + $xmlWriter->startElement('text:h'); + $xmlWriter->writeAttribute('text:outline-level', $element->getDepth()); + $text = $element->getText(); + if (is_string($text)) { + $this->writeText($text); + } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + $containerWriter = new Container($xmlWriter, $text); + $containerWriter->write(); + } + $xmlWriter->endElement(); // text:h + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Part/AbstractPart.php b/PhpOffice/PhpWord/Writer/ODText/Part/AbstractPart.php new file mode 100644 index 0000000..f2844de --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Part/AbstractPart.php @@ -0,0 +1,109 @@ +writeAttribute('office:version', '1.2'); + $xmlWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $xmlWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0'); + $xmlWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0'); + $xmlWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'); + $xmlWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'); + $xmlWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0'); + $xmlWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $xmlWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $xmlWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $xmlWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0'); + $xmlWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0'); + $xmlWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0'); + $xmlWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0'); + $xmlWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML'); + $xmlWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0'); + $xmlWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0'); + $xmlWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $xmlWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer'); + $xmlWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc'); + $xmlWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events'); + $xmlWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report'); + $xmlWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2'); + $xmlWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml'); + $xmlWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#'); + $xmlWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table'); + $xmlWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/'); + } + + /** + * Write font faces declaration. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + protected function writeFontFaces(XMLWriter $xmlWriter) + { + $xmlWriter->startElement('office:font-face-decls'); + $fontTable = array(); + $styles = Style::getStyles(); + $numFonts = 0; + if (count($styles) > 0) { + foreach ($styles as $style) { + // Font + if ($style instanceof Font) { + $numFonts++; + $name = $style->getName(); + if (!in_array($name, $fontTable)) { + $fontTable[] = $name; + + // style:font-face + $xmlWriter->startElement('style:font-face'); + $xmlWriter->writeAttribute('style:name', $name); + $xmlWriter->writeAttribute('svg:font-family', $name); + $xmlWriter->endElement(); + } + } + } + } + if (!in_array(Settings::getDefaultFontName(), $fontTable)) { + $xmlWriter->startElement('style:font-face'); + $xmlWriter->writeAttribute('style:name', Settings::getDefaultFontName()); + $xmlWriter->writeAttribute('svg:font-family', Settings::getDefaultFontName()); + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Part/Content.php b/PhpOffice/PhpWord/Writer/ODText/Part/Content.php new file mode 100644 index 0000000..99ee935 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Part/Content.php @@ -0,0 +1,302 @@ + array(), 'Image' => array(), 'Table' => array()); + + /** + * Write part + * + * @return string + */ + public function write() + { + $xmlWriter = $this->getXmlWriter(); + $phpWord = $this->getParentWriter()->getPhpWord(); + + $this->getAutoStyles($phpWord); + + $xmlWriter->startDocument('1.0', 'UTF-8'); + $xmlWriter->startElement('office:document-content'); + $this->writeCommonRootAttributes($xmlWriter); + $xmlWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms'); + $xmlWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema'); + $xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $xmlWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0'); + $xmlWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0'); + + // Font declarations and automatic styles + $this->writeFontFaces($xmlWriter); // office:font-face-decls + $this->writeAutoStyles($xmlWriter); // office:automatic-styles + + // Body + $xmlWriter->startElement('office:body'); + $xmlWriter->startElement('office:text'); + + // Tracked changes declarations + $trackedChanges = array(); + $sections = $phpWord->getSections(); + foreach ($sections as $section) { + $this->collectTrackedChanges($section, $trackedChanges); + } + $xmlWriter->startElement('text:tracked-changes'); + foreach ($trackedChanges as $trackedElement) { + $trackedChange = $trackedElement->getTrackChange(); + $xmlWriter->startElement('text:changed-region'); + $trackedChange->setElementId(); + $xmlWriter->writeAttribute('text:id', $trackedChange->getElementId()); + + if (($trackedChange->getChangeType() == TrackChange::INSERTED)) { + $xmlWriter->startElement('text:insertion'); + } elseif ($trackedChange->getChangeType() == TrackChange::DELETED) { + $xmlWriter->startElement('text:deletion'); + } + + $xmlWriter->startElement('office:change-info'); + $xmlWriter->writeElement('dc:creator', $trackedChange->getAuthor()); + if ($trackedChange->getDate() != null) { + $xmlWriter->writeElement('dc:date', $trackedChange->getDate()->format('Y-m-d\TH:i:s\Z')); + } + $xmlWriter->endElement(); // office:change-info + if ($trackedChange->getChangeType() == TrackChange::DELETED) { + $xmlWriter->writeElement('text:p', $trackedElement->getText()); + } + + $xmlWriter->endElement(); // text:insertion|text:deletion + $xmlWriter->endElement(); // text:changed-region + } + $xmlWriter->endElement(); // text:tracked-changes + + // Sequence declarations + $sequences = array('Illustration', 'Table', 'Text', 'Drawing'); + $xmlWriter->startElement('text:sequence-decls'); + foreach ($sequences as $sequence) { + $xmlWriter->startElement('text:sequence-decl'); + $xmlWriter->writeAttribute('text:display-outline-level', 0); + $xmlWriter->writeAttribute('text:name', $sequence); + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); // text:sequence-decl + + // Sections + $sections = $phpWord->getSections(); + foreach ($sections as $section) { + $name = 'Section' . $section->getSectionId(); + $xmlWriter->startElement('text:section'); + $xmlWriter->writeAttribute('text:name', $name); + $xmlWriter->writeAttribute('text:style-name', $name); + $containerWriter = new Container($xmlWriter, $section); + $containerWriter->write(); + $xmlWriter->endElement(); // text:section + } + + $xmlWriter->endElement(); // office:text + $xmlWriter->endElement(); // office:body + + $xmlWriter->endElement(); // office:document-content + + return $xmlWriter->getData(); + } + + /** + * Write automatic styles other than fonts and paragraphs. + * + * @since 0.11.0 + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writeAutoStyles(XMLWriter $xmlWriter) + { + $xmlWriter->startElement('office:automatic-styles'); + + $this->writeTextStyles($xmlWriter); + foreach ($this->autoStyles as $element => $styles) { + $writerClass = 'PhpOffice\\PhpWord\\Writer\\ODText\\Style\\' . $element; + foreach ($styles as $style) { + /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ + $styleWriter = new $writerClass($xmlWriter, $style); + $styleWriter->write(); + } + } + + $xmlWriter->endElement(); // office:automatic-styles + } + + /** + * Write automatic styles. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writeTextStyles(XMLWriter $xmlWriter) + { + $styles = Style::getStyles(); + $paragraphStyleCount = 0; + if (count($styles) > 0) { + foreach ($styles as $style) { + if ($style->isAuto() === true) { + $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style)); + if (class_exists($styleClass)) { + /** @var \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle $styleWriter Type hint */ + $styleWriter = new $styleClass($xmlWriter, $style); + $styleWriter->write(); + } + if ($style instanceof Paragraph) { + $paragraphStyleCount++; + } + } + } + if ($paragraphStyleCount == 0) { + $style = new Paragraph(); + $style->setStyleName('P1'); + $style->setAuto(); + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + } + } + } + + /** + * Get automatic styles. + * + * @param \PhpOffice\PhpWord\PhpWord $phpWord + */ + private function getAutoStyles(PhpWord $phpWord) + { + $sections = $phpWord->getSections(); + $paragraphStyleCount = 0; + $fontStyleCount = 0; + foreach ($sections as $section) { + $style = $section->getStyle(); + $style->setStyleName("Section{$section->getSectionId()}"); + $this->autoStyles['Section'][] = $style; + $this->getContainerStyle($section, $paragraphStyleCount, $fontStyleCount); + } + } + + /** + * Get all styles of each elements in container recursively + * + * Table style can be null or string of the style name + * + * @param \PhpOffice\PhpWord\Element\AbstractContainer $container + * @param int $paragraphStyleCount + * @param int $fontStyleCount + * @todo Simplify the logic + */ + private function getContainerStyle($container, &$paragraphStyleCount, &$fontStyleCount) + { + $elements = $container->getElements(); + foreach ($elements as $element) { + if ($element instanceof TextRun) { + $this->getContainerStyle($element, $paragraphStyleCount, $fontStyleCount); + } elseif ($element instanceof Text) { + $this->getElementStyle($element, $paragraphStyleCount, $fontStyleCount); + } elseif ($element instanceof Image) { + $style = $element->getStyle(); + $style->setStyleName('fr' . $element->getMediaIndex()); + $this->autoStyles['Image'][] = $style; + } elseif ($element instanceof Table) { + /** @var \PhpOffice\PhpWord\Style\Table $style */ + $style = $element->getStyle(); + if ($style === null) { + $style = new TableStyle(); + } elseif (is_string($style)) { + $style = Style::getStyle($style); + } + $style->setStyleName($element->getElementId()); + $style->setColumnWidths($element->findFirstDefinedCellWidths()); + $this->autoStyles['Table'][] = $style; + } + } + } + + /** + * Get style of individual element + * + * @param \PhpOffice\PhpWord\Element\Text $element + * @param int $paragraphStyleCount + * @param int $fontStyleCount + */ + private function getElementStyle(&$element, &$paragraphStyleCount, &$fontStyleCount) + { + $fontStyle = $element->getFontStyle(); + $paragraphStyle = $element->getParagraphStyle(); + $phpWord = $this->getParentWriter()->getPhpWord(); + + if ($fontStyle instanceof Font) { + // Font + $fontStyleCount++; + $style = $phpWord->addFontStyle("T{$fontStyleCount}", $fontStyle); + $style->setAuto(); + $element->setFontStyle("T{$fontStyleCount}"); + } elseif ($paragraphStyle instanceof Paragraph) { + // Paragraph + $paragraphStyleCount++; + $style = $phpWord->addParagraphStyle("P{$paragraphStyleCount}", array()); + $style->setAuto(); + $element->setParagraphStyle("P{$paragraphStyleCount}"); + } + } + + /** + * Finds all tracked changes + * + * @param AbstractContainer $container + * @param \PhpOffice\PhpWord\Element\AbstractElement[] $trackedChanges + */ + private function collectTrackedChanges(AbstractContainer $container, &$trackedChanges = array()) + { + $elements = $container->getElements(); + foreach ($elements as $element) { + if ($element->getTrackChange() != null) { + $trackedChanges[] = $element; + } + if (is_callable(array($element, 'getElements'))) { + $this->collectTrackedChanges($element, $trackedChanges); + } + } + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Part/Manifest.php b/PhpOffice/PhpWord/Writer/ODText/Part/Manifest.php new file mode 100644 index 0000000..f952b4c --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Part/Manifest.php @@ -0,0 +1,71 @@ +getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8'); + $xmlWriter->startElement('manifest:manifest'); + $xmlWriter->writeAttribute('manifest:version', '1.2'); + $xmlWriter->writeAttribute('xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'); + + $xmlWriter->startElement('manifest:file-entry'); + $xmlWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.text'); + $xmlWriter->writeAttribute('manifest:full-path', '/'); + $xmlWriter->writeAttribute('manifest:version', '1.2'); + $xmlWriter->endElement(); + + // Parts + foreach ($parts as $part) { + $xmlWriter->startElement('manifest:file-entry'); + $xmlWriter->writeAttribute('manifest:media-type', 'text/xml'); + $xmlWriter->writeAttribute('manifest:full-path', $part); + $xmlWriter->endElement(); + } + + // Media files + $media = Media::getElements('section'); + foreach ($media as $medium) { + if ($medium['type'] == 'image') { + $xmlWriter->startElement('manifest:file-entry'); + $xmlWriter->writeAttribute('manifest:media-type', $medium['imageType']); + $xmlWriter->writeAttribute('manifest:full-path', 'Pictures/' . $medium['target']); + $xmlWriter->endElement(); + } + } + + $xmlWriter->endElement(); // manifest:manifest + + return $xmlWriter->getData(); + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Part/Meta.php b/PhpOffice/PhpWord/Writer/ODText/Part/Meta.php new file mode 100644 index 0000000..f38ad01 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Part/Meta.php @@ -0,0 +1,105 @@ +getParentWriter()->getPhpWord(); + $docProps = $phpWord->getDocInfo(); + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8'); + $xmlWriter->startElement('office:document-meta'); + $xmlWriter->writeAttribute('office:version', '1.2'); + $xmlWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'); + $xmlWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + $xmlWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $xmlWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'); + $xmlWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office'); + $xmlWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#'); + $xmlWriter->startElement('office:meta'); + + // Core properties + $xmlWriter->writeElement('dc:title', $docProps->getTitle()); + $xmlWriter->writeElement('dc:subject', $docProps->getSubject()); + $xmlWriter->writeElement('dc:description', $docProps->getDescription()); + $xmlWriter->writeElement('dc:creator', $docProps->getLastModifiedBy()); + $xmlWriter->writeElement('dc:date', gmdate($this->dateFormat, $docProps->getModified())); + + // Extended properties + $xmlWriter->writeElement('meta:generator', 'PHPWord'); + $xmlWriter->writeElement('meta:initial-creator', $docProps->getCreator()); + $xmlWriter->writeElement('meta:creation-date', gmdate($this->dateFormat, $docProps->getCreated())); + $xmlWriter->writeElement('meta:keyword', $docProps->getKeywords()); + + // Category, company, and manager are put in meta namespace + $properties = array('Category', 'Company', 'Manager'); + foreach ($properties as $property) { + $method = "get{$property}"; + if ($docProps->$method() !== null) { + $this->writeCustomProperty($xmlWriter, $property, $docProps->$method()); + } + } + + // Other custom properties + // @todo Check type. Currently all assumed as string + foreach ($docProps->getCustomProperties() as $property) { + $value = $docProps->getCustomPropertyValue($property); + $this->writeCustomProperty($xmlWriter, $property, $value); + } + + $xmlWriter->endElement(); // office:meta + $xmlWriter->endElement(); // office:document-meta + + return $xmlWriter->getData(); + } + + /** + * Write individual property + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $property + * @param string $value + * + * @todo Handle other `$type`: double|date|dateTime|duration|boolean (4th arguments) + */ + private function writeCustomProperty(XMLWriter $xmlWriter, $property, $value) + { + $xmlWriter->startElement('meta:user-defined'); + $xmlWriter->writeAttribute('meta:name', $property); + // if ($type !== null) { + // $xmlWriter->writeAttribute('meta:value-type', $type); + // } + $this->writeText($value); + $xmlWriter->endElement(); // meta:user-defined + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Part/Mimetype.php b/PhpOffice/PhpWord/Writer/ODText/Part/Mimetype.php new file mode 100644 index 0000000..552f544 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Part/Mimetype.php @@ -0,0 +1,34 @@ +getXmlWriter(); + + // XML header + $xmlWriter->startDocument('1.0', 'UTF-8'); + $xmlWriter->startElement('office:document-styles'); + $this->writeCommonRootAttributes($xmlWriter); + + // Font declarations + $this->writeFontFaces($xmlWriter); + + // Office styles + $xmlWriter->startElement('office:styles'); + $this->writeDefault($xmlWriter); + $this->writeNamed($xmlWriter); + $xmlWriter->endElement(); + + // Automatic styles + $xmlWriter->startElement('office:automatic-styles'); + $this->writePageLayout($xmlWriter); + $xmlWriter->endElement(); // office:automatic-styles + + // Master style + $this->writeMaster($xmlWriter); + + $xmlWriter->endElement(); // office:document-styles + + return $xmlWriter->getData(); + } + + /** + * Write default styles. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writeDefault(XMLWriter $xmlWriter) + { + $xmlWriter->startElement('style:default-style'); + $xmlWriter->writeAttribute('style:family', 'paragraph'); + + // Paragraph + $xmlWriter->startElement('style:paragraph-properties'); + $xmlWriter->writeAttribute('fo:hyphenation-ladder-count', 'no-limit'); + $xmlWriter->writeAttribute('style:text-autospace', 'ideograph-alpha'); + $xmlWriter->writeAttribute('style:punctuation-wrap', 'hanging'); + $xmlWriter->writeAttribute('style:line-break', 'strict'); + $xmlWriter->writeAttribute('style:tab-stop-distance', '1.249cm'); + $xmlWriter->writeAttribute('style:writing-mode', 'page'); + $xmlWriter->endElement(); // style:paragraph-properties + + $language = $this->getParentWriter()->getPhpWord()->getSettings()->getThemeFontLang(); + $latinLang = $language != null && is_string($language->getLatin()) ? explode('-', $language->getLatin()) : array('fr', 'FR'); + $asianLang = $language != null && is_string($language->getEastAsia()) ? explode('-', $language->getEastAsia()) : array('zh', 'CN'); + $complexLang = $language != null && is_string($language->getBidirectional()) ? explode('-', $language->getBidirectional()) : array('hi', 'IN'); + + // Font + $xmlWriter->startElement('style:text-properties'); + $xmlWriter->writeAttribute('style:use-window-font-color', 'true'); + $xmlWriter->writeAttribute('style:font-name', Settings::getDefaultFontName()); + $xmlWriter->writeAttribute('fo:font-size', Settings::getDefaultFontSize() . 'pt'); + $xmlWriter->writeAttribute('fo:language', $latinLang[0]); + $xmlWriter->writeAttribute('fo:country', $latinLang[1]); + $xmlWriter->writeAttribute('style:letter-kerning', 'true'); + $xmlWriter->writeAttribute('style:font-name-asian', Settings::getDefaultFontName() . '2'); + $xmlWriter->writeAttribute('style:font-size-asian', Settings::getDefaultFontSize() . 'pt'); + $xmlWriter->writeAttribute('style:language-asian', $asianLang[0]); + $xmlWriter->writeAttribute('style:country-asian', $asianLang[1]); + $xmlWriter->writeAttribute('style:font-name-complex', Settings::getDefaultFontName() . '2'); + $xmlWriter->writeAttribute('style:font-size-complex', Settings::getDefaultFontSize() . 'pt'); + $xmlWriter->writeAttribute('style:language-complex', $complexLang[0]); + $xmlWriter->writeAttribute('style:country-complex', $complexLang[1]); + $xmlWriter->writeAttribute('fo:hyphenate', 'false'); + $xmlWriter->writeAttribute('fo:hyphenation-remain-char-count', '2'); + $xmlWriter->writeAttribute('fo:hyphenation-push-char-count', '2'); + $xmlWriter->endElement(); // style:text-properties + + $xmlWriter->endElement(); // style:default-style + } + + /** + * Write named styles. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writeNamed(XMLWriter $xmlWriter) + { + $styles = Style::getStyles(); + if (count($styles) > 0) { + foreach ($styles as $style) { + if ($style->isAuto() === false) { + $styleClass = str_replace('\\Style\\', '\\Writer\\ODText\\Style\\', get_class($style)); + if (class_exists($styleClass)) { + /** @var $styleWriter \PhpOffice\PhpWord\Writer\ODText\Style\AbstractStyle Type hint */ + $styleWriter = new $styleClass($xmlWriter, $style); + $styleWriter->write(); + } + } + } + } + } + + /** + * Write page layout styles. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writePageLayout(XMLWriter $xmlWriter) + { + $xmlWriter->startElement('style:page-layout'); + $xmlWriter->writeAttribute('style:name', 'Mpm1'); + + $xmlWriter->startElement('style:page-layout-properties'); + $xmlWriter->writeAttribute('fo:page-width', '21.001cm'); + $xmlWriter->writeAttribute('fo:page-height', '29.7cm'); + $xmlWriter->writeAttribute('style:num-format', '1'); + $xmlWriter->writeAttribute('style:print-orientation', 'portrait'); + $xmlWriter->writeAttribute('fo:margin-top', '2.501cm'); + $xmlWriter->writeAttribute('fo:margin-bottom', '2cm'); + $xmlWriter->writeAttribute('fo:margin-left', '2.501cm'); + $xmlWriter->writeAttribute('fo:margin-right', '2.501cm'); + $xmlWriter->writeAttribute('style:writing-mode', 'lr-tb'); + $xmlWriter->writeAttribute('style:layout-grid-color', '#c0c0c0'); + $xmlWriter->writeAttribute('style:layout-grid-lines', '25199'); + $xmlWriter->writeAttribute('style:layout-grid-base-height', '0.423cm'); + $xmlWriter->writeAttribute('style:layout-grid-ruby-height', '0cm'); + $xmlWriter->writeAttribute('style:layout-grid-mode', 'none'); + $xmlWriter->writeAttribute('style:layout-grid-ruby-below', 'false'); + $xmlWriter->writeAttribute('style:layout-grid-print', 'false'); + $xmlWriter->writeAttribute('style:layout-grid-display', 'false'); + $xmlWriter->writeAttribute('style:layout-grid-base-width', '0.37cm'); + $xmlWriter->writeAttribute('style:layout-grid-snap-to', 'true'); + $xmlWriter->writeAttribute('style:footnote-max-height', '0cm'); + + $xmlWriter->startElement('style:footnote-sep'); + $xmlWriter->writeAttribute('style:width', '0.018cm'); + $xmlWriter->writeAttribute('style:line-style', 'solid'); + $xmlWriter->writeAttribute('style:adjustment', 'left'); + $xmlWriter->writeAttribute('style:rel-width', '25%'); + $xmlWriter->writeAttribute('style:color', '#000000'); + $xmlWriter->endElement(); //style:footnote-sep + + $xmlWriter->endElement(); // style:page-layout-properties + + $xmlWriter->startElement('style:header-style'); + $xmlWriter->endElement(); // style:header-style + + $xmlWriter->startElement('style:footer-style'); + $xmlWriter->endElement(); // style:footer-style + + $xmlWriter->endElement(); // style:page-layout + } + + /** + * Write master style. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writeMaster(XMLWriter $xmlWriter) + { + $xmlWriter->startElement('office:master-styles'); + + $xmlWriter->startElement('style:master-page'); + $xmlWriter->writeAttribute('style:name', 'Standard'); + $xmlWriter->writeAttribute('style:page-layout-name', 'Mpm1'); + $xmlWriter->endElement(); // style:master-page + + $xmlWriter->endElement(); // office:master-styles + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Style/AbstractStyle.php b/PhpOffice/PhpWord/Writer/ODText/Style/AbstractStyle.php new file mode 100644 index 0000000..f7679ab --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Style/AbstractStyle.php @@ -0,0 +1,29 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Font) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('style:style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName()); + $xmlWriter->writeAttribute('style:family', 'text'); + $xmlWriter->startElement('style:text-properties'); + + // Name + $font = $style->getName(); + $xmlWriter->writeAttributeIf($font != '', 'style:font-name', $font); + $xmlWriter->writeAttributeIf($font != '', 'style:font-name-complex', $font); + $size = $style->getSize(); + + // Size + $xmlWriter->writeAttributeIf(is_numeric($size), 'fo:font-size', $size . 'pt'); + $xmlWriter->writeAttributeIf(is_numeric($size), 'style:font-size-asian', $size . 'pt'); + $xmlWriter->writeAttributeIf(is_numeric($size), 'style:font-size-complex', $size . 'pt'); + + // Color + $color = $style->getColor(); + $xmlWriter->writeAttributeIf($color != '', 'fo:color', '#' . $color); + + // Bold & italic + $xmlWriter->writeAttributeIf($style->isBold(), 'fo:font-weight', 'bold'); + $xmlWriter->writeAttributeIf($style->isBold(), 'style:font-weight-asian', 'bold'); + $xmlWriter->writeAttributeIf($style->isItalic(), 'fo:font-style', 'italic'); + $xmlWriter->writeAttributeIf($style->isItalic(), 'style:font-style-asian', 'italic'); + $xmlWriter->writeAttributeIf($style->isItalic(), 'style:font-style-complex', 'italic'); + + // Underline + // @todo Various mode of underline + $underline = $style->getUnderline(); + $xmlWriter->writeAttributeIf($underline != 'none', 'style:text-underline-style', 'solid'); + + // Strikethrough, double strikethrough + $xmlWriter->writeAttributeIf($style->isStrikethrough(), 'style:text-line-through-type', 'single'); + $xmlWriter->writeAttributeIf($style->isDoubleStrikethrough(), 'style:text-line-through-type', 'double'); + + // Small caps, all caps + $xmlWriter->writeAttributeIf($style->isSmallCaps(), 'fo:font-variant', 'small-caps'); + $xmlWriter->writeAttributeIf($style->isAllCaps(), 'fo:text-transform', 'uppercase'); + + //Hidden text + $xmlWriter->writeAttributeIf($style->isHidden(), 'text:display', 'none'); + + // Superscript/subscript + $xmlWriter->writeAttributeIf($style->isSuperScript(), 'style:text-position', 'super'); + $xmlWriter->writeAttributeIf($style->isSubScript(), 'style:text-position', 'sub'); + + // @todo Foreground-Color + + // @todo Background color + + $xmlWriter->endElement(); // style:text-properties + $xmlWriter->endElement(); // style:style + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Style/Image.php b/PhpOffice/PhpWord/Writer/ODText/Style/Image.php new file mode 100644 index 0000000..13005a7 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Style/Image.php @@ -0,0 +1,49 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Image) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('style:style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName()); + $xmlWriter->writeAttribute('style:family', 'graphic'); + $xmlWriter->writeAttribute('style:parent-style-name', 'Graphics'); + $xmlWriter->startElement('style:graphic-properties'); + $xmlWriter->writeAttribute('style:vertical-pos', 'top'); + $xmlWriter->writeAttribute('style:vertical-rel', 'baseline'); + $xmlWriter->endElement(); // style:graphic-properties + $xmlWriter->endElement(); // style:style + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Style/Paragraph.php b/PhpOffice/PhpWord/Writer/ODText/Style/Paragraph.php new file mode 100644 index 0000000..f247dcc --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Style/Paragraph.php @@ -0,0 +1,65 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Paragraph) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $marginTop = (is_null($style->getSpaceBefore()) || $style->getSpaceBefore() == 0) ? '0' : round(17.6 / $style->getSpaceBefore(), 2); + $marginBottom = (is_null($style->getSpaceAfter()) || $style->getSpaceAfter() == 0) ? '0' : round(17.6 / $style->getSpaceAfter(), 2); + + $xmlWriter->startElement('style:style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName()); + $xmlWriter->writeAttribute('style:family', 'paragraph'); + if ($style->isAuto()) { + $xmlWriter->writeAttribute('style:parent-style-name', 'Standard'); + $xmlWriter->writeAttribute('style:master-page-name', 'Standard'); + } + + $xmlWriter->startElement('style:paragraph-properties'); + if ($style->isAuto()) { + $xmlWriter->writeAttribute('style:page-number', 'auto'); + } else { + $xmlWriter->writeAttribute('fo:margin-top', $marginTop . 'cm'); + $xmlWriter->writeAttribute('fo:margin-bottom', $marginBottom . 'cm'); + $xmlWriter->writeAttribute('fo:text-align', $style->getAlignment()); + } + + //Right to left + $xmlWriter->writeAttributeIf($style->isBidi(), 'style:writing-mode', 'rl-tb'); + + $xmlWriter->endElement(); //style:paragraph-properties + + $xmlWriter->endElement(); //style:style + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Style/Section.php b/PhpOffice/PhpWord/Writer/ODText/Style/Section.php new file mode 100644 index 0000000..92d8891 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Style/Section.php @@ -0,0 +1,51 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Section) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('style:style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName()); + $xmlWriter->writeAttribute('style:family', 'section'); + $xmlWriter->startElement('style:section-properties'); + + $xmlWriter->startElement('style:columns'); + $xmlWriter->writeAttribute('fo:column-count', $style->getColsNum()); + $xmlWriter->endElement(); // style:columns + + $xmlWriter->endElement(); // style:section-properties + $xmlWriter->endElement(); // style:style + } +} diff --git a/PhpOffice/PhpWord/Writer/ODText/Style/Table.php b/PhpOffice/PhpWord/Writer/ODText/Style/Table.php new file mode 100644 index 0000000..646f2e4 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/ODText/Style/Table.php @@ -0,0 +1,64 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Table) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('style:style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName()); + $xmlWriter->writeAttribute('style:family', 'table'); + $xmlWriter->startElement('style:table-properties'); + //$xmlWriter->writeAttribute('style:width', 'table'); + $xmlWriter->writeAttribute('style:rel-width', 100); + $xmlWriter->writeAttribute('table:align', 'center'); + $xmlWriter->writeAttributeIf($style->isBidiVisual(), 'style:writing-mode', 'rl-tb'); + $xmlWriter->endElement(); // style:table-properties + $xmlWriter->endElement(); // style:style + + $cellWidths = $style->getColumnWidths(); + $countCellWidths = $cellWidths === null ? 0 : count($cellWidths); + + for ($i = 0; $i < $countCellWidths; $i++) { + $width = $cellWidths[$i]; + $xmlWriter->startElement('style:style'); + $xmlWriter->writeAttribute('style:name', $style->getStyleName() . '.' . $i); + $xmlWriter->writeAttribute('style:family', 'table-column'); + $xmlWriter->startElement('style:table-column-properties'); + $xmlWriter->writeAttribute('style:column-width', number_format($width * 0.0017638889, 2, '.', '') . 'cm'); + $xmlWriter->endElement(); // style:table-column-properties + $xmlWriter->endElement(); // style:style + } + } +} diff --git a/PhpOffice/PhpWord/Writer/PDF.php b/PhpOffice/PhpWord/Writer/PDF.php new file mode 100644 index 0000000..64dcc78 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/PDF.php @@ -0,0 +1,79 @@ +renderer = new $rendererName($phpWord); + } + + /** + * Magic method to handle direct calls to the configured PDF renderer wrapper class. + * + * @param string $name Renderer library method name + * @param mixed[] $arguments Array of arguments to pass to the renderer method + * @return mixed Returned data from the PDF renderer wrapper method + */ + public function __call($name, $arguments) + { + // Note: Commented because all exceptions should already be catched by `__construct` + // if ($this->renderer === null) { + // throw new Exception("PDF Rendering library has not been defined."); + // } + + return call_user_func_array(array($this->renderer, $name), $arguments); + } +} diff --git a/PhpOffice/PhpWord/Writer/PDF/AbstractRenderer.php b/PhpOffice/PhpWord/Writer/PDF/AbstractRenderer.php new file mode 100644 index 0000000..5f9e3b3 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/PDF/AbstractRenderer.php @@ -0,0 +1,206 @@ + 'A4', // (210 mm by 297 mm) + ); + + /** + * Create new instance + * + * @param PhpWord $phpWord PhpWord object + * + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + public function __construct(PhpWord $phpWord) + { + parent::__construct($phpWord); + if ($this->includeFile != null) { + $includeFile = Settings::getPdfRendererPath() . '/' . $this->includeFile; + if (file_exists($includeFile)) { + /** @noinspection PhpIncludeInspection Dynamic includes */ + require_once $includeFile; + } else { + // @codeCoverageIgnoreStart + // Can't find any test case. Uncomment when found. + throw new Exception('Unable to load PDF Rendering library'); + // @codeCoverageIgnoreEnd + } + } + } + + /** + * Get Font + * + * @return string + */ + public function getFont() + { + return $this->font; + } + + /** + * Set font. Examples: + * 'arialunicid0-chinese-simplified' + * 'arialunicid0-chinese-traditional' + * 'arialunicid0-korean' + * 'arialunicid0-japanese' + * + * @param string $fontName + * @return self + */ + public function setFont($fontName) + { + $this->font = $fontName; + + return $this; + } + + /** + * Get Paper Size + * + * @return int + */ + public function getPaperSize() + { + return $this->paperSize; + } + + /** + * Set Paper Size + * + * @param int $value Paper size = PAPERSIZE_A4 + * @return self + */ + public function setPaperSize($value = 9) + { + $this->paperSize = $value; + + return $this; + } + + /** + * Get Orientation + * + * @return string + */ + public function getOrientation() + { + return $this->orientation; + } + + /** + * Set Orientation + * + * @param string $value Page orientation ORIENTATION_DEFAULT + * @return self + */ + public function setOrientation($value = 'default') + { + $this->orientation = $value; + + return $this; + } + + /** + * Save PhpWord to PDF file, pre-save + * + * @param string $filename Name of the file to save as + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * @return resource + */ + protected function prepareForSave($filename = null) + { + $fileHandle = fopen($filename, 'w'); + // @codeCoverageIgnoreStart + // Can't find any test case. Uncomment when found. + if ($fileHandle === false) { + throw new Exception("Could not open file $filename for writing."); + } + // @codeCoverageIgnoreEnd + $this->isPdf = true; + + return $fileHandle; + } + + /** + * Save PhpWord to PDF file, post-save + * + * @param resource $fileHandle + * + * @throws Exception + */ + protected function restoreStateAfterSave($fileHandle) + { + fclose($fileHandle); + } +} diff --git a/PhpOffice/PhpWord/Writer/PDF/DomPDF.php b/PhpOffice/PhpWord/Writer/PDF/DomPDF.php new file mode 100644 index 0000000..5fa8f75 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/PDF/DomPDF.php @@ -0,0 +1,62 @@ +setPaper(strtolower($paperSize), $orientation); + $pdf->loadHtml(str_replace(PHP_EOL, '', $this->getContent())); + $pdf->render(); + + // Write to file + fwrite($fileHandle, $pdf->output()); + + parent::restoreStateAfterSave($fileHandle); + } +} diff --git a/PhpOffice/PhpWord/Writer/PDF/MPDF.php b/PhpOffice/PhpWord/Writer/PDF/MPDF.php new file mode 100644 index 0000000..e63f5df --- /dev/null +++ b/PhpOffice/PhpWord/Writer/PDF/MPDF.php @@ -0,0 +1,99 @@ +includeFile = 'mpdf.php'; + } + parent::__construct($phpWord); + } + + /** + * Save PhpWord to file. + * + * @param string $filename Name of the file to save as + */ + public function save($filename = null) + { + $fileHandle = parent::prepareForSave($filename); + + // PDF settings + $paperSize = strtoupper('A4'); + $orientation = strtoupper('portrait'); + + // Create PDF + $mPdfClass = $this->getMPdfClassName(); + $pdf = new $mPdfClass(); + $pdf->_setPageSize($paperSize, $orientation); + $pdf->addPage($orientation); + + // Write document properties + $phpWord = $this->getPhpWord(); + $docProps = $phpWord->getDocInfo(); + $pdf->setTitle($docProps->getTitle()); + $pdf->setAuthor($docProps->getCreator()); + $pdf->setSubject($docProps->getSubject()); + $pdf->setKeywords($docProps->getKeywords()); + $pdf->setCreator($docProps->getCreator()); + + $pdf->writeHTML($this->getContent()); + + // Write to file + fwrite($fileHandle, $pdf->output($filename, 'S')); + + parent::restoreStateAfterSave($fileHandle); + } + + /** + * Return classname of MPDF to instantiate + * + * @codeCoverageIgnore + * @return string + */ + private function getMPdfClassName() + { + if ($this->includeFile != null) { + // MPDF version 5.* + return '\mpdf'; + } + + // MPDF version > 6.* + return '\Mpdf\Mpdf'; + } +} diff --git a/PhpOffice/PhpWord/Writer/PDF/TCPDF.php b/PhpOffice/PhpWord/Writer/PDF/TCPDF.php new file mode 100644 index 0000000..badab04 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/PDF/TCPDF.php @@ -0,0 +1,75 @@ +setFontSubsetting(false); + $pdf->setPrintHeader(false); + $pdf->setPrintFooter(false); + $pdf->AddPage(); + $pdf->SetFont($this->getFont()); + $pdf->writeHTML($this->getContent()); + + // Write document properties + $phpWord = $this->getPhpWord(); + $docProps = $phpWord->getDocInfo(); + $pdf->SetTitle($docProps->getTitle()); + $pdf->SetAuthor($docProps->getCreator()); + $pdf->SetSubject($docProps->getSubject()); + $pdf->SetKeywords($docProps->getKeywords()); + $pdf->SetCreator($docProps->getCreator()); + + // Write to file + fwrite($fileHandle, $pdf->Output($filename, 'S')); + + parent::restoreStateAfterSave($fileHandle); + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF.php b/PhpOffice/PhpWord/Writer/RTF.php new file mode 100644 index 0000000..0604e8b --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF.php @@ -0,0 +1,126 @@ +setPhpWord($phpWord); + + $this->parts = array('Header', 'Document'); + foreach ($this->parts as $partName) { + $partClass = get_class($this) . '\\Part\\' . $partName; + if (class_exists($partClass)) { + /** @var \PhpOffice\PhpWord\Writer\RTF\Part\AbstractPart $part Type hint */ + $part = new $partClass(); + $part->setParentWriter($this); + $this->writerParts[strtolower($partName)] = $part; + } + } + } + + /** + * Save content to file. + * + * @param string $filename + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + public function save($filename = null) + { + $this->writeFile($this->openFile($filename), $this->getContent()); + } + + /** + * Get content + * + * @return string + * @since 0.11.0 + */ + private function getContent() + { + $content = ''; + + $content .= '{'; + $content .= '\rtf1' . PHP_EOL; + $content .= $this->getWriterPart('Header')->write(); + $content .= $this->getWriterPart('Document')->write(); + $content .= '}'; + + return $content; + } + + /** + * Get font table. + * + * @return array + */ + public function getFontTable() + { + return $this->getWriterPart('Header')->getFontTable(); + } + + /** + * Get color table. + * + * @return array + */ + public function getColorTable() + { + return $this->getWriterPart('Header')->getColorTable(); + } + + /** + * Get last paragraph style. + * + * @return mixed + */ + public function getLastParagraphStyle() + { + return $this->lastParagraphStyle; + } + + /** + * Set last paragraph style. + * + * @param mixed $value + */ + public function setLastParagraphStyle($value = '') + { + $this->lastParagraphStyle = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/AbstractElement.php b/PhpOffice/PhpWord/Writer/RTF/Element/AbstractElement.php new file mode 100644 index 0000000..132890e --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/AbstractElement.php @@ -0,0 +1,180 @@ +escaper = new Rtf(); + } + + /** + * Get font and paragraph styles. + */ + protected function getStyles() + { + /** @var \PhpOffice\PhpWord\Writer\RTF $parentWriter Type hint */ + $parentWriter = $this->parentWriter; + + /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ + $element = $this->element; + + // Font style + if (method_exists($element, 'getFontStyle')) { + $this->fontStyle = $element->getFontStyle(); + if (is_string($this->fontStyle)) { + $this->fontStyle = Style::getStyle($this->fontStyle); + } + } + + // Paragraph style + if (method_exists($element, 'getParagraphStyle')) { + $this->paragraphStyle = $element->getParagraphStyle(); + if (is_string($this->paragraphStyle)) { + $this->paragraphStyle = Style::getStyle($this->paragraphStyle); + } + + if ($this->paragraphStyle !== null && !$this->withoutP) { + if ($parentWriter->getLastParagraphStyle() != $element->getParagraphStyle()) { + $parentWriter->setLastParagraphStyle($element->getParagraphStyle()); + } else { + $parentWriter->setLastParagraphStyle(); + $this->paragraphStyle = null; + } + } else { + $parentWriter->setLastParagraphStyle(); + $this->paragraphStyle = null; + } + } + } + + /** + * Write opening + * + * @return string + */ + protected function writeOpening() + { + if ($this->withoutP || !$this->paragraphStyle instanceof ParagraphStyle) { + return ''; + } + + $styleWriter = new ParagraphStyleWriter($this->paragraphStyle); + $styleWriter->setNestedLevel($this->element->getNestedLevel()); + + return $styleWriter->write(); + } + + /** + * Write text + * + * @param string $text + * @return string + */ + protected function writeText($text) + { + if (Settings::isOutputEscapingEnabled()) { + return $this->escaper->escape($text); + } + + return CommonText::toUnicode($text); // todo: replace with `return $text;` later. + } + + /** + * Write closing + * + * @return string + */ + protected function writeClosing() + { + if ($this->withoutP) { + return ''; + } + + return '\par' . PHP_EOL; + } + + /** + * Write font style + * + * @return string + */ + protected function writeFontStyle() + { + if (!$this->fontStyle instanceof FontStyle) { + return ''; + } + + /** @var \PhpOffice\PhpWord\Writer\RTF $parentWriter Type hint */ + $parentWriter = $this->parentWriter; + + // Create style writer and set color/name index + $styleWriter = new FontStyleWriter($this->fontStyle); + if ($this->fontStyle->getColor() != null) { + $colorIndex = array_search($this->fontStyle->getColor(), $parentWriter->getColorTable()); + if ($colorIndex !== false) { + $styleWriter->setColorIndex($colorIndex + 1); + } + } + if ($this->fontStyle->getName() != null) { + $fontIndex = array_search($this->fontStyle->getName(), $parentWriter->getFontTable()); + if ($fontIndex !== false) { + $styleWriter->setNameIndex($fontIndex); + } + } + + // Write style + $content = $styleWriter->write(); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/Container.php b/PhpOffice/PhpWord/Writer/RTF/Element/Container.php new file mode 100644 index 0000000..58c1925 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/Container.php @@ -0,0 +1,35 @@ +element; + if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + return; + } + + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{'; + $content .= $this->writeFontStyle(); + + $methodName = 'write' . ucfirst(strtolower($element->getType())); + if (!method_exists($this, $methodName)) { + // Unsupported field + $content .= ''; + } else { + $content .= '\\field{\\*\\fldinst '; + $content .= $this->$methodName($element); + $content .= '}{\\fldrslt}'; + } + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } + + protected function writePage() + { + return 'PAGE'; + } + + protected function writeNumpages() + { + return 'NUMPAGES'; + } + + protected function writeDate(\PhpOffice\PhpWord\Element\Field $element) + { + $content = ''; + $content .= 'DATE'; + $properties = $element->getProperties(); + if (isset($properties['dateformat'])) { + $content .= ' \\\\@ "' . $properties['dateformat'] . '"'; + } + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/Image.php b/PhpOffice/PhpWord/Writer/RTF/Element/Image.php new file mode 100644 index 0000000..f1e7270 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/Image.php @@ -0,0 +1,57 @@ +element instanceof ImageElement) { + return ''; + } + + $this->getStyles(); + $style = $this->element->getStyle(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{\*\shppict {\pict'; + $content .= '\pngblip\picscalex100\picscaley100'; + $content .= '\picwgoal' . round(Converter::pixelToTwip($style->getWidth())); + $content .= '\pichgoal' . round(Converter::pixelToTwip($style->getHeight())); + $content .= PHP_EOL; + $content .= $this->element->getImageStringData(); + $content .= '}}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/Link.php b/PhpOffice/PhpWord/Writer/RTF/Element/Link.php new file mode 100644 index 0000000..25954ed --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/Link.php @@ -0,0 +1,50 @@ +element instanceof \PhpOffice\PhpWord\Element\Link) { + return ''; + } + + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{\field {\*\fldinst {HYPERLINK "' . $this->element->getSource() . '"}}{\\fldrslt {'; + $content .= $this->writeFontStyle(); + $content .= $this->writeText($this->element->getText()); + $content .= '}}}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/ListItem.php b/PhpOffice/PhpWord/Writer/RTF/Element/ListItem.php new file mode 100644 index 0000000..29e7f66 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/ListItem.php @@ -0,0 +1,27 @@ +element instanceof TableElement) { + return ''; + } + $element = $this->element; + // No nesting table for now + if ($element->getNestedLevel() >= 1) { + return ''; + } + + $content = ''; + $rows = $element->getRows(); + $rowCount = count($rows); + + if ($rowCount > 0) { + $content .= '\pard' . PHP_EOL; + + for ($i = 0; $i < $rowCount; $i++) { + $content .= '\trowd '; + $content .= $this->writeRowDef($rows[$i]); + $content .= PHP_EOL; + $content .= $this->writeRow($rows[$i]); + $content .= '\row' . PHP_EOL; + } + $content .= '\pard' . PHP_EOL; + } + + return $content; + } + + /** + * Write column + * + * @param \PhpOffice\PhpWord\Element\Row $row + * @return string + */ + private function writeRowDef(RowElement $row) + { + $content = ''; + + $rightMargin = 0; + foreach ($row->getCells() as $cell) { + $width = $cell->getWidth(); + $vMerge = $this->getVMerge($cell->getStyle()->getVMerge()); + if ($width === null) { + $width = 720; // Arbitrary default width + } + $rightMargin += $width; + $content .= "{$vMerge}\cellx{$rightMargin} "; + } + + return $content; + } + + /** + * Write row + * + * @param \PhpOffice\PhpWord\Element\Row $row + * @return string + */ + private function writeRow(RowElement $row) + { + $content = ''; + + // Write cells + foreach ($row->getCells() as $cell) { + $content .= $this->writeCell($cell); + } + + return $content; + } + + /** + * Write cell + * + * @param \PhpOffice\PhpWord\Element\Cell $cell + * @return string + */ + private function writeCell(CellElement $cell) + { + $content = '\intbl' . PHP_EOL; + + // Write content + $writer = new Container($this->parentWriter, $cell); + $content .= $writer->write(); + + $content .= '\cell' . PHP_EOL; + + return $content; + } + + /** + * Get vertical merge style + * + * @param string $value + * @return string + * @todo Move to style + */ + private function getVMerge($value) + { + $style = ''; + if ($value == 'restart') { + $style = '\clvmgf'; + } elseif ($value == 'continue') { + $style = '\clvmrg'; + } + + return $style; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/Text.php b/PhpOffice/PhpWord/Writer/RTF/Element/Text.php new file mode 100644 index 0000000..b9e56e8 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/Text.php @@ -0,0 +1,53 @@ +element; + $elementClass = str_replace('\\Writer\\RTF', '', get_class($this)); + if (!$element instanceof $elementClass || !is_string($element->getText())) { + return ''; + } + + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{'; + $content .= $this->writeFontStyle(); + $content .= $this->writeText($element->getText()); + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/TextBreak.php b/PhpOffice/PhpWord/Writer/RTF/Element/TextBreak.php new file mode 100644 index 0000000..4aab276 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/TextBreak.php @@ -0,0 +1,40 @@ +parentWriter; + $parentWriter->setLastParagraphStyle(); + + return '\pard\par' . PHP_EOL; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/TextRun.php b/PhpOffice/PhpWord/Writer/RTF/Element/TextRun.php new file mode 100644 index 0000000..e2865d8 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/TextRun.php @@ -0,0 +1,46 @@ +parentWriter, $this->element); + $this->getStyles(); + + $content = ''; + $content .= $this->writeOpening(); + $content .= '{'; + $content .= $writer->write(); + $content .= '}'; + $content .= $this->writeClosing(); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Element/Title.php b/PhpOffice/PhpWord/Writer/RTF/Element/Title.php new file mode 100644 index 0000000..77ebff1 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Element/Title.php @@ -0,0 +1,92 @@ +element; + $style = $element->getStyle(); + $style = str_replace('Heading', 'Heading_', $style); + $style = \PhpOffice\PhpWord\Style::getStyle($style); + if ($style instanceof \PhpOffice\PhpWord\Style\Font) { + $this->fontStyle = $style; + $pstyle = $style->getParagraph(); + if ($pstyle instanceof \PhpOffice\PhpWord\Style\Paragraph && $pstyle->hasPageBreakBefore()) { + $sect = $element->getParent(); + if ($sect instanceof \PhpOffice\PhpWord\Element\Section) { + $elems = $sect->getElements(); + if ($elems[0] === $element) { + $pstyle = clone $pstyle; + $pstyle->setPageBreakBefore(false); + } + } + } + $this->paragraphStyle = $pstyle; + } + } + + /** + * Write element + * + * @return string + */ + public function write() + { + /** @var \PhpOffice\PhpWord\Element\Title $element Type hint */ + $element = $this->element; + $elementClass = str_replace('\\Writer\\RTF', '', get_class($this)); + if (!$element instanceof $elementClass || !is_string($element->getText())) { + return ''; + } + + $this->getStyles(); + + $content = ''; + + $content .= $this->writeOpening(); + $endout = ''; + $style = $element->getStyle(); + if (is_string($style)) { + $style = str_replace('Heading', '', $style); + if (is_numeric($style)) { + $style = (int) $style - 1; + if ($style >= 0 && $style <= 8) { + $content .= '{\\outlinelevel' . $style; + $endout = '}'; + } + } + } + + $content .= '{'; + $content .= $this->writeFontStyle(); + $content .= $this->writeText($element->getText()); + $content .= '}'; + $content .= $this->writeClosing(); + $content .= $endout; + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Part/AbstractPart.php b/PhpOffice/PhpWord/Writer/RTF/Part/AbstractPart.php new file mode 100644 index 0000000..8171b0d --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Part/AbstractPart.php @@ -0,0 +1,68 @@ +escaper = new Rtf(); + } + + /** + * @return string + */ + abstract public function write(); + + /** + * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer + */ + public function setParentWriter(AbstractWriter $writer = null) + { + $this->parentWriter = $writer; + } + + /** + * @throws \PhpOffice\PhpWord\Exception\Exception + * @return \PhpOffice\PhpWord\Writer\AbstractWriter + */ + public function getParentWriter() + { + if ($this->parentWriter !== null) { + return $this->parentWriter; + } + throw new Exception('No parent WriterInterface assigned.'); + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Part/Document.php b/PhpOffice/PhpWord/Writer/RTF/Part/Document.php new file mode 100644 index 0000000..14d9009 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Part/Document.php @@ -0,0 +1,231 @@ +writeInfo(); + $content .= $this->writeFormatting(); + $content .= $this->writeSections(); + + return $content; + } + + /** + * Write document information + * + * @return string + */ + private function writeInfo() + { + $docProps = $this->getParentWriter()->getPhpWord()->getDocInfo(); + $properties = array('title', 'subject', 'category', 'keywords', 'comment', + 'author', 'operator', 'creatim', 'revtim', 'company', 'manager', ); + $mapping = array( + 'comment' => 'description', + 'author' => 'creator', + 'operator' => 'lastModifiedBy', + 'creatim' => 'created', + 'revtim' => 'modified', ); + $dateFields = array('creatim', 'revtim'); + + $content = ''; + + $content .= '{'; + $content .= '\info'; + foreach ($properties as $property) { + $method = 'get' . (isset($mapping[$property]) ? $mapping[$property] : $property); + if (!in_array($property, $dateFields) && Settings::isOutputEscapingEnabled()) { + $value = $this->escaper->escape($docProps->$method()); + } else { + $value = $docProps->$method(); + } + $value = in_array($property, $dateFields) ? $this->getDateValue($value) : $value; + $content .= "{\\{$property} {$value}}"; + } + $content .= '}'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write document formatting properties + * + * @return string + */ + private function writeFormatting() + { + $docSettings = $this->getParentWriter()->getPhpWord()->getSettings(); + // Applies a language to a text run (defaults to 1036 : French (France)) + $langId = $docSettings->getThemeFontLang() != null && $docSettings->getThemeFontLang()->getLangId() != null ? $docSettings->getThemeFontLang()->getLangId() : 1036; + + $content = ''; + + $content .= '\deftab720'; // Set the default tab size (720 twips) + $content .= '\viewkind1'; // Set the view mode of the document + + $content .= '\uc1'; // Set the numberof bytes that follows a unicode character + $content .= '\pard'; // Resets to default paragraph properties. + $content .= '\nowidctlpar'; // No widow/orphan control + $content .= '\lang' . $langId; + $content .= '\kerning1'; // Point size (in half-points) above which to kern character pairs + $content .= '\fs' . (Settings::getDefaultFontSize() * 2); // Set the font size in half-points + if ($docSettings->hasEvenAndOddHeaders()) { + $content .= '\\facingp'; + } + $content .= PHP_EOL; + + return $content; + } + + /** + * Write titlepg directive if any "f" headers or footers + * + * @param \PhpOffice\PhpWord\Element\Section $section + * @return string + */ + private static function writeTitlepg($section) + { + foreach ($section->getHeaders() as $header) { + if ($header->getType() === Footer::FIRST) { + return '\\titlepg' . PHP_EOL; + } + } + foreach ($section->getFooters() as $header) { + if ($header->getType() === Footer::FIRST) { + return '\\titlepg' . PHP_EOL; + } + } + + return ''; + } + + /** + * Write sections + * + * @return string + */ + private function writeSections() + { + $content = ''; + + $sections = $this->getParentWriter()->getPhpWord()->getSections(); + $evenOdd = $this->getParentWriter()->getPhpWord()->getSettings()->hasEvenAndOddHeaders(); + foreach ($sections as $section) { + $styleWriter = new SectionStyleWriter($section->getStyle()); + $styleWriter->setParentWriter($this->getParentWriter()); + $content .= $styleWriter->write(); + $content .= self::writeTitlepg($section); + + foreach ($section->getHeaders() as $header) { + $type = $header->getType(); + if ($evenOdd || $type !== FOOTER::EVEN) { + $content .= '{\\header'; + if ($type === Footer::FIRST) { + $content .= 'f'; + } elseif ($evenOdd) { + $content .= ($type === FOOTER::EVEN) ? 'l' : 'r'; + } + foreach ($header->getElements() as $element) { + $cl = get_class($element); + $cl2 = str_replace('Element', 'Writer\\RTF\\Element', $cl); + if (class_exists($cl2)) { + $elementWriter = new $cl2($this->getParentWriter(), $element); + $content .= $elementWriter->write(); + } + } + $content .= '}' . PHP_EOL; + } + } + foreach ($section->getFooters() as $footer) { + $type = $footer->getType(); + if ($evenOdd || $type !== FOOTER::EVEN) { + $content .= '{\\footer'; + if ($type === Footer::FIRST) { + $content .= 'f'; + } elseif ($evenOdd) { + $content .= ($type === FOOTER::EVEN) ? 'l' : 'r'; + } + foreach ($footer->getElements() as $element) { + $cl = get_class($element); + $cl2 = str_replace('Element', 'Writer\\RTF\\Element', $cl); + if (class_exists($cl2)) { + $elementWriter = new $cl2($this->getParentWriter(), $element); + $content .= $elementWriter->write(); + } + } + $content .= '}' . PHP_EOL; + } + } + + $elementWriter = new Container($this->getParentWriter(), $section); + $content .= $elementWriter->write(); + + $content .= '\sect' . PHP_EOL; + } + + return $content; + } + + /** + * Get date value + * + * The format of date value is `\yr?\mo?\dy?\hr?\min?\sec?` + * + * @param int $value + * @return string + */ + private function getDateValue($value) + { + $dateParts = array( + 'Y' => 'yr', + 'm' => 'mo', + 'd' => 'dy', + 'H' => 'hr', + 'i' => 'min', + 's' => 'sec', + ); + $result = ''; + foreach ($dateParts as $dateFormat => $controlWord) { + $result .= '\\' . $controlWord . date($dateFormat, $value); + } + + return $result; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Part/Header.php b/PhpOffice/PhpWord/Writer/RTF/Part/Header.php new file mode 100644 index 0000000..01439bc --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Part/Header.php @@ -0,0 +1,255 @@ +fontTable; + } + + /** + * Get color table. + * + * @return array + */ + public function getColorTable() + { + return $this->colorTable; + } + + /** + * Write part + * + * @return string + */ + public function write() + { + $this->registerFont(); + + $content = ''; + + $content .= $this->writeCharset(); + $content .= $this->writeDefaults(); + $content .= $this->writeFontTable(); + $content .= $this->writeColorTable(); + $content .= $this->writeGenerator(); + $content .= PHP_EOL; + + return $content; + } + + /** + * Write character set + * + * @return string + */ + private function writeCharset() + { + $content = ''; + + $content .= '\ansi'; + $content .= '\ansicpg1252'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write header defaults + * + * @return string + */ + private function writeDefaults() + { + $content = ''; + + $content .= '\deff0'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write font table + * + * @return string + */ + private function writeFontTable() + { + $content = ''; + + $content .= '{'; + $content .= '\fonttbl'; + foreach ($this->fontTable as $index => $font) { + $content .= "{\\f{$index}\\fnil\\fcharset0 {$font};}"; + } + $content .= '}'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write color table + * + * @return string + */ + private function writeColorTable() + { + $content = ''; + + $content .= '{'; + $content .= '\colortbl;'; + foreach ($this->colorTable as $color) { + list($red, $green, $blue) = Converter::htmlToRgb($color); + $content .= "\\red{$red}\\green{$green}\\blue{$blue};"; + } + $content .= '}'; + $content .= PHP_EOL; + + return $content; + } + + /** + * Write + * + * @return string + */ + private function writeGenerator() + { + $content = ''; + + $content .= '{\*\generator PHPWord;}'; // Set the generator + $content .= PHP_EOL; + + return $content; + } + + /** + * Register all fonts and colors in both named and inline styles to appropriate header table. + */ + private function registerFont() + { + $phpWord = $this->getParentWriter()->getPhpWord(); + $this->fontTable[] = Settings::getDefaultFontName(); + + // Search named styles + $styles = Style::getStyles(); + foreach ($styles as $style) { + $this->registerFontItems($style); + } + + // Search inline styles + $sections = $phpWord->getSections(); + foreach ($sections as $section) { + $elements = $section->getElements(); + $this->registerBorderColor($section->getStyle()); + foreach ($elements as $element) { + if (method_exists($element, 'getFontStyle')) { + $style = $element->getFontStyle(); + $this->registerFontItems($style); + } + } + } + } + + /** + * Register border colors. + * + * @param \PhpOffice\PhpWord\Style\Border $style + */ + private function registerBorderColor($style) + { + $colors = $style->getBorderColor(); + foreach ($colors as $color) { + if ($color !== null) { + $this->registerTableItem($this->colorTable, $color); + } + } + } + + /** + * Register fonts and colors. + * + * @param \PhpOffice\PhpWord\Style\AbstractStyle $style + */ + private function registerFontItems($style) + { + $defaultFont = Settings::getDefaultFontName(); + $defaultColor = Settings::DEFAULT_FONT_COLOR; + + if ($style instanceof Font) { + $this->registerTableItem($this->fontTable, $style->getName(), $defaultFont); + $this->registerTableItem($this->colorTable, $style->getColor(), $defaultColor); + $this->registerTableItem($this->colorTable, $style->getFgColor(), $defaultColor); + } + } + + /** + * Register individual font and color. + * + * @param array &$table + * @param string $value + * @param string $default + */ + private function registerTableItem(&$table, $value, $default = null) + { + if (in_array($value, $table) === false && $value !== null && $value != $default) { + $table[] = $value; + } + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Style/AbstractStyle.php b/PhpOffice/PhpWord/Writer/RTF/Style/AbstractStyle.php new file mode 100644 index 0000000..57aa6bb --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Style/AbstractStyle.php @@ -0,0 +1,29 @@ +sizes); + + // Page border measure + // 8 = from text, infront off; 32 = from edge, infront on; 40 = from edge, infront off + $content .= '\pgbrdropt32'; + + for ($i = 0; $i < $sizeCount; $i++) { + if ($this->sizes[$i] !== null) { + $color = null; + if (isset($this->colors[$i])) { + $color = $this->colors[$i]; + } + $content .= $this->writeSide($sides[$i], $this->sizes[$i], $color); + } + } + + return $content; + } + + /** + * Write side + * + * @param string $side + * @param int $width + * @param string $color + * @return string + */ + private function writeSide($side, $width, $color = '') + { + /** @var \PhpOffice\PhpWord\Writer\RTF $rtfWriter */ + $rtfWriter = $this->getParentWriter(); + $colorIndex = 0; + if ($rtfWriter !== null) { + $colorTable = $rtfWriter->getColorTable(); + $index = array_search($color, $colorTable); + if ($index !== false && $colorIndex !== null) { + $colorIndex = $index + 1; + } + } + + $content = ''; + + $content .= '\pgbrdr' . substr($side, 0, 1); + $content .= '\brdrs'; // Single-thickness border; @todo Get other type of border + $content .= '\brdrw' . round($width); // Width + $content .= '\brdrcf' . $colorIndex; // Color + $content .= '\brsp480'; // Space in twips between borders and the paragraph (24pt, following OOXML) + $content .= ' '; + + return $content; + } + + /** + * Set sizes. + * + * @param int[] $value + */ + public function setSizes($value) + { + $this->sizes = $value; + } + + /** + * Set colors. + * + * @param string[] $value + */ + public function setColors($value) + { + $this->colors = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Style/Font.php b/PhpOffice/PhpWord/Writer/RTF/Style/Font.php new file mode 100644 index 0000000..34f6c1a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Style/Font.php @@ -0,0 +1,89 @@ +getStyle(); + if (!$style instanceof FontStyle) { + return ''; + } + + $content = ''; + $content .= $this->getValueIf($style->isRTL(), '\rtlch'); + $content .= '\cf' . $this->colorIndex; + $content .= '\f' . $this->nameIndex; + + $size = $style->getSize(); + $content .= $this->getValueIf(is_numeric($size), '\fs' . round($size * 2)); + + $content .= $this->getValueIf($style->isBold(), '\b'); + $content .= $this->getValueIf($style->isItalic(), '\i'); + $content .= $this->getValueIf($style->getUnderline() != FontStyle::UNDERLINE_NONE, '\ul'); + $content .= $this->getValueIf($style->isStrikethrough(), '\strike'); + $content .= $this->getValueIf($style->isSuperScript(), '\super'); + $content .= $this->getValueIf($style->isSubScript(), '\sub'); + + return $content . ' '; + } + + /** + * Set font name index. + * + * + * @param int $value + */ + public function setNameIndex($value = 0) + { + $this->nameIndex = $value; + } + + /** + * Set font color index. + * + * @param int $value + */ + public function setColorIndex($value = 0) + { + $this->colorIndex = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Style/Indentation.php b/PhpOffice/PhpWord/Writer/RTF/Style/Indentation.php new file mode 100644 index 0000000..50e8ad9 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Style/Indentation.php @@ -0,0 +1,45 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) { + return ''; + } + + $content = '\fi' . round($style->getFirstLine()); + $content .= '\li' . round($style->getLeft()); + $content .= '\ri' . round($style->getRight()); + + return $content . ' '; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Style/Paragraph.php b/PhpOffice/PhpWord/Writer/RTF/Style/Paragraph.php new file mode 100644 index 0000000..9f8cf9d --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Style/Paragraph.php @@ -0,0 +1,130 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Paragraph) { + return ''; + } + + $alignments = array( + Jc::START => '\ql', + Jc::END => '\qr', + Jc::CENTER => '\qc', + Jc::BOTH => '\qj', + ); + + $spaceAfter = $style->getSpaceAfter(); + $spaceBefore = $style->getSpaceBefore(); + + $content = ''; + if ($this->nestedLevel == 0) { + $content .= '\pard\nowidctlpar '; + } + if (isset($alignments[$style->getAlignment()])) { + $content .= $alignments[$style->getAlignment()]; + } + $content .= $this->writeIndentation($style->getIndentation()); + $content .= $this->getValueIf($spaceBefore !== null, '\sb' . round($spaceBefore)); + $content .= $this->getValueIf($spaceAfter !== null, '\sa' . round($spaceAfter)); + $lineHeight = $style->getLineHeight(); + if ($lineHeight) { + $lineHeightAdjusted = (int) ($lineHeight * 240); + $content .= "\\sl$lineHeightAdjusted\\slmult1"; + } + if ($style->hasPageBreakBefore()) { + $content .= '\\page'; + } + + $styles = $style->getStyleValues(); + $content .= $this->writeTabs($styles['tabs']); + + return $content; + } + + /** + * Writes an \PhpOffice\PhpWord\Style\Indentation + * + * @param null|\PhpOffice\PhpWord\Style\Indentation $indent + * @return string + */ + private function writeIndentation($indent = null) + { + if (isset($indent) && $indent instanceof \PhpOffice\PhpWord\Style\Indentation) { + $writer = new Indentation($indent); + + return $writer->write(); + } + + return ''; + } + + /** + * Writes tabs + * + * @param \PhpOffice\PhpWord\Style\Tab[] $tabs + * @return string + */ + private function writeTabs($tabs = null) + { + $content = ''; + if (!empty($tabs)) { + foreach ($tabs as $tab) { + $styleWriter = new Tab($tab); + $content .= $styleWriter->write(); + } + } + + return $content; + } + + /** + * Set nested level. + * + * @param int $value + */ + public function setNestedLevel($value) + { + $this->nestedLevel = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Style/Section.php b/PhpOffice/PhpWord/Writer/RTF/Style/Section.php new file mode 100644 index 0000000..c680194 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Style/Section.php @@ -0,0 +1,70 @@ +getStyle(); + if (!$style instanceof SectionStyle) { + return ''; + } + + $content = ''; + + $content .= '\sectd '; + + // Size & margin + $content .= $this->getValueIf($style->getPageSizeW() !== null, '\pgwsxn' . round($style->getPageSizeW())); + $content .= $this->getValueIf($style->getPageSizeH() !== null, '\pghsxn' . round($style->getPageSizeH())); + $content .= ' '; + $content .= $this->getValueIf($style->getMarginTop() !== null, '\margtsxn' . round($style->getMarginTop())); + $content .= $this->getValueIf($style->getMarginRight() !== null, '\margrsxn' . round($style->getMarginRight())); + $content .= $this->getValueIf($style->getMarginBottom() !== null, '\margbsxn' . round($style->getMarginBottom())); + $content .= $this->getValueIf($style->getMarginLeft() !== null, '\marglsxn' . round($style->getMarginLeft())); + $content .= $this->getValueIf($style->getHeaderHeight() !== null, '\headery' . round($style->getHeaderHeight())); + $content .= $this->getValueIf($style->getFooterHeight() !== null, '\footery' . round($style->getFooterHeight())); + $content .= $this->getValueIf($style->getGutter() !== null, '\guttersxn' . round($style->getGutter())); + $content .= $this->getValueIf($style->getPageNumberingStart() !== null, '\pgnstarts' . $style->getPageNumberingStart() . '\pgnrestart'); + $content .= ' '; + + // Borders + if ($style->hasBorder()) { + $styleWriter = new Border($style); + $styleWriter->setParentWriter($this->getParentWriter()); + $styleWriter->setSizes($style->getBorderSize()); + $styleWriter->setColors($style->getBorderColor()); + $content .= $styleWriter->write(); + } + + return $content . PHP_EOL; + } +} diff --git a/PhpOffice/PhpWord/Writer/RTF/Style/Tab.php b/PhpOffice/PhpWord/Writer/RTF/Style/Tab.php new file mode 100644 index 0000000..a21b13d --- /dev/null +++ b/PhpOffice/PhpWord/Writer/RTF/Style/Tab.php @@ -0,0 +1,49 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Tab) { + return; + } + $tabs = array( + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_RIGHT => '\tqr', + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_CENTER => '\tqc', + \PhpOffice\PhpWord\Style\Tab::TAB_STOP_DECIMAL => '\tqdec', + ); + $content = ''; + if (isset($tabs[$style->getType()])) { + $content .= $tabs[$style->getType()]; + } + $content .= '\tx' . round($style->getPosition()); + + return $content; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007.php b/PhpOffice/PhpWord/Writer/Word2007.php new file mode 100644 index 0000000..eee215b --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007.php @@ -0,0 +1,334 @@ + array(), 'override' => array()); + + /** + * Document relationship + * + * @var array + */ + private $relationships = array(); + + /** + * Create new Word2007 writer + * + * @param \PhpOffice\PhpWord\PhpWord + */ + public function __construct(PhpWord $phpWord = null) + { + // Assign PhpWord + $this->setPhpWord($phpWord); + + // Create parts + $this->parts = array( + 'ContentTypes' => '[Content_Types].xml', + 'Rels' => '_rels/.rels', + 'DocPropsApp' => 'docProps/app.xml', + 'DocPropsCore' => 'docProps/core.xml', + 'DocPropsCustom' => 'docProps/custom.xml', + 'RelsDocument' => 'word/_rels/document.xml.rels', + 'Document' => 'word/document.xml', + 'Comments' => 'word/comments.xml', + 'Styles' => 'word/styles.xml', + 'Numbering' => 'word/numbering.xml', + 'Settings' => 'word/settings.xml', + 'WebSettings' => 'word/webSettings.xml', + 'FontTable' => 'word/fontTable.xml', + 'Theme' => 'word/theme/theme1.xml', + 'RelsPart' => '', + 'Header' => '', + 'Footer' => '', + 'Footnotes' => '', + 'Endnotes' => '', + 'Chart' => '', + ); + foreach (array_keys($this->parts) as $partName) { + $partClass = get_class($this) . '\\Part\\' . $partName; + if (class_exists($partClass)) { + /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $part Type hint */ + $part = new $partClass(); + $part->setParentWriter($this); + $this->writerParts[strtolower($partName)] = $part; + } + } + + // Set package paths + $this->mediaPaths = array('image' => 'word/media/', 'object' => 'word/embeddings/'); + } + + /** + * Save document by name. + * + * @param string $filename + */ + public function save($filename = null) + { + $filename = $this->getTempFile($filename); + $zip = $this->getZipArchive($filename); + $phpWord = $this->getPhpWord(); + + // Content types + $this->contentTypes['default'] = array( + 'rels' => 'application/vnd.openxmlformats-package.relationships+xml', + 'xml' => 'application/xml', + ); + + // Add section media files + $sectionMedia = Media::getElements('section'); + if (!empty($sectionMedia)) { + $this->addFilesToPackage($zip, $sectionMedia); + $this->registerContentTypes($sectionMedia); + foreach ($sectionMedia as $element) { + $this->relationships[] = $element; + } + } + + // Add header/footer media files & relations + $this->addHeaderFooterMedia($zip, 'header'); + $this->addHeaderFooterMedia($zip, 'footer'); + + // Add header/footer contents + $rId = Media::countElements('section') + 6; // @see Rels::writeDocRels for 6 first elements + $sections = $phpWord->getSections(); + foreach ($sections as $section) { + $this->addHeaderFooterContent($section, $zip, 'header', $rId); + $this->addHeaderFooterContent($section, $zip, 'footer', $rId); + } + + $this->addNotes($zip, $rId, 'footnote'); + $this->addNotes($zip, $rId, 'endnote'); + $this->addComments($zip, $rId); + $this->addChart($zip, $rId); + + // Write parts + foreach ($this->parts as $partName => $fileName) { + if ($fileName != '') { + $zip->addFromString($fileName, $this->getWriterPart($partName)->write()); + } + } + + // Close zip archive and cleanup temp file + $zip->close(); + $this->cleanupTempFile(); + } + + /** + * Get content types + * + * @return array + */ + public function getContentTypes() + { + return $this->contentTypes; + } + + /** + * Get content types + * + * @return array + */ + public function getRelationships() + { + return $this->relationships; + } + + /** + * Add header/footer media files, e.g. footer1.xml.rels. + * + * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip + * @param string $docPart + */ + private function addHeaderFooterMedia(ZipArchive $zip, $docPart) + { + $elements = Media::getElements($docPart); + if (!empty($elements)) { + foreach ($elements as $file => $media) { + if (count($media) > 0) { + if (!empty($media)) { + $this->addFilesToPackage($zip, $media); + $this->registerContentTypes($media); + } + + /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ + $writerPart = $this->getWriterPart('relspart')->setMedia($media); + $zip->addFromString("word/_rels/{$file}.xml.rels", $writerPart->write()); + } + } + } + } + + /** + * Add header/footer content. + * + * @param \PhpOffice\PhpWord\Element\Section &$section + * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip + * @param string $elmType header|footer + * @param int &$rId + */ + private function addHeaderFooterContent(Section &$section, ZipArchive $zip, $elmType, &$rId) + { + $getFunction = $elmType == 'header' ? 'getHeaders' : 'getFooters'; + $elmCount = ($section->getSectionId() - 1) * 3; + $elements = $section->$getFunction(); + /** @var \PhpOffice\PhpWord\Element\AbstractElement $element Type hint */ + foreach ($elements as &$element) { + $elmCount++; + $element->setRelationId(++$rId); + $elmFile = "{$elmType}{$elmCount}.xml"; // e.g. footer1.xml + $this->contentTypes['override']["/word/$elmFile"] = $elmType; + $this->relationships[] = array('target' => $elmFile, 'type' => $elmType, 'rID' => $rId); + + /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ + $writerPart = $this->getWriterPart($elmType)->setElement($element); + $zip->addFromString("word/$elmFile", $writerPart->write()); + } + } + + /** + * Add footnotes/endnotes + * + * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip + * @param int &$rId + * @param string $noteType + */ + private function addNotes(ZipArchive $zip, &$rId, $noteType = 'footnote') + { + $phpWord = $this->getPhpWord(); + $noteType = ($noteType == 'endnote') ? 'endnote' : 'footnote'; + $partName = "{$noteType}s"; + $method = 'get' . $partName; + $collection = $phpWord->$method(); + + // Add footnotes media files, relations, and contents + /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */ + if ($collection->countItems() > 0) { + $media = Media::getElements($noteType); + $this->addFilesToPackage($zip, $media); + $this->registerContentTypes($media); + $this->contentTypes['override']["/word/{$partName}.xml"] = $partName; + $this->relationships[] = array('target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId); + + // Write relationships file, e.g. word/_rels/footnotes.xml + if (!empty($media)) { + /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $writerPart Type hint */ + $writerPart = $this->getWriterPart('relspart')->setMedia($media); + $zip->addFromString("word/_rels/{$partName}.xml.rels", $writerPart->write()); + } + + // Write content file, e.g. word/footnotes.xml + $writerPart = $this->getWriterPart($partName)->setElements($collection->getItems()); + $zip->addFromString("word/{$partName}.xml", $writerPart->write()); + } + } + + /** + * Add comments + * + * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip + * @param int &$rId + */ + private function addComments(ZipArchive $zip, &$rId) + { + $phpWord = $this->getPhpWord(); + $collection = $phpWord->getComments(); + $partName = 'comments'; + + // Add comment relations and contents + /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collection Type hint */ + if ($collection->countItems() > 0) { + $this->relationships[] = array('target' => "{$partName}.xml", 'type' => $partName, 'rID' => ++$rId); + + // Write content file, e.g. word/comments.xml + $writerPart = $this->getWriterPart($partName)->setElements($collection->getItems()); + $zip->addFromString("word/{$partName}.xml", $writerPart->write()); + } + } + + /** + * Add chart. + * + * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip + * @param int &$rId + */ + private function addChart(ZipArchive $zip, &$rId) + { + $phpWord = $this->getPhpWord(); + + $collection = $phpWord->getCharts(); + $index = 0; + if ($collection->countItems() > 0) { + /** @var \PhpOffice\PhpWord\Element\Chart $chart */ + foreach ($collection->getItems() as $chart) { + $index++; + $rId++; + $filename = "charts/chart{$index}.xml"; + + // ContentTypes.xml + $this->contentTypes['override']["/word/{$filename}"] = 'chart'; + + // word/_rels/document.xml.rel + $this->relationships[] = array('target' => $filename, 'type' => 'chart', 'rID' => $rId); + + // word/charts/chartN.xml + $chart->setRelationId($rId); + $writerPart = $this->getWriterPart('Chart'); + $writerPart->setElement($chart); + $zip->addFromString("word/{$filename}", $writerPart->write()); + } + } + } + + /** + * Register content types for each media. + * + * @param array $media + */ + private function registerContentTypes($media) + { + foreach ($media as $medium) { + $mediumType = $medium['type']; + if ($mediumType == 'image') { + $extension = $medium['imageExtension']; + if (!isset($this->contentTypes['default'][$extension])) { + $this->contentTypes['default'][$extension] = $medium['imageType']; + } + } elseif ($mediumType == 'object') { + if (!isset($this->contentTypes['default']['bin'])) { + $this->contentTypes['default']['bin'] = 'application/vnd.openxmlformats-officedocument.oleObject'; + } + } + } + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/AbstractElement.php b/PhpOffice/PhpWord/Writer/Word2007/Element/AbstractElement.php new file mode 100644 index 0000000..63f45a7 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/AbstractElement.php @@ -0,0 +1,227 @@ +xmlWriter = $xmlWriter; + $this->element = $element; + $this->withoutP = $withoutP; + } + + /** + * Get XML Writer + * + * @return \PhpOffice\Common\XMLWriter + */ + protected function getXmlWriter() + { + return $this->xmlWriter; + } + + /** + * Get element + * + * @return \PhpOffice\PhpWord\Element\AbstractElement + */ + protected function getElement() + { + return $this->element; + } + + /** + * Start w:p DOM element. + * + * @uses \PhpOffice\PhpWord\Writer\Word2007\Element\PageBreak::write() + */ + protected function startElementP() + { + if (!$this->withoutP) { + $this->xmlWriter->startElement('w:p'); + // Paragraph style + if (method_exists($this->element, 'getParagraphStyle')) { + $this->writeParagraphStyle(); + } + } + $this->writeCommentRangeStart(); + } + + /** + * End w:p DOM element. + */ + protected function endElementP() + { + $this->writeCommentRangeEnd(); + if (!$this->withoutP) { + $this->xmlWriter->endElement(); // w:p + } + } + + /** + * Writes the w:commentRangeStart DOM element + */ + protected function writeCommentRangeStart() + { + if ($this->element->getCommentRangeStart() != null) { + $comment = $this->element->getCommentRangeStart(); + //only set the ID if it is not yet set, otherwise it will overwrite it + if ($comment->getElementId() == null) { + $comment->setElementId(); + } + + $this->xmlWriter->writeElementBlock('w:commentRangeStart', array('w:id' => $comment->getElementId())); + } + } + + /** + * Writes the w:commentRangeEnd DOM element + */ + protected function writeCommentRangeEnd() + { + if ($this->element->getCommentRangeEnd() != null) { + $comment = $this->element->getCommentRangeEnd(); + //only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen + if ($comment->getElementId() == null) { + $comment->setElementId(); // @codeCoverageIgnore + } // @codeCoverageIgnore + + $this->xmlWriter->writeElementBlock('w:commentRangeEnd', array('w:id' => $comment->getElementId())); + $this->xmlWriter->startElement('w:r'); + $this->xmlWriter->writeElementBlock('w:commentReference', array('w:id' => $comment->getElementId())); + $this->xmlWriter->endElement(); + } elseif ($this->element->getCommentRangeStart() != null && $this->element->getCommentRangeStart()->getEndElement() == null) { + $comment = $this->element->getCommentRangeStart(); + //only set the ID if it is not yet set, otherwise it will overwrite it, this should normally not happen + if ($comment->getElementId() == null) { + $comment->setElementId(); // @codeCoverageIgnore + } // @codeCoverageIgnore + + $this->xmlWriter->writeElementBlock('w:commentRangeEnd', array('w:id' => $comment->getElementId())); + $this->xmlWriter->startElement('w:r'); + $this->xmlWriter->writeElementBlock('w:commentReference', array('w:id' => $comment->getElementId())); + $this->xmlWriter->endElement(); + } + } + + /** + * Write ending. + */ + protected function writeParagraphStyle() + { + $this->writeTextStyle('Paragraph'); + } + + /** + * Write ending. + */ + protected function writeFontStyle() + { + $this->writeTextStyle('Font'); + } + + /** + * Write text style. + * + * @param string $styleType Font|Paragraph + */ + private function writeTextStyle($styleType) + { + $method = "get{$styleType}Style"; + $class = "PhpOffice\\PhpWord\\Writer\\Word2007\\Style\\{$styleType}"; + $styleObject = $this->element->$method(); + + /** @var \PhpOffice\PhpWord\Writer\Word2007\Style\AbstractStyle $styleWriter Type Hint */ + $styleWriter = new $class($this->xmlWriter, $styleObject); + if (method_exists($styleWriter, 'setIsInline')) { + $styleWriter->setIsInline(true); + } + + $styleWriter->write(); + } + + /** + * Convert text to valid format + * + * @param string $text + * @return string + */ + protected function getText($text) + { + return CommonText::controlCharacterPHP2OOXML($text); + } + + /** + * Write an XML text, this will call text() or writeRaw() depending on the value of Settings::isOutputEscapingEnabled() + * + * @param string $content The text string to write + * @return bool Returns true on success or false on failure + */ + protected function writeText($content) + { + if (Settings::isOutputEscapingEnabled()) { + return $this->getXmlWriter()->text($content); + } + + return $this->getXmlWriter()->writeRaw($content); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Bookmark.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Bookmark.php new file mode 100644 index 0000000..04eaacf --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Bookmark.php @@ -0,0 +1,49 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Bookmark) { + return; + } + + $rId = $element->getRelationId(); + + $xmlWriter->startElement('w:bookmarkStart'); + $xmlWriter->writeAttribute('w:id', $rId); + $xmlWriter->writeAttribute('w:name', $element->getName()); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:bookmarkEnd'); + $xmlWriter->writeAttribute('w:id', $rId); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Chart.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Chart.php new file mode 100644 index 0000000..f88ca2d --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Chart.php @@ -0,0 +1,76 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof ChartElement) { + return; + } + + $rId = $element->getRelationId(); + $style = $element->getStyle(); + + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + } + $this->writeCommentRangeStart(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:drawing'); + $xmlWriter->startElement('wp:inline'); + + // EMU + $xmlWriter->writeElementBlock('wp:extent', array('cx' => $style->getWidth(), 'cy' => $style->getHeight())); + $xmlWriter->writeElementBlock('wp:docPr', array('id' => $rId, 'name' => "Chart{$rId}")); + + $xmlWriter->startElement('a:graphic'); + $xmlWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $xmlWriter->startElement('a:graphicData'); + $xmlWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + + $xmlWriter->startElement('c:chart'); + $xmlWriter->writeAttribute('r:id', "rId{$rId}"); + $xmlWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->endElement(); // c:chart + + $xmlWriter->endElement(); // a:graphicData + $xmlWriter->endElement(); // a:graphic + + $xmlWriter->endElement(); // wp:inline + $xmlWriter->endElement(); // w:drawing + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/CheckBox.php b/PhpOffice/PhpWord/Writer/Word2007/Element/CheckBox.php new file mode 100644 index 0000000..05692a0 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/CheckBox.php @@ -0,0 +1,90 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\CheckBox) { + return; + } + + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->startElement('w:ffData'); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', $this->getText($element->getName())); + $xmlWriter->endElement(); //w:name + $xmlWriter->writeAttribute('w:enabled', ''); + $xmlWriter->startElement('w:calcOnExit'); + $xmlWriter->writeAttribute('w:val', '0'); + $xmlWriter->endElement(); //w:calcOnExit + $xmlWriter->startElement('w:checkBox'); + $xmlWriter->writeAttribute('w:sizeAuto', ''); + $xmlWriter->startElement('w:default'); + $xmlWriter->writeAttribute('w:val', 0); + $xmlWriter->endElement(); //w:default + $xmlWriter->endElement(); //w:checkBox + $xmlWriter->endElement(); // w:ffData + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text(' FORMCHECKBOX '); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'separate'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + + $this->writeFontStyle(); + + $xmlWriter->startElement('w:t'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $this->writeText($this->getText($element->getText())); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Container.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Container.php new file mode 100644 index 0000000..892da05 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Container.php @@ -0,0 +1,92 @@ +getElement(); + if (!$container instanceof ContainerElement) { + return; + } + $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); + $withoutP = in_array($containerClass, array('TextRun', 'Footnote', 'Endnote', 'ListItemRun')); + $xmlWriter = $this->getXmlWriter(); + + // Loop through elements + $elements = $container->getElements(); + $elementClass = ''; + foreach ($elements as $element) { + $elementClass = $this->writeElement($xmlWriter, $element, $withoutP); + } + + // Special case for Cell: They have to contain a w:p element at the end. + // The $elementClass contains the last element name. If it's empty string + // or Table, the last element is not w:p + $writeLastTextBreak = ($containerClass == 'Cell') && ($elementClass == '' || $elementClass == 'Table'); + if ($writeLastTextBreak) { + $writerClass = $this->namespace . '\\TextBreak'; + /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ + $writer = new $writerClass($xmlWriter, new TextBreakElement(), $withoutP); + $writer->write(); + } + } + + /** + * Write individual element + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\AbstractElement $element + * @param bool $withoutP + * @return string + */ + private function writeElement(XMLWriter $xmlWriter, Element $element, $withoutP) + { + $elementClass = substr(get_class($element), strrpos(get_class($element), '\\') + 1); + $writerClass = $this->namespace . '\\' . $elementClass; + + if (class_exists($writerClass)) { + /** @var \PhpOffice\PhpWord\Writer\Word2007\Element\AbstractElement $writer Type hint */ + $writer = new $writerClass($xmlWriter, $element, $withoutP); + $writer->write(); + } + + return $elementClass; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Endnote.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Endnote.php new file mode 100644 index 0000000..9a2eb3f --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Endnote.php @@ -0,0 +1,33 @@ +getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Field) { + return; + } + + $methodName = 'write' . ucfirst(strtolower($element->getType())); + if (method_exists($this, $methodName)) { + $this->$methodName($element); + } else { + $this->writeDefault($element); + } + } + + private function writeDefault(\PhpOffice\PhpWord\Element\Field $element) + { + $xmlWriter = $this->getXmlWriter(); + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $instruction = ' ' . $element->getType() . ' '; + if ($element->getText() != null) { + if (is_string($element->getText())) { + $instruction .= '"' . $element->getText() . '" '; + $instruction .= $this->buildPropertiesAndOptions($element); + } else { + $instruction .= '"'; + } + } else { + $instruction .= $this->buildPropertiesAndOptions($element); + } + $xmlWriter->startElement('w:r'); + $this->writeFontStyle(); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text($instruction); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + + if ($element->getText() != null) { + if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) { + $containerWriter = new Container($xmlWriter, $element->getText(), true); + $containerWriter->write(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->text('"' . $this->buildPropertiesAndOptions($element)); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text(' '); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + } + } + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'separate'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:noProof'); + $xmlWriter->endElement(); // w:noProof + $xmlWriter->endElement(); // w:rPr + $xmlWriter->writeElement('w:t', $element->getText() != null && is_string($element->getText()) ? $element->getText() : '1'); + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } + + /** + * Writes a macrobutton field + * + * //TODO A lot of code duplication with general method, should maybe be refactored + * @param \PhpOffice\PhpWord\Element\Field $element + */ + protected function writeMacrobutton(\PhpOffice\PhpWord\Element\Field $element) + { + $xmlWriter = $this->getXmlWriter(); + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $instruction = ' ' . $element->getType() . ' ' . $this->buildPropertiesAndOptions($element); + if (is_string($element->getText())) { + $instruction .= $element->getText() . ' '; + } + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text($instruction); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + + if ($element->getText() != null) { + if ($element->getText() instanceof \PhpOffice\PhpWord\Element\TextRun) { + $containerWriter = new Container($xmlWriter, $element->getText(), true); + $containerWriter->write(); + } + } + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } + + private function buildPropertiesAndOptions(\PhpOffice\PhpWord\Element\Field $element) + { + $propertiesAndOptions = ''; + $properties = $element->getProperties(); + foreach ($properties as $propkey => $propval) { + switch ($propkey) { + case 'format': + $propertiesAndOptions .= '\* ' . $propval . ' '; + break; + case 'numformat': + $propertiesAndOptions .= '\# ' . $propval . ' '; + break; + case 'dateformat': + $propertiesAndOptions .= '\@ "' . $propval . '" '; + break; + case 'macroname': + $propertiesAndOptions .= $propval . ' '; + break; + default: + $propertiesAndOptions .= '"' . $propval . '" '; + break; + } + } + + $options = $element->getOptions(); + foreach ($options as $option) { + switch ($option) { + case 'PreserveFormat': + $propertiesAndOptions .= '\* MERGEFORMAT '; + break; + case 'LunarCalendar': + $propertiesAndOptions .= '\h '; + break; + case 'SakaEraCalendar': + $propertiesAndOptions .= '\s '; + break; + case 'LastUsedFormat': + $propertiesAndOptions .= '\l '; + break; + case 'Bold': + $propertiesAndOptions .= '\b '; + break; + case 'Italic': + $propertiesAndOptions .= '\i '; + break; + default: + $propertiesAndOptions .= $option . ' '; + } + } + + return $propertiesAndOptions; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Footnote.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Footnote.php new file mode 100644 index 0000000..56a5332 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Footnote.php @@ -0,0 +1,60 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Footnote) { + return; + } + + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rStyle'); + $xmlWriter->writeAttribute('w:val', ucfirst($this->referenceType)); + $xmlWriter->endElement(); // w:rStyle + $xmlWriter->endElement(); // w:rPr + $xmlWriter->startElement("w:{$this->referenceType}"); + $xmlWriter->writeAttribute('w:id', $element->getRelationId()); + $xmlWriter->endElement(); // w:$referenceType + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/FormField.php b/PhpOffice/PhpWord/Writer/Word2007/Element/FormField.php new file mode 100644 index 0000000..b59cf58 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/FormField.php @@ -0,0 +1,170 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof FormFieldElement) { + return; + } + + $type = $element->getType(); + $instructions = array('textinput' => 'FORMTEXT', 'checkbox' => 'FORMCHECKBOX', 'dropdown' => 'FORMDROPDOWN'); + $instruction = $instructions[$type]; + $writeFormField = "write{$type}"; + $name = $element->getName(); + if ($name === null) { + $name = $type . $element->getElementId(); + } + $value = $element->getValue(); + if ($value === null) { + $value = str_repeat(' ', self::FILLER_LENGTH); + } + + $this->startElementP(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->startElement('w:ffData'); + $xmlWriter->writeElementBlock('w:enabled', 'w:val', 1); + $xmlWriter->writeElementBlock('w:name', 'w:val', $name); + $xmlWriter->writeElementBlock('w:calcOnExit', 'w:val', 0); + $this->$writeFormField($xmlWriter, $element); + $xmlWriter->endElement(); // w:ffData + $xmlWriter->endElement(); // w:fldChar + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $this->writeFontStyle(); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text("{$instruction}"); + $xmlWriter->endElement(); // w:instrText + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $this->writeFontStyle(); + $xmlWriter->writeElementBlock('w:fldChar', 'w:fldCharType', 'separate'); + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $this->writeFontStyle(); + $xmlWriter->startElement('w:t'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $this->writeText($value); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $this->writeFontStyle(); + $xmlWriter->writeElementBlock('w:fldChar', 'w:fldCharType', 'end'); + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } + + /** + * Write textinput. + * + * @see http://www.datypic.com/sc/ooxml/t-w_CT_FFTextInput.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\FormField $element + */ + private function writeTextInput(XMLWriter $xmlWriter, FormFieldElement $element) + { + $default = $element->getDefault(); + + $xmlWriter->startElement('w:textInput'); + $xmlWriter->writeElementBlock('w:default', 'w:val', $default); + $xmlWriter->endElement(); + } + + /** + * Write checkbox. + * + * @see http://www.datypic.com/sc/ooxml/t-w_CT_FFCheckBox.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\FormField $element + */ + private function writeCheckBox(XMLWriter $xmlWriter, FormFieldElement $element) + { + $default = $element->getDefault() ? 1 : 0; + $value = $element->getValue(); + if ($value == null) { + $value = $default; + } + $value = $value ? 1 : 0; + + $xmlWriter->startElement('w:checkBox'); + $xmlWriter->writeElementBlock('w:sizeAuto', 'w:val', ''); + $xmlWriter->writeElementBlock('w:default', 'w:val', $default); + $xmlWriter->writeElementBlock('w:checked', 'w:val', $value); + $xmlWriter->endElement(); + } + + /** + * Write dropdown. + * + * @see http://www.datypic.com/sc/ooxml/t-w_CT_FFDDList.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\FormField $element + */ + private function writeDropDown(XMLWriter $xmlWriter, FormFieldElement $element) + { + $default = $element->getDefault(); + $value = $element->getValue(); + if ($value == null) { + $value = $default; + } + $entries = $element->getEntries(); + + $xmlWriter->startElement('w:ddList'); + $xmlWriter->writeElementBlock('w:result', 'w:val', $value); + $xmlWriter->writeElementBlock('w:default', 'w:val', $default); + foreach ($entries as $entry) { + if ($entry == null || $entry == '') { + $entry = str_repeat(' ', self::FILLER_LENGTH); + } + $xmlWriter->writeElementBlock('w:listEntry', 'w:val', $entry); + } + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Image.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Image.php new file mode 100644 index 0000000..5bebb89 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Image.php @@ -0,0 +1,127 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof ImageElement) { + return; + } + + if ($element->isWatermark()) { + $this->writeWatermark($xmlWriter, $element); + } else { + $this->writeImage($xmlWriter, $element); + } + } + + /** + * Write image element. + */ + private function writeImage(XMLWriter $xmlWriter, ImageElement $element) + { + $rId = $element->getRelationId() + ($element->isInSection() ? 6 : 0); + $style = $element->getStyle(); + $styleWriter = new ImageStyleWriter($xmlWriter, $style); + + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + $styleWriter->writeAlignment(); + } + $this->writeCommentRangeStart(); + + $xmlWriter->startElement('w:r'); + + // Write position + $position = $style->getPosition(); + if ($position && $style->getWrap() == FrameStyle::WRAP_INLINE) { + $fontStyle = new FontStyle('text'); + $fontStyle->setPosition($position); + $fontStyleWriter = new FontStyleWriter($xmlWriter, $fontStyle); + $fontStyleWriter->write(); + } + + $xmlWriter->startElement('w:pict'); + $xmlWriter->startElement('v:shape'); + $xmlWriter->writeAttribute('type', '#_x0000_t75'); + + $styleWriter->write(); + + $xmlWriter->startElement('v:imagedata'); + $xmlWriter->writeAttribute('r:id', 'rId' . $rId); + $xmlWriter->writeAttribute('o:title', ''); + $xmlWriter->endElement(); // v:imagedata + + $xmlWriter->endElement(); // v:shape + $xmlWriter->endElement(); // w:pict + $xmlWriter->endElement(); // w:r + + $this->endElementP(); + } + + /** + * Write watermark element. + */ + private function writeWatermark(XMLWriter $xmlWriter, ImageElement $element) + { + $rId = $element->getRelationId(); + $style = $element->getStyle(); + $style->setPositioning('absolute'); + $styleWriter = new ImageStyleWriter($xmlWriter, $style); + + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + } + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:pict'); + $xmlWriter->startElement('v:shape'); + $xmlWriter->writeAttribute('type', '#_x0000_t75'); + + $styleWriter->write(); + + $xmlWriter->startElement('v:imagedata'); + $xmlWriter->writeAttribute('r:id', 'rId' . $rId); + $xmlWriter->writeAttribute('o:title', ''); + $xmlWriter->endElement(); // v:imagedata + $xmlWriter->endElement(); // v:shape + $xmlWriter->endElement(); // w:pict + $xmlWriter->endElement(); // w:r + if (!$this->withoutP) { + $xmlWriter->endElement(); // w:p + } + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Line.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Line.php new file mode 100644 index 0000000..a114be6 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Line.php @@ -0,0 +1,88 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof LineElement) { + return; + } + + $style = $element->getStyle(); + $styleWriter = new LineStyleWriter($xmlWriter, $style); + + $elementId = $element->getElementIndex(); + + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + $styleWriter->writeAlignment(); + } + $this->writeCommentRangeStart(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:pict'); + + // Shapetype could be defined for each line separately, but then a unique id would be necessary + if ($elementId == 1) { + $xmlWriter->startElement('v:shapetype'); + $xmlWriter->writeAttribute('id', '_x0000_t32'); + $xmlWriter->writeAttribute('coordsize', '21600,21600'); + $xmlWriter->writeAttribute('o:spt', '32'); + $xmlWriter->writeAttribute('o:oned', 't'); + $xmlWriter->writeAttribute('path', 'm,l21600,21600e'); + $xmlWriter->writeAttribute('filled', 'f'); + $xmlWriter->startElement('v:path'); + $xmlWriter->writeAttribute('arrowok', 't'); + $xmlWriter->writeAttribute('fillok', 'f'); + $xmlWriter->writeAttribute('o:connecttype', 'none'); + $xmlWriter->endElement(); // v:path + $xmlWriter->startElement('o:lock'); + $xmlWriter->writeAttribute('v:ext', 'edit'); + $xmlWriter->writeAttribute('shapetype', 't'); + $xmlWriter->endElement(); // o:lock + $xmlWriter->endElement(); // v:shapetype + } + + $xmlWriter->startElement('v:shape'); + $xmlWriter->writeAttribute('id', sprintf('_x0000_s1%1$03d', $elementId)); + $xmlWriter->writeAttribute('type', '#_x0000_t32'); //type should correspond to shapetype id + + $styleWriter->write(); + $styleWriter->writeStroke(); + + $xmlWriter->endElement(); // v:shape + + $xmlWriter->endElement(); // w:pict + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Link.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Link.php new file mode 100644 index 0000000..f0adf85 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Link.php @@ -0,0 +1,62 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Link) { + return; + } + + $rId = $element->getRelationId() + ($element->isInSection() ? 6 : 0); + + $this->startElementP(); + + $xmlWriter->startElement('w:hyperlink'); + if ($element->isInternal()) { + $xmlWriter->writeAttribute('w:anchor', $element->getSource()); + } else { + $xmlWriter->writeAttribute('r:id', 'rId' . $rId); + } + $xmlWriter->writeAttribute('w:history', '1'); + $xmlWriter->startElement('w:r'); + + $this->writeFontStyle(); + + $xmlWriter->startElement('w:t'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $this->writeText($element->getText()); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + $xmlWriter->endElement(); // w:hyperlink + + $this->endElementP(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/ListItem.php b/PhpOffice/PhpWord/Writer/Word2007/Element/ListItem.php new file mode 100644 index 0000000..ef738e1 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/ListItem.php @@ -0,0 +1,67 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\ListItem) { + return; + } + + $textObject = $element->getTextObject(); + + $styleWriter = new ParagraphStyleWriter($xmlWriter, $textObject->getParagraphStyle()); + $styleWriter->setWithoutPPR(true); + $styleWriter->setIsInline(true); + + $xmlWriter->startElement('w:p'); + + $xmlWriter->startElement('w:pPr'); + $styleWriter->write(); + + $xmlWriter->startElement('w:numPr'); + $xmlWriter->startElement('w:ilvl'); + $xmlWriter->writeAttribute('w:val', $element->getDepth()); + $xmlWriter->endElement(); // w:ilvl + $xmlWriter->startElement('w:numId'); + $xmlWriter->writeAttribute('w:val', $element->getStyle()->getNumId()); + $xmlWriter->endElement(); // w:numId + $xmlWriter->endElement(); // w:numPr + + $xmlWriter->endElement(); // w:pPr + + $elementWriter = new Text($xmlWriter, $textObject, true); + $elementWriter->write(); + + $xmlWriter->endElement(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/ListItemRun.php b/PhpOffice/PhpWord/Writer/Word2007/Element/ListItemRun.php new file mode 100644 index 0000000..fda2b07 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/ListItemRun.php @@ -0,0 +1,87 @@ +getElement(); + + if (!$element instanceof ListItemRunElement) { + return; + } + + $this->writeParagraph($element); + } + + private function writeParagraph(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:p'); + + $this->writeParagraphProperties($element); + + $containerWriter = new Container($xmlWriter, $element); + $containerWriter->write(); + + $xmlWriter->endElement(); // w:p + } + + private function writeParagraphProperties(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:pPr'); + + $styleWriter = new ParagraphStyleWriter($xmlWriter, $element->getParagraphStyle()); + $styleWriter->setIsInline(true); + $styleWriter->setWithoutPPR(true); + $styleWriter->write(); + + $this->writeParagraphPropertiesNumbering($element); + + $xmlWriter->endElement(); // w:pPr + } + + private function writeParagraphPropertiesNumbering(ListItemRunElement $element) + { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:numPr'); + + $xmlWriter->writeElementBlock('w:ilvl', array( + 'w:val' => $element->getDepth(), + )); + + $xmlWriter->writeElementBlock('w:numId', array( + 'w:val' => $element->getStyle()->getNumId(), + )); + + $xmlWriter->endElement(); // w:numPr + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/OLEObject.php b/PhpOffice/PhpWord/Writer/Word2007/Element/OLEObject.php new file mode 100644 index 0000000..c55ff6c --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/OLEObject.php @@ -0,0 +1,88 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\OLEObject) { + return; + } + + $rIdObject = $element->getRelationId() + ($element->isInSection() ? 6 : 0); + $rIdImage = $element->getImageRelationId() + ($element->isInSection() ? 6 : 0); + $shapeId = md5($rIdObject . '_' . $rIdImage); + $objectId = $element->getRelationId() + 1325353440; + + $style = $element->getStyle(); + $styleWriter = new ImageStyleWriter($xmlWriter, $style); + + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + $styleWriter->writeAlignment(); + } + $this->writeCommentRangeStart(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:object'); + $xmlWriter->writeAttribute('w:dxaOrig', '249'); + $xmlWriter->writeAttribute('w:dyaOrig', '160'); + + // Icon + $xmlWriter->startElement('v:shape'); + $xmlWriter->writeAttribute('id', $shapeId); + $xmlWriter->writeAttribute('type', '#_x0000_t75'); + $xmlWriter->writeAttribute('style', 'width:104px;height:67px'); + $xmlWriter->writeAttribute('o:ole', ''); + + $xmlWriter->startElement('v:imagedata'); + $xmlWriter->writeAttribute('r:id', 'rId' . $rIdImage); + $xmlWriter->writeAttribute('o:title', ''); + $xmlWriter->endElement(); // v:imagedata + + $xmlWriter->endElement(); // v:shape + + // Object + $xmlWriter->startElement('o:OLEObject'); + $xmlWriter->writeAttribute('Type', 'Embed'); + $xmlWriter->writeAttribute('ProgID', 'Package'); + $xmlWriter->writeAttribute('ShapeID', $shapeId); + $xmlWriter->writeAttribute('DrawAspect', 'Icon'); + $xmlWriter->writeAttribute('ObjectID', '_' . $objectId); + $xmlWriter->writeAttribute('r:id', 'rId' . $rIdObject); + $xmlWriter->endElement(); // o:OLEObject + + $xmlWriter->endElement(); // w:object + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/PageBreak.php b/PhpOffice/PhpWord/Writer/Word2007/Element/PageBreak.php new file mode 100644 index 0000000..0801e4d --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/PageBreak.php @@ -0,0 +1,44 @@ +getXmlWriter(); + + $xmlWriter->startElement('w:p'); + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:br'); + $xmlWriter->writeAttribute('w:type', 'page'); + $xmlWriter->endElement(); // w:br + $xmlWriter->endElement(); // w:r + $xmlWriter->endElement(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php b/PhpOffice/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php new file mode 100644 index 0000000..5563f60 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php @@ -0,0 +1,60 @@ +attributes['w:val'] = $value; + } + + /** + * @since 0.13.0 + * + * @return string + */ + final public function getName() + { + return $this->name; + } + + /** + * @since 0.13.0 + * + * @return string[] + */ + final public function getAttributes() + { + return $this->attributes; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/PreserveText.php b/PhpOffice/PhpWord/Writer/Word2007/Element/PreserveText.php new file mode 100644 index 0000000..94ce6ed --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/PreserveText.php @@ -0,0 +1,91 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\PreserveText) { + return; + } + + $texts = $element->getText(); + if (!is_array($texts)) { + $texts = array($texts); + } + + $this->startElementP(); + + foreach ($texts as $text) { + if (substr($text, 0, 1) == '{') { + $text = substr($text, 1, -1); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + + $this->writeFontStyle(); + + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $this->writeText($text); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'separate'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } else { + $xmlWriter->startElement('w:r'); + + $this->writeFontStyle(); + + $xmlWriter->startElement('w:t'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $this->writeText($this->getText($text)); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } + } + + $this->endElementP(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/SDT.php b/PhpOffice/PhpWord/Writer/Word2007/Element/SDT.php new file mode 100644 index 0000000..edf89b5 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/SDT.php @@ -0,0 +1,137 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof SDTElement) { + return; + } + $type = $element->getType(); + $writeFormField = "write{$type}"; + $alias = $element->getAlias(); + $tag = $element->getTag(); + $value = $element->getValue(); + if ($value === null) { + $value = 'Pick value'; + } + + $this->startElementP(); + + $xmlWriter->startElement('w:sdt'); + + // Properties + $xmlWriter->startElement('w:sdtPr'); + $xmlWriter->writeElementIf($alias != null, 'w:alias', 'w:val', $alias); + $xmlWriter->writeElementBlock('w:lock', 'w:val', 'sdtLocked'); + $xmlWriter->writeElementBlock('w:id', 'w:val', rand(100000000, 999999999)); + $xmlWriter->writeElementIf($tag != null, 'w:tag', 'w:val', $tag); + $this->$writeFormField($xmlWriter, $element); + $xmlWriter->endElement(); // w:sdtPr + + // Content + $xmlWriter->startElement('w:sdtContent'); + $xmlWriter->startElement('w:r'); + $xmlWriter->writeElement('w:t', $value); + $xmlWriter->endElement(); // w:r + $xmlWriter->endElement(); // w:sdtContent + + $xmlWriter->endElement(); // w:sdt + + $this->endElementP(); // w:p + } + + /** + * Write text. + * + * @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtText.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writePlainText(XMLWriter $xmlWriter) + { + $xmlWriter->startElement('w:text'); + $xmlWriter->endElement(); // w:text + } + + /** + * Write combo box. + * + * @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtComboBox.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\SDT $element + */ + private function writeComboBox(XMLWriter $xmlWriter, SDTElement $element) + { + $type = $element->getType(); + $listItems = $element->getListItems(); + + $xmlWriter->startElement("w:{$type}"); + foreach ($listItems as $key => $val) { + $xmlWriter->writeElementBlock('w:listItem', array('w:value' => $key, 'w:displayText' => $val)); + } + $xmlWriter->endElement(); // w:{$type} + } + + /** + * Write drop down list. + * + * @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtDropDownList.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\SDT $element + */ + private function writeDropDownList(XMLWriter $xmlWriter, SDTElement $element) + { + $this->writeComboBox($xmlWriter, $element); + } + + /** + * Write date. + * + * @see http://www.datypic.com/sc/ooxml/t-w_CT_SdtDate.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\SDT $element + */ + private function writeDate(XMLWriter $xmlWriter, SDTElement $element) + { + $type = $element->getType(); + + $xmlWriter->startElement("w:{$type}"); + $xmlWriter->writeElementBlock('w:dateFormat', 'w:val', 'd/M/yyyy'); + $xmlWriter->writeElementBlock('w:lid', 'w:val', 'en-US'); + $xmlWriter->writeElementBlock('w:storeMappedDataAs', 'w:val', 'dateTime'); + $xmlWriter->writeElementBlock('w:calendar', 'w:val', 'gregorian'); + $xmlWriter->endElement(); // w:date + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Shape.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Shape.php new file mode 100644 index 0000000..250d5c1 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Shape.php @@ -0,0 +1,169 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof ShapeElement) { + return; + } + + $style = $element->getStyle(); + $styleWriter = new ShapeStyleWriter($xmlWriter, $style); + + $type = $element->getType(); + if ($type == 'rect' && $style->getRoundness() !== null) { + $type = 'roundrect'; + } + $method = "write{$type}"; + + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + } + $this->writeCommentRangeStart(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:pict'); + $xmlWriter->startElement("v:{$type}"); + + // Element style + if (method_exists($this, $method)) { + $this->$method($xmlWriter, $style); + } + + // Child style + $styleWriter->write(); + + $xmlWriter->endElement(); // v:$type + $xmlWriter->endElement(); // w:pict + $xmlWriter->endElement(); // w:r + + $this->endElementP(); // w:p + } + + /** + * Write arc. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Shape $style + */ + private function writeArc(XMLWriter $xmlWriter, ShapeStyle $style) + { + $points = $this->getPoints('arc', $style->getPoints()); + + $xmlWriter->writeAttributeIf($points['start'] !== null, 'startAngle', $points['start']); + $xmlWriter->writeAttributeIf($points['end'] !== null, 'endAngle', $points['end']); + } + + /** + * Write curve. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Shape $style + */ + private function writeCurve(XMLWriter $xmlWriter, ShapeStyle $style) + { + $points = $this->getPoints('curve', $style->getPoints()); + + $this->writeLine($xmlWriter, $style); + $xmlWriter->writeAttributeIf($points['point1'] !== null, 'control1', $points['point1']); + $xmlWriter->writeAttributeIf($points['point2'] !== null, 'control2', $points['point2']); + } + + /** + * Write line. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Shape $style + */ + private function writeLine(XMLWriter $xmlWriter, ShapeStyle $style) + { + $points = $this->getPoints('line', $style->getPoints()); + + $xmlWriter->writeAttributeIf($points['start'] !== null, 'from', $points['start']); + $xmlWriter->writeAttributeIf($points['end'] !== null, 'to', $points['end']); + } + + /** + * Write polyline. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Shape $style + */ + private function writePolyline(XMLWriter $xmlWriter, ShapeStyle $style) + { + $xmlWriter->writeAttributeIf($style->getPoints() !== null, 'points', $style->getPoints()); + } + + /** + * Write rectangle. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Shape $style + */ + private function writeRoundRect(XMLWriter $xmlWriter, ShapeStyle $style) + { + $xmlWriter->writeAttribute('arcsize', $style->getRoundness()); + } + + /** + * Set points + * + * @param string $type + * @param string $value + * @return array + */ + private function getPoints($type, $value) + { + $points = array(); + + switch ($type) { + case 'arc': + case 'line': + $points = explode(' ', $value); + list($start, $end) = array_pad($points, 2, null); + $points = array('start' => $start, 'end' => $end); + break; + case 'curve': + $points = explode(' ', $value); + list($start, $end, $point1, $point2) = array_pad($points, 4, null); + $points = array('start' => $start, 'end' => $end, 'point1' => $point1, 'point2' => $point2); + break; + } + + return $points; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/TOC.php b/PhpOffice/PhpWord/Writer/Word2007/Element/TOC.php new file mode 100644 index 0000000..94437cb --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/TOC.php @@ -0,0 +1,208 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof TOCElement) { + return; + } + + $titles = $element->getTitles(); + $writeFieldMark = true; + + foreach ($titles as $title) { + $this->writeTitle($xmlWriter, $element, $title, $writeFieldMark); + if ($writeFieldMark) { + $writeFieldMark = false; + } + } + + $xmlWriter->startElement('w:p'); + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } + + /** + * Write title + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\TOC $element + * @param \PhpOffice\PhpWord\Element\Title $title + * @param bool $writeFieldMark + */ + private function writeTitle(XMLWriter $xmlWriter, TOCElement $element, $title, $writeFieldMark) + { + $tocStyle = $element->getStyleTOC(); + $fontStyle = $element->getStyleFont(); + $isObject = ($fontStyle instanceof Font) ? true : false; + $rId = $title->getRelationId(); + $indent = ($title->getDepth() - 1) * $tocStyle->getIndent(); + + $xmlWriter->startElement('w:p'); + + // Write style and field mark + $this->writeStyle($xmlWriter, $element, $indent); + if ($writeFieldMark) { + $this->writeFieldMark($xmlWriter, $element); + } + + // Hyperlink + $xmlWriter->startElement('w:hyperlink'); + $xmlWriter->writeAttribute('w:anchor', "_Toc{$rId}"); + $xmlWriter->writeAttribute('w:history', '1'); + + // Title text + $xmlWriter->startElement('w:r'); + if ($isObject) { + $styleWriter = new FontStyleWriter($xmlWriter, $fontStyle); + $styleWriter->write(); + } + $xmlWriter->startElement('w:t'); + $this->writeText($title->getText()); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + + $xmlWriter->startElement('w:r'); + $xmlWriter->writeElement('w:tab', null); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text("PAGEREF _Toc{$rId} \h"); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'end'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->endElement(); // w:hyperlink + + $xmlWriter->endElement(); // w:p + } + + /** + * Write style + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\TOC $element + * @param int $indent + */ + private function writeStyle(XMLWriter $xmlWriter, TOCElement $element, $indent) + { + $tocStyle = $element->getStyleTOC(); + $fontStyle = $element->getStyleFont(); + $isObject = ($fontStyle instanceof Font) ? true : false; + + $xmlWriter->startElement('w:pPr'); + + // Paragraph + if ($isObject && !is_null($fontStyle->getParagraph())) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $fontStyle->getParagraph()); + $styleWriter->write(); + } + + // Font + if (!empty($fontStyle) && !$isObject) { + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rStyle'); + $xmlWriter->writeAttribute('w:val', $fontStyle); + $xmlWriter->endElement(); + $xmlWriter->endElement(); // w:rPr + } + + // Tab + $xmlWriter->startElement('w:tabs'); + $styleWriter = new TabStyleWriter($xmlWriter, $tocStyle); + $styleWriter->write(); + $xmlWriter->endElement(); + + // Indent + if ($indent > 0) { + $xmlWriter->startElement('w:ind'); + $xmlWriter->writeAttribute('w:left', $indent); + $xmlWriter->endElement(); + } + + $xmlWriter->endElement(); // w:pPr + } + + /** + * Write TOC Field. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\TOC $element + */ + private function writeFieldMark(XMLWriter $xmlWriter, TOCElement $element) + { + $minDepth = $element->getMinDepth(); + $maxDepth = $element->getMaxDepth(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'begin'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:instrText'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text("TOC \o {$minDepth}-{$maxDepth} \h \z \u"); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:fldChar'); + $xmlWriter->writeAttribute('w:fldCharType', 'separate'); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Table.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Table.php new file mode 100644 index 0000000..c365b02 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Table.php @@ -0,0 +1,143 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof TableElement) { + return; + } + + $rows = $element->getRows(); + $rowCount = count($rows); + + if ($rowCount > 0) { + $xmlWriter->startElement('w:tbl'); + + // Write columns + $this->writeColumns($xmlWriter, $element); + + // Write style + $styleWriter = new TableStyleWriter($xmlWriter, $element->getStyle()); + $styleWriter->setWidth($element->getWidth()); + $styleWriter->write(); + + // Write rows + for ($i = 0; $i < $rowCount; $i++) { + $this->writeRow($xmlWriter, $rows[$i]); + } + + $xmlWriter->endElement(); // w:tbl + } + } + + /** + * Write column. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Table $element + */ + private function writeColumns(XMLWriter $xmlWriter, TableElement $element) + { + $cellWidths = $element->findFirstDefinedCellWidths(); + + $xmlWriter->startElement('w:tblGrid'); + foreach ($cellWidths as $width) { + $xmlWriter->startElement('w:gridCol'); + if ($width !== null) { + $xmlWriter->writeAttribute('w:w', $width); + $xmlWriter->writeAttribute('w:type', 'dxa'); + } + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); // w:tblGrid + } + + /** + * Write row. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Row $row + */ + private function writeRow(XMLWriter $xmlWriter, RowElement $row) + { + $xmlWriter->startElement('w:tr'); + + // Write style + $rowStyle = $row->getStyle(); + if ($rowStyle instanceof RowStyle) { + $styleWriter = new RowStyleWriter($xmlWriter, $rowStyle); + $styleWriter->setHeight($row->getHeight()); + $styleWriter->write(); + } + + // Write cells + foreach ($row->getCells() as $cell) { + $this->writeCell($xmlWriter, $cell); + } + + $xmlWriter->endElement(); // w:tr + } + + /** + * Write cell. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Cell $cell + */ + private function writeCell(XMLWriter $xmlWriter, CellElement $cell) + { + $xmlWriter->startElement('w:tc'); + + // Write style + $cellStyle = $cell->getStyle(); + if ($cellStyle instanceof CellStyle) { + $styleWriter = new CellStyleWriter($xmlWriter, $cellStyle); + $styleWriter->setWidth($cell->getWidth()); + $styleWriter->write(); + } + + // Write content + $containerWriter = new Container($xmlWriter, $cell); + $containerWriter->write(); + + $xmlWriter->endElement(); // w:tc + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/TableAlignment.php b/PhpOffice/PhpWord/Writer/Word2007/Element/TableAlignment.php new file mode 100644 index 0000000..f44e9eb --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/TableAlignment.php @@ -0,0 +1,60 @@ +attributes['w:val'] = $value; + } + + /** + * @since 0.13.0 + * + * @return string + */ + final public function getName() + { + return $this->name; + } + + /** + * @since 0.13.0 + * + * @return string[] + */ + final public function getAttributes() + { + return $this->attributes; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Text.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Text.php new file mode 100644 index 0000000..f421c1a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Text.php @@ -0,0 +1,104 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Text) { + return; + } + + $this->startElementP(); + + $this->writeOpeningTrackChange(); + + $xmlWriter->startElement('w:r'); + + $this->writeFontStyle(); + + $textElement = 'w:t'; + //'w:delText' in case of deleted text + $changed = $element->getTrackChange(); + if ($changed != null && $changed->getChangeType() == TrackChange::DELETED) { + $textElement = 'w:delText'; + } + $xmlWriter->startElement($textElement); + + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $this->writeText($this->getText($element->getText())); + $xmlWriter->endElement(); + $xmlWriter->endElement(); // w:r + + $this->writeClosingTrackChange(); + + $this->endElementP(); // w:p + } + + /** + * Write opening of changed element + */ + protected function writeOpeningTrackChange() + { + $changed = $this->getElement()->getTrackChange(); + if ($changed == null) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + + if (($changed->getChangeType() == TrackChange::INSERTED)) { + $xmlWriter->startElement('w:ins'); + } elseif ($changed->getChangeType() == TrackChange::DELETED) { + $xmlWriter->startElement('w:del'); + } + $xmlWriter->writeAttribute('w:author', $changed->getAuthor()); + if ($changed->getDate() != null) { + $xmlWriter->writeAttribute('w:date', $changed->getDate()->format('Y-m-d\TH:i:s\Z')); + } + $xmlWriter->writeAttribute('w:id', $this->getElement()->getElementId()); + } + + /** + * Write ending + */ + protected function writeClosingTrackChange() + { + $changed = $this->getElement()->getTrackChange(); + if ($changed == null) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->endElement(); // w:ins|w:del + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/TextBox.php b/PhpOffice/PhpWord/Writer/Word2007/Element/TextBox.php new file mode 100644 index 0000000..9dd4bc3 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/TextBox.php @@ -0,0 +1,73 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\TextBox) { + return; + } + $style = $element->getStyle(); + $styleWriter = new TextBoxStyleWriter($xmlWriter, $style); + + if (!$this->withoutP) { + $xmlWriter->startElement('w:p'); + $styleWriter->writeAlignment(); + } + $this->writeCommentRangeStart(); + + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:pict'); + $xmlWriter->startElement('v:shape'); + $xmlWriter->writeAttribute('type', '#_x0000_t0202'); + + $styleWriter->write(); + $styleWriter->writeBorder(); + + $xmlWriter->startElement('v:textbox'); + $styleWriter->writeInnerMargin(); + + // TextBox content, serving as a container + $xmlWriter->startElement('w:txbxContent'); + $containerWriter = new Container($xmlWriter, $element); + $containerWriter->write(); + $xmlWriter->endElement(); // w:txbxContent + + $xmlWriter->endElement(); // v: textbox + + $xmlWriter->endElement(); // v:shape + $xmlWriter->endElement(); // w:pict + $xmlWriter->endElement(); // w:r + + $this->endElementP(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/TextBreak.php b/PhpOffice/PhpWord/Writer/Word2007/Element/TextBreak.php new file mode 100644 index 0000000..7b3d999 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/TextBreak.php @@ -0,0 +1,53 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\TextBreak) { + return; + } + + if (!$this->withoutP) { + $hasStyle = $element->hasStyle(); + $this->startElementP(); + + if ($hasStyle) { + $xmlWriter->startElement('w:pPr'); + $this->writeFontStyle(); + $xmlWriter->endElement(); // w:pPr + } + + $this->endElementP(); // w:p + } else { + $xmlWriter->writeElement('w:br'); + } + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/TextRun.php b/PhpOffice/PhpWord/Writer/Word2007/Element/TextRun.php new file mode 100644 index 0000000..e46ad3f --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/TextRun.php @@ -0,0 +1,42 @@ +getXmlWriter(); + $element = $this->getElement(); + + $this->startElementP(); + + $containerWriter = new Container($xmlWriter, $element); + $containerWriter->write(); + + $this->endElementP(); // w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Element/Title.php b/PhpOffice/PhpWord/Writer/Word2007/Element/Title.php new file mode 100644 index 0000000..6a05a34 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Element/Title.php @@ -0,0 +1,83 @@ +getXmlWriter(); + $element = $this->getElement(); + if (!$element instanceof \PhpOffice\PhpWord\Element\Title) { + return; + } + + $style = $element->getStyle(); + + $xmlWriter->startElement('w:p'); + + if (!empty($style)) { + $xmlWriter->startElement('w:pPr'); + $xmlWriter->startElement('w:pStyle'); + $xmlWriter->writeAttribute('w:val', $style); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } + + $bookmarkRId = null; + if ($element->getDepth() !== 0) { + $rId = $element->getRelationId(); + $bookmarkRId = $element->getPhpWord()->addBookmark(); + + // Bookmark start for TOC + $xmlWriter->startElement('w:bookmarkStart'); + $xmlWriter->writeAttribute('w:id', $bookmarkRId); + $xmlWriter->writeAttribute('w:name', "_Toc{$rId}"); + $xmlWriter->endElement(); //w:bookmarkStart + } + + // Actual text + $text = $element->getText(); + if (is_string($text)) { + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:t'); + $this->writeText($text); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + } elseif ($text instanceof \PhpOffice\PhpWord\Element\AbstractContainer) { + $containerWriter = new Container($xmlWriter, $text); + $containerWriter->write(); + } + + if ($element->getDepth() !== 0) { + // Bookmark end + $xmlWriter->startElement('w:bookmarkEnd'); + $xmlWriter->writeAttribute('w:id', $bookmarkRId); + $xmlWriter->endElement(); //w:bookmarkEnd + } + $xmlWriter->endElement(); //w:p + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/AbstractPart.php b/PhpOffice/PhpWord/Writer/Word2007/Part/AbstractPart.php new file mode 100644 index 0000000..ce4e41c --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/AbstractPart.php @@ -0,0 +1,107 @@ +parentWriter = $writer; + } + + /** + * Get parent writer + * + * @throws \PhpOffice\PhpWord\Exception\Exception + * @return \PhpOffice\PhpWord\Writer\AbstractWriter + */ + public function getParentWriter() + { + if (!is_null($this->parentWriter)) { + return $this->parentWriter; + } + throw new Exception('No parent WriterInterface assigned.'); + } + + /** + * Get XML Writer + * + * @return \PhpOffice\Common\XMLWriter + */ + protected function getXmlWriter() + { + $useDiskCaching = false; + if (!is_null($this->parentWriter)) { + if ($this->parentWriter->isUseDiskCaching()) { + $useDiskCaching = true; + } + } + if ($useDiskCaching) { + return new XMLWriter(XMLWriter::STORAGE_DISK, $this->parentWriter->getDiskCachingDirectory(), Settings::hasCompatibility()); + } + + return new XMLWriter(XMLWriter::STORAGE_MEMORY, './', Settings::hasCompatibility()); + } + + /** + * Write an XML text, this will call text() or writeRaw() depending on the value of Settings::isOutputEscapingEnabled() + * + * @param string $content The text string to write + * @return bool Returns true on success or false on failure + */ + protected function writeText($content) + { + if (Settings::isOutputEscapingEnabled()) { + return $this->getXmlWriter()->text($content); + } + + return $this->getXmlWriter()->writeRaw($content); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Chart.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Chart.php new file mode 100644 index 0000000..812d3bf --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Chart.php @@ -0,0 +1,444 @@ + array('type' => 'pie', 'colors' => 1), + 'doughnut' => array('type' => 'doughnut', 'colors' => 1, 'hole' => 75, 'no3d' => true), + 'bar' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'clustered'), + 'stacked_bar' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'stacked'), + 'percent_stacked_bar' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'bar', 'grouping' => 'percentStacked'), + 'column' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'clustered'), + 'stacked_column' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'stacked'), + 'percent_stacked_column' => array('type' => 'bar', 'colors' => 0, 'axes' => true, 'bar' => 'col', 'grouping' => 'percentStacked'), + 'line' => array('type' => 'line', 'colors' => 0, 'axes' => true), + 'area' => array('type' => 'area', 'colors' => 0, 'axes' => true), + 'radar' => array('type' => 'radar', 'colors' => 0, 'axes' => true, 'radar' => 'standard', 'no3d' => true), + 'scatter' => array('type' => 'scatter', 'colors' => 0, 'axes' => true, 'scatter' => 'marker', 'no3d' => true), + ); + + /** + * Chart options + * + * @var array + */ + private $options = array(); + + /** + * Set chart element. + * + * @param \PhpOffice\PhpWord\Element\Chart $element + */ + public function setElement(ChartElement $element) + { + $this->element = $element; + } + + /** + * Write part + * + * @return string + */ + public function write() + { + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('c:chartSpace'); + $xmlWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + $xmlWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + $this->writeChart($xmlWriter); + $this->writeShape($xmlWriter); + + $xmlWriter->endElement(); // c:chartSpace + + return $xmlWriter->getData(); + } + + /** + * Write chart + * + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_Chart.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writeChart(XMLWriter $xmlWriter) + { + $xmlWriter->startElement('c:chart'); + + $this->writePlotArea($xmlWriter); + + $xmlWriter->endElement(); // c:chart + } + + /** + * Write plot area. + * + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_PlotArea.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_PieChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_DoughnutChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_BarChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_LineChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_AreaChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_RadarChart.html + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_ScatterChart.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + */ + private function writePlotArea(XMLWriter $xmlWriter) + { + $type = $this->element->getType(); + $style = $this->element->getStyle(); + $this->options = $this->types[$type]; + + $title = $style->getTitle(); + $showLegend = $style->isShowLegend(); + + //Chart title + if ($title) { + $xmlWriter->startElement('c:title'); + $xmlWriter->startElement('c:tx'); + $xmlWriter->startElement('c:rich'); + $xmlWriter->writeRaw(' + + + + + ' . $title . ' + + '); + $xmlWriter->endElement(); // c:rich + $xmlWriter->endElement(); // c:tx + $xmlWriter->endElement(); // c:title + } else { + $xmlWriter->writeElementBlock('c:autoTitleDeleted', 'val', 1); + } + + //Chart legend + if ($showLegend) { + $xmlWriter->writeRaw(''); + } + + $xmlWriter->startElement('c:plotArea'); + $xmlWriter->writeElement('c:layout'); + + // Chart + $chartType = $this->options['type']; + $chartType .= $style->is3d() && !isset($this->options['no3d']) ? '3D' : ''; + $chartType .= 'Chart'; + $xmlWriter->startElement("c:{$chartType}"); + + $xmlWriter->writeElementBlock('c:varyColors', 'val', $this->options['colors']); + if ($type == 'area') { + $xmlWriter->writeElementBlock('c:grouping', 'val', 'standard'); + } + if (isset($this->options['hole'])) { + $xmlWriter->writeElementBlock('c:holeSize', 'val', $this->options['hole']); + } + if (isset($this->options['bar'])) { + $xmlWriter->writeElementBlock('c:barDir', 'val', $this->options['bar']); // bar|col + $xmlWriter->writeElementBlock('c:grouping', 'val', $this->options['grouping']); // 3d; standard = percentStacked + } + if (isset($this->options['radar'])) { + $xmlWriter->writeElementBlock('c:radarStyle', 'val', $this->options['radar']); + } + if (isset($this->options['scatter'])) { + $xmlWriter->writeElementBlock('c:scatterStyle', 'val', $this->options['scatter']); + } + + // Series + $this->writeSeries($xmlWriter, isset($this->options['scatter'])); + + $xmlWriter->writeElementBlock('c:overlap', 'val', '100'); + + // Axes + if (isset($this->options['axes'])) { + $xmlWriter->writeElementBlock('c:axId', 'val', 1); + $xmlWriter->writeElementBlock('c:axId', 'val', 2); + } + + $xmlWriter->endElement(); // chart type + + // Axes + if (isset($this->options['axes'])) { + $this->writeAxis($xmlWriter, 'cat'); + $this->writeAxis($xmlWriter, 'val'); + } + + $xmlWriter->endElement(); // c:plotArea + } + + /** + * Write series. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param bool $scatter + */ + private function writeSeries(XMLWriter $xmlWriter, $scatter = false) + { + $series = $this->element->getSeries(); + $style = $this->element->getStyle(); + $colors = $style->getColors(); + + $index = 0; + foreach ($series as $seriesItem) { + $categories = $seriesItem['categories']; + $values = $seriesItem['values']; + + $xmlWriter->startElement('c:ser'); + + $xmlWriter->writeElementBlock('c:idx', 'val', $index); + $xmlWriter->writeElementBlock('c:order', 'val', $index); + + if (!is_null($seriesItem['name']) && $seriesItem['name'] != '') { + $xmlWriter->startElement('c:tx'); + $xmlWriter->startElement('c:strRef'); + $xmlWriter->startElement('c:strCache'); + $xmlWriter->writeElementBlock('c:ptCount', 'val', 1); + $xmlWriter->startElement('c:pt'); + $xmlWriter->writeAttribute('idx', 0); + $xmlWriter->startElement('c:v'); + $xmlWriter->writeRaw($seriesItem['name']); + $xmlWriter->endElement(); // c:v + $xmlWriter->endElement(); // c:pt + $xmlWriter->endElement(); // c:strCache + $xmlWriter->endElement(); // c:strRef + $xmlWriter->endElement(); // c:tx + } + + // The c:dLbls was added to make word charts look more like the reports in SurveyGizmo + // This section needs to be made configurable before a pull request is made + $xmlWriter->startElement('c:dLbls'); + + foreach ($style->getDataLabelOptions() as $option => $val) { + $xmlWriter->writeElementBlock("c:{$option}", 'val', (int) $val); + } + + $xmlWriter->endElement(); // c:dLbls + + if (isset($this->options['scatter'])) { + $this->writeShape($xmlWriter); + } + + if ($scatter === true) { + $this->writeSeriesItem($xmlWriter, 'xVal', $categories); + $this->writeSeriesItem($xmlWriter, 'yVal', $values); + } else { + $this->writeSeriesItem($xmlWriter, 'cat', $categories); + $this->writeSeriesItem($xmlWriter, 'val', $values); + + // setting the chart colors was taken from https://github.com/PHPOffice/PHPWord/issues/494 + if (is_array($colors) && count($colors)) { + // This is a workaround to make each series in a stack chart use a different color + if ($this->options['type'] == 'bar' && stristr($this->options['grouping'], 'stacked')) { + array_shift($colors); + } + $colorIndex = 0; + foreach ($colors as $color) { + $xmlWriter->startElement('c:dPt'); + $xmlWriter->writeElementBlock('c:idx', 'val', $colorIndex); + $xmlWriter->startElement('c:spPr'); + $xmlWriter->startElement('a:solidFill'); + $xmlWriter->writeElementBlock('a:srgbClr', 'val', $color); + $xmlWriter->endElement(); // a:solidFill + $xmlWriter->endElement(); // c:spPr + $xmlWriter->endElement(); // c:dPt + $colorIndex++; + } + } + } + + $xmlWriter->endElement(); // c:ser + $index++; + } + } + + /** + * Write series items. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $type + * @param array $values + */ + private function writeSeriesItem(XMLWriter $xmlWriter, $type, $values) + { + $types = array( + 'cat' => array('c:cat', 'c:strLit'), + 'val' => array('c:val', 'c:numLit'), + 'xVal' => array('c:xVal', 'c:strLit'), + 'yVal' => array('c:yVal', 'c:numLit'), + ); + list($itemType, $itemLit) = $types[$type]; + + $xmlWriter->startElement($itemType); + $xmlWriter->startElement($itemLit); + $xmlWriter->writeElementBlock('c:ptCount', 'val', count($values)); + + $index = 0; + foreach ($values as $value) { + $xmlWriter->startElement('c:pt'); + $xmlWriter->writeAttribute('idx', $index); + if (\PhpOffice\PhpWord\Settings::isOutputEscapingEnabled()) { + $xmlWriter->writeElement('c:v', $value); + } else { + $xmlWriter->startElement('c:v'); + $xmlWriter->writeRaw($value); + $xmlWriter->endElement(); // c:v + } + $xmlWriter->endElement(); // c:pt + $index++; + } + + $xmlWriter->endElement(); // $itemLit + $xmlWriter->endElement(); // $itemType + } + + /** + * Write axis + * + * @see http://www.datypic.com/sc/ooxml/t-draw-chart_CT_CatAx.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $type + */ + private function writeAxis(XMLWriter $xmlWriter, $type) + { + $style = $this->element->getStyle(); + $types = array( + 'cat' => array('c:catAx', 1, 'b', 2), + 'val' => array('c:valAx', 2, 'l', 1), + ); + list($axisType, $axisId, $axisPos, $axisCross) = $types[$type]; + + $xmlWriter->startElement($axisType); + + $xmlWriter->writeElementBlock('c:axId', 'val', $axisId); + $xmlWriter->writeElementBlock('c:axPos', 'val', $axisPos); + + $categoryAxisTitle = $style->getCategoryAxisTitle(); + $valueAxisTitle = $style->getValueAxisTitle(); + + if ($axisType == 'c:catAx') { + if (!is_null($categoryAxisTitle)) { + $this->writeAxisTitle($xmlWriter, $categoryAxisTitle); + } + } elseif ($axisType == 'c:valAx') { + if (!is_null($valueAxisTitle)) { + $this->writeAxisTitle($xmlWriter, $valueAxisTitle); + } + } + + $xmlWriter->writeElementBlock('c:crossAx', 'val', $axisCross); + $xmlWriter->writeElementBlock('c:auto', 'val', 1); + + if (isset($this->options['axes'])) { + $xmlWriter->writeElementBlock('c:delete', 'val', 0); + $xmlWriter->writeElementBlock('c:majorTickMark', 'val', $style->getMajorTickPosition()); + $xmlWriter->writeElementBlock('c:minorTickMark', 'val', 'none'); + if ($style->showAxisLabels()) { + if ($axisType == 'c:catAx') { + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', $style->getCategoryLabelPosition()); + } else { + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', $style->getValueLabelPosition()); + } + } else { + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', 'none'); + } + $xmlWriter->writeElementBlock('c:crosses', 'val', 'autoZero'); + } + if (isset($this->options['radar']) || ($type == 'cat' && $style->showGridX()) || ($type == 'val' && $style->showGridY())) { + $xmlWriter->writeElement('c:majorGridlines'); + } + + $xmlWriter->startElement('c:scaling'); + $xmlWriter->writeElementBlock('c:orientation', 'val', 'minMax'); + $xmlWriter->endElement(); // c:scaling + + $this->writeShape($xmlWriter, true); + + $xmlWriter->endElement(); // $axisType + } + + /** + * Write shape + * + * @see http://www.datypic.com/sc/ooxml/t-a_CT_ShapeProperties.html + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param bool $line + */ + private function writeShape(XMLWriter $xmlWriter, $line = false) + { + $xmlWriter->startElement('c:spPr'); + $xmlWriter->startElement('a:ln'); + if ($line === true) { + $xmlWriter->writeElement('a:solidFill'); + } else { + $xmlWriter->writeElement('a:noFill'); + } + $xmlWriter->endElement(); // a:ln + $xmlWriter->endElement(); // c:spPr + } + + private function writeAxisTitle(XMLWriter $xmlWriter, $title) + { + $xmlWriter->startElement('c:title'); //start c:title + $xmlWriter->startElement('c:tx'); //start c:tx + $xmlWriter->startElement('c:rich'); // start c:rich + $xmlWriter->writeElement('a:bodyPr'); + $xmlWriter->writeElement('a:lstStyle'); + $xmlWriter->startElement('a:p'); + $xmlWriter->startElement('a:pPr'); + $xmlWriter->writeElement('a:defRPr'); + $xmlWriter->endElement(); // end a:pPr + $xmlWriter->startElement('a:r'); + $xmlWriter->writeElementBlock('a:rPr', 'lang', 'en-US'); + + $xmlWriter->startElement('a:t'); + $xmlWriter->writeRaw($title); + $xmlWriter->endElement(); //end a:t + + $xmlWriter->endElement(); // end a:r + $xmlWriter->endElement(); //end a:p + $xmlWriter->endElement(); //end c:rich + $xmlWriter->endElement(); // end c:tx + $xmlWriter->writeElementBlock('c:overlay', 'val', '0'); + $xmlWriter->endElement(); // end c:title + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Comments.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Comments.php new file mode 100644 index 0000000..33c9f59 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Comments.php @@ -0,0 +1,104 @@ +getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('w:comments'); + $xmlWriter->writeAttribute('xmlns:ve', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + $xmlWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $xmlWriter->writeAttribute('xmlns:wp', 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'); + $xmlWriter->writeAttribute('xmlns:w10', 'urn:schemas-microsoft-com:office:word'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); + + if ($this->elements !== null) { + foreach ($this->elements as $element) { + if ($element instanceof Comment) { + $this->writeComment($xmlWriter, $element); + } + } + } + + $xmlWriter->endElement(); // w:comments + + return $xmlWriter->getData(); + } + + /** + * Write comment item. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Comment $comment + */ + protected function writeComment(XMLWriter $xmlWriter, Comment $comment) + { + $xmlWriter->startElement('w:comment'); + $xmlWriter->writeAttribute('w:id', $comment->getElementId()); + $xmlWriter->writeAttribute('w:author', $comment->getAuthor()); + if ($comment->getDate() != null) { + $xmlWriter->writeAttribute('w:date', $comment->getDate()->format($this->dateFormat)); + } + $xmlWriter->writeAttributeIf($comment->getInitials() != null, 'w:initials', $comment->getInitials()); + + $containerWriter = new Container($xmlWriter, $comment); + $containerWriter->write(); + + $xmlWriter->endElement(); // w:comment + } + + /** + * Set element + * + * @param \PhpOffice\PhpWord\Element\Comment[] $elements + * @return self + */ + public function setElements($elements) + { + $this->elements = $elements; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/ContentTypes.php b/PhpOffice/PhpWord/Writer/Word2007/Part/ContentTypes.php new file mode 100644 index 0000000..28a2d29 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/ContentTypes.php @@ -0,0 +1,98 @@ +getParentWriter(); + $contentTypes = $parentWriter->getContentTypes(); + + $openXMLPrefix = 'application/vnd.openxmlformats-'; + $wordMLPrefix = $openXMLPrefix . 'officedocument.wordprocessingml.'; + $drawingMLPrefix = $openXMLPrefix . 'officedocument.drawingml.'; + $overrides = array( + '/docProps/core.xml' => $openXMLPrefix . 'package.core-properties+xml', + '/docProps/app.xml' => $openXMLPrefix . 'officedocument.extended-properties+xml', + '/docProps/custom.xml' => $openXMLPrefix . 'officedocument.custom-properties+xml', + '/word/document.xml' => $wordMLPrefix . 'document.main+xml', + '/word/styles.xml' => $wordMLPrefix . 'styles+xml', + '/word/numbering.xml' => $wordMLPrefix . 'numbering+xml', + '/word/settings.xml' => $wordMLPrefix . 'settings+xml', + '/word/theme/theme1.xml' => $openXMLPrefix . 'officedocument.theme+xml', + '/word/webSettings.xml' => $wordMLPrefix . 'webSettings+xml', + '/word/fontTable.xml' => $wordMLPrefix . 'fontTable+xml', + '/word/comments.xml' => $wordMLPrefix . 'comments+xml', + ); + + $defaults = $contentTypes['default']; + if (!empty($contentTypes['override'])) { + foreach ($contentTypes['override'] as $key => $val) { + if ($val == 'chart') { + $overrides[$key] = $drawingMLPrefix . $val . '+xml'; + } else { + $overrides[$key] = $wordMLPrefix . $val . '+xml'; + } + } + } + + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('Types'); + $xmlWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types'); + + $this->writeContentType($xmlWriter, $defaults, true); + $this->writeContentType($xmlWriter, $overrides, false); + + $xmlWriter->endElement(); // Types + + return $xmlWriter->getData(); + } + + /** + * Write content types element + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter XML Writer + * @param array $parts + * @param bool $isDefault + */ + private function writeContentType(XMLWriter $xmlWriter, $parts, $isDefault) + { + foreach ($parts as $partName => $contentType) { + $partType = $isDefault ? 'Default' : 'Override'; + $partAttribute = $isDefault ? 'Extension' : 'PartName'; + $xmlWriter->startElement($partType); + $xmlWriter->writeAttribute($partAttribute, $partName); + $xmlWriter->writeAttribute('ContentType', $contentType); + $xmlWriter->endElement(); + } + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsApp.php b/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsApp.php new file mode 100644 index 0000000..3452d86 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsApp.php @@ -0,0 +1,51 @@ +getParentWriter()->getPhpWord(); + $xmlWriter = $this->getXmlWriter(); + $schema = 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'; + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('Properties'); + $xmlWriter->writeAttribute('xmlns', $schema); + $xmlWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + + $xmlWriter->writeElement('Application', 'PHPWord'); + $xmlWriter->writeElement('Company', $phpWord->getDocInfo()->getCompany()); + $xmlWriter->writeElement('Manager', $phpWord->getDocInfo()->getManager()); + + $xmlWriter->endElement(); // Properties + + return $xmlWriter->getData(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCore.php b/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCore.php new file mode 100644 index 0000000..caefbd8 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCore.php @@ -0,0 +1,70 @@ +getParentWriter()->getPhpWord(); + $xmlWriter = $this->getXmlWriter(); + $schema = 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'; + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('cp:coreProperties'); + $xmlWriter->writeAttribute('xmlns:cp', $schema); + $xmlWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + $xmlWriter->writeAttribute('xmlns:dcterms', 'http://purl.org/dc/terms/'); + $xmlWriter->writeAttribute('xmlns:dcmitype', 'http://purl.org/dc/dcmitype/'); + $xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + + $xmlWriter->writeElement('dc:creator', $phpWord->getDocInfo()->getCreator()); + $xmlWriter->writeElement('dc:title', $phpWord->getDocInfo()->getTitle()); + $xmlWriter->writeElement('dc:description', $phpWord->getDocInfo()->getDescription()); + $xmlWriter->writeElement('dc:subject', $phpWord->getDocInfo()->getSubject()); + $xmlWriter->writeElement('cp:keywords', $phpWord->getDocInfo()->getKeywords()); + $xmlWriter->writeElement('cp:category', $phpWord->getDocInfo()->getCategory()); + $xmlWriter->writeElement('cp:lastModifiedBy', $phpWord->getDocInfo()->getLastModifiedBy()); + + // dcterms:created + $xmlWriter->startElement('dcterms:created'); + $xmlWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); + $xmlWriter->text(date($this->dateFormat, $phpWord->getDocInfo()->getCreated())); + $xmlWriter->endElement(); + + // dcterms:modified + $xmlWriter->startElement('dcterms:modified'); + $xmlWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); + $xmlWriter->text(date($this->dateFormat, $phpWord->getDocInfo()->getModified())); + $xmlWriter->endElement(); + + $xmlWriter->endElement(); // cp:coreProperties + + return $xmlWriter->getData(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCustom.php b/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCustom.php new file mode 100644 index 0000000..478075d --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCustom.php @@ -0,0 +1,80 @@ +getParentWriter()->getPhpWord(); + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('Properties'); + $xmlWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties'); + $xmlWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + + $docProps = $phpWord->getDocInfo(); + $properties = $docProps->getCustomProperties(); + foreach ($properties as $key => $property) { + $propertyValue = $docProps->getCustomPropertyValue($property); + $propertyType = $docProps->getCustomPropertyType($property); + + $xmlWriter->startElement('property'); + $xmlWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}'); + $xmlWriter->writeAttribute('pid', $key + 2); + $xmlWriter->writeAttribute('name', $property); + switch ($propertyType) { + case 'i': + $xmlWriter->writeElement('vt:i4', $propertyValue); + break; + case 'f': + $xmlWriter->writeElement('vt:r8', $propertyValue); + break; + case 'b': + $xmlWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false'); + break; + case 'd': + if ($propertyValue instanceof \DateTime) { + $xmlWriter->writeElement('vt:filetime', $propertyValue->format($this->dateFormat)); + } else { + $xmlWriter->writeElement('vt:filetime', date($this->dateFormat, $propertyValue)); + } + break; + default: + $xmlWriter->writeElement('vt:lpwstr', $propertyValue); + break; + } + $xmlWriter->endElement(); // property + } + + $xmlWriter->endElement(); // Properties + + return $xmlWriter->getData(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Document.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Document.php new file mode 100644 index 0000000..e0cabd7 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Document.php @@ -0,0 +1,161 @@ +getParentWriter()->getPhpWord(); + $xmlWriter = $this->getXmlWriter(); + + $sections = $phpWord->getSections(); + $sectionCount = count($sections); + $currentSection = 0; + $drawingSchema = 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'; + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('w:document'); + $xmlWriter->writeAttribute('xmlns:ve', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + $xmlWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $xmlWriter->writeAttribute('xmlns:wp', $drawingSchema); + $xmlWriter->writeAttribute('xmlns:w10', 'urn:schemas-microsoft-com:office:word'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); + + $xmlWriter->startElement('w:body'); + + if ($sectionCount > 0) { + foreach ($sections as $section) { + $currentSection++; + + $containerWriter = new Container($xmlWriter, $section); + $containerWriter->write(); + + if ($currentSection == $sectionCount) { + $this->writeSectionSettings($xmlWriter, $section); + } else { + $this->writeSection($xmlWriter, $section); + } + } + } + + $xmlWriter->endElement(); // w:body + $xmlWriter->endElement(); // w:document + + return $xmlWriter->getData(); + } + + /** + * Write begin section. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Section $section + */ + private function writeSection(XMLWriter $xmlWriter, Section $section) + { + $xmlWriter->startElement('w:p'); + $xmlWriter->startElement('w:pPr'); + $this->writeSectionSettings($xmlWriter, $section); + $xmlWriter->endElement(); + $xmlWriter->endElement(); + } + + /** + * Write end section. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Section $section + */ + private function writeSectionSettings(XMLWriter $xmlWriter, Section $section) + { + $xmlWriter->startElement('w:sectPr'); + + // Header reference + foreach ($section->getHeaders() as $header) { + $rId = $header->getRelationId(); + $xmlWriter->startElement('w:headerReference'); + $xmlWriter->writeAttribute('w:type', $header->getType()); + $xmlWriter->writeAttribute('r:id', 'rId' . $rId); + $xmlWriter->endElement(); + } + + // Footer reference + foreach ($section->getFooters() as $footer) { + $rId = $footer->getRelationId(); + $xmlWriter->startElement('w:footerReference'); + $xmlWriter->writeAttribute('w:type', $footer->getType()); + $xmlWriter->writeAttribute('r:id', 'rId' . $rId); + $xmlWriter->endElement(); + } + + // Different first page + if ($section->hasDifferentFirstPage()) { + $xmlWriter->startElement('w:titlePg'); + $xmlWriter->endElement(); + } + + // Footnote properties + if ($section->getFootnoteProperties() !== null) { + $xmlWriter->startElement('w:footnotePr'); + if ($section->getFootnoteProperties()->getPos() != null) { + $xmlWriter->startElement('w:pos'); + $xmlWriter->writeAttribute('w:val', $section->getFootnoteProperties()->getPos()); + $xmlWriter->endElement(); + } + if ($section->getFootnoteProperties()->getNumFmt() != null) { + $xmlWriter->startElement('w:numFmt'); + $xmlWriter->writeAttribute('w:val', $section->getFootnoteProperties()->getNumFmt()); + $xmlWriter->endElement(); + } + if ($section->getFootnoteProperties()->getNumStart() != null) { + $xmlWriter->startElement('w:numStart'); + $xmlWriter->writeAttribute('w:val', $section->getFootnoteProperties()->getNumStart()); + $xmlWriter->endElement(); + } + if ($section->getFootnoteProperties()->getNumRestart() != null) { + $xmlWriter->startElement('w:numRestart'); + $xmlWriter->writeAttribute('w:val', $section->getFootnoteProperties()->getNumRestart()); + $xmlWriter->endElement(); + } + $xmlWriter->endElement(); + } + + // Section settings + $styleWriter = new SectionStyleWriter($xmlWriter, $section->getStyle()); + $styleWriter->write(); + + $xmlWriter->endElement(); // w:sectPr + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Endnotes.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Endnotes.php new file mode 100644 index 0000000..ce3a46b --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Endnotes.php @@ -0,0 +1,52 @@ +'; + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + + return $str; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Footer.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Footer.php new file mode 100644 index 0000000..97b4779 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Footer.php @@ -0,0 +1,83 @@ +getXmlWriter(); + $drawingSchema = 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'; + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement($this->rootElement); + $xmlWriter->writeAttribute('xmlns:ve', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + $xmlWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $xmlWriter->writeAttribute('xmlns:wp', $drawingSchema); + $xmlWriter->writeAttribute('xmlns:w10', 'urn:schemas-microsoft-com:office:word'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); + + $containerWriter = new Container($xmlWriter, $this->element); + $containerWriter->write(); + + $xmlWriter->endElement(); // $this->rootElement + + return $xmlWriter->getData(); + } + + /** + * Set element + * + * @param \PhpOffice\PhpWord\Element\Footer|\PhpOffice\PhpWord\Element\Header $element + * @return self + */ + public function setElement($element) + { + $this->element = $element; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Footnotes.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Footnotes.php new file mode 100644 index 0000000..59bf183 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Footnotes.php @@ -0,0 +1,176 @@ +getXmlWriter(); + $drawingSchema = 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'; + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement($this->rootNode); + $xmlWriter->writeAttribute('xmlns:ve', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + $xmlWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $xmlWriter->writeAttribute('xmlns:wp', $drawingSchema); + $xmlWriter->writeAttribute('xmlns:w10', 'urn:schemas-microsoft-com:office:word'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); + + // Separator and continuation separator + $xmlWriter->startElement($this->elementNode); + $xmlWriter->writeAttribute('w:id', -1); + $xmlWriter->writeAttribute('w:type', 'separator'); + $xmlWriter->startElement('w:p'); + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:separator'); + $xmlWriter->endElement(); // w:separator + $xmlWriter->endElement(); // w:r + $xmlWriter->endElement(); // w:p + $xmlWriter->endElement(); // $this->elementNode + $xmlWriter->startElement($this->elementNode); + $xmlWriter->writeAttribute('w:id', 0); + $xmlWriter->writeAttribute('w:type', 'continuationSeparator'); + $xmlWriter->startElement('w:p'); + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:continuationSeparator'); + $xmlWriter->endElement(); // w:continuationSeparator + $xmlWriter->endElement(); // w:r + $xmlWriter->endElement(); // w:p + $xmlWriter->endElement(); // $this->elementNode + + /** @var array $elements Type hint */ + $elements = $this->elements; + foreach ($elements as $element) { + if ($element instanceof Footnote) { + $this->writeNote($xmlWriter, $element); + } + } + + $xmlWriter->endElement(); // $this->rootNode + + return $xmlWriter->getData(); + } + + /** + * Set element + * + * @param \PhpOffice\PhpWord\Collection\Footnotes|\PhpOffice\PhpWord\Collection\Endnotes $elements + * @return self + */ + public function setElements($elements) + { + $this->elements = $elements; + + return $this; + } + + /** + * Write note item. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Element\Footnote|\PhpOffice\PhpWord\Element\Endnote $element + */ + protected function writeNote(XMLWriter $xmlWriter, $element) + { + $xmlWriter->startElement($this->elementNode); + $xmlWriter->writeAttribute('w:id', $element->getRelationId()); + $xmlWriter->startElement('w:p'); + + // Paragraph style + $styleWriter = new ParagraphStyleWriter($xmlWriter, $element->getParagraphStyle()); + $styleWriter->setIsInline(true); + $styleWriter->write(); + + // Reference symbol + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rStyle'); + $xmlWriter->writeAttribute('w:val', $this->refStyle); + $xmlWriter->endElement(); // w:rStyle + $xmlWriter->endElement(); // w:rPr + $xmlWriter->writeElement($this->refNode); + $xmlWriter->endElement(); // w:r + + // Empty space after refence symbol + $xmlWriter->startElement('w:r'); + $xmlWriter->startElement('w:t'); + $xmlWriter->writeAttribute('xml:space', 'preserve'); + $xmlWriter->text(' '); + $xmlWriter->endElement(); // w:t + $xmlWriter->endElement(); // w:r + + $containerWriter = new Container($xmlWriter, $element); + $containerWriter->write(); + + $xmlWriter->endElement(); // w:p + $xmlWriter->endElement(); // $this->elementNode + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Header.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Header.php new file mode 100644 index 0000000..b58df1f --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Header.php @@ -0,0 +1,31 @@ +getXmlWriter(); + $styles = Style::getStyles(); + $drawingSchema = 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'; + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('w:numbering'); + $xmlWriter->writeAttribute('xmlns:ve', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + $xmlWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $xmlWriter->writeAttribute('xmlns:wp', $drawingSchema); + $xmlWriter->writeAttribute('xmlns:w10', 'urn:schemas-microsoft-com:office:word'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:wne', 'http://schemas.microsoft.com/office/word/2006/wordml'); + + // Abstract numbering definitions + foreach ($styles as $style) { + if ($style instanceof NumberingStyle) { + $levels = $style->getLevels(); + + $xmlWriter->startElement('w:abstractNum'); + $xmlWriter->writeAttribute('w:abstractNumId', $style->getIndex()); + + $xmlWriter->startElement('w:nsid'); + $xmlWriter->writeAttribute('w:val', $this->getRandomHexNumber()); + $xmlWriter->endElement(); // w:nsid + + $xmlWriter->startElement('w:multiLevelType'); + $xmlWriter->writeAttribute('w:val', $style->getType()); + $xmlWriter->endElement(); // w:multiLevelType + + if (is_array($levels)) { + foreach ($levels as $level) { + $this->writeLevel($xmlWriter, $level); + } + } + $xmlWriter->endElement(); // w:abstractNum + } + } + + // Numbering definition instances + foreach ($styles as $style) { + if ($style instanceof NumberingStyle) { + $xmlWriter->startElement('w:num'); + $xmlWriter->writeAttribute('w:numId', $style->getIndex()); + $xmlWriter->startElement('w:abstractNumId'); + $xmlWriter->writeAttribute('w:val', $style->getIndex()); + $xmlWriter->endElement(); // w:abstractNumId + $xmlWriter->endElement(); // w:num + } + } + + $xmlWriter->endElement(); // w:numbering + + return $xmlWriter->getData(); + } + + /** + * Write level. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\NumberingLevel $level + */ + private function writeLevel(XMLWriter $xmlWriter, NumberingLevel $level) + { + $xmlWriter->startElement('w:lvl'); + $xmlWriter->writeAttribute('w:ilvl', $level->getLevel()); + + // Numbering level properties + $properties = array( + 'start' => 'start', + 'format' => 'numFmt', + 'restart' => 'lvlRestart', + 'pStyle' => 'pStyle', + 'suffix' => 'suff', + 'text' => 'lvlText', + 'alignment' => 'lvlJc', + ); + foreach ($properties as $property => $nodeName) { + $getMethod = "get{$property}"; + if ('' !== $level->$getMethod() // this condition is now supported by `alignment` only + && !is_null($level->$getMethod())) { + $xmlWriter->startElement("w:{$nodeName}"); + $xmlWriter->writeAttribute('w:val', $level->$getMethod()); + $xmlWriter->endElement(); // w:start + } + } + + // Paragraph & font styles + $this->writeParagraph($xmlWriter, $level); + $this->writeFont($xmlWriter, $level); + + $xmlWriter->endElement(); // w:lvl + } + + /** + * Write level paragraph. + * + * @since 0.11.0 + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\NumberingLevel $level + * @todo Use paragraph style writer + */ + private function writeParagraph(XMLWriter $xmlWriter, NumberingLevel $level) + { + $tabPos = $level->getTabPos(); + $left = $level->getLeft(); + $hanging = $level->getHanging(); + + $xmlWriter->startElement('w:pPr'); + + $xmlWriter->startElement('w:tabs'); + $xmlWriter->startElement('w:tab'); + $xmlWriter->writeAttribute('w:val', 'num'); + $xmlWriter->writeAttributeIf($tabPos !== null, 'w:pos', $tabPos); + $xmlWriter->endElement(); // w:tab + $xmlWriter->endElement(); // w:tabs + + $xmlWriter->startElement('w:ind'); + $xmlWriter->writeAttributeIf($left !== null, 'w:left', $left); + $xmlWriter->writeAttributeIf($hanging !== null, 'w:hanging', $hanging); + $xmlWriter->endElement(); // w:ind + + $xmlWriter->endElement(); // w:pPr + } + + /** + * Write level font. + * + * @since 0.11.0 + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\NumberingLevel $level + * @todo Use font style writer + */ + private function writeFont(XMLWriter $xmlWriter, NumberingLevel $level) + { + $font = $level->getFont(); + $hint = $level->getHint(); + + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rFonts'); + $xmlWriter->writeAttributeIf($font !== null, 'w:ascii', $font); + $xmlWriter->writeAttributeIf($font !== null, 'w:hAnsi', $font); + $xmlWriter->writeAttributeIf($font !== null, 'w:cs', $font); + $xmlWriter->writeAttributeIf($hint !== null, 'w:hint', $hint); + $xmlWriter->endElement(); // w:rFonts + $xmlWriter->endElement(); // w:rPr + } + + /** + * Get random hexadecimal number value + * + * @param int $length + * @return string + */ + private function getRandomHexNumber($length = 8) + { + return strtoupper(substr(md5(rand()), 0, $length)); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Rels.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Rels.php new file mode 100644 index 0000000..661a4fa --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Rels.php @@ -0,0 +1,130 @@ + 'package/2006/relationships/metadata/core-properties', + 'docProps/app.xml' => 'officeDocument/2006/relationships/extended-properties', + 'docProps/custom.xml' => 'officeDocument/2006/relationships/custom-properties', + 'word/document.xml' => 'officeDocument/2006/relationships/officeDocument', + ); + $xmlWriter = $this->getXmlWriter(); + $this->writeRels($xmlWriter, $xmlRels); + + return $xmlWriter->getData(); + } + + /** + * Write relationships. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param array $xmlRels + * @param array $mediaRels + * @param int $relId + */ + protected function writeRels(XMLWriter $xmlWriter, $xmlRels = array(), $mediaRels = array(), $relId = 1) + { + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('Relationships'); + $xmlWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + + // XML files relationships + foreach ($xmlRels as $target => $type) { + $this->writeRel($xmlWriter, $relId++, $type, $target); + } + + // Media relationships + foreach ($mediaRels as $mediaRel) { + $this->writeMediaRel($xmlWriter, $relId++, $mediaRel); + } + + $xmlWriter->endElement(); // Relationships + } + + /** + * Write media relationships. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param int $relId + * @param array $mediaRel + */ + private function writeMediaRel(XMLWriter $xmlWriter, $relId, $mediaRel) + { + $typePrefix = 'officeDocument/2006/relationships/'; + $typeMapping = array('image' => 'image', 'object' => 'oleObject', 'link' => 'hyperlink'); + $targetMapping = array('image' => 'media/', 'object' => 'embeddings/'); + + $mediaType = $mediaRel['type']; + $type = isset($typeMapping[$mediaType]) ? $typeMapping[$mediaType] : $mediaType; + $targetPrefix = isset($targetMapping[$mediaType]) ? $targetMapping[$mediaType] : ''; + $target = $mediaRel['target']; + $targetMode = ($type == 'hyperlink') ? 'External' : ''; + + $this->writeRel($xmlWriter, $relId, $typePrefix . $type, $targetPrefix . $target, $targetMode); + } + + /** + * Write individual rels entry. + * + * Format: + * + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param int $relId Relationship ID + * @param string $type Relationship type + * @param string $target Relationship target + * @param string $targetMode Relationship target mode + * + * @throws \PhpOffice\PhpWord\Exception\Exception + */ + private function writeRel(XMLWriter $xmlWriter, $relId, $type, $target, $targetMode = '') + { + if ($type != '' && $target != '') { + if (strpos($relId, 'rId') === false) { + $relId = 'rId' . $relId; + } + $xmlWriter->startElement('Relationship'); + $xmlWriter->writeAttribute('Id', $relId); + $xmlWriter->writeAttribute('Type', 'http://schemas.openxmlformats.org/' . $type); + $xmlWriter->writeAttribute('Target', $target); + if ($targetMode != '') { + $xmlWriter->writeAttribute('TargetMode', $targetMode); + } + $xmlWriter->endElement(); + } else { + throw new Exception('Invalid parameters passed.'); + } + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/RelsDocument.php b/PhpOffice/PhpWord/Writer/Word2007/Part/RelsDocument.php new file mode 100644 index 0000000..2a0c5e1 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/RelsDocument.php @@ -0,0 +1,50 @@ + 'officeDocument/2006/relationships/styles', + 'numbering.xml' => 'officeDocument/2006/relationships/numbering', + 'settings.xml' => 'officeDocument/2006/relationships/settings', + 'theme/theme1.xml' => 'officeDocument/2006/relationships/theme', + 'webSettings.xml' => 'officeDocument/2006/relationships/webSettings', + 'fontTable.xml' => 'officeDocument/2006/relationships/fontTable', + ); + $xmlWriter = $this->getXmlWriter(); + + /** @var \PhpOffice\PhpWord\Writer\Word2007 $parentWriter Type hint */ + $parentWriter = $this->getParentWriter(); + $this->writeRels($xmlWriter, $xmlRels, $parentWriter->getRelationships()); + + return $xmlWriter->getData(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/RelsPart.php b/PhpOffice/PhpWord/Writer/Word2007/Part/RelsPart.php new file mode 100644 index 0000000..ac61a0c --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/RelsPart.php @@ -0,0 +1,59 @@ +getXmlWriter(); + $this->writeRels($xmlWriter, array(), $this->media); + + return $xmlWriter->getData(); + } + + /** + * Set media + * + * @param array $media + * @return self + */ + public function setMedia($media) + { + $this->media = $media; + + return $this; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Settings.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Settings.php new file mode 100644 index 0000000..b764642 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Settings.php @@ -0,0 +1,328 @@ +getSettings(); + + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('w:settings'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + $xmlWriter->writeAttribute('xmlns:m', 'http://schemas.openxmlformats.org/officeDocument/2006/math'); + $xmlWriter->writeAttribute('xmlns:sl', 'http://schemas.openxmlformats.org/schemaLibrary/2006/main'); + $xmlWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); + $xmlWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); + $xmlWriter->writeAttribute('xmlns:w10', 'urn:schemas-microsoft-com:office:word'); + + foreach ($this->settings as $settingKey => $settingValue) { + $this->writeSetting($xmlWriter, $settingKey, $settingValue); + } + + $xmlWriter->endElement(); // w:settings + + return $xmlWriter->getData(); + } + + /** + * Write indivual setting, recursive to any child settings. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $settingKey + * @param array|string $settingValue + */ + protected function writeSetting($xmlWriter, $settingKey, $settingValue) + { + if ($settingValue == '') { + $xmlWriter->writeElement($settingKey); + } elseif (is_array($settingValue) && !empty($settingValue)) { + $xmlWriter->startElement($settingKey); + + /** @var array $settingValue Type hint */ + foreach ($settingValue as $childKey => $childValue) { + if ($childKey == '@attributes') { + foreach ($childValue as $key => $val) { + $xmlWriter->writeAttribute($key, $val); + } + } else { + $this->writeSetting($xmlWriter, $childKey, $childValue); + } + } + $xmlWriter->endElement(); + } + } + + /** + * Get settings. + */ + private function getSettings() + { + /** @var \PhpOffice\PhpWord\Metadata\Settings $documentSettings */ + $documentSettings = $this->getParentWriter()->getPhpWord()->getSettings(); + + // Default settings + $this->settings = array( + 'w:defaultTabStop' => array('@attributes' => array('w:val' => '708')), + 'w:hyphenationZone' => array('@attributes' => array('w:val' => '425')), + 'w:characterSpacingControl' => array('@attributes' => array('w:val' => 'doNotCompress')), + 'w:decimalSymbol' => array('@attributes' => array('w:val' => $documentSettings->getDecimalSymbol())), + 'w:listSeparator' => array('@attributes' => array('w:val' => ';')), + 'w:compat' => array(), + 'm:mathPr' => array( + 'm:mathFont' => array('@attributes' => array('m:val' => 'Cambria Math')), + 'm:brkBin' => array('@attributes' => array('m:val' => 'before')), + 'm:brkBinSub' => array('@attributes' => array('m:val' => '--')), + 'm:smallFrac' => array('@attributes' => array('m:val' => 'off')), + 'm:dispDef' => '', + 'm:lMargin' => array('@attributes' => array('m:val' => '0')), + 'm:rMargin' => array('@attributes' => array('m:val' => '0')), + 'm:defJc' => array('@attributes' => array('m:val' => 'centerGroup')), + 'm:wrapIndent' => array('@attributes' => array('m:val' => '1440')), + 'm:intLim' => array('@attributes' => array('m:val' => 'subSup')), + 'm:naryLim' => array('@attributes' => array('m:val' => 'undOvr')), + ), + 'w:clrSchemeMapping' => array( + '@attributes' => array( + 'w:bg1' => 'light1', + 'w:t1' => 'dark1', + 'w:bg2' => 'light2', + 'w:t2' => 'dark2', + 'w:accent1' => 'accent1', + 'w:accent2' => 'accent2', + 'w:accent3' => 'accent3', + 'w:accent4' => 'accent4', + 'w:accent5' => 'accent5', + 'w:accent6' => 'accent6', + 'w:hyperlink' => 'hyperlink', + 'w:followedHyperlink' => 'followedHyperlink', + ), + ), + ); + + $this->setOnOffValue('w:mirrorMargins', $documentSettings->hasMirrorMargins()); + $this->setOnOffValue('w:hideSpellingErrors', $documentSettings->hasHideSpellingErrors()); + $this->setOnOffValue('w:hideGrammaticalErrors', $documentSettings->hasHideGrammaticalErrors()); + $this->setOnOffValue('w:trackRevisions', $documentSettings->hasTrackRevisions()); + $this->setOnOffValue('w:doNotTrackMoves', $documentSettings->hasDoNotTrackMoves()); + $this->setOnOffValue('w:doNotTrackFormatting', $documentSettings->hasDoNotTrackFormatting()); + $this->setOnOffValue('w:evenAndOddHeaders', $documentSettings->hasEvenAndOddHeaders()); + $this->setOnOffValue('w:updateFields', $documentSettings->hasUpdateFields()); + $this->setOnOffValue('w:autoHyphenation', $documentSettings->hasAutoHyphenation()); + $this->setOnOffValue('w:doNotHyphenateCaps', $documentSettings->hasDoNotHyphenateCaps()); + + $this->setThemeFontLang($documentSettings->getThemeFontLang()); + $this->setRevisionView($documentSettings->getRevisionView()); + $this->setDocumentProtection($documentSettings->getDocumentProtection()); + $this->setProofState($documentSettings->getProofState()); + $this->setZoom($documentSettings->getZoom()); + $this->setConsecutiveHyphenLimit($documentSettings->getConsecutiveHyphenLimit()); + $this->setHyphenationZone($documentSettings->getHyphenationZone()); + $this->setCompatibility(); + } + + /** + * Adds a boolean attribute to the settings array + * + * @param string $settingName + * @param bool|null $booleanValue + */ + private function setOnOffValue($settingName, $booleanValue) + { + if (!is_bool($booleanValue)) { + return; + } + + $value = $booleanValue ? 'true' : 'false'; + $this->settings[$settingName] = array('@attributes' => array('w:val' => $value)); + } + + /** + * Get protection settings. + * + * @param \PhpOffice\PhpWord\Metadata\Protection $documentProtection + */ + private function setDocumentProtection($documentProtection) + { + if ($documentProtection->getEditing() !== null) { + if ($documentProtection->getPassword() == null) { + $this->settings['w:documentProtection'] = array( + '@attributes' => array( + 'w:enforcement' => 1, + 'w:edit' => $documentProtection->getEditing(), + ), + ); + } else { + if ($documentProtection->getSalt() == null) { + $documentProtection->setSalt(openssl_random_pseudo_bytes(16)); + } + $passwordHash = PasswordEncoder::hashPassword($documentProtection->getPassword(), $documentProtection->getAlgorithm(), $documentProtection->getSalt(), $documentProtection->getSpinCount()); + $this->settings['w:documentProtection'] = array( + '@attributes' => array( + 'w:enforcement' => 1, + 'w:edit' => $documentProtection->getEditing(), + 'w:cryptProviderType' => 'rsaFull', + 'w:cryptAlgorithmClass' => 'hash', + 'w:cryptAlgorithmType' => 'typeAny', + 'w:cryptAlgorithmSid' => PasswordEncoder::getAlgorithmId($documentProtection->getAlgorithm()), + 'w:cryptSpinCount' => $documentProtection->getSpinCount(), + 'w:hash' => $passwordHash, + 'w:salt' => base64_encode($documentProtection->getSalt()), + ), + ); + } + } + } + + /** + * Set the Proof state + * + * @param ProofState $proofState + */ + private function setProofState(ProofState $proofState = null) + { + if ($proofState != null && $proofState->getGrammar() !== null && $proofState->getSpelling() !== null) { + $this->settings['w:proofState'] = array( + '@attributes' => array( + 'w:spelling' => $proofState->getSpelling(), + 'w:grammar' => $proofState->getGrammar(), + ), + ); + } + } + + /** + * Set the Revision View + * + * @param TrackChangesView $trackChangesView + */ + private function setRevisionView(TrackChangesView $trackChangesView = null) + { + if ($trackChangesView != null) { + $revisionView = array(); + $revisionView['w:markup'] = $trackChangesView->hasMarkup() ? 'true' : 'false'; + $revisionView['w:comments'] = $trackChangesView->hasComments() ? 'true' : 'false'; + $revisionView['w:insDel'] = $trackChangesView->hasInsDel() ? 'true' : 'false'; + $revisionView['w:formatting'] = $trackChangesView->hasFormatting() ? 'true' : 'false'; + $revisionView['w:inkAnnotations'] = $trackChangesView->hasInkAnnotations() ? 'true' : 'false'; + + $this->settings['w:revisionView'] = array('@attributes' => $revisionView); + } + } + + /** + * Sets the language + * + * @param Language $language + */ + private function setThemeFontLang(Language $language = null) + { + $latinLanguage = ($language == null || $language->getLatin() === null) ? 'en-US' : $language->getLatin(); + $lang = array(); + $lang['w:val'] = $latinLanguage; + if ($language != null) { + $lang['w:eastAsia'] = $language->getEastAsia() === null ? 'x-none' : $language->getEastAsia(); + $lang['w:bidi'] = $language->getBidirectional() === null ? 'x-none' : $language->getBidirectional(); + } + $this->settings['w:themeFontLang'] = array('@attributes' => $lang); + } + + /** + * Set the magnification + * + * @param mixed $zoom + */ + private function setZoom($zoom = null) + { + if ($zoom !== null) { + $attr = is_int($zoom) ? 'w:percent' : 'w:val'; + $this->settings['w:zoom'] = array('@attributes' => array($attr => $zoom)); + } + } + + /** + * @param int|null $consecutiveHyphenLimit + */ + private function setConsecutiveHyphenLimit($consecutiveHyphenLimit) + { + if ($consecutiveHyphenLimit === null) { + return; + } + + $this->settings['w:consecutiveHyphenLimit'] = array( + '@attributes' => array('w:val' => $consecutiveHyphenLimit), + ); + } + + /** + * @param float|null $hyphenationZone + */ + private function setHyphenationZone($hyphenationZone) + { + if ($hyphenationZone === null) { + return; + } + + $this->settings['w:hyphenationZone'] = array( + '@attributes' => array('w:val' => $hyphenationZone), + ); + } + + /** + * Get compatibility setting. + */ + private function setCompatibility() + { + $compatibility = $this->getParentWriter()->getPhpWord()->getCompatibility(); + if ($compatibility->getOoxmlVersion() !== null) { + $this->settings['w:compat']['w:compatSetting'] = array( + '@attributes' => array( + 'w:name' => 'compatibilityMode', + 'w:uri' => 'http://schemas.microsoft.com/office/word', + 'w:val' => $compatibility->getOoxmlVersion(), + ), + ); + } + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Styles.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Styles.php new file mode 100644 index 0000000..d05338c --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Styles.php @@ -0,0 +1,286 @@ +getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('w:styles'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + + // Write default styles + $styles = Style::getStyles(); + $this->writeDefaultStyles($xmlWriter, $styles); + + // Write styles + if (count($styles) > 0) { + foreach ($styles as $styleName => $style) { + if ($styleName == 'Normal') { + continue; + } + + // Get style class and execute if the private method exists + $styleClass = substr(get_class($style), strrpos(get_class($style), '\\') + 1); + $method = "write{$styleClass}Style"; + if (method_exists($this, $method)) { + $this->$method($xmlWriter, $styleName, $style); + } + } + } + + $xmlWriter->endElement(); // w:styles + + return $xmlWriter->getData(); + } + + /** + * Write default font and other default styles. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\AbstractStyle[] $styles + */ + private function writeDefaultStyles(XMLWriter $xmlWriter, $styles) + { + $phpWord = $this->getParentWriter()->getPhpWord(); + $fontName = $phpWord->getDefaultFontName(); + $fontSize = $phpWord->getDefaultFontSize(); + $language = $phpWord->getSettings()->getThemeFontLang(); + $latinLanguage = ($language == null || $language->getLatin() === null) ? 'en-US' : $language->getLatin(); + + // Default font + $xmlWriter->startElement('w:docDefaults'); + $xmlWriter->startElement('w:rPrDefault'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rFonts'); + $xmlWriter->writeAttribute('w:ascii', $fontName); + $xmlWriter->writeAttribute('w:hAnsi', $fontName); + $xmlWriter->writeAttribute('w:eastAsia', $fontName); + $xmlWriter->writeAttribute('w:cs', $fontName); + $xmlWriter->endElement(); // w:rFonts + $xmlWriter->startElement('w:sz'); + $xmlWriter->writeAttribute('w:val', $fontSize * 2); + $xmlWriter->endElement(); // w:sz + $xmlWriter->startElement('w:szCs'); + $xmlWriter->writeAttribute('w:val', $fontSize * 2); + $xmlWriter->endElement(); // w:szCs + $xmlWriter->startElement('w:lang'); + $xmlWriter->writeAttribute('w:val', $latinLanguage); + if ($language != null) { + $xmlWriter->writeAttributeIf($language->getEastAsia() !== null, 'w:eastAsia', $language->getEastAsia()); + $xmlWriter->writeAttributeIf($language->getBidirectional() !== null, 'w:bidi', $language->getBidirectional()); + } + $xmlWriter->endElement(); // w:lang + $xmlWriter->endElement(); // w:rPr + $xmlWriter->endElement(); // w:rPrDefault + $xmlWriter->endElement(); // w:docDefaults + + // Normal style + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', 'paragraph'); + $xmlWriter->writeAttribute('w:default', '1'); + $xmlWriter->writeAttribute('w:styleId', 'Normal'); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', 'Normal'); + $xmlWriter->endElement(); // w:name + if (isset($styles['Normal'])) { + $normalStyle = $styles['Normal']; + // w:pPr + if ($normalStyle instanceof Fontstyle && $normalStyle->getParagraph() != null) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $normalStyle->getParagraph()); + $styleWriter->write(); + } elseif ($normalStyle instanceof ParagraphStyle) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $normalStyle); + $styleWriter->write(); + } + + // w:rPr + $styleWriter = new FontStyleWriter($xmlWriter, $normalStyle); + $styleWriter->write(); + } + $xmlWriter->endElement(); // w:style + + // FootnoteReference style + if (!isset($styles['FootnoteReference'])) { + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', 'character'); + $xmlWriter->writeAttribute('w:styleId', 'FootnoteReference'); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', 'Footnote Reference'); + $xmlWriter->endElement(); // w:name + $xmlWriter->writeElement('w:semiHidden'); + $xmlWriter->writeElement('w:unhideWhenUsed'); + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:vertAlign'); + $xmlWriter->writeAttribute('w:val', 'superscript'); + $xmlWriter->endElement(); // w:vertAlign + $xmlWriter->endElement(); // w:rPr + $xmlWriter->endElement(); // w:style + } + } + + /** + * Write font style. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $styleName + * @param \PhpOffice\PhpWord\Style\Font $style + */ + private function writeFontStyle(XMLWriter $xmlWriter, $styleName, FontStyle $style) + { + $paragraphStyle = $style->getParagraph(); + $styleType = $style->getStyleType(); + $type = ($styleType == 'title') ? 'paragraph' : 'character'; + if (!is_null($paragraphStyle)) { + $type = 'paragraph'; + } + + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', $type); + + // Heading style + if ($styleType == 'title') { + $arrStyle = explode('_', $styleName); + if (count($arrStyle) > 1) { + $styleId = 'Heading' . $arrStyle[1]; + $styleName = 'heading ' . $arrStyle[1]; + $styleLink = 'Heading' . $arrStyle[1] . 'Char'; + } else { + $styleId = $styleName; + $styleName = strtolower($styleName); + $styleLink = $styleName . 'Char'; + } + $xmlWriter->writeAttribute('w:styleId', $styleId); + + $xmlWriter->startElement('w:link'); + $xmlWriter->writeAttribute('w:val', $styleLink); + $xmlWriter->endElement(); + } elseif (!is_null($paragraphStyle)) { + // if type is 'paragraph' it should have a styleId + $xmlWriter->writeAttribute('w:styleId', $styleName); + } + + // Style name + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', $styleName); + $xmlWriter->endElement(); + + // Parent style + if (!is_null($paragraphStyle)) { + if ($paragraphStyle->getStyleName() != null) { + $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getStyleName()); + } elseif ($paragraphStyle->getBasedOn() != null) { + $xmlWriter->writeElementBlock('w:basedOn', 'w:val', $paragraphStyle->getBasedOn()); + } + } + + // w:pPr + if (!is_null($paragraphStyle)) { + $styleWriter = new ParagraphStyleWriter($xmlWriter, $paragraphStyle); + $styleWriter->write(); + } + + // w:rPr + $styleWriter = new FontStyleWriter($xmlWriter, $style); + $styleWriter->write(); + + $xmlWriter->endElement(); + } + + /** + * Write paragraph style. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $styleName + * @param \PhpOffice\PhpWord\Style\Paragraph $style + */ + private function writeParagraphStyle(XMLWriter $xmlWriter, $styleName, ParagraphStyle $style) + { + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', 'paragraph'); + $xmlWriter->writeAttribute('w:customStyle', '1'); + $xmlWriter->writeAttribute('w:styleId', $styleName); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', $styleName); + $xmlWriter->endElement(); + + // Parent style + $basedOn = $style->getBasedOn(); + $xmlWriter->writeElementIf(!is_null($basedOn), 'w:basedOn', 'w:val', $basedOn); + + // Next paragraph style + $next = $style->getNext(); + $xmlWriter->writeElementIf(!is_null($next), 'w:next', 'w:val', $next); + + // w:pPr + $styleWriter = new ParagraphStyleWriter($xmlWriter, $style); + $styleWriter->write(); + + $xmlWriter->endElement(); + } + + /** + * Write table style. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $styleName + * @param \PhpOffice\PhpWord\Style\Table $style + */ + private function writeTableStyle(XMLWriter $xmlWriter, $styleName, TableStyle $style) + { + $xmlWriter->startElement('w:style'); + $xmlWriter->writeAttribute('w:type', 'table'); + $xmlWriter->writeAttribute('w:customStyle', '1'); + $xmlWriter->writeAttribute('w:styleId', $styleName); + $xmlWriter->startElement('w:name'); + $xmlWriter->writeAttribute('w:val', $styleName); + $xmlWriter->endElement(); + $xmlWriter->startElement('w:uiPriority'); + $xmlWriter->writeAttribute('w:val', '99'); + $xmlWriter->endElement(); + + $styleWriter = new TableStyleWriter($xmlWriter, $style); + $styleWriter->write(); + + $xmlWriter->endElement(); // w:style + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/Theme.php b/PhpOffice/PhpWord/Writer/Word2007/Part/Theme.php new file mode 100644 index 0000000..f4ef478 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/Theme.php @@ -0,0 +1,422 @@ +'; + $str .= ''; + $str .= ''; + $str .= $this->writeColorScheme(); + $str .= $this->writeFontScheme(); + $str .= $this->writeFormatScheme(); + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + return $str; + } + + /** + * Write color scheme + * + * @return string + */ + private function writeColorScheme() + { + $str = ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + return $str; + } + + /** + * Write font scheme + * + * @return string + */ + private function writeFontScheme() + { + $str = ''; + + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + $str .= ''; + + return $str; + } + + /** + * Write format scheme + * + * @return string + */ + private function writeFormatScheme() + { + $str = ''; + + $str .= ''; + $str .= $this->writeFormatFill(); + $str .= $this->writeFormatLine(); + $str .= $this->writeFormatEffect(); + $str .= $this->writeFormatBackground(); + $str .= ''; + + return $str; + } + + /** + * Write fill format scheme + * + * @return string + */ + private function writeFormatFill() + { + $str = ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + return $str; + } + + /** + * Write line format scheme + * + * @return string + */ + private function writeFormatLine() + { + $str = ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + return $str; + } + + /** + * Write effect format scheme + * + * @return string + */ + private function writeFormatEffect() + { + $str = ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + return $str; + } + + /** + * Write background format scheme + * + * @return string + */ + private function writeFormatBackground() + { + $str = ''; + + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + $str .= ''; + + return $str; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Part/WebSettings.php b/PhpOffice/PhpWord/Writer/Word2007/Part/WebSettings.php new file mode 100644 index 0000000..46252e8 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Part/WebSettings.php @@ -0,0 +1,51 @@ + '', + ); + + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startDocument('1.0', 'UTF-8', 'yes'); + $xmlWriter->startElement('w:webSettings'); + $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $xmlWriter->writeAttribute('xmlns:w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'); + + foreach ($settings as $settingKey => $settingValue) { + $this->writeSetting($xmlWriter, $settingKey, $settingValue); + } + + $xmlWriter->endElement(); // w:settings + + return $xmlWriter->getData(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/AbstractStyle.php b/PhpOffice/PhpWord/Writer/Word2007/Style/AbstractStyle.php new file mode 100644 index 0000000..fcd4aeb --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/AbstractStyle.php @@ -0,0 +1,156 @@ +xmlWriter = $xmlWriter; + $this->style = $style; + } + + /** + * Get XML Writer + * + * @return \PhpOffice\Common\XMLWriter + */ + protected function getXmlWriter() + { + return $this->xmlWriter; + } + + /** + * Get Style + * + * @return string|\PhpOffice\PhpWord\Style\AbstractStyle + */ + protected function getStyle() + { + return $this->style; + } + + /** + * Convert twip value + * + * @param int|float $value + * @param int $default (int|float) + * @return int|float + */ + protected function convertTwip($value, $default = 0) + { + $factors = array( + Settings::UNIT_CM => 567, + Settings::UNIT_MM => 56.7, + Settings::UNIT_INCH => 1440, + Settings::UNIT_POINT => 20, + Settings::UNIT_PICA => 240, + ); + $unit = Settings::getMeasurementUnit(); + $factor = 1; + if (in_array($unit, $factors) && $value != $default) { + $factor = $factors[$unit]; + } + + return $value * $factor; + } + + /** + * Write child style. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $name + * @param mixed $value + */ + protected function writeChildStyle(XMLWriter $xmlWriter, $name, $value) + { + if ($value !== null) { + $class = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Style\\' . $name; + + /** @var \PhpOffice\PhpWord\Writer\Word2007\Style\AbstractStyle $writer */ + $writer = new $class($xmlWriter, $value); + $writer->write(); + } + } + + /** + * Writes boolean as 0 or 1 + * + * @param bool $value + * @return null|string + */ + protected function writeOnOf($value = null) + { + if ($value === null) { + return null; + } + + return $value ? '1' : '0'; + } + + /** + * Assemble style array into style string + * + * @param array $styles + * @return string + */ + protected function assembleStyle($styles = array()) + { + $style = ''; + foreach ($styles as $key => $value) { + if (!is_null($value) && $value != '') { + $style .= "{$key}:{$value}; "; + } + } + + return trim($style); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Cell.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Cell.php new file mode 100644 index 0000000..733b7b4 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Cell.php @@ -0,0 +1,104 @@ +getStyle(); + if (!$style instanceof CellStyle) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('w:tcPr'); + + // Width + if (!is_null($this->width) || !is_null($style->getWidth())) { + $width = is_null($this->width) ? $style->getWidth() : $this->width; + + $xmlWriter->startElement('w:tcW'); + $xmlWriter->writeAttribute('w:w', $width); + $xmlWriter->writeAttribute('w:type', $style->getUnit()); + $xmlWriter->endElement(); // w:tcW + } + + // Text direction + $textDir = $style->getTextDirection(); + $xmlWriter->writeElementIf(!is_null($textDir), 'w:textDirection', 'w:val', $textDir); + + // Vertical alignment + $vAlign = $style->getVAlign(); + $xmlWriter->writeElementIf(!is_null($vAlign), 'w:vAlign', 'w:val', $vAlign); + + // Border + if ($style->hasBorder()) { + $xmlWriter->startElement('w:tcBorders'); + + $styleWriter = new MarginBorder($xmlWriter); + $styleWriter->setSizes($style->getBorderSize()); + $styleWriter->setColors($style->getBorderColor()); + $styleWriter->setStyles($style->getBorderStyle()); + $styleWriter->setAttributes(array('defaultColor' => CellStyle::DEFAULT_BORDER_COLOR)); + $styleWriter->write(); + + $xmlWriter->endElement(); + } + + // Shading + $shading = $style->getShading(); + if (!is_null($shading)) { + $styleWriter = new Shading($xmlWriter, $shading); + $styleWriter->write(); + } + + // Colspan & rowspan + $gridSpan = $style->getGridSpan(); + $vMerge = $style->getVMerge(); + $xmlWriter->writeElementIf(!is_null($gridSpan), 'w:gridSpan', 'w:val', $gridSpan); + $xmlWriter->writeElementIf(!is_null($vMerge), 'w:vMerge', 'w:val', $vMerge); + + $xmlWriter->endElement(); // w:tcPr + } + + /** + * Set width. + * + * @param int $value + */ + public function setWidth($value = null) + { + $this->width = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Extrusion.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Extrusion.php new file mode 100644 index 0000000..1939934 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Extrusion.php @@ -0,0 +1,44 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Extrusion) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('o:extrusion'); + $xmlWriter->writeAttribute('on', 't'); + $xmlWriter->writeAttributeIf($style->getType() !== null, 'type', $style->getType()); + $xmlWriter->writeAttributeIf($style->getColor() !== null, 'color', $style->getColor()); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Fill.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Fill.php new file mode 100644 index 0000000..53d0397 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Fill.php @@ -0,0 +1,41 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Fill) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->writeAttribute('on', 't'); + $xmlWriter->writeAttributeIf($style->getColor() !== null, 'fillcolor', $style->getColor()); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Font.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Font.php new file mode 100644 index 0000000..2f2218f --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Font.php @@ -0,0 +1,176 @@ +getXmlWriter(); + + $isStyleName = $this->isInline && !is_null($this->style) && is_string($this->style); + if ($isStyleName) { + $xmlWriter->startElement('w:rPr'); + $xmlWriter->startElement('w:rStyle'); + $xmlWriter->writeAttribute('w:val', $this->style); + $xmlWriter->endElement(); + $style = \PhpOffice\PhpWord\Style::getStyle($this->style); + if ($style instanceof \PhpOffice\PhpWord\Style\Font) { + $xmlWriter->writeElementIf($style->isRTL(), 'w:rtl'); + } + $xmlWriter->endElement(); + } else { + $this->writeStyle(); + } + } + + /** + * Write full style. + */ + private function writeStyle() + { + $style = $this->getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Font) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('w:rPr'); + + // Style name + if ($this->isInline === true) { + $styleName = $style->getStyleName(); + $xmlWriter->writeElementIf($styleName !== null, 'w:rStyle', 'w:val', $styleName); + } + + // Font name/family + $font = $style->getName(); + $hint = $style->getHint(); + if ($font !== null) { + $xmlWriter->startElement('w:rFonts'); + $xmlWriter->writeAttribute('w:ascii', $font); + $xmlWriter->writeAttribute('w:hAnsi', $font); + $xmlWriter->writeAttribute('w:eastAsia', $font); + $xmlWriter->writeAttribute('w:cs', $font); + $xmlWriter->writeAttributeIf($hint !== null, 'w:hint', $hint); + $xmlWriter->endElement(); + } + + //Language + $language = $style->getLang(); + if ($language != null && ($language->getLatin() !== null || $language->getEastAsia() !== null || $language->getBidirectional() !== null)) { + $xmlWriter->startElement('w:lang'); + $xmlWriter->writeAttributeIf($language->getLatin() !== null, 'w:val', $language->getLatin()); + $xmlWriter->writeAttributeIf($language->getEastAsia() !== null, 'w:eastAsia', $language->getEastAsia()); + $xmlWriter->writeAttributeIf($language->getBidirectional() !== null, 'w:bidi', $language->getBidirectional()); + //if bidi is not set but we are writing RTL, write the latin language in the bidi tag + if ($style->isRTL() && $language->getBidirectional() === null && $language->getLatin() !== null) { + $xmlWriter->writeAttribute('w:bidi', $language->getLatin()); + } + $xmlWriter->endElement(); + } + + // Color + $color = $style->getColor(); + $xmlWriter->writeElementIf($color !== null, 'w:color', 'w:val', $color); + + // Size + $size = $style->getSize(); + $xmlWriter->writeElementIf($size !== null, 'w:sz', 'w:val', $size * 2); + $xmlWriter->writeElementIf($size !== null, 'w:szCs', 'w:val', $size * 2); + + // Bold, italic + $xmlWriter->writeElementIf($style->isBold() !== null, 'w:b', 'w:val', $this->writeOnOf($style->isBold())); + $xmlWriter->writeElementIf($style->isBold() !== null, 'w:bCs', 'w:val', $this->writeOnOf($style->isBold())); + $xmlWriter->writeElementIf($style->isItalic() !== null, 'w:i', 'w:val', $this->writeOnOf($style->isItalic())); + $xmlWriter->writeElementIf($style->isItalic() !== null, 'w:iCs', 'w:val', $this->writeOnOf($style->isItalic())); + + // Strikethrough, double strikethrough + $xmlWriter->writeElementIf($style->isStrikethrough() !== null, 'w:strike', 'w:val', $this->writeOnOf($style->isStrikethrough())); + $xmlWriter->writeElementIf($style->isDoubleStrikethrough() !== null, 'w:dstrike', 'w:val', $this->writeOnOf($style->isDoubleStrikethrough())); + + // Small caps, all caps + $xmlWriter->writeElementIf($style->isSmallCaps() !== null, 'w:smallCaps', 'w:val', $this->writeOnOf($style->isSmallCaps())); + $xmlWriter->writeElementIf($style->isAllCaps() !== null, 'w:caps', 'w:val', $this->writeOnOf($style->isAllCaps())); + + //Hidden text + $xmlWriter->writeElementIf($style->isHidden(), 'w:vanish', 'w:val', $this->writeOnOf($style->isHidden())); + + // Underline + $xmlWriter->writeElementIf($style->getUnderline() != 'none', 'w:u', 'w:val', $style->getUnderline()); + + // Foreground-Color + $xmlWriter->writeElementIf($style->getFgColor() !== null, 'w:highlight', 'w:val', $style->getFgColor()); + + // Superscript/subscript + $xmlWriter->writeElementIf($style->isSuperScript(), 'w:vertAlign', 'w:val', 'superscript'); + $xmlWriter->writeElementIf($style->isSubScript(), 'w:vertAlign', 'w:val', 'subscript'); + + // Spacing + $xmlWriter->writeElementIf($style->getScale() !== null, 'w:w', 'w:val', $style->getScale()); + $xmlWriter->writeElementIf($style->getSpacing() !== null, 'w:spacing', 'w:val', $style->getSpacing()); + $xmlWriter->writeElementIf($style->getKerning() !== null, 'w:kern', 'w:val', $style->getKerning() * 2); + + // noProof + $xmlWriter->writeElementIf($style->isNoProof() !== null, 'w:noProof', 'w:val', $this->writeOnOf($style->isNoProof())); + + // Background-Color + $shading = $style->getShading(); + if (!is_null($shading)) { + $styleWriter = new Shading($xmlWriter, $shading); + $styleWriter->write(); + } + + // RTL + if ($this->isInline === true) { + $styleName = $style->getStyleName(); + $xmlWriter->writeElementIf($styleName === null && $style->isRTL(), 'w:rtl'); + } + + // Position + $xmlWriter->writeElementIf($style->getPosition() !== null, 'w:position', 'w:val', $style->getPosition()); + + $xmlWriter->endElement(); + } + + /** + * Set is inline. + * + * @param bool $value + */ + public function setIsInline($value) + { + $this->isInline = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Frame.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Frame.php new file mode 100644 index 0000000..10e5b15 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Frame.php @@ -0,0 +1,171 @@ +getStyle(); + if (!$style instanceof FrameStyle) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $maxZIndex = min(PHP_INT_MAX, self::PHP_32BIT_INT_MAX); + $zIndices = array(FrameStyle::WRAP_INFRONT => $maxZIndex, FrameStyle::WRAP_BEHIND => -$maxZIndex); + + $properties = array( + 'width' => 'width', + 'height' => 'height', + 'left' => 'margin-left', + 'top' => 'margin-top', + 'wrapDistanceTop' => 'mso-wrap-distance-top', + 'wrapDistanceBottom' => 'mso-wrap-distance-bottom', + 'wrapDistanceLeft' => 'mso-wrap-distance-left', + 'wrapDistanceRight' => 'mso-wrap-distance-right', + ); + $sizeStyles = $this->getStyles($style, $properties, $style->getUnit()); + + $properties = array( + 'pos' => 'position', + 'hPos' => 'mso-position-horizontal', + 'vPos' => 'mso-position-vertical', + 'hPosRelTo' => 'mso-position-horizontal-relative', + 'vPosRelTo' => 'mso-position-vertical-relative', + ); + $posStyles = $this->getStyles($style, $properties); + + $styles = array_merge($sizeStyles, $posStyles); + + // zIndex for infront & behind wrap + $wrap = $style->getWrap(); + if ($wrap !== null && isset($zIndices[$wrap])) { + $styles['z-index'] = $zIndices[$wrap]; + $wrap = null; + } + + // Style attribute + $xmlWriter->writeAttribute('style', $this->assembleStyle($styles)); + + $this->writeWrap($xmlWriter, $style, $wrap); + } + + /** + * Write alignment. + */ + public function writeAlignment() + { + $style = $this->getStyle(); + if (!$style instanceof FrameStyle) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:pPr'); + + if ('' !== $style->getAlignment()) { + $paragraphAlignment = new ParagraphAlignment($style->getAlignment()); + $xmlWriter->startElement($paragraphAlignment->getName()); + foreach ($paragraphAlignment->getAttributes() as $attributeName => $attributeValue) { + $xmlWriter->writeAttribute($attributeName, $attributeValue); + } + $xmlWriter->endElement(); + } + + $xmlWriter->endElement(); + } + + /** + * Write wrap. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Frame $style + * @param string $wrap + */ + private function writeWrap(XMLWriter $xmlWriter, FrameStyle $style, $wrap) + { + if ($wrap !== null) { + $xmlWriter->startElement('w10:wrap'); + $xmlWriter->writeAttribute('type', $wrap); + + $relativePositions = array( + FrameStyle::POS_RELTO_MARGIN => 'margin', + FrameStyle::POS_RELTO_PAGE => 'page', + FrameStyle::POS_RELTO_TMARGIN => 'margin', + FrameStyle::POS_RELTO_BMARGIN => 'page', + FrameStyle::POS_RELTO_LMARGIN => 'margin', + FrameStyle::POS_RELTO_RMARGIN => 'page', + ); + $pos = $style->getPos(); + $hPos = $style->getHPosRelTo(); + $vPos = $style->getVPosRelTo(); + + if ($pos == FrameStyle::POS_ABSOLUTE) { + $xmlWriter->writeAttribute('anchorx', 'page'); + $xmlWriter->writeAttribute('anchory', 'page'); + } elseif ($pos == FrameStyle::POS_RELATIVE) { + if (isset($relativePositions[$hPos])) { + $xmlWriter->writeAttribute('anchorx', $relativePositions[$hPos]); + } + if (isset($relativePositions[$vPos])) { + $xmlWriter->writeAttribute('anchory', $relativePositions[$vPos]); + } + } + + $xmlWriter->endElement(); // w10:wrap + } + } + + /** + * Get style values in associative array + * + * @param \PhpOffice\PhpWord\Style\Frame $style + * @param array $properties + * @param string $suffix + * @return array + */ + private function getStyles(FrameStyle $style, $properties, $suffix = '') + { + $styles = array(); + + foreach ($properties as $key => $property) { + $method = "get{$key}"; + $value = $style->$method(); + if ($value !== null) { + $styles[$property] = $style->$method() . $suffix; + } + } + + return $styles; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Image.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Image.php new file mode 100644 index 0000000..ef23ed1 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Image.php @@ -0,0 +1,27 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Indentation) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('w:ind'); + + $xmlWriter->writeAttribute('w:left', $this->convertTwip($style->getLeft())); + $xmlWriter->writeAttribute('w:right', $this->convertTwip($style->getRight())); + + $firstLine = $style->getFirstLine(); + $xmlWriter->writeAttributeIf(!is_null($firstLine), 'w:firstLine', $this->convertTwip($firstLine)); + + $hanging = $style->getHanging(); + $xmlWriter->writeAttributeIf(!is_null($hanging), 'w:hanging', $this->convertTwip($hanging)); + + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Line.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Line.php new file mode 100644 index 0000000..154a42c --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Line.php @@ -0,0 +1,68 @@ +getXmlWriter(); + $style = $this->getStyle(); + if (!$style instanceof LineStyle) { + return; + } + + $dash = $style->getDash(); + $dashStyles = array( + LineStyle::DASH_STYLE_DASH => 'dash', + LineStyle::DASH_STYLE_ROUND_DOT => '1 1', + LineStyle::DASH_STYLE_SQUARE_DOT => '1 1', + LineStyle::DASH_STYLE_DASH_DOT => 'dashDot', + LineStyle::DASH_STYLE_LONG_DASH => 'longDash', + LineStyle::DASH_STYLE_LONG_DASH_DOT => 'longDashDot', + LineStyle::DASH_STYLE_LONG_DASH_DOT_DOT => 'longDashDotDot', + ); + + $xmlWriter->startElement('v:stroke'); + + $xmlWriter->writeAttributeIf($style->getWeight() !== null, 'weight', $style->getWeight() . 'pt'); + $xmlWriter->writeAttributeIf($style->getColor() !== null, 'color', $style->getColor()); + $xmlWriter->writeAttributeIf($style->getBeginArrow() !== null, 'startarrow', $style->getBeginArrow()); + $xmlWriter->writeAttributeIf($style->getEndArrow() !== null, 'endarrow', $style->getEndArrow()); + + if ($dash !== null) { + if (isset($dashStyles[$dash])) { + $xmlWriter->writeAttribute('dashstyle', $dashStyles[$dash]); + } + if ($dash == LineStyle::DASH_STYLE_ROUND_DOT) { + $xmlWriter->writeAttribute('endcap', 'round'); + } + } + + $xmlWriter->endElement(); //v:stroke + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/LineNumbering.php b/PhpOffice/PhpWord/Writer/Word2007/Style/LineNumbering.php new file mode 100644 index 0000000..4bf08b6 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/LineNumbering.php @@ -0,0 +1,46 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\LineNumbering) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('w:lnNumType'); + $xmlWriter->writeAttribute('w:start', $style->getStart() - 1); + $xmlWriter->writeAttribute('w:countBy', $style->getIncrement()); + $xmlWriter->writeAttribute('w:distance', $style->getDistance()); + $xmlWriter->writeAttribute('w:restart', $style->getRestart()); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/MarginBorder.php b/PhpOffice/PhpWord/Writer/Word2007/Style/MarginBorder.php new file mode 100644 index 0000000..f5c4b01 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/MarginBorder.php @@ -0,0 +1,150 @@ +getXmlWriter(); + + $sides = array('top', 'left', 'right', 'bottom', 'insideH', 'insideV'); + + foreach ($this->sizes as $i => $size) { + if ($size !== null) { + $color = null; + if (isset($this->colors[$i])) { + $color = $this->colors[$i]; + } + $style = isset($this->styles[$i]) ? $this->styles[$i] : 'single'; + $this->writeSide($xmlWriter, $sides[$i], $this->sizes[$i], $color, $style); + } + } + } + + /** + * Write side. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $side + * @param int $width + * @param string $color + * @param string $borderStyle + */ + private function writeSide(XMLWriter $xmlWriter, $side, $width, $color = null, $borderStyle = 'solid') + { + $xmlWriter->startElement('w:' . $side); + if (!empty($this->colors)) { + if ($color === null && !empty($this->attributes)) { + if (isset($this->attributes['defaultColor'])) { + $color = $this->attributes['defaultColor']; + } + } + $xmlWriter->writeAttribute('w:val', $borderStyle); + $xmlWriter->writeAttribute('w:sz', $width); + $xmlWriter->writeAttributeIf($color != null, 'w:color', $color); + if (!empty($this->attributes)) { + if (isset($this->attributes['space'])) { + $xmlWriter->writeAttribute('w:space', $this->attributes['space']); + } + } + } else { + $xmlWriter->writeAttribute('w:w', $width); + $xmlWriter->writeAttribute('w:type', 'dxa'); + } + $xmlWriter->endElement(); + } + + /** + * Set sizes. + * + * @param int[] $value + */ + public function setSizes($value) + { + $this->sizes = $value; + } + + /** + * Set colors. + * + * @param string[] $value + */ + public function setColors($value) + { + $this->colors = $value; + } + + /** + * Set border styles. + * + * @param string[] $value + */ + public function setStyles($value) + { + $this->styles = $value; + } + + /** + * Set attributes. + * + * @param array $value + */ + public function setAttributes($value) + { + $this->attributes = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Outline.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Outline.php new file mode 100644 index 0000000..ae4c1da --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Outline.php @@ -0,0 +1,49 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Outline) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('v:stroke'); + $xmlWriter->writeAttribute('on', 't'); + $xmlWriter->writeAttributeIf($style->getColor() !== null, 'color', $style->getColor()); + $xmlWriter->writeAttributeIf($style->getWeight() !== null, 'weight', $style->getWeight() . $style->getUnit()); + $xmlWriter->writeAttributeIf($style->getDash() !== null, 'dashstyle', $style->getDash()); + $xmlWriter->writeAttributeIf($style->getLine() !== null, 'linestyle', $style->getLine()); + $xmlWriter->writeAttributeIf($style->getEndCap() !== null, 'endcap', $style->getEndCap()); + $xmlWriter->writeAttributeIf($style->getStartArrow() !== null, 'startarrow', $style->getStartArrow()); + $xmlWriter->writeAttributeIf($style->getEndArrow() !== null, 'endarrow', $style->getEndArrow()); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Paragraph.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Paragraph.php new file mode 100644 index 0000000..6761608 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Paragraph.php @@ -0,0 +1,212 @@ +getXmlWriter(); + + $isStyleName = $this->isInline && !is_null($this->style) && is_string($this->style); + if ($isStyleName) { + if (!$this->withoutPPR) { + $xmlWriter->startElement('w:pPr'); + } + $xmlWriter->startElement('w:pStyle'); + $xmlWriter->writeAttribute('w:val', $this->style); + $xmlWriter->endElement(); + if (!$this->withoutPPR) { + $xmlWriter->endElement(); + } + } else { + $this->writeStyle(); + } + } + + /** + * Write full style. + */ + private function writeStyle() + { + $style = $this->getStyle(); + if (!$style instanceof ParagraphStyle) { + return; + } + $xmlWriter = $this->getXmlWriter(); + $styles = $style->getStyleValues(); + + if (!$this->withoutPPR) { + $xmlWriter->startElement('w:pPr'); + } + + // Style name + if ($this->isInline === true) { + $xmlWriter->writeElementIf($styles['name'] !== null, 'w:pStyle', 'w:val', $styles['name']); + } + + // Pagination + $xmlWriter->writeElementIf($styles['pagination']['widowControl'] === false, 'w:widowControl', 'w:val', '0'); + $xmlWriter->writeElementIf($styles['pagination']['keepNext'] === true, 'w:keepNext', 'w:val', '1'); + $xmlWriter->writeElementIf($styles['pagination']['keepLines'] === true, 'w:keepLines', 'w:val', '1'); + $xmlWriter->writeElementIf($styles['pagination']['pageBreak'] === true, 'w:pageBreakBefore', 'w:val', '1'); + + // Paragraph alignment + if ('' !== $styles['alignment']) { + $paragraphAlignment = new ParagraphAlignment($styles['alignment']); + $xmlWriter->startElement($paragraphAlignment->getName()); + foreach ($paragraphAlignment->getAttributes() as $attributeName => $attributeValue) { + $xmlWriter->writeAttribute($attributeName, $attributeValue); + } + $xmlWriter->endElement(); + } + + //Right to left + $xmlWriter->writeElementIf($styles['bidi'] === true, 'w:bidi'); + + //Paragraph contextualSpacing + $xmlWriter->writeElementIf($styles['contextualSpacing'] === true, 'w:contextualSpacing'); + + //Paragraph textAlignment + $xmlWriter->writeElementIf($styles['textAlignment'] !== null, 'w:textAlignment', 'w:val', $styles['textAlignment']); + + // Hyphenation + $xmlWriter->writeElementIf($styles['suppressAutoHyphens'] === true, 'w:suppressAutoHyphens'); + + // Child style: alignment, indentation, spacing, and shading + $this->writeChildStyle($xmlWriter, 'Indentation', $styles['indentation']); + $this->writeChildStyle($xmlWriter, 'Spacing', $styles['spacing']); + $this->writeChildStyle($xmlWriter, 'Shading', $styles['shading']); + + // Tabs + $this->writeTabs($xmlWriter, $styles['tabs']); + + // Numbering + $this->writeNumbering($xmlWriter, $styles['numbering']); + + // Border + if ($style->hasBorder()) { + $xmlWriter->startElement('w:pBdr'); + + $styleWriter = new MarginBorder($xmlWriter); + $styleWriter->setSizes($style->getBorderSize()); + $styleWriter->setColors($style->getBorderColor()); + $styleWriter->write(); + + $xmlWriter->endElement(); + } + + if (!$this->withoutPPR) { + $xmlWriter->endElement(); // w:pPr + } + } + + /** + * Write tabs. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Tab[] $tabs + */ + private function writeTabs(XMLWriter $xmlWriter, $tabs) + { + if (!empty($tabs)) { + $xmlWriter->startElement('w:tabs'); + foreach ($tabs as $tab) { + $styleWriter = new Tab($xmlWriter, $tab); + $styleWriter->write(); + } + $xmlWriter->endElement(); + } + } + + /** + * Write numbering. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param array $numbering + */ + private function writeNumbering(XMLWriter $xmlWriter, $numbering) + { + $numStyle = $numbering['style']; + $numLevel = $numbering['level']; + + /** @var \PhpOffice\PhpWord\Style\Numbering $numbering */ + $numbering = Style::getStyle($numStyle); + if ($numStyle !== null && $numbering !== null) { + $xmlWriter->startElement('w:numPr'); + $xmlWriter->startElement('w:numId'); + $xmlWriter->writeAttribute('w:val', $numbering->getIndex()); + $xmlWriter->endElement(); // w:numId + $xmlWriter->startElement('w:ilvl'); + $xmlWriter->writeAttribute('w:val', $numLevel); + $xmlWriter->endElement(); // w:ilvl + $xmlWriter->endElement(); // w:numPr + + $xmlWriter->startElement('w:outlineLvl'); + $xmlWriter->writeAttribute('w:val', $numLevel); + $xmlWriter->endElement(); // w:outlineLvl + } + } + + /** + * Set without w:pPr. + * + * @param bool $value + */ + public function setWithoutPPR($value) + { + $this->withoutPPR = $value; + } + + /** + * Set is inline. + * + * @param bool $value + */ + public function setIsInline($value) + { + $this->isInline = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Row.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Row.php new file mode 100644 index 0000000..82028d2 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Row.php @@ -0,0 +1,66 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Row) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:trPr'); + + if ($this->height !== null) { + $xmlWriter->startElement('w:trHeight'); + $xmlWriter->writeAttribute('w:val', $this->height); + $xmlWriter->writeAttribute('w:hRule', ($style->isExactHeight() ? 'exact' : 'atLeast')); + $xmlWriter->endElement(); + } + $xmlWriter->writeElementIf($style->isTblHeader(), 'w:tblHeader', 'w:val', '1'); + $xmlWriter->writeElementIf($style->isCantSplit(), 'w:cantSplit', 'w:val', '1'); + + $xmlWriter->endElement(); // w:trPr + } + + /** + * Set height. + * + * @param int $value + */ + public function setHeight($value = null) + { + $this->height = $value; + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Section.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Section.php new file mode 100644 index 0000000..1122b6f --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Section.php @@ -0,0 +1,101 @@ +getStyle(); + if (!$style instanceof SectionStyle) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + // Break type + $breakType = $style->getBreakType(); + $xmlWriter->writeElementIf(!is_null($breakType), 'w:type', 'w:val', $breakType); + + // Page size & orientation + $xmlWriter->startElement('w:pgSz'); + $xmlWriter->writeAttribute('w:orient', $style->getOrientation()); + $xmlWriter->writeAttribute('w:w', $style->getPageSizeW()); + $xmlWriter->writeAttribute('w:h', $style->getPageSizeH()); + $xmlWriter->endElement(); // w:pgSz + + // Vertical alignment + $vAlign = $style->getVAlign(); + $xmlWriter->writeElementIf(!is_null($vAlign), 'w:vAlign', 'w:val', $vAlign); + + // Margins + $margins = array( + 'w:top' => array('getMarginTop', SectionStyle::DEFAULT_MARGIN), + 'w:right' => array('getMarginRight', SectionStyle::DEFAULT_MARGIN), + 'w:bottom' => array('getMarginBottom', SectionStyle::DEFAULT_MARGIN), + 'w:left' => array('getMarginLeft', SectionStyle::DEFAULT_MARGIN), + 'w:header' => array('getHeaderHeight', SectionStyle::DEFAULT_HEADER_HEIGHT), + 'w:footer' => array('getFooterHeight', SectionStyle::DEFAULT_FOOTER_HEIGHT), + 'w:gutter' => array('getGutter', SectionStyle::DEFAULT_GUTTER), + ); + $xmlWriter->startElement('w:pgMar'); + foreach ($margins as $attribute => $value) { + list($method, $default) = $value; + $xmlWriter->writeAttribute($attribute, $this->convertTwip($style->$method(), $default)); + } + $xmlWriter->endElement(); + + // Borders + if ($style->hasBorder()) { + $xmlWriter->startElement('w:pgBorders'); + $xmlWriter->writeAttribute('w:offsetFrom', 'page'); + + $styleWriter = new MarginBorder($xmlWriter); + $styleWriter->setSizes($style->getBorderSize()); + $styleWriter->setColors($style->getBorderColor()); + $styleWriter->setAttributes(array('space' => '24')); + $styleWriter->write(); + + $xmlWriter->endElement(); + } + + // Columns + $colsSpace = $style->getColsSpace(); + $xmlWriter->startElement('w:cols'); + $xmlWriter->writeAttribute('w:num', $style->getColsNum()); + $xmlWriter->writeAttribute('w:space', $this->convertTwip($colsSpace, SectionStyle::DEFAULT_COLUMN_SPACING)); + $xmlWriter->endElement(); + + // Page numbering start + $pageNum = $style->getPageNumberingStart(); + $xmlWriter->writeElementIf(!is_null($pageNum), 'w:pgNumType', 'w:start', $pageNum); + + // Line numbering + $styleWriter = new LineNumbering($xmlWriter, $style->getLineNumbering()); + $styleWriter->write(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Shading.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Shading.php new file mode 100644 index 0000000..0f9d6cc --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Shading.php @@ -0,0 +1,44 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Shading) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('w:shd'); + $xmlWriter->writeAttributeIf(!is_null($style->getPattern()), 'w:val', $style->getPattern()); + $xmlWriter->writeAttributeIf(!is_null($style->getColor()), 'w:color', $style->getColor()); + $xmlWriter->writeAttributeIf(!is_null($style->getFill()), 'w:fill', $style->getFill()); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Shadow.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Shadow.php new file mode 100644 index 0000000..7fcb12a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Shadow.php @@ -0,0 +1,44 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Shadow) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('v:shadow'); + $xmlWriter->writeAttribute('on', 't'); + $xmlWriter->writeAttributeIf($style->getColor() !== null, 'color', $style->getColor()); + $xmlWriter->writeAttributeIf($style->getOffset() !== null, 'offset', $style->getOffset()); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Shape.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Shape.php new file mode 100644 index 0000000..2def684 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Shape.php @@ -0,0 +1,45 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Shape) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + + $childStyles = array('Frame', 'Fill', 'Outline', 'Shadow', 'Extrusion'); + foreach ($childStyles as $childStyle) { + $method = "get{$childStyle}"; + $this->writeChildStyle($xmlWriter, $childStyle, $style->$method()); + } + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Spacing.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Spacing.php new file mode 100644 index 0000000..fdfb89a --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Spacing.php @@ -0,0 +1,57 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Spacing) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('w:spacing'); + + $before = $style->getBefore(); + $xmlWriter->writeAttributeIf(!is_null($before), 'w:before', $this->convertTwip($before)); + + $after = $style->getAfter(); + $xmlWriter->writeAttributeIf(!is_null($after), 'w:after', $this->convertTwip($after)); + + $line = $style->getLine(); + //if linerule is auto, the spacing is supposed to include the height of the line itself, which is 240 twips + if (null !== $line && 'auto' === $style->getLineRule()) { + $line += \PhpOffice\PhpWord\Style\Paragraph::LINE_HEIGHT; + } + $xmlWriter->writeAttributeIf(!is_null($line), 'w:line', $line); + + $xmlWriter->writeAttributeIf(!is_null($line), 'w:lineRule', $style->getLineRule()); + + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Tab.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Tab.php new file mode 100644 index 0000000..b41653f --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Tab.php @@ -0,0 +1,44 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\Tab) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('w:tab'); + $xmlWriter->writeAttribute('w:val', $style->getType()); + $xmlWriter->writeAttribute('w:leader', $style->getLeader()); + $xmlWriter->writeAttribute('w:pos', $this->convertTwip($style->getPosition())); + $xmlWriter->endElement(); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/Table.php b/PhpOffice/PhpWord/Writer/Word2007/Style/Table.php new file mode 100644 index 0000000..443d670 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/Table.php @@ -0,0 +1,238 @@ +getStyle(); + $xmlWriter = $this->getXmlWriter(); + + if ($style instanceof TableStyle) { + $this->writeStyle($xmlWriter, $style); + } elseif (is_string($style)) { + $xmlWriter->startElement('w:tblPr'); + $xmlWriter->startElement('w:tblStyle'); + $xmlWriter->writeAttribute('w:val', $style); + $xmlWriter->endElement(); + if (null !== $this->width) { + $this->writeTblWidth($xmlWriter, 'w:tblW', TblWidth::PERCENT, $this->width); + } + $xmlWriter->endElement(); + } + } + + /** + * Write full style. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Table $style + */ + private function writeStyle(XMLWriter $xmlWriter, TableStyle $style) + { + // w:tblPr + $xmlWriter->startElement('w:tblPr'); + + // Table alignment + if ('' !== $style->getAlignment()) { + $tableAlignment = new TableAlignment($style->getAlignment()); + $xmlWriter->startElement($tableAlignment->getName()); + foreach ($tableAlignment->getAttributes() as $attributeName => $attributeValue) { + $xmlWriter->writeAttribute($attributeName, $attributeValue); + } + $xmlWriter->endElement(); + } + + $this->writeTblWidth($xmlWriter, 'w:tblW', $style->getUnit(), $style->getWidth()); + $this->writeTblWidth($xmlWriter, 'w:tblCellSpacing', TblWidth::TWIP, $style->getCellSpacing()); + $this->writeIndent($xmlWriter, $style); + $this->writeLayout($xmlWriter, $style->getLayout()); + + // Position + $styleWriter = new TablePosition($xmlWriter, $style->getPosition()); + $styleWriter->write(); + + //Right to left + $xmlWriter->writeElementIf($style->isBidiVisual() !== null, 'w:bidiVisual', 'w:val', $this->writeOnOf($style->isBidiVisual())); + + $this->writeMargin($xmlWriter, $style); + $this->writeBorder($xmlWriter, $style); + + $xmlWriter->endElement(); // w:tblPr + + $this->writeShading($xmlWriter, $style); + + // First row style + $firstRow = $style->getFirstRow(); + if ($firstRow instanceof TableStyle) { + $this->writeFirstRow($xmlWriter, $firstRow); + } + } + + /** + * Enable/Disable automatic resizing of the table + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $layout autofit / fixed + */ + private function writeLayout(XMLWriter $xmlWriter, $layout) + { + $xmlWriter->startElement('w:tblLayout'); + $xmlWriter->writeAttribute('w:type', $layout); + $xmlWriter->endElement(); // w:tblLayout + } + + /** + * Write margin. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Table $style + */ + private function writeMargin(XMLWriter $xmlWriter, TableStyle $style) + { + if ($style->hasMargin()) { + $xmlWriter->startElement('w:tblCellMar'); + + $styleWriter = new MarginBorder($xmlWriter); + $styleWriter->setSizes($style->getCellMargin()); + $styleWriter->write(); + + $xmlWriter->endElement(); // w:tblCellMar + } + } + + /** + * Write border. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Table $style + */ + private function writeBorder(XMLWriter $xmlWriter, TableStyle $style) + { + if ($style->hasBorder()) { + $xmlWriter->startElement('w:tblBorders'); + + $styleWriter = new MarginBorder($xmlWriter); + $styleWriter->setSizes($style->getBorderSize()); + $styleWriter->setColors($style->getBorderColor()); + $styleWriter->write(); + + $xmlWriter->endElement(); // w:tblBorders + } + } + + /** + * Writes a table width + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param string $elementName + * @param string $unit + * @param int|float $width + */ + private function writeTblWidth(XMLWriter $xmlWriter, $elementName, $unit, $width = null) + { + if (null === $width) { + return; + } + $xmlWriter->startElement($elementName); + $xmlWriter->writeAttributeIf(null !== $width, 'w:w', $width); + $xmlWriter->writeAttribute('w:type', $unit); + $xmlWriter->endElement(); + } + + /** + * Write row style. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Table $style + */ + private function writeFirstRow(XMLWriter $xmlWriter, TableStyle $style) + { + $xmlWriter->startElement('w:tblStylePr'); + $xmlWriter->writeAttribute('w:type', 'firstRow'); + $xmlWriter->startElement('w:tcPr'); + + $this->writeBorder($xmlWriter, $style); + $this->writeShading($xmlWriter, $style); + + $xmlWriter->endElement(); // w:tcPr + $xmlWriter->endElement(); // w:tblStylePr + } + + /** + * Write shading. + * + * @param \PhpOffice\Common\XMLWriter $xmlWriter + * @param \PhpOffice\PhpWord\Style\Table $style + */ + private function writeShading(XMLWriter $xmlWriter, TableStyle $style) + { + if (null !== $style->getShading()) { + $xmlWriter->startElement('w:tcPr'); + + $styleWriter = new Shading($xmlWriter, $style->getShading()); + $styleWriter->write(); + + $xmlWriter->endElement(); + } + } + + /** + * Set width. + * + * @param int $value + */ + public function setWidth($value = null) + { + $this->width = $value; + } + + /** + * @param XMLWriter $xmlWriter + * @param TableStyle $style + */ + private function writeIndent(XMLWriter $xmlWriter, TableStyle $style) + { + $indent = $style->getIndent(); + + if ($indent === null) { + return; + } + + $this->writeTblWidth($xmlWriter, 'w:tblInd', $indent->getType(), $indent->getValue()); + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/TablePosition.php b/PhpOffice/PhpWord/Writer/Word2007/Style/TablePosition.php new file mode 100644 index 0000000..fa57b93 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/TablePosition.php @@ -0,0 +1,65 @@ +getStyle(); + if (!$style instanceof \PhpOffice\PhpWord\Style\TablePosition) { + return; + } + + $values = array(); + $properties = array( + 'leftFromText', + 'rightFromText', + 'topFromText', + 'bottomFromText', + 'vertAnchor', + 'horzAnchor', + 'tblpXSpec', + 'tblpX', + 'tblpYSpec', + 'tblpY', + ); + foreach ($properties as $property) { + $method = 'get' . $property; + if (method_exists($style, $method)) { + $values[$property] = $style->$method(); + } + } + $values = array_filter($values); + + if ($values) { + $xmlWriter = $this->getXmlWriter(); + $xmlWriter->startElement('w:tblpPr'); + foreach ($values as $property => $value) { + $xmlWriter->writeAttribute('w:' . $property, $value); + } + $xmlWriter->endElement(); + } + } +} diff --git a/PhpOffice/PhpWord/Writer/Word2007/Style/TextBox.php b/PhpOffice/PhpWord/Writer/Word2007/Style/TextBox.php new file mode 100644 index 0000000..627d0c8 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/Word2007/Style/TextBox.php @@ -0,0 +1,61 @@ +getStyle(); + if (!$style instanceof TextBoxStyle || !$style->hasInnerMargins()) { + return; + } + + $xmlWriter = $this->getXmlWriter(); + $margins = implode(', ', $style->getInnerMargin()); + + $xmlWriter->writeAttribute('inset', $margins); + } + + /** + * Writer border. + */ + public function writeBorder() + { + $style = $this->getStyle(); + if (!$style instanceof TextBoxStyle) { + return; + } + $xmlWriter = $this->getXmlWriter(); + + $xmlWriter->startElement('v:stroke'); + $xmlWriter->writeAttributeIf($style->getBorderSize() !== null, 'weight', $style->getBorderSize() . 'pt'); + $xmlWriter->writeAttributeIf($style->getBorderColor() !== null, 'color', $style->getBorderColor()); + $xmlWriter->endElement(); // v:stroke + } +} diff --git a/PhpOffice/PhpWord/Writer/WriterInterface.php b/PhpOffice/PhpWord/Writer/WriterInterface.php new file mode 100644 index 0000000..499cde3 --- /dev/null +++ b/PhpOffice/PhpWord/Writer/WriterInterface.php @@ -0,0 +1,31 @@ +0{{R3N2#!80000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzQ&3D)MgRZ*0002t=Ji~3tGCGXHb!Y? zeY1m{+VJxFdy&6FRe$>X{`>s?{r>+{Y@~07x=L=rNOQ~tHG)iIn>S#t05NwhRIBRl z_y8eM=}aaf{Nayy{$g%gNH>D_Nxe|Nj6SMq`QM_4oXlvF-Hs`~3a>MO=(m zebHZq-6cqOIAXL%cG+c!+caRpt-8>Qq}@YlxrwFeQFh7zC0adWzw-3@6+2aCiq(6U z*cLo)c8Z+V-0DANw4k@}J5qNCJBTk)k0wisIcT_&qqQhfpZNLxF#dt- z_uX#SR(tRN|I)^<+{jmyZq)XA`T5O~lY>H!qAd!i^Y)K@VAX7ctD^`7k~{HkL|E zAcb-R(47;o6|y+{sa3R45JD71HAGR@7vpf|$v;_R?$z8jK%$t8Ml}-0yi}1Q1F|SK z>Z~0Q98y&Cl7CKr6~(_~;5#16{y257P9lZUBVG(^>rst~(_T%ivv^?lCYIT4yT77^ z)ChzyJzC`31&XQ%S!RV-Lf38D9_VwF;tOQ)LP?kSQAWz_(zH8`7OM2129Hy^qE>1t zp4+>Q)8P#jEhHPzBAFC z$Yh$WTgOFYr;tLvG7i5N_^adN!t%;?=Im@Oe(SghA7bbT?|%RImsdW~ba9tV${^%My7w!<$0EU(Az z^f?eP@4dSVdY3S_00XAk8zV43K27RFz3TvAz70U;N&WN8ohL$rgS!8>vW(~d29G>vf?KbCZ<^iBfdBg+S#J!O^9loZ)5&~}hRr$*06U-Xms&}joSa@!{{$ES XtjuZObd?ae00000NkvXXu0mjf9*{Wp literal 0 HcmV?d00001 diff --git a/PhpOffice/PhpWord/resources/ppt.png b/PhpOffice/PhpWord/resources/ppt.png new file mode 100644 index 0000000000000000000000000000000000000000..3e925f50199cf96e881dee05455b410fcc282731 GIT binary patch literal 1886 zcmV-k2ch_hP)0{{R3N2#!80000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#08mU+MgHT}{J4`=3l5Y_PyNxl|M>L& z>*4*!s;emi|Mc&CLQc?fRsZbb{J)<5-p=8CT>QYHHw6p-`uOdVZSI#@{@cm__VezG zROXUu|K{0_NlpLgx#W&%(_<;@mTfg0dJ4Of%qf{&CtBn2Az3Gi<++`{M z@8$pg{n&6(xit#IJ`S!*HfRwK>6LD)MKZ!+NXteO_MdtG_3-`6v63|{{JWURYEJ+E z|C~@yg+@-gRzT&1V*SLX{lcWydRzYK-~Z#&?PVta`S$(Hv;Xqz|Lx?(WlQ0SX5)ch z%49h1gh&77*#G0y{@23&*24YNzHSu}f<#T}i)Ztib@G^V!CpnYTSM-VN&VHq?t@d_ zhGW@(UEX?FkvTHfZcof_QqyNic|uV1oOiNPG~9Mp)oV@ZVI$sG9@tVF&q)=&It(oY z3$-!_u`UH*4h;YR0RQ~@|Nj24UPJ%!>i_of|K-=AMl$`+xBukU|LNWT|Nj5{``KzP z|LEMaPB{OsasTMHyj&^KWk>ppOXPGr{FGAYr-uIF((r97uSPiMoOjG&DdTN0w^Tm= z)S~~`rsaG_|K_stbTapTKIf!@^LaPtpnU(xlmE_}sZ}ekSS$ajXyt)V|Dj>!k7NJ+ z{Qtaz{h3+oVkG96aKUq0|K6(jg+>3vi{*t?<%(SYw|V~L*5hd{|K--?a5db5VB~l~ z{m!-jsB(`icXt#Q<8O7fe0 z>4|RQfMn!@Vg1dtfJ9FI=-vM5-7>lgfdBvl^hrcPRCwBA@TL=B7`15BqEU;6n?-Ur zPg#0w=i)_cR`1@jZ}R$$TW3z+ zwS48WIiZ?Vw#e1BEImU*SlA-%mH%&k{(_3$LkkWlsH&b(NujPq$I@G^&A`mo53HP5*tFh>CbB4~T2b4g=$#qL*0aD& zYauap#o+yXO3B>{R}_7YJ5bf4yv1^xH?Q#4(K&bT?wubR*MENg#eedTrB$R)HFYgI zyhymeWtI0MV3xfd^yC&W!vgcGyJaMg;R)(ml)naO(L4~VKg=y6@;?KglKD|RSspgS znkK4R6tvpn#ii|_7`nvDsmi7`TQrHqon4m4h*`U-m6{ek*`0PtNW$BjrLC~=u_q)c z=&{c?GGeZ!u8+94yb{vip88wupG#HOANGfAj))J=aOb`_@s{qO6(E-P~p75+g=g#dw5jHU;B{x(q8qJ_JPo~bWrftCu1 z;H2QmXxh`m)gVY!i;S07s<$6dP*AY6l$D)tU@%o)e%)GA(}V=BM(SELY328bc2!j? z#Yi4DBctAl=OraO5B4M^gp@W4_EFO!`(-8Vs#g>hd4Trxg6xU!>;%~pQp!Ohscg!!W`DC<4e9 z!9|f&BKfGfxf$6XP>7@$jfpCU;v<~$49GExZc#HvSQ2j$LN5^(VZ=0PKJq4D5jt-6 z#ui83-q_qiAdb8-a|3D`!VNvsF={;)Es6|8EB7gt` Y0LsdDm^Yzu*8l(j07*qoM6N<$f~$WKuK)l5 literal 0 HcmV?d00001 diff --git a/PhpOffice/PhpWord/resources/xls.png b/PhpOffice/PhpWord/resources/xls.png new file mode 100644 index 0000000000000000000000000000000000000000..a7d7697fc5666629b9d8bb3090339c5f557536b5 GIT binary patch literal 1924 zcmV-~2YdL5P)0{{R3N2#!80000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#08mU+MKVMjJ8dS}_T8hyou0{*wA87C zmvPhL)a~~1NrWgzm_<~EN>Exj*6P;k^5g?(0%wzE$L6|btYq}{@H&V*R)JDem^j_+ z;>+g3+2z`es)5Pf!teX;s?(l1cNJY^MNEetb|F z_5Ar)lT>TAX7ly=6eI}5-n?LlN-8xJ@Ksom<`OJX!7Iw5nmXINZ8)#lgP=-T-H z_fm^EUusEIeL9cJfqS}c&f3#-onmB;QswdHe5GrFyn9TKLm_Ygc$Zv6j3xT{`dgzx z;O*m`(U@PDSx1sKc(!zQvvlh6>Iz~6PMKF?s#O2~`;w@1e7bvKqEmjTX;z;-=k4a& z?b+$--CLwo|N8!Ro@n~~`IEVge6er&`t^IJaay2A=JD!;vT}&5f>o(hcD7nJZy3+& z&incPkj05>m01d66;+~A_WbpAy=rooZu9!^aGPIMrcG?9V|KD^<>%t!=iGXtWI=r* z+Uebp!i8g+UrTU2ZM0tg{``WidE)Qh|NsALY*rsA4BP3`Rbf2m^Y8lq{NL^0BrFsz zK_+IZUg7fMXp&Vrf*=+h3?(%XH%=e`007+T+u`r#=kMd?_T%mE>WjU8fV_6~{qXMc z=|E;C$l=An>%iCV*Uje0&g;L}?cCq&+TP~k{rdIh_~LT9Wa<6v8z2+Z-`8QGTl)0( zZK6;O+D8ZtY>$yY?;iD|Nj42rdl6+k>n z^yc*EnZuFR=H>nU|IzHp{Qv)dlxP3_|9Ze^ki3Ea{QWLJ81wk}?EUrj_w;3;C`$@AU0!xN!LV?n)1(VE_OG8c9S!RCwBAm`NwVFly1LMWYtc$)al+ z8QJI0_xEQ9^%n(ob~XhEFV)xAH`V9%^HcgmO^c@Q-1$ag>kd6VJ!4kZ$FeLeFF&f8 z1eA8)?oL}tRUcJbT(z_;DRFePoGx{>TI$jzYwMHNZ133EBA-yzqPONh9n)W@f0~wF zmj1M(qxf5yy71;3!khUvYh+Q^N9Gcz?#}%4cK(m~#hRL5bacLa;V?^n@q5Wy(->-7 z%j~;YnH-Y*OzAsyEh?8NF`It7XYsB0$u~P>4(PQDOb)iS zwPniH$XR)pnihrXo%T)=eC*Wd6eb|~El^;8z()6bqAAZbmPtiiLyWo>1+iBDpRe5DkAjF zzC2$lThz%~?H&E?fbtzKJ*UOpLRq5C(kg~)d&R}&eLhpwq9)dAow7~eWa{sS8><~l zyLWGjvyo9R@Aiqa6bz_q(c|jYXpZPTT3!6%vKw2vx@uCU*ama&dF30mg1Q#PN$r*4 zP`2PINJ(JLY_aJ|OPL}XY`ZH@p2|L2dgr+CS7qMR(y0afTya$u_qba46(*eF<@Jp+ zplUu^>U3O3XAaj?lK`O>{)D*O4i4c7&34R^_Otw`>!aYt<6p#6-7_GGt9`?2H92t5>h~^78a# zx3@Utae=x%@~eHfe*OB~+}yQm7r3}g^zxFFWcOmXueS$UL`@&1o4U8Z;CnO!2zKY? z@q&RTyQk_7Rr$K*RP~W*caf5kk_Hg@LV$;dhrEZ!TzQ@?)V0X(=(!jm$jK2C6Z26} zP~ha8FhLFo>gpn>YmvrEFkk?I&ju?%VD@a?g$H$kkeU_&n~rbMh5mqvKSVb1M>}Gp z7L8g&GmBP%0ah7=3MB3-9I6OggwtI}Ca=P+kW`DHMnN6F3SlywTs0Fe3iIPCIFke) zt%4W^MKB84A}sQ&NbwOgj#i