Passed
Pull Request — master (#2)
by
unknown
04:03
created

DocxDocument::__construct()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 7.3471

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 5
eloc 11
c 1
b 0
f 1
nc 5
nop 2
dl 0
loc 18
ccs 6
cts 11
cp 0.5455
crap 7.3471
rs 9.6111
1
<?php
2
3
namespace PhpDocxTemplate;
4
5
use DOMDocument;
6
use DOMElement;
7
use Exception;
8
use ZipArchive;
9
use RecursiveIteratorIterator;
10
use RecursiveDirectoryIterator;
11
12
/**
13
 * Class DocxDocument
14
 *
15
 * @package PhpDocxTemplate
16
 */
17
class DocxDocument
18
{
19
    private $path;
20
    private $tmpDir = "./tmp/";
21
    private $document;
22
23
    /**
24
     * Construct an instance of Document
25
     *
26
     * @param string $path - path to the document
27
     * @param string $tmpPath - path to the temp directory
28
     *
29
     * @throws Exception
30
     */
31 8
    public function __construct(string $path, ?string $tmpPath = null)
32
    {
33 8
        if (!empty($tmpPath)) {
34
            if (!is_dir($tmpPath) && !mkdir($tmpPath, 0777, true)) {
35
                throw new Exception(
36
                    "The specified path \"" . $tmpPath . "\" for \"temp\" folder is not valid"
37
                );
38
            }
39
40
            $this->tmpDir = $tmpPath . "/";
41
        }
42
43 8
        if (file_exists($path)) {
44 8
            $this->path = $path;
45 8
            $this->tmpDir .= uniqid("", true) . date("His");
46 8
            $this->extract();
47
        } else {
48
            throw new Exception("The template " . $path . " was not found!");
49
        }
50 8
    }
51
52
    /**
53
     * Extract (unzip) document contents
54
     */
55 8
    private function extract(): void
56
    {
57 8
        if (file_exists($this->tmpDir) && is_dir($this->tmpDir)) {
58
            $this->rrmdir($this->tmpDir);
59
        }
60
61 8
        mkdir($this->tmpDir);
62
63 8
        $zip = new ZipArchive();
64 8
        $zip->open($this->path);
65 8
        $zip->extractTo($this->tmpDir);
66 8
        $zip->close();
67
68 8
        $this->document = file_get_contents($this->tmpDir . "/word/document.xml");
69 8
    }
70
71
    /**
72
     * Get document.xml contents as DOMDocument
73
     *
74
     * @return DOMDocument
75
     */
76 5
    public function getDOMDocument(): DOMDocument
77
    {
78 5
        $dom = new DOMDocument();
79 5
        $dom->loadXML($this->document);
80 5
        return $dom;
81
    }
82
83
    /**
84
     * Update document.xml contents
85
     *
86
     * @param DOMDocument $dom - new contents
87
     */
88 3
    public function updateDOMDocument(DOMDocument $dom): void
89
    {
90 3
        $this->document = $dom->saveXml();
91 3
        file_put_contents($this->tmpDir . "/word/document.xml", $this->document);
92 3
    }
93
94
    /**
95
     * Fix table corruption
96
     *
97
     * @param string $xml - xml to fix
98
     *
99
     * @return DOMDocument
100
     */
101 3
    public function fixTables(string $xml): DOMDocument
102
    {
103 3
        $dom = new DOMDocument();
104 3
        $dom->loadXML($xml);
105 3
        $tables = $dom->getElementsByTagName('tbl');
106 3
        foreach ($tables as $table) {
107 1
            $columns = [];
108 1
            $columnsLen = 0;
109 1
            $toAdd = 0;
110 1
            $tableGrid = null;
111 1
            foreach ($table->childNodes as $el) {
112 1
                if ($el->nodeName == 'w:tblGrid') {
113 1
                    $tableGrid = $el;
114 1
                    foreach ($el->childNodes as $col) {
115 1
                        if ($col->nodeName == 'w:gridCol') {
116 1
                            $columns[] = $col;
117 1
                            $columnsLen += 1;
118
                        }
119
                    }
120 1
                } elseif ($el->nodeName == 'w:tr') {
121 1
                    $cellsLen = 0;
122 1
                    foreach ($el->childNodes as $col) {
123 1
                        if ($col->nodeName == 'w:tc') {
124 1
                            $cellsLen += 1;
125
                        }
126
                    }
127 1
                    if (($columnsLen + $toAdd) < $cellsLen) {
128 1
                        $toAdd = $cellsLen - $columnsLen;
129
                    }
130
                }
131
            }
132
133
            // add columns, if necessary
134 1
            if (!is_null($tableGrid) && $toAdd > 0) {
135
                $width = 0;
136
                foreach ($columns as $col) {
137
                    if (!is_null($col->getAttribute('w:w'))) {
138
                        $width += $col->getAttribute('w:w');
139
                    }
140
                }
141
                if ($width > 0) {
142
                    $oldAverage = $width / $columnsLen;
143
                    $newAverage = round($width / ($columnsLen + $toAdd));
144
                    foreach ($columns as $col) {
145
                        $col->setAttribute('w:w', round($col->getAttribute('w:w') * $newAverage / $oldAverage));
146
                    }
147
                    while ($toAdd > 0) {
148
                        $newCol = $dom->createElement("w:gridCol");
149
                        $newCol->setAttribute('w:w', $newAverage);
150
                        $tableGrid->appendChild($newCol);
151
                        $toAdd -= 1;
152
                    }
153
                }
154
            }
155
156
            // remove columns, if necessary
157 1
            $columns = [];
158 1
            foreach ($tableGrid->childNodes as $col) {
159 1
                if ($col->nodeName == 'w:gridCol') {
160 1
                    $columns[] = $col;
161
                }
162
            }
163 1
            $columnsLen = count($columns);
164
165 1
            $cellsLen = 0;
166 1
            $cellsLenMax = 0;
167 1
            foreach ($table->childNodes as $el) {
168 1
                if ($el->nodeName == 'w:tr') {
169 1
                    $cells = [];
170 1
                    foreach ($el->childNodes as $col) {
171 1
                        if ($col->nodeName == 'w:tc') {
172 1
                            $cells[] = $col;
173
                        }
174
                    }
175 1
                    $cellsLen = $this->getCellLen($cells);
176 1
                    $cellsLenMax = max($cellsLenMax, $cellsLen);
177
                }
178
            }
179 1
            $toRemove = $cellsLen - $cellsLenMax;
180 1
            if ($toRemove > 0) {
181
                $removedWidth = 0.0;
182
                for ($i = $columnsLen - 1; ($i + 1) >= $toRemove; $i -= 1) {
183
                    $extraCol = $columns[$i];
184
                    $removedWidth += $extraCol->getAttribute('w:w');
185
                    $tableGrid->removeChild($extraCol);
186
                }
187
188
                $columnsLeft = [];
189
                foreach ($tableGrid->childNodes as $col) {
190
                    if ($col->nodeName == 'w:gridCol') {
191
                        $columnsLeft[] = $col;
192
                    }
193
                }
194
                $extraSpace = 0;
195
                if (count($columnsLeft) > 0) {
196
                    $extraSpace = $removedWidth / count($columnsLeft);
197
                }
198
                foreach ($columnsLeft as $col) {
199 1
                    $col->setAttribute('w:w', round($col->getAttribute('w:w') + $extraSpace));
200
                }
201
            }
202
        }
203 3
        return $dom;
204
    }
205
206
    /**
207
     * Get total cells length
208
     *
209
     * @param array $cells - cells
210
     *
211
     * @return int
212
     */
213 1
    private function getCellLen(array $cells): int
214
    {
215 1
        $total = 0;
216 1
        foreach ($cells as $cell) {
217 1
            foreach ($cell->childNodes as $tc) {
218 1
                if ($tc->nodeName == 'w:tcPr') {
219 1
                    foreach ($tc->childNodes as $span) {
220 1
                        if ($span->nodeName == 'w:gridSpan') {
221 1
                            $total += intval($span->getAttribute('w:val'));
222 1
                            break;
223
                        }
224
                    }
225 1
                    break;
226
                }
227
            }
228
        }
229 1
        return $total + 1;
230
    }
231
232
    /**
233
     * Save the document to the target path
234
     *
235
     * @param string $path - target path
236
     */
237 1
    public function save(string $path): void
238
    {
239 1
        $rootPath = realpath($this->tmpDir);
240
241 1
        $zip = new ZipArchive();
242 1
        $zip->open($path, ZipArchive::CREATE | ZipArchive::OVERWRITE);
243
244 1
        $files = new RecursiveIteratorIterator(
245 1
            new RecursiveDirectoryIterator($rootPath),
246 1
            RecursiveIteratorIterator::LEAVES_ONLY
247
        );
248
249 1
        foreach ($files as $name => $file) {
250 1
            if (!$file->isDir()) {
251 1
                $filePath = $file->getRealPath();
252 1
                $relativePath = substr($filePath, strlen($rootPath) + 1);
253 1
                $zip->addFile($filePath, $relativePath);
254
            }
255
        }
256
257 1
        $zip->close();
258
259 1
        $this->rrmdir($this->tmpDir);
260 1
    }
261
262
    /**
263
     * Remove recursively directory
264
     *
265
     * @param string $dir - target directory
266
     */
267 6
    private function rrmdir(string $dir): void
268
    {
269 6
        $objects = scandir($dir);
270 6
        if (is_array($objects)) {
271 6
            foreach ($objects as $object) {
272 6
                if ($object != "." && $object != "..") {
273 6
                    if (filetype($dir . "/" . $object) == "dir") {
274 6
                        $this->rrmdir($dir . "/" . $object);
275
                    } else {
276 6
                        unlink($dir . "/" . $object);
277
                    }
278
                }
279
            }
280 6
            reset($objects);
281 6
            rmdir($dir);
282
        }
283 6
    }
284
285
    /**
286
     * Close document
287
     */
288 5
    public function close(): void
289
    {
290 5
        $this->rrmdir($this->tmpDir);
291 5
    }
292
}
293